| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | | using System.Threading; |
| | 5 | | using Itinero.Network; |
| | 6 | | using Itinero.Network.Enumerators.Edges; |
| | 7 | | using Itinero.Profiles; |
| | 8 | | using Itinero.Routes.Paths; |
| | 9 | |
|
| | 10 | | namespace Itinero.Routes.Builders; |
| | 11 | |
|
| | 12 | | /// <summary> |
| | 13 | | /// Default route builder implementation. |
| | 14 | | /// </summary> |
| | 15 | | public class RouteBuilder : IRouteBuilder |
| | 16 | | { |
| | 17 | | private readonly Func<IEnumerable<(string, string)>, bool, double, RoutingNetworkEdgeEnumerator, IEnumerable<(string |
| 4 | 18 | | private static readonly ThreadLocal<RouteBuilder> DefaultLazy = new(() => new RouteBuilder()); |
| | 19 | |
|
| | 20 | | /// <summary> |
| | 21 | | /// Gets the default instance (for the local thread). |
| | 22 | | /// </summary> |
| 21 | 23 | | public static RouteBuilder Default => DefaultLazy.Value; |
| | 24 | |
|
| | 25 | | /// <summary> |
| | 26 | | /// </summary> |
| | 27 | | /// <param name="calculateExtraAttributes"> |
| | 28 | | /// If this callback is given, it will be executed on every segment to inject extra attributes into the ShapeMeta. |
| | 29 | | /// The parameters are: |
| | 30 | | /// - attributes:be a reference to the attributes which the edgeEnumerator gave |
| | 31 | | /// - direction: if we are travelling in forward direction over the edge |
| | 32 | | /// - distance: the length we are travelling on the edge |
| | 33 | | /// - edgeEnumerator: the current edgeEnumerator |
| | 34 | | /// </param> |
| 3 | 35 | | public RouteBuilder(Func<IEnumerable<(string, string)>, bool, double, RoutingNetworkEdgeEnumerator, IEnumerable<(str |
| 3 | 36 | | { |
| 3 | 37 | | _calculateExtraAttributes = calculateExtraAttributes; |
| 3 | 38 | | } |
| | 39 | |
|
| | 40 | | /// <inheritdoc /> |
| | 41 | | public Result<Route> Build(RoutingNetwork routingNetwork, Profile profile, Path path) |
| 21 | 42 | | { |
| 21 | 43 | | var edgeEnumerator = routingNetwork.GetEdgeEnumerator(); |
| 21 | 44 | | var profileCached = routingNetwork.GetCachedProfile(profile); |
| | 45 | |
|
| | 46 | | // SpareEnumerator is used in the branch-calculation. IT offers no guarantees about the state |
| 21 | 47 | | var spareEnumerator = routingNetwork.GetEdgeEnumerator(); |
| 21 | 48 | | var route = new Route { Profile = profile.Name }; |
| 21 | 49 | | var branches = new List<Route.Branch>(); |
| 21 | 50 | | var seenEdges = 0; |
| | 51 | |
|
| 21 | 52 | | var allEdgeIds = new HashSet<EdgeId>(); |
| 123 | 53 | | foreach (var edge in path) |
| 30 | 54 | | { |
| 30 | 55 | | allEdgeIds.Add(edge.edge); |
| 30 | 56 | | } |
| | 57 | |
|
| 123 | 58 | | foreach (var (edge, direction, offset1, offset2) in path) |
| 30 | 59 | | { |
| 30 | 60 | | if (route.Shape.Count == 0) |
| 21 | 61 | | { |
| | 62 | | // This is the first edge of the route - we have to check for branches at the start loction |
| | 63 | | bool firstEdgeIsFullyContained; |
| 21 | 64 | | if (direction) |
| 13 | 65 | | { |
| | 66 | | // Forward: we look to offset1 |
| 13 | 67 | | firstEdgeIsFullyContained = offset1 == 0; |
| 13 | 68 | | } |
| | 69 | | else |
| 8 | 70 | | { |
| 8 | 71 | | firstEdgeIsFullyContained = offset2 == ushort.MaxValue; |
| 8 | 72 | | } |
| | 73 | |
|
| 21 | 74 | | if (firstEdgeIsFullyContained) |
| 21 | 75 | | { |
| | 76 | | // We check for branches |
| 21 | 77 | | edgeEnumerator.MoveTo(edge, direction); |
| 21 | 78 | | this.AddBranches(edgeEnumerator.Tail, edgeEnumerator, spareEnumerator, 0, branches, allEdgeIds); |
| 21 | 79 | | } |
| 21 | 80 | | } |
| | 81 | |
|
| 30 | 82 | | edgeEnumerator.MoveTo(edge, direction); |
| | 83 | |
|
| 30 | 84 | | var attributes = edgeEnumerator.Attributes; |
| 30 | 85 | | var factor = profileCached.Factor(edgeEnumerator); |
| 30 | 86 | | var distance = edgeEnumerator.EdgeLength(); |
| 30 | 87 | | distance = (offset2 - offset1) / (double)ushort.MaxValue * distance; |
| 30 | 88 | | route.TotalDistance += distance; |
| | 89 | |
|
| 30 | 90 | | if (_calculateExtraAttributes != null) |
| 0 | 91 | | { |
| 0 | 92 | | attributes = attributes.Concat(_calculateExtraAttributes(attributes, direction, distance, edgeEnumerator |
| 0 | 93 | | } |
| 30 | 94 | | var speed = factor.ForwardSpeedMeterPerSecond; |
| 30 | 95 | | if (speed <= 0) |
| 0 | 96 | | { |
| 0 | 97 | | speed = 10; |
| | 98 | | // Something is wrong here |
| | 99 | | // throw new ArgumentException("Speed is zero! Route is not possible with the given profile."); |
| 0 | 100 | | } |
| | 101 | |
|
| 30 | 102 | | var time = distance / speed; |
| 30 | 103 | | route.TotalTime += time; |
| | 104 | |
|
| | 105 | |
|
| | 106 | | // add shape points to route. |
| 30 | 107 | | using var shapeBetween = edgeEnumerator.GetShapeBetween(offset1, offset2).GetEnumerator(); |
| | 108 | | // skip first coordinate if there are already previous edges. |
| 30 | 109 | | if (route.Shape.Count > 0 && offset1 == 0) |
| 9 | 110 | | { |
| 9 | 111 | | shapeBetween.MoveNext(); |
| 9 | 112 | | } |
| | 113 | |
|
| | 114 | |
|
| 113 | 115 | | while (shapeBetween.MoveNext()) |
| 83 | 116 | | { |
| 83 | 117 | | route.Shape.Add(shapeBetween.Current); |
| 83 | 118 | | } |
| | 119 | |
|
| 30 | 120 | | if (route.Shape.Count == 0) |
| 0 | 121 | | { |
| 0 | 122 | | route.Shape.Add(edgeEnumerator.LocationOnEdge(offset2)); |
| 0 | 123 | | } |
| | 124 | |
|
| 30 | 125 | | if (offset1 != 0 || |
| 30 | 126 | | offset2 != ushort.MaxValue) |
| 0 | 127 | | { |
| 0 | 128 | | attributes = attributes.Concat(new (string key, string value)[] |
| 0 | 129 | | { |
| 0 | 130 | | ("_segment_offset1", |
| 0 | 131 | | (offset1 / (double)ushort.MaxValue).ToString(System.Globalization.CultureInfo |
| 0 | 132 | | .InvariantCulture)), |
| 0 | 133 | | ("_segment_offset2", |
| 0 | 134 | | (offset2 / (double)ushort.MaxValue).ToString(System.Globalization.CultureInfo |
| 0 | 135 | | .InvariantCulture)), |
| 0 | 136 | | }); |
| 0 | 137 | | } |
| | 138 | |
|
| 30 | 139 | | attributes = attributes.Concat(new (string key, string value)[] |
| 30 | 140 | | { |
| 30 | 141 | | ("_segment_forward", direction.ToString()) |
| 30 | 142 | | }); |
| | 143 | |
|
| 30 | 144 | | route.ShapeMeta.Add(new Route.Meta |
| 30 | 145 | | { |
| 30 | 146 | | Shape = route.Shape.Count - 1, |
| 30 | 147 | | Attributes = attributes, |
| 30 | 148 | | AttributesAreForward = direction, |
| 30 | 149 | | Distance = distance, |
| 30 | 150 | | Profile = profile.Name, |
| 30 | 151 | | Time = time |
| 30 | 152 | | }); |
| | 153 | |
|
| | 154 | | // Intermediate points of an edge will never have branches |
| | 155 | | // So, to calculate branches, it is enough to only look at the last point of the edge |
| | 156 | | // (and the first point of the first edge - a true edge case) |
| | 157 | | // (Also note that the first and last edge might not be needed entirely, so that means we possible can ignor |
| | 158 | |
|
| | 159 | | // What is the end vertex? Add its branches... |
| 30 | 160 | | if (seenEdges + 1 == path.Count) |
| 21 | 161 | | { |
| | 162 | | // Hmm, this is the last edge |
| | 163 | | // We should add the branches of it, but only if the edge is completely contained |
| | 164 | | bool lastEdgeIsFullyContained; |
| 21 | 165 | | if (!direction) |
| 8 | 166 | | { |
| | 167 | | // Backward: we look to offset1 |
| 8 | 168 | | lastEdgeIsFullyContained = offset1 == 0; |
| 8 | 169 | | } |
| | 170 | | else |
| 13 | 171 | | { |
| 13 | 172 | | lastEdgeIsFullyContained = offset2 == ushort.MaxValue; |
| 13 | 173 | | } |
| | 174 | |
|
| 21 | 175 | | if (lastEdgeIsFullyContained) |
| 21 | 176 | | { |
| 21 | 177 | | this.AddBranches(edgeEnumerator.Head, |
| 21 | 178 | | edgeEnumerator, spareEnumerator, route.Shape.Count - 1, branches, allEdgeIds); |
| 21 | 179 | | } |
| 21 | 180 | | } |
| | 181 | | else |
| 9 | 182 | | { |
| 9 | 183 | | this.AddBranches(edgeEnumerator.Head, edgeEnumerator, spareEnumerator, route.Shape.Count - 1, branches, |
| 9 | 184 | | allEdgeIds); |
| 9 | 185 | | } |
| | 186 | |
|
| 30 | 187 | | seenEdges++; |
| 30 | 188 | | } |
| | 189 | |
|
| 21 | 190 | | route.Branches = branches.ToArray(); |
| | 191 | |
|
| 21 | 192 | | return route; |
| 21 | 193 | | } |
| | 194 | |
|
| | 195 | | /// <summary> |
| | 196 | | /// Calculate all the branches of 'centralVertexid' |
| | 197 | | /// </summary> |
| | 198 | | /// <param name="edgeEnumerator">The edge enumerator, moved to the point of which the branches should be added</para |
| | 199 | | /// <param name="spareEnumerator">An extra enumerator to use. Will be moved during the call</param> |
| | 200 | | /// <param name="centralVertexId">The vertex id of the point under consideration</param> |
| | 201 | | /// <param name="shapeIndex">The index of the shape of the current vertex</param> |
| | 202 | | /// <param name="addTo">All the branches are collected into this collection</param> |
| | 203 | | /// <param name="branchBlacklist">Edges in this list won't be used as branches</param> |
| | 204 | | private void AddBranches(VertexId centralVertexId, RoutingNetworkEdgeEnumerator edgeEnumerator, |
| | 205 | | RoutingNetworkEdgeEnumerator spareEnumerator, int shapeIndex, |
| | 206 | | ICollection<Route.Branch> addTo, HashSet<EdgeId> branchBlacklist) |
| 51 | 207 | | { |
| 51 | 208 | | edgeEnumerator.MoveTo(centralVertexId); |
| 119 | 209 | | while (edgeEnumerator.MoveNext()) |
| 68 | 210 | | { |
| | 211 | | // Iterates over all edges of the endvertex |
| | 212 | |
|
| 68 | 213 | | if (branchBlacklist.Contains(edgeEnumerator.EdgeId)) |
| 60 | 214 | | { |
| | 215 | | // We make sure not to pick the current nor the next edge of the path |
| | 216 | | // This is simple as we have a hashset containing _all_ the edge ids ¯\_(ツ)_/¯ |
| 60 | 217 | | continue; |
| | 218 | | } |
| | 219 | |
|
| | 220 | | // If the vertex of the crossroads are the same as the from, we would walk forward over the branch if we wou |
| 8 | 221 | | var isForward = edgeEnumerator.Forward; |
| 8 | 222 | | spareEnumerator.MoveTo(edgeEnumerator.EdgeId, isForward); |
| 8 | 223 | | using var shapeEnum = spareEnumerator.GetShapeBetween(includeVertices: false).GetEnumerator(); |
| 8 | 224 | | shapeEnum.MoveNext(); // Init enumerator at first value |
| 8 | 225 | | shapeEnum.MoveNext(); |
| 8 | 226 | | var branch = new Route.Branch |
| 8 | 227 | | { |
| 8 | 228 | | Attributes = edgeEnumerator.Attributes, |
| 8 | 229 | | Shape = shapeIndex, |
| 8 | 230 | | AttributesAreForward = isForward, |
| 8 | 231 | | Coordinate = shapeEnum.Current |
| 8 | 232 | | }; |
| 8 | 233 | | addTo.Add(branch); |
| 8 | 234 | | } |
| 51 | 235 | | } |
| | 236 | | } |