Multiselect vertex/way to control splitting

This commit is contained in:
John Firebaugh
2013-03-29 15:32:10 -07:00
parent 2bd6178f07
commit 93dd4a2658
5 changed files with 147 additions and 53 deletions
+8 -2
View File
@@ -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.
+10 -2
View File
@@ -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."
}
+29 -23
View File
@@ -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';
};
+27 -9
View File
@@ -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";
+73 -17
View File
@@ -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,7 +180,7 @@ 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']);
@@ -160,6 +188,34 @@ describe("iD.actions.Split", function () {
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'},