Building logic for turn restrictions

This commit is contained in:
John Firebaugh
2013-08-30 14:23:05 -07:00
parent a32ce33238
commit 405a49506b
5 changed files with 230 additions and 0 deletions
+1
View File
@@ -59,6 +59,7 @@
<script src="js/id/svg/tag_classes.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/intro.js'></script>
+75
View File
@@ -0,0 +1,75 @@
iD.svg.Restrictions = function(context) {
var projection = context.projection;
function drawRestrictions(surface) {
var turns = drawRestrictions.turns(context.graph(), context.selectedIDs());
var groups = surface.select('.layer-hit').selectAll('g.restriction')
.data(turns, iD.Entity.key);
var enter = groups.enter().append('g')
.attr('class', 'restriction');
enter.append('circle')
.attr('class', 'restriction')
.attr('r', 4);
groups
.attr('transform', function(restriction) {
var via = context.entity(restriction.memberByRole('via').id);
return iD.svg.PointTransform(projection)(via);
});
groups.exit()
.remove();
return this;
}
drawRestrictions.turns = function (graph, selectedIDs) {
if (selectedIDs.length != 1)
return [];
var from = graph.entity(selectedIDs[0]);
if (from.type !== 'way')
return [];
return graph.parentRelations(from).filter(function(relation) {
var f = relation.memberById(from.id),
t = relation.memberByRole('to'),
v = relation.memberByRole('via');
return relation.tags.type === 'restriction' && f.role === 'from' &&
t && t.type === 'way' && graph.hasEntity(t.id) &&
v && v.type === 'node' && graph.hasEntity(v.id) &&
!graph.entity(t.id).isDegenerate() &&
!graph.entity(f.id).isDegenerate() &&
graph.entity(t.id).affix(v.id) &&
graph.entity(f.id).affix(v.id);
});
};
drawRestrictions.datum = function(graph, from, restriction, projection) {
var to = graph.entity(restriction.memberByRole('to').id),
a = graph.entity(restriction.memberByRole('via').id),
b;
if (to.first() === a.id) {
b = graph.entity(to.nodes[1]);
} else {
b = graph.entity(to.nodes[to.nodes.length - 2]);
}
a = projection(a.loc);
b = projection(b.loc);
return {
from: from,
to: to,
restriction: restriction,
angle: Math.atan2(b[1] - a[1], b[0] - a[0])
}
};
return drawRestrictions;
};
+2
View File
@@ -60,6 +60,7 @@
<script src="../js/id/svg/tag_classes.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>
@@ -242,6 +243,7 @@
<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>
+1
View File
@@ -72,6 +72,7 @@
<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>
+151
View File
@@ -0,0 +1,151 @@
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({
'u': iD.Node({id: 'u'}),
'v': iD.Node({id: 'v'}),
'w': iD.Node({id: 'w'}),
'f': iD.Way({id: 'f', nodes: ['u', 'v']}),
't': iD.Way({id: 't', nodes: ['v', 'w']}),
'r': 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({
'u': iD.Node({id: 'u', loc: [0, 0]}),
'v': iD.Node({id: 'v', loc: [1, 0]}),
'w': iD.Node({id: 'w', loc: [1, 1]}),
'x': iD.Node({id: 'w', loc: [2, 1]}),
'y': iD.Node({id: 'w', loc: [3, 1]}),
'=': iD.Way({id: '=', nodes: ['u', 'v']}),
'-': iD.Way({id: '-', nodes: ['v', 'w', 'x', 'y']}),
'r': 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({
'u': iD.Node({id: 'u', loc: [0, 0]}),
'v': iD.Node({id: 'v', loc: [1, 0]}),
'w': iD.Node({id: 'w', loc: [1, 1]}),
'x': iD.Node({id: 'w', loc: [2, 1]}),
'y': iD.Node({id: 'w', loc: [3, 1]}),
'=': iD.Way({id: '=', nodes: ['u', 'v']}),
'-': iD.Way({id: '-', nodes: ['y', 'x', 'w', 'v']}),
'r': 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);
});
});
});