Enable the Split operation for multiple selected nodes (close #7990)

This commit is contained in:
Quincy Morgan
2020-09-24 15:13:05 -04:00
parent a0d4d01804
commit c940d827e6
6 changed files with 59 additions and 41 deletions
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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",
+31 -14
View File
@@ -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';
}
}
};
+23 -22
View File
@@ -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 });
};
+2 -2
View File
@@ -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);
});
+1 -1
View File
@@ -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