Skip to content

Commit 88cacba

Browse files
author
Pete Sramek
committed
refactoring
1 parent 547a267 commit 88cacba

25 files changed

+732
-228
lines changed

PolylineAlgorithm.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<Project Path="benchmarks/PolylineAlgorithm.Comparison.Benchmarks/PolylineAlgorithm.Comparison.Benchmarks.csproj" />
55
</Folder>
66
<Folder Name="/src/">
7+
<Project Path="src/PolylineAlgorithm.Abstraction/PolylineAlgorithm.Abstraction.csproj" Id="a39b8ed4-13da-4ef7-b144-68f0c281474f" />
78
<Project Path="src/PolylineAlgorithm/PolylineAlgorithm.csproj" />
89
</Folder>
910
<Folder Name="/tests/">

benchmarks/PolylineAlgorithm.Benchmarks/PolylineBuilderBenchmark.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ namespace PolylineAlgorithm.Benchmarks;
77

88
using BenchmarkDotNet.Attributes;
99
using PolylineAlgorithm;
10+
using PolylineAlgorithm.Abstraction.Internal;
1011
using PolylineAlgorithm.Internal;
1112
using PolylineAlgorithm.Utility;
13+
using System.Buffers;
1214

1315
/// <summary>
1416
/// Benchmarks for the <see cref="PolylineValue"/> struct.
@@ -46,7 +48,7 @@ public void SetupData() {
4648
/// </summary>
4749
/// <returns>The encoded polyline.</returns>
4850
[Benchmark]
49-
public Polyline PolylineBuilder_Append_Memory() {
51+
public ReadOnlySequence<char> PolylineBuilder_Append_Memory() {
5052
for (int i = 0; i < SegmentsCount; i++) {
5153
Builder
5254
.Append(MemoryValue);

benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace PolylineAlgorithm.Benchmarks;
77

88
using BenchmarkDotNet.Attributes;
99
using PolylineAlgorithm;
10+
using PolylineAlgorithm.Abstraction;
1011
using PolylineAlgorithm.Utility;
1112
using System.Collections.Generic;
1213

src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs renamed to src/PolylineAlgorithm.Abstraction/IPolylineDecoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace PolylineAlgorithm.Abstraction;
1010
/// <summary>
1111
/// Defines a contract for decoding an encoded polyline into a collection of geographic coordinates.
1212
/// </summary>
13-
public interface IPolylineDecoder {
13+
public interface IPolylineDecoder<TCoordinate, TPolyline> {
1414
/// <summary>
1515
/// Decodes the specified encoded polyline into a sequence of geographic coordinates.
1616
/// </summary>
@@ -20,5 +20,5 @@ public interface IPolylineDecoder {
2020
/// <returns>
2121
/// An <see cref="IEnumerable{T}"/> of <see cref="Coordinate"/> representing the decoded latitude and longitude pairs.
2222
/// </returns>
23-
IEnumerable<Coordinate> Decode(Polyline polyline);
23+
IEnumerable<TCoordinate> Decode(TPolyline coordinates);
2424
}

src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs renamed to src/PolylineAlgorithm.Abstraction/IPolylineEncoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace PolylineAlgorithm.Abstraction;
1010
/// <summary>
1111
/// Defines a contract for encoding a collection of geographic coordinates into an encoded polyline string.
1212
/// </summary>
13-
public interface IPolylineEncoder {
13+
public interface IPolylineEncoder<TCoordinate, TPolyline> {
1414
/// <summary>
1515
/// Encodes a sequence of geographic coordinates into an encoded polyline representation.
1616
/// </summary>
@@ -20,5 +20,5 @@ public interface IPolylineEncoder {
2020
/// <returns>
2121
/// A <see cref="Polyline"/> containing the encoded polyline string that represents the input coordinates.
2222
/// </returns>
23-
Polyline Encode(IEnumerable<Coordinate> coordinates);
23+
TPolyline Encode(IEnumerable<TCoordinate> coordinates);
2424
}

src/PolylineAlgorithm/Internal/CoordinateVariance.cs renamed to src/PolylineAlgorithm.Abstraction/Internal/CoordinateVariance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
55

6-
namespace PolylineAlgorithm.Internal;
6+
namespace PolylineAlgorithm.Abstraction.Internal;
77

88
using System;
99
using System.Diagnostics;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.Abstraction.Internal;
7+
using System.Diagnostics.CodeAnalysis;
8+
9+
/// <summary>
10+
/// Provides default values and constants used throughout the Polyline Algorithm.
11+
/// Organizes defaults for algorithm parameters, polyline encoding, and geographic coordinates into nested static classes.
12+
/// </summary>
13+
[ExcludeFromCodeCoverage]
14+
internal static class Defaults {
15+
/// <summary>
16+
/// Contains default values and constants specific to the polyline encoding algorithm.
17+
/// </summary>
18+
public static class Algorithm {
19+
/// <summary>
20+
/// The precision factor used to round coordinate values during polyline encoding.
21+
/// </summary>
22+
public const int Precision = 100_000;
23+
24+
/// <summary>
25+
/// The number of bits to shift during polyline encoding.
26+
/// </summary>
27+
public const byte ShiftLength = 5;
28+
29+
/// <summary>
30+
/// The ASCII value for the question mark character ('?').
31+
/// </summary>
32+
public const byte QuestionMark = 63;
33+
34+
/// <summary>
35+
/// The ASCII value for the space character (' ').
36+
/// </summary>
37+
public const byte Space = 32;
38+
39+
/// <summary>
40+
/// The ASCII value for the unit separator character.
41+
/// </summary>
42+
public const byte UnitSeparator = 31;
43+
}
44+
45+
/// <summary>
46+
/// Contains default values and constants related to polyline encoding.
47+
/// </summary>
48+
public static class Polyline {
49+
/// <summary>
50+
/// An array of delimiter byte values used in polyline encoding, derived by adding the ASCII value of the question mark ('?') to a range of integers.
51+
/// </summary>
52+
public static readonly byte[] Delimiters = [.. Enumerable.Range(0, 32).Select(n => (byte)(n + Algorithm.QuestionMark))];
53+
54+
/// <summary>
55+
/// The minimum number of characters required to represent an encoded coordinate.
56+
/// </summary>
57+
public const int MinEncodedCoordinateLength = 2;
58+
59+
/// <summary>
60+
/// The maximum number of characters allowed to represent an encoded coordinate.
61+
/// </summary>
62+
public const int MaxEncodedCoordinateLength = 12;
63+
}
64+
}

src/PolylineAlgorithm/Internal/PolylineBuilder.cs renamed to src/PolylineAlgorithm.Abstraction/Internal/PolylineBuilder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
55

6-
namespace PolylineAlgorithm.Internal;
6+
namespace PolylineAlgorithm.Abstraction.Internal;
77

88
using System;
9+
using System.Buffers;
910
using System.Runtime.InteropServices;
1011

1112
/// <summary>
@@ -42,11 +43,11 @@ public void Append(ReadOnlyMemory<char> value) {
4243
/// A <see cref="Polyline"/> representing the combined character segments.
4344
/// If no segments have been appended, returns an empty <see cref="Polyline"/>.
4445
/// </returns>
45-
public readonly Polyline Build() {
46+
public readonly ReadOnlySequence<char> Build() {
4647
if (_initial is null) {
47-
return Polyline.FromMemory(ReadOnlyMemory<char>.Empty);
48+
return new();
4849
}
4950

50-
return Polyline.FromSequence(new(_initial, 0, _last, _last!.Memory.Length));
51+
return new(_initial, 0, _last, _last!.Memory.Length);
5152
}
5253
}

src/PolylineAlgorithm/Internal/PolylineSegment.cs renamed to src/PolylineAlgorithm.Abstraction/Internal/PolylineSegment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
55

6-
namespace PolylineAlgorithm.Internal;
6+
namespace PolylineAlgorithm.Abstraction.Internal;
77

88
using System.Buffers;
99

src/PolylineAlgorithm/InvalidPolylineException.cs renamed to src/PolylineAlgorithm.Abstraction/InvalidPolylineException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
55

6-
namespace PolylineAlgorithm;
6+
namespace PolylineAlgorithm.Abstraction;
77

8-
using PolylineAlgorithm.Properties;
8+
using PolylineAlgorithm.Abstraction.Properties;
99
using System;
1010
using System.Diagnostics;
1111
using System.Diagnostics.CodeAnalysis;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<LangVersion>13.0</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<InvariantGlobalization>true</InvariantGlobalization>
9+
<NeutralLanguage>en</NeutralLanguage>
10+
</PropertyGroup>
11+
12+
<PropertyGroup>
13+
<AnalysisMode>All</AnalysisMode>
14+
<AnalysisLevel>latest</AnalysisLevel>
15+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
16+
<EnableNETAnalyzers>true</EnableNETAnalyzers>
17+
</PropertyGroup>
18+
19+
<PropertyGroup>
20+
<GenerateDocumentationFile>True</GenerateDocumentationFile>
21+
</PropertyGroup>
22+
23+
<PropertyGroup>
24+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
25+
</PropertyGroup>
26+
27+
<PropertyGroup>
28+
<DebugType>pdbonly</DebugType>
29+
<IncludeSymbols>true</IncludeSymbols>
30+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
31+
</PropertyGroup>
32+
33+
<PropertyGroup>
34+
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
35+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
36+
<Title>PolylineAlgorithm.Abstraction for .NET</Title>
37+
<PackageReadmeFile>README.md</PackageReadmeFile>
38+
<PackageTags>google;polyline;algorithm;maps;encoding;</PackageTags>
39+
<Copyright>© 2025, Pete Sramek.</Copyright>
40+
</PropertyGroup>
41+
42+
<ItemGroup>
43+
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.*">
44+
<PrivateAssets>all</PrivateAssets>
45+
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
46+
</PackageReference>
47+
</ItemGroup>
48+
49+
<ItemGroup>
50+
<Compile Update="Properties\ExceptionMessageResource.Designer.cs">
51+
<DependentUpon>ExceptionMessageResource.resx</DependentUpon>
52+
<DesignTime>True</DesignTime>
53+
<AutoGen>True</AutoGen>
54+
</Compile>
55+
</ItemGroup>
56+
57+
<ItemGroup>
58+
<EmbeddedResource Update="Properties\ExceptionMessageResource.resx">
59+
<LastGenOutput>ExceptionMessageResource.Designer.cs</LastGenOutput>
60+
<Generator>ResXFileCodeGenerator</Generator>
61+
</EmbeddedResource>
62+
</ItemGroup>
63+
64+
<ItemGroup>
65+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
66+
<_Parameter1>PolylineAlgorithm.Tests</_Parameter1>
67+
</AssemblyAttribute>
68+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
69+
<_Parameter1>PolylineAlgorithm.Benchmarks</_Parameter1>
70+
</AssemblyAttribute>
71+
</ItemGroup>
72+
73+
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.Abstraction;
7+
8+
using PolylineAlgorithm.Abstraction.Internal;
9+
using PolylineAlgorithm.Abstraction.Properties;
10+
using System;
11+
using System.Buffers;
12+
13+
/// <summary>
14+
/// Decodes encoded polyline strings into sequences of geographic coordinates.
15+
/// Implements the <see cref="IPolylineDecoder"/> interface.
16+
/// </summary>
17+
public abstract class PolylineDecoder<TCoordinate, TPolyline> : IPolylineDecoder<TCoordinate, TPolyline> {
18+
/// <summary>
19+
/// Decodes an encoded <see cref="Polyline"/> into a sequence of <see cref="Coordinate"/> instances.
20+
/// </summary>
21+
/// <param name="polyline">
22+
/// The <see cref="Polyline"/> instance containing the encoded polyline string to decode.
23+
/// </param>
24+
/// <returns>
25+
/// An <see cref="IEnumerable{T}"/> of <see cref="Coordinate"/> representing the decoded latitude and longitude pairs.
26+
/// </returns>
27+
/// <exception cref="ArgumentException">
28+
/// Thrown when <paramref name="polyline"/> is empty.
29+
/// </exception>
30+
/// <exception cref="InvalidPolylineException">
31+
/// Thrown when the polyline format is invalid or malformed at a specific position.
32+
/// </exception>
33+
public IEnumerable<TCoordinate> Decode(TPolyline polyline) {
34+
if (polyline is null) {
35+
throw new ArgumentNullException(nameof(polyline));
36+
}
37+
38+
ReadOnlySequence<char> source = GetReadOnlySequence(polyline);
39+
40+
if (source.Length < Defaults.Polyline.MinEncodedCoordinateLength) {
41+
throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullEmptyOrWhitespaceMessage, nameof(polyline));
42+
}
43+
44+
ReadOnlySequence<char>.Enumerator enumerator = source.GetEnumerator();
45+
46+
int consumed = 0;
47+
int latitude = 0;
48+
int longitude = 0;
49+
50+
int position;
51+
ReadOnlyMemory<char> sequence;
52+
53+
while (enumerator.MoveNext()) {
54+
position = 0;
55+
sequence = enumerator.Current;
56+
57+
while (PolylineEncoding.Default.TryReadValue(ref latitude, ref sequence, ref position)
58+
&& PolylineEncoding.Default.TryReadValue(ref longitude, ref sequence, ref position)
59+
) {
60+
yield return CreateCoordinate(PolylineEncoding.Default.Denormalize(latitude), PolylineEncoding.Default.Denormalize(longitude));
61+
}
62+
63+
consumed += position;
64+
65+
if (sequence.Length != position) {
66+
InvalidPolylineException.Throw(consumed);
67+
}
68+
}
69+
}
70+
71+
protected abstract ReadOnlySequence<char> GetReadOnlySequence(TPolyline? polyline);
72+
73+
protected abstract TCoordinate CreateCoordinate(double latitude, double longitude);
74+
}

0 commit comments

Comments
 (0)