modules/actions/reverse.js (138 lines of code) (raw):
/*
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.)
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.
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 actionReverse(entityID, options) {
var ignoreKey = /^.*(_|:)?(description|name|note|website|ref|source|comment|watch|attribution)(_|:)?/;
var numeric = /^([+\-]?)(?=[\d.])/;
var directionKey = /direction$/;
var turn_lanes = /^turn:lanes:?/;
var keyReplacements = [
[/:right$/, ':left'],
[/:left$/, ':right'],
[/:forward$/, ':backward'],
[/:backward$/, ':forward'],
[/:right:/, ':left:'],
[/:left:/, ':right:'],
[/:forward:/, ':backward:'],
[/:backward:/, ':forward:']
];
var valueReplacements = {
left: 'right',
right: 'left',
up: 'down',
down: 'up',
forward: 'backward',
backward: 'forward',
forwards: 'backward',
backwards: 'forward',
};
var roleReplacements = {
forward: 'backward',
backward: 'forward',
forwards: 'backward',
backwards: 'forward'
};
var onewayReplacements = {
yes: '-1',
'1': '-1',
'-1': 'yes'
};
var compassReplacements = {
N: 'S',
NNE: 'SSW',
NE: 'SW',
ENE: 'WSW',
E: 'W',
ESE: 'WNW',
SE: 'NW',
SSE: 'NNW',
S: 'N',
SSW: 'NNE',
SW: 'NE',
WSW: 'ENE',
W: 'E',
WNW: 'ESE',
NW: 'SE',
NNW: 'SSE'
};
function reverseKey(key) {
for (var i = 0; i < keyReplacements.length; ++i) {
var replacement = keyReplacements[i];
if (replacement[0].test(key)) {
return key.replace(replacement[0], replacement[1]);
}
}
return key;
}
function reverseValue(key, value, includeAbsolute) {
if (ignoreKey.test(key)) return value;
// Turn lanes are left/right to key (not way) direction - #5674
if (turn_lanes.test(key)) {
return value;
} else if (key === 'incline' && numeric.test(value)) {
return value.replace(numeric, function(_, sign) { return sign === '-' ? '' : '-'; });
} else if (options && options.reverseOneway && key === 'oneway') {
return onewayReplacements[value] || value;
} else if (includeAbsolute && directionKey.test(key)) {
if (compassReplacements[value]) return compassReplacements[value];
var degrees = parseFloat(value);
if (typeof degrees === 'number' && !isNaN(degrees)) {
if (degrees < 180) {
degrees += 180;
} else {
degrees -= 180;
}
return degrees.toString();
}
}
return valueReplacements[value] || value;
}
// Reverse the direction of tags attached to the nodes - #3076
function reverseNodeTags(graph, nodeIDs) {
for (var i = 0; i < nodeIDs.length; i++) {
var node = graph.hasEntity(nodeIDs[i]);
if (!node || !Object.keys(node.tags).length) continue;
var tags = {};
for (var key in node.tags) {
tags[reverseKey(key)] = reverseValue(key, node.tags[key], node.id === entityID);
}
graph = graph.replace(node.update({tags: tags}));
}
return graph;
}
function reverseWay(graph, way) {
var nodes = way.nodes.slice().reverse();
var tags = {};
var role;
for (var 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 = roleReplacements[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 reverseNodeTags(graph, nodes)
.replace(way.update({nodes: nodes, tags: tags}));
}
var action = function(graph) {
var entity = graph.entity(entityID);
if (entity.type === 'way') {
return reverseWay(graph, entity);
}
return reverseNodeTags(graph, [entityID]);
};
action.disabled = function(graph) {
var entity = graph.hasEntity(entityID);
if (!entity || entity.type === 'way') return false;
for (var key in entity.tags) {
var value = entity.tags[key];
if (reverseKey(key) !== key || reverseValue(key, value, true) !== value) {
return false;
}
}
return 'nondirectional_node';
};
action.entityID = function() {
return entityID;
};
return action;
}