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:
Bryan Housel
2018-01-12 17:23:56 -05:00
parent 8f6cb207fc
commit 0fd801d750
2 changed files with 171 additions and 78 deletions
+122 -39
View File
@@ -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;
}
+49 -39
View File
@@ -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'});
});
});