From c229326aad6c2d2f7cbe4152aab47bd560bccb9a Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 17 Sep 2019 10:57:55 -0400 Subject: [PATCH] Allow using the Reverse operation on directional nodes (close #6850) --- data/core.yaml | 7 ++++++- dist/locales/en.json | 12 ++++++++++-- modules/actions/reverse.js | 29 ++++++++++++++++++++++++++--- modules/operations/reverse.js | 35 ++++++++++++++++++++++++++++++----- test/spec/actions/reverse.js | 6 ++++++ 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 8ddc5e233..7aa4c95bd 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -309,6 +309,11 @@ en: description: Make this line go in the opposite direction. key: V annotation: Reversed a line. + node: + description: + single: Flip the direction of this point. + annotation: + single: Reversed a point. split: title: Split description: @@ -1898,7 +1903,7 @@ en: disconnect: "Disconnect features at the selected node" extract: "Extract a point from a feature" split: "Split a line into two at the selected node" - reverse: "Reverse a line" + reverse: "Reverse selected features" move: "Move selected features" rotate: "Rotate selected features" orthogonalize: "Square corners of a line or area" diff --git a/dist/locales/en.json b/dist/locales/en.json index a0cc0fa96..13151d965 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -398,7 +398,15 @@ "title": "Reverse", "description": "Make this line go in the opposite direction.", "key": "V", - "annotation": "Reversed a line." + "annotation": "Reversed a line.", + "node": { + "description": { + "single": "Flip the direction of this point." + }, + "annotation": { + "single": "Reversed a point." + } + } }, "split": { "title": "Split", @@ -2321,7 +2329,7 @@ "disconnect": "Disconnect features at the selected node", "extract": "Extract a point from a feature", "split": "Split a line into two at the selected node", - "reverse": "Reverse a line", + "reverse": "Reverse selected features", "move": "Move selected features", "rotate": "Rotate selected features", "orthogonalize": "Square corners of a line or area", diff --git a/modules/actions/reverse.js b/modules/actions/reverse.js index a560abbc0..ea29f67bd 100644 --- a/modules/actions/reverse.js +++ b/modules/actions/reverse.js @@ -17,7 +17,7 @@ References: http://wiki.openstreetmap.org/wiki/Tag:highway%3Dstop http://wiki.openstreetmap.org/wiki/Key:traffic_sign#On_a_way_or_area */ -export function actionReverse(wayID, options) { +export function actionReverse(entityID, options) { var ignoreKey = /^.*(_|:)?(description|name|note|website|ref|source|comment|watch|attribution)(_|:)?/; var numeric = /^([+\-]?)(?=[\d.])/; var turn_lanes = /^turn:lanes:?/; @@ -97,8 +97,7 @@ export function actionReverse(wayID, options) { } - return function(graph) { - var way = graph.entity(wayID); + function reverseWay(graph, way) { var nodes = way.nodes.slice().reverse(); var tags = {}; var role; @@ -120,5 +119,29 @@ export function actionReverse(wayID, options) { // the way itself with the reversed node ids and updated way tags return reverseNodeTags(graph, nodes) .replace(way.update({nodes: nodes, tags: tags})); + } + + + var action = function(graph) { + var entity = graph.entity(entityID); + if (entity.type === 'way') { + return reverseWay(graph, entity); + } + return reverseNodeTags(graph, [entityID]); }; + + action.disabled = function(graph) { + var entity = graph.hasEntity(entityID); + if (!entity || entity.type === 'way') return false; + + for (var key in entity.tags) { + var value = entity.tags[key]; + if (reverseKey(key) !== key || reverseValue(key, value) !== value) { + return false; + } + } + return 'nondirectional_node'; + }; + + return action; } diff --git a/modules/operations/reverse.js b/modules/operations/reverse.js index 0f0a0952b..89958bafb 100644 --- a/modules/operations/reverse.js +++ b/modules/operations/reverse.js @@ -7,13 +7,36 @@ export function operationReverse(selectedIDs, context) { var entityID = selectedIDs[0]; var operation = function() { - context.perform(actionReverse(entityID), operation.annotation()); + context.perform(action(), operation.annotation()); context.validator().validate(); }; + function action() { + return actionReverse(entityID); + } - operation.available = function() { - return selectedIDs.length === 1 && context.geometry(entityID) === 'line'; + function isNode() { + var entity = context.hasEntity(entityID); + return entity && entity.type === 'node'; + } + + + operation.available = function(situation) { + if (situation === 'toolbar') { + if (!selectedIDs.some(function(id) { + var entity = context.hasEntity(id); + return entity && entity.type === 'way' && (entity.isOneWay() || entity.isSided()); + })) { + return false; + } + } + if (selectedIDs.length !== 1) return false; + + var geometry = context.geometry(entityID); + if (geometry !== 'line' && geometry !== 'vertex' && geometry !== 'point') { + return false; + } + return action().disabled(context.graph()) === false; }; @@ -23,12 +46,14 @@ export function operationReverse(selectedIDs, context) { operation.tooltip = function() { - return t('operations.reverse.description'); + var id = isNode() ? 'node.description.single' : 'description'; + return t('operations.reverse.' + id); }; operation.annotation = function() { - return t('operations.reverse.annotation'); + var id = isNode() ? 'node.annotation.single' : 'annotation'; + return t('operations.reverse.' + id); }; diff --git a/test/spec/actions/reverse.js b/test/spec/actions/reverse.js index 6ed6ca10c..c95d3397b 100644 --- a/test/spec/actions/reverse.js +++ b/test/spec/actions/reverse.js @@ -511,4 +511,10 @@ describe('iD.actionReverse', function () { }); }); + it('reverses directional values on nodes', function () { + var node1 = iD.osmNode({ tags: { 'direction': 'forward' } }); + var graph = iD.actionReverse(node1.id)(iD.coreGraph([node1])); + expect(graph.entity(node1.id).tags).to.eql({ 'direction': 'backward' }); + }); + });