Add support for complex intersection and via way restrictions

This commit is contained in:
Bryan Housel
2018-02-02 19:58:09 -05:00
parent db0858f7d2
commit c1378a141f
11 changed files with 1771 additions and 1435 deletions
+1 -1
View File
@@ -1836,7 +1836,7 @@ input[type=number] {
.form-field-restrictions .preset-input-wrap {
position: relative;
height: 300px;
height: 400px;
}
.form-field-restrictions svg.surface {
+20 -53
View File
@@ -1,9 +1,6 @@
import { actionSplit } from './split';
import {
osmInferRestriction,
osmRelation,
osmWay
osmRelation
} from '../osm';
@@ -11,21 +8,20 @@ import {
//
// {
// from: { node: <node ID>, way: <way ID> },
// via: { node: <node ID> },
// via: { node: <node ID>, ways: [<way ID>,<way ID>,...] },
// to: { node: <node ID>, way: <way ID> },
// restriction: <'no_right_turn', 'no_left_turn', etc.>
// }
//
// This specifies a restriction of type `restriction` when traveling from
// `from.node` in `from.way` toward `to.node` in `to.way` via `via.node`.
// `from.node` in `from.way` toward `to.node` in `to.way` via `via.node` OR `via.ways`.
// (The action does not check that these entities form a valid intersection.)
//
// If `restriction` is not provided, it is automatically determined by
// osmInferRestriction.
//
// If necessary, the `from` and `to` ways are split. In these cases, `from.node`
// and `to.node` are used to determine which portion of the split ways become
// members of the restriction.
// From, to, and via ways should be split before calling this action.
// (old versions of the code would split the ways here, but we no longer do it)
//
// For testing convenience, accepts an ID to assign to the new relation.
// Normally, this will be undefined and the relation will automatically
@@ -34,47 +30,23 @@ import {
export function actionRestrictTurn(turn, projection, restrictionId) {
return function(graph) {
var from = graph.entity(turn.from.way),
via = graph.entity(turn.via.node),
to = graph.entity(turn.to.way);
var fromWay = graph.entity(turn.from.way);
var toWay = graph.entity(turn.to.way);
var viaNode = turn.via.node && graph.entity(turn.via.node);
var viaWays = turn.via.ways && turn.via.ways.map(function(id) { return graph.entity(id); });
var members = [];
function isClosingNode(way, nodeId) {
return nodeId === way.first() && nodeId === way.last();
members.push({ id: fromWay.id, type: 'way', role: 'from' });
if (viaNode) {
members.push({ id: viaNode.id, type: 'node', role: 'via' });
} else if (viaWays) {
viaWays.forEach(function(viaWay) {
members.push({ id: viaWay.id, type: 'way', role: 'via' });
});
}
function split(toOrFrom) {
var newID = toOrFrom.newID || osmWay().id;
graph = actionSplit(via.id, [newID])
.limitWays([toOrFrom.way])(graph);
var a = graph.entity(newID),
b = graph.entity(toOrFrom.way);
if (a.nodes.indexOf(toOrFrom.node) !== -1) {
return [a, b];
} else {
return [b, a];
}
}
if (!from.affix(via.id) || isClosingNode(from, via.id)) {
if (turn.from.node === turn.to.node) {
// U-turn
from = to = split(turn.from)[0];
} else if (turn.from.way === turn.to.way) {
// Straight-on or circular
var s = split(turn.from);
from = s[0];
to = s[1];
} else {
// Other
from = split(turn.from)[0];
}
}
if (!to.affix(via.id) || isClosingNode(to, via.id)) {
to = split(turn.to)[0];
}
members.push({ id: toWay.id, type: 'way', role: 'to' });
return graph.replace(osmRelation({
id: restrictionId,
@@ -84,15 +56,10 @@ export function actionRestrictTurn(turn, projection, restrictionId) {
osmInferRestriction(
graph,
turn.from,
turn.via,
turn.to,
projection)
},
members: [
{id: from.id, type: 'way', role: 'from'},
{id: via.id, type: 'node', role: 'via'},
{id: to.id, type: 'way', role: 'to'}
]
members: members
}));
};
}
+4 -1
View File
@@ -53,7 +53,10 @@ export function actionSplit(nodeId, newWayIds) {
}
function dist(nA, nB) {
return geoSphericalDistance(graph.entity(nA).loc, graph.entity(nB).loc);
var locA = graph.entity(nA).loc;
var locB = graph.entity(nB).loc;
var epsilon = 1e-6;
return (locA && locB) ? geoSphericalDistance(locA, locB) : epsilon;
}
// calculate lengths
+522 -161
View File
@@ -1,195 +1,556 @@
import _each from 'lodash-es/each';
import _clone from 'lodash-es/clone';
import _every from 'lodash-es/every';
import _extend from 'lodash-es/extend';
import _find from 'lodash-es/find';
import _indexOf from 'lodash-es/indexOf';
import _keys from 'lodash-es/keys';
import _values from 'lodash-es/values';
import _uniq from 'lodash-es/uniq';
import { geoAngle } from '../geo/index';
import { osmWay } from './way';
import {
actionDeleteRelation,
actionReverse,
actionSplit
} from '../actions';
import { coreGraph } from '../core';
import { geoAngle, geoSphericalDistance } from '../geo';
import { osmEntity } from './entity';
export function osmTurn(turn) {
if (!(this instanceof osmTurn))
if (!(this instanceof osmTurn)) {
return new osmTurn(turn);
}
_extend(this, turn);
}
export function osmIntersection(graph, vertexId) {
var vertex = graph.entity(vertexId),
parentWays = graph.parentWays(vertex),
coincident = [],
highways = {};
export function osmIntersection(graph, startVertexId) {
var vgraph = coreGraph(), // virtual graph
i, j, k;
function addHighway(way, adjacentNodeId) {
if (highways[adjacentNodeId]) {
coincident.push(adjacentNodeId);
} else {
highways[adjacentNodeId] = way;
function memberOfRestriction(entity) {
return graph.parentRelations(entity)
.some(function(r) { return r.isRestriction(); });
}
function isRoad(way) {
if (way.isArea() || way.isDegenerate()) return false;
var roads = {
'motorway': true,
'motorway_link': true,
'trunk': true,
'trunk_link': true,
'primary': true,
'primary_link': true,
'secondary': true,
'secondary_link': true,
'tertiary': true,
'tertiary_link': true,
'residential': true,
'unclassified': true,
'living_street': true,
'service': true,
'road': true,
'track': true
};
return roads[way.tags.highway];
}
var distCutoff = 20; // meters
var checkVertices = [graph.entity(startVertexId)];
var checkWays;
var vertices = [];
var vertexIds = [];
var vertex;
var ways = [];
var wayIds = [];
var way;
var nodes = [];
var node;
var parents = [];
var parent;
// `actions` will store whatever actions must be performed to satisfy
// preconditions for adding a turn restriction to this intersection.
// - Remove any existing degenerate turn restrictions (missing from/to, etc)
// - Reverse oneways so that they are drawn in the forward direction
// - Split ways on key vertices
var actions = [];
// STEP 1: walk the graph outwards from starting vertex to search
// for more key vertices and ways to include in the intersection..
while (checkVertices.length) {
vertex = checkVertices.pop();
// check this vertex for parent ways that are roads
checkWays = graph.parentWays(vertex);
var hasWays = false;
for (i = 0; i < checkWays.length; i++) {
way = checkWays[i];
if (!isRoad(way) && !memberOfRestriction(way)) continue;
ways.push(way); // it's a road, or it's already in a turn restriction
hasWays = true;
// check the way's children for more key vertices
nodes = _uniq(graph.childNodes(way));
for (j = 0; j < nodes.length; j++) {
node = nodes[j];
if (node === vertex) continue; // same thing
if (vertices.indexOf(node) !== -1) continue; // seen it already
if (node.loc && vertex.loc && geoSphericalDistance(node.loc, vertex.loc) > distCutoff) continue; // too far
// a key vertex will have parents that are also roads
var hasParents = false;
parents = graph.parentWays(node);
for (k = 0; k < parents.length; k++) {
parent = parents[k];
if (parent === way) continue; // same thing
if (ways.indexOf(parent) !== -1) continue; // seen it already
if (!isRoad(parent)) continue; // not a road
hasParents = true;
break;
}
if (hasParents) {
checkVertices.push(node);
}
}
}
if (hasWays) {
vertices.push(vertex);
}
}
// Pre-split ways that would need to be split in
// order to add a restriction. The real split will
// happen when the restriction is added.
parentWays.forEach(function(way) {
if (!way.tags.highway || way.isArea() || way.isDegenerate())
return;
var isFirst = (vertexId === way.first()),
isLast = (vertexId === way.last()),
isAffix = (isFirst || isLast),
isClosingNode = (isFirst && isLast);
if (isAffix && !isClosingNode) {
var index = (isFirst ? 1 : way.nodes.length - 2);
addHighway(way, way.nodes[index]);
} else {
var splitIndex, wayA, wayB, indexA, indexB;
if (isClosingNode) {
splitIndex = Math.ceil(way.nodes.length / 2); // split at midpoint
wayA = osmWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)});
wayB = osmWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)});
indexA = 1;
indexB = way.nodes.length - 2;
} else {
splitIndex = _indexOf(way.nodes, vertex.id, 1); // split at vertexid
wayA = osmWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)});
wayB = osmWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)});
indexA = splitIndex - 1;
indexB = splitIndex + 1;
}
graph = graph.replace(wayA).replace(wayB);
addHighway(wayA, way.nodes[indexA]);
addHighway(wayB, way.nodes[indexB]);
}
});
// remove any ways from this intersection that are coincident
// (i.e. any adjacent node used by more than one intersecting way)
coincident.forEach(function (n) {
delete highways[n];
});
vertices = _uniq(vertices);
ways = _uniq(ways);
var intersection = {
highways: highways,
ways: _values(highways),
graph: graph
};
intersection.adjacentNodeId = function(fromWayId) {
return _find(_keys(highways), function(k) {
return highways[k].id === fromWayId;
// STEP 2: Build a virtual graph containing only the entities in the intersection..
// Everything done after this step should act on the virtual graph
// Any actions that must be performed later to the main graph go in `actions` array
ways.forEach(function(way) {
graph.childNodes(way).forEach(function(node) {
vgraph = vgraph.replace(node);
});
};
vgraph = vgraph.replace(way);
intersection.turns = function(fromNodeId) {
var start = highways[fromNodeId];
if (!start)
return [];
if (start.first() === vertex.id && start.tags.oneway === 'yes')
return [];
if (start.last() === vertex.id && start.tags.oneway === '-1')
return [];
function withRestriction(turn) {
graph.parentRelations(graph.entity(turn.from.way)).forEach(function(relation) {
if (relation.tags.type !== 'restriction')
return;
var f = relation.memberByRole('from'),
t = relation.memberByRole('to'),
v = relation.memberByRole('via');
if (f && f.id === turn.from.way &&
v && v.id === turn.via.node &&
t && t.id === turn.to.way) {
turn.restriction = relation.id;
} else if (/^only_/.test(relation.tags.restriction) &&
f && f.id === turn.from.way &&
v && v.id === turn.via.node &&
t && t.id !== turn.to.way) {
turn.restriction = relation.id;
turn.indirect_restriction = true;
graph.parentRelations(way).forEach(function(relation) {
if (relation.isRestriction()) {
if (relation.isValidRestriction(graph)) {
vgraph = vgraph.replace(relation);
} else if (relation.isComplete(graph)) {
actions.push(actionDeleteRelation(relation.id));
}
});
return osmTurn(turn);
}
var from = {
node: fromNodeId,
way: start.id.split(/-(a|b)/)[0]
},
via = { node: vertex.id },
turns = [];
_each(highways, function(end, adjacentNodeId) {
if (end === start)
return;
// backward
if (end.first() !== vertex.id && end.tags.oneway !== 'yes') {
turns.push(withRestriction({
from: from,
via: via,
to: {
node: adjacentNodeId,
way: end.id.split(/-(a|b)/)[0]
}
}));
}
// forward
if (end.last() !== vertex.id && end.tags.oneway !== '-1') {
turns.push(withRestriction({
from: from,
via: via,
to: {
node: adjacentNodeId,
way: end.id.split(/-(a|b)/)[0]
}
}));
}
});
});
// STEP 3: Force all oneways to be drawn in the forward direction
ways.forEach(function(w) {
var way = vgraph.entity(w.id);
if (way.tags.oneway === '-1') {
var action = actionReverse(way.id, { reverseOneway: true });
actions.push(action);
vgraph = action(vgraph);
}
});
// STEP 4: Split ways on key vertices
var origCount = osmEntity.id.next.way;
vertices.forEach(function(v) {
// This is an odd way to do it, but we need to find all the ways that
// will be split here, then split them one at a time to ensure that these
// actions can be replayed on the main graph exactly in the same order.
// (It is unintuitive, but the order of ways returned from graph.parentWays()
// is arbitrary, depending on how the main graph and vgraph were built)
var splitAll = actionSplit(v.id);
if (!splitAll.disabled(vgraph)) {
splitAll.ways(vgraph).forEach(function(way) {
var splitOne = actionSplit(v.id).limitWays([way.id]);
actions.push(splitOne);
vgraph = splitOne(vgraph);
});
}
});
// In here is where we should also split the intersection at nearby junction.
// for https://github.com/mapbox/iD-internal/issues/31
// nearbyVertices.forEach(function(v) {
// });
// Reasons why we reset the way id count here:
// 1. Continuity with way ids created by the splits so that we can replay
// these actions later if the user decides to create a turn restriction
// 2. Avoids churning way ids just by hovering over a vertex
// and displaying the turn restriction editor
osmEntity.id.next.way = origCount;
// STEP 5: Update arrays to point to vgraph entities
vertexIds = vertices.map(function(v) { return v.id; });
vertices = [];
ways = [];
vertexIds.forEach(function(id) {
var vertex = vgraph.entity(id);
var parents = vgraph.parentWays(vertex);
vertices.push(vertex);
ways = ways.concat(parents);
});
vertices = _uniq(vertices);
ways = _uniq(ways);
vertexIds = vertices.map(function(v) { return v.id; });
wayIds = ways.map(function(w) { return w.id; });
// STEP 6: Update the ways with some metadata that will be useful for
// walking the intersection graph later and rendering turn arrows.
function withMetadata(way, vertexIds) {
var __oneWay = way.isOneWay();
// which affixes are key vertices?
var __first = (vertexIds.indexOf(way.first()) !== -1);
var __last = (vertexIds.indexOf(way.last()) !== -1);
// what roles is this way eligible for?
var __via = (__first && __last);
var __from = ((__first && !__oneWay) || __last);
var __to = (__first || (__last && !__oneWay));
return way.update({
__first: __first,
__last: __last,
__from: __from,
__via: __via,
__to: __to,
__oneWay: __oneWay
});
}
ways = [];
wayIds.forEach(function(id) {
var way = withMetadata(vgraph.entity(id), vertexIds);
vgraph = vgraph.replace(way);
ways.push(way);
});
// STEP 7: Simplify - This is an iterative process where we:
// 1. Find trivial vertices with only 2 parents
// 2. trim off the leaf way from those vertices and remove from vgraph
var keepGoing;
var removeWayIds = [];
var removeVertexIds = [];
do {
keepGoing = false;
checkVertices = vertexIds.slice();
for (i = 0; i < checkVertices.length; i++) {
var vertexId = checkVertices[i];
vertex = vgraph.hasEntity(vertexId);
if (!vertex) {
vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
removeVertexIds.push(vertexId);
continue;
}
parents = vgraph.parentWays(vertex);
if (parents.length < 3) {
vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
}
if (parents.length === 2) { // vertex with 2 parents is trivial
var a = parents[0];
var b = parents[1];
var aIsLeaf = a && !a.__via;
var bIsLeaf = b && !b.__via;
var leaf, survivor;
if (aIsLeaf && !bIsLeaf) {
leaf = a;
survivor = b;
} else if (!aIsLeaf && bIsLeaf) {
leaf = b;
survivor = a;
}
if (leaf && survivor) {
survivor = withMetadata(survivor, vertexIds); // update survivor way
vgraph = vgraph.replace(survivor).remove(leaf); // update graph
removeWayIds.push(leaf.id);
keepGoing = true;
}
}
parents = vgraph.parentWays(vertex);
if (parents.length < 2) { // vertex is no longer a key vertex
vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
removeVertexIds.push(vertexId);
keepGoing = true;
}
if (parents.length < 1) { // vertex is no longer attached to anything
vgraph = vgraph.remove(vertex);
}
}
} while (keepGoing);
vertices = vertices
.filter(function(vertex) { return removeVertexIds.indexOf(vertex.id) === -1; })
.map(function(vertex) { return vgraph.entity(vertex.id); });
ways = ways
.filter(function(way) { return removeWayIds.indexOf(way.id) === -1; })
.map(function(way) { return vgraph.entity(way.id); });
// OK! Here is our intersection..
var intersection = {
graph: vgraph,
actions: actions,
vertices: vertices,
ways: ways,
};
// Get all the valid turns through this intersection given a starting way id.
// This operates on the virtual graph for everything.
//
// Basically, walk through all possible paths from starting way,
// honoring the existing turn restrictions as we go (watch out for loops!)
//
// For each path found, generate and return a `osmTurn` datastructure.
//
intersection.turns = function(fromWayId) {
if (!fromWayId) return [];
var vgraph = intersection.graph;
var keyVertexIds = intersection.vertices.map(function(v) { return v.id; });
var keyWayIds = intersection.ways.map(function(w) { return w.id; });
var start = vgraph.entity(fromWayId);
if (!start || !(start.__from || start.__via)) return [];
var maxPathLength = 7; // from-*-via-*-via-*-to (2 vias max)
var maxStepDist = 20; // meters
var turns = [];
step(start);
return turns;
// traverse the intersection graph and find all the valid paths
function step(entity, currPath, currRestrictions, matchedRestriction) {
currPath = _clone(currPath || []);
if (currPath.length >= maxPathLength) return;
currPath.push(entity.id);
currRestrictions = _clone(currRestrictions || []);
if (entity.type === 'node') {
var parents = vgraph.parentWays(entity);
var nextWays = [];
// which ways can we step into?
for (var i = 0; i < parents.length; i++) {
var way = parents[i];
// if next way is a oneway incoming to this vertex, skip
if (way.__oneWay && way.nodes[0] !== entity.id) continue;
// if we have seen it before (allowing for an initial u-turn), skip
if (currPath.indexOf(way.id) !== -1 && currPath.length >= 3) continue;
// Check all "current" restrictions (where we've already walked the `from`)
var restrict = undefined;
for (var j = 0; j < currRestrictions.length; j++) {
var restriction = currRestrictions[j];
var v = restriction.membersByRole('via');
var t = restriction.memberByRole('to');
var isOnly = /^only_/.test(restriction.tags.restriction);
// Are all the vias part of this local intersection?
// This matters for flagging "indirect" restrictions
var isLocalVia;
if (v.length === 1 && v[0].type === 'node') {
isLocalVia = (keyVertexIds.indexOf(v[0].id) !== -1);
} else {
isLocalVia = _every(v, function(via) { return keyWayIds.indexOf(via.id) !== -1; });
}
// Does this path match the turn restriction?
var isMatch = false;
if ( // match via node, to way
v.length === 1 &&
v[0].type === 'node' &&
v[0].id === entity.id &&
t.id === way.id
) {
isMatch = true;
} else if ( // match via ways, to way
_every(v, function(via) { return currPath.indexOf(via.id) !== -1; }) &&
t.id === way.id
) {
isMatch = true;
}
if (isMatch && isOnly) {
restrict = { id: restriction.id, only: true };
break;
} else if (isMatch && !isOnly) {
restrict = { id: restriction.id, direct: true };
break;
} else if (!isMatch && isOnly && isLocalVia) {
restrict = { id: restriction.id, indirect: true };
// no break - keep looking for a "better" direct or only
}
}
nextWays.push({ way: way, restrict: restrict });
}
nextWays.forEach(function(nextWay) {
step(nextWay.way, currPath, currRestrictions, nextWay.restrict);
});
} else { // entity.type === 'way'
if (currPath.length >= 3) { // this is a "complete" path..
var turn = pathToTurn(currPath);
if (turn) {
if (matchedRestriction) {
turn.restriction = matchedRestriction.id;
turn.only = matchedRestriction.only;
turn.direct = matchedRestriction.direct;
turn.indirect = matchedRestriction.indirect;
}
turns.push(osmTurn(turn));
}
if (currPath[0] === currPath[2]) return; // we made a u-turn - stop here
}
if (matchedRestriction) return; // don't advance any further
// which nodes can we step into?
var n1 = vgraph.entity(entity.first()),
n2 = vgraph.entity(entity.last()),
dist = n1.loc && n2.loc && geoSphericalDistance(n1.loc, n2.loc),
nextNodes = [];
if (currPath.length > 1) {
if (dist > maxStepDist) return; // the next node is too far
if (!entity.__via) return; // this way is a leaf / can't be a via
}
if (!entity.__oneWay && // bidirectional..
keyVertexIds.indexOf(n1.id) !== -1 && // key vertex..
currPath.indexOf(n1.id) === -1) { // haven't seen it yet..
nextNodes.push(n1); // can advance to first node
}
if (keyVertexIds.indexOf(n2.id) !== -1 && // key vertex..
currPath.indexOf(n2.id) === -1) { // haven't seen it yet..
nextNodes.push(n2); // can advance to last node
}
// gather restrictions FROM this way
var fromRestrictions = vgraph.parentRelations(entity).filter(function(r) {
if (!r.isRestriction()) return false;
var f = r.memberByRole('from');
return f && f.id === entity.id;
});
nextNodes.forEach(function(node) {
step(node, currPath, currRestrictions.concat(fromRestrictions), false);
});
}
}
// assumes path is alternating way-node-way of odd length
function pathToTurn(path) {
if (path.length < 3) return;
var fromWayId, fromNodeId, fromVertexId;
var toWayId, toNodeId, toVertexId;
var viaWayIds, viaNodeId, isUturn;
fromWayId = path[0];
toWayId = path[path.length - 1];
if (path.length === 3 && fromWayId === toWayId) { // u turn
var way = vgraph.entity(fromWayId);
if (way.__oneWay) return null;
isUturn = true;
viaNodeId = fromVertexId = toVertexId = path[1];
fromNodeId = toNodeId = adjacentNode(fromWayId, viaNodeId);
} else {
isUturn = false;
fromVertexId = path[1];
fromNodeId = adjacentNode(fromWayId, fromVertexId);
toVertexId = path[path.length - 2];
toNodeId = adjacentNode(toWayId, toVertexId);
if (path.length === 3) {
viaNodeId = path[1];
} else {
viaWayIds = path.filter(function(entityId) { return entityId[0] === 'w'; });
viaWayIds = viaWayIds.slice(1, viaWayIds.length - 1); // remove first, last
}
}
return {
key: path.join(','),
path: path,
from: { node: fromNodeId, way: fromWayId, vertex: fromVertexId },
via: { node: viaNodeId, ways: viaWayIds },
to: { node: toNodeId, way: toWayId, vertex: toVertexId },
u: isUturn
};
function adjacentNode(wayId, affixId) {
var nodes = vgraph.entity(wayId).nodes;
return affixId === nodes[0] ? nodes[1] : nodes[nodes.length - 2];
}
// U-turn
if (start.tags.oneway !== 'yes' && start.tags.oneway !== '-1') {
turns.push(withRestriction({
from: from,
via: via,
to: from,
u: true
}));
}
return turns;
};
return intersection;
}
export function osmInferRestriction(graph, from, via, to, projection) {
var fromWay = graph.entity(from.way),
fromNode = graph.entity(from.node),
toWay = graph.entity(to.way),
toNode = graph.entity(to.node),
viaNode = graph.entity(via.node),
fromOneWay = (fromWay.tags.oneway === 'yes' && fromWay.last() === via.node) ||
(fromWay.tags.oneway === '-1' && fromWay.first() === via.node),
toOneWay = (toWay.tags.oneway === 'yes' && toWay.first() === via.node) ||
(toWay.tags.oneway === '-1' && toWay.last() === via.node),
angle = geoAngle(viaNode, fromNode, projection) -
geoAngle(viaNode, toNode, projection);
export function osmInferRestriction(graph, from, to, projection) {
var fromWay = graph.entity(from.way);
var fromNode = graph.entity(from.node);
var fromVertex = graph.entity(from.vertex);
var toWay = graph.entity(to.way);
var toNode = graph.entity(to.node);
var toVertex = graph.entity(to.vertex);
var fromOneWay = (fromWay.tags.oneway === 'yes');
var toOneWay = (toWay.tags.oneway === 'yes');
var angle = geoAngle(fromVertex, fromNode, projection) -
geoAngle(toVertex, toNode, projection);
angle = angle * 180 / Math.PI;
+32 -2
View File
@@ -109,6 +109,16 @@ _extend(osmRelation.prototype, {
}
},
// Same as memberByRole, but returns all members with the given role
membersByRole: function(role) {
var result = [];
for (var i = 0; i < this.members.length; i++) {
if (this.members[i].role === role) {
result.push(_extend({}, this.members[i], {index: i}));
}
}
return result;
},
// Return the first member with the given id. A copy of the member object
// is returned, extended with an 'index' property whose value is the member index.
@@ -253,6 +263,26 @@ _extend(osmRelation.prototype, {
},
isValidRestriction: function() {
if (!this.isRestriction()) return false;
var froms = this.members.filter(function(m) { return m.role === 'from'; });
var vias = this.members.filter(function(m) { return m.role === 'via'; });
var tos = this.members.filter(function(m) { return m.role === 'to'; });
if (froms.length !== 1 && this.tags.restriction !== 'no_entry') return false;
if (froms.some(function(m) { return m.type !== 'way'; })) return false;
if (tos.length !== 1 && this.tags.restriction !== 'no_exit') return false;
if (tos.some(function(m) { return m.type !== 'way'; })) return false;
if (vias.length === 0) return false;
if (vias.length > 1 && vias.some(function(m) { return m.type !== 'way'; })) return false;
return true;
},
// Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm],
// where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings.
//
@@ -264,8 +294,8 @@ _extend(osmRelation.prototype, {
// rings not matched with the intended outer ring.
//
multipolygon: function(resolver) {
var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }),
inners = this.members.filter(function(m) { return 'inner' === m.role; });
var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); });
var inners = this.members.filter(function(m) { return 'inner' === m.role; });
outers = osmJoinWays(outers, resolver);
inners = osmJoinWays(inners, resolver);
+19 -17
View File
@@ -1,26 +1,28 @@
import { geoAngle } from '../geo';
import { geoAngle } from '../geo/index';
export function svgTurns(projection) {
return function drawTurns(selection, graph, turns) {
function key(turn) {
return [turn.from.node + turn.via.node + turn.to.node].join('-');
}
function icon(turn) {
var u = turn.u ? '-u' : '';
if (!turn.restriction)
return '#turn-yes' + u;
var restriction = graph.entity(turn.restriction).tags.restriction;
return '#turn-' +
(!turn.indirect_restriction && /^only_/.test(restriction) ? 'only' : 'no') + u;
if (turn.direct || turn.indirect) return '#turn-no' + u;
if (turn.only) return '#turn-only' + u;
return '#turn-yes' + u;
}
var layer = selection.selectAll('.layer-points .layer-points-turns');
var layer = selection.selectAll('.data-layer-osm').selectAll('.layer-turns')
.data([0]);
layer = layer.enter()
.append('g')
.attr('class', 'layer-osm layer-turns')
.merge(layer);
var groups = layer.selectAll('g.turn')
.data(turns, key);
.data(turns, function(d) { return d.key; });
groups.exit()
.remove();
@@ -61,11 +63,11 @@ export function svgTurns(projection) {
groups
.attr('transform', function (turn) {
var v = graph.entity(turn.via.node),
t = graph.entity(turn.to.node),
a = geoAngle(v, t, projection),
p = projection(v.loc),
r = turn.u ? 0 : 60;
var t = graph.entity(turn.to.node);
var v = graph.entity(turn.to.vertex);
var a = geoAngle(v, t, projection);
var p = projection(v.loc);
var r = turn.u ? 0 : 60;
return 'translate(' + (r * Math.cos(a) + p[0]) + ',' + (r * Math.sin(a) + p[1]) + ') ' +
'rotate(' + a * 180 / Math.PI + ')';
+114 -85
View File
@@ -6,44 +6,14 @@ import {
} from 'd3-selection';
import { t } from '../../util/locale';
import {
behaviorBreathe,
behaviorHover
} from '../../behavior';
import {
osmEntity,
osmIntersection,
osmInferRestriction,
osmTurn
} from '../../osm';
import {
actionRestrictTurn,
actionUnrestrictTurn
} from '../../actions';
import {
geoExtent,
geoRawMercator,
geoZoomToScale
} from '../../geo';
import {
svgLayers,
svgLines,
svgTurns,
svgVertices
} from '../../svg';
import { actionRestrictTurn, actionUnrestrictTurn } from '../../actions';
import { behaviorBreathe, behaviorHover } from '../../behavior';
import { geoExtent, geoRawMercator } from '../../geo';
import { osmIntersection, osmInferRestriction, osmTurn, osmWay } from '../../osm';
import { svgLabels, svgLayers, svgLines, svgTurns, svgVertices } from '../../svg';
import { utilRebind } from '../../util/rebind';
import { utilFunctor } from '../../util';
import {
utilGetDimensions,
utilSetDimensions
} from '../../util/dimensions';
import { utilGetDimensions, utilSetDimensions } from '../../util/dimensions';
export function uiFieldRestrictions(field, context) {
@@ -51,19 +21,31 @@ export function uiFieldRestrictions(field, context) {
var breathe = behaviorBreathe(context);
var hover = behaviorHover(context);
var initialized = false;
var graph;
var vertexID;
var fromNodeID;
var fromWayID;
function restrictions(selection) {
function restrictions(selection, intersection) {
// if form field is hidden or has detached from dom, clean up.
if (!d3_select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode) {
if (!d3_select('.inspector-wrap.inspector-hidden').empty() ||
!selection.node().parentNode || !selection.node().parentNode.parentNode) {
selection.call(restrictions.off);
return;
}
// try to reuse the intersection, but always rebuild it if the graph has changed
if (context.graph() !== graph || !intersection) {
graph = context.graph();
intersection = osmIntersection(graph, vertexID);
}
var ok = (intersection.vertices.length && intersection.ways.length);
var wrap = selection.selectAll('.preset-input-wrap')
.data([0]);
.data(ok ? [0] : []);
wrap.exit()
.remove();
var enter = wrap.enter()
.append('div')
@@ -73,31 +55,35 @@ export function uiFieldRestrictions(field, context) {
.append('div')
.attr('class', 'restriction-help');
// hack: no actual intersection exists here, just dont show the field
if (!ok) return;
var intersection = osmIntersection(context.graph(), vertexID);
var graph = intersection.graph;
var vertex = graph.entity(vertexID);
var vgraph = intersection.graph;
var filter = utilFunctor(true);
var extent = geoExtent();
var projection = geoRawMercator();
var d = utilGetDimensions(wrap.merge(enter));
var c = [d[0] / 2, d[1] / 2];
var z = 24;
var z = intersection.vertices.length === 1 ? 22 : 20;
projection
.scale(geoZoomToScale(z));
.scale(256 * Math.pow(2, z) / (2 * Math.PI));
var s = projection(vertex.loc);
// fit extent to include all key vertices
for (var i = 0; i < intersection.vertices.length; i++) {
extent._extend(intersection.vertices[i].extent());
}
var s = projection(extent.center());
projection
.translate([c[0] - s[0], c[1] - s[1]])
.clipExtent([[0, 0], d]);
var extent = geoExtent(projection.invert([0, d[1]]), projection.invert([d[0], 0]));
var drawLayers = svgLayers(projection, context).only('osm').dimensions(d);
var drawVertices = svgVertices(projection, context);
var drawLines = svgLines(projection, context);
// var drawLabels = svgLabels(projection, context, true);
var drawTurns = svgTurns(projection, context);
enter
@@ -117,9 +103,10 @@ export function uiFieldRestrictions(field, context) {
surface
.call(utilSetDimensions, d)
.call(drawVertices, graph, [vertex], filter, extent, true)
.call(drawLines, graph, intersection.ways, filter)
.call(drawTurns, graph, intersection.turns(fromNodeID));
.call(drawVertices, vgraph, intersection.vertices, filter, extent, z)
.call(drawLines, vgraph, intersection.ways, filter)
// .call(drawLabels, vgraph, intersection.ways, filter, d, true)
.call(drawTurns, vgraph, intersection.turns(fromWayID));
surface
.on('click.restrictions', click)
@@ -130,9 +117,9 @@ export function uiFieldRestrictions(field, context) {
.selectAll('.selected')
.classed('selected', false);
if (fromNodeID) {
if (fromWayID) {
surface
.selectAll('.' + intersection.highways[fromNodeID].id)
.selectAll('.' + fromWayID)
.classed('selected', true);
}
@@ -155,69 +142,111 @@ export function uiFieldRestrictions(field, context) {
var datum = d3_event.target.__data__;
var entity = datum && datum.properties && datum.properties.entity;
if (entity) datum = entity;
if (entity) {
datum = entity;
}
if (datum instanceof osmEntity) {
fromNodeID = intersection.adjacentNodeId(datum.id);
if (datum instanceof osmWay && (datum.__from || datum.__via)) {
fromWayID = datum.id;
render();
} else if (datum instanceof osmTurn) {
var actions;
if (datum.restriction) {
context.perform(
actions = intersection.actions.concat([
actionUnrestrictTurn(datum, projection),
t('operations.restriction.annotation.delete')
);
]);
} else {
context.perform(
actions = intersection.actions.concat([
actionRestrictTurn(datum, projection),
t('operations.restriction.annotation.create')
);
]);
}
context.perform.apply(context, actions);
} else {
fromWayID = null;
render();
}
}
function mouseover() {
var datum = d3_event.target.__data__;
if (datum instanceof osmTurn) {
var graph = context.graph();
var presets = context.presets();
var preset;
var entity = datum && datum.properties && datum.properties.entity;
if (entity) {
datum = entity;
}
if (datum instanceof osmWay) {
wrap.selectAll('.restriction-help')
.text(datum.id);
} else if (datum instanceof osmTurn) {
//DEBUG
var str = '';
if (datum.restriction) {
preset = presets.match(graph.entity(datum.restriction), graph);
} else {
preset = presets.item('type/restriction/' +
osmInferRestriction(
graph,
datum.from,
datum.via,
datum.to,
projection
)
);
if (datum.only) { str += 'ONLY_ '; }
if (datum.direct) { str += 'NO_ '; }
if (datum.indirect) { str += 'indirect '; }
str += datum.restriction;
}
str += ' FROM ' + datum.from.way +
' VIA ' + (datum.via.node || datum.via.ways.join(',')) +
' TO ' + datum.to.way;
wrap.selectAll('.restriction-help')
.text(t('operations.restriction.help.' +
(datum.restriction ? 'toggle_off' : 'toggle_on'),
{ restriction: preset.name() })
);
.text(str);
// return;
// var presets = context.presets(),
// preset;
// if (datum.restriction) {
// preset = presets.match(vgraph.entity(datum.restriction), vgraph);
// } else {
// preset = presets.item('type/restriction/' +
// osmInferRestriction(
// vgraph,
// datum.from,
// datum.to,
// projection
// )
// );
// }
// wrap.selectAll('.restriction-help')
// .text(t('operations.restriction.help.' +
// (datum.restriction ? 'toggle_off' : 'toggle_on'),
// { restriction: preset.name() })
// );
}
}
function mouseout() {
wrap.selectAll('.restriction-help')
.text(t('operations.restriction.help.' +
(fromNodeID ? 'toggle' : 'select'))
);
if (fromWayID) {
wrap.selectAll('.restriction-help')
.text('FROM ' + fromWayID);
} else {
wrap.selectAll('.restriction-help')
.text('Click to select the FROM way');
}
// wrap.selectAll('.restriction-help')
// .text(t('operations.restriction.help.' +
// (fromWayID ? 'toggle' : 'select'))
// );
}
function render() {
if (context.hasEntity(vertexID)) {
restrictions(selection);
restrictions(selection, intersection);
}
}
}
@@ -225,7 +254,7 @@ export function uiFieldRestrictions(field, context) {
restrictions.entity = function(_) {
if (!vertexID || vertexID !== _.id) {
fromNodeID = null;
fromWayID = null;
vertexID = _.id;
}
};
+2 -2
View File
@@ -68,7 +68,7 @@ export function uiInit(context) {
container
.append('div')
.attr('id', 'sidebar')
.attr('class', 'col4')
.attr('class', 'col5')
.call(ui.sidebar);
var content = container
@@ -94,7 +94,7 @@ export function uiInit(context) {
bar
.append('div')
.attr('class', 'spacer col4');
.attr('class', 'spacer col5');
var limiter = bar.append('div')
.attr('class', 'limiter');
+258 -453
View File
@@ -1,483 +1,288 @@
describe('iD.actionRestrictTurn', function() {
var projection = d3.geoMercator().scale(250 / Math.PI);
it('adds a restriction to an unrestricted turn', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*']}),
iD.Way({id: '-', nodes: ['*', 'w']})
]),
action = iD.actionRestrictTurn({
describe('via node', function() {
it('adds a via node restriction to an unrestricted turn', function() {
// u====*--->w
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*']}),
iD.osmWay({id: '-', nodes: ['*', 'w']})
]);
var action = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'no_right_turn'
}, projection, 'r');
graph = action(graph);
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(r.memberByRole('from').id).to.eql('=');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('-');
expect(r.memberByRole('to').type).to.eql('way');
});
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
it('splits the from way when necessary (forward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
var f = r.memberByRole('from');
expect(f.id).to.eql('=');
expect(f.type).to.eql('way');
var v = r.memberByRole('via');
expect(v.id).to.eql('*');
expect(v.type).to.eql('node');
var t = r.memberByRole('to');
expect(t.id).to.eql('-');
expect(t.type).to.eql('way');
});
//TODO?
it.skip('infers the restriction type based on the turn angle', function() {
// u====*~~~~w
// |
// x
var graph = iD.coreGraph([
iD.osmNode({id: 'u', loc: [-1, 0]}),
iD.osmNode({id: '*', loc: [ 0, 0]}),
iD.osmNode({id: 'w', loc: [ 1, 0]}),
iD.osmNode({id: 'x', loc: [ 0, -1]}),
iD.osmWay({id: '=', nodes: ['u', '*']}),
iD.osmWay({id: '-', nodes: ['*', 'x']}),
iD.osmWay({id: '~', nodes: ['*', 'w']})
]);
var r1 = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'},
restriction: 'no_right_turn'
}, projection, 'r');
to: {node: 'x', way: '-'}
}, projection, 'r')(graph);
expect(r1.entity('r').tags.restriction).to.equal('no_right_turn');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(r.memberByRole('from').id).to.eql('=');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('-');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from way when necessary (backward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
from: {node: 'w', way: '=', newID: '=='},
via: {node: '*'},
to: {node: 'x', way: '-'},
restriction: 'no_left_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_left_turn'});
expect(r.memberByRole('from').id).to.eql('==');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('-');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from way when necessary (straight on forward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
from: {node: 'u', way: '=', newID: '=='},
via: {node: '*'},
to: {node: 'w', way: '='},
restriction: 'no_straight_on'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_straight_on'});
expect(r.memberByRole('from').id).to.eql('=');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('==');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from way when necessary (straight on backward)', function() {
// u<===*====w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['w', '*', 'u']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
from: {node: 'u', way: '=', newID: '=='},
via: {node: '*'},
to: {node: 'w', way: '='},
restriction: 'no_straight_on'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_straight_on'});
expect(r.memberByRole('from').id).to.eql('==');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('=');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from way when necessary (vertex closes from)', function() {
//
// b -- c
// | |
// a -- * === w
//
var graph = iD.Graph([
iD.Node({id: 'a', loc: [-1, 0]}),
iD.Node({id: 'b', loc: [-1, 1]}),
iD.Node({id: 'c', loc: [ 0, 1]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'w', loc: [ 1, 0]}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*']}),
iD.Way({id: '=', nodes: ['*', 'w']})
]),
action = iD.actionRestrictTurn({
from: {node: 'c', way: '-', newID: '--'},
via: {node: '*'},
to: {node: 'w', way: '='},
restriction: 'no_left_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_left_turn'});
expect(r.memberByRole('from').id).to.eql('--');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('=');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from/to way when necessary (vertex closes from/to)', function() {
//
// b -- c
// | |
// a -- * === w
//
var graph = iD.Graph([
iD.Node({id: 'a', loc: [-1, 0]}),
iD.Node({id: 'b', loc: [-1, 1]}),
iD.Node({id: 'c', loc: [ 0, 1]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'w', loc: [ 1, 0]}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*']}),
iD.Way({id: '=', nodes: ['*', 'w']})
]),
action = iD.actionRestrictTurn({
from: {node: 'a', way: '-', newID: '--'},
via: {node: '*'},
to: {node: 'c', way: '-'},
restriction: 'no_left_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_left_turn'});
expect(r.memberByRole('from').id).to.eql('-');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('--');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the to way when necessary (forward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
var r2 = iD.actionRestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'w', way: '=', newID: '=='},
restriction: 'no_right_turn'
}, projection, 'r');
to: {node: 'w', way: '~'}
}, projection, 'r')(graph);
expect(r2.entity('r').tags.restriction).to.equal('no_right_turn');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(r.memberByRole('from').id).to.eql('-');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('==');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the to way when necessary (backward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
var l1 = iD.actionRestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='},
restriction: 'no_left_turn'
}, projection, 'r');
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(l1.entity('r').tags.restriction).to.equal('no_left_turn');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_left_turn'});
expect(r.memberByRole('from').id).to.eql('-');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('=');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the to way when necessary (vertex closes to)', function() {
//
// b -- c
// | |
// a -- * === w
//
var graph = iD.Graph([
iD.Node({id: 'a', loc: [-1, 0]}),
iD.Node({id: 'b', loc: [-1, 1]}),
iD.Node({id: 'c', loc: [ 0, 1]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'w', loc: [ 1, 0]}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*']}),
iD.Way({id: '=', nodes: ['*', 'w']})
]),
action = iD.actionRestrictTurn({
from: {node: 'w', way: '='},
var l2 = iD.actionRestrictTurn({
from: {node: 'w', way: '~'},
via: {node: '*'},
to: {node: 'c', way: '-', newID: '--'},
restriction: 'no_right_turn'
}, projection, 'r');
to: {node: 'x', way: '-'}
}, projection, 'r')(graph);
expect(l2.entity('r').tags.restriction).to.equal('no_left_turn');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(r.memberByRole('from').id).to.eql('=');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('--');
expect(r.memberByRole('to').type).to.eql('way');
});
it('splits the from/to way of a U-turn (forward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
var s = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
to: {node: 'w', way: '~'}
}, projection, 'r')(graph);
expect(s.entity('r').tags.restriction).to.equal('no_straight_on');
var u = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(u.entity('r').tags.restriction).to.equal('no_u_turn');
});
//TODO?
it.skip('infers no_u_turn from acute angle made by forward oneways', function() {
// *
// / \
// w2/ \w1
// / \
// u x
var graph = iD.coreGraph([
iD.osmNode({id: 'u', loc: [-1, -20]}),
iD.osmNode({id: '*', loc: [ 0, 0]}),
iD.osmNode({id: 'x', loc: [ 1, -20]}),
iD.osmWay({id: 'w1', nodes: ['x', '*'], tags: {oneway: 'yes'}}),
iD.osmWay({id: 'w2', nodes: ['*', 'u'], tags: {oneway: 'yes'}})
]);
var r = iD.actionRestrictTurn({
from: {node: 'x', way: 'w1'},
via: {node: '*'},
to: {node: 'u', way: 'w2'}
}, projection, 'r')(graph);
expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
});
//TODO?
it.skip('infers no_u_turn from acute angle made by reverse oneways', function() {
// *
// / \
// w2/ \w1
// / \
// u x
var graph = iD.coreGraph([
iD.osmNode({id: 'u', loc: [-1, -20]}),
iD.osmNode({id: '*', loc: [ 0, 0]}),
iD.osmNode({id: 'x', loc: [ 1, -20]}),
iD.osmWay({id: 'w1', nodes: ['*', 'x'], tags: {oneway: '-1'}}),
iD.osmWay({id: 'w2', nodes: ['u', '*'], tags: {oneway: '-1'}})
]);
var r = iD.actionRestrictTurn({
from: {node: 'x', way: 'w1'},
via: {node: '*'},
to: {node: 'u', way: 'w2'}
}, projection, 'r')(graph);
expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
});
});
describe('via way', function() {
it('adds a via way restriction to an unrestricted turn', function() {
// u ==== VIA ---> w
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: 'V1'}),
iD.osmNode({id: 'V2'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', 'V1']}),
iD.osmWay({id: 'VIA', nodes: ['V1', 'V2']}),
iD.osmWay({id: '-', nodes: ['V2', 'w']})
]);
var action = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {ways: ['VIA']},
to: {node: 'w', way: '-'},
restriction: 'no_u_turn'
}, projection, 'r');
graph = action(graph);
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_u_turn'});
expect(r.memberByRole('from').id).to.eql('=');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('=');
expect(r.memberByRole('to').type).to.eql('way');
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_u_turn'});
var f = r.memberByRole('from');
expect(f.id).to.eql('=');
expect(f.type).to.eql('way');
var v = r.memberByRole('via');
expect(v.id).to.eql('VIA');
expect(v.type).to.eql('way');
var t = r.memberByRole('to');
expect(t.id).to.eql('-');
expect(t.type).to.eql('way');
});
// TODO?
// it('infers the restriction type based on the turn angle', function() {
// // u====*~~~~w
// // |
// // x
// var graph = iD.coreGraph([
// iD.osmNode({id: 'u', loc: [-1, 0]}),
// iD.osmNode({id: '*', loc: [ 0, 0]}),
// iD.osmNode({id: 'w', loc: [ 1, 0]}),
// iD.osmNode({id: 'x', loc: [ 0, -1]}),
// iD.osmWay({id: '=', nodes: ['u', '*']}),
// iD.osmWay({id: '-', nodes: ['*', 'x']}),
// iD.osmWay({id: '~', nodes: ['*', 'w']})
// ]);
// var r1 = iD.actionRestrictTurn({
// from: {node: 'u', way: '='},
// via: {node: '*'},
// to: {node: 'x', way: '-'}
// }, projection, 'r')(graph);
// expect(r1.entity('r').tags.restriction).to.equal('no_right_turn');
// var r2 = iD.actionRestrictTurn({
// from: {node: 'x', way: '-'},
// via: {node: '*'},
// to: {node: 'w', way: '~'}
// }, projection, 'r')(graph);
// expect(r2.entity('r').tags.restriction).to.equal('no_right_turn');
// var l1 = iD.actionRestrictTurn({
// from: {node: 'x', way: '-'},
// via: {node: '*'},
// to: {node: 'u', way: '='}
// }, projection, 'r')(graph);
// expect(l1.entity('r').tags.restriction).to.equal('no_left_turn');
// var l2 = iD.actionRestrictTurn({
// from: {node: 'w', way: '~'},
// via: {node: '*'},
// to: {node: 'x', way: '-'}
// }, projection, 'r')(graph);
// expect(l2.entity('r').tags.restriction).to.equal('no_left_turn');
// var s = iD.actionRestrictTurn({
// from: {node: 'u', way: '='},
// via: {node: '*'},
// to: {node: 'w', way: '~'}
// }, projection, 'r')(graph);
// expect(s.entity('r').tags.restriction).to.equal('no_straight_on');
// var u = iD.actionRestrictTurn({
// from: {node: 'u', way: '='},
// via: {node: '*'},
// to: {node: 'u', way: '='}
// }, projection, 'r')(graph);
// expect(u.entity('r').tags.restriction).to.equal('no_u_turn');
// });
// it('infers no_u_turn from acute angle made by forward oneways', function() {
// // *
// // / \
// // w2/ \w1
// // / \
// // u x
// var graph = iD.coreGraph([
// iD.osmNode({id: 'u', loc: [-1, -20]}),
// iD.osmNode({id: '*', loc: [ 0, 0]}),
// iD.osmNode({id: 'x', loc: [ 1, -20]}),
// iD.osmWay({id: 'w1', nodes: ['x', '*'], tags: {oneway: 'yes'}}),
// iD.osmWay({id: 'w2', nodes: ['*', 'u'], tags: {oneway: 'yes'}})
// ]);
// var r = iD.actionRestrictTurn({
// from: {node: 'x', way: 'w1'},
// via: {node: '*'},
// to: {node: 'u', way: 'w2'}
// }, projection, 'r')(graph);
// expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
// });
// it('infers no_u_turn from acute angle made by reverse oneways', function() {
// // *
// // / \
// // w2/ \w1
// // / \
// // u x
// var graph = iD.coreGraph([
// iD.osmNode({id: 'u', loc: [-1, -20]}),
// iD.osmNode({id: '*', loc: [ 0, 0]}),
// iD.osmNode({id: 'x', loc: [ 1, -20]}),
// iD.osmWay({id: 'w1', nodes: ['*', 'x'], tags: {oneway: '-1'}}),
// iD.osmWay({id: 'w2', nodes: ['u', '*'], tags: {oneway: '-1'}})
// ]);
// var r = iD.actionRestrictTurn({
// from: {node: 'x', way: 'w1'},
// via: {node: '*'},
// to: {node: 'u', way: 'w2'}
// }, projection, 'r')(graph);
// expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
// });
});
it('splits the from/to way of a U-turn (backward)', function() {
// u====*===>w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*', 'w']}),
iD.Way({id: '-', nodes: ['*', 'x']})
]),
action = iD.actionRestrictTurn({
from: {node: 'w', way: '=', newID: '=='},
via: {node: '*'},
to: {node: 'w', way: '=', newID: '~~'},
restriction: 'no_u_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_u_turn'});
expect(r.memberByRole('from').id).to.eql('==');
expect(r.memberByRole('from').type).to.eql('way');
expect(r.memberByRole('via').id).to.eql('*');
expect(r.memberByRole('via').type).to.eql('node');
expect(r.memberByRole('to').id).to.eql('==');
expect(r.memberByRole('to').type).to.eql('way');
});
it('infers the restriction type based on the turn angle', function() {
// u====*~~~~w
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u', loc: [-1, 0]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'w', loc: [ 1, 0]}),
iD.Node({id: 'x', loc: [ 0, -1]}),
iD.Way({id: '=', nodes: ['u', '*']}),
iD.Way({id: '-', nodes: ['*', 'x']}),
iD.Way({id: '~', nodes: ['*', 'w']})
]);
var r1 = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'}
}, projection, 'r')(graph);
expect(r1.entity('r').tags.restriction).to.equal('no_right_turn');
var r2 = iD.actionRestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'w', way: '~'}
}, projection, 'r')(graph);
expect(r2.entity('r').tags.restriction).to.equal('no_right_turn');
var l1 = iD.actionRestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(l1.entity('r').tags.restriction).to.equal('no_left_turn');
var l2 = iD.actionRestrictTurn({
from: {node: 'w', way: '~'},
via: {node: '*'},
to: {node: 'x', way: '-'}
}, projection, 'r')(graph);
expect(l2.entity('r').tags.restriction).to.equal('no_left_turn');
var s = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '~'}
}, projection, 'r')(graph);
expect(s.entity('r').tags.restriction).to.equal('no_straight_on');
var u = iD.actionRestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(u.entity('r').tags.restriction).to.equal('no_u_turn');
});
it('infers no_u_turn from acute angle made by forward oneways', function() {
// *
// / \
// w2/ \w1
// / \
// u x
var graph = iD.Graph([
iD.Node({id: 'u', loc: [-1, -20]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'x', loc: [ 1, -20]}),
iD.Way({id: 'w1', nodes: ['x', '*'], tags: {oneway: 'yes'}}),
iD.Way({id: 'w2', nodes: ['*', 'u'], tags: {oneway: 'yes'}})
]);
var r = iD.actionRestrictTurn({
from: {node: 'x', way: 'w1'},
via: {node: '*'},
to: {node: 'u', way: 'w2'}
}, projection, 'r')(graph);
expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
});
it('infers no_u_turn from acute angle made by reverse oneways', function() {
// *
// / \
// w2/ \w1
// / \
// u x
var graph = iD.Graph([
iD.Node({id: 'u', loc: [-1, -20]}),
iD.Node({id: '*', loc: [ 0, 0]}),
iD.Node({id: 'x', loc: [ 1, -20]}),
iD.Way({id: 'w1', nodes: ['*', 'x'], tags: {oneway: '-1'}}),
iD.Way({id: 'w2', nodes: ['u', '*'], tags: {oneway: '-1'}})
]);
var r = iD.actionRestrictTurn({
from: {node: 'x', way: 'w1'},
via: {node: '*'},
to: {node: 'u', way: 'w2'}
}, projection, 'r')(graph);
expect(r.entity('r').tags.restriction).to.equal('no_u_turn');
});
});
+390 -403
View File
@@ -1,108 +1,114 @@
describe('iD.osmIntersection', function() {
describe('highways', function() {
it('excludes non-highways', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*']}),
iD.Way({id: '-', nodes: ['*', 'w']})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*']}),
iD.osmWay({id: '-', nodes: ['*', 'w']})
]);
expect(iD.osmIntersection(graph, '*').ways).to.eql([]);
});
it('excludes degenerate highways', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*'], tags: {highway: 'residential'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*'], tags: {highway: 'residential'}})
]);
var ids = iD.osmIntersection(graph, '*').ways.map(function (w) { return w.id; });
expect(ids).to.have.same.members(['=']);
var result = iD.osmIntersection(graph, '*').ways;
expect(result.map(function(i) { return i.id; })).to.eql(['=']);
});
it('excludes coincident highways', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['u', '*'], tags: {highway: 'residential'}})
]);
expect(iD.osmIntersection(graph, '*').ways).to.eql([]);
});
//TODO?
// it('excludes coincident highways', function() {
// var graph = iD.coreGraph([
// iD.osmNode({id: 'u'}),
// iD.osmNode({id: '*'}),
// iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
// iD.osmWay({id: '-', nodes: ['u', '*'], tags: {highway: 'residential'}})
// ]);
// expect(iD.osmIntersection(graph, '*').ways).to.eql([]);
// });
it('includes line highways', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w']})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*', 'w']})
]);
var ids = iD.osmIntersection(graph, '*').ways.map(function (w) { return w.id; });
expect(ids).to.have.same.members(['=']);
var result = iD.osmIntersection(graph, '*').ways;
expect(result.map(function(i) { return i.id; })).to.eql(['=']);
});
it('excludes area highways', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*', 'w'], tags: {highway: 'pedestrian', area: 'yes'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*', 'w'], tags: {highway: 'pedestrian', area: 'yes'}})
]);
expect(iD.osmIntersection(graph, '*').ways).to.eql([]);
});
it('auto-splits highways at the intersection', function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*', 'w'], tags: {highway: 'residential'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*', 'w'], tags: {highway: 'residential'}})
]);
var ids = iD.osmIntersection(graph, '*').ways.map(function (w) { return w.id; });
expect(ids).to.have.ordered.members(['=-a', '=-b']);
expect(iD.osmIntersection(graph, '*').ways.length).to.eql(2);
});
});
describe('#turns', function() {
it('permits turns onto a way forward', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
});
it('permits turns onto a way backward', function() {
// u====*<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
});
it('permits turns from a way that must be split', function() {
@@ -111,33 +117,29 @@ describe('iD.osmIntersection', function() {
// u===*
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*', 'x'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('w');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmNode({id: 'x'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*', 'x'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('-');
expect(turns.length).to.eql(3);
expect(turns[0]).to.eql({
from: {node: 'w', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
});
expect(turns[1]).to.eql({
from: {node: 'w', way: '-'},
via: {node: '*'},
to: {node: 'x', way: '-'}
});
expect(turns[2]).to.eql({
from: {node: 'w', way: '-'},
via: {node: '*'},
to: {node: 'w', way: '-'},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('-,*,=');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('-,*,-');
expect(turns[1].u).to.be.true;
expect(turns[2]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[2].key).to.match(/^-,\*,w-\d+$/); // new way
expect(turns[2].u).to.be.not.ok;
});
it('permits turns to a way that must be split', function() {
@@ -146,244 +148,243 @@ describe('iD.osmIntersection', function() {
// u===*
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*', 'x'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmNode({id: 'x'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*', 'x'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(3);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'}
});
expect(turns[2]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
expect(turns[2]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[2].key).to.match(/^=,\*,w-\d+$/); // new way
expect(turns[2].u).to.be.not.ok;
});
it('permits turns from a oneway forward', function() {
// u===>v----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(1);
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,-');
expect(turns[0].u).to.be.not.ok;
});
it('permits turns from a reverse oneway backward', function() {
// u<===*----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential', oneway: '-1'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(1);
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,-');
expect(turns[0].u).to.be.not.ok;
});
it('omits turns from a oneway backward', function() {
// u<===*----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.osmIntersection(graph, '*').turns('u')).to.eql([]);
});
it('omits turns from a reverse oneway forward', function() {
// u===>*----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.osmIntersection(graph, '*').turns('u')).to.eql([]);
});
it('permits turns onto a oneway forward', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential', oneway: 'yes'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential', oneway: 'yes'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
});
it('permits turns onto a reverse oneway backward', function() {
// u====*<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*'], tags: {highway: 'residential', oneway: '-1'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*'], tags: {highway: 'residential', oneway: '-1'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
});
it('omits turns onto a oneway backward', function() {
// u====*<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*'], tags: {highway: 'residential', oneway: 'yes'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*'], tags: {highway: 'residential', oneway: 'yes'}})
]);
expect(iD.osmIntersection(graph, '*').turns('u').length).to.eql(1);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(1);
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
});
it('omits turns onto a reverse oneway forward', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential', oneway: '-1'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential', oneway: '-1'}})
]);
expect(iD.osmIntersection(graph, '*').turns('u').length).to.eql(1);
});
it('includes U-turns', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(1);
expect(turns.length).to.eql(2);
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
});
it('restricts turns with a restriction relation', function() {
// u====*--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: '*'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{id: '=', role: 'from', type: 'way'},
{id: '-', role: 'to', type: 'way'},
{id: '*', role: 'via', type: 'node'}
]})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'w'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['*', 'w'], tags: {highway: 'residential'}}),
iD.osmRelation({id: 'r', tags: {type: 'restriction'}, members: [
{id: '=', role: 'from', type: 'way'},
{id: '-', role: 'to', type: 'way'},
{id: '*', role: 'via', type: 'node'}
]})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'r'
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,-');
expect(turns[1].u).to.be.not.ok;
expect(turns[1].restriction).to.eql('r');
expect(turns[1].direct).to.be.true;
expect(turns[1].indirect).to.be.not.ok;
expect(turns[1].only).to.be.not.ok;
});
it('restricts turns affected by an only_* restriction relation', function() {
// u====*~~~~v
// |
// w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Node({id: '*'}),
iD.Way({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '~', nodes: ['v', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', '*'], tags: {highway: 'residential'}}),
iD.Relation({id: 'r', tags: {type: 'restriction', restriction: 'only_right_turn'}, members: [
{id: '=', role: 'from', type: 'way'},
{id: '-', role: 'to', type: 'way'},
{id: '*', role: 'via', type: 'node'}
]})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'u'}),
iD.osmNode({id: 'v'}),
iD.osmNode({id: 'w'}),
iD.osmNode({id: '*'}),
iD.osmWay({id: '=', nodes: ['u', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '~', nodes: ['v', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '-', nodes: ['w', '*'], tags: {highway: 'residential'}}),
iD.osmRelation({id: 'r', tags: {type: 'restriction', restriction: 'only_right_turn'}, members: [
{id: '=', role: 'from', type: 'way'},
{id: '-', role: 'to', type: 'way'},
{id: '*', role: 'via', type: 'node'}
]})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(3);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'v', way: '~'},
restriction: 'r',
indirect_restriction: true
});
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'r'
});
expect(turns[2]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
restriction: 'r',
indirect_restriction: true,
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,=');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,~');
expect(turns[1].restriction).to.eql('r');
expect(turns[1].u).to.be.not.ok;
expect(turns[1].direct).to.be.not.ok;
expect(turns[1].indirect).to.be.true;
expect(turns[1].only).to.be.not.ok;
expect(turns[2]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[2].key).to.eql('=,*,-');
expect(turns[2].restriction).to.eql('r');
expect(turns[2].u).to.be.not.ok;
expect(turns[2].direct).to.be.not.ok;
expect(turns[2].indirect).to.be.not.ok;
expect(turns[2].only).to.be.true;
});
it('permits turns to a circular way', function() {
@@ -392,34 +393,30 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(3);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'a', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'c', way: '-'}
});
expect(turns[2]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,-');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,=');
expect(turns[1].u).to.be.true;
expect(turns[2]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[2].key).to.match(/^=,\*,w-\d+$/); // new way
expect(turns[2].u).to.be.not.ok;
});
it('permits turns from a circular way', function() {
@@ -428,34 +425,30 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('a');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('-');
expect(turns.length).to.eql(3);
expect(turns[0]).to.eql({
from: {node: 'a', way: '-'},
via: {node: '*'},
to: {node: 'c', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'a', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
});
expect(turns[2]).to.eql({
from: {node: 'a', way: '-'},
via: {node: '*'},
to: {node: 'a', way: '-'},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('-,*,-');
expect(turns[0].u).to.be.true;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('-,*,=');
expect(turns[1].u).to.be.not.ok;
expect(turns[2]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[2].key).to.match(/^-,\*,w-\d+$/); // new way
expect(turns[2].u).to.be.not.ok;
});
it('permits turns to a oneway circular way', function() {
@@ -464,29 +457,26 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'a', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,-');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,=');
expect(turns[1].u).to.be.true;
});
it('permits turns to a reverse oneway circular way', function() {
@@ -495,29 +485,26 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('u');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var turns = iD.osmIntersection(graph, '*').turns('=');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'c', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: true
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql('=,*,-');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql('=,*,=');
expect(turns[1].u).to.be.true;
});
it('permits turns from a oneway circular way', function() {
@@ -526,28 +513,28 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('c');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var intersection = iD.osmIntersection(graph, '*');
var newWay = intersection.ways.find(function(w) { return /^w-\d+$/.test(w.id); });
var turns = iD.osmIntersection(graph, '*').turns(newWay.id);
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'c', way: '-'},
via: {node: '*'},
to: {node: 'a', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'c', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql(newWay.id + ',*,-');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql(newWay.id + ',*,=');
expect(turns[1].u).to.be.not.ok;
});
it('permits turns from a reverse oneway circular way', function() {
@@ -556,28 +543,28 @@ describe('iD.osmIntersection', function() {
// | |
// a -- * === u
//
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: '*'}),
iD.Node({id: 'u'}),
iD.Way({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]),
turns = iD.osmIntersection(graph, '*').turns('a');
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: '*'}),
iD.osmNode({id: 'u'}),
iD.osmWay({id: '-', nodes: ['*', 'a', 'b', 'c', '*'], tags: {highway: 'residential', oneway: '-1'}}),
iD.osmWay({id: '=', nodes: ['*', 'u'], tags: {highway: 'residential'}})
]);
var intersection = iD.osmIntersection(graph, '*');
var newWay = intersection.ways.find(function(w) { return /^w-\d+$/.test(w.id); });
var turns = iD.osmIntersection(graph, '*').turns(newWay.id);
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'a', way: '-'},
via: {node: '*'},
to: {node: 'c', way: '-'}
});
expect(turns[1]).to.eql({
from: {node: 'a', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
});
expect(turns[0]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[0].key).to.eql(newWay.id + ',*,-');
expect(turns[0].u).to.be.not.ok;
expect(turns[1]).to.be.an.instanceOf(iD.osmTurn);
expect(turns[1].key).to.eql(newWay.id + ',*,=');
expect(turns[1].u).to.be.not.ok;
});
});
+409 -257
View File
@@ -1,98 +1,98 @@
describe('iD.osmRelation', function () {
if (iD.debug) {
it('freezes nodes', function () {
expect(Object.isFrozen(iD.Relation().members)).to.be.true;
expect(Object.isFrozen(iD.osmRelation().members)).to.be.true;
});
}
it('returns a relation', function () {
expect(iD.Relation()).to.be.an.instanceOf(iD.Relation);
expect(iD.Relation().type).to.equal('relation');
expect(iD.osmRelation()).to.be.an.instanceOf(iD.osmRelation);
expect(iD.osmRelation().type).to.equal('relation');
});
it('defaults members to an empty array', function () {
expect(iD.Relation().members).to.eql([]);
expect(iD.osmRelation().members).to.eql([]);
});
it('sets members as specified', function () {
expect(iD.Relation({members: ['n-1']}).members).to.eql(['n-1']);
expect(iD.osmRelation({members: ['n-1']}).members).to.eql(['n-1']);
});
it('defaults tags to an empty object', function () {
expect(iD.Relation().tags).to.eql({});
expect(iD.osmRelation().tags).to.eql({});
});
it('sets tags as specified', function () {
expect(iD.Relation({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
expect(iD.osmRelation({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
});
describe('#copy', function () {
it('returns a new Relation', function () {
var r = iD.Relation({id: 'r'}),
result = r.copy(null, {});
var r = iD.osmRelation({id: 'r'});
var result = r.copy(null, {});
expect(result).to.be.an.instanceof(iD.Relation);
expect(result).to.be.an.instanceof(iD.osmRelation);
expect(result).not.to.equal(r);
});
it('adds the new Relation to input object', function () {
var r = iD.Relation({id: 'r'}),
copies = {},
result = r.copy(null, copies);
var r = iD.osmRelation({id: 'r'});
var copies = {};
var result = r.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(copies.r).to.equal(result);
});
it('returns an existing copy in input object', function () {
var r = iD.Relation({id: 'r'}),
copies = {},
result1 = r.copy(null, copies),
result2 = r.copy(null, copies);
var r = iD.osmRelation({id: 'r'});
var copies = {};
var result1 = r.copy(null, copies);
var result2 = r.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(result1).to.equal(result2);
});
it('deep copies members', function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w = iD.Way({id: 'w', nodes: ['a','b','c','a']}),
r = iD.Relation({id: 'r', members: [{id: 'w', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w, r]),
copies = {},
result = r.copy(graph, copies);
var a = iD.osmNode({id: 'a'});
var b = iD.osmNode({id: 'b'});
var c = iD.osmNode({id: 'c'});
var w = iD.osmWay({id: 'w', nodes: ['a','b','c','a']});
var r = iD.osmRelation({id: 'r', members: [{id: 'w', role: 'outer'}]});
var graph = iD.coreGraph([a, b, c, w, r]);
var copies = {};
var result = r.copy(graph, copies);
expect(Object.keys(copies)).to.have.length(5);
expect(copies.w).to.be.an.instanceof(iD.Way);
expect(copies.a).to.be.an.instanceof(iD.Node);
expect(copies.b).to.be.an.instanceof(iD.Node);
expect(copies.c).to.be.an.instanceof(iD.Node);
expect(copies.w).to.be.an.instanceof(iD.osmWay);
expect(copies.a).to.be.an.instanceof(iD.osmNode);
expect(copies.b).to.be.an.instanceof(iD.osmNode);
expect(copies.c).to.be.an.instanceof(iD.osmNode);
expect(result.members[0].id).not.to.equal(r.members[0].id);
expect(result.members[0].role).to.equal(r.members[0].role);
});
it('deep copies non-tree relation graphs without duplicating children', function () {
var w = iD.Way({id: 'w'}),
r1 = iD.Relation({id: 'r1', members: [{id: 'r2'}, {id: 'w'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'w'}]}),
graph = iD.Graph([w, r1, r2]),
copies = {};
var w = iD.osmWay({id: 'w'});
var r1 = iD.osmRelation({id: 'r1', members: [{id: 'r2'}, {id: 'w'}]});
var r2 = iD.osmRelation({id: 'r2', members: [{id: 'w'}]});
var graph = iD.coreGraph([w, r1, r2]);
var copies = {};
r1.copy(graph, copies);
expect(Object.keys(copies)).to.have.length(3);
expect(copies.r1).to.be.an.instanceof(iD.Relation);
expect(copies.r2).to.be.an.instanceof(iD.Relation);
expect(copies.w).to.be.an.instanceof(iD.Way);
expect(copies.r1).to.be.an.instanceof(iD.osmRelation);
expect(copies.r2).to.be.an.instanceof(iD.osmRelation);
expect(copies.w).to.be.an.instanceof(iD.osmWay);
expect(copies.r1.members[0].id).to.equal(copies.r2.id);
expect(copies.r1.members[1].id).to.equal(copies.w.id);
expect(copies.r2.members[0].id).to.equal(copies.w.id);
});
it('deep copies cyclical relation graphs without issue', function () {
var r1 = iD.Relation({id: 'r1', members: [{id: 'r2'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
graph = iD.Graph([r1, r2]),
copies = {};
var r1 = iD.osmRelation({id: 'r1', members: [{id: 'r2'}]});
var r2 = iD.osmRelation({id: 'r2', members: [{id: 'r1'}]});
var graph = iD.coreGraph([r1, r2]);
var copies = {};
r1.copy(graph, copies);
expect(Object.keys(copies)).to.have.length(2);
@@ -101,9 +101,9 @@ describe('iD.osmRelation', function () {
});
it('deep copies self-referencing relations without issue', function () {
var r = iD.Relation({id: 'r', members: [{id: 'r'}]}),
graph = iD.Graph([r]),
copies = {};
var r = iD.osmRelation({id: 'r', members: [{id: 'r'}]});
var graph = iD.coreGraph([r]);
var copies = {};
r.copy(graph, copies);
expect(Object.keys(copies)).to.have.length(1);
@@ -113,53 +113,53 @@ describe('iD.osmRelation', function () {
describe('#extent', function () {
it('returns the minimal extent containing the extents of all members', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [5, 10]}),
r = iD.Relation({members: [{id: a.id}, {id: b.id}]}),
graph = iD.Graph([a, b, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [5, 10]});
var r = iD.osmRelation({members: [{id: a.id}, {id: b.id}]});
var graph = iD.coreGraph([a, b, r]);
expect(r.extent(graph).equals([[0, 0], [5, 10]])).to.be.ok;
});
it('returns the known extent of incomplete relations', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [5, 10]}),
r = iD.Relation({members: [{id: a.id}, {id: b.id}]}),
graph = iD.Graph([a, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [5, 10]});
var r = iD.osmRelation({members: [{id: a.id}, {id: b.id}]});
var graph = iD.coreGraph([a, r]);
expect(r.extent(graph).equals([[0, 0], [0, 0]])).to.be.ok;
});
it('does not error on self-referencing relations', function () {
var r = iD.Relation();
var r = iD.osmRelation();
r = r.addMember({id: r.id});
expect(r.extent(iD.Graph([r]))).to.eql(iD.geoExtent());
expect(r.extent(iD.coreGraph([r]))).to.eql(iD.geoExtent());
});
});
describe('#geometry', function () {
it('returns \'area\' for multipolygons', function () {
expect(iD.Relation({tags: {type: 'multipolygon'}}).geometry(iD.Graph())).to.equal('area');
expect(iD.osmRelation({tags: {type: 'multipolygon'}}).geometry(iD.coreGraph())).to.equal('area');
});
it('returns \'relation\' for other relations', function () {
expect(iD.Relation().geometry(iD.Graph())).to.equal('relation');
expect(iD.osmRelation().geometry(iD.coreGraph())).to.equal('relation');
});
});
describe('#isDegenerate', function () {
it('returns true for a relation without members', function () {
expect(iD.Relation().isDegenerate()).to.equal(true);
expect(iD.osmRelation().isDegenerate()).to.equal(true);
});
it('returns false for a relation with members', function () {
expect(iD.Relation({members: [{id: 'a', role: 'inner'}]}).isDegenerate()).to.equal(false);
expect(iD.osmRelation({members: [{id: 'a', role: 'inner'}]}).isDegenerate()).to.equal(false);
});
});
describe('#memberByRole', function () {
it('returns the first member with the given role', function () {
var r = iD.Relation({members: [
var r = iD.osmRelation({members: [
{id: 'a', role: 'inner'},
{id: 'b', role: 'outer'},
{id: 'c', role: 'outer'}]});
@@ -167,13 +167,13 @@ describe('iD.osmRelation', function () {
});
it('returns undefined if no members have the given role', function () {
expect(iD.Relation().memberByRole('outer')).to.be.undefined;
expect(iD.osmRelation().memberByRole('outer')).to.be.undefined;
});
});
describe('#memberById', function () {
it('returns the first member with the given id', function () {
var r = iD.Relation({members: [
var r = iD.osmRelation({members: [
{id: 'a', role: 'outer'},
{id: 'b', role: 'outer'},
{id: 'b', role: 'inner'}]});
@@ -181,101 +181,247 @@ describe('iD.osmRelation', function () {
});
it('returns undefined if no members have the given role', function () {
expect(iD.Relation().memberById('b')).to.be.undefined;
expect(iD.osmRelation().memberById('b')).to.be.undefined;
});
});
describe('#isRestriction', function () {
it('returns true for \'restriction\' type', function () {
expect(iD.Relation({tags: {type: 'restriction'}}).isRestriction()).to.be.true;
expect(iD.osmRelation({tags: {type: 'restriction'}}).isRestriction()).to.be.true;
});
it('returns true for \'restriction:type\' types', function () {
expect(iD.Relation({tags: {type: 'restriction:bus'}}).isRestriction()).to.be.true;
expect(iD.osmRelation({tags: {type: 'restriction:bus'}}).isRestriction()).to.be.true;
});
it('returns false otherwise', function () {
expect(iD.Relation().isRestriction()).to.be.false;
expect(iD.Relation({tags: {type: 'multipolygon'}}).isRestriction()).to.be.false;
expect(iD.osmRelation().isRestriction()).to.be.false;
expect(iD.osmRelation({tags: {type: 'multipolygon'}}).isRestriction()).to.be.false;
});
});
describe('#isValidRestriction', function () {
it('not a restriction', function () {
var r = iD.osmRelation({ id: 'r', tags: { type: 'multipolygon' }});
var graph = iD.coreGraph([r]);
expect(r.isValidRestriction(graph)).to.be.false;
});
it('typical restriction (from way, via node, to way) is valid', function () {
var f = iD.osmWay({id: 'f'});
var v = iD.osmNode({id: 'v'});
var t = iD.osmWay({id: 't'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_left_turn' },
members: [
{ role: 'from', id: 'f', type: 'way' },
{ role: 'via', id: 'v', type: 'node' },
{ role: 'to', id: 't', type: 'way' },
]
});
var graph = iD.coreGraph([f, v, t, r]);
expect(r.isValidRestriction(graph)).to.be.true;
});
it('multiple froms, normal restriction is invalid', function () {
var f1 = iD.osmWay({id: 'f1'});
var f2 = iD.osmWay({id: 'f2'});
var v = iD.osmNode({id: 'v'});
var t = iD.osmWay({id: 't'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_left_turn' },
members: [
{ role: 'from', id: 'f1', type: 'way' },
{ role: 'from', id: 'f2', type: 'way' },
{ role: 'via', id: 'v', type: 'node' },
{ role: 'to', id: 't', type: 'way' },
]
});
var graph = iD.coreGraph([f1, f2, v, t, r]);
expect(r.isValidRestriction(graph)).to.be.false;
});
it('multiple froms, no_entry restriction is valid', function () {
var f1 = iD.osmWay({id: 'f1'});
var f2 = iD.osmWay({id: 'f2'});
var v = iD.osmNode({id: 'v'});
var t = iD.osmWay({id: 't'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_entry' },
members: [
{ role: 'from', id: 'f1', type: 'way' },
{ role: 'from', id: 'f2', type: 'way' },
{ role: 'via', id: 'v', type: 'node' },
{ role: 'to', id: 't', type: 'way' },
]
});
var graph = iD.coreGraph([f1, f2, v, t, r]);
expect(r.isValidRestriction(graph)).to.be.true;
});
it('multiple tos, normal restriction is invalid', function () {
var f = iD.osmWay({id: 'f'});
var v = iD.osmNode({id: 'v'});
var t1 = iD.osmWay({id: 't1'});
var t2 = iD.osmWay({id: 't2'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_left_turn' },
members: [
{ role: 'from', id: 'f', type: 'way' },
{ role: 'via', id: 'v', type: 'node' },
{ role: 'to', id: 't1', type: 'way' },
{ role: 'to', id: 't2', type: 'way' },
]
});
var graph = iD.coreGraph([f, v, t1, t2, r]);
expect(r.isValidRestriction(graph)).to.be.false;
});
it('multiple tos, no_exit restriction is valid', function () {
var f = iD.osmWay({id: 'f'});
var v = iD.osmNode({id: 'v'});
var t1 = iD.osmWay({id: 't1'});
var t2 = iD.osmWay({id: 't2'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_exit' },
members: [
{ role: 'from', id: 'f', type: 'way' },
{ role: 'via', id: 'v', type: 'node' },
{ role: 'to', id: 't1', type: 'way' },
{ role: 'to', id: 't2', type: 'way' },
]
});
var graph = iD.coreGraph([f, v, t1, t2, r]);
expect(r.isValidRestriction(graph)).to.be.true;
});
it('multiple vias, with some as node is invalid', function () {
var f = iD.osmWay({id: 'f'});
var v1 = iD.osmNode({id: 'v1'});
var v2 = iD.osmWay({id: 'v2'});
var t = iD.osmWay({id: 't'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_left_turn' },
members: [
{ role: 'from', id: 'f', type: 'way' },
{ role: 'via', id: 'v1', type: 'node' },
{ role: 'via', id: 'v2', type: 'way' },
{ role: 'to', id: 't', type: 'way' },
]
});
var graph = iD.coreGraph([f, v1, v2, t, r]);
expect(r.isValidRestriction(graph)).to.be.false;
});
it('multiple vias, with all as way is valid', function () {
var f = iD.osmWay({id: 'f'});
var v1 = iD.osmWay({id: 'v1'});
var v2 = iD.osmWay({id: 'v2'});
var t = iD.osmWay({id: 't'});
var r = iD.osmRelation({
id: 'r',
tags: { type: 'restriction', restriction: 'no_left_turn' },
members: [
{ role: 'from', id: 'f', type: 'way' },
{ role: 'via', id: 'v1', type: 'way' },
{ role: 'via', id: 'v2', type: 'way' },
{ role: 'to', id: 't', type: 'way' },
]
});
var graph = iD.coreGraph([f, v1, v2, t, r]);
expect(r.isValidRestriction(graph)).to.be.true;
});
});
describe('#indexedMembers', function () {
it('returns an array of members extended with indexes', function () {
var r = iD.Relation({members: [{id: '1'}, {id: '3'}]});
var r = iD.osmRelation({members: [{id: '1'}, {id: '3'}]});
expect(r.indexedMembers()).to.eql([{id: '1', index: 0}, {id: '3', index: 1}]);
});
});
describe('#addMember', function () {
it('adds a member at the end of the relation', function () {
var r = iD.Relation();
var r = iD.osmRelation();
expect(r.addMember({id: '1'}).members).to.eql([{id: '1'}]);
});
it('adds a member at index 0', function () {
var r = iD.Relation({members: [{id: '1'}]});
var r = iD.osmRelation({members: [{id: '1'}]});
expect(r.addMember({id: '2'}, 0).members).to.eql([{id: '2'}, {id: '1'}]);
});
it('adds a member at a positive index', function () {
var r = iD.Relation({members: [{id: '1'}, {id: '3'}]});
var r = iD.osmRelation({members: [{id: '1'}, {id: '3'}]});
expect(r.addMember({id: '2'}, 1).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
});
it('adds a member at a negative index', function () {
var r = iD.Relation({members: [{id: '1'}, {id: '3'}]});
var r = iD.osmRelation({members: [{id: '1'}, {id: '3'}]});
expect(r.addMember({id: '2'}, -1).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
});
});
describe('#updateMember', function () {
it('updates the properties of the relation member at the specified index', function () {
var r = iD.Relation({members: [{role: 'forward'}]});
var r = iD.osmRelation({members: [{role: 'forward'}]});
expect(r.updateMember({role: 'backward'}, 0).members).to.eql([{role: 'backward'}]);
});
});
describe('#removeMember', function () {
it('removes the member at the specified index', function () {
var r = iD.Relation({members: [{id: 'a'}, {id: 'b'}, {id: 'c'}]});
var r = iD.osmRelation({members: [{id: 'a'}, {id: 'b'}, {id: 'c'}]});
expect(r.removeMember(1).members).to.eql([{id: 'a'}, {id: 'c'}]);
});
});
describe('#removeMembersWithID', function () {
it('removes members with the given ID', function () {
var r = iD.Relation({members: [{id: 'a'}, {id: 'b'}, {id: 'a'}]});
var r = iD.osmRelation({members: [{id: 'a'}, {id: 'b'}, {id: 'a'}]});
expect(r.removeMembersWithID('a').members).to.eql([{id: 'b'}]);
});
});
describe('#replaceMember', function () {
it('returns self if self does not contain needle', function () {
var r = iD.Relation({members: []});
var r = iD.osmRelation({members: []});
expect(r.replaceMember({id: 'a'}, {id: 'b'})).to.equal(r);
});
it('replaces a member which doesn\'t already exist', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a'}]});
var r = iD.osmRelation({members: [{id: 'a', role: 'a'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members)
.to.eql([{id: 'b', role: 'a', type: 'node'}]);
});
it('preserves the existing role', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a', type: 'node'}]});
var r = iD.osmRelation({members: [{id: 'a', role: 'a', type: 'node'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members)
.to.eql([{id: 'b', role: 'a', type: 'node'}]);
});
it('uses the replacement type', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a', type: 'node'}]});
var r = iD.osmRelation({members: [{id: 'a', role: 'a', type: 'node'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'way'}).members)
.to.eql([{id: 'b', role: 'a', type: 'way'}]);
});
it('removes members if replacing them would produce duplicates', function () {
var r = iD.Relation({members: [
var r = iD.osmRelation({members: [
{id: 'a', role: 'b', type: 'node'},
{id: 'b', role: 'b', type: 'node'}
]});
@@ -283,7 +429,7 @@ describe('iD.osmRelation', function () {
.to.eql([{id: 'b', role: 'b', type: 'node'}]);
});
it('keeps duplicate members if `keepDuplicates = true`', function () {
var r = iD.Relation({members: [
var r = iD.osmRelation({members: [
{id: 'a', role: 'b', type: 'node'},
{id: 'b', role: 'b', type: 'node'}
]});
@@ -294,7 +440,7 @@ describe('iD.osmRelation', function () {
describe('#asJXON', function () {
it('converts a relation to jxon', function() {
var relation = iD.Relation({id: 'r-1', members: [{id: 'w1', role: 'forward', type: 'way'}], tags: {type: 'route'}});
var relation = iD.osmRelation({id: 'r-1', members: [{id: 'w1', role: 'forward', type: 'way'}], tags: {type: 'route'}});
expect(relation.asJXON()).to.eql({relation: {
'@id': '-1',
'@version': 0,
@@ -303,56 +449,56 @@ describe('iD.osmRelation', function () {
});
it('includes changeset if provided', function() {
expect(iD.Relation().asJXON('1234').relation['@changeset']).to.equal('1234');
expect(iD.osmRelation().asJXON('1234').relation['@changeset']).to.equal('1234');
});
});
describe('#asGeoJSON', function (){
describe('#asGeoJSON', function () {
it('converts a multipolygon to a GeoJSON MultiPolygon geometry', function() {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]),
json = r.asGeoJSON(g);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var r = iD.osmRelation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w, r]);
var json = r.asGeoJSON(g);
expect(json.type).to.equal('MultiPolygon');
expect(json.coordinates).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
it('forces clockwise winding order for outer multipolygon ways', function() {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
w = iD.Way({nodes: [a.id, c.id, b.id, a.id]}),
r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]),
json = r.asGeoJSON(g);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var w = iD.osmWay({nodes: [a.id, c.id, b.id, a.id]});
var r = iD.osmRelation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w, r]);
var json = r.asGeoJSON(g);
expect(json.coordinates[0][0]).to.eql([a.loc, b.loc, c.loc, a.loc]);
});
it('forces counterclockwise winding order for inner multipolygon ways', function() {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
d = iD.Node({loc: [0.1, 0.1]}),
e = iD.Node({loc: [0.1, 0.2]}),
f = iD.Node({loc: [0.2, 0.1]}),
outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
inner = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, outer, inner, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var d = iD.osmNode({loc: [0.1, 0.1]});
var e = iD.osmNode({loc: [0.1, 0.2]});
var f = iD.osmNode({loc: [0.2, 0.1]});
var outer = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var inner = iD.osmWay({nodes: [d.id, e.id, f.id, d.id]});
var r = iD.osmRelation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]});
var g = iD.coreGraph([a, b, c, d, e, f, outer, inner, r]);
expect(r.multipolygon(g)[0][1]).to.eql([d.loc, f.loc, e.loc, d.loc]);
});
it('converts a relation to a GeoJSON FeatureCollection', function() {
var a = iD.Node({loc: [1, 1]}),
r = iD.Relation({tags: {type: 'type'}, members: [{id: a.id, role: 'role'}]}),
g = iD.Graph([a, r]),
json = r.asGeoJSON(g);
var a = iD.osmNode({loc: [1, 1]});
var r = iD.osmRelation({tags: {type: 'type'}, members: [{id: a.id, role: 'role'}]});
var g = iD.coreGraph([a, r]);
var json = r.asGeoJSON(g);
expect(json.type).to.equal('FeatureCollection');
expect(json.properties).to.eql({type: 'type'});
@@ -365,214 +511,220 @@ describe('iD.osmRelation', function () {
describe('#multipolygon', function () {
specify('single polygon consisting of a single way', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var r = iD.osmRelation({members: [{id: w.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify('single polygon consisting of multiple ways', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w1 = iD.Way({nodes: [a.id, b.id]}),
w2 = iD.Way({nodes: [b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w1 = iD.osmWay({nodes: [a.id, b.id]});
var w2 = iD.osmWay({nodes: [b.id, c.id, a.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify('single polygon consisting of multiple ways, one needing reversal', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w1 = iD.Way({nodes: [a.id, b.id]}),
w2 = iD.Way({nodes: [a.id, c.id, b.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w1 = iD.osmWay({nodes: [a.id, b.id]});
var w2 = iD.osmWay({nodes: [a.id, c.id, b.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify('multiple polygons consisting of single ways', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
d = iD.Node({loc: [4, 4]}),
e = iD.Node({loc: [6, 6]}),
f = iD.Node({loc: [5, 5]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
w2 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var d = iD.osmNode({loc: [4, 4]});
var e = iD.osmNode({loc: [6, 6]});
var f = iD.osmNode({loc: [5, 5]});
var w1 = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var w2 = iD.osmWay({nodes: [d.id, e.id, f.id, d.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, d, e, f, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]], [[d.loc, e.loc, f.loc, d.loc]]]);
});
specify('invalid geometry: unclosed ring consisting of a single way', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w = iD.osmWay({nodes: [a.id, b.id, c.id]});
var r = iD.osmRelation({members: [{id: w.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
specify('invalid geometry: unclosed ring consisting of multiple ways', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [3, 3]}),
c = iD.Node({loc: [2, 2]}),
w1 = iD.Way({nodes: [a.id, b.id]}),
w2 = iD.Way({nodes: [b.id, c.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
var c = iD.osmNode({loc: [2, 2]});
var w1 = iD.osmWay({nodes: [a.id, b.id]});
var w2 = iD.osmWay({nodes: [b.id, c.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
specify('invalid geometry: unclosed ring consisting of multiple ways, alternate order', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [c.id, d.id]}),
w2 = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [2, 2]});
var c = iD.osmNode({loc: [3, 3]});
var d = iD.osmNode({loc: [4, 4]});
var w1 = iD.osmWay({nodes: [c.id, d.id]});
var w2 = iD.osmWay({nodes: [a.id, b.id, c.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[d.loc, c.loc, b.loc, a.loc]]]);
});
specify('invalid geometry: unclosed ring consisting of multiple ways, one needing reversal', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [d.id, c.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [2, 2]});
var c = iD.osmNode({loc: [3, 3]});
var d = iD.osmNode({loc: [4, 4]});
var w1 = iD.osmWay({nodes: [a.id, b.id, c.id]});
var w2 = iD.osmWay({nodes: [d.id, c.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[d.loc, c.loc, b.loc, a.loc]]]);
});
specify('invalid geometry: unclosed ring consisting of multiple ways, one needing reversal, alternate order', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [c.id, d.id]}),
w2 = iD.Way({nodes: [c.id, b.id, a.id]}),
r = iD.Relation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [2, 2]});
var c = iD.osmNode({loc: [3, 3]});
var d = iD.osmNode({loc: [4, 4]});
var w1 = iD.osmWay({nodes: [c.id, d.id]});
var w2 = iD.osmWay({nodes: [c.id, b.id, a.id]});
var r = iD.osmRelation({members: [{id: w1.id, type: 'way'}, {id: w2.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[d.loc, c.loc, b.loc, a.loc]]]);
});
specify('single polygon with single single-way inner', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
d = iD.Node({loc: [0.1, 0.1]}),
e = iD.Node({loc: [0.2, 0.1]}),
f = iD.Node({loc: [0.1, 0.2]}),
outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
inner = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, outer, inner, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var d = iD.osmNode({loc: [0.1, 0.1]});
var e = iD.osmNode({loc: [0.2, 0.1]});
var f = iD.osmNode({loc: [0.1, 0.2]});
var outer = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var inner = iD.osmWay({nodes: [d.id, e.id, f.id, d.id]});
var r = iD.osmRelation({members: [
{id: outer.id, type: 'way'},
{id: inner.id, role: 'inner', type: 'way'}
]});
var g = iD.coreGraph([a, b, c, d, e, f, outer, inner, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify('single polygon with single multi-way inner', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
d = iD.Node({loc: [0.1, 0.1]}),
e = iD.Node({loc: [0.2, 0.1]}),
f = iD.Node({loc: [0.2, 0.1]}),
outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
inner1 = iD.Way({nodes: [d.id, e.id]}),
inner2 = iD.Way({nodes: [e.id, f.id, d.id]}),
r = iD.Relation({members: [
{id: outer.id, type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, outer, inner1, inner2, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var d = iD.osmNode({loc: [0.1, 0.1]});
var e = iD.osmNode({loc: [0.2, 0.1]});
var f = iD.osmNode({loc: [0.2, 0.1]});
var outer = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var inner1 = iD.osmWay({nodes: [d.id, e.id]});
var inner2 = iD.osmWay({nodes: [e.id, f.id, d.id]});
var r = iD.osmRelation({members: [
{id: outer.id, type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, e, f, outer, inner1, inner2, r]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify('single polygon with multiple single-way inners', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
d = iD.Node({loc: [0.1, 0.1]}),
e = iD.Node({loc: [0.2, 0.1]}),
f = iD.Node({loc: [0.1, 0.2]}),
g = iD.Node({loc: [0.2, 0.2]}),
h = iD.Node({loc: [0.3, 0.2]}),
i = iD.Node({loc: [0.2, 0.3]}),
outer = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
inner1 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
inner2 = iD.Way({nodes: [g.id, h.id, i.id, g.id]}),
r = iD.Relation({members: [
{id: outer.id, type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer, inner1, inner2, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var d = iD.osmNode({loc: [0.1, 0.1]});
var e = iD.osmNode({loc: [0.2, 0.1]});
var f = iD.osmNode({loc: [0.1, 0.2]});
var g = iD.osmNode({loc: [0.2, 0.2]});
var h = iD.osmNode({loc: [0.3, 0.2]});
var i = iD.osmNode({loc: [0.2, 0.3]});
var outer = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var inner1 = iD.osmWay({nodes: [d.id, e.id, f.id, d.id]});
var inner2 = iD.osmWay({nodes: [g.id, h.id, i.id, g.id]});
var r = iD.osmRelation({members: [
{id: outer.id, type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, e, f, g, h, i, outer, inner1, inner2, r]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc], [g.loc, h.loc, i.loc, g.loc]]]);
});
specify('multiple polygons with single single-way inner', function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [0, 1]}),
c = iD.Node({loc: [1, 0]}),
d = iD.Node({loc: [0.1, 0.1]}),
e = iD.Node({loc: [0.2, 0.1]}),
f = iD.Node({loc: [0.1, 0.2]}),
g = iD.Node({loc: [0, 0]}),
h = iD.Node({loc: [0, -1]}),
i = iD.Node({loc: [-1, 0]}),
outer1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
outer2 = iD.Way({nodes: [g.id, h.id, i.id, g.id]}),
inner = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [
{id: outer1.id, type: 'way'},
{id: outer2.id, type: 'way'},
{id: inner.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer1, outer2, inner, r]);
var a = iD.osmNode({loc: [0, 0]});
var b = iD.osmNode({loc: [0, 1]});
var c = iD.osmNode({loc: [1, 0]});
var d = iD.osmNode({loc: [0.1, 0.1]});
var e = iD.osmNode({loc: [0.2, 0.1]});
var f = iD.osmNode({loc: [0.1, 0.2]});
var g = iD.osmNode({loc: [0, 0]});
var h = iD.osmNode({loc: [0, -1]});
var i = iD.osmNode({loc: [-1, 0]});
var outer1 = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var outer2 = iD.osmWay({nodes: [g.id, h.id, i.id, g.id]});
var inner = iD.osmWay({nodes: [d.id, e.id, f.id, d.id]});
var r = iD.osmRelation({members: [
{id: outer1.id, type: 'way'},
{id: outer2.id, type: 'way'},
{id: inner.id, role: 'inner', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, e, f, g, h, i, outer1, outer2, inner, r]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]], [[g.loc, h.loc, i.loc, g.loc]]]);
});
specify('invalid geometry: unmatched inner', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [2, 2]});
var c = iD.osmNode({loc: [3, 3]});
var w = iD.osmWay({nodes: [a.id, b.id, c.id, a.id]});
var r = iD.osmRelation({members: [{id: w.id, role: 'inner', type: 'way'}]});
var g = iD.coreGraph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify('incomplete relation', function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way(),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, r]);
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [2, 2]});
var c = iD.osmNode({loc: [3, 3]});
var w1 = iD.osmWay({nodes: [a.id, b.id, c.id]});
var w2 = iD.osmWay();
var r = iD.osmRelation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]});
var g = iD.coreGraph([a, b, c, w1, r]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
@@ -580,17 +732,17 @@ describe('iD.osmRelation', function () {
describe('.creationOrder comparator', function () {
specify('orders existing relations newest-first', function () {
var a = iD.Relation({ id: 'r1' }),
b = iD.Relation({ id: 'r2' });
expect(iD.Relation.creationOrder(a, b)).to.be.above(0);
expect(iD.Relation.creationOrder(b, a)).to.be.below(0);
var a = iD.osmRelation({ id: 'r1' });
var b = iD.osmRelation({ id: 'r2' });
expect(iD.osmRelation.creationOrder(a, b)).to.be.above(0);
expect(iD.osmRelation.creationOrder(b, a)).to.be.below(0);
});
specify('orders new relations newest-first', function () {
var a = iD.Relation({ id: 'r-1' }),
b = iD.Relation({ id: 'r-2' });
expect(iD.Relation.creationOrder(a, b)).to.be.above(0);
expect(iD.Relation.creationOrder(b, a)).to.be.below(0);
var a = iD.osmRelation({ id: 'r-1' });
var b = iD.osmRelation({ id: 'r-2' });
expect(iD.osmRelation.creationOrder(a, b)).to.be.above(0);
expect(iD.osmRelation.creationOrder(b, a)).to.be.below(0);
});
});
});