diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js index 08c2a27a5..4788ad0d1 100644 --- a/js/id/graph/relation.js +++ b/js/id/graph/relation.js @@ -25,49 +25,80 @@ iD.Relation = iD.Entity.extend({ .filter(function (m) { return m.type === 'way'; }) .map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.fetch(m.id).nodes }; }); - var outers = members.filter(function (m) { return m.role === 'outer'; }), - inners = members.filter(function (m) { return m.role === 'inner'; }); + function join(ways) { + var joined = [], current, first, last, i, how, what; - var result = [], current, first, last, i, how, what; + while (ways.length) { + current = ways.pop().nodes.slice(); + joined.push(current); - while (outers.length) { - current = outers.pop().nodes.slice(); - result.push([current]); + while (ways.length && _.first(current) !== _.last(current)) { + first = _.first(current); + last = _.last(current); - while (outers.length && _.first(current) !== _.last(current)) { - first = _.first(current); - last = _.last(current); + for (i = 0; i < ways.length; i++) { + what = ways[i].nodes; - for (i = 0; i < outers.length; i++) { - what = outers[i].nodes; - - if (last === _.first(what)) { - how = current.push; - what = what.slice(1); - break; - } else if (last === _.last(what)) { - how = current.push; - what = what.slice(0, -1).reverse(); - break; - } else if (first == _.last(what)) { - how = current.unshift; - what = what.slice(0, -1); - break; - } else if (first == _.first(what)) { - how = current.unshift; - what = what.slice(1).reverse(); - break; - } else { - what = how = null; + if (last === _.first(what)) { + how = current.push; + what = what.slice(1); + break; + } else if (last === _.last(what)) { + how = current.push; + what = what.slice(0, -1).reverse(); + break; + } else if (first == _.last(what)) { + how = current.unshift; + what = what.slice(0, -1); + break; + } else if (first == _.first(what)) { + how = current.unshift; + what = what.slice(1).reverse(); + break; + } else { + what = how = null; + } } + + if (!what) + break; // Invalid geometry (unclosed ring) + + ways.splice(i, 1); + how.apply(current, what); } - - if (!what) - break; // Invalid geometry (unclosed ring) - - outers.splice(i, 1); - how.apply(current, what); } + + return joined; + } + + function findOuter(inner) { + var o, outer; + + inner = _.pluck(inner, 'loc'); + + for (o = 0; o < outers.length; o++) { + outer = _.pluck(outers[o], 'loc'); + if (iD.util.geo.polygonContainsPolygon(outer, inner)) + return o; + } + + for (o = 0; o < outers.length; o++) { + outer = _.pluck(outers[o], 'loc'); + if (iD.util.geo.polygonIntersectsPolygon(outer, inner)) + return o; + } + } + + var outers = join(members.filter(function (m) { return m.role === 'outer'; })), + inners = join(members.filter(function (m) { return m.role === 'inner'; })), + result = outers.map(function (o) { return [o]; }); + + for (var i = 0; i < inners.length; i++) { + var o = findOuter(inners[i]); + if (o !== undefined) + result[o].push(inners[i]); + else + result.push(inners[i]); // Invalid geometry } return result; diff --git a/js/id/util.js b/js/id/util.js index 20cb722dc..0ce60c2bd 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -133,3 +133,15 @@ iD.util.geo.pointInPolygon = function(point, polygon) { return inside; }; + +iD.util.geo.polygonContainsPolygon = function(outer, inner) { + return _.every(inner, function (point) { + return iD.util.geo.pointInPolygon(point, outer); + }); +}; + +iD.util.geo.polygonIntersectsPolygon = function(outer, inner) { + return _.some(inner, function (point) { + return iD.util.geo.pointInPolygon(point, outer); + }); +}; diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js index 128c12a2a..26a1a2022 100644 --- a/test/spec/graph/relation.js +++ b/test/spec/graph/relation.js @@ -154,5 +154,83 @@ describe('iD.Relation', function () { expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]); }); + + specify("single polygon with single single-way inner", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [1, 0]}), + c = iD.Node({loc: [0, 1]}), + d = iD.Node({loc: [0.1, 0.1]}), + e = iD.Node({loc: [0.2, 0.1]}), + f = iD.Node({loc: [0.1, 0.2]}), + outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + inner = iD.Way({nodes: [d.id, e.id, f.id, d.id]}), + r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}), + g = iD.Graph([a, b, c, d, e, f, outer, inner, r]); + + expect(r.multipolygon(g)).to.eql([[[a, b, c, a], [d, e, f, d]]]); + }); + + specify("single polygon with single multi-way inner", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [1, 0]}), + c = iD.Node({loc: [0, 1]}), + d = iD.Node({loc: [0.1, 0.1]}), + e = iD.Node({loc: [0.2, 0.1]}), + f = iD.Node({loc: [0.2, 0.1]}), + outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + inner1 = iD.Way({nodes: [d.id, e.id]}), + inner2 = iD.Way({nodes: [e.id, f.id, d.id]}), + r = iD.Relation({members: [ + {id: outer.id, type: 'way'}, + {id: inner2.id, role: 'inner', type: 'way'}, + {id: inner1.id, role: 'inner', type: 'way'}]}), + graph = iD.Graph([a, b, c, d, e, f, outer, inner1, inner2, r]); + + expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]]]); + }); + + specify("single polygon with multiple single-way inners", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [1, 0]}), + c = iD.Node({loc: [0, 1]}), + d = iD.Node({loc: [0.1, 0.1]}), + e = iD.Node({loc: [0.2, 0.1]}), + f = iD.Node({loc: [0.1, 0.2]}), + g = iD.Node({loc: [0.2, 0.2]}), + h = iD.Node({loc: [0.3, 0.2]}), + i = iD.Node({loc: [0.2, 0.3]}), + outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + inner1 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}), + inner2 = iD.Way({nodes: [g.id, h.id, i.id, g.id]}), + r = iD.Relation({members: [ + {id: outer.id, type: 'way'}, + {id: inner2.id, role: 'inner', type: 'way'}, + {id: inner1.id, role: 'inner', type: 'way'}]}), + graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer, inner1, inner2, r]); + + expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d], [g, h, i, g]]]); + }); + + specify("multiple polygons with single single-way inner", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [1, 0]}), + c = iD.Node({loc: [0, 1]}), + d = iD.Node({loc: [0.1, 0.1]}), + e = iD.Node({loc: [0.2, 0.1]}), + f = iD.Node({loc: [0.1, 0.2]}), + g = iD.Node({loc: [0, 0]}), + h = iD.Node({loc: [-1, 0]}), + i = iD.Node({loc: [0, -1]}), + outer1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + outer2 = iD.Way({nodes: [g.id, h.id, i.id, g.id]}), + inner = iD.Way({nodes: [d.id, e.id, f.id, d.id]}), + r = iD.Relation({members: [ + {id: outer2.id, type: 'way'}, + {id: outer1.id, type: 'way'}, + {id: inner.id, role: 'inner', type: 'way'}]}), + graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer1, outer2, inner, r]); + + expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]], [[g, h, i, g]]]); + }); }); });