mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-28 19:01:31 +02:00
Prefer to join member ways in a way that preserves their order
(re #4589) Strongly prefer to generate a forward path that preserves the order of the members array. For multipolygons and most relations, member order does not matter - but for routes, it does. If we started this sequence backwards (i.e. next member way attaches to the start node and not the end node), reverse the initial way before continuing.
This commit is contained in:
+122
-39
@@ -82,13 +82,7 @@ export function osmSimpleMultipolygonOuterMember(entity, graph) {
|
||||
// Incomplete members (those for which `graph.hasEntity(element.id)` returns
|
||||
// false) and non-way members are ignored.
|
||||
//
|
||||
export function osmJoinWays(array, graph) {
|
||||
var joined = [], member, current, nodes, first, last, i, how, what;
|
||||
|
||||
array = array.filter(function(member) {
|
||||
return member.type === 'way' && graph.hasEntity(member.id);
|
||||
});
|
||||
|
||||
export function osmJoinWays(toJoin, graph) {
|
||||
function resolve(member) {
|
||||
return graph.childNodes(graph.entity(member.id));
|
||||
}
|
||||
@@ -97,52 +91,141 @@ export function osmJoinWays(array, graph) {
|
||||
return member.tags ? actionReverse(member.id, { reverseOneway: true })(graph).entity(member.id) : member;
|
||||
}
|
||||
|
||||
while (array.length) {
|
||||
member = array.shift();
|
||||
current = [member];
|
||||
current.nodes = nodes = resolve(member).slice();
|
||||
joined.push(current);
|
||||
|
||||
while (array.length && nodes[0] !== nodes[nodes.length - 1]) {
|
||||
first = nodes[0];
|
||||
last = nodes[nodes.length - 1];
|
||||
// make a copy containing only the ways to join
|
||||
toJoin = toJoin.filter(function(member) {
|
||||
return member.type === 'way' && graph.hasEntity(member.id);
|
||||
});
|
||||
|
||||
for (i = 0; i < array.length; i++) {
|
||||
member = array[i];
|
||||
what = resolve(member);
|
||||
var sequences = [];
|
||||
|
||||
if (last === what[0]) {
|
||||
how = nodes.push;
|
||||
what = what.slice(1);
|
||||
while (toJoin.length) {
|
||||
// start a new sequence
|
||||
var way = toJoin.shift();
|
||||
var currWays = [way];
|
||||
var currNodes = resolve(way).slice();
|
||||
var doneSequence = false;
|
||||
|
||||
// add to it
|
||||
while (toJoin.length && !doneSequence) {
|
||||
var start = currNodes[0];
|
||||
var end = currNodes[currNodes.length - 1];
|
||||
var fn = null;
|
||||
var nodes = null;
|
||||
var i;
|
||||
|
||||
// find the next way
|
||||
for (i = 0; i < toJoin.length; i++) {
|
||||
way = toJoin[i];
|
||||
nodes = resolve(way);
|
||||
|
||||
// Strongly prefer to generate a forward path that preserves the order
|
||||
// of the members array. For multipolygons and most relations, member
|
||||
// order does not matter - but for routes, it does. If we started this
|
||||
// sequence backwards (i.e. next member way attaches to the start node
|
||||
// and not the end node), reverse the initial way before continuing.
|
||||
if (currWays.length === 1 && nodes[0] !== end && nodes[nodes.length - 1] !== end &&
|
||||
(nodes[nodes.length - 1] === start || nodes[0] === start)
|
||||
) {
|
||||
currWays[0] = reverse(currWays[0]);
|
||||
currNodes.reverse();
|
||||
start = currNodes[0];
|
||||
end = currNodes[currNodes.length - 1];
|
||||
}
|
||||
|
||||
if (nodes[0] === end) {
|
||||
fn = currNodes.push; // join to end
|
||||
nodes = nodes.slice(1);
|
||||
break;
|
||||
} else if (last === what[what.length - 1]) {
|
||||
how = nodes.push;
|
||||
what = what.slice(0, -1).reverse();
|
||||
member = reverse(member);
|
||||
} else if (nodes[nodes.length - 1] === end) {
|
||||
fn = currNodes.push; // join to end
|
||||
nodes = nodes.slice(0, -1).reverse();
|
||||
way = reverse(way);
|
||||
break;
|
||||
} else if (first === what[what.length - 1]) {
|
||||
how = nodes.unshift;
|
||||
what = what.slice(0, -1);
|
||||
} else if (nodes[nodes.length - 1] === start) {
|
||||
fn = currNodes.unshift; // join to beginning
|
||||
nodes = nodes.slice(0, -1);
|
||||
break;
|
||||
} else if (first === what[0]) {
|
||||
how = nodes.unshift;
|
||||
what = what.slice(1).reverse();
|
||||
member = reverse(member);
|
||||
} else if (nodes[0] === start) {
|
||||
fn = currNodes.unshift; // join to beginning
|
||||
nodes = nodes.slice(1).reverse();
|
||||
way = reverse(way);
|
||||
break;
|
||||
} else {
|
||||
what = how = null;
|
||||
fn = nodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!what)
|
||||
break; // No more joinable ways.
|
||||
if (!nodes) {
|
||||
doneSequence = true; // couldn't find a joinable way
|
||||
break;
|
||||
}
|
||||
|
||||
how.apply(current, [member]);
|
||||
how.apply(nodes, what);
|
||||
fn.apply(currWays, [way]);
|
||||
fn.apply(currNodes, nodes);
|
||||
|
||||
array.splice(i, 1);
|
||||
toJoin.splice(i, 1);
|
||||
}
|
||||
|
||||
|
||||
currWays.nodes = currNodes;
|
||||
sequences.push(currWays);
|
||||
}
|
||||
|
||||
return joined;
|
||||
return sequences;
|
||||
|
||||
|
||||
// var joined = [];
|
||||
|
||||
// while (array.length) {
|
||||
// var member = array.shift();
|
||||
// var current = [member];
|
||||
// var nodes = resolve(member).slice();
|
||||
|
||||
// current.nodes = nodes;
|
||||
// joined.push(current);
|
||||
|
||||
// while (array.length && nodes[0] !== nodes[nodes.length - 1]) {
|
||||
// var first = nodes[0];
|
||||
// var last = nodes[nodes.length - 1];
|
||||
// var how, what, i;
|
||||
|
||||
// for (i = 0; i < array.length; i++) {
|
||||
// member = array[i];
|
||||
// what = resolve(member);
|
||||
|
||||
// if (last === what[0]) {
|
||||
// how = nodes.push;
|
||||
// what = what.slice(1);
|
||||
// break;
|
||||
// } else if (last === what[what.length - 1]) {
|
||||
// how = nodes.push;
|
||||
// what = what.slice(0, -1).reverse();
|
||||
// member = reverse(member);
|
||||
// break;
|
||||
// } else if (first === what[what.length - 1]) {
|
||||
// how = nodes.unshift;
|
||||
// what = what.slice(0, -1);
|
||||
// break;
|
||||
// } else if (first === what[0]) {
|
||||
// how = nodes.unshift;
|
||||
// what = what.slice(1).reverse();
|
||||
// member = reverse(member);
|
||||
// break;
|
||||
// } else {
|
||||
// what = how = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!what)
|
||||
// break; // No more joinable ways.
|
||||
|
||||
// how.apply(current, [member]);
|
||||
// how.apply(nodes, what);
|
||||
|
||||
// array.splice(i, 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return joined;
|
||||
}
|
||||
|
||||
@@ -160,8 +160,7 @@ describe('iD.osmJoinWays', function() {
|
||||
expect(result.length).to.equal(1);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a']);
|
||||
expect(result[0].length).to.equal(1);
|
||||
expect(result[0][0]).to.have.own.property('id', '-');
|
||||
expect(result[0][0]).to.have.own.property('type', 'way');
|
||||
expect(result[0][0]).to.eql(member);
|
||||
});
|
||||
|
||||
it('joins ways', function() {
|
||||
@@ -203,10 +202,8 @@ describe('iD.osmJoinWays', function() {
|
||||
expect(result.length).to.equal(1);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
|
||||
expect(result[0].length).to.equal(2);
|
||||
expect(result[0][0]).to.have.own.property('id', '-');
|
||||
expect(result[0][0]).to.have.own.property('type', 'way');
|
||||
expect(result[0][1]).to.have.own.property('id', '=');
|
||||
expect(result[0][1]).to.have.own.property('type', 'way');
|
||||
expect(result[0][0]).to.eql({id: '-', type: 'way'});
|
||||
expect(result[0][1]).to.eql({id: '=', type: 'way'});
|
||||
});
|
||||
|
||||
it('returns joined members in the correct order', function() {
|
||||
@@ -232,12 +229,9 @@ describe('iD.osmJoinWays', function() {
|
||||
expect(result.length).to.equal(1);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c', 'd']);
|
||||
expect(result[0].length).to.equal(3);
|
||||
expect(result[0][0]).to.have.own.property('id', '=');
|
||||
expect(result[0][0]).to.have.own.property('type', 'way');
|
||||
expect(result[0][1]).to.have.own.property('id', '-');
|
||||
expect(result[0][1]).to.have.own.property('type', 'way');
|
||||
expect(result[0][2]).to.have.own.property('id', '~');
|
||||
expect(result[0][2]).to.have.own.property('type', 'way');
|
||||
expect(result[0][0]).to.eql({id: '=', type: 'way'});
|
||||
expect(result[0][1]).to.eql({id: '-', type: 'way'});
|
||||
expect(result[0][2]).to.eql({id: '~', type: 'way'});
|
||||
});
|
||||
|
||||
it('reverses member tags of reversed segements', function() {
|
||||
@@ -245,7 +239,7 @@ describe('iD.osmJoinWays', function() {
|
||||
// Source:
|
||||
// a ---> b <=== c
|
||||
// Result:
|
||||
// a ---> b ===> c (and b === c reversed)
|
||||
// a ---> b ===> c (and === reversed)
|
||||
//
|
||||
var a = iD.osmNode({id: 'a', loc: [0, 0]});
|
||||
var b = iD.osmNode({id: 'b', loc: [1, 0]});
|
||||
@@ -264,6 +258,31 @@ describe('iD.osmJoinWays', function() {
|
||||
expect(result[0][1].tags).to.eql({'oneway': '-1', 'lanes:backward': 2});
|
||||
});
|
||||
|
||||
it('reverses the initial segment to preserve member order', function() {
|
||||
//
|
||||
// Source:
|
||||
// a <--- b ===> c
|
||||
// Result:
|
||||
// a ---> b ===> c (and --- reversed)
|
||||
//
|
||||
var a = iD.osmNode({id: 'a', loc: [0, 0]});
|
||||
var b = iD.osmNode({id: 'b', loc: [1, 0]});
|
||||
var c = iD.osmNode({id: 'c', loc: [2, 0]});
|
||||
var w1 = iD.osmWay({id: '-', nodes: ['b', 'a'], tags: {'oneway': 'yes', 'lanes:forward': 2}});
|
||||
var w2 = iD.osmWay({id: '=', nodes: ['b', 'c']});
|
||||
var graph = iD.coreGraph([a, b, c, w1, w2]);
|
||||
|
||||
var result = iD.osmJoinWays([w1, w2], graph);
|
||||
expect(result.length).to.equal(1);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
|
||||
expect(result[0].length).to.equal(2);
|
||||
expect(result[0][0]).to.be.an.instanceof(iD.osmWay);
|
||||
expect(result[0][0].nodes).to.eql(['a', 'b']);
|
||||
expect(result[0][0].tags).to.eql({'oneway': '-1', 'lanes:backward': 2});
|
||||
expect(result[0][1]).to.eql(w2);
|
||||
});
|
||||
|
||||
|
||||
it('ignores non-way members', function() {
|
||||
var node = iD.osmNode({loc: [0, 0]});
|
||||
var member = {id: 'n', type: 'node'};
|
||||
@@ -280,12 +299,12 @@ describe('iD.osmJoinWays', function() {
|
||||
it('returns multiple arrays for disjoint ways', function() {
|
||||
//
|
||||
// b
|
||||
// / \ d ---> e ===> f
|
||||
// a c
|
||||
// / \
|
||||
// a c d ---> e ===> f
|
||||
//
|
||||
var a = iD.osmNode({id: 'a', loc: [0, -1]});
|
||||
var a = iD.osmNode({id: 'a', loc: [0, 0]});
|
||||
var b = iD.osmNode({id: 'b', loc: [1, 1]});
|
||||
var c = iD.osmNode({id: 'c', loc: [2, -1]});
|
||||
var c = iD.osmNode({id: 'c', loc: [2, 0]});
|
||||
var d = iD.osmNode({id: 'd', loc: [5, 0]});
|
||||
var e = iD.osmNode({id: 'e', loc: [6, 0]});
|
||||
var f = iD.osmNode({id: 'f', loc: [7, 0]});
|
||||
@@ -298,6 +317,7 @@ describe('iD.osmJoinWays', function() {
|
||||
var result = iD.osmJoinWays([w1, w2, w3, w4], graph);
|
||||
|
||||
expect(result.length).to.equal(2);
|
||||
|
||||
expect(result[0].length).to.equal(2);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
|
||||
expect(result[0][0]).to.eql(w1);
|
||||
@@ -337,19 +357,16 @@ describe('iD.osmJoinWays', function() {
|
||||
var result = iD.osmJoinWays(r.members, graph);
|
||||
|
||||
expect(result.length).to.equal(2);
|
||||
|
||||
expect(result[0].length).to.equal(2);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
|
||||
expect(result[0][0]).to.have.own.property('id', '/');
|
||||
expect(result[0][0]).to.have.own.property('type', 'way');
|
||||
expect(result[0][1]).to.have.own.property('id', '\\');
|
||||
expect(result[0][1]).to.have.own.property('type', 'way');
|
||||
expect(result[0][0]).to.eql({id: '/', type: 'way'});
|
||||
expect(result[0][1]).to.eql({id: '\\', type: 'way'});
|
||||
|
||||
expect(result[1].length).to.equal(2);
|
||||
expect(getIDs(result[1].nodes)).to.eql(['d', 'e', 'f']);
|
||||
expect(result[1][0]).to.have.own.property('id', '-');
|
||||
expect(result[1][0]).to.have.own.property('type', 'way');
|
||||
expect(result[1][1]).to.have.own.property('id', '=');
|
||||
expect(result[1][1]).to.have.own.property('type', 'way');
|
||||
expect(result[1][0]).to.eql({id: '-', type: 'way'});
|
||||
expect(result[1][1]).to.eql({id: '=', type: 'way'});
|
||||
});
|
||||
|
||||
it('understands doubled-back relation members', function() {
|
||||
@@ -385,20 +402,13 @@ describe('iD.osmJoinWays', function() {
|
||||
expect(result.length).to.equal(1);
|
||||
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c', 'd', 'e', 'c', 'b', 'a']);
|
||||
expect(result[0].length).to.equal(7);
|
||||
expect(result[0][0]).to.have.own.property('id', '=');
|
||||
expect(result[0][0]).to.have.own.property('type', 'way');
|
||||
expect(result[0][1]).to.have.own.property('id', '-');
|
||||
expect(result[0][1]).to.have.own.property('type', 'way');
|
||||
expect(result[0][2]).to.have.own.property('id', '~');
|
||||
expect(result[0][2]).to.have.own.property('type', 'way');
|
||||
expect(result[0][3]).to.have.own.property('id', '\\');
|
||||
expect(result[0][3]).to.have.own.property('type', 'way');
|
||||
expect(result[0][4]).to.have.own.property('id', '/');
|
||||
expect(result[0][4]).to.have.own.property('type', 'way');
|
||||
expect(result[0][5]).to.have.own.property('id', '-');
|
||||
expect(result[0][5]).to.have.own.property('type', 'way');
|
||||
expect(result[0][6]).to.have.own.property('id', '=');
|
||||
expect(result[0][6]).to.have.own.property('type', 'way');
|
||||
expect(result[0][0]).to.eql({id: '=', type: 'way'});
|
||||
expect(result[0][1]).to.eql({id: '-', type: 'way'});
|
||||
expect(result[0][2]).to.eql({id: '~', type: 'way'});
|
||||
expect(result[0][3]).to.eql({id: '\\', type: 'way'});
|
||||
expect(result[0][4]).to.eql({id: '/', type: 'way'});
|
||||
expect(result[0][5]).to.eql({id: '-', type: 'way'});
|
||||
expect(result[0][6]).to.eql({id: '=', type: 'way'});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user