mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-14 13:18:15 +02:00
Start on multipolygon reconstruction algorithm
This commit is contained in:
@@ -8,5 +8,68 @@ iD.Relation = iD.Entity.extend({
|
||||
|
||||
geometry: function() {
|
||||
return 'relation';
|
||||
},
|
||||
|
||||
// Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm],
|
||||
// where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings.
|
||||
//
|
||||
// This corresponds to the structure needed for rendering a multipolygon path using a
|
||||
// `evenodd` fill rule, as well as the structure of a GeoJSON MultiPolygon geometry.
|
||||
//
|
||||
// In the case of invalid geometries, this function will still return a result which
|
||||
// includes the nodes of all way members, but some Nds may be unclosed and some inner
|
||||
// rings not matched with the intended outer ring.
|
||||
//
|
||||
multipolygon: function(resolver) {
|
||||
var members = this.members
|
||||
.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'; });
|
||||
|
||||
var result = [], current, first, last, i, how, what;
|
||||
|
||||
while (outers.length) {
|
||||
current = outers.pop().nodes.slice();
|
||||
result.push([current]);
|
||||
|
||||
while (outers.length && _.first(current) !== _.last(current)) {
|
||||
first = _.first(current);
|
||||
last = _.last(current);
|
||||
|
||||
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 (!what)
|
||||
break; // Invalid geometry (unclosed ring)
|
||||
|
||||
outers.splice(i, 1);
|
||||
how.apply(current, what);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,4 +38,121 @@ describe('iD.Relation', function () {
|
||||
describe("#extent", function () {
|
||||
it("returns the minimal extent containing the extents of all members");
|
||||
});
|
||||
|
||||
describe("#multipolygon", function () {
|
||||
specify("single polygon consisting of a single way", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
|
||||
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, w, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
|
||||
});
|
||||
|
||||
specify("single polygon consisting of multiple ways", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
w2 = iD.Way({nodes: [c.id, d.id, a.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
|
||||
});
|
||||
|
||||
specify("single polygon consisting of multiple ways, one needing reversal", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
w2 = iD.Way({nodes: [a.id, d.id, c.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
|
||||
});
|
||||
|
||||
specify("multiple polygons consisting of single ways", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
e = iD.Node(),
|
||||
f = iD.Node(),
|
||||
w1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
|
||||
w2 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, e, f, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, a]], [[d, e, f, d]]]);
|
||||
});
|
||||
|
||||
specify("invalid geometry: unclosed ring consisting of a single way", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
w = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, w, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c]]]);
|
||||
});
|
||||
|
||||
specify("invalid geometry: unclosed ring consisting of multiple ways", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
w2 = iD.Way({nodes: [c.id, d.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
|
||||
});
|
||||
|
||||
specify("invalid geometry: unclosed ring consisting of multiple ways, alternate order", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [c.id, d.id]}),
|
||||
w2 = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
|
||||
});
|
||||
|
||||
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
|
||||
w2 = iD.Way({nodes: [d.id, c.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
|
||||
});
|
||||
|
||||
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal, alternate order", function () {
|
||||
var a = iD.Node(),
|
||||
b = iD.Node(),
|
||||
c = iD.Node(),
|
||||
d = iD.Node(),
|
||||
w1 = iD.Way({nodes: [c.id, d.id]}),
|
||||
w2 = iD.Way({nodes: [c.id, b.id, a.id]}),
|
||||
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
|
||||
g = iD.Graph([a, b, c, d, w1, w2, r]);
|
||||
|
||||
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user