From 241159b547f85060ec07dae4bf33f7d08771c091 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 28 Feb 2018 23:55:59 -0500 Subject: [PATCH] Cleanup docs and tests for actionRestrictTurn / actionUnrestrictTurn - actionRestrictTurn will no longer "infer" the turn type - restrictionType *must* be passed in - this is ok because the only code we use this action (restrictions.js) already has inferred the type - this simplifies what the action actually does - moved the tests from restrict_turn.js that were really just testing the restriction type inferrence over to intersection.js (and added a few more tests for iD.osmInferRestriction) --- modules/actions/restrict_turn.js | 30 +-- modules/actions/unrestrict_turn.js | 19 +- modules/ui/fields/restrictions.js | 21 +- test/spec/actions/restrict_turn.js | 332 +++++---------------------- test/spec/actions/unrestrict_turn.js | 4 +- test/spec/osm/intersection.js | 150 ++++++++++++ 6 files changed, 239 insertions(+), 317 deletions(-) diff --git a/modules/actions/restrict_turn.js b/modules/actions/restrict_turn.js index 50490012d..c21ace82c 100644 --- a/modules/actions/restrict_turn.js +++ b/modules/actions/restrict_turn.js @@ -1,33 +1,23 @@ -import { - osmInferRestriction, - osmRelation -} from '../osm'; +import { osmRelation } from '../osm'; -// Create a restriction relation for `turn`, which must have the following structure: +// `actionRestrictTurn` creates a turn restriction relation. // -// { -// from: { node: , way: }, -// via: { node: , ways: [,,...] }, -// to: { node: , way: }, -// restriction: <'no_right_turn', 'no_left_turn', etc.> -// } +// `turn` must be an `osmTurn` object +// see osm/intersection.js, pathToTurn() // // This specifies a restriction of type `restriction` when traveling from -// `from.node` in `from.way` toward `to.node` in `to.way` via `via.node` OR `via.ways`. +// `turn.from.way` toward `turn.to.way` via `turn.via.node` OR `turn.via.ways`. // (The action does not check that these entities form a valid intersection.) // -// If `restriction` is not provided, it is automatically determined by -// osmInferRestriction. -// // From, to, and via ways should be split before calling this action. // (old versions of the code would split the ways here, but we no longer do it) // -// 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. +// For testing convenience, accepts a restrictionID to assign to the new +// relation. Normally, this will be undefined and the relation will +// automatically be assigned a new ID. // -export function actionRestrictTurn(turn, projection, restrictionID) { +export function actionRestrictTurn(turn, restrictionType, restrictionID) { return function(graph) { var fromWay = graph.entity(turn.from.way); @@ -52,7 +42,7 @@ export function actionRestrictTurn(turn, projection, restrictionID) { id: restrictionID, tags: { type: 'restriction', - restriction: turn.restriction || osmInferRestriction(graph, turn, projection) + restriction: restrictionType }, members: members })); diff --git a/modules/actions/unrestrict_turn.js b/modules/actions/unrestrict_turn.js index 4210b3a7b..8d980f459 100644 --- a/modules/actions/unrestrict_turn.js +++ b/modules/actions/unrestrict_turn.js @@ -1,23 +1,10 @@ import { actionDeleteRelation } from './delete_relation'; -// Remove the effects of `turn.restriction` on `turn`, which must have the -// following structure: +// `actionUnrestrictTurn` deletes a turn restriction relation. // -// { -// from: { node: , way: }, -// via: { node: }, -// to: { node: , way: }, -// restrictionID: -// } -// -// In the simple case, `restrictionID` is a reference to a `no_*` restriction -// on the turn itself. In this case, it is simply deleted. -// -// The more complex case is where `restrictionID` 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. +// `turn` must be an `osmTurn` object with a `restrictionID` property. +// see osm/intersection.js, pathToTurn() // export function actionUnrestrictTurn(turn) { return function(graph) { diff --git a/modules/ui/fields/restrictions.js b/modules/ui/fields/restrictions.js index a60bd547a..97b10a03c 100644 --- a/modules/ui/fields/restrictions.js +++ b/modules/ui/fields/restrictions.js @@ -339,15 +339,15 @@ export function uiFieldRestrictions(field, context) { } else if (datum instanceof osmTurn) { var actions, extraActions, turns, i; - datum.restriction = osmInferRestriction(vgraph, datum, projection); + var restrictionType = osmInferRestriction(vgraph, datum, projection); if (datum.restrictionID && !datum.direct) { return; - } else if (datum.restrictionID && !datum.only) { // cycle NO -> ONLY + } else if (datum.restrictionID && !datum.only) { // NO -> ONLY var datumOnly = _cloneDeep(datum); datumOnly.only = true; - datumOnly.restriction = datumOnly.restriction.replace(/^no/, 'only'); + restrictionType = restrictionType.replace(/^no/, 'only'); // Adding an ONLY restriction should destroy all other direct restrictions from the FROM. // We will remember them in _oldTurns, and restore them if the user clicks again. @@ -356,17 +356,18 @@ export function uiFieldRestrictions(field, context) { _oldTurns = []; for (i = 0; i < turns.length; i++) { if (turns[i].direct) { + turns[i].restrictionType = osmInferRestriction(vgraph, turns[i], projection); _oldTurns.push(turns[i]); - extraActions.push(actionUnrestrictTurn(turns[i], projection)); + extraActions.push(actionUnrestrictTurn(turns[i])); } } actions = _intersection.actions.concat(extraActions, [ - actionRestrictTurn(datumOnly, projection), + actionRestrictTurn(datumOnly, restrictionType), t('operations.restriction.annotation.create') ]); - } else if (datum.restrictionID) { // cycle ONLY -> Allowed + } else if (datum.restrictionID) { // ONLY -> Allowed // Restore whatever restrictions we might have destroyed by cycling thru the ONLY state. // This relies on the assumption that the intersection was already split up when we // performed the previous action (NO -> ONLY), so the IDs in _oldTurns shouldn't have changed. @@ -374,19 +375,19 @@ export function uiFieldRestrictions(field, context) { extraActions = []; for (i = 0; i < turns.length; i++) { if (turns[i].key !== datum.key) { - extraActions.push(actionRestrictTurn(turns[i], projection)); + extraActions.push(actionRestrictTurn(turns[i], turns[i].restrictionType)); } } _oldTurns = null; actions = _intersection.actions.concat(extraActions, [ - actionUnrestrictTurn(datum, projection), + actionUnrestrictTurn(datum), t('operations.restriction.annotation.delete') ]); - } else { // cycle Allowed -> NO + } else { // Allowed -> NO actions = _intersection.actions.concat([ - actionRestrictTurn(datum, projection), + actionRestrictTurn(datum, restrictionType), t('operations.restriction.annotation.create') ]); } diff --git a/test/spec/actions/restrict_turn.js b/test/spec/actions/restrict_turn.js index 96de8be59..46f273be7 100644 --- a/test/spec/actions/restrict_turn.js +++ b/test/spec/actions/restrict_turn.js @@ -1,288 +1,80 @@ describe('iD.actionRestrictTurn', function() { - var projection = d3.geoMercator().scale(250 / Math.PI); + it('adds a via node restriction to an unrestricted turn', function() { + // + // u === * --- w + // + var graph = iD.coreGraph([ + iD.osmNode({id: 'u'}), + iD.osmNode({id: '*'}), + iD.osmNode({id: 'w'}), + iD.osmWay({id: '=', nodes: ['u', '*']}), + iD.osmWay({id: '-', nodes: ['*', 'w']}) + ]); - describe('via node', function() { + var turn = { + from: { node: 'u', way: '=' }, + via: { node: '*'}, + to: { node: 'w', way: '-' } + }; - it('adds a via node restriction to an unrestricted turn', function() { - // u====*--->w - var graph = iD.coreGraph([ - iD.osmNode({id: 'u'}), - iD.osmNode({id: '*'}), - iD.osmNode({id: 'w'}), - iD.osmWay({id: '=', nodes: ['u', '*']}), - iD.osmWay({id: '-', nodes: ['*', 'w']}) - ]); - var action = iD.actionRestrictTurn({ - from: {node: 'u', way: '='}, - via: {node: '*'}, - to: {node: 'w', way: '-'}, - restriction: 'no_right_turn' - }, projection, 'r'); + var action = iD.actionRestrictTurn(turn, 'no_straight_on', 'r'); + graph = action(graph); - graph = action(graph); + var r = graph.entity('r'); + expect(r.tags).to.eql({type: 'restriction', restriction: 'no_straight_on'}); - var r = graph.entity('r'); - expect(r.tags).to.eql({type: 'restriction', restriction: 'no_right_turn'}); + var f = r.memberByRole('from'); + expect(f.id).to.eql('='); + expect(f.type).to.eql('way'); - var f = r.memberByRole('from'); - expect(f.id).to.eql('='); - expect(f.type).to.eql('way'); + var v = r.memberByRole('via'); + expect(v.id).to.eql('*'); + expect(v.type).to.eql('node'); - var v = r.memberByRole('via'); - expect(v.id).to.eql('*'); - expect(v.type).to.eql('node'); - - var t = r.memberByRole('to'); - expect(t.id).to.eql('-'); - expect(t.type).to.eql('way'); - }); - -//TODO? - it.skip('infers the restriction type based on the turn angle', function() { - // u====*~~~~w - // | - // x - var graph = iD.coreGraph([ - iD.osmNode({id: 'u', loc: [-1, 0]}), - iD.osmNode({id: '*', loc: [ 0, 0]}), - iD.osmNode({id: 'w', loc: [ 1, 0]}), - iD.osmNode({id: 'x', loc: [ 0, -1]}), - iD.osmWay({id: '=', nodes: ['u', '*']}), - iD.osmWay({id: '-', nodes: ['*', 'x']}), - iD.osmWay({id: '~', nodes: ['*', 'w']}) - ]); - - var r1 = iD.actionRestrictTurn({ - from: {node: 'u', way: '='}, - via: {node: '*'}, - to: {node: 'x', way: '-'} - }, projection, 'r')(graph); - expect(r1.entity('r').tags.restriction).to.equal('no_right_turn'); - - var r2 = iD.actionRestrictTurn({ - from: {node: 'x', way: '-'}, - via: {node: '*'}, - to: {node: 'w', way: '~'} - }, projection, 'r')(graph); - expect(r2.entity('r').tags.restriction).to.equal('no_right_turn'); - - var l1 = iD.actionRestrictTurn({ - from: {node: 'x', way: '-'}, - via: {node: '*'}, - to: {node: 'u', way: '='} - }, projection, 'r')(graph); - expect(l1.entity('r').tags.restriction).to.equal('no_left_turn'); - - var l2 = iD.actionRestrictTurn({ - from: {node: 'w', way: '~'}, - via: {node: '*'}, - to: {node: 'x', way: '-'} - }, projection, 'r')(graph); - expect(l2.entity('r').tags.restriction).to.equal('no_left_turn'); - - var s = iD.actionRestrictTurn({ - 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.actionRestrictTurn({ - from: {node: 'u', way: '='}, - via: {node: '*'}, - to: {node: 'u', way: '='} - }, projection, 'r')(graph); - expect(u.entity('r').tags.restriction).to.equal('no_u_turn'); - }); - -//TODO? - it.skip('infers no_u_turn from acute angle made by forward oneways', function() { - // * - // / \ - // w2/ \w1 - // / \ - // u x - var graph = iD.coreGraph([ - iD.osmNode({id: 'u', loc: [-1, -20]}), - iD.osmNode({id: '*', loc: [ 0, 0]}), - iD.osmNode({id: 'x', loc: [ 1, -20]}), - iD.osmWay({id: 'w1', nodes: ['x', '*'], tags: {oneway: 'yes'}}), - iD.osmWay({id: 'w2', nodes: ['*', 'u'], tags: {oneway: 'yes'}}) - ]); - - var r = iD.actionRestrictTurn({ - from: {node: 'x', way: 'w1'}, - via: {node: '*'}, - to: {node: 'u', way: 'w2'} - }, projection, 'r')(graph); - expect(r.entity('r').tags.restriction).to.equal('no_u_turn'); - }); - -//TODO? - it.skip('infers no_u_turn from acute angle made by reverse oneways', function() { - // * - // / \ - // w2/ \w1 - // / \ - // u x - var graph = iD.coreGraph([ - iD.osmNode({id: 'u', loc: [-1, -20]}), - iD.osmNode({id: '*', loc: [ 0, 0]}), - iD.osmNode({id: 'x', loc: [ 1, -20]}), - iD.osmWay({id: 'w1', nodes: ['*', 'x'], tags: {oneway: '-1'}}), - iD.osmWay({id: 'w2', nodes: ['u', '*'], tags: {oneway: '-1'}}) - ]); - - var r = iD.actionRestrictTurn({ - from: {node: 'x', way: 'w1'}, - via: {node: '*'}, - to: {node: 'u', way: 'w2'} - }, projection, 'r')(graph); - expect(r.entity('r').tags.restriction).to.equal('no_u_turn'); - }); + var t = r.memberByRole('to'); + expect(t.id).to.eql('-'); + expect(t.type).to.eql('way'); }); - describe('via way', function() { + it('adds a via way restriction to an unrestricted turn', function() { + // + // u === v1 + // | + // w --- v2 + // + var graph = iD.coreGraph([ + iD.osmNode({id: 'u'}), + iD.osmNode({id: 'v1'}), + iD.osmNode({id: 'v2'}), + iD.osmNode({id: 'w'}), + iD.osmWay({id: '=', nodes: ['u', 'v1']}), + iD.osmWay({id: '|', nodes: ['v1', 'v2']}), + iD.osmWay({id: '-', nodes: ['v2', 'w']}) + ]); - it('adds a via way restriction to an unrestricted turn', function() { - // u ==== VIA ---> w - var graph = iD.coreGraph([ - iD.osmNode({id: 'u'}), - iD.osmNode({id: 'V1'}), - iD.osmNode({id: 'V2'}), - iD.osmNode({id: 'w'}), - iD.osmWay({id: '=', nodes: ['u', 'V1']}), - iD.osmWay({id: 'VIA', nodes: ['V1', 'V2']}), - iD.osmWay({id: '-', nodes: ['V2', 'w']}) - ]); - var action = iD.actionRestrictTurn({ - from: {node: 'u', way: '='}, - via: {ways: ['VIA']}, - to: {node: 'w', way: '-'}, - restriction: 'no_u_turn' - }, projection, 'r'); + var turn = { + from: { node: 'u', way: '=' }, + via: { ways: ['|'] }, + to: { node: 'w', way: '-' } + }; - graph = action(graph); + var action = iD.actionRestrictTurn(turn, 'no_u_turn', 'r'); + graph = action(graph); - var r = graph.entity('r'); - expect(r.tags).to.eql({type: 'restriction', restriction: 'no_u_turn'}); + var r = graph.entity('r'); + expect(r.tags).to.eql({type: 'restriction', restriction: 'no_u_turn'}); - var f = r.memberByRole('from'); - expect(f.id).to.eql('='); - expect(f.type).to.eql('way'); + var f = r.memberByRole('from'); + expect(f.id).to.eql('='); + expect(f.type).to.eql('way'); - var v = r.memberByRole('via'); - expect(v.id).to.eql('VIA'); - expect(v.type).to.eql('way'); + var v = r.memberByRole('via'); + expect(v.id).to.eql('|'); + expect(v.type).to.eql('way'); - var t = r.memberByRole('to'); - expect(t.id).to.eql('-'); - expect(t.type).to.eql('way'); - }); - - -// TODO? - - // it('infers the restriction type based on the turn angle', function() { - // // u====*~~~~w - // // | - // // x - // var graph = iD.coreGraph([ - // iD.osmNode({id: 'u', loc: [-1, 0]}), - // iD.osmNode({id: '*', loc: [ 0, 0]}), - // iD.osmNode({id: 'w', loc: [ 1, 0]}), - // iD.osmNode({id: 'x', loc: [ 0, -1]}), - // iD.osmWay({id: '=', nodes: ['u', '*']}), - // iD.osmWay({id: '-', nodes: ['*', 'x']}), - // iD.osmWay({id: '~', nodes: ['*', 'w']}) - // ]); - - // var r1 = iD.actionRestrictTurn({ - // from: {node: 'u', way: '='}, - // via: {node: '*'}, - // to: {node: 'x', way: '-'} - // }, projection, 'r')(graph); - // expect(r1.entity('r').tags.restriction).to.equal('no_right_turn'); - - // var r2 = iD.actionRestrictTurn({ - // from: {node: 'x', way: '-'}, - // via: {node: '*'}, - // to: {node: 'w', way: '~'} - // }, projection, 'r')(graph); - // expect(r2.entity('r').tags.restriction).to.equal('no_right_turn'); - - // var l1 = iD.actionRestrictTurn({ - // from: {node: 'x', way: '-'}, - // via: {node: '*'}, - // to: {node: 'u', way: '='} - // }, projection, 'r')(graph); - // expect(l1.entity('r').tags.restriction).to.equal('no_left_turn'); - - // var l2 = iD.actionRestrictTurn({ - // from: {node: 'w', way: '~'}, - // via: {node: '*'}, - // to: {node: 'x', way: '-'} - // }, projection, 'r')(graph); - // expect(l2.entity('r').tags.restriction).to.equal('no_left_turn'); - - // var s = iD.actionRestrictTurn({ - // 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.actionRestrictTurn({ - // from: {node: 'u', way: '='}, - // via: {node: '*'}, - // to: {node: 'u', way: '='} - // }, projection, 'r')(graph); - // expect(u.entity('r').tags.restriction).to.equal('no_u_turn'); - // }); - - // it('infers no_u_turn from acute angle made by forward oneways', function() { - // // * - // // / \ - // // w2/ \w1 - // // / \ - // // u x - // var graph = iD.coreGraph([ - // iD.osmNode({id: 'u', loc: [-1, -20]}), - // iD.osmNode({id: '*', loc: [ 0, 0]}), - // iD.osmNode({id: 'x', loc: [ 1, -20]}), - // iD.osmWay({id: 'w1', nodes: ['x', '*'], tags: {oneway: 'yes'}}), - // iD.osmWay({id: 'w2', nodes: ['*', 'u'], tags: {oneway: 'yes'}}) - // ]); - - // var r = iD.actionRestrictTurn({ - // from: {node: 'x', way: 'w1'}, - // via: {node: '*'}, - // to: {node: 'u', way: 'w2'} - // }, projection, 'r')(graph); - // expect(r.entity('r').tags.restriction).to.equal('no_u_turn'); - // }); - - // it('infers no_u_turn from acute angle made by reverse oneways', function() { - // // * - // // / \ - // // w2/ \w1 - // // / \ - // // u x - // var graph = iD.coreGraph([ - // iD.osmNode({id: 'u', loc: [-1, -20]}), - // iD.osmNode({id: '*', loc: [ 0, 0]}), - // iD.osmNode({id: 'x', loc: [ 1, -20]}), - // iD.osmWay({id: 'w1', nodes: ['*', 'x'], tags: {oneway: '-1'}}), - // iD.osmWay({id: 'w2', nodes: ['u', '*'], tags: {oneway: '-1'}}) - // ]); - - // var r = iD.actionRestrictTurn({ - // from: {node: 'x', way: 'w1'}, - // via: {node: '*'}, - // to: {node: 'u', way: 'w2'} - // }, projection, 'r')(graph); - // expect(r.entity('r').tags.restriction).to.equal('no_u_turn'); - // }); + var t = r.memberByRole('to'); + expect(t.id).to.eql('-'); + expect(t.type).to.eql('way'); }); }); diff --git a/test/spec/actions/unrestrict_turn.js b/test/spec/actions/unrestrict_turn.js index 9ab094c6e..3c617618d 100644 --- a/test/spec/actions/unrestrict_turn.js +++ b/test/spec/actions/unrestrict_turn.js @@ -1,6 +1,8 @@ describe('iD.actionUnrestrictTurn', function() { it('removes a restriction from a restricted turn', function() { - // u====*--->w + // + // u === * --- w + // var graph = iD.coreGraph([ iD.osmNode({ id: 'u' }), iD.osmNode({ id: '*' }), diff --git a/test/spec/osm/intersection.js b/test/spec/osm/intersection.js index 880b281a5..ba4785a49 100644 --- a/test/spec/osm/intersection.js +++ b/test/spec/osm/intersection.js @@ -557,3 +557,153 @@ describe('iD.osmIntersection', function() { }); }); + + +describe('iD.osmInferRestriction', function() { + var projection = d3.geoMercator().scale(250 / Math.PI); + + it('infers the restriction type based on the turn angle', function() { + // + // u === * ~~~ w + // | + // x + // + var graph = iD.coreGraph([ + iD.osmNode({id: 'u', loc: [-1, 0]}), + iD.osmNode({id: '*', loc: [ 0, 0]}), + iD.osmNode({id: 'w', loc: [ 1, 0]}), + iD.osmNode({id: 'x', loc: [ 0, -1]}), + iD.osmWay({id: '=', nodes: ['u', '*']}), + iD.osmWay({id: '-', nodes: ['*', 'x']}), + iD.osmWay({id: '~', nodes: ['*', 'w']}) + ]); + + var r1 = iD.osmInferRestriction(graph, { + from: { node: 'u', way: '=', vertex: '*' }, + to: { node: 'x', way: '-', vertex: '*' } + }, projection); + expect(r1).to.equal('no_right_turn'); + + var r2 = iD.osmInferRestriction(graph, { + from: { node: 'x', way: '-', vertex: '*' }, + to: { node: 'w', way: '~', vertex: '*' } + }, projection); + expect(r2).to.equal('no_right_turn'); + + var l1 = iD.osmInferRestriction(graph, { + from: { node: 'x', way: '-', vertex: '*' }, + to: { node: 'u', way: '=', vertex: '*' } + }, projection); + expect(l1).to.equal('no_left_turn'); + + var l2 = iD.osmInferRestriction(graph, { + from: { node: 'w', way: '~', vertex: '*' }, + to: { node: 'x', way: '-', vertex: '*' } + }, projection); + expect(l2).to.equal('no_left_turn'); + + var s = iD.osmInferRestriction(graph, { + from: { node: 'u', way: '=', vertex: '*' }, + to: { node: 'w', way: '~', vertex: '*' } + }, projection); + expect(s).to.equal('no_straight_on'); + + var u = iD.osmInferRestriction(graph, { + from: { node: 'u', way: '=', vertex: '*' }, + to: { node: 'u', way: '=', vertex: '*' } + }, projection); + expect(u).to.equal('no_u_turn'); + }); + + + it('infers no_u_turn from sharply acute angle made by forward oneways', function() { + // * + // / \ + // w2/ \w1 angle ≈22.6° + // / \ + // u x + var graph = iD.coreGraph([ + iD.osmNode({ id: 'u', loc: [0, -5] }), + iD.osmNode({ id: '*', loc: [1, 0] }), + iD.osmNode({ id: 'x', loc: [2, -5] }), + iD.osmWay({ id: 'w1', nodes: ['x', '*'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: 'w2', nodes: ['*', 'u'], tags: { oneway: 'yes' } }) + ]); + + var r = iD.osmInferRestriction(graph, { + from: { node: 'x', way: 'w1', vertex: '*' }, + to: { node: 'u', way: 'w2', vertex: '*' } + }, projection); + expect(r).to.equal('no_u_turn'); + }); + + + it('does not infer no_u_turn from widely acute angle made by forward oneways', function() { + // * + // / \ + // w2/ \w1 angle ≈36.9° + // / \ + // u x + var graph = iD.coreGraph([ + iD.osmNode({ id: 'u', loc: [0, -3] }), + iD.osmNode({ id: '*', loc: [1, 0] }), + iD.osmNode({ id: 'x', loc: [2, -3] }), + iD.osmWay({ id: 'w1', nodes: ['x', '*'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: 'w2', nodes: ['*', 'u'], tags: { oneway: 'yes' } }) + ]); + + var r = iD.osmInferRestriction(graph, { + from: { node: 'x', way: 'w1', vertex: '*' }, + to: { node: 'u', way: 'w2', vertex: '*' } + }, projection); + expect(r).to.equal('no_left_turn'); + }); + + + it('infers no_u_turn from sharply acute angle made by forward oneways with a via way', function() { + // * -- + + // / \ + // w2/ \w1 angle ≈22.6° + // / \ + // u x + var graph = iD.coreGraph([ + iD.osmNode({ id: 'u', loc: [0, -5] }), + iD.osmNode({ id: '*', loc: [1, 0] }), + iD.osmNode({ id: '+', loc: [2, 0] }), + iD.osmNode({ id: 'x', loc: [3, -5] }), + iD.osmWay({ id: 'w1', nodes: ['x', '+'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: 'w2', nodes: ['*', 'u'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: '-', nodes: ['*', '+'] }) + ]); + + var r = iD.osmInferRestriction(graph, { + from: { node: 'x', way: 'w1', vertex: '+' }, + to: { node: 'u', way: 'w2', vertex: '*' } + }, projection); + expect(r).to.equal('no_u_turn'); + }); + + + it('infers no_u_turn from widely acute angle made by forward oneways with a via way', function() { + // * -- + + // / \ + // w2/ \w1 angle ≈36.9° + // / \ + // u x + var graph = iD.coreGraph([ + iD.osmNode({ id: 'u', loc: [0, -3] }), + iD.osmNode({ id: '*', loc: [1, 0] }), + iD.osmNode({ id: '+', loc: [2, 0] }), + iD.osmNode({ id: 'x', loc: [3, -3] }), + iD.osmWay({ id: 'w1', nodes: ['x', '+'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: 'w2', nodes: ['*', 'u'], tags: { oneway: 'yes' } }), + iD.osmWay({ id: '-', nodes: ['*', '+'] }) + ]); + + var r = iD.osmInferRestriction(graph, { + from: { node: 'x', way: 'w1', vertex: '+' }, + to: { node: 'u', way: 'w2', vertex: '*' } + }, projection); + expect(r).to.equal('no_u_turn'); + }); +});