diff --git a/modules/actions/merge_nodes.js b/modules/actions/merge_nodes.js index 123d3b641..26f73eb00 100644 --- a/modules/actions/merge_nodes.js +++ b/modules/actions/merge_nodes.js @@ -7,7 +7,7 @@ import { geoVecAdd, geoVecScale } from '../geo'; // 1. move all the nodes to a common location // 2. `actionConnect` them -export function actionMergeNodes(nodeIDs) { +export function actionMergeNodes(nodeIDs, loc) { // If there is a single "interesting" node, use that as the location. // Otherwise return the average location of all the nodes. @@ -31,11 +31,16 @@ export function actionMergeNodes(nodeIDs) { var action = function(graph) { if (nodeIDs.length < 2) return graph; - var toLoc = chooseLoc(graph); + var toLoc = loc; + if (!toLoc) { + toLoc = chooseLoc(graph); + } for (var i = 0; i < nodeIDs.length; i++) { var node = graph.entity(nodeIDs[i]); - graph = graph.replace(node.move(toLoc)); + if (node.loc !== toLoc) { + graph = graph.replace(node.move(toLoc)); + } } return actionConnect(nodeIDs)(graph); diff --git a/modules/geo/geo.js b/modules/geo/geo.js index 1e9bee80d..ce239eafe 100644 --- a/modules/geo/geo.js +++ b/modules/geo/geo.js @@ -66,3 +66,23 @@ export function geoZoomToScale(z, tileSize) { return tileSize * Math.pow(2, z) / TAU; } + +// returns info about the node from `nodes` closest to the given `point` +export function geoSphericalClosestNode(nodes, point) { + var minDistance = Infinity, distance; + var indexOfMin; + + for (var i in nodes) { + distance = geoSphericalDistance(nodes[i].loc, point); + if (distance < minDistance) { + minDistance = distance; + indexOfMin = i; + } + } + + if (indexOfMin !== undefined) { + return { index: indexOfMin, distance: minDistance, node: nodes[indexOfMin] }; + } else { + return null; + } +} diff --git a/modules/geo/index.js b/modules/geo/index.js index 5480eb1fd..e27f00cfc 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -7,6 +7,7 @@ export { geoMetersToLon } from './geo.js'; export { geoMetersToOffset } from './geo.js'; export { geoOffsetToMeters } from './geo.js'; export { geoScaleToZoom } from './geo.js'; +export { geoSphericalClosestNode } from './geo.js'; export { geoSphericalDistance } from './geo.js'; export { geoZoomToScale } from './geo.js'; diff --git a/modules/validations/almost_junction.js b/modules/validations/almost_junction.js index fb6b52ab0..31a8755bc 100644 --- a/modules/validations/almost_junction.js +++ b/modules/validations/almost_junction.js @@ -6,14 +6,16 @@ import { geoMetersToLon, geoSphericalDistance, geoVecInterp, - geoHasSelfIntersections + geoHasSelfIntersections, + geoSphericalClosestNode } from '../geo'; import { utilDisplayLabel } from '../util'; import { actionAddMidpoint, - actionChangeTags + actionChangeTags, + actionMergeNodes } from '../actions'; import { t } from '../util/locale'; import { @@ -135,20 +137,30 @@ export function validationAlmostJunction() { extendableNodeInfos.forEach(function(extendableNodeInfo) { var node = extendableNodeInfo.node; var edgeHighway = graph.entity(extendableNodeInfo.wid); - var fixes = [ - new validationIssueFix({ - title: t('issues.fix.connect_almost_junction.title'), - onClick: function() { - var endNode = this.issue.entities[1], - targetEdge = this.issue.info.edge, - crossLoc = this.issue.info.cross_loc; - context.perform( - actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode), - t('issues.fix.connect_almost_junction.undo_redo') - ); - } - }) - ]; + var fixes = [new validationIssueFix({ + title: t('issues.fix.connect_almost_junction.title'), + onClick: function() { + var endNode = this.issue.entities[1], + edgeWay = this.issue.entities[2], + crossLoc = this.issue.info.cross_loc; + var edgeWayNodes = context.graph().childNodes(edgeWay); + var closestNodeInfo = geoSphericalClosestNode(edgeWayNodes, crossLoc); + // if there is already a point nearby, just connect to that + if (closestNodeInfo.distance < 0.8) { + context.perform( + actionMergeNodes([closestNodeInfo.node.id, endNode.id], closestNodeInfo.node.loc), + t('issues.fix.connect_almost_junction.undo_redo') + ); + // else add the end node to the edge way + } else { + var targetEdge = this.issue.info.edge; + context.perform( + actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode), + t('issues.fix.connect_almost_junction.undo_redo') + ); + } + } + })]; if (Object.keys(node.tags).length === 0) { // node has no tags, suggest noexit fix fixes.push(new validationIssueFix({