mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-19 17:43:39 +00:00
iD.geo.euclideanDistance should only be used for calculations of projected coordinates or display (pixel) coordinates. iD.geo.sphericalDistance calculates approximate geographical distances, accounting for distortions at higher latitudes. This can be used for determining the nearest node (operations.Delete, actions.Circularize) or relative length comparisons (actions.Split).
110 lines
4.3 KiB
JavaScript
110 lines
4.3 KiB
JavaScript
iD.actions.Circularize = function(wayId, projection, maxAngle) {
|
|
maxAngle = (maxAngle || 20) * Math.PI / 180;
|
|
|
|
var action = function(graph) {
|
|
var way = graph.entity(wayId),
|
|
nodes = _.uniq(graph.childNodes(way)),
|
|
keyNodes = nodes.filter(function(n) { return graph.parentWays(n).length != 1; }),
|
|
points = nodes.map(function(n) { return projection(n.loc); }),
|
|
keyPoints = keyNodes.map(function(n) { return projection(n.loc); }),
|
|
centroid = d3.geom.polygon(points).centroid(),
|
|
radius = d3.median(points, function(p) { return iD.geo.euclideanDistance(centroid, p); }),
|
|
sign = d3.geom.polygon(points).area() > 0 ? 1 : -1,
|
|
ids;
|
|
|
|
// we need atleast two key nodes for the algorithm to work
|
|
if (!keyNodes.length) {
|
|
keyNodes = [nodes[0]];
|
|
keyPoints = [points[0]];
|
|
}
|
|
|
|
if (keyNodes.length == 1) {
|
|
var index = nodes.indexOf(keyNodes[0]),
|
|
oppositeIndex = Math.floor((index + nodes.length / 2) % nodes.length);
|
|
|
|
keyNodes.push(nodes[oppositeIndex]);
|
|
keyPoints.push(points[oppositeIndex]);
|
|
}
|
|
|
|
// key points and nodes are those connected to the ways,
|
|
// they are projected onto the circle, inbetween nodes are moved
|
|
// to constant internals between key nodes, extra inbetween nodes are
|
|
// added if necessary.
|
|
for (var i = 0; i < keyPoints.length; i++) {
|
|
var nextKeyNodeIndex = (i + 1) % keyNodes.length,
|
|
startNodeIndex = nodes.indexOf(keyNodes[i]),
|
|
endNodeIndex = nodes.indexOf(keyNodes[nextKeyNodeIndex]),
|
|
numberNewPoints = -1,
|
|
indexRange = endNodeIndex - startNodeIndex,
|
|
distance, totalAngle, eachAngle, startAngle, endAngle,
|
|
angle, loc, node, j;
|
|
|
|
if (indexRange < 0) {
|
|
indexRange += nodes.length;
|
|
}
|
|
|
|
// position this key node
|
|
distance = iD.geo.euclideanDistance(centroid, keyPoints[i]);
|
|
keyPoints[i] = [
|
|
centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius,
|
|
centroid[1] + (keyPoints[i][1] - centroid[1]) / distance * radius];
|
|
graph = graph.replace(keyNodes[i].move(projection.invert(keyPoints[i])));
|
|
|
|
// figure out the between delta angle we want to match to
|
|
startAngle = Math.atan2(keyPoints[i][1] - centroid[1], keyPoints[i][0] - centroid[0]);
|
|
endAngle = Math.atan2(keyPoints[nextKeyNodeIndex][1] - centroid[1], keyPoints[nextKeyNodeIndex][0] - centroid[0]);
|
|
totalAngle = endAngle - startAngle;
|
|
|
|
// detects looping around -pi/pi
|
|
if (totalAngle*sign > 0) {
|
|
totalAngle = -sign * (2 * Math.PI - Math.abs(totalAngle));
|
|
}
|
|
|
|
do {
|
|
numberNewPoints++;
|
|
eachAngle = totalAngle / (indexRange + numberNewPoints);
|
|
} while (Math.abs(eachAngle) > maxAngle);
|
|
|
|
// move existing points
|
|
for (j = 1; j < indexRange; j++) {
|
|
angle = startAngle + j * eachAngle;
|
|
loc = projection.invert([
|
|
centroid[0] + Math.cos(angle)*radius,
|
|
centroid[1] + Math.sin(angle)*radius]);
|
|
|
|
node = nodes[(j + startNodeIndex) % nodes.length].move(loc);
|
|
graph = graph.replace(node);
|
|
}
|
|
|
|
// add new inbetween nodes if necessary
|
|
for (j = 0; j < numberNewPoints; j++) {
|
|
angle = startAngle + (indexRange + j) * eachAngle;
|
|
loc = projection.invert([
|
|
centroid[0] + Math.cos(angle) * radius,
|
|
centroid[1] + Math.sin(angle) * radius]);
|
|
|
|
node = iD.Node({loc: loc});
|
|
graph = graph.replace(node);
|
|
|
|
nodes.splice(endNodeIndex + j, 0, node);
|
|
}
|
|
}
|
|
|
|
// update the way to have all the new nodes
|
|
ids = nodes.map(function(n) { return n.id; });
|
|
ids.push(ids[0]);
|
|
|
|
way = way.update({nodes: ids});
|
|
graph = graph.replace(way);
|
|
|
|
return graph;
|
|
};
|
|
|
|
action.disabled = function(graph) {
|
|
if (!graph.entity(wayId).isClosed())
|
|
return 'not_closed';
|
|
};
|
|
|
|
return action;
|
|
};
|