Implement basic restriction toggling

This commit is contained in:
John Firebaugh
2014-05-16 17:03:55 -07:00
parent f74f6ad54a
commit 3d210ac3c1
14 changed files with 487 additions and 97 deletions
+4
View File
@@ -145,6 +145,10 @@ en:
multiple: "Split {n} lines/area boundaries."
not_eligible: Lines can't be split at their beginning or end.
multiple_ways: There are too many lines here to split.
restriction:
annotation:
create: Added a turn restriction
delete: Deleted a turn restriction
undo:
tooltip: "Undo: {action}"
nothing: Nothing to undo.
+6
View File
@@ -182,6 +182,12 @@
},
"not_eligible": "Lines can't be split at their beginning or end.",
"multiple_ways": "There are too many lines here to split."
},
"restriction": {
"annotation": {
"create": "Added a turn restriction",
"delete": "Deleted a turn restriction"
}
}
},
"undo": {
+6 -4
View File
@@ -138,6 +138,7 @@
<script src='js/id/actions/change_member.js'></script>
<script src='js/id/actions/change_preset.js'></script>
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/connect.js'></script>
<script src='js/id/actions/delete_member.js'></script>
<script src='js/id/actions/delete_multiple.js'></script>
@@ -151,13 +152,14 @@
<script src='js/id/actions/merge_polygon.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move.js'></script>
<script src='js/id/actions/rotate_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
<script src='js/id/actions/straighten.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
<script src='js/id/actions/rotate_way.js'></script>
<script src='js/id/actions/restrict_turn.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/straighten.js'></script>
<script src='js/id/actions/split.js'></script>
<script src='js/id/actions/unrestrict_turn.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
+88
View File
@@ -0,0 +1,88 @@
// Create a restriction relation for `turn`, which must have the following structure:
//
// {
// from: { node: <node ID>, way: <way ID> },
// via: { node: <node 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`.
// (The action does not check that these entities form a valid intersection.)
//
// If `restriction` is not provided, it is automatically determined by the
// angle of the turn:
//
// 0-23 degrees: no_u_turn
// 23-158 degrees: no_right_turn
// 158-202 degrees: no_straight_on
// 202-326 degrees: no_left_turn
// 336-360 degrees: no_u_turn
//
// 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.
//
// For testing convenience, accepts an ID to assign to the new relation.
// Normally, this will be undefined and the relation will automatically
// be assigned a new ID.
//
iD.actions.RestrictTurn = function(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);
if (!from.affix(via.id)) {
var newFromId = turn.from.newID || iD.Way().id;
graph = iD.actions.Split(via.id, [newFromId])
.limitWays([from.id])(graph);
var newFrom = graph.entity(newFromId);
if (newFrom.nodes.indexOf(turn.from.node) !== -1)
from = newFrom;
}
if (turn.from.way === turn.to.way) {
to = from;
} else if (!to.affix(via.id)) {
var newToId = turn.to.newID || iD.Way().id;
graph = iD.actions.Split(via.id, [newToId])
.limitWays([to.id])(graph);
var newTo = graph.entity(newToId);
if (newTo.nodes.indexOf(turn.to.node) !== -1)
to = newTo;
}
return graph.replace(iD.Relation({
id: restrictionId,
tags: {
type: 'restriction',
restriction: turn.restriction || guessRestriction()
},
members: [
{id: from.id, type: 'way', role: 'from'},
{id: via.id, type: 'node', role: 'via'},
{id: to.id, type: 'way', role: 'to'}
]
}));
function guessRestriction() {
var angle = iD.geo.angle(via, graph.entity(turn.from.node), projection) -
iD.geo.angle(via, graph.entity(turn.to.node), projection);
if (angle > 158 || angle < -158)
return 'no_straight_on';
if (angle > 23)
return 'no_right_turn';
if (angle < -22)
return 'no_left_turn';
return 'no_u_turn';
}
};
};
+23
View File
@@ -0,0 +1,23 @@
// Remove the effects of `turn.restriction` on `turn`, which must have the
// following structure:
//
// {
// from: { node: <node ID>, way: <way ID> },
// via: { node: <node ID> },
// to: { node: <node ID>, way: <way ID> },
// restriction: <relation ID>
// }
//
// In the simple case, `restriction` is a reference to a `no_*` restriction
// on the turn itself. In this case, it is simply deleted.
//
// The more complex case is where `restriction` references an `only_*`
// restriction on a different turn in the same intersection. In that case,
// that restriction is also deleted, but at the same time restrictions on
// the turns other than the first two are created.
//
iD.actions.UnrestrictTurn = function(turn) {
return function(graph) {
return iD.actions.DeleteRelation(turn.restriction)(graph);
};
};
+8
View File
@@ -33,6 +33,14 @@ iD.geo.edgeEqual = function(a, b) {
(a[0] === b[1] && a[1] === b[0]);
};
// Return the counterclockwise angle in the range (-180, 180) degrees
// between the positive X axis and the line intersecting a and b.
iD.geo.angle = function(a, b, projection) {
a = projection(a.loc);
b = projection(b.loc);
return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI;
};
// Choose the edge with the minimal distance from `point` to its orthogonal
// projection onto that edge, if such a projection exists, or the distance to
// the closest vertex on that edge. Returns an object with the `index` of the
+19 -33
View File
@@ -1,21 +1,7 @@
iD.geo.Turn = function(turn) {
turn = _.clone(turn);
turn.key = function() {
var components = [turn.from, turn.to, turn.via, turn.toward];
if (turn.restriction)
components.push(turn.restriction);
return components.map(iD.Entity.key).join('-');
};
turn.angle = function(projection) {
var v = projection(turn.via.loc),
t = projection(turn.toward.loc);
return Math.atan2(t[1] - v[1], t[0] - v[0]);
};
return turn;
if (!(this instanceof iD.geo.Turn))
return new iD.geo.Turn(turn);
_.extend(this, turn);
};
iD.geo.Intersection = function(graph, vertexId) {
@@ -33,8 +19,8 @@ iD.geo.Intersection = function(graph, vertexId) {
highways.push(way);
} else {
var idx = _.indexOf(way.nodes, vertex.id, 1),
wayA = iD.Way({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, idx + 1)}),
wayB = iD.Way({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(idx)});
wayA = iD.Way({id: way.id + '.a', tags: way.tags, nodes: way.nodes.slice(0, idx + 1)}),
wayB = iD.Way({id: way.id + '.b', tags: way.tags, nodes: way.nodes.slice(idx)});
graph = graph.replace(wayA);
graph = graph.replace(wayB);
@@ -60,7 +46,7 @@ iD.geo.Intersection = function(graph, vertexId) {
return [];
function withRestriction(turn) {
graph.parentRelations(turn.from).forEach(function(relation) {
graph.parentRelations(graph.entity(turn.from.way)).forEach(function(relation) {
if (relation.tags.type !== 'restriction')
return;
@@ -68,17 +54,19 @@ iD.geo.Intersection = function(graph, vertexId) {
t = relation.memberByRole('to'),
v = relation.memberByRole('via');
if (f && f.id === turn.from.id &&
t && t.id === turn.to.id &&
v && v.id === turn.via.id) {
turn.restriction = relation;
if (f && f.id === turn.from.way &&
v && v.id === turn.via.node &&
t && t.id === turn.to.way) {
turn.restriction = relation.id;
}
});
return iD.geo.Turn(turn);
}
var turns = [];
var from = {node: way.nodes[way.first() === vertex.id ? 1 : way.nodes.length - 2], way: way.id},
via = {node: vertex.id},
turns = [];
highways.forEach(function(parent) {
if (parent === way)
@@ -89,20 +77,18 @@ iD.geo.Intersection = function(graph, vertexId) {
// backward
if (parent.first() !== vertex.id && parent.tags.oneway !== 'yes') {
turns.push(withRestriction({
from: way,
to: parent,
via: vertex,
toward: graph.entity(parent.nodes[index - 1])
from: from,
via: via,
to: {node: parent.nodes[index - 1], way: parent.id.split('.')[0]}
}));
}
// forward
if (parent.last() !== vertex.id && parent.tags.oneway !== '-1') {
turns.push(withRestriction({
from: way,
to: parent,
via: vertex,
toward: graph.entity(parent.nodes[index + 1])
from: from,
via: via,
to: {node: parent.nodes[index + 1], way: parent.id.split('.')[0]}
}));
}
});
+7 -3
View File
@@ -1,7 +1,7 @@
iD.svg.Turns = function(projection) {
return function(surface, graph, turns) {
var groups = surface.select('.layer-hit').selectAll('g.turn')
.data(turns, function(turn) { return turn.key(); });
.data(turns);
var enter = groups.enter().append('g')
.attr('class', 'turn');
@@ -17,10 +17,14 @@ iD.svg.Turns = function(projection) {
return turn.restriction;
})
.attr('transform', function(turn) {
return iD.svg.PointTransform(projection)(turn.via) +
'rotate(' + turn.angle(projection) * 180 / Math.PI + ')';
var v = graph.entity(turn.via.node),
t = graph.entity(turn.to.node);
return iD.svg.PointTransform(projection)(v) +
'rotate(' + iD.geo.angle(v, t, projection) + ')';
});
groups.select('path'); // Propagate updated data.
groups.exit()
.remove();
+10 -2
View File
@@ -7,8 +7,6 @@ iD.ui.preset.restrictions = function(field, context) {
var wrap = selection.selectAll('.preset-input-wrap')
.data([0]);
// Enter
var enter = wrap.enter().append('div')
.attr('class', 'preset-input-wrap');
@@ -48,6 +46,16 @@ iD.ui.preset.restrictions = function(field, context) {
if (datum instanceof iD.Entity) {
selectedID = datum.id;
render();
} else if (datum instanceof iD.geo.Turn) {
if (datum.restriction) {
context.perform(
iD.actions.UnrestrictTurn(datum, projection),
t('operations.restriction.annotation.delete'));
} else {
context.perform(
iD.actions.RestrictTurn(datum, projection),
t('operations.restriction.annotation.create'));
}
}
});
+8 -4
View File
@@ -118,6 +118,7 @@
<script src='../js/id/actions/change_member.js'></script>
<script src='../js/id/actions/change_preset.js'></script>
<script src='../js/id/actions/change_tags.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/connect.js'></script>
<script src='../js/id/actions/delete_member.js'></script>
<script src='../js/id/actions/delete_multiple.js'></script>
@@ -131,13 +132,14 @@
<script src='../js/id/actions/merge_polygon.js'></script>
<script src='../js/id/actions/move_node.js'></script>
<script src='../js/id/actions/move.js'></script>
<script src='../js/id/actions/rotate_way.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/orthogonalize.js'></script>
<script src='../js/id/actions/straighten.js'></script>
<script src='../js/id/actions/noop.js'></script>
<script src='../js/id/actions/orthogonalize.js'></script>
<script src='../js/id/actions/restrict_turn.js'></script>
<script src='../js/id/actions/reverse.js'></script>
<script src='../js/id/actions/rotate_way.js'></script>
<script src='../js/id/actions/split.js'></script>
<script src='../js/id/actions/straighten.js'></script>
<script src='../js/id/actions/unrestrict_turn.js'></script>
<script src='../js/id/behavior.js'></script>
<script src='../js/id/behavior/add_way.js'></script>
@@ -230,8 +232,10 @@
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/restrict_turn.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
<script src="spec/actions/unrestrict_turn.js"></script>
<script src="spec/geo/extent.js"></script>
<script src="spec/geo/intersection.js"></script>
+1
View File
@@ -47,6 +47,7 @@
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
<script src="spec/actions/unrestrict_turn.js"></script>
<script src="spec/geo/extent.js"></script>
<script src="spec/geo/intersection.js"></script>
+239
View File
@@ -0,0 +1,239 @@
describe("iD.actions.RestrictTurn", function() {
var projection = d3.geo.mercator().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.actions.RestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'no_right_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '=', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '-', type: 'way'});
});
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.actions.RestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'},
restriction: 'no_right_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '=', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '-', type: '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.actions.RestrictTurn({
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(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '==', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '-', type: '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.actions.RestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'w', way: '=', newID: '=='},
restriction: 'no_right_turn'
}, projection, 'r');
graph = action(graph);
var r = graph.entity('r');
expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'});
expect(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '-', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '==', type: '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.actions.RestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'u', 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(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '-', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '=', type: '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.actions.RestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
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(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '=', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '=', type: 'way'});
});
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.actions.RestrictTurn({
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(_.pick(r.memberByRole('from'), 'id', 'type')).to.eql({id: '==', type: 'way'});
expect(_.pick(r.memberByRole('via'), 'id', 'type')).to.eql({id: '*', type: 'node'});
expect(_.pick(r.memberByRole('to'), 'id', 'type')).to.eql({id: '==', type: 'way'});
});
it('guesses 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: [ 0, 1]}),
iD.Node({id: 'x', loc: [ 0, -1]}),
iD.Way({id: '=', nodes: ['u', '*']}),
iD.Way({id: '-', nodes: ['*', 'x']}),
iD.Way({id: '~', nodes: ['*', 'w']})
]);
var r = iD.actions.RestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'}
}, projection, 'r')(graph);
expect(r.entity('r').tags.restriction).to.equal('no_right_turn');
var l = iD.actions.RestrictTurn({
from: {node: 'x', way: '-'},
via: {node: '*'},
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(l.entity('r').tags.restriction).to.equal('no_left_turn');
var s = iD.actions.RestrictTurn({
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.actions.RestrictTurn({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='}
}, projection, 'r')(graph);
expect(u.entity('r').tags.restriction).to.equal('no_u_turn');
});
});
+24
View File
@@ -0,0 +1,24 @@
describe("iD.actions.UnrestrictTurn", function() {
it('removes a restriction from a restricted turn', 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'}
]})
]),
action = iD.actions.UnrestrictTurn({
restriction: 'r'
});
graph = action(graph);
expect(graph.hasEntity('r')).to.be.undefined;
});
});
+44 -51
View File
@@ -1,18 +1,3 @@
describe('iD.geo.Turn', function() {
describe('#angle', function() {
it("calculates the angle of via to toward", function() {
function projection(x) { return x; }
var turn = iD.geo.Turn({
via: iD.Node({id: 'v', loc: [1, 0]}),
toward: iD.Node({id: 'w', loc: [1, 1]})
});
expect(turn.angle(projection)).to.eql(Math.PI / 2);
});
});
});
describe("iD.geo.Intersection", function() {
describe('highways', function() {
it('excludes non-highways', function() {
@@ -64,27 +49,11 @@ describe("iD.geo.Intersection", function() {
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', '*', 'w'], tags: {highway: 'residential'}})
]);
expect(_.pluck(iD.geo.Intersection(graph, '*').highways, 'id')).to.eql(['=-a', '=-b']);
expect(_.pluck(iD.geo.Intersection(graph, '*').highways, 'id')).to.eql(['=.a', '=.b']);
});
});
describe('#turns', function() {
function ids(turns) {
return turns.map(function (turn) {
var result = {
from: turn.from.id,
to: turn.to.id,
via: turn.via.id,
toward: turn.toward.id
};
if (turn.restriction)
result.restriction = turn.restriction.id;
return result;
});
}
it("permits turns onto a way forward", function() {
// u====*--->w
var graph = iD.Graph([
@@ -96,11 +65,10 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{
from: '=',
to: '-',
via: '*',
toward: 'w'
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
@@ -115,7 +83,11 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{from: '=', to: '-', via: '*', toward: 'w'}]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
it("permits turns onto a way in both directions", function() {
@@ -134,10 +106,15 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([
{from: '=', to: '--a', via: '*', toward: 'w'},
{from: '=', to: '--b', via: '*', toward: 'x'}
]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}, {
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'x', way: '-'}
}]);
});
it("permits turns from a oneway forward", function() {
@@ -151,7 +128,11 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{from: '=', to: '-', via: '*', toward: 'w'}]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
it("permits turns from a reverse oneway backward", function() {
@@ -165,7 +146,11 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{from: '=', to: '-', via: '*', toward: 'w'}]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
it("omits turns from a oneway backward", function() {
@@ -203,7 +188,11 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{from: '=', to: '-', via: '*', toward: 'w'}]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
it("permits turns onto a reverse oneway backward", function() {
@@ -217,7 +206,11 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{from: '=', to: '-', via: '*', toward: 'w'}]);
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
it("omits turns onto a oneway backward", function() {
@@ -260,11 +253,10 @@ describe("iD.geo.Intersection", function() {
]),
turns = iD.geo.Intersection(graph, '*').turns('=');
expect(ids(turns)).to.eql([{
from: '=',
to: '-',
via: '*',
toward: 'w',
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'r'
}]);
});
@@ -273,4 +265,5 @@ describe("iD.geo.Intersection", function() {
// 'no' vs 'only'
// U-turns
// Self-intersections
// Incomplete relations
});