mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-25 17:37:49 +02:00
Implement basic restriction toggling
This commit is contained in:
@@ -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.
|
||||
|
||||
Vendored
+6
@@ -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
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user