diff --git a/modules/actions/straighten.js b/modules/actions/straighten.js index 68cb482b8..c09187375 100644 --- a/modules/actions/straighten.js +++ b/modules/actions/straighten.js @@ -1,5 +1,6 @@ import { actionDeleteNode } from './delete_node'; import _difference from 'lodash-es/difference'; +import _filter from 'lodash-es/filter'; import { geoVecInterp, @@ -17,12 +18,12 @@ export function actionStraighten(selectedIDs, projection) { (Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); } - // Return all ways as a continuous, ordered array of nodes - var allNodes = function(graph) { + // Return all selected ways as a continuous, ordered array of nodes + function allNodes(graph) { var nodes = [], startNodes = [], endNodes = [], - ways = {}, + remainingWays = [], selectedWays = selectedIDs.filter(function(w) { return graph.entity(w).type === 'way'; }), @@ -32,27 +33,47 @@ export function actionStraighten(selectedIDs, projection) { for (var i = 0; i < selectedWays.length; i++) { var way = graph.entity(selectedWays[i]); - nodes = graph.childNodes(way); - ways[nodes[0].id] = nodes; + nodes = way.nodes.slice(0); + remainingWays.push(nodes); startNodes.push(nodes[0]); endNodes.push(nodes[nodes.length-1]); } - var startNode = _difference(startNodes, endNodes)[0], - endNode = _difference(endNodes, startNodes)[0]; + // Remove duplicate end/startNodes (duplicate nodes cannot be at the line end, + // and need to be removed so currNode _difference calculation below works) + // i.e. ["n-1", "n-1", "n-2"] => ["n-2"] + startNodes = _filter(startNodes, function(n) { + return startNodes.indexOf(n) == startNodes.lastIndexOf(n); + }); + endNodes = _filter(endNodes, function(n) { + return endNodes.indexOf(n) == endNodes.lastIndexOf(n); + }); - nodes = ways[startNode.id]; + // Choose the initial endpoint to start from + var currNode = _difference(startNodes, endNodes).concat(_difference(endNodes, startNodes))[0], + nextWay = []; + nodes = []; - // Add nodes to end of array, until endNode matches end of array - while (nodes[nodes.length-1] !== endNode) { - var currEndNode = nodes[nodes.length-1]; - nodes = nodes.concat(ways[currEndNode.id]); + // Add nodes to end of nodes array, until all ways are added + while (remainingWays.length) { + nextWay = _filter(remainingWays, function(way) { + return way[0] == currNode || way[way.length-1] == currNode; + })[0]; + + remainingWays = _difference(remainingWays, [nextWay]); + + if (nextWay[0] != currNode) { + nextWay.reverse(); + } + nodes = nodes.concat(nextWay); + + currNode = nodes[nodes.length-1]; } // If user selected 2 nodes to straighten between, then slice nodes array to those nodes - if (selectedNodes.length) { - var startNodeIdx = nodes.indexOf(graph.entity(selectedNodes[0])), - endNodeIdx = nodes.indexOf(graph.entity(selectedNodes[1])), + if (selectedNodes.length == 2) { + var startNodeIdx = nodes.indexOf(selectedNodes[0]), + endNodeIdx = nodes.indexOf(selectedNodes[1]), sortedStartEnd = [startNodeIdx, endNodeIdx]; sortedStartEnd.sort(function(a, b) { @@ -62,10 +83,9 @@ export function actionStraighten(selectedIDs, projection) { nodes = nodes.slice(sortedStartEnd[0], sortedStartEnd[1]+1); } - return nodes; + return nodes.map(function(n) { return graph.entity(n); }); }; - var action = function(graph, t) { if (t === null || !isFinite(t)) t = 1; t = Math.min(Math.max(+t, 0), 1); @@ -137,7 +157,6 @@ export function actionStraighten(selectedIDs, projection) { } }; - action.transitionable = true; diff --git a/modules/operations/straighten.js b/modules/operations/straighten.js index 73525af27..7765186fa 100644 --- a/modules/operations/straighten.js +++ b/modules/operations/straighten.js @@ -17,6 +17,7 @@ export function operationStraighten(selectedIDs, context) { operation.available = function() { + console.log("Running operation.available.."); var nodes = [], startNodes = [], endNodes = [], @@ -42,11 +43,17 @@ export function operationStraighten(selectedIDs, context) { endNodes.push(entity.nodes[entity.nodes.length-1]); } + // Remove duplicate end/startNodes (duplicate nodes cannot be at the line end) + // i.e. ["n-1", "n-1", "n-2"] => ["n-2"] + startNodes = startNodes.filter(n => startNodes.indexOf(n) == startNodes.lastIndexOf(n)); + endNodes = endNodes.filter(n => endNodes.indexOf(n) == endNodes.lastIndexOf(n)); + + // Return false if line is only 2 nodes long + // Return false unless exactly 0 or 2 specific nodes are selected if (_uniq(nodes).length <= 2 || !_includes([0,2], selectedNodes.length)) return false; - // Ensure all ways are connected (i.e. only one unique start point and one unique end point) - if (_difference(startNodes, endNodes).length !== 1 || - _difference(endNodes, startNodes).length !== 1) return false; + // Ensure all ways are connected (i.e. only 2 unique endpoints/startpoints) + if (_difference(startNodes, endNodes).length + _difference(endNodes, startNodes).length !== 2) return false; // Ensure both selected nodes lie on the selected path if (selectedNodes.length && (!_includes(nodes, selectedNodes[0]) || @@ -57,6 +64,7 @@ export function operationStraighten(selectedIDs, context) { operation.disabled = function() { + console.log("Running operation.disabled.."); var reason; for (var i = 0; i < selectedIDs.length; i++) { if (context.hasHiddenConnections(selectedIDs[i])) { @@ -68,6 +76,7 @@ export function operationStraighten(selectedIDs, context) { operation.tooltip = function() { + console.log("Running operation.tooltip"); var disable = operation.disabled(); return disable ? t('operations.straighten.' + disable) : diff --git a/test/spec/actions/straighten.js b/test/spec/actions/straighten.js index 9cb4fa426..17bb0dbff 100644 --- a/test/spec/actions/straighten.js +++ b/test/spec/actions/straighten.js @@ -102,6 +102,28 @@ describe('iD.actionStraighten', function () { expect(graph.hasEntity('g')).to.eq(undefined); }); + it('straightens multiple, connected ways going in different directions', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}), + + iD.Node({id: 'e', loc: [4, 0]}), + iD.Node({id: 'f', loc: [5, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'g', loc: [6, -0.01]}), + iD.Node({id: 'h', loc: [7, 0]}), + iD.Way({id: '--', nodes: ['h', 'g', 'f', 'e', 'd']}) + ]); + + graph = iD.actionStraighten(['-', '--'], projection)(graph); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'd']); + expect(graph.entity('--').nodes).to.eql(['h', 'f', 'd']); + expect(graph.entity('f').loc[0]).to.be.closeTo(5, 1e-6); + expect(graph.entity('f').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.hasEntity('g')).to.eq(undefined); + }); describe('transitions', function () { it('is transitionable', function() { diff --git a/test/spec/operations/straighten.js b/test/spec/operations/straighten.js index 86d341771..8f699d9b8 100644 --- a/test/spec/operations/straighten.js +++ b/test/spec/operations/straighten.js @@ -29,9 +29,11 @@ describe('iD.operationStraighten', function () { iD.osmNode({ id: 'n10', type: 'node' }), iD.osmNode({ id: 'n11', type: 'node' }), iD.osmNode({ id: 'n12', type: 'node' }), + iD.osmNode({ id: 'n13', type: 'node' }), iD.osmWay({ id: 'w1', nodes: ['n1', 'n2'] }), iD.osmWay({ id: 'w1-2', nodes: ['n2', 'n2-1'] }), iD.osmWay({ id: 'w2', nodes: ['n2', 'n3', 'n4'] }), + iD.osmWay({ id: 'w2-2', nodes: ['n4', 'n13', 'n2'] }), // w-2 reversed iD.osmWay({ id: 'w3', nodes: ['n4', 'n5', 'n6'] }), iD.osmWay({ id: 'w4', nodes: ['n6', 'n7', 'n8'] }), iD.osmWay({ id: 'w5', nodes: ['n9', 'n10', 'n11', 'n12'] }), @@ -78,6 +80,11 @@ describe('iD.operationStraighten', function () { expect(result).to.be.ok; }); + it('is available for selected, continuous ways with different way-directions', function () { + var result = iD.operationStraighten(['w1', 'w3', 'w2-2'], fakeContext.graph()).available(); + expect(result).to.be.ok; + }); + it('is available for 2 selected nodes in the same way, more than one node apart', function () { var result = iD.operationStraighten(['w5', 'n9', 'n11'], fakeContext.graph()).available(); expect(result).to.be.ok; @@ -93,6 +100,11 @@ describe('iD.operationStraighten', function () { expect(result).to.be.ok; }); + it('is available for 2 selected nodes in non-adjacent, non-same-directional ways, providing inbetween ways are selected', function () { + var result = iD.operationStraighten(['n2', 'n7', 'w4', 'w1', 'w3', 'w2-2'], fakeContext.graph()).available(); + expect(result).to.be.ok; + }); + it('is not available for nodes not on selected ways', function () { var result = iD.operationStraighten(['w5', 'n4', 'n11'], fakeContext.graph()).available(); expect(result).to.be.not.ok;