Permalink
Browse files

Multiselect vertex/way to control splitting

  • Loading branch information...
jfirebaugh committed Mar 29, 2013
1 parent 2bd6178 commit 93dd4a265844b21fd631129caf976c676f0a4015
Showing with 147 additions and 53 deletions.
  1. +8 −2 data/core.yaml
  2. +10 −2 data/locales.js
  3. +29 −23 js/id/actions/split.js
  4. +27 −9 js/id/operations/split.js
  5. +73 −17 test/spec/actions/split.js
View
@@ -109,9 +109,15 @@ en:
annotation: Reversed a line.
split:
title: Split
- description: Split this into two ways at this point.
+ description:
+ line: Split this line into two at this point.
+ area: Split the boundary of this area into two.
+ multiple: Split the lines/area boundaries at this point into two.
key: X
- annotation: Split a way.
+ annotation:
+ line: Split a line.
+ area: Split an area boundary.
+ multiple: "Split {n} lines/area boundaries."
not_eligible: Lines can't be split at their beginning or end.
multiple_ways: There are too many lines here to split.
nothing_to_undo: Nothing to undo.
View
@@ -139,9 +139,17 @@ locale.en = {
},
"split": {
"title": "Split",
- "description": "Split this into two ways at this point.",
+ "description": {
+ "line": "Split this line into two at this point.",
+ "area": "Split the boundary of this area into two.",
+ "multiple": "Split the lines/area boundaries at this point into two."
+ },
"key": "X",
- "annotation": "Split a way.",
+ "annotation": {
+ "line": "Split a line.",
+ "area": "Split an area boundary.",
+ "multiple": "Split {n} lines/area boundaries."
+ },
"not_eligible": "Lines can't be split at their beginning or end.",
"multiple_ways": "There are too many lines here to split."
}
View
@@ -1,5 +1,8 @@
// Split a way at the given node.
//
+// Optionally, split only the given ways, if multiple ways share
+// the given node.
+//
// This is the inverse of `iD.actions.Join`.
//
// For testing convenience, accepts an ID to assign to the new way.
@@ -9,26 +12,7 @@
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
//
-iD.actions.Split = function(nodeId, newWayIds) {
- function candidateWays(graph) {
- var node = graph.entity(nodeId),
- parents = graph.parentWays(node);
-
- return parents.filter(function(parent) {
- if (parent.isClosed()) {
- return true;
- }
-
- for (var i = 1; i < parent.nodes.length - 1; i++) {
- if (parent.nodes[i] === nodeId) {
- return true;
- }
- }
-
- return false;
- });
- }
-
+iD.actions.Split = function(nodeId, wayIds, newWayIds) {
function split(graph, wayA, newWayId) {
var wayB = iD.Way({id: newWayId, tags: wayA.tags}),
nodesA,
@@ -102,16 +86,38 @@ iD.actions.Split = function(nodeId, newWayIds) {
}
var action = function(graph) {
- var candidates = candidateWays(graph);
+ var candidates = action.ways(graph);
for (var i = 0; i < candidates.length; i++) {
graph = split(graph, candidates[i], newWayIds && newWayIds[i]);
}
return graph;
};
+ action.ways = function(graph) {
+ var node = graph.entity(nodeId),
+ parents = graph.parentWays(node);
+
+ return parents.filter(function(parent) {
+ if (wayIds && wayIds.length && wayIds.indexOf(parent.id) === -1)
+ return false;
+
+ if (parent.isClosed()) {
+ return true;
+ }
+
+ for (var i = 1; i < parent.nodes.length - 1; i++) {
+ if (parent.nodes[i] === nodeId) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+ };
+
action.disabled = function(graph) {
- var candidates = candidateWays(graph);
- if (candidates.length === 0)
+ var candidates = action.ways(graph);
+ if (candidates.length === 0 || (wayIds && wayIds.length && wayIds.length !== candidates.length))
return 'not_eligible';
};
View
@@ -1,16 +1,27 @@
iD.operations.Split = function(selection, context) {
- var entityId = selection[0],
- action = iD.actions.Split(entityId);
+ var vertices = _.filter(selection, function vertex(entityId) {
+ return context.geometry(entityId) === 'vertex'
+ });
+
+ var entityId = vertices[0],
+ action = iD.actions.Split(entityId, _.without(selection, entityId));
var operation = function() {
- var annotation = t('operations.split.annotation'),
- difference = context.perform(action, annotation);
+ var annotation;
+
+ var ways = action.ways(context.graph());
+ if (ways.length === 1) {
+ annotation = t('operations.split.annotation.' + context.geometry(ways[0].id));
+ } else {
+ annotation = t('operations.split.annotation.multiple', {n: ways.length});
+ }
+
+ var difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
- return selection.length === 1 &&
- context.geometry(entityId) === 'vertex';
+ return vertices.length === 1;
};
operation.disabled = function() {
@@ -19,9 +30,16 @@ iD.operations.Split = function(selection, context) {
operation.tooltip = function() {
var disable = operation.disabled();
- return disable ?
- t('operations.split.' + disable) :
- t('operations.split.description');
+ if (disable) {
+ return t('operations.split.' + disable);
+ }
+
+ var ways = action.ways(context.graph());
+ if (ways.length === 1) {
+ return t('operations.split.description.' + context.geometry(ways[0].id));
+ } else {
+ return t('operations.split.description.multiple');
+ }
};
operation.id = "split";
View
@@ -25,6 +25,20 @@ describe("iD.actions.Split", function () {
expect(iD.actions.Split('*').disabled(graph)).not.to.be.ok;
});
+ it("returns falsy for an intersection of two ways with parent way specified", 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 falsy for a self-intersection", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -56,6 +70,20 @@ describe("iD.actions.Split", function () {
expect(iD.actions.Split('b').disabled(graph)).to.equal('not_eligible');
});
+
+ it("returns 'not_eligible' for an intersection of two ways with non-parent way specified", 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)).to.equal('not_eligible');
+ });
});
it("creates a new way with the appropriate nodes", function () {
@@ -74,7 +102,7 @@ describe("iD.actions.Split", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
- graph = iD.actions.Split('b', ['='])(graph);
+ graph = iD.actions.Split('b', undefined, ['='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b']);
expect(graph.entity('=').nodes).to.eql(['b', 'c']);
@@ -89,7 +117,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', undefined, ['='])(graph);
// Immutable tags => should be shared by identity.
expect(graph.entity('-').tags).to.equal(tags);
@@ -118,7 +146,7 @@ describe("iD.actions.Split", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
- graph = iD.actions.Split('b', ['='])(graph);
+ graph = iD.actions.Split('b', undefined, ['='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b']);
expect(graph.entity('=').nodes).to.eql(['b', 'c']);
@@ -152,14 +180,42 @@ describe("iD.actions.Split", function () {
'|': iD.Way({id: '|', nodes: ['c', '*', 'd']})
});
- graph = iD.actions.Split('*', ['=', '¦'])(graph);
+ graph = iD.actions.Split('*', undefined, ['=', '¦'])(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 the specified ways at an intersection", 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']})
+ });
+
+ var g1 = iD.actions.Split('*', ['-'], ['='])(graph);
+ expect(g1.entity('-').nodes).to.eql(['a', '*']);
+ expect(g1.entity('=').nodes).to.eql(['*', 'b']);
+ expect(g1.entity('|').nodes).to.eql(['c', '*', 'd']);
+
+ var g2 = iD.actions.Split('*', ['|'], ['¦'])(graph);
+ expect(g2.entity('-').nodes).to.eql(['a', '*', 'b']);
+ expect(g2.entity('|').nodes).to.eql(['c', '*']);
+ expect(g2.entity('¦').nodes).to.eql(['*', 'd']);
+
+ var g3 = iD.actions.Split('*', ['-', '|'], ['=', '¦'])(graph);
+ expect(g3.entity('-').nodes).to.eql(['a', '*']);
+ expect(g3.entity('=').nodes).to.eql(['*', 'b']);
+ expect(g3.entity('|').nodes).to.eql(['c', '*']);
+ expect(g3.entity('¦').nodes).to.eql(['*', 'd']);
+ });
+
it("splits self-intersecting ways", function () {
// Situation:
// b
@@ -183,7 +239,7 @@ describe("iD.actions.Split", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a', 'd']})
});
- graph = iD.actions.Split('a', ['='])(graph);
+ graph = iD.actions.Split('a', undefined, ['='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'a']);
expect(graph.entity('=').nodes).to.eql(['a', 'd']);
@@ -210,19 +266,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', undefined, ['='])(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', undefined, ['='])(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', undefined, ['='])(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', undefined, ['='])(graph);
expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']);
expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']);
});
@@ -236,7 +292,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', undefined, ['='])(graph);
expect(graph.entity('-').tags).to.eql({});
expect(graph.entity('=').tags).to.eql({});
expect(graph.parentRelations(graph.entity('-'))).to.have.length(1);
@@ -268,7 +324,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', undefined, ['='])(graph);
expect(graph.entity('r').members).to.eql([
{id: '-', type: 'way', role: 'forward'},
@@ -297,7 +353,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', undefined, ['='])(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']);
});
@@ -323,7 +379,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', undefined, ['='])(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']);
});
@@ -337,7 +393,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', undefined, ['='])(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']);
});
@@ -367,7 +423,7 @@ describe("iD.actions.Split", function () {
{id: 'c', role: 'via'}]})
});
- graph = iD.actions.Split('b', ['='])(graph);
+ graph = iD.actions.Split('b', undefined, ['='])(graph);
expect(graph.entity('r').members).to.eql([
{id: '=', role: 'from'},
@@ -399,7 +455,7 @@ describe("iD.actions.Split", function () {
{id: 'c', role: 'via'}]})
});
- graph = iD.actions.Split('b', ['='])(graph);
+ graph = iD.actions.Split('b', undefined, ['='])(graph);
expect(graph.entity('r').members).to.eql([
{id: '~', role: 'from'},
@@ -431,7 +487,7 @@ describe("iD.actions.Split", function () {
{id: 'c', role: 'via'}]})
});
- graph = iD.actions.Split('b', ['='])(graph);
+ graph = iD.actions.Split('b', undefined, ['='])(graph);
expect(graph.entity('r').members).to.eql([
{id: '-', role: 'from'},

0 comments on commit 93dd4a2

Please sign in to comment.