Add more turn logic

This commit is contained in:
John Firebaugh
2013-08-30 19:27:21 -07:00
parent 405a49506b
commit 156aa90e27
5 changed files with 342 additions and 0 deletions

View File

@@ -43,6 +43,7 @@
<script src="js/id/geo.js"></script>
<script src="js/id/geo/extent.js"></script>
<script src="js/id/geo/multipolygon.js"></script>
<script src="js/id/geo/turn.js"></script>
<script src='js/id/renderer/background.js'></script>
<script src='js/id/renderer/background_source.js'></script>

62
js/id/geo/turn.js Normal file
View File

@@ -0,0 +1,62 @@
iD.geo.turns = function(graph, entityID) {
var way = graph.entity(entityID);
if (way.type !== 'way' || !way.tags.highway || way.isArea())
return [];
function withRestriction(turn) {
graph.parentRelations(turn.from).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.id &&
t && t.id === turn.to.id &&
v && v.id === turn.via.id) {
turn.restriction = relation;
}
});
return turn;
}
var turns = [];
[way.first(), way.last()].forEach(function(nodeID) {
var node = graph.entity(nodeID);
graph.parentWays(node).forEach(function(parent) {
if (parent === way || parent.isDegenerate() || !parent.tags.highway)
return;
if (way.first() === node.id && way.tags.oneway === 'yes')
return;
if (way.last() === node.id && way.tags.oneway === '-1')
return;
var index = parent.nodes.indexOf(node.id);
// backward
if (parent.first() !== node.id && parent.tags.oneway !== 'yes') {
turns.push(withRestriction({
from: way,
to: parent,
via: node,
toward: graph.entity(parent.nodes[index - 1])
}));
}
// forward
if (parent.last() !== node.id && parent.tags.oneway !== '-1') {
turns.push(withRestriction({
from: way,
to: parent,
via: node,
toward: graph.entity(parent.nodes[index + 1])
}));
}
});
});
return turns;
};

View File

@@ -44,6 +44,7 @@
<script src="../js/id/geo.js"></script>
<script src="../js/id/geo/extent.js"></script>
<script src="../js/id/geo/multipolygon.js"></script>
<script src="../js/id/geo/turn.js"></script>
<script src='../js/id/renderer/background.js'></script>
<script src='../js/id/renderer/background_source.js'></script>
@@ -221,6 +222,7 @@
<script src="spec/geo/extent.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>

View File

@@ -50,6 +50,7 @@
<script src="spec/geo/extent.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>

276
test/spec/geo/turn.js Normal file
View File

@@ -0,0 +1,276 @@
describe("iD.geo.turns", function() {
it("returns an empty array for non-ways", function() {
var graph = iD.Graph({
'n': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': 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({
'u': iD.Node({id: 'u'}),
'v': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': iD.Node({id: 'w'}),
'x': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': iD.Node({id: 'w'}),
'x': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': 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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': iD.Node({id: 'w'}),
'=': iD.Way({id: '=', nodes: ['u', 'v'], tags: {highway: 'residential'}}),
'-': iD.Way({id: '-', nodes: ['v', 'w'], tags: {highway: 'residential'}}),
'r': 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
});