mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-18 14:45:12 +02:00
Redo iD.geo.chooseIndex
It now calculates proper orthogonal projections and finds the minimal one. Rename it iD.geo.chooseEdge and minimize its coupling to context. This version also copes with 0- and 1-node ways. Fixes #1428.
This commit is contained in:
@@ -58,7 +58,7 @@ iD.behavior.Draw = function(context) {
|
||||
function click() {
|
||||
var d = datum();
|
||||
if (d.type === 'way') {
|
||||
var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context),
|
||||
var choice = iD.geo.chooseEdge(context.childNodes(d), d3.mouse(context.surface().node()), context.projection),
|
||||
edge = [d.nodes[choice.index - 1], d.nodes[choice.index]];
|
||||
event.clickWay(choice.loc, edge);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
|
||||
} else if (datum.type === 'node') {
|
||||
loc = datum.loc;
|
||||
} else if (datum.type === 'way') {
|
||||
loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
|
||||
loc = iD.geo.chooseEdge(context.childNodes(datum), d3.mouse(context.surface().node()), context.projection).loc;
|
||||
}
|
||||
|
||||
context.replace(iD.actions.MoveNode(end.id, loc));
|
||||
|
||||
+36
-12
@@ -15,24 +15,48 @@ iD.geo.dist = function(a, b) {
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
};
|
||||
|
||||
iD.geo.chooseIndex = function(way, point, context) {
|
||||
// Choose the edge with the minimal distance from `point` to its orthogonal
|
||||
// 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.
|
||||
iD.geo.chooseEdge = function(nodes, point, projection) {
|
||||
var dist = iD.geo.dist,
|
||||
graph = context.graph(),
|
||||
nodes = graph.childNodes(way),
|
||||
projNodes = nodes.map(function(n) { return context.projection(n.loc); });
|
||||
points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
min = Infinity,
|
||||
idx, loc;
|
||||
|
||||
for (var i = 0, changes = []; i < projNodes.length - 1; i++) {
|
||||
changes[i] =
|
||||
(dist(projNodes[i], point) + dist(point, projNodes[i + 1])) /
|
||||
dist(projNodes[i], projNodes[i + 1]);
|
||||
function dot(p, q) {
|
||||
return p[0] * q[0] + p[1] * q[1];
|
||||
}
|
||||
|
||||
var idx = _.indexOf(changes, _.min(changes)),
|
||||
ratio = dist(projNodes[idx], point) / dist(projNodes[idx], projNodes[idx + 1]),
|
||||
loc = iD.geo.interp(nodes[idx].loc, nodes[idx + 1].loc, ratio);
|
||||
for (var i = 0; i < points.length - 1; i++) {
|
||||
var o = points[i],
|
||||
s = [points[i + 1][0] - o[0],
|
||||
points[i + 1][1] - o[1]],
|
||||
v = [point[0] - o[0],
|
||||
point[1] - o[1]],
|
||||
proj = dot(v, s) / dot(s, s),
|
||||
p;
|
||||
|
||||
if (proj < 0) {
|
||||
p = o;
|
||||
} else if (proj > 1) {
|
||||
p = points[i + 1];
|
||||
} else {
|
||||
p = [o[0] + proj * s[0], o[1] + proj * s[1]];
|
||||
}
|
||||
|
||||
var d = dist(p, point);
|
||||
if (d < min) {
|
||||
min = d;
|
||||
idx = i + 1;
|
||||
loc = projection.invert(p);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
index: idx + 1,
|
||||
index: idx,
|
||||
distance: min,
|
||||
loc: loc
|
||||
};
|
||||
};
|
||||
|
||||
@@ -88,6 +88,10 @@ window.iD = function () {
|
||||
return history.graph().entity(id);
|
||||
};
|
||||
|
||||
context.childNodes = function(way) {
|
||||
return history.graph().childNodes(way);
|
||||
};
|
||||
|
||||
context.geometry = function(id) {
|
||||
return context.entity(id).geometry(history.graph());
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ iD.modes.DragNode = function(context) {
|
||||
if (d.type === 'node' && d.id !== entity.id) {
|
||||
loc = d.loc;
|
||||
} else if (d.type === 'way') {
|
||||
loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc;
|
||||
loc = iD.geo.chooseEdge(context.childNodes(d), d3.mouse(context.surface().node()), context.projection).loc;
|
||||
}
|
||||
|
||||
context.replace(
|
||||
@@ -115,7 +115,7 @@ iD.modes.DragNode = function(context) {
|
||||
var d = datum();
|
||||
|
||||
if (d.type === 'way') {
|
||||
var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context);
|
||||
var choice = iD.geo.chooseEdge(context.childNodes(d), d3.mouse(context.surface().node()), context.projection);
|
||||
context.replace(
|
||||
iD.actions.AddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity),
|
||||
connectAnnotation(d));
|
||||
|
||||
@@ -131,8 +131,8 @@ iD.modes.Select = function(context, selection) {
|
||||
datum = target.datum();
|
||||
|
||||
if (datum instanceof iD.Way && !target.classed('fill')) {
|
||||
var choice = iD.geo.chooseIndex(datum,
|
||||
d3.mouse(context.surface().node()), context),
|
||||
var choice = iD.geo.chooseEdge(context.childNodes(datum),
|
||||
d3.mouse(context.surface().node()), context.projection),
|
||||
node = iD.Node();
|
||||
|
||||
var prev = datum.nodes[choice.index - 1],
|
||||
|
||||
@@ -20,11 +20,11 @@ iD.ui.preset.address = function(field, context) {
|
||||
var loc = context.projection([
|
||||
(extent[0][0] + extent[1][0]) / 2,
|
||||
(extent[0][1] + extent[1][1]) / 2]),
|
||||
closest = context.projection(iD.geo.chooseIndex(d, loc, context).loc);
|
||||
choice = iD.geo.chooseEdge(context.childNodes(d), loc, context.projection);
|
||||
return {
|
||||
title: d.tags.name,
|
||||
value: d.tags.name,
|
||||
dist: iD.geo.dist(closest, loc)
|
||||
dist: choice.distance
|
||||
};
|
||||
}).sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
|
||||
@@ -36,6 +36,77 @@ describe('iD.geo', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.chooseEdge', function() {
|
||||
var projection = function (l) { return l; };
|
||||
projection.invert = projection;
|
||||
|
||||
it('returns undefined properties for a degenerate way (no nodes)', function() {
|
||||
expect(iD.geo.chooseEdge([], [0, 0], projection)).to.eql({
|
||||
index: undefined,
|
||||
distance: Infinity,
|
||||
loc: undefined
|
||||
})
|
||||
});
|
||||
|
||||
it('returns undefined properties for a degenerate way (single node)', function() {
|
||||
expect(iD.geo.chooseEdge([iD.Node({loc: [0, 0]})], [0, 0], projection)).to.eql({
|
||||
index: undefined,
|
||||
distance: Infinity,
|
||||
loc: undefined
|
||||
})
|
||||
});
|
||||
|
||||
it('calculates the orthogonal projection of a point onto a segment', function() {
|
||||
// a --*--- b
|
||||
// |
|
||||
// c
|
||||
//
|
||||
// * = [2, 0]
|
||||
var a = [0, 0],
|
||||
b = [5, 0],
|
||||
c = [2, 1],
|
||||
nodes = [
|
||||
iD.Node({loc: a}),
|
||||
iD.Node({loc: b})
|
||||
];
|
||||
|
||||
var choice = iD.geo.chooseEdge(nodes, c, projection);
|
||||
expect(choice.index).to.eql(1);
|
||||
expect(choice.distance).to.eql(1);
|
||||
expect(choice.loc).to.eql([2, 0]);
|
||||
});
|
||||
|
||||
it('returns the starting vertex when the orthogonal projection is < 0', function() {
|
||||
var a = [0, 0],
|
||||
b = [5, 0],
|
||||
c = [-3, 4],
|
||||
nodes = [
|
||||
iD.Node({loc: a}),
|
||||
iD.Node({loc: b})
|
||||
];
|
||||
|
||||
var choice = iD.geo.chooseEdge(nodes, c, projection);
|
||||
expect(choice.index).to.eql(1);
|
||||
expect(choice.distance).to.eql(5);
|
||||
expect(choice.loc).to.eql([0, 0]);
|
||||
});
|
||||
|
||||
it('returns the ending vertex when the orthogonal projection is > 1', function() {
|
||||
var a = [0, 0],
|
||||
b = [5, 0],
|
||||
c = [8, 4],
|
||||
nodes = [
|
||||
iD.Node({loc: a}),
|
||||
iD.Node({loc: b})
|
||||
];
|
||||
|
||||
var choice = iD.geo.chooseEdge(nodes, c, projection);
|
||||
expect(choice.index).to.eql(1);
|
||||
expect(choice.distance).to.eql(5);
|
||||
expect(choice.loc).to.eql([5, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.pointInPolygon', function() {
|
||||
it('says a point in a polygon is on a polygon', function() {
|
||||
var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
|
||||
|
||||
Reference in New Issue
Block a user