diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 24f2b4318..ea2100238 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -15,37 +15,55 @@ iD.actions.Split = function(nodeId, newWayId) { parents = graph.parentWays(node); return parents.filter(function (parent) { - return parent.first() !== nodeId && - parent.last() !== nodeId; + return parent.isClosed() || + (parent.first() !== nodeId && + parent.last() !== nodeId); }); } var action = function(graph) { - if (!action.enabled(graph)) - return graph; + var wayA = candidateWays(graph)[0], + wayB = iD.Way({id: newWayId, tags: wayA.tags}), + nodesA, + nodesB, + isArea = wayA.isArea(); - var way = candidateWays(graph)[0], - idx = _.indexOf(way.nodes, nodeId); + if (wayA.isClosed()) { + var nodes = wayA.nodes.slice(0, -1), + idxA = _.indexOf(nodes, nodeId), + idxB = idxA + Math.floor(nodes.length / 2); - // Create a 'b' way that contains all of the tags in the second - // half of this way - var newWay = iD.Way({id: newWayId, tags: way.tags, nodes: way.nodes.slice(idx)}); - graph = graph.replace(newWay); + if (idxB >= nodes.length) { + idxB %= nodes.length; + nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1)); + nodesB = nodes.slice(idxB, idxA + 1); + } else { + nodesA = nodes.slice(idxA, idxB + 1); + nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1)); + } + } else { + var idx = _.indexOf(wayA.nodes, nodeId); + nodesA = wayA.nodes.slice(0, idx + 1); + nodesB = wayA.nodes.slice(idx); + } - // Reduce the original way to only contain the first set of nodes - graph = graph.replace(way.update({nodes: way.nodes.slice(0, idx + 1)})); + wayA = wayA.update({nodes: nodesA}); + wayB = wayB.update({nodes: nodesB}); - graph.parentRelations(way).forEach(function(relation) { + graph = graph.replace(wayA); + graph = graph.replace(wayB); + + graph.parentRelations(wayA).forEach(function(relation) { if (relation.isRestriction()) { var via = relation.memberByRole('via'); - if (via && newWay.contains(via.id)) { - relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index); + if (via && wayB.contains(via.id)) { + relation = relation.updateMember({id: wayB.id}, relation.memberById(wayA.id).index); graph = graph.replace(relation); } } else { - var role = relation.memberById(way.id).role, - last = newWay.last(), - i = relation.memberById(way.id).index, + var role = relation.memberById(wayA.id).role, + last = wayB.last(), + i = relation.memberById(wayA.id).index, j; for (j = 0; j < relation.members.length; j++) { @@ -55,11 +73,24 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); + if (isArea) { + var multipolygon = iD.Relation({ + tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), + members: [ + {id: wayA.id, role: 'outer', type: 'way'}, + {id: wayB.id, role: 'outer', type: 'way'} + ]}); + + graph = graph.replace(multipolygon); + graph = graph.replace(wayA.update({tags: {}})); + graph = graph.replace(wayB.update({tags: {}})); + } + return graph; }; diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 5ee68e67e..417525941 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -99,6 +99,66 @@ describe("iD.actions.Split", function () { expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); + it("splits a closed way at the given point and its antipode", function () { + // Situation: + // a ---- b + // | | + // d ---- c + // + // Split at a. + // + // Expected result: + // a ---- b + // || | + // 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'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + 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); + 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); + 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); + expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); + expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); + }); + + 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'}), + '-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + 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); + + var relation = graph.parentRelations(graph.entity('-'))[0]; + expect(relation.tags).to.eql({type: 'multipolygon', building: 'yes'}); + expect(relation.members).to.eql([ + {id: '-', role: 'outer', type: 'way'}, + {id: '=', role: 'outer', type: 'way'} + ]); + }); + it("adds the new way to parent relations (no connections)", function () { // Situation: // a ---- b ---- c