66using Microsoft . Extensions . Logging ;
77using PolylineAlgorithm . Internal ;
88using PolylineAlgorithm . Internal . Diagnostics ;
9+ using System . Buffers ;
910using System . Runtime . CompilerServices ;
1011
1112namespace PolylineAlgorithm . Abstraction ;
1213
1314/// <summary>
14- /// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates .
15+ /// Provides a base implementation for decoding encoded polyline strings into sequences of items .
1516/// </summary>
1617/// <remarks>
17- /// Derive from this class to implement a decoder for a specific polyline type. Override <see cref="GetReadOnlyMemory"/>
18- /// and <see cref="CreateCoordinate"/> to provide type-specific behavior.
18+ /// Derive from this class to implement a decoder for a specific polyline type. Override
19+ /// <see cref="ValuesPerItem"/>, <see cref="GetReadOnlyMemory"/>, and <see cref="CreateItem"/> to provide
20+ /// type-specific behavior.
21+ /// <para>
22+ /// The polyline format encodes each item as a fixed-length run of <see cref="ValuesPerItem"/> delta-compressed
23+ /// values. All items in a single polyline must have the same number of values. For example, a 2D GPS decoder
24+ /// sets <see cref="ValuesPerItem"/> to 2 (latitude, longitude), while a 3D GPS decoder sets it to 3
25+ /// (latitude, longitude, altitude).
26+ /// </para>
1927/// </remarks>
2028/// <typeparam name="TPolyline">The type that represents the encoded polyline input.</typeparam>
21- /// <typeparam name="TCoordinate">The type that represents a decoded geographic coordinate .</typeparam>
29+ /// <typeparam name="TCoordinate">The type that represents a decoded item .</typeparam>
2230public abstract class AbstractPolylineDecoder < TPolyline , TCoordinate > : IPolylineDecoder < TPolyline , TCoordinate > {
2331 private readonly ILogger < AbstractPolylineDecoder < TPolyline , TCoordinate > > _logger ;
2432
@@ -53,6 +61,16 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
5361 /// </summary>
5462 public PolylineEncodingOptions Options { get ; }
5563
64+ /// <summary>
65+ /// Gets the number of encoded values that make up a single decoded item.
66+ /// </summary>
67+ /// <remarks>
68+ /// Override this property to specify the arity of each item. For example, return <c>2</c> for
69+ /// latitude/longitude pairs, <c>3</c> for latitude/longitude/altitude triples, or any other count
70+ /// that matches the encoding scheme used to produce the polyline.
71+ /// </remarks>
72+ protected abstract int ValuesPerItem { get ; }
73+
5674 /// <summary>
5775 /// Decodes an encoded <typeparamref name="TPolyline"/> into a sequence of <typeparamref name="TCoordinate"/> instances,
5876 /// with support for cancellation.
@@ -64,7 +82,7 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
6482 /// A <see cref="CancellationToken"/> that can be used to cancel the decoding operation.
6583 /// </param>
6684 /// <returns>
67- /// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded latitude and longitude pairs .
85+ /// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded items .
6886 /// </returns>
6987 /// <exception cref="ArgumentNullException">
7088 /// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
@@ -90,30 +108,45 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken can
90108 ValidateSequence ( sequence , _logger ) ;
91109 ValidateFormat ( sequence , _logger ) ;
92110
111+ int valuesPerItem = ValuesPerItem ;
93112 int position = 0 ;
94- int encodedLatitude = 0 ;
95- int encodedLongitude = 0 ;
113+
114+ int [ ] ? runningRent = ArrayPool < int > . Shared . Rent ( valuesPerItem ) ;
115+ // Zero-initialize so delta decoding starts from 0 for all dimensions.
116+ for ( int j = 0 ; j < valuesPerItem ; j ++ ) {
117+ runningRent [ j ] = 0 ;
118+ }
96119
97120 try {
98121 while ( position < sequence . Length ) {
99122 cancellationToken . ThrowIfCancellationRequested ( ) ;
100123
101- if ( ! PolylineEncoding . TryReadValue ( ref encodedLatitude , sequence , ref position )
102- || ! PolylineEncoding . TryReadValue ( ref encodedLongitude , sequence , ref position ) ) {
124+ bool allRead = true ;
125+ for ( int j = 0 ; j < valuesPerItem ; j ++ ) {
126+ if ( ! PolylineEncoding . TryReadValue ( ref runningRent [ j ] , sequence , ref position ) ) {
127+ allRead = false ;
128+ break ;
129+ }
130+ }
131+
132+ if ( ! allRead ) {
103133 _logger ? . LogOperationFailedDebug ( OperationName ) ;
104134 _logger ? . LogInvalidPolylineWarning ( position ) ;
105135
106136 ExceptionGuard . ThrowInvalidPolylineFormat ( position ) ;
107137 }
108138
109- double decodedLatitude = PolylineEncoding . Denormalize ( encodedLatitude , Options . Precision ) ;
110- double decodedLongitude = PolylineEncoding . Denormalize ( encodedLongitude , Options . Precision ) ;
139+ double [ ] decoded = new double [ valuesPerItem ] ;
140+ for ( int j = 0 ; j < valuesPerItem ; j ++ ) {
141+ decoded [ j ] = PolylineEncoding . Denormalize ( runningRent [ j ] , Options . Precision ) ;
142+ }
111143
112- _logger ? . LogDecodedCoordinateDebug ( decodedLatitude , decodedLongitude , position ) ;
144+ _logger ? . LogDecodedValuesDebug ( valuesPerItem , position ) ;
113145
114- yield return CreateCoordinate ( decodedLatitude , decodedLongitude ) ;
146+ yield return CreateItem ( decoded . AsMemory ( ) ) ;
115147 }
116148 } finally {
149+ ArrayPool < int > . Shared . Return ( runningRent ! ) ;
117150 _logger ? . LogOperationFinishedDebug ( OperationName ) ;
118151 }
119152 }
@@ -188,17 +221,19 @@ protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? lo
188221 protected abstract ReadOnlyMemory < char > GetReadOnlyMemory ( in TPolyline polyline ) ;
189222
190223 /// <summary>
191- /// Creates a <typeparamref name="TCoordinate"/> instance from the specified latitude and longitude values.
224+ /// Creates a <typeparamref name="TCoordinate"/> instance from the specified decoded values.
192225 /// </summary>
193- /// <param name="latitude">
194- /// The latitude component of the coordinate, in degrees.
195- /// </param>
196- /// <param name="longitude">
197- /// The longitude component of the coordinate, in degrees.
226+ /// <param name="values">
227+ /// A <see cref="ReadOnlyMemory{T}"/> of <see cref="double"/> containing exactly <see cref="ValuesPerItem"/>
228+ /// decoded values for this item, in the same order they were encoded.
198229 /// </param>
199230 /// <returns>
200- /// A <typeparamref name="TCoordinate"/> instance representing the specified geographic coordinate .
231+ /// A <typeparamref name="TCoordinate"/> instance representing the decoded item .
201232 /// </returns>
233+ /// <remarks>
234+ /// Implementations should read all required values from <paramref name="values"/> and construct the
235+ /// <typeparamref name="TCoordinate"/> immediately. The memory is valid only for the duration of this call.
236+ /// </remarks>
202237 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
203- protected abstract TCoordinate CreateCoordinate ( double latitude , double longitude ) ;
238+ protected abstract TCoordinate CreateItem ( ReadOnlyMemory < double > values ) ;
204239}
0 commit comments