| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | | using Itinero.Geo; |
| | 5 | |
|
| | 6 | | namespace Itinero.Network.Enumerators.Edges; |
| | 7 | |
|
| | 8 | | /// <summary> |
| | 9 | | /// Contains edge enumerator extensions. |
| | 10 | | /// </summary> |
| | 11 | | public static class IEdgeEnumeratorExtensions |
| | 12 | | { |
| | 13 | | /// <summary> |
| | 14 | | /// Gets the complete shape, including start end end vertices. |
| | 15 | | /// </summary> |
| | 16 | | /// <param name="enumerator">The enumerator.</param> |
| | 17 | | /// <returns>The complete shape.</returns> |
| | 18 | | public static IEnumerable<(double longitude, double latitude, float? e)> GetCompleteShape( |
| | 19 | | this IEdgeEnumerator enumerator) |
| 338 | 20 | | { |
| 338 | 21 | | yield return enumerator.TailLocation; |
| | 22 | |
|
| 338 | 23 | | var shape = enumerator.Shape; |
| 1334 | 24 | | foreach (var s in shape) |
| 160 | 25 | | { |
| 160 | 26 | | yield return s; |
| 160 | 27 | | } |
| | 28 | |
|
| 338 | 29 | | yield return enumerator.HeadLocation; |
| 330 | 30 | | } |
| | 31 | |
|
| | 32 | | /// <summary> |
| | 33 | | /// Gets the length of an edge in centimeters. |
| | 34 | | /// </summary> |
| | 35 | | /// <param name="enumerator">The enumerator.</param> |
| | 36 | | /// <returns>The length in meters.</returns> |
| | 37 | | public static double EdgeLength(this IEdgeEnumerator enumerator) |
| 241 | 38 | | { |
| 241 | 39 | | if (enumerator.Length != null) return enumerator.Length.Value / 100.0; |
| | 40 | |
|
| 241 | 41 | | return enumerator.GetCompleteShape().DistanceEstimateInMeter(); |
| 241 | 42 | | } |
| | 43 | |
|
| | 44 | | /// <summary> |
| | 45 | | /// Returns the part of the edge between the two offsets not including the vertices at the start or the end. |
| | 46 | | /// </summary> |
| | 47 | | /// <param name="enumerator">The enumerator.</param> |
| | 48 | | /// <param name="offset1">The start offset.</param> |
| | 49 | | /// <param name="offset2">The end offset.</param> |
| | 50 | | /// <param name="includeVertices">Include vertices in case the range start at min offset or ends at max.</param> |
| | 51 | | /// <returns>The shape points between the given offsets. Includes the vertices by default when offsets at min/max.</ |
| | 52 | | public static IEnumerable<(double longitude, double latitude, float? e)> GetShapeBetween( |
| | 53 | | this IEdgeEnumerator enumerator, |
| | 54 | | ushort offset1 = 0, ushort offset2 = ushort.MaxValue, bool includeVertices = true) |
| 57 | 55 | | { |
| 57 | 56 | | if (offset1 > offset2) |
| 0 | 57 | | { |
| 0 | 58 | | throw new ArgumentException($"{nameof(offset1)} has to smaller than or equal to {nameof(offset2)}"); |
| | 59 | | } |
| | 60 | |
|
| | 61 | | // return the entire edge if requested. |
| 57 | 62 | | if (offset1 == 0 && offset2 == ushort.MaxValue) |
| 57 | 63 | | { |
| 475 | 64 | | foreach (var s in enumerator.GetCompleteShape()) |
| 156 | 65 | | { |
| 156 | 66 | | yield return s; |
| 148 | 67 | | } |
| | 68 | |
|
| 49 | 69 | | yield break; |
| | 70 | | } |
| | 71 | |
|
| | 72 | | // get edge and shape details. |
| 0 | 73 | | var shape = enumerator.Shape.ToList(); |
| | 74 | |
|
| | 75 | | // calculate offsets in meters. |
| 0 | 76 | | var edgeLength = enumerator.EdgeLength(); |
| 0 | 77 | | var offset1Length = offset1 / (double)ushort.MaxValue * edgeLength; |
| 0 | 78 | | var offset2Length = offset2 / (double)ushort.MaxValue * edgeLength; |
| | 79 | |
|
| | 80 | | // TODO: can we make this easier with the complete shape enumeration? |
| | 81 | | // calculate coordinate shape. |
| 0 | 82 | | var before = offset1 > 0; // when there is a start offset. |
| 0 | 83 | | var length = 0.0; |
| 0 | 84 | | var previous = enumerator.TailLocation; |
| 0 | 85 | | if (offset1 == 0 && includeVertices) |
| 0 | 86 | | { |
| 0 | 87 | | yield return previous; |
| 0 | 88 | | } |
| | 89 | |
|
| 0 | 90 | | for (var i = 0; i < shape.Count + 1; i++) |
| 0 | 91 | | { |
| | 92 | | (double longitude, double latitude, float? e) next; |
| 0 | 93 | | if (i < shape.Count) |
| 0 | 94 | | { |
| | 95 | | // the |
| 0 | 96 | | next = shape[i]; |
| 0 | 97 | | } |
| | 98 | | else |
| 0 | 99 | | { |
| | 100 | | // the last location. |
| 0 | 101 | | next = enumerator.HeadLocation; |
| 0 | 102 | | } |
| | 103 | |
|
| 0 | 104 | | var segmentLength = previous.DistanceEstimateInMeter(next); |
| 0 | 105 | | if (before) |
| 0 | 106 | | { |
| | 107 | | // check if offset1 length has exceeded. |
| 0 | 108 | | if (segmentLength + length >= offset1Length && |
| 0 | 109 | | offset1 > 0) |
| 0 | 110 | | { |
| | 111 | | // we are before, but not we have move to after. |
| 0 | 112 | | var segmentOffset = offset1Length - length; |
| 0 | 113 | | var location = (previous, next).PositionAlongLine(segmentOffset / segmentLength); |
| 0 | 114 | | previous = next; |
| 0 | 115 | | before = false; |
| 0 | 116 | | yield return location; |
| 0 | 117 | | } |
| 0 | 118 | | } |
| | 119 | |
|
| 0 | 120 | | if (!before) |
| 0 | 121 | | { |
| | 122 | | // check if offset2 length has exceeded. |
| 0 | 123 | | if (segmentLength + length > offset2Length && |
| 0 | 124 | | offset2 < ushort.MaxValue) |
| 0 | 125 | | { |
| | 126 | | // we are after but now we are after. |
| 0 | 127 | | var segmentOffset = offset2Length - length; |
| 0 | 128 | | var location = (previous, next).PositionAlongLine(segmentOffset / segmentLength); |
| 0 | 129 | | yield return location; |
| 0 | 130 | | yield break; |
| | 131 | | } |
| | 132 | |
|
| | 133 | | // the case where include vertices is false. |
| 0 | 134 | | if (i == shape.Count && !includeVertices) |
| 0 | 135 | | { |
| 0 | 136 | | yield break; |
| | 137 | | } |
| | 138 | |
|
| 0 | 139 | | yield return next; |
| 0 | 140 | | } |
| | 141 | |
|
| | 142 | | // move to the next segment. |
| 0 | 143 | | previous = next; |
| 0 | 144 | | length += segmentLength; |
| 0 | 145 | | } |
| 0 | 146 | | } |
| | 147 | |
|
| | 148 | | /// <summary> |
| | 149 | | /// Returns the location on the given edge using the given offset. |
| | 150 | | /// </summary> |
| | 151 | | /// <param name="enumerator">The enumerator.</param> |
| | 152 | | /// <param name="offset">The offset.</param> |
| | 153 | | /// <returns>The location on the network.</returns> |
| | 154 | | internal static (double longitude, double latitude, float? e) LocationOnEdge(this IEdgeEnumerator enumerator, |
| | 155 | | in ushort offset) |
| 19 | 156 | | { |
| | 157 | | // TODO: this can be optimized, build a performance test. |
| 19 | 158 | | var shape = enumerator.GetShapeBetween().ToList(); |
| 19 | 159 | | var length = enumerator.EdgeLength(); |
| 19 | 160 | | var currentLength = 0.0; |
| 19 | 161 | | var targetLength = length * (offset / (double)ushort.MaxValue); |
| 56 | 162 | | for (var i = 1; i < shape.Count; i++) |
| 25 | 163 | | { |
| 25 | 164 | | var segmentLength = shape[i - 1].DistanceEstimateInMeter(shape[i]); |
| 25 | 165 | | if (segmentLength + currentLength > targetLength) |
| 16 | 166 | | { |
| 16 | 167 | | var segmentOffsetLength = segmentLength + currentLength - targetLength; |
| 16 | 168 | | var segmentOffset = 1 - (segmentOffsetLength / segmentLength); |
| 16 | 169 | | float? e = null; |
| 16 | 170 | | if (shape[i - 1].e.HasValue && |
| 16 | 171 | | shape[i].e.HasValue) |
| 0 | 172 | | { |
| 0 | 173 | | e = (float)(shape[i - 1].e.Value + (segmentOffset * (shape[i].e.Value - shape[i - 1].e.Value))); |
| 0 | 174 | | } |
| | 175 | |
|
| 16 | 176 | | return (shape[i - 1].longitude + (segmentOffset * (shape[i].longitude - shape[i - 1].longitude)), |
| 16 | 177 | | shape[i - 1].latitude + (segmentOffset * (shape[i].latitude - shape[i - 1].latitude)), e); |
| | 178 | | } |
| | 179 | |
|
| 9 | 180 | | currentLength += segmentLength; |
| 9 | 181 | | } |
| | 182 | |
|
| 3 | 183 | | return shape[^1]; |
| 19 | 184 | | } |
| | 185 | | } |