From d5bf2d9762920b4b27d91380250a226fc3c088f8 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 24 Jan 2018 23:16:51 -0500 Subject: [PATCH] Add geoHasLineIntersections, better for testing multipolygon rings (closes #4741) --- modules/geo/geom.js | 48 ++++++++++++++++++++++++- modules/geo/index.js | 1 + modules/modes/drag_node.js | 4 +-- test/spec/geo/geom.js | 73 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/modules/geo/geom.js b/modules/geo/geom.js index ed097c375..d9eec87a0 100644 --- a/modules/geo/geom.js +++ b/modules/geo/geom.js @@ -80,12 +80,57 @@ export function geoChooseEdge(nodes, point, projection, activeID) { } -// check active (dragged or drawing) segments against inactive segments +// Test active (dragged or drawing) segments against inactive segments +// This is used to test e.g. multipolygon rings that cross +// `activeNodes` is the ring containing the activeID being dragged. +// `inactiveNodes` is the other ring to test against +export function geoHasLineIntersections(activeNodes, inactiveNodes, activeID) { + var actives = []; + var inactives = []; + var j, k, n1, n2, segment; + + // gather active segments (only segments in activeNodes that contain the activeID) + for (j = 0; j < activeNodes.length - 1; j++) { + n1 = activeNodes[j]; + n2 = activeNodes[j+1]; + segment = [n1.loc, n2.loc]; + if (n1.id === activeID || n2.id === activeID) { + actives.push(segment); + } + } + + // gather inactive segments + for (j = 0; j < inactiveNodes.length - 1; j++) { + n1 = inactiveNodes[j]; + n2 = inactiveNodes[j+1]; + segment = [n1.loc, n2.loc]; + inactives.push(segment); + } + + // test + for (j = 0; j < actives.length; j++) { + for (k = 0; k < inactives.length; k++) { + var p = actives[j]; + var q = inactives[k]; + var hit = geoLineIntersection(p, q); + if (hit) { + return true; + } + } + } + + return false; +} + + +// Test active (dragged or drawing) segments against inactive segments +// This is used to test whether a way intersects with itself. export function geoHasSelfIntersections(nodes, activeID) { var actives = []; var inactives = []; var j, k; + // group active and passive segments along the nodes for (j = 0; j < nodes.length - 1; j++) { var n1 = nodes[j]; var n2 = nodes[j+1]; @@ -97,6 +142,7 @@ export function geoHasSelfIntersections(nodes, activeID) { } } + // test for (j = 0; j < actives.length; j++) { for (k = 0; k < inactives.length; k++) { var p = actives[j]; diff --git a/modules/geo/index.js b/modules/geo/index.js index 0c80b12d6..8252336ab 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -13,6 +13,7 @@ export { geoZoomToScale } from './geo.js'; export { geoAngle } from './geom.js'; export { geoChooseEdge } from './geom.js'; export { geoEdgeEqual } from './geom.js'; +export { geoHasLineIntersections } from './geom.js'; export { geoHasSelfIntersections } from './geom.js'; export { geoRotate } from './geom.js'; export { geoLineIntersection } from './geom.js'; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index fc4b77276..a824c4f81 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -24,8 +24,8 @@ import { import { geoChooseEdge, + geoHasLineIntersections, geoHasSelfIntersections, - geoPathHasIntersections, geoVecSubtract, geoViewportEdge } from '../geo'; @@ -245,7 +245,7 @@ export function modeDragNode(context) { if (k === activeIndex) continue; // make sure active ring doesnt cross passive rings - if (geoPathHasIntersections(rings[activeIndex].coords, rings[k].coords)) { + if (geoHasLineIntersections(rings[activeIndex].nodes, rings[k].nodes, entity.id)) { return true; } } diff --git a/test/spec/geo/geom.js b/test/spec/geo/geom.js index 188c738fd..e93a3b53b 100644 --- a/test/spec/geo/geom.js +++ b/test/spec/geo/geom.js @@ -160,6 +160,79 @@ describe('iD.geo - geometry', function() { }); }); + describe('geoHasLineIntersections', function() { + it('returns false for a degenerate way (no nodes)', function() { + expect(iD.geoHasLineIntersections([], '')).to.be.false; + }); + + it('returns false if no activeID', function() { + var a = iD.osmNode({id: 'a', loc: [2, 2]}); + var b = iD.osmNode({id: 'b', loc: [4, 2]}); + var c = iD.osmNode({id: 'c', loc: [4, 4]}); + var d = iD.osmNode({id: 'd', loc: [2, 4]}); + var nodes = [a, b, c, d, a]; + expect(iD.geoHasLineIntersections(nodes, '')).to.be.false; + }); + + it('returns false if there are no intersections', function() { + // e --------- f + // | | + // | a --- b | + // | | | | + // | | | | + // | d --- c | + // | | + // h --------- g + var a = iD.osmNode({id: 'a', loc: [2, 2]}); + var b = iD.osmNode({id: 'b', loc: [4, 2]}); + var c = iD.osmNode({id: 'c', loc: [4, 4]}); + var d = iD.osmNode({id: 'd', loc: [2, 4]}); + var e = iD.osmNode({id: 'e', loc: [0, 0]}); + var f = iD.osmNode({id: 'f', loc: [8, 0]}); + var g = iD.osmNode({id: 'g', loc: [8, 8]}); + var h = iD.osmNode({id: 'h', loc: [0, 8]}); + var inner = [a, b, c, d, a]; + var outer = [e, f, g, h, e]; + expect(iD.geoHasLineIntersections(inner, outer, 'a')).to.be.false; + expect(iD.geoHasLineIntersections(inner, outer, 'b')).to.be.false; + expect(iD.geoHasLineIntersections(inner, outer, 'c')).to.be.false; + expect(iD.geoHasLineIntersections(inner, outer, 'd')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'e')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'f')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'g')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'h')).to.be.false; + }); + + it('returns true if the activeID is causing intersections', function() { + // e --------- f + // | | + // | a --------- b + // | | |/ + // | | /| + // | d --- c | + // | | + // h --------- g + var a = iD.osmNode({id: 'a', loc: [2, 2]}); + var b = iD.osmNode({id: 'b', loc: [10, 2]}); + var c = iD.osmNode({id: 'c', loc: [4, 4]}); + var d = iD.osmNode({id: 'd', loc: [2, 4]}); + var e = iD.osmNode({id: 'e', loc: [0, 0]}); + var f = iD.osmNode({id: 'f', loc: [8, 0]}); + var g = iD.osmNode({id: 'g', loc: [8, 8]}); + var h = iD.osmNode({id: 'h', loc: [0, 8]}); + var inner = [a, b, c, d, a]; + var outer = [e, f, g, h, e]; + expect(iD.geoHasLineIntersections(inner, outer, 'a')).to.be.true; + expect(iD.geoHasLineIntersections(inner, outer, 'b')).to.be.true; + expect(iD.geoHasLineIntersections(inner, outer, 'c')).to.be.true; + expect(iD.geoHasLineIntersections(inner, outer, 'd')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'e')).to.be.false; + expect(iD.geoHasLineIntersections(outer, inner, 'f')).to.be.true; + expect(iD.geoHasLineIntersections(outer, inner, 'g')).to.be.true; + expect(iD.geoHasLineIntersections(outer, inner, 'h')).to.be.false; + }); + }); + describe('geoHasSelfIntersections', function() { it('returns false for a degenerate way (no nodes)', function() { expect(iD.geoHasSelfIntersections([], '')).to.be.false;