diff --git a/modules/geo/geom.js b/modules/geo/geom.js index dbef35800..da76cb607 100644 --- a/modules/geo/geom.js +++ b/modules/geo/geom.js @@ -157,6 +157,20 @@ export function geoPathIntersections(path1, path2) { return intersections; } +export function geoPathHasIntersections(path1, path2) { + for (var i = 0; i < path1.length - 1; i++) { + for (var j = 0; j < path2.length - 1; j++) { + var a = [ path1[i], path1[i+1] ]; + var b = [ path2[j], path2[j+1] ]; + var hit = geoLineIntersection(a, b); + if (hit) { + return true; + } + } + } + return false; +} + // Return whether point is contained in polygon. // @@ -195,24 +209,13 @@ export function geoPolygonContainsPolygon(outer, inner) { export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) { - function testSegments(outer, inner) { - for (var i = 0; i < outer.length - 1; i++) { - for (var j = 0; j < inner.length - 1; j++) { - var a = [ outer[i], outer[i + 1] ]; - var b = [ inner[j], inner[j + 1] ]; - if (geoLineIntersection(a, b)) return true; - } - } - return false; - } - function testPoints(outer, inner) { return _some(inner, function(point) { return geoPointInPolygon(point, outer); }); } - return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner)); + return testPoints(outer, inner) || (!!checkSegments && geoPathHasIntersections(outer, inner)); } diff --git a/modules/geo/index.js b/modules/geo/index.js index 982596f04..0c80b12d6 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -16,6 +16,7 @@ export { geoEdgeEqual } from './geom.js'; export { geoHasSelfIntersections } from './geom.js'; export { geoRotate } from './geom.js'; export { geoLineIntersection } from './geom.js'; +export { geoPathHasIntersections } from './geom.js'; export { geoPathIntersections } from './geom.js'; export { geoPathLength } from './geom.js'; export { geoPointInPolygon } from './geom.js'; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 4251fb7a8..fff0f08a0 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -1,3 +1,5 @@ +import _find from 'lodash-es/find'; + import { event as d3_event, select as d3_select @@ -21,12 +23,13 @@ import { import { geoChooseEdge, geoHasSelfIntersections, + geoPathHasIntersections, geoVecSubtract, geoViewportEdge } from '../geo'; import { modeBrowse, modeSelect } from './index'; -import { osmNode } from '../osm'; +import { osmJoinWays, osmNode } from '../osm'; import { uiFlash } from '../ui'; @@ -174,15 +177,53 @@ export function modeDragNode(context) { function invalidGeometry(entity, graph) { var parents = graph.parentWays(entity); + var i, j, k; - for (var i = 0; i < parents.length; i++) { + for (i = 0; i < parents.length; i++) { var parent = parents[i]; - var nodes = parent.nodes.map(function(nodeID) { return graph.entity(nodeID); }); - if (parent.isClosed()) { - if (geoHasSelfIntersections(nodes, entity.id)) { + var nodes = []; + var activeIndex = null; // which multipolygon ring contains node being dragged + + // test any parent multipolygons for valid geometry + var relations = graph.parentRelations(parent); + for (j = 0; j < relations.length; j++) { + if (!relations[j].isMultipolygon()) continue; + + var rings = osmJoinWays(relations[j].members, graph); + + // find active ring and test it for self intersections + for (k = 0; k < rings.length; k++) { + nodes = rings[k].nodes; + if (_find(nodes, function(n) { return n.id === entity.id; })) { + activeIndex = k; + if (geoHasSelfIntersections(nodes, entity.id)) { + return true; + } + } + rings[k].coords = nodes.map(function(n) { return n.loc; }); + } + + // test active ring for intersections with other rings in the multipolygon + for (k = 0; k < rings.length; k++) { + if (k === activeIndex) continue; + + // make sure active ring doesnt cross passive rings + if (geoPathHasIntersections(rings[activeIndex].coords, rings[k].coords)) { + return true; + } + } + } + + + // If we still haven't tested this node's parent way for self-intersections. + // (because it's not a member of a multipolygon), test it now. + if (activeIndex !== null && parent.isClosed()) { + nodes = parent.nodes.map(function(nodeID) { return graph.entity(nodeID); }); + if (nodes.length && geoHasSelfIntersections(nodes, entity.id)) { return true; } } + } return false; diff --git a/test/spec/geo/geom.js b/test/spec/geo/geom.js index 3acf7f940..188c738fd 100644 --- a/test/spec/geo/geom.js +++ b/test/spec/geo/geom.js @@ -342,6 +342,12 @@ describe('iD.geo - geometry', function() { expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.true; }); + it('returns false when inner polygon fully contains outer', function() { + var inner = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var outer = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.false; + }); + it('returns true when outer polygon partially contains inner (some vertices contained)', function() { var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];