more clever splitting of closed ways

if a way is closed, iD needs to choose a second node to
split the way at.

This algorithm looks for a node that is both far away from
the initial node in terms of way segment length and nearby
in terms of beeline-distance. This assures that areas get
split on the most "natural" points (independent of the number
of nodes).

For example: bone-shaped areas get split across their waist-
line, circles across the diameter.
This commit is contained in:
tyr
2013-09-12 13:14:33 +02:00
committed by John Firebaugh
parent be30344cfd
commit 6afe3adfbe
3 changed files with 59 additions and 11 deletions

View File

@@ -16,6 +16,16 @@ iD.actions.Split = function(nodeId, newWayIds) {
var wayIds;
function split(graph, wayA, newWayId) {
// if the way is closed, we need to search for a partner node
// to split the way at.
//
// The following looks for a node that is both far away from
// the initial node in terms of way segment length and nearby
// in terms of beeline-distance. This assures that areas get
// split on the most "natural" points (independent of the number
// of nodes).
// For example: bone-shaped areas get split across their waist
// line, circles across the diameter.
var wayB = iD.Way({id: newWayId, tags: wayA.tags}),
nodesA,
nodesB,
@@ -24,10 +34,41 @@ iD.actions.Split = function(nodeId, newWayIds) {
if (wayA.isClosed()) {
var nodes = wayA.nodes.slice(0, -1),
idxA = _.indexOf(nodes, nodeId),
idxB = idxA + Math.floor(nodes.length / 2);
idxB,
lengths = Array(nodes.length),
cum_length,
i,
best = 0.0;
if (idxB >= nodes.length) {
idxB %= nodes.length;
function _wrap(index) {
return iD.util.wrap(index,nodes.length);
}
function _dist(nA, nB) {
return iD.geo.dist(graph.entity(nA).loc, graph.entity(nB).loc);
}
// calculate lengths
cum_length = 0.0;
for (i = _wrap(idxA+1); i != idxA; i = _wrap(i+1)) {
cum_length += _dist(nodes[i], nodes[_wrap(i-1)]);
lengths[i] = cum_length;
}
cum_length = 0.0;
for (i = _wrap(idxA-1); i != idxA; i = _wrap(i-1)) {
cum_length += _dist(nodes[i], nodes[_wrap(i+1)]);
if (cum_length < lengths[i])
lengths[i] = cum_length;
}
// determine best opposite node to split
for (i = 0; i < nodes.length; i++) {
var cost = lengths[i] / _dist(nodes[idxA], nodes[i]);
if (cost > best) {
idxB = i;
best = cost;
}
}
if (idxB < idxA) {
nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1));
nodesB = nodes.slice(idxB, idxA + 1);
} else {

View File

@@ -146,3 +146,10 @@ iD.util.asyncMap = function(inputs, func, callback) {
});
});
};
// wraps an index to an interval [0..length-1]
iD.util.wrap = function(index, length) {
if (index < 0)
index += Math.ceil(-index/length)*length;
return index % length;
};

View File

@@ -259,10 +259,10 @@ describe("iD.actions.Split", function () {
// d ==== c
//
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'd': iD.Node({id: 'd'}),
'a': iD.Node({id: 'a', loc: [0,1]}),
'b': iD.Node({id: 'b', loc: [1,1]}),
'c': iD.Node({id: 'c', loc: [1,0]}),
'd': iD.Node({id: 'd', loc: [0,0]}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
});
@@ -285,10 +285,10 @@ describe("iD.actions.Split", function () {
it("splits an area by converting it to a multipolygon", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'd': iD.Node({id: 'd'}),
'a': iD.Node({id: 'a', loc: [0,1]}),
'b': iD.Node({id: 'b', loc: [1,1]}),
'c': iD.Node({id: 'c', loc: [1,0]}),
'd': iD.Node({id: 'd', loc: [0,0]}),
'-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']})
});