diff --git a/data/core.yaml b/data/core.yaml index 2c4edca74..048a433dc 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -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. diff --git a/dist/locales/en.json b/dist/locales/en.json index 2c5a525fe..cd29e7272 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -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": { diff --git a/index.html b/index.html index aa156ee5e..48cd10b82 100644 --- a/index.html +++ b/index.html @@ -138,6 +138,7 @@ + @@ -151,13 +152,14 @@ - - - - + + + + + diff --git a/js/id/actions/restrict_turn.js b/js/id/actions/restrict_turn.js new file mode 100644 index 000000000..e6786e6e6 --- /dev/null +++ b/js/id/actions/restrict_turn.js @@ -0,0 +1,88 @@ +// Create a restriction relation for `turn`, which must have the following structure: +// +// { +// from: { node: , way: }, +// via: { node: }, +// to: { node: , way: }, +// 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'; + } + }; +}; diff --git a/js/id/actions/unrestrict_turn.js b/js/id/actions/unrestrict_turn.js new file mode 100644 index 000000000..f57186d7a --- /dev/null +++ b/js/id/actions/unrestrict_turn.js @@ -0,0 +1,23 @@ +// Remove the effects of `turn.restriction` on `turn`, which must have the +// following structure: +// +// { +// from: { node: , way: }, +// via: { node: }, +// to: { node: , way: }, +// restriction: +// } +// +// 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); + }; +}; diff --git a/js/id/geo.js b/js/id/geo.js index 2fa2e2ace..f667ca100 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -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 diff --git a/js/id/geo/intersection.js b/js/id/geo/intersection.js index c53e6db2a..c76405e08 100644 --- a/js/id/geo/intersection.js +++ b/js/id/geo/intersection.js @@ -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]} })); } }); diff --git a/js/id/svg/turns.js b/js/id/svg/turns.js index dce3ca924..f20f20a3d 100644 --- a/js/id/svg/turns.js +++ b/js/id/svg/turns.js @@ -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(); diff --git a/js/id/ui/preset/restrictions.js b/js/id/ui/preset/restrictions.js index c604d04bd..61baebc88 100644 --- a/js/id/ui/preset/restrictions.js +++ b/js/id/ui/preset/restrictions.js @@ -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')); + } } }); diff --git a/test/index.html b/test/index.html index 5b6469948..dec049083 100644 --- a/test/index.html +++ b/test/index.html @@ -118,6 +118,7 @@ + @@ -131,13 +132,14 @@ - - - - + + + + + @@ -230,8 +232,10 @@ + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 0f83951a5..b22e812ee 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -47,6 +47,7 @@ + diff --git a/test/spec/actions/restrict_turn.js b/test/spec/actions/restrict_turn.js new file mode 100644 index 000000000..302f14cad --- /dev/null +++ b/test/spec/actions/restrict_turn.js @@ -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'); + }); +}); diff --git a/test/spec/actions/unrestrict_turn.js b/test/spec/actions/unrestrict_turn.js new file mode 100644 index 000000000..f7c5a6830 --- /dev/null +++ b/test/spec/actions/unrestrict_turn.js @@ -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; + }); +}); diff --git a/test/spec/geo/intersection.js b/test/spec/geo/intersection.js index c77b50c78..68def534d 100644 --- a/test/spec/geo/intersection.js +++ b/test/spec/geo/intersection.js @@ -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 });