-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAbstractPolylineDecoder.cs
More file actions
227 lines (201 loc) · 10.9 KB
/
AbstractPolylineDecoder.cs
File metadata and controls
227 lines (201 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//
// Copyright © Pete Sramek. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
using Microsoft.Extensions.Logging;
using PolylineAlgorithm.Diagnostics;
using PolylineAlgorithm.Internal;
using PolylineAlgorithm.Internal.Diagnostics;
using PolylineAlgorithm.Internal.Diagnostics;
using System.Runtime.CompilerServices;
namespace PolylineAlgorithm.Abstraction {
/// <summary>
/// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates.
/// </summary>
/// <remarks>
/// Derive from this class to implement a decoder for a specific polyline type. Override <see cref="GetReadOnlyMemory"/>
/// and <see cref="CreateCoordinate"/> to provide type-specific behavior.
/// </remarks>
/// <typeparam name="TPolyline">The type that represents the encoded polyline input.</typeparam>
/// <typeparam name="TCoordinate">The type that represents a decoded geographic coordinate.</typeparam>
public abstract class AbstractPolylineDecoder<TPolyline, TCoordinate> : IPolylineDecoder<TPolyline, TCoordinate> {
private readonly ILogger<AbstractPolylineDecoder<TPolyline, TCoordinate>> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="AbstractPolylineDecoder{TPolyline, TCoordinate}"/> class with default encoding options.
/// </summary>
protected AbstractPolylineDecoder()
: this(new PolylineEncodingOptions()) { }
/// <summary>
/// Initializes a new instance of the <see cref="AbstractPolylineDecoder{TPolyline, TCoordinate}"/> class with the specified encoding options.
/// </summary>
/// <param name="options">
/// The <see cref="PolylineEncodingOptions"/> to use for encoding operations.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="options"/> is <see langword="null"/>.
/// </exception>
protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
if (options is null) {
ExceptionGuard.ThrowArgumentNull(nameof(options));
}
Options = options;
_logger = Options
.LoggerFactory
.CreateLogger<AbstractPolylineDecoder<TPolyline, TCoordinate>>();
}
/// <summary>
/// Gets the encoding options used by this polyline decoder.
/// </summary>
public PolylineEncodingOptions Options { get; }
/// <summary>
/// Decodes an encoded <typeparamref name="TPolyline"/> into a sequence of <typeparamref name="TCoordinate"/> instances.
/// </summary>
/// <param name="polyline">
/// The <typeparamref name="TPolyline"/> instance containing the encoded polyline string to decode.
/// </param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded latitude and longitude pairs.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="polyline"/> is empty.
/// </exception>
/// <exception cref="InvalidPolylineException">
/// Thrown when the polyline format is invalid or malformed at a specific position.
/// </exception>
public IEnumerable<TCoordinate> Decode(TPolyline polyline)
=> Decode(polyline, CancellationToken.None);
/// <summary>
/// Decodes an encoded <typeparamref name="TPolyline"/> into a sequence of <typeparamref name="TCoordinate"/> instances,
/// with support for cancellation.
/// </summary>
/// <param name="polyline">
/// The <typeparamref name="TPolyline"/> instance containing the encoded polyline string to decode.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> that can be used to cancel the decoding operation.
/// </param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded latitude and longitude pairs.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="polyline"/> is empty.
/// </exception>
/// <exception cref="InvalidPolylineException">
/// Thrown when the polyline format is invalid or malformed at a specific position.
/// </exception>
/// <exception cref="OperationCanceledException">
/// Thrown when <paramref name="cancellationToken"/> is canceled during decoding.
/// </exception>
public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken cancellationToken) {
const string OperationName = nameof(Decode);
_logger?.LogOperationStartedDebug(OperationName);
ValidateNullPolyline(polyline, _logger);
ReadOnlyMemory<char> sequence = GetReadOnlyMemory(in polyline);
ValidateSequence(sequence, _logger);
ValidateFormat(sequence, _logger);
int position = 0;
int encodedLatitude = 0;
int encodedLongitude = 0;
try {
while (position < sequence.Length) {
cancellationToken.ThrowIfCancellationRequested();
if (!PolylineEncoding.TryReadValue(ref encodedLatitude, sequence, ref position)
|| !PolylineEncoding.TryReadValue(ref encodedLongitude, sequence, ref position)) {
_logger?.LogOperationFailedDebug(OperationName);
_logger?.LogInvalidPolylineWarning(position);
ExceptionGuard.ThrowInvalidPolylineFormat(position);
}
double decodedLatitude = PolylineEncoding.Denormalize(encodedLatitude, Options.Precision);
double decodedLongitude = PolylineEncoding.Denormalize(encodedLongitude, Options.Precision);
_logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, position);
yield return CreateCoordinate(decodedLatitude, decodedLongitude);
}
} finally {
_logger?.LogOperationFinishedDebug(OperationName);
}
}
/// <summary>
/// Validates that the provided polyline is not <see langword="null"/>.
/// </summary>
/// <param name="polyline">The polyline instance to validate.</param>
/// <param name="logger">An optional <see cref="ILogger"/> used to log a warning when validation fails.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateNullPolyline(TPolyline polyline, ILogger? logger) {
if (polyline is null) {
logger?.LogNullArgumentWarning(nameof(polyline));
ExceptionGuard.ThrowArgumentNull(nameof(polyline));
}
}
/// <summary>
/// Validates that the polyline character sequence meets the minimum required length.
/// </summary>
/// <param name="polylineSequence">The polyline character sequence to validate.</param>
/// <param name="logger">An optional <see cref="ILogger"/> used to log diagnostic messages when validation fails.</param>
/// <exception cref="InvalidPolylineException">
/// Thrown when <paramref name="polylineSequence"/> is shorter than the minimum allowed length.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateSequence(ReadOnlyMemory<char> polylineSequence, ILogger? logger) {
if (polylineSequence.Length < Defaults.Polyline.Block.Length.Min) {
logger?.LogOperationFailedDebug(nameof(Decode));
logger?.LogPolylineCannotBeShorterThanWarning(polylineSequence.Length, Defaults.Polyline.Block.Length.Min);
ExceptionGuard.ThrowInvalidPolylineLength(polylineSequence.Length, Defaults.Polyline.Block.Length.Min);
}
}
/// <summary>
/// Validates the format of the polyline character sequence, ensuring all characters are within the allowed range.
/// </summary>
/// <param name="sequence">
/// The read-only memory region of characters representing the polyline to validate.
/// </param>
/// <param name="logger">
/// An optional <see cref="ILogger"/> used to log a warning when format validation fails.
/// </param>
/// <exception cref="ArgumentException">
/// Thrown when the polyline contains characters outside the valid encoding range or has an invalid block structure.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? logger) {
try {
PolylineEncoding.ValidateFormat(sequence.Span);
} catch (ArgumentException ex) {
logger?.LogInvalidPolylineFormatWarning(ex);
throw;
}
}
/// <summary>
/// Extracts the underlying read-only memory region of characters from the specified polyline instance.
/// </summary>
/// <param name="polyline">
/// The <typeparamref name="TPolyline"/> instance from which to extract the character sequence.
/// </param>
/// <returns>
/// A <see cref="ReadOnlyMemory{T}"/> of <see cref="char"/> representing the encoded polyline characters.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract ReadOnlyMemory<char> GetReadOnlyMemory(in TPolyline polyline);
/// <summary>
/// Creates a <typeparamref name="TCoordinate"/> instance from the specified latitude and longitude values.
/// </summary>
/// <param name="latitude">
/// The latitude component of the coordinate, in degrees.
/// </param>
/// <param name="longitude">
/// The longitude component of the coordinate, in degrees.
/// </param>
/// <returns>
/// A <typeparamref name="TCoordinate"/> instance representing the specified geographic coordinate.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract TCoordinate CreateCoordinate(double latitude, double longitude);
}
}