@@ -14,13 +14,24 @@ namespace PolylineAlgorithm.Abstraction;
1414/// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates.
1515/// </summary>
1616/// <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.
17+ /// <para>
18+ /// <b>Formatter-based use (no subclassing required):</b>
19+ /// Supply a <see cref="PolylineOptions{TPolyline, TCoordinate}"/> via the
20+ /// <see cref="AbstractPolylineDecoder{TPolyline, TCoordinate}(PolylineOptions{TPolyline, TCoordinate})"/>
21+ /// constructor. The formatters handle all type-specific concerns; override nothing.
22+ /// </para>
23+ /// <para>
24+ /// <b>Legacy override-based use:</b>
25+ /// Derive from this class and override <see cref="GetReadOnlyMemory"/> and <see cref="CreateCoordinate"/>
26+ /// to provide type-specific behaviour. These overrides take priority over any registered formatter.
27+ /// </para>
1928/// </remarks>
2029/// <typeparam name="TPolyline">The type that represents the encoded polyline input.</typeparam>
2130/// <typeparam name="TCoordinate">The type that represents a decoded geographic coordinate.</typeparam>
22- public abstract class AbstractPolylineDecoder < TPolyline , TCoordinate > : IPolylineDecoder < TPolyline , TCoordinate > {
31+ public class AbstractPolylineDecoder < TPolyline , TCoordinate > : IPolylineDecoder < TPolyline , TCoordinate > {
2332 private readonly ILogger < AbstractPolylineDecoder < TPolyline , TCoordinate > > _logger ;
33+ private readonly IPolylineValueFormatter < TCoordinate > ? _valueFormatter ;
34+ private readonly IPolylineFormatter < TPolyline > ? _polylineFormatter ;
2435
2536 /// <summary>
2637 /// Initializes a new instance of the <see cref="AbstractPolylineDecoder{TPolyline, TCoordinate}"/> class with default encoding options.
@@ -48,6 +59,35 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
4859 . CreateLogger < AbstractPolylineDecoder < TPolyline , TCoordinate > > ( ) ;
4960 }
5061
62+ /// <summary>
63+ /// Initializes a new instance of the <see cref="AbstractPolylineDecoder{TPolyline, TCoordinate}"/> class
64+ /// using the supplied <see cref="PolylineOptions{TCoordinate, TPolyline}"/>.
65+ /// </summary>
66+ /// <remarks>
67+ /// Use this constructor when you want formatter-driven decoding without subclassing.
68+ /// The <see cref="GetReadOnlyMemory"/> and <see cref="CreateCoordinate"/> hooks are not called;
69+ /// all type-specific logic is delegated to the formatters.
70+ /// </remarks>
71+ /// <param name="options">
72+ /// A <see cref="PolylineOptions{TCoordinate, TPolyline}"/> that carries both the value formatter and
73+ /// the polyline formatter together with the underlying <see cref="PolylineEncodingOptions"/>.
74+ /// </param>
75+ /// <exception cref="ArgumentNullException">
76+ /// Thrown when <paramref name="options"/> is <see langword="null"/>.
77+ /// </exception>
78+ public AbstractPolylineDecoder ( PolylineOptions < TCoordinate , TPolyline > options ) {
79+ if ( options is null ) {
80+ ExceptionGuard . ThrowArgumentNull ( nameof ( options ) ) ;
81+ }
82+
83+ Options = options . Encoding ;
84+ _polylineFormatter = options . PolylineFormatter ;
85+ _valueFormatter = options . ValueFormatter ;
86+ _logger = Options
87+ . LoggerFactory
88+ . CreateLogger < AbstractPolylineDecoder < TPolyline , TCoordinate > > ( ) ;
89+ }
90+
5191 /// <summary>
5292 /// Gets the encoding options used by this polyline decoder.
5393 /// </summary>
@@ -78,43 +118,75 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
78118 /// <exception cref="OperationCanceledException">
79119 /// Thrown when <paramref name="cancellationToken"/> is canceled during decoding.
80120 /// </exception>
121+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Design" , "MA0051:Method is too long" , Justification = "Method contains two path implementations." ) ]
81122 public IEnumerable < TCoordinate > Decode ( TPolyline polyline , CancellationToken cancellationToken = default ) {
82123 const string OperationName = nameof ( Decode ) ;
83124
84125 _logger ? . LogOperationStartedDebug ( OperationName ) ;
85126
86127 ValidateNullPolyline ( polyline , _logger ) ;
87128
88- ReadOnlyMemory < char > sequence = GetReadOnlyMemory ( in polyline ) ;
129+ ReadOnlyMemory < char > sequence = ( _valueFormatter is not null && _polylineFormatter is not null )
130+ ? _polylineFormatter . Read ( polyline )
131+ : GetReadOnlyMemory ( in polyline ) ;
89132
90133 ValidateSequence ( sequence , _logger ) ;
91134 ValidateFormat ( sequence , _logger ) ;
92135
93136 int position = 0 ;
94- int encodedLatitude = 0 ;
95- int encodedLongitude = 0 ;
96137
97- try {
98- while ( position < sequence . Length ) {
99- cancellationToken . ThrowIfCancellationRequested ( ) ;
138+ if ( _valueFormatter is not null && _polylineFormatter is not null ) {
139+ int width = _valueFormatter . Width ;
140+ int [ ] accumulated = new int [ width ] ;
141+ long [ ] longValues = new long [ width ] ;
142+
143+ try {
144+ while ( position < sequence . Length ) {
145+ cancellationToken . ThrowIfCancellationRequested ( ) ;
100146
101- if ( ! PolylineEncoding . TryReadValue ( ref encodedLatitude , sequence , ref position )
102- || ! PolylineEncoding . TryReadValue ( ref encodedLongitude , sequence , ref position ) ) {
103- _logger ? . LogOperationFailedDebug ( OperationName ) ;
104- _logger ? . LogInvalidPolylineWarning ( position ) ;
147+ for ( int j = 0 ; j < width ; j ++ ) {
148+ if ( ! PolylineEncoding . TryReadValue ( ref accumulated [ j ] , sequence , ref position ) ) {
149+ _logger ? . LogOperationFailedDebug ( OperationName ) ;
150+ _logger ? . LogInvalidPolylineWarning ( position ) ;
151+ ExceptionGuard . ThrowInvalidPolylineFormat ( position ) ;
152+ }
153+ }
105154
106- ExceptionGuard . ThrowInvalidPolylineFormat ( position ) ;
155+ for ( int j = 0 ; j < width ; j ++ ) {
156+ longValues [ j ] = accumulated [ j ] ;
157+ }
158+
159+ yield return _valueFormatter . CreateItem ( longValues . AsSpan ( ) ) ;
107160 }
161+ } finally {
162+ _logger ? . LogOperationFinishedDebug ( OperationName ) ;
163+ }
164+ } else {
165+ int encodedLatitude = 0 ;
166+ int encodedLongitude = 0 ;
167+
168+ try {
169+ while ( position < sequence . Length ) {
170+ cancellationToken . ThrowIfCancellationRequested ( ) ;
108171
109- double decodedLatitude = PolylineEncoding . Denormalize ( encodedLatitude , Options . Precision ) ;
110- double decodedLongitude = PolylineEncoding . Denormalize ( encodedLongitude , Options . Precision ) ;
172+ if ( ! PolylineEncoding . TryReadValue ( ref encodedLatitude , sequence , ref position )
173+ || ! PolylineEncoding . TryReadValue ( ref encodedLongitude , sequence , ref position ) ) {
174+ _logger ? . LogOperationFailedDebug ( OperationName ) ;
175+ _logger ? . LogInvalidPolylineWarning ( position ) ;
111176
112- _logger ? . LogDecodedCoordinateDebug ( decodedLatitude , decodedLongitude , position ) ;
177+ ExceptionGuard . ThrowInvalidPolylineFormat ( position ) ;
178+ }
113179
114- yield return CreateCoordinate ( decodedLatitude , decodedLongitude ) ;
180+ double decodedLatitude = PolylineEncoding . Denormalize ( encodedLatitude , Options . Precision ) ;
181+ double decodedLongitude = PolylineEncoding . Denormalize ( encodedLongitude , Options . Precision ) ;
182+
183+ _logger ? . LogDecodedCoordinateDebug ( decodedLatitude , decodedLongitude , position ) ;
184+
185+ yield return CreateCoordinate ( decodedLatitude , decodedLongitude ) ;
186+ }
187+ } finally {
188+ _logger ? . LogOperationFinishedDebug ( OperationName ) ;
115189 }
116- } finally {
117- _logger ? . LogOperationFinishedDebug ( OperationName ) ;
118190 }
119191 }
120192
@@ -184,8 +256,16 @@ protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? lo
184256 /// <returns>
185257 /// A <see cref="ReadOnlyMemory{T}"/> of <see cref="char"/> representing the encoded polyline characters.
186258 /// </returns>
259+ /// <exception cref="NotSupportedException">
260+ /// Thrown by the default implementation when no polyline formatter is registered and the method
261+ /// has not been overridden in a derived class.
262+ /// </exception>
187263 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
188- protected abstract ReadOnlyMemory < char > GetReadOnlyMemory ( in TPolyline polyline ) ;
264+ protected virtual ReadOnlyMemory < char > GetReadOnlyMemory ( in TPolyline polyline ) {
265+ throw new NotSupportedException (
266+ $ "Override { nameof ( GetReadOnlyMemory ) } in a derived class, or provide a " +
267+ $ "{ nameof ( PolylineOptions < TPolyline , TCoordinate > ) } with a polyline formatter.") ;
268+ }
189269
190270 /// <summary>
191271 /// Creates a <typeparamref name="TCoordinate"/> instance from the specified latitude and longitude values.
@@ -199,6 +279,14 @@ protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? lo
199279 /// <returns>
200280 /// A <typeparamref name="TCoordinate"/> instance representing the specified geographic coordinate.
201281 /// </returns>
282+ /// <exception cref="NotSupportedException">
283+ /// Thrown by the default implementation when no value formatter is registered and the method
284+ /// has not been overridden in a derived class.
285+ /// </exception>
202286 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
203- protected abstract TCoordinate CreateCoordinate ( double latitude , double longitude ) ;
287+ protected virtual TCoordinate CreateCoordinate ( double latitude , double longitude ) {
288+ throw new NotSupportedException (
289+ $ "Override { nameof ( CreateCoordinate ) } in a derived class, or provide a " +
290+ $ "{ nameof ( PolylineOptions < TPolyline , TCoordinate > ) } with a value formatter.") ;
291+ }
204292}
0 commit comments