Skip to content

Commit 9660b87

Browse files
committed
Revert "refactor: replace fixed lat/lon pair contract with N-value GetValues/CreateItem/ValuesPerItem"
This reverts commit a94f7c7.
1 parent a5ee061 commit 9660b87

16 files changed

Lines changed: 206 additions & 349 deletions

File tree

benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,8 @@ public void PolylineDecoder_Decode_Memory() {
9393
}
9494

9595
private sealed class StringPolylineDecoder : AbstractPolylineDecoder<string, (double Latitude, double Longitude)> {
96-
protected override int ValuesPerItem => 2;
97-
protected override (double Latitude, double Longitude) CreateItem(ReadOnlyMemory<double> values) {
98-
ReadOnlySpan<double> span = values.Span;
99-
return (span[0], span[1]);
96+
protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) {
97+
return (latitude, longitude);
10098
}
10199

102100
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in string polyline) {
@@ -105,10 +103,8 @@ protected override ReadOnlyMemory<char> GetReadOnlyMemory(in string polyline) {
105103
}
106104

107105
private sealed class CharArrayPolylineDecoder : AbstractPolylineDecoder<char[], (double Latitude, double Longitude)> {
108-
protected override int ValuesPerItem => 2;
109-
protected override (double Latitude, double Longitude) CreateItem(ReadOnlyMemory<double> values) {
110-
ReadOnlySpan<double> span = values.Span;
111-
return (span[0], span[1]);
106+
protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) {
107+
return (latitude, longitude);
112108
}
113109

114110
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in char[] polyline) {
@@ -117,10 +113,8 @@ protected override ReadOnlyMemory<char> GetReadOnlyMemory(in char[] polyline) {
117113
}
118114

119115
private sealed class MemoryCharPolylineDecoder : AbstractPolylineDecoder<ReadOnlyMemory<char>, (double Latitude, double Longitude)> {
120-
protected override int ValuesPerItem => 2;
121-
protected override (double Latitude, double Longitude) CreateItem(ReadOnlyMemory<double> values) {
122-
ReadOnlySpan<double> span = values.Span;
123-
return (span[0], span[1]);
116+
protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) {
117+
return (latitude, longitude);
124118
}
125119

126120
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in ReadOnlyMemory<char> polyline) {

benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,8 @@ public void PolylineEncoder_Encode_List() {
8585
}
8686

8787
private sealed class StringPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> {
88-
protected override int ValuesPerItem => 2;
8988
protected override string CreatePolyline(ReadOnlyMemory<char> polyline) => polyline.ToString();
90-
protected override void GetValues((double Latitude, double Longitude) item, Span<double> destination) {
91-
destination[0] = item.Latitude;
92-
destination[1] = item.Longitude;
93-
}
89+
protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude;
90+
protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude;
9491
}
9592
}

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,14 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample;
1414
/// </summary>
1515
internal sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder<string, Point> {
1616
/// <summary>
17-
/// Gets the number of encoded values per item: latitude (index 0) and longitude (index 1).
17+
/// Creates a NetTopologySuite point from latitude and longitude.
1818
/// </summary>
19-
protected override int ValuesPerItem => 2;
20-
21-
/// <summary>
22-
/// Creates a NetTopologySuite point from the decoded values.
23-
/// </summary>
24-
/// <param name="values">
25-
/// A memory region containing two values: index 0 is latitude, index 1 is longitude.
26-
/// </param>
19+
/// <param name="latitude">Latitude value.</param>
20+
/// <param name="longitude">Longitude value.</param>
2721
/// <returns>Point instance.</returns>
28-
protected override Point CreateItem(ReadOnlyMemory<double> values) {
29-
ReadOnlySpan<double> span = values.Span;
22+
protected override Point CreateCoordinate(double latitude, double longitude) {
3023
// NetTopologySuite Point: x = longitude, y = latitude
31-
return new Point(span[1], span[0]);
24+
return new Point(longitude, latitude);
3225
}
3326

3427
/// <summary>

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample;
77

88
using global::NetTopologySuite.Geometries;
99
using PolylineAlgorithm.Abstraction;
10-
using System;
1110

1211
/// <summary>
1312
/// Polyline encoder using NetTopologySuite's Point type.
1413
/// </summary>
1514
internal sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder<Point, string> {
16-
/// <summary>
17-
/// Gets the number of values per item: latitude (index 0) and longitude (index 1).
18-
/// </summary>
19-
protected override int ValuesPerItem => 2;
20-
2115
/// <summary>
2216
/// Creates encoded polyline string from memory.
2317
/// </summary>
@@ -32,15 +26,26 @@ protected override string CreatePolyline(ReadOnlyMemory<char> polyline) {
3226
}
3327

3428
/// <summary>
35-
/// Fills destination with latitude (index 0) and longitude (index 1) from the point.
29+
/// Gets latitude from point.
3630
/// </summary>
37-
/// <param name="item">Point instance.</param>
38-
/// <param name="destination">Span of length 2 to fill.</param>
39-
protected override void GetValues(Point item, Span<double> destination) {
40-
ArgumentNullException.ThrowIfNull(item);
41-
42-
// NetTopologySuite Point: Y = latitude, X = longitude
43-
destination[0] = item.Y;
44-
destination[1] = item.X;
31+
/// <param name="current">Point instance.</param>
32+
/// <returns>Latitude value.</returns>
33+
protected override double GetLatitude(Point current) {
34+
ArgumentNullException.ThrowIfNull(current);
35+
36+
// NetTopologySuite Point: Y = latitude
37+
return current.Y;
38+
}
39+
40+
/// <summary>
41+
/// Gets longitude from point.
42+
/// </summary>
43+
/// <param name="current">Point instance.</param>
44+
/// <returns>Longitude value.</returns>
45+
protected override double GetLongitude(Point current) {
46+
ArgumentNullException.ThrowIfNull(current);
47+
48+
// NetTopologySuite Point: X = longitude
49+
return current.X;
4550
}
4651
}

src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs

Lines changed: 21 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,19 @@
66
using Microsoft.Extensions.Logging;
77
using PolylineAlgorithm.Internal;
88
using PolylineAlgorithm.Internal.Diagnostics;
9-
using System.Buffers;
109
using System.Runtime.CompilerServices;
1110

1211
namespace PolylineAlgorithm.Abstraction;
1312

1413
/// <summary>
15-
/// Provides a base implementation for decoding encoded polyline strings into sequences of items.
14+
/// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates.
1615
/// </summary>
1716
/// <remarks>
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>
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.
2719
/// </remarks>
2820
/// <typeparam name="TPolyline">The type that represents the encoded polyline input.</typeparam>
29-
/// <typeparam name="TCoordinate">The type that represents a decoded item.</typeparam>
21+
/// <typeparam name="TCoordinate">The type that represents a decoded geographic coordinate.</typeparam>
3022
public abstract class AbstractPolylineDecoder<TPolyline, TCoordinate> : IPolylineDecoder<TPolyline, TCoordinate> {
3123
private readonly ILogger<AbstractPolylineDecoder<TPolyline, TCoordinate>> _logger;
3224

@@ -61,16 +53,6 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
6153
/// </summary>
6254
public PolylineEncodingOptions Options { get; }
6355

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-
7456
/// <summary>
7557
/// Decodes an encoded <typeparamref name="TPolyline"/> into a sequence of <typeparamref name="TCoordinate"/> instances,
7658
/// with support for cancellation.
@@ -82,7 +64,7 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
8264
/// A <see cref="CancellationToken"/> that can be used to cancel the decoding operation.
8365
/// </param>
8466
/// <returns>
85-
/// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded items.
67+
/// An <see cref="IEnumerable{T}"/> of <typeparamref name="TCoordinate"/> representing the decoded latitude and longitude pairs.
8668
/// </returns>
8769
/// <exception cref="ArgumentNullException">
8870
/// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
@@ -108,45 +90,30 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken can
10890
ValidateSequence(sequence, _logger);
10991
ValidateFormat(sequence, _logger);
11092

111-
int valuesPerItem = ValuesPerItem;
11293
int position = 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-
}
94+
int encodedLatitude = 0;
95+
int encodedLongitude = 0;
11996

12097
try {
12198
while (position < sequence.Length) {
12299
cancellationToken.ThrowIfCancellationRequested();
123100

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) {
101+
if (!PolylineEncoding.TryReadValue(ref encodedLatitude, sequence, ref position)
102+
|| !PolylineEncoding.TryReadValue(ref encodedLongitude, sequence, ref position)) {
133103
_logger?.LogOperationFailedDebug(OperationName);
134104
_logger?.LogInvalidPolylineWarning(position);
135105

136106
ExceptionGuard.ThrowInvalidPolylineFormat(position);
137107
}
138108

139-
double[] decoded = new double[valuesPerItem];
140-
for (int j = 0; j < valuesPerItem; j++) {
141-
decoded[j] = PolylineEncoding.Denormalize(runningRent[j], Options.Precision);
142-
}
109+
double decodedLatitude = PolylineEncoding.Denormalize(encodedLatitude, Options.Precision);
110+
double decodedLongitude = PolylineEncoding.Denormalize(encodedLongitude, Options.Precision);
143111

144-
_logger?.LogDecodedValuesDebug(valuesPerItem, position);
112+
_logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, position);
145113

146-
yield return CreateItem(decoded.AsMemory());
114+
yield return CreateCoordinate(decodedLatitude, decodedLongitude);
147115
}
148116
} finally {
149-
ArrayPool<int>.Shared.Return(runningRent!);
150117
_logger?.LogOperationFinishedDebug(OperationName);
151118
}
152119
}
@@ -221,19 +188,17 @@ protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? lo
221188
protected abstract ReadOnlyMemory<char> GetReadOnlyMemory(in TPolyline polyline);
222189

223190
/// <summary>
224-
/// Creates a <typeparamref name="TCoordinate"/> instance from the specified decoded values.
191+
/// Creates a <typeparamref name="TCoordinate"/> instance from the specified latitude and longitude values.
225192
/// </summary>
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.
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.
229198
/// </param>
230199
/// <returns>
231-
/// A <typeparamref name="TCoordinate"/> instance representing the decoded item.
200+
/// A <typeparamref name="TCoordinate"/> instance representing the specified geographic coordinate.
232201
/// </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>
237202
[MethodImpl(MethodImplOptions.AggressiveInlining)]
238-
protected abstract TCoordinate CreateItem(ReadOnlyMemory<double> values);
203+
protected abstract TCoordinate CreateCoordinate(double latitude, double longitude);
239204
}

0 commit comments

Comments
 (0)