diff --git a/data/core.yaml b/data/core.yaml index 88787ade9..58ce0643b 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -2203,7 +2203,7 @@ en: merge: "Combine (merge) selected features" disconnect: "Disconnect selected features" extract: "Extract a point from a feature" - split: "Split a line into two at the selected node" + split: "Split features at the selected points" reverse: "Reverse selected features" move: "Move selected features" nudge: "Nudge selected features" diff --git a/dist/locales/en.json b/dist/locales/en.json index 2a7826b1c..2c696bc52 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -2725,7 +2725,7 @@ "merge": "Combine (merge) selected features", "disconnect": "Disconnect selected features", "extract": "Extract a point from a feature", - "split": "Split a line into two at the selected node", + "split": "Split features at the selected points", "reverse": "Reverse selected features", "move": "Move selected features", "nudge": "Nudge selected features", diff --git a/modules/actions/split.js b/modules/actions/split.js index 6d6795b1e..46cbafab9 100644 --- a/modules/actions/split.js +++ b/modules/actions/split.js @@ -3,7 +3,7 @@ import { geoSphericalDistance } from '../geo/geo'; import { osmIsOldMultipolygonOuterMember } from '../osm/multipolygon'; import { osmRelation } from '../osm/relation'; import { osmWay } from '../osm/way'; -import { utilArrayIntersection, utilWrap } from '../util'; +import { utilArrayIntersection, utilWrap, utilArrayUniq } from '../util'; // Split a way at the given node. @@ -20,13 +20,16 @@ import { utilArrayIntersection, utilWrap } from '../util'; // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -export function actionSplit(nodeId, newWayIds) { +export function actionSplit(nodeIds, newWayIds) { + // accept single ID for backwards-compatiblity + if (typeof nodeIds === 'string') nodeIds = [nodeIds]; + var _wayIDs; // the strategy for picking which way will have a new version and which way is newly created var _keepHistoryOn = 'longest'; // 'longest', 'first' // The IDs of the ways actually created by running this action - var createdWayIDs = []; + var _createdWayIDs = []; function dist(graph, nA, nB) { var locA = graph.entity(nA).loc; @@ -91,7 +94,7 @@ export function actionSplit(nodeId, newWayIds) { return totalLength; } - function split(graph, wayA, newWayId) { + function split(graph, nodeId, wayA, newWayId) { var wayB = osmWay({ id: newWayId, tags: wayA.tags }); // `wayB` is the NEW way var origNodes = wayA.nodes.slice(); var nodesA; @@ -220,25 +223,30 @@ export function actionSplit(nodeId, newWayIds) { graph = graph.replace(wayB.update({ tags: {} })); } - createdWayIDs.push(wayB.id); + _createdWayIDs.push(wayB.id); return graph; } var action = function(graph) { - var candidates = action.ways(graph); - createdWayIDs = []; - for (var i = 0; i < candidates.length; i++) { - graph = split(graph, candidates[i], newWayIds && newWayIds[i]); + _createdWayIDs = []; + var newWayIndex = 0; + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + var candidates = action.waysForNode(nodeId, graph); + for (var j = 0; j < candidates.length; j++) { + graph = split(graph, nodeId, candidates[j], newWayIds && newWayIds[newWayIndex]); + newWayIndex += 1; + } } return graph; }; action.getCreatedWayIDs = function() { - return createdWayIDs; + return _createdWayIDs; }; - action.ways = function(graph) { + action.waysForNode = function(nodeId, graph) { var node = graph.entity(nodeId); var parents = graph.parentWays(node); var hasLines = parents.some(function(parent) { @@ -266,11 +274,20 @@ export function actionSplit(nodeId, newWayIds) { }); }; + action.ways = function(graph) { + return utilArrayUniq([].concat.apply([], nodeIds.map(function(nodeId) { + return action.waysForNode(nodeId, graph); + }))); + }; + action.disabled = function(graph) { - var candidates = action.ways(graph); - if (candidates.length === 0 || (_wayIDs && _wayIDs.length !== candidates.length)) { - return 'not_eligible'; + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + var candidates = action.waysForNode(nodeId, graph); + if (candidates.length === 0 || (_wayIDs && _wayIDs.length !== candidates.length)) { + return 'not_eligible'; + } } }; diff --git a/modules/operations/split.js b/modules/operations/split.js index e5527b84b..dd691568f 100644 --- a/modules/operations/split.js +++ b/modules/operations/split.js @@ -5,40 +5,42 @@ import { modeSelect } from '../modes/select'; export function operationSplit(context, selectedIDs) { - var vertices = selectedIDs - .filter(function(id) { return context.graph().geometry(id) === 'vertex'; }); - var entityID = vertices[0]; - var action = actionSplit(entityID); - var ways = []; + var _vertexIds = selectedIDs.filter(function(id) { + return context.graph().geometry(id) === 'vertex'; + }); + var _selectedWayIds = selectedIDs.filter(function(id) { + var entity = context.graph().hasEntity(id); + return entity && entity.type === 'way'; + }); + var _isAvailable = _vertexIds.length > 0 && + _vertexIds.length + _selectedWayIds.length === selectedIDs.length; + var _action = actionSplit(_vertexIds); + var _ways = []; - if (vertices.length === 1) { - if (entityID && selectedIDs.length > 1) { - var ids = selectedIDs.filter(function(id) { return id !== entityID; }); - action.limitWays(ids); - } - ways = action.ways(context.graph()); + if (_isAvailable) { + if (_selectedWayIds.length) _action.limitWays(_selectedWayIds); + _ways = _action.ways(context.graph()); } var operation = function() { - var difference = context.perform(action, operation.annotation()); + var difference = context.perform(_action, operation.annotation()); context.enter(modeSelect(context, difference.extantIDs())); }; operation.available = function() { - return vertices.length === 1; + return _isAvailable; }; operation.disabled = function() { - var reason = action.disabled(context.graph()); + var reason = _action.disabled(context.graph()); if (reason) { return reason; } else if (selectedIDs.some(context.hasHiddenConnections)) { return 'connected_to_hidden'; } - return false; }; @@ -47,18 +49,17 @@ export function operationSplit(context, selectedIDs) { var disable = operation.disabled(); if (disable) { return t('operations.split.' + disable); - } else if (ways.length === 1) { - return t('operations.split.description.' + context.graph().geometry(ways[0].id)); - } else { - return t('operations.split.description.multiple'); + } else if (_ways.length === 1) { + return t('operations.split.description.' + context.graph().geometry(_ways[0].id)); } + return t('operations.split.description.multiple'); }; operation.annotation = function() { - return ways.length === 1 ? - t('operations.split.annotation.' + context.graph().geometry(ways[0].id)) : - t('operations.split.annotation.feature', { n: ways.length }); + return _ways.length === 1 ? + t('operations.split.annotation.' + context.graph().geometry(_ways[0].id)) : + t('operations.split.annotation.feature', { n: _ways.length }); }; diff --git a/modules/osm/intersection.js b/modules/osm/intersection.js index 39b1f98db..a2f780c48 100644 --- a/modules/osm/intersection.js +++ b/modules/osm/intersection.js @@ -164,10 +164,10 @@ export function osmIntersection(graph, startVertexId, maxDistance) { // actions can be replayed on the main graph exactly in the same order. // (It is unintuitive, but the order of ways returned from graph.parentWays() // is arbitrary, depending on how the main graph and vgraph were built) - var splitAll = actionSplit(v.id).keepHistoryOn('first'); + var splitAll = actionSplit([v.id]).keepHistoryOn('first'); if (!splitAll.disabled(vgraph)) { splitAll.ways(vgraph).forEach(function(way) { - var splitOne = actionSplit(v.id).limitWays([way.id]).keepHistoryOn('first'); + var splitOne = actionSplit([v.id]).limitWays([way.id]).keepHistoryOn('first'); actions.push(splitOne); vgraph = splitOne(vgraph); }); diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js index 7f3f339cb..e140059ef 100644 --- a/modules/validations/crossing_ways.js +++ b/modules/validations/crossing_ways.js @@ -647,7 +647,7 @@ export function validationCrossingWays(context) { // just bound the structure at the existing end node if (!newNode) newNode = endNode; - var splitAction = actionSplit(newNode.id) + var splitAction = actionSplit([newNode.id]) .limitWays(resultWayIDs); // only split selected or created ways // do the split