mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
131 lines
5.3 KiB
JavaScript
131 lines
5.3 KiB
JavaScript
import _ from 'lodash';
|
|
/*
|
|
Order the nodes of a way in reverse order and reverse any direction dependent tags
|
|
other than `oneway`. (We assume that correcting a backwards oneway is the primary
|
|
reason for reversing a way.)
|
|
|
|
The following transforms are performed:
|
|
|
|
Keys:
|
|
*:right=* ⟺ *:left=*
|
|
*:forward=* ⟺ *:backward=*
|
|
direction=up ⟺ direction=down
|
|
incline=up ⟺ incline=down
|
|
*=right ⟺ *=left
|
|
|
|
Relation members:
|
|
role=forward ⟺ role=backward
|
|
role=north ⟺ role=south
|
|
role=east ⟺ role=west
|
|
|
|
In addition, numeric-valued `incline` tags are negated.
|
|
|
|
The JOSM implementation was used as a guide, but transformations that were of unclear benefit
|
|
or adjusted tags that don't seem to be used in practice were omitted.
|
|
|
|
Also, each node on the way is examined for its own tags and the following transformations are performed
|
|
in order to ensure associated nodes (eg a Stop Sign) is also reversed
|
|
|
|
Node Keys:
|
|
direction=forward ⟺ direction=backward
|
|
*:forward=* ⟺ *:backward=*
|
|
|
|
References:
|
|
http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right
|
|
http://wiki.openstreetmap.org/wiki/Key:direction#Steps
|
|
http://wiki.openstreetmap.org/wiki/Key:incline
|
|
http://wiki.openstreetmap.org/wiki/Route#Members
|
|
http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
|
|
http://wiki.openstreetmap.org/wiki/Tag:highway%3Dstop
|
|
http://wiki.openstreetmap.org/wiki/Key:traffic_sign#On_a_way_or_area
|
|
*/
|
|
export function Reverse(wayId, options) {
|
|
var replacements = [
|
|
[/:right$/, ':left'], [/:left$/, ':right'],
|
|
[/:forward$/, ':backward'], [/:backward$/, ':forward']
|
|
],
|
|
numeric = /^([+\-]?)(?=[\d.])/,
|
|
roleReversals = {
|
|
forward: 'backward',
|
|
backward: 'forward',
|
|
north: 'south',
|
|
south: 'north',
|
|
east: 'west',
|
|
west: 'east'
|
|
};
|
|
|
|
function reverseKey(key) {
|
|
for (var i = 0; i < replacements.length; ++i) {
|
|
var replacement = replacements[i];
|
|
if (replacement[0].test(key)) {
|
|
return key.replace(replacement[0], replacement[1]);
|
|
}
|
|
}
|
|
return key;
|
|
}
|
|
|
|
function reverseValue(key, value) {
|
|
if (key === 'incline' && numeric.test(value)) {
|
|
return value.replace(numeric, function(_, sign) { return sign === '-' ? '' : '-'; });
|
|
} else if (key === 'incline' || key === 'direction') {
|
|
return {up: 'down', down: 'up'}[value] || value;
|
|
} else if (options && options.reverseOneway && key === 'oneway') {
|
|
return {yes: '-1', '1': '-1', '-1': 'yes'}[value] || value;
|
|
} else {
|
|
return {left: 'right', right: 'left'}[value] || value;
|
|
}
|
|
}
|
|
|
|
function reverseDirectionTags(node) {
|
|
// Update the direction based tags as appropriate then return an updated node
|
|
return node.update({tags: _.transform(node.tags, function(acc, tagValue, tagKey) {
|
|
// See if this is a direction tag and reverse (or use existing value if not forward/backward)
|
|
if (tagKey === 'direction') {
|
|
acc[tagKey] = tagValue === 'forward' ? 'backward'
|
|
: tagValue === 'backward' ? 'forward' : tagValue;
|
|
} else {
|
|
// Use the reverseKey method to cater for situations such as traffic_sign:forward=stop
|
|
// This will pass through other tags unchanged
|
|
acc[reverseKey(tagKey)] = tagValue;
|
|
}
|
|
return acc;
|
|
}, {})});
|
|
}
|
|
|
|
function reverseTagsOnNodes(graph, nodeIds) {
|
|
// Reverse the direction of appropriate tags attached to the nodes (#3076)
|
|
return _(nodeIds)
|
|
// Get each node from the graph
|
|
.map(function(nodeId) { return graph.entity(nodeId);})
|
|
// Check tags on the node, if there aren't any, we can skip
|
|
.filter(function(existingNode) { return existingNode.tags !== undefined;})
|
|
// Get a new version of each node with the appropriate tags reversed
|
|
.map(function(existingNode) { return reverseDirectionTags(existingNode);})
|
|
// Chain together consecutive updates to the graph for each updated node and return
|
|
.reduce(function (accGraph, value) { return accGraph.replace(value); }, graph);
|
|
}
|
|
|
|
return function(graph) {
|
|
var way = graph.entity(wayId),
|
|
nodes = way.nodes.slice().reverse(),
|
|
tags = {}, key, role;
|
|
|
|
for (key in way.tags) {
|
|
tags[reverseKey(key)] = reverseValue(key, way.tags[key]);
|
|
}
|
|
|
|
graph.parentRelations(way).forEach(function(relation) {
|
|
relation.members.forEach(function(member, index) {
|
|
if (member.id === way.id && (role = roleReversals[member.role])) {
|
|
relation = relation.updateMember({role: role}, index);
|
|
graph = graph.replace(relation);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Reverse any associated directions on nodes on the way and then replace
|
|
// the way itself with the reversed node ids and updated way tags
|
|
return reverseTagsOnNodes(graph, nodes).replace(way.update({nodes: nodes, tags: tags}));
|
|
};
|
|
}
|