mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 09:42:56 +00:00
@@ -1,31 +1,145 @@
|
||||
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
|
||||
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
|
||||
iD.actions.Move = function(ids, delta, projection) {
|
||||
function addNodes(ids, nodes, graph) {
|
||||
ids.forEach(function(id) {
|
||||
iD.actions.Move = function(moveIds, delta, projection, cache) {
|
||||
|
||||
function addIds(ids, nodes, ways, graph) {
|
||||
_.each(ids, function(id) {
|
||||
var entity = graph.entity(id);
|
||||
|
||||
if (entity.type === 'node') {
|
||||
nodes.push(id);
|
||||
nodes.push(entity);
|
||||
} else if (entity.type === 'way') {
|
||||
nodes.push.apply(nodes, entity.nodes);
|
||||
ways.push(entity);
|
||||
addIds(entity.nodes, nodes, ways, graph);
|
||||
} else {
|
||||
addNodes(_.pluck(entity.members, 'id'), nodes, graph);
|
||||
addIds(_.pluck(entity.members, 'id'), nodes, ways, graph);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Place a vertex where the moved vertex used to be, to preserve way shape..
|
||||
function replaceMovedVertex(nodeId, wayId, graph) {
|
||||
var way = graph.entity(wayId),
|
||||
moved = graph.entity(nodeId),
|
||||
movedIndex = way.nodes.indexOf(nodeId),
|
||||
len, prevIndex, nextIndex;
|
||||
|
||||
if (way.isClosed()) {
|
||||
len = way.nodes.length - 1;
|
||||
prevIndex = (movedIndex + len - 1) % len;
|
||||
nextIndex = (movedIndex + len + 1) % len;
|
||||
} else {
|
||||
len = way.nodes.length;
|
||||
prevIndex = movedIndex - 1;
|
||||
nextIndex = movedIndex + 1;
|
||||
}
|
||||
|
||||
var prev = graph.hasEntity(way.nodes[prevIndex]),
|
||||
next = graph.hasEntity(way.nodes[nextIndex]);
|
||||
|
||||
// Don't add orig vertex at endpoint..
|
||||
if (!prev || !next) return graph;
|
||||
|
||||
var orig = iD.Node({loc: cache.startLoc[nodeId]}),
|
||||
angle = Math.abs(iD.geo.angle(orig, prev, projection) -
|
||||
iD.geo.angle(orig, next, projection)) * 180 / Math.PI;
|
||||
|
||||
// Don't add orig vertex if it would just make a straight line..
|
||||
if (angle > 175 && angle < 185) return graph;
|
||||
|
||||
// moving forward or backward along way?
|
||||
var p1 = [prev.loc, orig.loc, moved.loc, next.loc].map(projection),
|
||||
p2 = [prev.loc, moved.loc, orig.loc, next.loc].map(projection),
|
||||
d1 = iD.geo.pathLength(p1),
|
||||
d2 = iD.geo.pathLength(p2),
|
||||
insertAt = (d1 < d2) ? movedIndex : nextIndex;
|
||||
|
||||
// moving around closed loop?
|
||||
if (way.isClosed() && insertAt === 0) insertAt = len;
|
||||
|
||||
way = way.addNode(orig.id, insertAt);
|
||||
return graph.replace(orig).replace(way);
|
||||
}
|
||||
|
||||
function isEndpoint(id, way) {
|
||||
return !way.isClosed() && way.affix(id);
|
||||
}
|
||||
|
||||
function cleanupWays(movedWays, graph) {
|
||||
_.each(movedWays, function(movedWay) {
|
||||
var movedVertices = _.filter(graph.childNodes(movedWay),
|
||||
function(vertex) { return (graph.parentWays(vertex).length === 2); });
|
||||
|
||||
_.each(movedVertices, function(movedVertex) {
|
||||
var id = movedVertex.id,
|
||||
loc = movedVertex.loc,
|
||||
firstTime = !cache.lastEdge[id],
|
||||
way = _.find(graph.parentWays(movedVertex),
|
||||
function(way) { return way.id !== movedWay.id; });
|
||||
|
||||
if (firstTime) {
|
||||
graph = replaceMovedVertex(id, way.id, graph);
|
||||
way = graph.entity(way.id);
|
||||
}
|
||||
|
||||
// get closest edge on connected way..
|
||||
var nodes = _.without(graph.childNodes(way), movedVertex);
|
||||
if (way.isClosed() && way.first() === id) nodes.push(nodes[0]);
|
||||
|
||||
var lastEdge = cache.lastEdge[id],
|
||||
currEdge = iD.geo.chooseEdge(nodes, projection(loc), projection);
|
||||
|
||||
// zorro happened, reorder nodes..
|
||||
if (lastEdge && lastEdge.index !== currEdge.index) {
|
||||
way = way.removeNode(id).addNode(id, currEdge.index);
|
||||
graph = graph.replace(way);
|
||||
}
|
||||
|
||||
// snap movedVertex to edge of connected way..
|
||||
if (!isEndpoint(id, way)) {
|
||||
graph = graph.replace(movedVertex.move(currEdge.loc));
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// extend search to a connected way beyond end of way?
|
||||
// don't mess up points between two intersections
|
||||
|
||||
cache.lastEdge[id] = currEdge;
|
||||
|
||||
});
|
||||
});
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Don't move a vertex where >2 ways meet, unless all parentWays are moving too..
|
||||
function canMove(entity, graph) {
|
||||
var parents = graph.parentWays(entity);
|
||||
if (parents.length < 3) return true;
|
||||
|
||||
return _.all(_.pluck(parents, 'id'), function(id) {
|
||||
return _.contains(moveIds, id);
|
||||
});
|
||||
}
|
||||
|
||||
var action = function(graph) {
|
||||
var nodes = [];
|
||||
if (_.isEqual(delta, [0,0])) return graph;
|
||||
|
||||
addNodes(ids, nodes, graph);
|
||||
var nodes = [],
|
||||
ways = [];
|
||||
|
||||
_.uniq(nodes).forEach(function(id) {
|
||||
var node = graph.entity(id),
|
||||
start = projection(node.loc),
|
||||
addIds(moveIds, nodes, ways, graph);
|
||||
nodes = _.filter(nodes, function(node) { return canMove(node, graph); });
|
||||
|
||||
_.uniq(nodes).forEach(function(node) {
|
||||
var start = projection(node.loc),
|
||||
end = projection.invert([start[0] + delta[0], start[1] + delta[1]]);
|
||||
graph = graph.replace(node.move(end));
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
graph = cleanupWays(_.uniq(ways), graph);
|
||||
}
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
||||
@@ -35,7 +149,7 @@ iD.actions.Move = function(ids, delta, projection) {
|
||||
return entity.type === 'relation' && !entity.isComplete(graph);
|
||||
}
|
||||
|
||||
if (_.any(ids, incompleteRelation))
|
||||
if (_.any(moveIds, incompleteRelation))
|
||||
return 'incomplete_relation';
|
||||
};
|
||||
|
||||
|
||||
@@ -9,9 +9,31 @@ iD.modes.Move = function(context, entityIDs) {
|
||||
annotation = entityIDs.length === 1 ?
|
||||
t('operations.move.annotation.' + context.geometry(entityIDs[0])) :
|
||||
t('operations.move.annotation.multiple'),
|
||||
cache,
|
||||
origin,
|
||||
nudgeInterval;
|
||||
|
||||
function clearCache() {
|
||||
cache = {
|
||||
startLoc: {},
|
||||
lastEdge: {}
|
||||
};
|
||||
}
|
||||
|
||||
function cacheLocs(ids) {
|
||||
_.each(ids, function(id) {
|
||||
var entity = context.entity(id);
|
||||
|
||||
if (entity.type === 'node') {
|
||||
cache.startLoc[id] = entity.loc;
|
||||
} else if (entity.type === 'way') {
|
||||
cacheLocs(entity.nodes);
|
||||
} else {
|
||||
cacheLocs(_.pluck(entity.members, 'id'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function edge(point, size) {
|
||||
var pad = [30, 100, 30, 100];
|
||||
if (point[0] > size[0] - pad[0]) return [-10, 0];
|
||||
@@ -26,7 +48,7 @@ iD.modes.Move = function(context, entityIDs) {
|
||||
nudgeInterval = window.setInterval(function() {
|
||||
context.pan(nudge);
|
||||
context.replace(
|
||||
iD.actions.Move(entityIDs, [-nudge[0], -nudge[1]], context.projection),
|
||||
iD.actions.Move(entityIDs, [-nudge[0], -nudge[1]], context.projection, cache),
|
||||
annotation);
|
||||
var c = context.projection(origin);
|
||||
origin = context.projection.invert([c[0] - nudge[0], c[1] - nudge[1]]);
|
||||
@@ -53,7 +75,7 @@ iD.modes.Move = function(context, entityIDs) {
|
||||
origin = context.map().mouseCoordinates();
|
||||
|
||||
context.replace(
|
||||
iD.actions.Move(entityIDs, delta, context.projection),
|
||||
iD.actions.Move(entityIDs, delta, context.projection, cache),
|
||||
annotation);
|
||||
}
|
||||
|
||||
@@ -76,6 +98,9 @@ iD.modes.Move = function(context, entityIDs) {
|
||||
}
|
||||
|
||||
mode.enter = function() {
|
||||
clearCache();
|
||||
cacheLocs(entityIDs);
|
||||
|
||||
context.install(edit);
|
||||
|
||||
context.perform(
|
||||
|
||||
Reference in New Issue
Block a user