From a0744d234bf95c6fa06c701b366912bd060b4c59 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 16 Jun 2015 23:10:54 -0400 Subject: [PATCH] in addTargets, DRY code and support snapping shape to nearby nodes --- js/id/behavior/draw_way.js | 155 +++++++++++++++++++++---------------- js/id/modes/add_line.js | 2 +- 2 files changed, 90 insertions(+), 67 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index afef396797..485e3a318c 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -42,32 +42,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { } - function ReplaceTemporaryNode(newNode, newNode2) { - return function(graph) { - if (isOrthogonal) { - var newWay = way - .addNode(newNode.id, -1) - .addNode(newNode2.id, -1); - return graph - .replace(newWay) - .remove(ortho1) - .remove(ortho2); - - } else if (isClosed) { - return graph - .replace(way.addNode(newNode.id, index)) - .remove(end); - - } else { - return graph - .replace(graph.entity(wayId).addNode(newNode.id, index)) - .remove(end) - .remove(segment) - .remove(start); - } - }; - } - function move(targets) { for (var i = 0; i < targets.length; i++) { var entity = targets[i].entity, @@ -77,7 +51,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { selfWay = (isOrthogonal || isClosed) ? wayId : segment.id; if (entity) { // snap to target entity unless it's self.. - // if (isOrthogonal) debugger; if (entity.type === 'node' && entity.id !== selfNode) { loc = entity.loc; } else if (entity.type === 'way' && entity.id !== selfWay) { @@ -147,55 +120,87 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { }; - // For now, orthogonal mode only, assume targets.length === 2 + // Add multiple click targets, snapping to target entities if necessary.. drawWay.addTargets = function(targets) { - // Avoid creating orthogonal shapes with duplicate nodes.. - for (var i = 0; i < targets.length; i++) { - var t = targets[i]; - if (!t.entity) continue; - if (t.entity.id === way.nodes[0] || t.entity.id === way.nodes[1]) return; - } + function vecAdd(a, b) { return [a[0] + b[0], a[1] + b[1]]; } - var entity, newNode1, newNode2, choice, edge; - - // targets[0] - entity = targets[0].entity; - if (entity) { - entity = context.entity(entity.id); - if (entity.type === 'node') { - newNode1 = entity; - } else if (entity.type === 'way') { - choice = iD.geo.chooseEdge(context.childNodes(entity), targets[0].point, context.projection); - edge = [entity.nodes[choice.index - 1], entity.nodes[choice.index]]; - newNode1 = iD.Node({loc: choice.loc}); - context.replace(iD.actions.AddMidpoint({ loc: choice.loc, edge: edge}, newNode1)); - } - } else { - newNode1 = iD.Node({loc: targets[0].loc}); - context.replace(iD.actions.AddEntity(newNode1)); + var newIds = [], + entity, target, choice, edge, newNode, i; + + // Avoid creating orthogonal shapes with duplicate nodes (i.e. a line).. + for (i = 0; i < targets.length; i++) { + entity = targets[i].entity; + if (!entity) continue; + // For now, orthogonal mode only.. + if (entity.id === way.nodes[0] || entity.id === way.nodes[1]) return; } + // For each target: create a node, or snap to an existing entity.. + for (i = 0; i < targets.length; i++) { + target = targets[i]; + entity = target.entity; + + if (entity) { + // Get latest.. If multiple nodes snap to the same target entity, + // it may have been modified during this loop.. + entity = context.entity(entity.id); + + if (entity.type === 'node') { + newIds.push(entity.id); + } else if (entity.type === 'way') { + choice = iD.geo.chooseEdge(context.childNodes(entity), target.point, context.projection); + edge = [entity.nodes[choice.index - 1], entity.nodes[choice.index]]; + newNode = iD.Node({loc: choice.loc}); + context.replace(iD.actions.AddMidpoint({ loc: choice.loc, edge: edge}, newNode)); + newIds.push(newNode.id); + } - // targets[1] - entity = targets[1].entity; - if (entity) { - entity = context.entity(entity.id); - if (entity.type === 'node') { - newNode2 = entity; - } else if (entity.type === 'way') { - choice = iD.geo.chooseEdge(context.childNodes(entity), targets[1].point, context.projection); - edge = [entity.nodes[choice.index - 1], entity.nodes[choice.index]]; - newNode2 = iD.Node({loc: choice.loc}); - context.replace(iD.actions.AddMidpoint({ loc: choice.loc, edge: edge}, newNode2)); + } else { + newNode = iD.Node({loc: target.loc}); + context.replace(iD.actions.AddEntity(newNode)); + newIds.push(newNode.id); } - } else { - newNode2 = iD.Node({loc: targets[1].loc}); - context.replace(iD.actions.AddEntity(newNode2)); } + // Replace temporary nodes.. + context.replace(function(graph) { + var newWay = way; + for (var i = 0; i < newIds.length; i++) { + newWay = newWay.addNode(newIds[i], -1); + } + // For now, orthogonal mode only.. + return graph + .replace(newWay) + .remove(ortho1) + .remove(ortho2); + } + ); + + // Try to snap other nearby nodes onto the new shape.. + if (!d3.event.altKey) { + var pad = 3, // pad extent by a few screen pixels + proj = context.projection, + newWay = context.entity(wayId), + extent = newWay.extent(context.graph()), + min = vecAdd( proj(extent[0]), [-pad, pad] ), + max = vecAdd( proj(extent[1]), [ pad, -pad] ), + padExtent = iD.geo.Extent(proj.invert(min), proj.invert(max)); + + var testNodes = context.intersects(padExtent).filter(function (entity) { + return entity.type === 'node' && newWay.nodes.indexOf(entity.id) === -1; + }); + + for (i = 0; i < testNodes.length; i++) { + choice = iD.geo.chooseEdge(context.childNodes(newWay), proj(testNodes[i].loc), proj); + if (choice.distance < pad) { + edge = [newWay.nodes[choice.index - 1], newWay.nodes[choice.index]]; + context.replace(iD.actions.AddMidpoint({ loc: choice.loc, edge: edge}, testNodes[i])); + newWay = context.entity(wayId); + } + } + } context.perform( - ReplaceTemporaryNode(newNode1, newNode2), iD.actions.ChangeTags(wayId, {building:'yes'}), // just for show, remove later.. annotation); @@ -203,6 +208,24 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(iD.modes.Browse(context)); // just for show, replace with Select later.. }; + + function ReplaceTemporaryNode(newNode) { + return function(graph) { + if (isClosed) { + return graph + .replace(way.addNode(newNode.id, index)) + .remove(end); + + } else { + return graph + .replace(graph.entity(wayId).addNode(newNode.id, index)) + .remove(end) + .remove(segment) + .remove(start); + } + }; + } + // Accept the current position of the temporary node and continue drawing. drawWay.add = function(loc) { // prevent duplicate nodes diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 599b07ff95..b1e071502e 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -57,7 +57,7 @@ iD.modes.AddLine = function(context, option) { } function startFromNode(node) { - var graph = context.graph(), + var baseGraph = context.graph(), way = iD.Way(); if (mode.option === 'orthogonal') {