| | 1 | | using System; |
| | 2 | | using Itinero.Geo; |
| | 3 | | using Itinero.Network.Attributes; |
| | 4 | |
|
| | 5 | | namespace Itinero.Instructions.Types.Generators; |
| | 6 | |
|
| | 7 | | internal class FollowBendGenerator : IInstructionGenerator |
| | 8 | | { |
| 10 | 9 | | public string Name { get; } = "followbend"; |
| | 10 | |
|
| | 11 | | private static bool DoesFollowBend(IndexedRoute route, int shapeI, double dAngle, int angleSign) |
| 22 | 12 | | { |
| | 13 | | // We aren't allowed to have branches on the inner side, to avoid confusing situations |
| | 14 | |
|
| 22 | 15 | | if (shapeI >= route.Branches.Count) |
| 0 | 16 | | { |
| 0 | 17 | | return true; |
| | 18 | | } |
| | 19 | |
|
| 69 | 20 | | foreach (var branch in route.Branches[shapeI]) |
| 2 | 21 | | { |
| | 22 | | // What is the angle-difference of the branch? |
| | 23 | | // This resembles the route.DirectionChangeAt-definition |
| 2 | 24 | | var selfAngle = route.ArrivingDirectionAt(shapeI); |
| 2 | 25 | | var branchAngle = route.Shape[shapeI].AngleWithMeridian(branch.Coordinate); |
| 2 | 26 | | var dBranchAngle = (selfAngle - branchAngle).NormalizeDegrees(); |
| | 27 | |
|
| | 28 | | // With the angle in hand, we can ask ourselves: lies it on the inner side? |
| 2 | 29 | | if (Math.Sign(dBranchAngle) != angleSign) |
| 1 | 30 | | { |
| | 31 | | // It lies on the other side; this branch doesn't pose a problem |
| 1 | 32 | | continue; |
| | 33 | | } |
| | 34 | |
|
| | 35 | | // We know the signs are the same; so we pretend both are going left (aka positive) |
| 1 | 36 | | var dAngleAbs = Math.Abs(dAngle); |
| 1 | 37 | | var dBranchAbs = Math.Abs(dBranchAngle); |
| | 38 | |
|
| | 39 | | // If the turning angle of the route is bigger, then the branch lies on the outer side |
| 1 | 40 | | if (dBranchAbs < dAngleAbs) |
| 0 | 41 | | { |
| 0 | 42 | | continue; |
| | 43 | | } |
| | 44 | |
|
| | 45 | | // At this point, we know the branch lies on _the inner side_ |
| | 46 | | // We cannot issue a simple follow bend |
| 1 | 47 | | return false; |
| | 48 | | } |
| | 49 | |
|
| 21 | 50 | | return true; |
| 22 | 51 | | } |
| | 52 | |
|
| | 53 | |
|
| | 54 | | public BaseInstruction? Generate(IndexedRoute route, int offset) |
| 7 | 55 | | { |
| 7 | 56 | | if (offset == 0 || offset == route.Last) |
| 0 | 57 | | { |
| | 58 | | // We never have a bend at first or as last... |
| 0 | 59 | | return null; |
| | 60 | | } |
| | 61 | | // Okay folks! |
| | 62 | | // We will be walking forward - as long as we are turning in one direction, it is fine! |
| | 63 | |
|
| | 64 | |
|
| 7 | 65 | | var angleDiff = route.DirectionChangeAt(offset); |
| 7 | 66 | | var angleSign = Math.Sign(angleDiff); |
| 7 | 67 | | var usedShapes = 1; |
| 7 | 68 | | route.Meta[offset].Attributes.TryGetValue("name", out var name); |
| | 69 | |
|
| | 70 | |
|
| 7 | 71 | | var totalDistance = route.DistanceToNextPoint(offset); |
| | 72 | | // We walk forward and detect a true gentle bend: |
| 28 | 73 | | while (offset + usedShapes < route.Last) |
| 24 | 74 | | { |
| 24 | 75 | | var distance = route.DistanceToNextPoint(offset + usedShapes); |
| 24 | 76 | | if (distance > 35) |
| 2 | 77 | | { |
| | 78 | | // a gentle bend must have pieces that are not too long at a time |
| 2 | 79 | | break; |
| | 80 | | } |
| | 81 | |
|
| 22 | 82 | | var dAngle = route.DirectionChangeAt(offset + usedShapes); |
| 22 | 83 | | if (Math.Sign(route.DirectionChangeAt(offset + usedShapes)) != angleSign) |
| 0 | 84 | | { |
| | 85 | | // The gentle bend should turn in the same direction as the first angle |
| | 86 | | // Here, it doesn't have that... |
| 0 | 87 | | break; |
| | 88 | | } |
| | 89 | |
|
| | 90 | |
|
| 22 | 91 | | if (!DoesFollowBend(route, offset + usedShapes, dAngle, angleSign)) |
| 1 | 92 | | { |
| | 93 | | // |
| 1 | 94 | | break; |
| | 95 | | } |
| | 96 | |
|
| 21 | 97 | | route.Meta[offset + usedShapes].Attributes.TryGetValue("name", out var newName); |
| 21 | 98 | | if (name != newName) |
| 0 | 99 | | { |
| | 100 | | // Different street |
| 0 | 101 | | break; |
| | 102 | | } |
| | 103 | |
|
| 21 | 104 | | totalDistance += distance; |
| 21 | 105 | | angleDiff += dAngle; |
| | 106 | | // We keep the total angle too; as it might turn more then 180° |
| | 107 | | // We do NOT normalize the angle |
| 21 | 108 | | usedShapes++; |
| 21 | 109 | | } |
| | 110 | |
|
| | 111 | |
|
| 7 | 112 | | if (usedShapes <= 2) |
| 2 | 113 | | { |
| | 114 | | // A 'bend' isn't a bend if there is only one point, otherwise it is a turn... |
| 2 | 115 | | return null; |
| | 116 | | } |
| | 117 | |
|
| | 118 | |
|
| | 119 | | // A gentle bend also does turn, at least a few degrees per meter |
| 5 | 120 | | if (Math.Abs(angleDiff) < 45) |
| 0 | 121 | | { |
| | 122 | | // There is little change - does it at least turn a bit? |
| 0 | 123 | | if (Math.Abs(angleDiff) / totalDistance < 2.5) |
| 0 | 124 | | { |
| | 125 | | // Nope, we turn only 2.5° per meter - that isn't a lot |
| 0 | 126 | | return null; |
| | 127 | | } |
| 0 | 128 | | } |
| | 129 | |
|
| 5 | 130 | | return new FollowBendInstruction( |
| 5 | 131 | | route, |
| 5 | 132 | | offset, |
| 5 | 133 | | offset + usedShapes, |
| 5 | 134 | | angleDiff |
| 5 | 135 | | ); |
| 7 | 136 | | } |
| | 137 | | } |