From 009d7b0d65343195ccf2525d1361308fb9eaf1d0 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Sun, 24 Dec 2017 09:21:32 -0500 Subject: [PATCH] Add layer blocker and polygon self-intersection geometry check --- css/20_map.css | 2 ++ modules/modes/drag_node.js | 74 ++++++++++++++++++++++++++++++++++++-- modules/svg/blocker.js | 26 ++++++++++++++ modules/svg/index.js | 1 + 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 modules/svg/blocker.js diff --git a/css/20_map.css b/css/20_map.css index 6eec27261..3b7225dd2 100644 --- a/css/20_map.css +++ b/css/20_map.css @@ -16,6 +16,7 @@ /* `.target` objects are interactive */ /* They can be picked up, clicked, hovered, or things can connect to them */ +.layer-blocker.target, .node.target { pointer-events: fill; fill-opacity: 0.8; @@ -34,6 +35,7 @@ } /* `.target-nope` objects are explicitly forbidden to join to */ +.layer-blocker.target.target-nope, .node.target.target-nope, .way.target.target-nope { cursor: not-allowed; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 9218527f1..e76180f47 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -12,10 +12,23 @@ import { actionNoop } from '../actions'; -import { behaviorEdit, behaviorHover, behaviorDrag } from '../behavior'; -import { geoChooseEdge, geoVecSubtract, geoViewportEdge } from '../geo'; +import { + behaviorEdit, + behaviorHover, + behaviorDrag +} from '../behavior'; + +import { + geoChooseEdge, + geoLineIntersection, + geoVecEquals, + geoVecSubtract, + geoViewportEdge +} from '../geo'; + import { modeBrowse, modeSelect } from './index'; import { osmNode } from '../osm'; +import { svgBlocker } from '../svg'; import { uiFlash } from '../ui'; @@ -27,6 +40,7 @@ export function modeDragNode(context) { var hover = behaviorHover(context).altDisables(true) .on('hover', context.ui().sidebar.hover); var edit = behaviorEdit(context); + var blocker = svgBlocker(context.projection, context); var _nudgeInterval; var _restoreSelectedIDs = []; @@ -147,10 +161,63 @@ export function modeDragNode(context) { moveAnnotation(entity) ); + + checkGeometry(entity); _lastLoc = loc; } + function checkGeometry(entity) { + var doBlock = false; + var graph = context.graph(); + var parents = graph.parentWays(entity); + + function checkSelfIntersections(way, activeID) { + // check active (dragged) segments against inactive segments + var actives = []; + var inactives = []; + var j, k; + for (j = 0; j < way.nodes.length - 1; j++) { + var n1 = graph.entity(way.nodes[j]); + var n2 = graph.entity(way.nodes[j+1]); + var segment = [n1.loc, n2.loc]; + if (n1.id === activeID || n2.id === activeID) { + actives.push(segment); + } else { + inactives.push(segment); + } + } + for (j = 0; j < actives.length; j++) { + for (k = 0; k < inactives.length; k++) { + var p = actives[j]; + var q = inactives[k]; + // skip if segments share an endpoint + if (geoVecEquals(p[1], q[0]) || geoVecEquals(p[0], q[1]) || + geoVecEquals(p[0], q[0]) || geoVecEquals(p[1], q[1]) ) { + continue; + } else if (geoLineIntersection(p, q)) { + return true; + } + } + } + return false; + } + + for (var i = 0; i < parents.length; i++) { + var parent = parents[i]; + if (parent.isClosed()) { // check for self intersections + if (checkSelfIntersections(parent, entity.id)) { + doBlock = true; + break; + } + } + } + + d3_select('.data-layer-osm') + .call(doBlock ? blocker : blocker.off); + } + + function move(entity) { if (_isCancelled) return; @@ -270,6 +337,9 @@ export function modeDragNode(context) { _activeEntity = null; + d3_select('.data-layer-osm') + .call(blocker.off); + context.surface() .selectAll('.active') .classed('active', false); diff --git a/modules/svg/blocker.js b/modules/svg/blocker.js new file mode 100644 index 000000000..93a3a5f1c --- /dev/null +++ b/modules/svg/blocker.js @@ -0,0 +1,26 @@ +export function svgBlocker(projection, context) { + + function blocker(selection) { + var dimensions = projection.clipExtent()[1]; + var fillClass = context.getDebug('target') ? 'red ' : 'nocolor '; + + var blocker = selection.selectAll('.layer-blocker') + .data([{id: 'target-nope'}]); + + blocker.enter() + .append('rect') + .attr('class', 'layer-blocker target target-nope ' + fillClass) + .attr('x', 0) + .attr('y', 0) + .merge(blocker) + .attr('width', dimensions[0]) + .attr('height', dimensions[1]); + } + + blocker.off = function(selection) { + selection.selectAll('.layer-blocker') + .remove(); + }; + + return blocker; +} diff --git a/modules/svg/index.js b/modules/svg/index.js index 68e2f2f28..798893dbb 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -1,4 +1,5 @@ export { svgAreas } from './areas.js'; +export { svgBlocker } from './blocker.js'; export { svgDebug } from './debug.js'; export { svgDefs } from './defs.js'; export { svgGpx } from './gpx.js';