mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
Extract self-intersection code to geoHasSelfIntersections
Test for self-intersecting areas in both drag_node and draw_way
This commit is contained in:
+2
-1
@@ -1,6 +1,7 @@
|
||||
/* Cursors */
|
||||
|
||||
.nope {
|
||||
.nope,
|
||||
.nope * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '../actions';
|
||||
|
||||
import { behaviorDraw } from './draw';
|
||||
import { geoChooseEdge } from '../geo';
|
||||
import { geoChooseEdge, geoHasSelfIntersections } from '../geo';
|
||||
import { modeBrowse, modeSelect } from '../modes';
|
||||
import { osmNode } from '../osm';
|
||||
|
||||
@@ -39,25 +39,48 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
// - `behavior/draw.js` `click()`
|
||||
// - `behavior/draw_way.js` `move()`
|
||||
function move(datum) {
|
||||
var loc;
|
||||
var target = datum && datum.id && context.hasEntity(datum.id);
|
||||
var nodeGroups = datum && datum.properties && datum.properties.nodes;
|
||||
var loc = context.map().mouseCoordinates();
|
||||
|
||||
if (datum.loc) { // snap to node/vertex - a real entity or a nope target with a `loc`
|
||||
loc = datum.loc;
|
||||
} else if (target && target.type === 'way') { // snap to way
|
||||
var choice = geoChooseEdge(
|
||||
context.childNodes(target), context.mouse(), context.projection, end.id
|
||||
);
|
||||
if (choice) {
|
||||
loc = choice.loc;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loc) {
|
||||
loc = context.map().mouseCoordinates();
|
||||
} else if (nodeGroups) { // snap to way - a line touch target or nope target with nodes
|
||||
var best = Infinity;
|
||||
for (var i = 0; i < nodeGroups.length; i++) {
|
||||
var childNodes = nodeGroups[i].map(function(id) { return context.entity(id); });
|
||||
var choice = geoChooseEdge(childNodes, context.mouse(), context.projection, end.id);
|
||||
if (choice && choice.distance < best) {
|
||||
best = choice.distance;
|
||||
loc = choice.loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.replace(actionMoveNode(end.id, loc));
|
||||
end = context.entity(end.id);
|
||||
|
||||
// check if this movement causes the geometry to break
|
||||
var doBlock = invalidGeometry(end, context.graph());
|
||||
context.surface()
|
||||
.classed('nope', doBlock);
|
||||
}
|
||||
|
||||
|
||||
function invalidGeometry(entity, graph) {
|
||||
var parents = graph.parentWays(entity);
|
||||
|
||||
for (var 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)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +170,10 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
|
||||
// Accept the current position of the drawing node and continue drawing.
|
||||
drawWay.add = function(loc, datum) {
|
||||
if (datum && datum.id && /-nope/.test(datum.id)) return; // can't click here
|
||||
if ((datum && datum.id && /-nope$/.test(datum.id)) ||
|
||||
context.surface().classed('nope')) {
|
||||
return; // can't click here
|
||||
}
|
||||
|
||||
context.pop(_tempEdits);
|
||||
_tempEdits = 0;
|
||||
@@ -163,6 +189,10 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
|
||||
// Connect the way to an existing way.
|
||||
drawWay.addWay = function(loc, edge) {
|
||||
if (context.surface().classed('nope')) {
|
||||
return; // can't click here
|
||||
}
|
||||
|
||||
context.pop(_tempEdits);
|
||||
_tempEdits = 0;
|
||||
|
||||
@@ -178,6 +208,10 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
|
||||
// Connect the way to an existing node and continue drawing.
|
||||
drawWay.addNode = function(node) {
|
||||
if (context.surface().classed('nope')) {
|
||||
return; // can't click here
|
||||
}
|
||||
|
||||
context.pop(_tempEdits);
|
||||
_tempEdits = 0;
|
||||
|
||||
@@ -194,6 +228,10 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
// If the way has enough nodes to be valid, it's selected.
|
||||
// Otherwise, delete everything and return to browse mode.
|
||||
drawWay.finish = function() {
|
||||
if (context.surface().classed('nope')) {
|
||||
return; // can't click here
|
||||
}
|
||||
|
||||
context.pop(_tempEdits);
|
||||
_tempEdits = 0;
|
||||
|
||||
@@ -224,6 +262,9 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
context.map().dblclickEnable(true);
|
||||
}, 1000);
|
||||
|
||||
context.surface()
|
||||
.classed('nope', false);
|
||||
|
||||
context.enter(modeBrowse(context));
|
||||
};
|
||||
|
||||
|
||||
+38
-2
@@ -5,6 +5,7 @@ import {
|
||||
geoVecAngle,
|
||||
geoVecCross,
|
||||
geoVecDot,
|
||||
geoVecEqual,
|
||||
geoVecInterp,
|
||||
geoVecLength,
|
||||
geoVecSubtract
|
||||
@@ -38,7 +39,7 @@ export function geoRotate(points, angle, around) {
|
||||
// projection onto that edge, if such a projection exists, or the distance to
|
||||
// the closest vertex on that edge. Returns an object with the `index` of the
|
||||
// chosen edge, the chosen `loc` on that edge, and the `distance` to to it.
|
||||
export function geoChooseEdge(nodes, point, projection, skipID) {
|
||||
export function geoChooseEdge(nodes, point, projection, activeID) {
|
||||
var dist = geoVecLength;
|
||||
var points = nodes.map(function(n) { return projection(n.loc); });
|
||||
var ids = nodes.map(function(n) { return n.id; });
|
||||
@@ -47,7 +48,7 @@ export function geoChooseEdge(nodes, point, projection, skipID) {
|
||||
var loc;
|
||||
|
||||
for (var i = 0; i < points.length - 1; i++) {
|
||||
if (ids[i] === skipID || ids[i + 1] === skipID) continue;
|
||||
if (ids[i] === activeID || ids[i + 1] === activeID) continue;
|
||||
|
||||
var o = points[i];
|
||||
var s = geoVecSubtract(points[i + 1], o);
|
||||
@@ -79,6 +80,41 @@ export function geoChooseEdge(nodes, point, projection, skipID) {
|
||||
}
|
||||
|
||||
|
||||
// check active (dragged or drawing) segments against inactive segments
|
||||
export function geoHasSelfIntersections(nodes, activeID) {
|
||||
var actives = [];
|
||||
var inactives = [];
|
||||
var j, k;
|
||||
|
||||
for (j = 0; j < nodes.length - 1; j++) {
|
||||
var n1 = nodes[j];
|
||||
var n2 = 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 (geoVecEqual(p[1], q[0]) || geoVecEqual(p[0], q[1]) ||
|
||||
geoVecEqual(p[0], q[0]) || geoVecEqual(p[1], q[1]) ) {
|
||||
continue;
|
||||
} else if (geoLineIntersection(p, q)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Return the intersection point of 2 line segments.
|
||||
// From https://github.com/pgkelley4/line-segments-intersect
|
||||
// This uses the vector cross product approach described below:
|
||||
|
||||
@@ -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 { geoHasSelfIntersections } from './geom.js';
|
||||
export { geoRotate } from './geom.js';
|
||||
export { geoLineIntersection } from './geom.js';
|
||||
export { geoPathIntersections } from './geom.js';
|
||||
|
||||
@@ -20,8 +20,7 @@ import {
|
||||
|
||||
import {
|
||||
geoChooseEdge,
|
||||
geoLineIntersection,
|
||||
geoVecEqual,
|
||||
geoHasSelfIntersections,
|
||||
geoVecSubtract,
|
||||
geoViewportEdge
|
||||
} from '../geo';
|
||||
@@ -133,7 +132,6 @@ export function modeDragNode(context) {
|
||||
var currPoint = (d3_event && d3_event.point) || context.projection(_lastLoc);
|
||||
var currMouse = geoVecSubtract(currPoint, nudge);
|
||||
var loc = context.projection.invert(currMouse);
|
||||
var didSnap = false;
|
||||
|
||||
if (!_nudgeInterval) { // If not nudging at the edge of the viewport, try to snap..
|
||||
// related code
|
||||
@@ -141,21 +139,19 @@ export function modeDragNode(context) {
|
||||
// - `behavior/draw.js` `click()`
|
||||
// - `behavior/draw_way.js` `move()`
|
||||
var d = datum();
|
||||
var nodegroups = d && d.properties && d.properties.nodes;
|
||||
var nodeGroups = d && d.properties && d.properties.nodes;
|
||||
|
||||
if (d.loc) { // snap to node/vertex - a real entity or a nope target with a `loc`
|
||||
loc = d.loc;
|
||||
didSnap = true;
|
||||
|
||||
} else if (nodegroups) { // snap to way - a line touch target or nope target with nodes
|
||||
} else if (nodeGroups) { // snap to way - a line touch target or nope target with nodes
|
||||
var best = Infinity;
|
||||
for (var i = 0; i < nodegroups.length; i++) {
|
||||
var childNodes = nodegroups[i].map(function(id) { return context.entity(id); });
|
||||
for (var i = 0; i < nodeGroups.length; i++) {
|
||||
var childNodes = nodeGroups[i].map(function(id) { return context.entity(id); });
|
||||
var choice = geoChooseEdge(childNodes, context.mouse(), context.projection, entity.id);
|
||||
if (choice && choice.distance < best) {
|
||||
best = choice.distance;
|
||||
loc = choice.loc;
|
||||
didSnap = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,11 +164,7 @@ export function modeDragNode(context) {
|
||||
|
||||
|
||||
// check if this movement causes the geometry to break
|
||||
var doBlock = false;
|
||||
if (!didSnap) {
|
||||
doBlock = invalidGeometry(entity, context.graph());
|
||||
}
|
||||
|
||||
var doBlock = invalidGeometry(entity, context.graph());
|
||||
context.surface()
|
||||
.classed('nope', doBlock);
|
||||
|
||||
@@ -183,41 +175,11 @@ export function modeDragNode(context) {
|
||||
function invalidGeometry(entity, graph) {
|
||||
var parents = graph.parentWays(entity);
|
||||
|
||||
function hasSelfIntersections(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 (geoVecEqual(p[1], q[0]) || geoVecEqual(p[0], q[1]) ||
|
||||
geoVecEqual(p[0], q[0]) || geoVecEqual(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 (hasSelfIntersections(parent, entity.id)) {
|
||||
var nodes = parent.nodes.map(function(nodeID) { return graph.entity(nodeID); });
|
||||
if (parent.isClosed()) {
|
||||
if (geoHasSelfIntersections(nodes, entity.id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user