mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-18 17:13:31 +00:00
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;
|
|
};
|