Match inners with outers

After thinking about how the `evenodd` fill rule works, I pretty sure
we don't actually need to do this. If I'm right, I'll back it out, and
it will be in the history if we need it for some other purpose.
This commit is contained in:
John Firebaugh
2013-01-13 18:17:16 -08:00
parent 5089e2370b
commit 1d707b0eb2
3 changed files with 157 additions and 36 deletions

View File

@@ -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;

View File

@@ -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);
});
};

View File

@@ -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]]]);
});
});
});