Check for valid multipolygon geometry when dragging nodes

(this can get a bit expensive for large/complex multipolygons)
This commit is contained in:
Bryan Housel
2017-12-31 02:26:19 -05:00
parent 1bd41b894c
commit 9c27893748
4 changed files with 68 additions and 17 deletions

View File

@@ -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));
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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]];