From bbd4cb80b69f54775910d1a08d449df729d44c21 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 29 Mar 2013 14:08:41 -0700 Subject: [PATCH] Split ways at intersections (fixes #750) --- js/id/actions/split.js | 17 +++++--- test/spec/actions/split.js | 79 ++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index e9ebc2a6e..d4c88595a 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -9,7 +9,7 @@ // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -iD.actions.Split = function(nodeId, newWayId) { +iD.actions.Split = function(nodeId, newWayIds) { function candidateWays(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node); @@ -21,9 +21,8 @@ iD.actions.Split = function(nodeId, newWayId) { }); } - var action = function(graph) { - var wayA = candidateWays(graph)[0], - wayB = iD.Way({id: newWayId, tags: wayA.tags}), + function split(graph, wayA, newWayId) { + var wayB = iD.Way({id: newWayId, tags: wayA.tags}), nodesA, nodesB, isArea = wayA.isArea(); @@ -91,6 +90,14 @@ iD.actions.Split = function(nodeId, newWayId) { graph = graph.replace(wayB.update({tags: {}})); } + return graph; + } + + var action = function(graph) { + var candidates = candidateWays(graph); + for (var i = 0; i < candidates.length; i++) { + graph = split(graph, candidates[i], newWayIds && newWayIds[i]); + } return graph; }; @@ -98,8 +105,6 @@ iD.actions.Split = function(nodeId, newWayId) { var candidates = candidateWays(graph); if (candidates.length === 0) return 'not_eligible'; - if (candidates.length > 1) - return 'multiple_ways'; }; return action; diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 11e2b4a6e..bc7227ba1 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -11,6 +11,20 @@ describe("iD.actions.Split", function () { expect(iD.actions.Split('b').disabled(graph)).not.to.be.ok; }); + it("returns falsy for an intersection of two ways", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'c'}), + '*': iD.Node({id: '*'}), + '-': iD.Way({id: '-', nodes: ['a', '*', 'b']}), + '|': iD.Way({id: '|', nodes: ['c', '*', 'd']}) + }); + + expect(iD.actions.Split('*').disabled(graph)).not.to.be.ok; + }); + it("returns 'not_eligible' for the first node of a single way", function () { var graph = iD.Graph({ 'a': iD.Node({id: 'a'}), @@ -48,7 +62,7 @@ describe("iD.actions.Split", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -63,7 +77,7 @@ describe("iD.actions.Split", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); // Immutable tags => should be shared by identity. expect(graph.entity('-').tags).to.equal(tags); @@ -92,13 +106,48 @@ describe("iD.actions.Split", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); + it("splits multiple ways at an intersection", function () { + // Situation: + // c + // | + // a ---- * ---- b + // ¦ + // d + // + // Split at b. + // + // Expected result: + // c + // | + // a ---- * ==== b + // ¦ + // d + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'c'}), + '*': iD.Node({id: '*'}), + '-': iD.Way({id: '-', nodes: ['a', '*', 'b']}), + '|': iD.Way({id: '|', nodes: ['c', '*', 'd']}) + }); + + graph = iD.actions.Split('*', ['=', '¦'])(graph); + + expect(graph.entity('-').nodes).to.eql(['a', '*']); + expect(graph.entity('=').nodes).to.eql(['*', 'b']); + expect(graph.entity('|').nodes).to.eql(['c', '*']); + expect(graph.entity('¦').nodes).to.eql(['*', 'd']); + }); + it("splits a closed way at the given point and its antipode", function () { // Situation: // a ---- b @@ -120,19 +169,19 @@ describe("iD.actions.Split", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) }); - var g1 = iD.actions.Split('a', '=')(graph); + var g1 = iD.actions.Split('a', ['='])(graph); expect(g1.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(g1.entity('=').nodes).to.eql(['c', 'd', 'a']); - var g2 = iD.actions.Split('b', '=')(graph); + var g2 = iD.actions.Split('b', ['='])(graph); expect(g2.entity('-').nodes).to.eql(['b', 'c', 'd']); expect(g2.entity('=').nodes).to.eql(['d', 'a', 'b']); - var g3 = iD.actions.Split('c', '=')(graph); + var g3 = iD.actions.Split('c', ['='])(graph); expect(g3.entity('-').nodes).to.eql(['c', 'd', 'a']); expect(g3.entity('=').nodes).to.eql(['a', 'b', 'c']); - var g4 = iD.actions.Split('d', '=')(graph); + var g4 = iD.actions.Split('d', ['='])(graph); expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); }); @@ -146,7 +195,7 @@ describe("iD.actions.Split", function () { '-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) }); - graph = iD.actions.Split('a', '=')(graph); + graph = iD.actions.Split('a', ['='])(graph); expect(graph.entity('-').tags).to.eql({}); expect(graph.entity('=').tags).to.eql({}); expect(graph.parentRelations(graph.entity('-'))).to.have.length(1); @@ -178,7 +227,7 @@ describe("iD.actions.Split", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way', role: 'forward'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '-', type: 'way', role: 'forward'}, @@ -207,7 +256,7 @@ describe("iD.actions.Split", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']); }); @@ -233,7 +282,7 @@ describe("iD.actions.Split", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']); }); @@ -247,7 +296,7 @@ describe("iD.actions.Split", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']); }); @@ -277,7 +326,7 @@ describe("iD.actions.Split", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '=', role: 'from'}, @@ -309,7 +358,7 @@ describe("iD.actions.Split", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '~', role: 'from'}, @@ -341,7 +390,7 @@ describe("iD.actions.Split", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.Split('b', '=')(graph); + graph = iD.actions.Split('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '-', role: 'from'},