Merge branch 'turn-restrictions'

This commit is contained in:
John Firebaugh
2014-05-21 14:22:22 -07:00
58 changed files with 2252 additions and 792 deletions
+13 -9
View File
@@ -45,8 +45,9 @@
<script src="../js/id/geo.js"></script>
<script src="../js/id/geo/extent.js"></script>
<script src="../js/id/geo/intersection.js"></script>
<script src="../js/id/geo/multipolygon.js"></script>
<script src="../js/id/geo/turn.js"></script>
<script src="../js/id/geo/raw_mercator.js"></script>
<script src='../js/id/renderer/background.js'></script>
<script src='../js/id/renderer/background_source.js'></script>
@@ -56,14 +57,14 @@
<script src="../js/id/svg.js"></script>
<script src="../js/id/svg/areas.js"></script>
<script src="../js/id/svg/labels.js"></script>
<script src="../js/id/svg/lines.js"></script>
<script src="../js/id/svg/midpoints.js"></script>
<script src="../js/id/svg/points.js"></script>
<script src="../js/id/svg/surface.js"></script>
<script src="../js/id/svg/tag_classes.js"></script>
<script src="../js/id/svg/turns.js"></script>
<script src="../js/id/svg/vertices.js"></script>
<script src="../js/id/svg/labels.js"></script>
<script src="../js/id/svg/restrictions.js"></script>
<script src="../js/id/ui.js"></script>
<script src='../js/id/ui/attribution.js'></script>
@@ -117,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>
@@ -130,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>
@@ -229,12 +232,14 @@
<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>
<script src="spec/geo/multipolygon.js"></script>
<script src="spec/geo/turn.js"></script>
<script src="spec/core/connection.js"></script>
<script src="spec/core/graph.js"></script>
@@ -257,7 +262,6 @@
<script src="spec/svg/points.js"></script>
<script src="spec/svg/vertices.js"></script>
<script src="spec/svg/tag_classes.js"></script>
<script src="spec/svg/restrictions.js"></script>
<script src="spec/ui/inspector.js"></script>
<script src="spec/ui/raw_tag_editor.js"></script>
+2 -2
View File
@@ -47,10 +47,11 @@
<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>
<script src="spec/geo/multipolygon.js"></script>
<script src="spec/geo/turn.js"></script>
<script src="spec/core/connection.js"></script>
<script src="spec/core/graph.js"></script>
@@ -73,7 +74,6 @@
<script src="spec/svg/points.js"></script>
<script src="spec/svg/vertices.js"></script>
<script src="spec/svg/tag_classes.js"></script>
<script src="spec/svg/restrictions.js"></script>
<script src="spec/ui/inspector.js"></script>
<script src="spec/ui/raw_tag_editor.js"></script>
+295
View File
@@ -0,0 +1,295 @@
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 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.actions.RestrictTurn({
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(_.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 (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.actions.RestrictTurn({
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(_.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;
});
});
+376
View File
@@ -0,0 +1,376 @@
describe("iD.geo.Intersection", 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']})
]);
expect(iD.geo.Intersection(graph, '*').highways).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'}})
]);
expect(_.pluck(iD.geo.Intersection(graph, '*').highways, 'id')).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']})
]);
expect(_.pluck(iD.geo.Intersection(graph, '*').highways, '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'}})
]);
expect(iD.geo.Intersection(graph, '*').highways).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'}})
]);
expect(_.pluck(iD.geo.Intersection(graph, '*').highways, 'id')).to.eql(['=-a', '=-b']);
});
});
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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
});
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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
});
it("permits turns from a way that must be split", function() {
// w
// |
// 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.geo.Intersection(graph, '*').turns('w');
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
});
});
it("permits turns to a way that must be split", function() {
// w
// |
// 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.geo.Intersection(graph, '*').turns('u');
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
});
});
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.geo.Intersection(graph, '*').turns('u');
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
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.geo.Intersection(graph, '*').turns('u');
expect(turns).to.eql([{
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
}]);
});
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'}})
]);
expect(iD.geo.Intersection(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'}})
]);
expect(iD.geo.Intersection(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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
});
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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'}
});
});
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'}})
]);
expect(iD.geo.Intersection(graph, '*').turns('u').length).to.eql(1);
});
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'}})
]);
expect(iD.geo.Intersection(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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[1]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'u', way: '='},
u: 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.geo.Intersection(graph, '*').turns('u');
expect(turns.length).to.eql(2);
expect(turns[0]).to.eql({
from: {node: 'u', way: '='},
via: {node: '*'},
to: {node: 'w', way: '-'},
restriction: 'r'
});
});
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.geo.Intersection(graph, '*').turns('u');
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
});
});
});
});
-276
View File
@@ -1,276 +0,0 @@
describe("iD.geo.turns", function() {
it("returns an empty array for non-ways", function() {
var graph = iD.Graph([
iD.Node({id: 'n'})
]);
expect(iD.geo.turns(graph, 'n')).to.eql([]);
});
it("returns an empty array for non-lines", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential', area: 'yes'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("returns an empty array for an unconnected way", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Way({id: '=', nodes: ['u', 'v']})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns onto degenerate ways", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns from non-highways", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v']}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns onto non-highways", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w']})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns onto non-lines", function() {
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w', 'x', 'v'], tags: {highway: 'residential', area: 'yes'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("permits turns onto a way forward", function() {
// u====v--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("permits turns onto a way backward", function() {
// u====v<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', 'v'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("permits turns onto a way in both directions", function() {
// w
// |
// u===v
// |
// x
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Node({id: 'x'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', 'v', 'x'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}, {
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('x')
}]);
});
it("permits turns from a oneway forward", function() {
// u===>v----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("permits turns from a reverse oneway backward", function() {
// u<===v----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['v', 'u'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("omits turns from a oneway backward", function() {
// u<===v----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['v', 'u'], tags: {highway: 'residential', oneway: 'yes'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns from a reverse oneway forward", function() {
// u===>v----w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential', oneway: '-1'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("permits turns onto a oneway forward", function() {
// u====v--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential', oneway: 'yes'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("permits turns onto a reverse oneway backward", function() {
// u====v<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', 'v'], tags: {highway: 'residential', oneway: '-1'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w')
}]);
});
it("omits turns onto a oneway backward", function() {
// u====v<---w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['w', 'v'], tags: {highway: 'residential', oneway: 'yes'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("omits turns onto a reverse oneway forward", function() {
// u====v--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential', oneway: '-1'}})
]);
expect(iD.geo.turns(graph, '=')).to.eql([]);
});
it("restricts turns with a restriction relation", function() {
// u====v--->w
var graph = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{id: '=', role: 'from', type: 'way'},
{id: '-', role: 'to', type: 'way'},
{id: 'v', role: 'via', type: 'node'}
]})
]);
expect(iD.geo.turns(graph, '=')).to.eql([{
from: graph.entity('='),
to: graph.entity('-'),
via: graph.entity('v'),
toward: graph.entity('w'),
restriction: graph.entity('r')
}]);
});
// U-turns
// Self-intersections
// Split point
});
-151
View File
@@ -1,151 +0,0 @@
describe("iD.svg.Restrictions", function() {
var restrictions = iD.svg.Restrictions({});
describe("#turns", function() {
it("returns an empty array with no selection", function() {
var graph = iD.Graph();
expect(restrictions.turns(graph, [])).to.eql([]);
});
it("returns an empty array with a multiselection", function() {
var graph = iD.Graph();
expect(restrictions.turns(graph, ['a', 'b'])).to.eql([]);
});
var valid = iD.Graph([
iD.Node({id: 'u'}),
iD.Node({id: 'v'}),
iD.Node({id: 'w'}),
iD.Way({id: 'f', nodes: ['u', 'v']}),
iD.Way({id: 't', nodes: ['v', 'w']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{ role: 'via', id: 'v', type: 'node' },
{ role: 'from', id: 'f', type: 'way' },
{ role: 'to', id: 't', type: 'way' }
]})
]);
it("returns a valid restriction when the selected way has role 'from'", function() {
expect(restrictions.turns(valid, ['f'])).to.eql([valid.entity('r')]);
});
it("returns an empty array when the selected way has role 'to'", function() {
expect(restrictions.turns(valid, ['t'])).to.eql([]);
});
it("ignores restrictions missing a 'to' role", function() {
var graph = valid.replace(valid.entity('r').removeMembersWithID('t'));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions with an incomplete 'to' role", function() {
var graph = valid.remove(valid.entity('t'));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions missing a 'via' role", function() {
var graph = valid.replace(valid.entity('r').removeMembersWithID('v'));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions with an incomplete 'via' role", function() {
var graph = valid.remove(valid.entity('v'));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'from' role is not a way", function() {
var graph = valid.replace(iD.Node({id: 'f2'}))
.replace(valid.entity('r').replaceMember({id: 'f'}, {id: 'f2', type: 'node'}));
expect(restrictions.turns(graph, ['f2'])).to.eql([]);
});
it("ignores restrictions whose 'to' role is not a way", function() {
var graph = valid.replace(iD.Node({id: 't2'}))
.replace(valid.entity('r').replaceMember({id: 't'}, {id: 't2', type: 'node'}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'via' role is not a node", function() {
var graph = valid.replace(iD.Way({id: 'v2'}))
.replace(valid.entity('r').replaceMember({id: 'v'}, {id: 'v2', type: 'way'}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'from' role does not start or end with the via node", function() {
var graph = valid.replace(valid.entity('f').update({nodes: ['o']}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'to' role does not start or end with the via node", function() {
var graph = valid.replace(valid.entity('t').update({nodes: ['o']}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'from' role has less than two nodes", function() {
var graph = valid.replace(valid.entity('f').update({nodes: ['v']}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restrictions whose 'to' role has less than two nodes", function() {
var graph = valid.replace(valid.entity('t').update({nodes: ['v']}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
it("ignores restriction subtypes", function() {
var graph = valid.replace(valid.entity('r').update({tags: {type: 'restriction:hgv'}}));
expect(restrictions.turns(graph, ['f'])).to.eql([]);
});
});
describe("#datum", function() {
function projection(x) { return x; }
it("calculates the angle of a forward 'to' role", function() {
// w---x--->y
// |
// u====>v
// From = to - via v
var graph = iD.Graph([
iD.Node({id: 'u', loc: [0, 0]}),
iD.Node({id: 'v', loc: [1, 0]}),
iD.Node({id: 'w', loc: [1, 1]}),
iD.Node({id: 'x', loc: [2, 1]}),
iD.Node({id: 'y', loc: [3, 1]}),
iD.Way({id: '=', nodes: ['u', 'v']}),
iD.Way({id: '-', nodes: ['v', 'w', 'x', 'y']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{ role: 'via', id: 'v', type: 'node' },
{ role: 'from', id: '=', type: 'way' },
{ role: 'to', id: '-', type: 'way' }
]})
]);
expect(restrictions.datum(graph, graph.entity('='), graph.entity('r'), projection).angle).to.eql(Math.PI / 2);
});
it("calculates the angle of a reverse 'to' role", function() {
// w<---x---y
// |
// u====>v
// From = to - via v
var graph = iD.Graph([
iD.Node({id: 'u', loc: [0, 0]}),
iD.Node({id: 'v', loc: [1, 0]}),
iD.Node({id: 'w', loc: [1, 1]}),
iD.Node({id: 'x', loc: [2, 1]}),
iD.Node({id: 'y', loc: [3, 1]}),
iD.Way({id: '=', nodes: ['u', 'v']}),
iD.Way({id: '-', nodes: ['y', 'x', 'w', 'v']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{ role: 'via', id: 'v', type: 'node' },
{ role: 'from', id: '=', type: 'way' },
{ role: 'to', id: '-', type: 'way' }
]})
]);
expect(restrictions.datum(graph, graph.entity('='), graph.entity('r'), projection).angle).to.eql(Math.PI / 2);
});
});
});