diff --git a/js/id/core/entity.js b/js/id/core/entity.js index 3769f8fc0..e8181da16 100644 --- a/js/id/core/entity.js +++ b/js/id/core/entity.js @@ -34,6 +34,10 @@ iD.Entity.key = function(entity) { return entity.id + ',' + entity.v; }; +iD.Entity.areaPath = d3.geo.path() + .projection(d3.geo.mercator() + .scale(12016420.517592335)); + iD.Entity.prototype = { tags: {}, @@ -95,9 +99,12 @@ iD.Entity.prototype = { return this.extent(resolver).intersects(extent); }, - area: function(resolver, path) { + // Returns the (possibly negative) area of the entity in square pixels at an + // arbitrary unspecified zoom level -- so basically, only useful for relative + // comparisons. + area: function(resolver) { return resolver.transient(this, 'area', function() { - return path.area(this); + return iD.Entity.areaPath.area(this.asGeoJSON(resolver, true)); }); }, diff --git a/js/id/core/way.js b/js/id/core/way.js index 34ac0339f..ddfb164e9 100644 --- a/js/id/core/way.js +++ b/js/id/core/way.js @@ -143,7 +143,7 @@ _.extend(iD.Way.prototype, { childnodes = childnodes.concat([childnodes[0]]); } - if (this.isArea() && (close || this.isClosed())) { + if (this.isArea() && childnodes.length >= 4 && (close || this.isClosed())) { return { type: 'Feature', properties: this.tags, diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index 7d3277233..b051c7b04 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -37,12 +37,12 @@ iD.svg.Areas = function(projection) { if (multipolygon = iD.geo.isSimpleMultipolygonOuterMember(entity, graph)) { areas[multipolygon.id] = { entity: multipolygon.mergeTags(entity.tags), - area: Math.abs(entity.area(graph, path)) + area: Math.abs(entity.area(graph)) }; } else if (!areas[entity.id]) { areas[entity.id] = { entity: entity, - area: Math.abs(entity.area(graph, path)) + area: Math.abs(entity.area(graph)) }; } } diff --git a/test/spec/core/entity.js b/test/spec/core/entity.js index 2190d7125..ef2cee6e5 100644 --- a/test/spec/core/entity.js +++ b/test/spec/core/entity.js @@ -144,7 +144,7 @@ describe('iD.Entity', function () { }); }); - describe("#hasDeprecatedTags", function () { + describe("#hasDeprecatedTags", function () { it("returns false if entity has no tags", function () { expect(iD.Entity().deprecatedTags()).to.eql({}); }); @@ -152,7 +152,7 @@ describe('iD.Entity', function () { it("returns true if entity has deprecated tags", function () { expect(iD.Entity({ tags: { barrier: 'wire_fence' } }).deprecatedTags()).to.eql({ barrier: 'wire_fence' }); }); - }); + }); describe("#hasInterestingTags", function () { it("returns false if the entity has no tags", function () { @@ -171,4 +171,42 @@ describe('iD.Entity', function () { expect(iD.Entity({tags: {'tiger:source': 'blah', 'tiger:foo': 'bar'}}).hasInterestingTags()).to.equal(false); }); }); + + describe("#area", function() { + it("returns a relative measure of area", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [-0.0002, 0.0001]}), + 'b': iD.Node({id: 'b', loc: [ 0.0002, 0.0001]}), + 'c': iD.Node({id: 'c', loc: [ 0.0002, -0.0001]}), + 'd': iD.Node({id: 'd', loc: [-0.0002, -0.0001]}), + 'e': iD.Node({id: 'a', loc: [-0.0004, 0.0002]}), + 'f': iD.Node({id: 'b', loc: [ 0.0004, 0.0002]}), + 'g': iD.Node({id: 'c', loc: [ 0.0004, -0.0002]}), + 'h': iD.Node({id: 'd', loc: [-0.0004, -0.0002]}), + 's': iD.Way({id: 's', tags: {area: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}), + 'l': iD.Way({id: 'l', tags: {area: 'yes'}, nodes: ['e', 'f', 'g', 'h', 'e']}) + }); + + var s = Math.abs(graph.entity('s').area(graph)), + l = Math.abs(graph.entity('l').area(graph)); + + expect(s).to.be.lt(l); + }); + + it("returns 0 for degenerate areas", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [-0.0002, 0.0001]}), + 'b': iD.Node({id: 'b', loc: [ 0.0002, 0.0001]}), + 'c': iD.Node({id: 'c', loc: [ 0.0002, -0.0001]}), + 'd': iD.Node({id: 'd', loc: [-0.0002, -0.0001]}), + '0': iD.Way({id: '0', tags: {area: 'yes'}, nodes: []}), + '1': iD.Way({id: '1', tags: {area: 'yes'}, nodes: ['a']}), + '2': iD.Way({id: '2', tags: {area: 'yes'}, nodes: ['a', 'b']}) + }); + + expect(graph.entity('0').area(graph)).to.equal(0); + expect(graph.entity('1').area(graph)).to.equal(0); + expect(graph.entity('2').area(graph)).to.equal(0); + }); + }); }); diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index bc14243e0..cebaff9bf 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -53,12 +53,16 @@ describe("iD.svg.Areas", function () { it("stacks smaller areas above larger ones", function () { var graph = iD.Graph({ - 'a': iD.Node({id: 'a', loc: [0, 0]}), - 'b': iD.Node({id: 'b', loc: [1, 0]}), - 'c': iD.Node({id: 'c', loc: [1, 1]}), - 'd': iD.Node({id: 'd', loc: [0, 1]}), - 's': iD.Way({id: 's', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}), - 'l': iD.Way({id: 'l', tags: {landuse: 'park'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + 'a': iD.Node({id: 'a', loc: [-0.0002, 0.0001]}), + 'b': iD.Node({id: 'b', loc: [ 0.0002, 0.0001]}), + 'c': iD.Node({id: 'c', loc: [ 0.0002, -0.0001]}), + 'd': iD.Node({id: 'd', loc: [-0.0002, -0.0001]}), + 'e': iD.Node({id: 'a', loc: [-0.0004, 0.0002]}), + 'f': iD.Node({id: 'b', loc: [ 0.0004, 0.0002]}), + 'g': iD.Node({id: 'c', loc: [ 0.0004, -0.0002]}), + 'h': iD.Node({id: 'd', loc: [-0.0004, -0.0002]}), + 's': iD.Way({id: 's', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}), + 'l': iD.Way({id: 'l', tags: {landuse: 'park'}, nodes: ['e', 'f', 'g', 'h', 'e']}) }), areas = [graph.entity('s'), graph.entity('l')];