diff --git a/README.md b/README.md index 0555c16f..5c6802f3 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,12 @@ Google's Encoded Polyline Algorithm compresses sequences of geographic coordinat ## Features - Fully compliant Google Encoded Polyline Algorithm for .NET Standard 2.1+ -- Extensible APIs — implement your own encoder/decoder for any coordinate or polyline type -- Robust input validation with descriptive exceptions for malformed or out-of-range data -- Advanced configuration via `PolylineEncodingOptions` (precision, buffer size, logging) +- Fully fluent `FormatterBuilder` — configure coordinate fields, factories, and polyline I/O in one chain +- Sealed, immutable `PolylineFormatter` produced by the builder +- Type-safe `PolylineEncoder` and `PolylineDecoder` with no inheritance required +- `PolylineOptions` for stack-alloc limits and optional logging - Extension methods for encoding directly from `List` and arrays +- Robust input validation with descriptive exceptions for malformed or out-of-range data - Logging and diagnostic support via `Microsoft.Extensions.Logging` - Low-level utilities for normalization, validation, and bit-level operations via static `PolylineEncoding` class - Thread-safe, stateless APIs @@ -48,75 +50,101 @@ Install-Package PolylineAlgorithm ## Usage -The library provides abstract base classes to implement your own encoder and decoder for any coordinate and polyline type. Inherit from `AbstractPolylineEncoder` or `AbstractPolylineDecoder`, override the coordinate accessors, then call `Encode` or `Decode`. +The library uses a fluent `FormatterBuilder` to describe how to map between your coordinate type and a polyline type — no inheritance required. Build a `PolylineFormatter`, wrap it in `PolylineOptions`, then instantiate `PolylineEncoder` and `PolylineDecoder`. ### Quick Start ```csharp -// 1. Implement a minimal encoder (see full example below) -var encoder = new MyPolylineEncoder(); -string encoded = encoder.Encode(coordinates); // e.g. "yseiHoc_MwacOjnwM" +using PolylineAlgorithm; + +// 1. Build a formatter that maps (double Lat, double Lon) ↔ string polyline +PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", static c => c.Lat) + .AddValue("lon", static c => c.Lon) + .WithCreate(static v => (v[0], v[1])) + .ForPolyline(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); + +PolylineOptions<(double Lat, double Lon), string> options = new(formatter); + +PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); +PolylineDecoder decoder = new(options); + +// 2. Encode +var coordinates = new List<(double, double)> { (48.858370, 2.294481), (51.500729, -0.124625) }; +string encoded = encoder.Encode(coordinates); // extension method for List +// Output: "yseiHoc_MwacOjnwM" -// 2. Implement a minimal decoder (see full example below) -var decoder = new MyPolylineDecoder(); -IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); +// 3. Decode +IEnumerable<(double Lat, double Lon)> decoded = decoder.Decode(encoded); ``` -### Custom encoder and decoder +### Building a formatter -#### Encoding +`FormatterBuilder` configures how the library reads and writes your types: -Custom encoder implementation. +| Method | Purpose | +|---|---| +| `FormatterBuilder.Create()` | Static factory to start building | +| `.AddValue(name, selector, precision=5)` | Register a coordinate field (latitude, longitude, …) | +| `.SetBaseline(long)` | Override the encoding baseline (optional) | +| `.WithCreate(factory)` | Factory delegate `PolylineItemFactory`: `TC(ReadOnlySpan values)` — required for decoding | +| `.ForPolyline(write, read)` | How to convert `ReadOnlyMemory` → `TP` and `TP` → `ReadOnlyMemory` | +| `.Build()` | Returns an immutable `PolylineFormatter` | ```csharp -using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; - -public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - protected override double GetLatitude((double Latitude, double Longitude) coordinate) => coordinate.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) coordinate) => coordinate.Longitude; - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); -} +PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", static c => c.Lat) + .AddValue("lon", static c => c.Lon) + .WithCreate(static v => (v[0], v[1])) + .ForPolyline(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); ``` -Custom encoder usage. +### Encoding ```csharp +using PolylineAlgorithm; using PolylineAlgorithm.Extensions; -var coordinates = new List<(double Latitude, double Longitude)> +PolylineOptions<(double Lat, double Lon), string> options = new(formatter); +PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + +var coordinates = new List<(double Lat, double Lon)> { (48.858370, 2.294481), (51.500729, -0.124625) }; -var encoder = new MyPolylineEncoder(); string encoded = encoder.Encode(coordinates); // extension method for List - -Console.WriteLine(encoded); +Console.WriteLine(encoded); // yseiHoc_MwacOjnwM ``` -#### Decoding - -Custom decoder implementation. +### Decoding ```csharp using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; -public sealed class MyPolylineDecoder : AbstractPolylineDecoder { - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); -} +PolylineOptions<(double Lat, double Lon), string> options = new(formatter); +PolylineDecoder decoder = new(options); + +IEnumerable<(double Lat, double Lon)> decoded = decoder.Decode("yseiHoc_MwacOjnwM"); ``` -Custom decoder usage. +### Advanced options (logging, stack-alloc limit) ```csharp -string encoded = "yseiHoc_MwacOjnwM"; +using Microsoft.Extensions.Logging; + +PolylineOptions<(double Lat, double Lon), string> options = new( + formatter, + stackAllocLimit: 1024, + loggerFactory: loggerFactory); -var decoder = new MyPolylineDecoder(); -IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); +var encoder = new PolylineEncoder<(double Lat, double Lon), string>(options); +var decoder = new PolylineDecoder(options); ``` > **Note:** @@ -148,7 +176,7 @@ A: All platforms supporting `netstandard2.1` (including .NET Core and .NET 5+). A: The decoder will throw descriptive exceptions (`InvalidPolylineException`) for malformed polyline strings. Check exception handling in your application. **Q: How do I customize encoding options (e.g., precision, buffer size, logging)?** -A: Use `PolylineEncodingOptionsBuilder` to set custom options and pass the built `PolylineEncodingOptions` to the encoder or decoder constructor. +A: Pass a `PolylineOptions` to the encoder/decoder constructor. Set `stackAllocLimit` to control buffer size and `loggerFactory` for logging. Precision is set per-field via `.AddValue(name, selector, precision)` on the `FormatterBuilder`. **Q: Is the library thread-safe?** A: Yes, the main encoding and decoding APIs are stateless and thread-safe. If using mutable shared resources, manage synchronization in your code. @@ -160,7 +188,7 @@ A: Yes! Any environment supporting `netstandard2.1` can use this library. A: Open a GitHub issue using the provided templates in the repository and tag @petesramek. **Q: Is there support for elevation, time stamps, or third coordinate values?** -A: Not currently, not planned to be added, but you can extend by implementing your own encoder/decoder using `PolylineEncoding` class methods. +A: Not currently, not planned to be added, but you can extend by adding extra `.AddValue(...)` calls in your `FormatterBuilder` and using `PolylineEncoding` class methods for low-level operations. **Q: How do I contribute documentation improvements?** A: Update XML doc comments in the codebase and submit a PR; all public APIs require XML documentation. To improve guides, update the relevant markdown file in the `/api-reference/guide` folder. diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml deleted file mode 100644 index b5693066..00000000 --- a/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml +++ /dev/null @@ -1,233 +0,0 @@ -### YamlMime:ApiPage -title: Class AbstractPolylineDecoder -body: -- api1: Class AbstractPolylineDecoder - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L22 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 - commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 -- facts: - - name: Namespace - value: - text: PolylineAlgorithm.Abstraction - url: PolylineAlgorithm.Abstraction.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. -- code: 'public abstract class AbstractPolylineDecoder : IPolylineDecoder' -- h4: Type Parameters -- parameters: - - name: TPolyline - description: The type that represents the encoded polyline input. - - name: TCoordinate - description: The type that represents a decoded geographic coordinate. -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: AbstractPolylineDecoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html -- h4: Implements -- list: - - text: IPolylineDecoder - url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html -- h4: Inherited Members -- list: - - text: object.Equals(object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - - text: object.Equals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) - - text: object.GetHashCode() - url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode - - text: object.GetType() - url: https://learn.microsoft.com/dotnet/api/system.object.gettype - - text: object.MemberwiseClone() - url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone - - text: object.ReferenceEquals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals - - text: object.ToString() - url: https://learn.microsoft.com/dotnet/api/system.object.tostring -- h2: Remarks -- markdown: >- - Derive from this class to implement a decoder for a specific polyline type. Override - - and to provide type-specific behavior. -- h2: Constructors -- api3: AbstractPolylineDecoder() - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L28 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor -- markdown: Initializes a new instance of the class with default encoding options. -- code: protected AbstractPolylineDecoder() -- api3: AbstractPolylineDecoder(PolylineEncodingOptions) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L40 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) -- markdown: Initializes a new instance of the class with the specified encoding options. -- code: protected AbstractPolylineDecoder(PolylineEncodingOptions options) -- h4: Parameters -- parameters: - - name: options - type: - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html - description: The to use for encoding operations. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when options is null. -- h2: Properties -- api3: Options - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Options - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L54 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Options - commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Options -- markdown: Gets the encoding options used by this polyline decoder. -- code: public PolylineEncodingOptions Options { get; } -- h4: Property Value -- parameters: - - type: - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html -- h2: Methods -- api3: CreateCoordinate(double, double) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_CreateCoordinate_System_Double_System_Double_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L202 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.CreateCoordinate(System.Double,System.Double) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.CreateCoordinate(System.Double,System.Double) -- markdown: Creates a TCoordinate instance from the specified latitude and longitude values. -- code: protected abstract TCoordinate CreateCoordinate(double latitude, double longitude) -- h4: Parameters -- parameters: - - name: latitude - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The latitude component of the coordinate, in degrees. - - name: longitude - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The longitude component of the coordinate, in degrees. -- h4: Returns -- parameters: - - type: - - TCoordinate - description: A TCoordinate instance representing the specified geographic coordinate. -- api3: Decode(TPolyline, CancellationToken) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L81 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) -- markdown: >- - Decodes an encoded TPolyline into a sequence of TCoordinate instances, - - with support for cancellation. -- code: public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) -- h4: Parameters -- parameters: - - name: polyline - type: - - TPolyline - description: The TPolyline instance containing the encoded polyline string to decode. - - name: cancellationToken - type: - - text: CancellationToken - url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken - description: A that can be used to cancel the decoding operation. - optional: true -- h4: Returns -- parameters: - - type: - - text: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 - - < - - TCoordinate - - '>' - description: An of TCoordinate representing the decoded latitude and longitude pairs. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when polyline is null. - - type: - - text: ArgumentException - url: https://learn.microsoft.com/dotnet/api/system.argumentexception - description: Thrown when polyline is empty. - - type: - - text: InvalidPolylineException - url: PolylineAlgorithm.InvalidPolylineException.html - description: Thrown when the polyline format is invalid or malformed at a specific position. - - type: - - text: OperationCanceledException - url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception - description: Thrown when cancellationToken is canceled during decoding. -- api3: GetReadOnlyMemory(in TPolyline) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_GetReadOnlyMemory__0__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L187 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) -- markdown: Extracts the underlying read-only memory region of characters from the specified polyline instance. -- code: protected abstract ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline) -- h4: Parameters -- parameters: - - name: polyline - type: - - TPolyline - description: The TPolyline instance from which to extract the character sequence. -- h4: Returns -- parameters: - - type: - - text: ReadOnlyMemory - url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 - - < - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '>' - description: A of representing the encoded polyline characters. -- api3: ValidateFormat(ReadOnlyMemory, ILogger?) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_ValidateFormat_System_ReadOnlyMemory_System_Char__Microsoft_Extensions_Logging_ILogger_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L167 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.ValidateFormat(System.ReadOnlyMemory{System.Char},Microsoft.Extensions.Logging.ILogger) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.ValidateFormat(System.ReadOnlyMemory{System.Char},Microsoft.Extensions.Logging.ILogger) -- markdown: Validates the format of the polyline character sequence, ensuring all characters are within the allowed range. -- code: protected virtual void ValidateFormat(ReadOnlyMemory sequence, ILogger? logger) -- h4: Parameters -- parameters: - - name: sequence - type: - - text: ReadOnlyMemory - url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 - - < - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '>' - description: The read-only memory region of characters representing the polyline to validate. - - name: logger - type: - - text: ILogger - url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger - - '?' - description: An optional used to log a warning when format validation fails. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentException - url: https://learn.microsoft.com/dotnet/api/system.argumentexception - description: Thrown when the polyline contains characters outside the valid encoding range or has an invalid block structure. -languageId: csharp -metadata: - description: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml deleted file mode 100644 index 64e97b11..00000000 --- a/api-reference/0.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml +++ /dev/null @@ -1,219 +0,0 @@ -### YamlMime:ApiPage -title: Class AbstractPolylineEncoder -body: -- api1: Class AbstractPolylineEncoder - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L27 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 - commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 -- facts: - - name: Namespace - value: - text: PolylineAlgorithm.Abstraction - url: PolylineAlgorithm.Abstraction.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. -- code: 'public abstract class AbstractPolylineEncoder : IPolylineEncoder' -- h4: Type Parameters -- parameters: - - name: TCoordinate - description: The type that represents a geographic coordinate to encode. - - name: TPolyline - description: The type that represents the encoded polyline output. -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: AbstractPolylineEncoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html -- h4: Implements -- list: - - text: IPolylineEncoder - url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html -- h4: Inherited Members -- list: - - text: object.Equals(object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - - text: object.Equals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) - - text: object.GetHashCode() - url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode - - text: object.GetType() - url: https://learn.microsoft.com/dotnet/api/system.object.gettype - - text: object.MemberwiseClone() - url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone - - text: object.ReferenceEquals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals - - text: object.ToString() - url: https://learn.microsoft.com/dotnet/api/system.object.tostring -- h4: Extension Methods -- list: - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, List) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TCoordinate[]) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ -- h2: Remarks -- markdown: >- - Derive from this class to implement an encoder for a specific coordinate and polyline type. Override - - , , and to provide type-specific behavior. -- h2: Constructors -- api3: AbstractPolylineEncoder() - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L33 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor -- markdown: Initializes a new instance of the class with default encoding options. -- code: protected AbstractPolylineEncoder() -- api3: AbstractPolylineEncoder(PolylineEncodingOptions) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L43 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) -- markdown: Initializes a new instance of the class with the specified encoding options. -- code: protected AbstractPolylineEncoder(PolylineEncodingOptions options) -- h4: Parameters -- parameters: - - name: options - type: - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html - description: The to use for encoding operations. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when options is null -- h2: Properties -- api3: Options - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Options - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L57 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options - commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options -- markdown: Gets the encoding options used by this polyline encoder. -- code: public PolylineEncodingOptions Options { get; } -- h4: Property Value -- parameters: - - type: - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html -- h2: Methods -- api3: CreatePolyline(ReadOnlyMemory) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_CreatePolyline_System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L174 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) -- markdown: Creates a polyline instance from the provided read-only sequence of characters. -- code: protected abstract TPolyline CreatePolyline(ReadOnlyMemory polyline) -- h4: Parameters -- parameters: - - name: polyline - type: - - text: ReadOnlyMemory - url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 - - < - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '>' - description: A containing the encoded polyline characters. -- h4: Returns -- parameters: - - type: - - TPolyline - description: An instance of TPolyline representing the encoded polyline. -- api3: Encode(ReadOnlySpan, CancellationToken) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_ReadOnlySpan__0__System_Threading_CancellationToken_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L80 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) -- markdown: Encodes a collection of TCoordinate instances into an encoded TPolyline string. -- code: >- - [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Method contains local methods. Actual method only 55 lines.")] - - public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) -- h4: Parameters -- parameters: - - name: coordinates - type: - - text: ReadOnlySpan - url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 - - < - - TCoordinate - - '>' - description: The collection of TCoordinate objects to encode. - - name: cancellationToken - type: - - text: CancellationToken - url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken - description: A that can be used to cancel the encoding operation. - optional: true -- h4: Returns -- parameters: - - type: - - TPolyline - description: An instance of TPolyline representing the encoded coordinates. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when coordinates is null. - - type: - - text: ArgumentException - url: https://learn.microsoft.com/dotnet/api/system.argumentexception - description: Thrown when coordinates is an empty enumeration. - - type: - - text: InvalidOperationException - url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception - description: Thrown when the internal encoding buffer cannot accommodate the encoded value. -- api3: GetLatitude(TCoordinate) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_GetLatitude__0_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L194 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) -- markdown: Extracts the latitude value from the specified coordinate. -- code: protected abstract double GetLatitude(TCoordinate current) -- h4: Parameters -- parameters: - - name: current - type: - - TCoordinate - description: The coordinate from which to extract the latitude. -- h4: Returns -- parameters: - - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The latitude value as a . -- api3: GetLongitude(TCoordinate) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_GetLongitude__0_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L184 - metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) -- markdown: Extracts the longitude value from the specified coordinate. -- code: protected abstract double GetLongitude(TCoordinate current) -- h4: Parameters -- parameters: - - name: current - type: - - TCoordinate - description: The coordinate from which to extract the longitude. -- h4: Returns -- parameters: - - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The longitude value as a . -languageId: csharp -metadata: - description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml index 733987cb..ce7f47e1 100644 --- a/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml +++ b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml @@ -3,7 +3,7 @@ title: Interface IPolylineDecoder body: - api1: Interface IPolylineDecoder id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L22 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L22 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineDecoder`2 @@ -15,7 +15,7 @@ body: - name: Assembly value: PolylineAlgorithm.dll - markdown: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. -- code: public interface IPolylineDecoder +- code: public interface IPolylineDecoder - h4: Type Parameters - parameters: - name: TPolyline @@ -29,17 +29,17 @@ body: contains latitude and longitude (for example a LatLng type or a ValueTuple<double,double>). - h2: Methods -- api3: Decode(TPolyline, CancellationToken) - id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L48 +- api3: Decode(TPolyline, PolylineDecodingOptions?, CancellationToken) + id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2_Decode__0_PolylineAlgorithm_PolylineDecodingOptions__1__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L48 metadata: - uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) - commentId: M:PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) + uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,PolylineAlgorithm.PolylineDecodingOptions{`1},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,PolylineAlgorithm.PolylineDecodingOptions{`1},System.Threading.CancellationToken) - markdown: >- Decodes the specified encoded polyline into an ordered sequence of geographic coordinates. The sequence preserves the original vertex order encoded by the polyline. -- code: IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) +- code: IEnumerable Decode(TPolyline polyline, PolylineDecodingOptions? options = null, CancellationToken cancellationToken = default) - h4: Parameters - parameters: - name: polyline @@ -51,6 +51,15 @@ body: Implementations SHOULD validate the input and may throw or for invalid formats. + - name: options + type: + - text: PolylineDecodingOptions + url: PolylineAlgorithm.PolylineDecodingOptions-1.html + - < + - TValue + - '>' + - '?' + optional: true - name: cancellationToken type: - text: CancellationToken diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml index 341a9de7..8951896e 100644 --- a/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml +++ b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml @@ -3,7 +3,7 @@ title: Interface IPolylineEncoder body: - api1: Interface IPolylineEncoder id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L36 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L36 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineEncoder`2 @@ -46,10 +46,8 @@ body: the chosen representation and any memory / ownership expectations. - h4: Extension Methods - list: - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, List) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TValue[]) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TValue[], PolylineEncodingOptions?, CancellationToken) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___PolylineAlgorithm_PolylineEncodingOptions___0__System_Threading_CancellationToken_ - h2: Remarks - markdown: >- - This interface is intentionally minimal to allow different encoding strategies (Google encoded polyline, @@ -61,17 +59,17 @@ body: - Implementations are encouraged to be memory-efficient; the API accepts a to avoid forced allocations when callers already have contiguous memory. - h2: Methods -- api3: Encode(ReadOnlySpan, CancellationToken) - id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2_Encode_System_ReadOnlySpan__0__System_Threading_CancellationToken_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L76 +- api3: Encode(ReadOnlySpan, PolylineEncodingOptions?, CancellationToken) + id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2_Encode_System_ReadOnlySpan__0__PolylineAlgorithm_PolylineEncodingOptions__0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L76 metadata: - uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) - commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) + uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},PolylineAlgorithm.PolylineEncodingOptions{`0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},PolylineAlgorithm.PolylineEncodingOptions{`0},System.Threading.CancellationToken) - markdown: >- Encodes a sequence of geographic coordinates into an encoded polyline representation. The order of coordinates in coordinates is preserved in the encoded result. -- code: TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) +- code: TPolyline Encode(ReadOnlySpan coordinates, PolylineEncodingOptions? options = null, CancellationToken cancellationToken = default) - h4: Parameters - parameters: - name: coordinates @@ -87,6 +85,15 @@ body: The span may be empty; implementations should return an appropriate empty encoded representation (for example an empty string or an empty memory slice) rather than null. + - name: options + type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions-1.html + - < + - TValue + - '>' + - '?' + optional: true - name: cancellationToken type: - text: CancellationToken diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineFormatter-2.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineFormatter-2.yml new file mode 100644 index 00000000..5de57abd --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.Abstraction.IPolylineFormatter-2.yml @@ -0,0 +1,204 @@ +### YamlMime:ApiPage +title: Interface IPolylineFormatter +body: +- api1: Interface IPolylineFormatter + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L23 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2 + commentId: T:PolylineAlgorithm.Abstraction.IPolylineFormatter`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: >- + Defines how to extract and scale values from a TValue for encoding, + + reconstruct a TValue from scaled values for decoding, + + produce a TPolyline from an encoded character buffer, and extract that buffer + + back from a TPolyline. +- code: public interface IPolylineFormatter +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate or item type. For example a struct with Latitude/Longitude. + - name: TPolyline + description: >- + The polyline surface type. For example or + of . +- h2: Remarks +- markdown: >- + Use to build a + + that implements this interface. +- h2: Properties +- api3: Width + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_Width + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L29 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Width + commentId: P:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Width +- markdown: >- + Gets the number of values (columns) per encoded item. + + This is the required length of the passed to + + and the length of the span received in . +- code: int Width { get; } +- h4: Property Value +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 +- h2: Methods +- api3: CreateItem(ReadOnlySpan) + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_CreateItem_System_ReadOnlySpan_System_Int64__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L76 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.CreateItem(System.ReadOnlySpan{System.Int64}) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.CreateItem(System.ReadOnlySpan{System.Int64}) +- markdown: >- + Reconstructs a TValue from the given accumulated scaled integer values. + + Called once per decoded item in the decoding loop. Implementations are responsible for + + denormalizing the raw scaled integers (e.g. dividing by the precision factor and adding back + + any baseline) before constructing the item. +- code: TValue CreateItem(ReadOnlySpan values) +- h4: Parameters +- parameters: + - name: values + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + - '>' + description: >- + The raw accumulated scaled integer values decoded from the polyline. Each element corresponds to + + the same column position as in . These are the direct output of the + + delta-accumulation loop in the decoder before any denormalization is applied. +- h4: Returns +- parameters: + - type: + - TValue + description: A TValue reconstructed from values. +- api3: GetBaseline(int) + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_GetBaseline_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L38 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.GetBaseline(System.Int32) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.GetBaseline(System.Int32) +- markdown: >- + Returns the baseline for the column at index, or 0 if none is configured. + + The encoder uses this as the starting point for the first item's delta computation: the initial + + delta for the column is scaled_first_value − baseline rather than scaled_first_value. +- code: long GetBaseline(int index) +- h4: Parameters +- parameters: + - name: index + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The zero-based column index. Must be in the range [0, ). +- h4: Returns +- parameters: + - type: + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: The baseline value, or 0 when no baseline has been defined for the column. +- api3: GetValues(TValue, Span) + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_GetValues__0_System_Span_System_Int64__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L48 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.GetValues(`0,System.Span{System.Int64}) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.GetValues(`0,System.Span{System.Int64}) +- markdown: >- + Extracts and scales all column values from item into the values span. + + Called once per item in the encoding loop. +- code: void GetValues(TValue item, Span values) +- h4: Parameters +- parameters: + - name: item + type: + - TValue + description: The source item from which column values are extracted. + - name: values + type: + - text: Span + url: https://learn.microsoft.com/dotnet/api/system.span-1 + - < + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + - '>' + description: Output buffer that receives the scaled integer values. Its length must equal . +- api3: Read(TPolyline) + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_Read__1_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L62 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Read(`1) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Read(`1) +- markdown: Extracts the character buffer from a TPolyline for the decoder to read. +- code: ReadOnlyMemory Read(TPolyline polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The polyline to read from. +- h4: Returns +- parameters: + - type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A of representing the encoded characters. +- api3: Write(ReadOnlyMemory) + id: PolylineAlgorithm_Abstraction_IPolylineFormatter_2_Write_System_ReadOnlyMemory_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs#L55 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Write(System.ReadOnlyMemory{System.Char}) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineFormatter`2.Write(System.ReadOnlyMemory{System.Char}) +- markdown: Creates a TPolyline from the encoded character buffer produced by the encoder. +- code: TPolyline Write(ReadOnlyMemory encoded) +- h4: Parameters +- parameters: + - name: encoded + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The encoded polyline as a read-only memory of characters. +- h4: Returns +- parameters: + - type: + - TPolyline + description: A TPolyline wrapping or derived from encoded. +languageId: csharp +metadata: + description: >- + Defines how to extract and scale values from a TValue for encoding, + + reconstruct a TValue from scaled values for decoding, + + produce a TPolyline from an encoded character buffer, and extract that buffer + + back from a TPolyline. diff --git a/api-reference/0.0/PolylineAlgorithm.Abstraction.yml b/api-reference/0.0/PolylineAlgorithm.Abstraction.yml index e9de48f7..854d4ab0 100644 --- a/api-reference/0.0/PolylineAlgorithm.Abstraction.yml +++ b/api-reference/0.0/PolylineAlgorithm.Abstraction.yml @@ -6,16 +6,6 @@ body: metadata: uid: PolylineAlgorithm.Abstraction commentId: N:PolylineAlgorithm.Abstraction -- h3: Classes -- parameters: - - type: - text: AbstractPolylineDecoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html - description: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. - - type: - text: AbstractPolylineEncoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html - description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. - h3: Interfaces - parameters: - type: @@ -31,4 +21,15 @@ body: Implementations interpret the generic TValue type and produce an encoded representation of those coordinates as TPolyline. + - type: + text: IPolylineFormatter + url: PolylineAlgorithm.Abstraction.IPolylineFormatter-2.html + description: >- + Defines how to extract and scale values from a TValue for encoding, + + reconstruct a TValue from scaled values for decoding, + + produce a TPolyline from an encoded character buffer, and extract that buffer + + back from a TPolyline. languageId: csharp diff --git a/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml b/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml deleted file mode 100644 index 8356b63c..00000000 --- a/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml +++ /dev/null @@ -1,195 +0,0 @@ -### YamlMime:ApiPage -title: Class PolylineDecoderExtensions -body: -- api1: Class PolylineDecoderExtensions - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L16 - metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions - commentId: T:PolylineAlgorithm.Extensions.PolylineDecoderExtensions -- facts: - - name: Namespace - value: - text: PolylineAlgorithm.Extensions - url: PolylineAlgorithm.Extensions.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Provides extension methods for the interface to facilitate decoding encoded polylines. -- code: public static class PolylineDecoderExtensions -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: PolylineDecoderExtensions - url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html -- h4: Inherited Members -- list: - - text: object.Equals(object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - - text: object.Equals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) - - text: object.GetHashCode() - url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode - - text: object.GetType() - url: https://learn.microsoft.com/dotnet/api/system.object.gettype - - text: object.MemberwiseClone() - url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone - - text: object.ReferenceEquals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals - - text: object.ToString() - url: https://learn.microsoft.com/dotnet/api/system.object.tostring -- h2: Methods -- api3: Decode(IPolylineDecoder, char[]) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_String___0__System_Char___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L33 - metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.Char[]) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.Char[]) -- markdown: Decodes an encoded polyline represented as a character array into a sequence of geographic coordinates. -- code: public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) -- h4: Parameters -- parameters: - - name: decoder - type: - - text: IPolylineDecoder - url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - - < - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - - ',' - - " " - - TValue - - '>' - description: The instance used to perform the decoding operation. - - name: polyline - type: - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '[' - - ']' - description: The encoded polyline as a character array to decode. The array is converted to a string internally. -- h4: Returns -- parameters: - - type: - - text: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 - - < - - TValue - - '>' - description: An of TValue containing the decoded coordinate pairs. -- h4: Type Parameters -- parameters: - - name: TValue - description: The coordinate type returned by the decoder. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when decoder or polyline is null. -- api3: Decode(IPolylineDecoder, ReadOnlyMemory) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_String___0__System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L61 - metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.ReadOnlyMemory{System.Char}) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.ReadOnlyMemory{System.Char}) -- markdown: Decodes an encoded polyline represented as a read-only memory of characters into a sequence of geographic coordinates. -- code: public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) -- h4: Parameters -- parameters: - - name: decoder - type: - - text: IPolylineDecoder - url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - - < - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - - ',' - - " " - - TValue - - '>' - description: The instance used to perform the decoding operation. - - name: polyline - type: - - text: ReadOnlyMemory - url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 - - < - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '>' - description: The encoded polyline as a read-only memory of characters to decode. The memory is converted to a string internally. -- h4: Returns -- parameters: - - type: - - text: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 - - < - - TValue - - '>' - description: An of TValue containing the decoded coordinate pairs. -- h4: Type Parameters -- parameters: - - name: TValue - description: The coordinate type returned by the decoder. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when decoder is null. -- api3: Decode(IPolylineDecoder, TValue>, string) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_ReadOnlyMemory_System_Char____0__System_String_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L86 - metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.ReadOnlyMemory{System.Char},``0},System.String) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.ReadOnlyMemory{System.Char},``0},System.String) -- markdown: >- - Decodes an encoded polyline string into a sequence of geographic coordinates, - - using a decoder that accepts of . -- code: public static IEnumerable Decode(this IPolylineDecoder, TValue> decoder, string polyline) -- h4: Parameters -- parameters: - - name: decoder - type: - - text: IPolylineDecoder - url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - - < - - text: ReadOnlyMemory - url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 - - < - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '>' - - ',' - - " " - - TValue - - '>' - description: The instance used to perform the decoding operation. - - name: polyline - type: - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - description: The encoded polyline string to decode. The string is converted to internally. -- h4: Returns -- parameters: - - type: - - text: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 - - < - - TValue - - '>' - description: An of TValue containing the decoded coordinate pairs. -- h4: Type Parameters -- parameters: - - name: TValue - description: The coordinate type returned by the decoder. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when decoder or polyline is null. -languageId: csharp -metadata: - description: Provides extension methods for the interface to facilitate decoding encoded polylines. diff --git a/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml b/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml index e5296518..69b97db4 100644 --- a/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml +++ b/api-reference/0.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml @@ -3,7 +3,7 @@ title: Class PolylineEncoderExtensions body: - api1: Class PolylineEncoderExtensions id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L19 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L15 metadata: uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions commentId: T:PolylineAlgorithm.Extensions.PolylineEncoderExtensions @@ -39,19 +39,14 @@ body: - text: object.ToString() url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Methods -- api3: Encode(IPolylineEncoder, List) - id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L37 +- api3: Encode(IPolylineEncoder, TValue[], PolylineEncodingOptions?, CancellationToken) + id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___PolylineAlgorithm_PolylineEncodingOptions___0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L33 metadata: - uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},System.Collections.Generic.List{``0}) - commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},System.Collections.Generic.List{``0}) -- markdown: Encodes a of TCoordinate instances into an encoded polyline. -- code: >- - [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We need a list as we do need to marshal it as span.")] - - [SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "We need a list as we do need to marshal it as span.")] - - public static TPolyline Encode(this IPolylineEncoder encoder, List coordinates) + uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[],PolylineAlgorithm.PolylineEncodingOptions{``0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[],PolylineAlgorithm.PolylineEncodingOptions{``0},System.Threading.CancellationToken) +- markdown: Encodes an array of TValue instances into an encoded polyline. +- code: public static TPolyline Encode(this IPolylineEncoder encoder, TValue[] values, PolylineEncodingOptions? options = null, CancellationToken cancellationToken = default) - h4: Parameters - parameters: - name: encoder @@ -59,64 +54,32 @@ body: - text: IPolylineEncoder url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html - < - - TCoordinate + - TValue - ',' - " " - TPolyline - '>' description: The instance used to perform the encoding operation. - - name: coordinates + - name: values type: - - text: List - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1 - - < - - TCoordinate - - '>' - description: The list of TCoordinate objects to encode. -- h4: Returns -- parameters: - - type: - - TPolyline - description: A TPolyline instance representing the encoded polyline for the provided coordinates. -- h4: Type Parameters -- parameters: - - name: TCoordinate - description: The type that represents a geographic coordinate to encode. - - name: TPolyline - description: The type that represents the encoded polyline output. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when encoder or coordinates is null. -- api3: Encode(IPolylineEncoder, TCoordinate[]) - id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L73 - metadata: - uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[]) - commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[]) -- markdown: Encodes an array of TCoordinate instances into an encoded polyline. -- code: public static TPolyline Encode(this IPolylineEncoder encoder, TCoordinate[] coordinates) -- h4: Parameters -- parameters: - - name: encoder + - TValue + - '[' + - ']' + description: The array of TValue objects to encode. + - name: options type: - - text: IPolylineEncoder - url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions-1.html - < - - TCoordinate - - ',' - - " " - - TPolyline + - TValue - '>' - description: The instance used to perform the encoding operation. - - name: coordinates + - '?' + optional: true + - name: cancellationToken type: - - TCoordinate - - '[' - - ']' - description: The array of TCoordinate objects to encode. + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + optional: true - h4: Returns - parameters: - type: @@ -124,7 +87,7 @@ body: description: A TPolyline instance representing the encoded polyline for the provided coordinates. - h4: Type Parameters - parameters: - - name: TCoordinate + - name: TValue description: The type that represents a geographic coordinate to encode. - name: TPolyline description: The type that represents the encoded polyline output. @@ -133,7 +96,7 @@ body: - type: - text: ArgumentNullException url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when encoder or coordinates is null. + description: Thrown when encoder or values is null. languageId: csharp metadata: description: Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. diff --git a/api-reference/0.0/PolylineAlgorithm.Extensions.yml b/api-reference/0.0/PolylineAlgorithm.Extensions.yml index c39da0ca..4c109ef2 100644 --- a/api-reference/0.0/PolylineAlgorithm.Extensions.yml +++ b/api-reference/0.0/PolylineAlgorithm.Extensions.yml @@ -8,10 +8,6 @@ body: commentId: N:PolylineAlgorithm.Extensions - h3: Classes - parameters: - - type: - text: PolylineDecoderExtensions - url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html - description: Provides extension methods for the interface to facilitate decoding encoded polylines. - type: text: PolylineEncoderExtensions url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html diff --git a/api-reference/0.0/PolylineAlgorithm.FormatterBuilder-2.yml b/api-reference/0.0/PolylineAlgorithm.FormatterBuilder-2.yml new file mode 100644 index 00000000..334c7da5 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.FormatterBuilder-2.yml @@ -0,0 +1,354 @@ +### YamlMime:ApiPage +title: Class FormatterBuilder +body: +- api1: Class FormatterBuilder + id: PolylineAlgorithm_FormatterBuilder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L30 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2 + commentId: T:PolylineAlgorithm.FormatterBuilder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides a fluent builder for constructing a . +- code: public sealed class FormatterBuilder +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate or item type from which column values are extracted. + - name: TPolyline + description: The polyline surface type produced and consumed by the formatter. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- +

+ + Use to obtain an instance, call once per column, + + optionally chain to set a reference baseline for the most-recently added column, + + optionally chain to register a factory for the decoding direction, + + call to supply the polyline surface delegates (required), then call + + to produce the immutable . + +

+ +

+ + The builder is the only way to create a + + — its constructor is internal. + +

+- h2: Methods +- api3: AddValue(string, Func, uint) + id: PolylineAlgorithm_FormatterBuilder_2_AddValue_System_String_System_Func__0_System_Double__System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L67 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.AddValue(System.String,System.Func{`0,System.Double},System.UInt32) + commentId: M:PolylineAlgorithm.FormatterBuilder`2.AddValue(System.String,System.Func{`0,System.Double},System.UInt32) +- markdown: Adds a column with the specified value selector and precision. +- code: public FormatterBuilder AddValue(string name, Func selector, uint precision = 5) +- h4: Parameters +- parameters: + - name: name + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: A unique, non-null, non-empty name that identifies the column. Used for diagnostics only. + - name: selector + type: + - text: Func + url: https://learn.microsoft.com/dotnet/api/system.func-2 + - < + - TValue + - ',' + - " " + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + - '>' + description: >- + A delegate that extracts the column's raw value from an item of type + + TValue. + - name: precision + type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: >- + The number of decimal places to preserve. Each extracted value is multiplied by + + 10^precision before encoding. Defaults to 5. + optional: true +- h4: Returns +- parameters: + - type: + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: The current builder instance for method chaining. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when name or selector is null. + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when name is empty, or a rule with the same name already exists. +- api3: Build() + id: PolylineAlgorithm_FormatterBuilder_2_Build + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L186 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.Build + commentId: M:PolylineAlgorithm.FormatterBuilder`2.Build +- markdown: >- + Bakes all added rules and delegates into a sealed, immutable + + . +- code: public PolylineFormatter Build() +- h4: Returns +- parameters: + - type: + - text: PolylineFormatter + url: PolylineAlgorithm.PolylineFormatter-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: >- + An immutable whose configuration can + + no longer be changed. +- h4: Exceptions +- parameters: + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: Thrown when no rules have been added, or when has not been called. +- api3: Create() + id: PolylineAlgorithm_FormatterBuilder_2_Create + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L43 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.Create + commentId: M:PolylineAlgorithm.FormatterBuilder`2.Create +- markdown: Creates a new instance. +- code: >- + [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Factory method on a generic builder intentionally lives on the type so callers write FormatterBuilder.Create() without needing a separate non-generic factory class.")] + + public static FormatterBuilder Create() +- h4: Returns +- parameters: + - type: + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: A fresh builder with no rules and no polyline delegates. +- api3: SetBaseline(long) + id: PolylineAlgorithm_FormatterBuilder_2_SetBaseline_System_Int64_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L106 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.SetBaseline(System.Int64) + commentId: M:PolylineAlgorithm.FormatterBuilder`2.SetBaseline(System.Int64) +- markdown: >- + Sets a reference value (baseline) on the most-recently added column. + + During encoding, the baseline is subtracted from the first item's scaled column value so that + + the initial delta is scaled_first_value − baseline rather than scaled_first_value. + + Use this when the absolute scaled value of the first data point for a column would otherwise + + produce a very large initial encoded delta. +- code: public FormatterBuilder SetBaseline(long baseline) +- h4: Parameters +- parameters: + - name: baseline + type: + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: >- + The reference value to subtract from the first item's scaled column value during encoding. + + The decoder automatically adds this value back, so the reconstructed item matches the + + original input. +- h4: Returns +- parameters: + - type: + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: The current builder instance for method chaining. +- h4: Exceptions +- parameters: + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: >- + Thrown when no rules have been added yet. Call before + + . +- api3: WithReaderWriter(Func, TPolyline>, Func>) + id: PolylineAlgorithm_FormatterBuilder_2_WithReaderWriter_System_Func_System_ReadOnlyMemory_System_Char___1__System_Func__1_System_ReadOnlyMemory_System_Char___ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L158 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.WithReaderWriter(System.Func{System.ReadOnlyMemory{System.Char},`1},System.Func{`1,System.ReadOnlyMemory{System.Char}}) + commentId: M:PolylineAlgorithm.FormatterBuilder`2.WithReaderWriter(System.Func{System.ReadOnlyMemory{System.Char},`1},System.Func{`1,System.ReadOnlyMemory{System.Char}}) +- markdown: >- + Supplies the polyline-surface delegates required to convert between the raw character buffer + + and a TPolyline. This call is mandatory before . +- code: public FormatterBuilder WithReaderWriter(Func, TPolyline> write, Func> read) +- h4: Parameters +- parameters: + - name: write + type: + - text: Func + url: https://learn.microsoft.com/dotnet/api/system.func-2 + - < + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + - ',' + - " " + - TPolyline + - '>' + description: >- + Converts the encoded of produced by the encoder + + into a TPolyline. + - name: read + type: + - text: Func + url: https://learn.microsoft.com/dotnet/api/system.func-2 + - < + - TPolyline + - ',' + - " " + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + - '>' + description: >- + Extracts the encoded character buffer from a TPolyline for the decoder to + + consume. +- h4: Returns +- parameters: + - type: + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: The current builder instance for method chaining. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when write or read is null. +- api3: WithValueFactory(PolylineItemFactory) + id: PolylineAlgorithm_FormatterBuilder_2_WithValueFactory_PolylineAlgorithm_PolylineItemFactory__0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/FormatterBuilder.cs#L132 + metadata: + uid: PolylineAlgorithm.FormatterBuilder`2.WithValueFactory(PolylineAlgorithm.PolylineItemFactory{`0}) + commentId: M:PolylineAlgorithm.FormatterBuilder`2.WithValueFactory(PolylineAlgorithm.PolylineItemFactory{`0}) +- markdown: >- + Registers a factory delegate used to reconstruct a TValue from + + denormalized values during decoding. +- code: public FormatterBuilder WithValueFactory(PolylineItemFactory create) +- h4: Parameters +- parameters: + - name: create + type: + - text: PolylineItemFactory + url: PolylineAlgorithm.PolylineItemFactory-1.html + - < + - TValue + - '>' + description: >- + A delegate that accepts the denormalized values reconstructed from the + + polyline and returns a TValue. The formatter automatically divides + + each accumulated scaled integer by its precision factor and adds back any baseline configured + + via , so the span values match the original values supplied to the + + encoder. The span length always equals the number of columns added via . +- h4: Returns +- parameters: + - type: + - text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: The current builder instance for method chaining. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when create is null. +languageId: csharp +metadata: + description: Provides a fluent builder for constructing a . diff --git a/api-reference/0.0/PolylineAlgorithm.InvalidPolylineException.yml b/api-reference/0.0/PolylineAlgorithm.InvalidPolylineException.yml index 2c4e3cc1..7bc75368 100644 --- a/api-reference/0.0/PolylineAlgorithm.InvalidPolylineException.yml +++ b/api-reference/0.0/PolylineAlgorithm.InvalidPolylineException.yml @@ -3,7 +3,7 @@ title: Class InvalidPolylineException body: - api1: Class InvalidPolylineException id: PolylineAlgorithm_InvalidPolylineException - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/InvalidPolylineException.cs#L17 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/InvalidPolylineException.cs#L17 metadata: uid: PolylineAlgorithm.InvalidPolylineException commentId: T:PolylineAlgorithm.InvalidPolylineException @@ -71,7 +71,7 @@ body: - h2: Constructors - api3: InvalidPolylineException() id: PolylineAlgorithm_InvalidPolylineException__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/InvalidPolylineException.cs#L22 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/InvalidPolylineException.cs#L22 metadata: uid: PolylineAlgorithm.InvalidPolylineException.#ctor commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor @@ -79,7 +79,7 @@ body: - code: public InvalidPolylineException() - api3: InvalidPolylineException(string, Exception) id: PolylineAlgorithm_InvalidPolylineException__ctor_System_String_System_Exception_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/InvalidPolylineException.cs#L43 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/InvalidPolylineException.cs#L43 metadata: uid: PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineDecoder-2.yml b/api-reference/0.0/PolylineAlgorithm.PolylineDecoder-2.yml new file mode 100644 index 00000000..789bba03 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineDecoder-2.yml @@ -0,0 +1,165 @@ +### YamlMime:ApiPage +title: Class PolylineDecoder +body: +- api1: Class PolylineDecoder + id: PolylineAlgorithm_PolylineDecoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecoder.cs#L27 + metadata: + uid: PolylineAlgorithm.PolylineDecoder`2 + commentId: T:PolylineAlgorithm.PolylineDecoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Decodes encoded polyline representations into sequences of geographic coordinates. +- code: 'public class PolylineDecoder : IPolylineDecoder' +- h4: Type Parameters +- parameters: + - name: TPolyline + description: The type that represents the encoded polyline input. + - name: TValue + description: The type that represents a decoded geographic coordinate. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineDecoder + url: PolylineAlgorithm.PolylineDecoder-2.html +- h4: Implements +- list: + - text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Pass a that carries a + + to the constructor. The formatter handles + + all type-specific concerns; no subclassing is required. +- h2: Constructors +- api3: PolylineDecoder(PolylineOptions) + id: PolylineAlgorithm_PolylineDecoder_2__ctor_PolylineAlgorithm_PolylineOptions__1__0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecoder.cs#L41 + metadata: + uid: PolylineAlgorithm.PolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineOptions{`1,`0}) + commentId: M:PolylineAlgorithm.PolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineOptions{`1,`0}) +- markdown: Initializes a new instance of . +- code: >- + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards.")] + + public PolylineDecoder(PolylineOptions options) +- h4: Parameters +- parameters: + - name: options + type: + - text: PolylineOptions + url: PolylineAlgorithm.PolylineOptions-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: >- + A that carries the formatter and settings. + + Must not be null. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when options is null. +- h2: Methods +- api3: Decode(TPolyline, PolylineDecodingOptions?, CancellationToken) + id: PolylineAlgorithm_PolylineDecoder_2_Decode__0_PolylineAlgorithm_PolylineDecodingOptions__1__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecoder.cs#L78 + metadata: + uid: PolylineAlgorithm.PolylineDecoder`2.Decode(`0,PolylineAlgorithm.PolylineDecodingOptions{`1},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.PolylineDecoder`2.Decode(`0,PolylineAlgorithm.PolylineDecodingOptions{`1},System.Threading.CancellationToken) +- markdown: >- + Decodes an encoded TPolyline into a sequence of + + TValue instances, applying per-call options to + + seed the accumulated-delta state. Use this overload to decode polylines that were produced by + + chunked encoding. +- code: public IEnumerable Decode(TPolyline polyline, PolylineDecodingOptions? options = null, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The encoded polyline to decode. Must not be null. + - name: options + type: + - text: PolylineDecodingOptions + url: PolylineAlgorithm.PolylineDecodingOptions-1.html + - < + - TValue + - '>' + - '?' + description: >- + Per-call options that control the accumulated-delta seed. Pass null or an + + instance with set to + + null to start from zero (same as calling + + Decode(TPolyline, CancellationToken)). + optional: true + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: A token that can be used to cancel the operation. + optional: true +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TValue + - '>' + description: >- + An of TValue representing the decoded + + coordinates. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when polyline is null. + - type: + - text: InvalidPolylineException + url: PolylineAlgorithm.InvalidPolylineException.html + description: Thrown when the polyline format is invalid or malformed. + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when cancellationToken is canceled during decoding. +languageId: csharp +metadata: + description: Decodes encoded polyline representations into sequences of geographic coordinates. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineDecodingOptions-1.yml b/api-reference/0.0/PolylineAlgorithm.PolylineDecodingOptions-1.yml new file mode 100644 index 00000000..297c034e --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineDecodingOptions-1.yml @@ -0,0 +1,117 @@ +### YamlMime:ApiPage +title: Class PolylineDecodingOptions +body: +- api1: Class PolylineDecodingOptions + id: PolylineAlgorithm_PolylineDecodingOptions_1 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecodingOptions.cs#L18 + metadata: + uid: PolylineAlgorithm.PolylineDecodingOptions`1 + commentId: T:PolylineAlgorithm.PolylineDecodingOptions`1 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Per-call options for a chunked decoding operation. +- code: public sealed class PolylineDecodingOptions +- h4: Type Parameters +- parameters: + - name: TValue + description: The value type understood by the formatter. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineDecodingOptions + url: PolylineAlgorithm.PolylineDecodingOptions-1.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Pass an instance of this class to the chunked + + overload to control + + the accumulated-delta seed used at the start of each chunk. When is + + false zero-initialisation is used, which is the existing default behaviour. +- h2: Constructors +- api3: PolylineDecodingOptions() + id: PolylineAlgorithm_PolylineDecodingOptions_1__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecodingOptions.cs#L24 + metadata: + uid: PolylineAlgorithm.PolylineDecodingOptions`1.#ctor + commentId: M:PolylineAlgorithm.PolylineDecodingOptions`1.#ctor +- markdown: >- + Initializes a new instance of with no + + previous value (zero-initialised baseline will be used). +- code: public PolylineDecodingOptions() +- api3: PolylineDecodingOptions(TValue) + id: PolylineAlgorithm_PolylineDecodingOptions_1__ctor__0_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecodingOptions.cs#L33 + metadata: + uid: PolylineAlgorithm.PolylineDecodingOptions`1.#ctor(`0) + commentId: M:PolylineAlgorithm.PolylineDecodingOptions`1.#ctor(`0) +- markdown: >- + Initializes a new instance of with the + + specified previous value used to seed the accumulated-delta state. +- code: public PolylineDecodingOptions(TValue previous) +- h4: Parameters +- parameters: + - name: previous + type: + - TValue + description: The last value of the previous chunk, used to seed the accumulated-delta state. +- h2: Properties +- api3: HasPrevious + id: PolylineAlgorithm_PolylineDecodingOptions_1_HasPrevious + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecodingOptions.cs#L43 + metadata: + uid: PolylineAlgorithm.PolylineDecodingOptions`1.HasPrevious + commentId: P:PolylineAlgorithm.PolylineDecodingOptions`1.HasPrevious +- markdown: >- + Gets a value indicating whether a previous value has been supplied to seed the + + accumulated-delta state. When false zero-initialisation is used, which is + + the existing default. +- code: public bool HasPrevious { get; } +- h4: Property Value +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean +- api3: Previous + id: PolylineAlgorithm_PolylineDecodingOptions_1_Previous + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineDecodingOptions.cs#L49 + metadata: + uid: PolylineAlgorithm.PolylineDecodingOptions`1.Previous + commentId: P:PolylineAlgorithm.PolylineDecodingOptions`1.Previous +- markdown: >- + Gets the last value of the previous chunk, used to seed the accumulated-delta state. + + Only meaningful when is true. +- code: public TValue Previous { get; } +- h4: Property Value +- parameters: + - type: + - TValue +languageId: csharp +metadata: + description: Per-call options for a chunked decoding operation. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineEncoder-2.yml b/api-reference/0.0/PolylineAlgorithm.PolylineEncoder-2.yml new file mode 100644 index 00000000..6c8caa5f --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineEncoder-2.yml @@ -0,0 +1,210 @@ +### YamlMime:ApiPage +title: Class PolylineEncoder +body: +- api1: Class PolylineEncoder + id: PolylineAlgorithm_PolylineEncoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoder.cs#L28 + metadata: + uid: PolylineAlgorithm.PolylineEncoder`2 + commentId: T:PolylineAlgorithm.PolylineEncoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Encodes sequences of geographic coordinates into encoded polyline representations. +- code: 'public class PolylineEncoder : IPolylineEncoder' +- h4: Type Parameters +- parameters: + - name: TValue + description: The type that represents a geographic coordinate to encode. + - name: TPolyline + description: The type that represents the encoded polyline output. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncoder + url: PolylineAlgorithm.PolylineEncoder-2.html +- h4: Implements +- list: + - text: IPolylineEncoder + url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h4: Extension Methods +- list: + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TValue[], PolylineEncodingOptions?, CancellationToken) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___PolylineAlgorithm_PolylineEncodingOptions___0__System_Threading_CancellationToken_ +- h2: Remarks +- markdown: >- + Pass a that carries a + + to the constructor. The formatter handles + + all type-specific concerns; no subclassing is required. +- h2: Constructors +- api3: PolylineEncoder(PolylineOptions) + id: PolylineAlgorithm_PolylineEncoder_2__ctor_PolylineAlgorithm_PolylineOptions__0__1__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoder.cs#L43 + metadata: + uid: PolylineAlgorithm.PolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineOptions{`0,`1}) + commentId: M:PolylineAlgorithm.PolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineOptions{`0,`1}) +- markdown: Initializes a new instance of . +- code: >- + [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards.")] + + public PolylineEncoder(PolylineOptions options) +- h4: Parameters +- parameters: + - name: options + type: + - text: PolylineOptions + url: PolylineAlgorithm.PolylineOptions-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: >- + A that carries the formatter and settings. + + Must not be null. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when options is null. +- h2: Methods +- api3: Encode(ReadOnlySpan, CancellationToken) + id: PolylineAlgorithm_PolylineEncoder_2_Encode_System_ReadOnlySpan__0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoder.cs#L72 + metadata: + uid: PolylineAlgorithm.PolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.PolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) +- markdown: >- + Encodes a collection of TValue instances into an encoded + + TPolyline. +- code: public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: coordinates + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - TValue + - '>' + description: The collection of coordinates to encode. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: A token that can be used to cancel the operation. + optional: true +- h4: Returns +- parameters: + - type: + - TPolyline + description: An instance of TPolyline representing the encoded coordinates. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when coordinates is empty. + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: Thrown when the internal encoding buffer cannot accommodate the encoded value. + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when cancellationToken is canceled. +- api3: Encode(ReadOnlySpan, PolylineEncodingOptions?, CancellationToken) + id: PolylineAlgorithm_PolylineEncoder_2_Encode_System_ReadOnlySpan__0__PolylineAlgorithm_PolylineEncodingOptions__0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoder.cs#L158 + metadata: + uid: PolylineAlgorithm.PolylineEncoder`2.Encode(System.ReadOnlySpan{`0},PolylineAlgorithm.PolylineEncodingOptions{`0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.PolylineEncoder`2.Encode(System.ReadOnlySpan{`0},PolylineAlgorithm.PolylineEncodingOptions{`0},System.Threading.CancellationToken) +- markdown: >- + Encodes a collection of TValue instances into an encoded + + TPolyline, applying per-call options to control the + + delta baseline. Use this overload to encode large sequences in independent chunks that can be + + concatenated into a single valid polyline. +- code: public TPolyline Encode(ReadOnlySpan coordinates, PolylineEncodingOptions? options, CancellationToken cancellationToken) +- h4: Parameters +- parameters: + - name: coordinates + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - TValue + - '>' + description: The collection of coordinates to encode. + - name: options + type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions-1.html + - < + - TValue + - '>' + - '?' + description: >- + Per-call options that control the starting delta baseline. Pass null or an + + instance with set to + + null to use the formatter's default baseline (same as calling + + ). + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: A token that can be used to cancel the operation. +- h4: Returns +- parameters: + - type: + - TPolyline + description: An instance of TPolyline representing the encoded coordinates. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when coordinates is empty. + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: Thrown when the internal encoding buffer cannot accommodate the encoded value. + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when cancellationToken is canceled. +languageId: csharp +metadata: + description: Encodes sequences of geographic coordinates into encoded polyline representations. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineEncoding.yml b/api-reference/0.0/PolylineAlgorithm.PolylineEncoding.yml index 1add8147..f5f6f243 100644 --- a/api-reference/0.0/PolylineAlgorithm.PolylineEncoding.yml +++ b/api-reference/0.0/PolylineAlgorithm.PolylineEncoding.yml @@ -3,7 +3,7 @@ title: Class PolylineEncoding body: - api1: Class PolylineEncoding id: PolylineAlgorithm_PolylineEncoding - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L23 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L23 metadata: uid: PolylineAlgorithm.PolylineEncoding commentId: T:PolylineAlgorithm.PolylineEncoding @@ -48,21 +48,21 @@ body: coordinates. It also provides validation utilities to ensure values conform to expected ranges for latitude and longitude. - h2: Methods -- api3: Denormalize(int, uint) - id: PolylineAlgorithm_PolylineEncoding_Denormalize_System_Int32_System_UInt32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L122 +- api3: Denormalize(long, uint) + id: PolylineAlgorithm_PolylineEncoding_Denormalize_System_Int64_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L119 metadata: - uid: PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,System.UInt32) - commentId: M:PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,System.UInt32) + uid: PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int64,System.UInt32) + commentId: M:PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int64,System.UInt32) - markdown: Converts a normalized integer coordinate value back to its floating-point representation based on the specified precision. -- code: public static double Denormalize(int value, uint precision = 5) +- code: public static double Denormalize(long value, uint precision = 5) - h4: Parameters - parameters: - name: value type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: The integer value to denormalize. Typically produced by the method. + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: The long integer value to denormalize. Typically produced by the method. - name: precision type: - text: uint @@ -115,22 +115,22 @@ body: - text: OverflowException url: https://learn.microsoft.com/dotnet/api/system.overflowexception description: Thrown if the arithmetic operation overflows during conversion. -- api3: GetRequiredBufferSize(int) - id: PolylineAlgorithm_PolylineEncoding_GetRequiredBufferSize_System_Int32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L298 +- api3: GetRequiredBufferSize(long) + id: PolylineAlgorithm_PolylineEncoding_GetRequiredBufferSize_System_Int64_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L296 metadata: - uid: PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) - commentId: M:PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) + uid: PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int64) + commentId: M:PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int64) - markdown: Calculates the number of characters required to encode a delta value in polyline format. -- code: public static int GetRequiredBufferSize(int delta) +- code: public static int GetRequiredBufferSize(long delta) - h4: Parameters - parameters: - name: delta type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 description: >- - The integer delta value to calculate the encoded size for. This value typically represents the difference between + The long delta value to calculate the encoded size for. This value typically represents the difference between consecutive coordinate values in polyline encoding. - h4: Returns @@ -145,7 +145,7 @@ body: This method determines how many characters will be needed to represent an integer delta value when encoded - using the polyline encoding algorithm. It performs the same zigzag encoding transformation as + using the polyline encoding algorithm. It performs the same zigzag encoding transformation as but only calculates the required buffer size without actually writing any data. @@ -170,7 +170,9 @@ body:

- The method uses a long internally to prevent overflow during the left-shift operation on large negative values. + The method uses ulong internally to handle the full range of 64-bit signed values correctly + + during the zigzag encoding step.

- h4: See Also @@ -179,10 +181,10 @@ body: url: PolylineAlgorithm.PolylineEncoding.html - . - text: TryWriteValue - url: PolylineAlgorithm.PolylineEncoding.html#PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char__System_Int32__ + url: PolylineAlgorithm.PolylineEncoding.html#PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int64_System_Span_System_Char__System_Int32__ - ( - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 - ',' - " " - text: Span @@ -200,12 +202,12 @@ body: - ) - api3: Normalize(double, uint) id: PolylineAlgorithm_PolylineEncoding_Normalize_System_Double_System_UInt32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L61 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L58 metadata: uid: PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,System.UInt32) commentId: M:PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,System.UInt32) - markdown: Normalizes a geographic coordinate value to an integer representation based on the specified precision. -- code: public static int Normalize(double value, uint precision = 5) +- code: public static long Normalize(double value, uint precision = 5) - h4: Parameters - parameters: - name: value @@ -227,9 +229,9 @@ body: - h4: Returns - parameters: - type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: An integer representing the normalized value. Returns 0 if the input value is 0.0. + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: A long integer representing the normalized value. Returns 0 if the input value is 0.0. - h4: Remarks - markdown: >-

@@ -262,25 +264,21 @@ body: - text: ArgumentOutOfRangeException url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception description: Thrown when value is not a finite number (NaN or infinity). - - type: - - text: OverflowException - url: https://learn.microsoft.com/dotnet/api/system.overflowexception - description: Thrown when the normalized result exceeds the range of a 32-bit signed integer during the conversion from double to int. -- api3: TryReadValue(ref int, ReadOnlyMemory, ref int) - id: PolylineAlgorithm_PolylineEncoding_TryReadValue_System_Int32__System_ReadOnlyMemory_System_Char__System_Int32__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L169 +- api3: TryReadValue(ref long, ReadOnlyMemory, ref int) + id: PolylineAlgorithm_PolylineEncoding_TryReadValue_System_Int64__System_ReadOnlyMemory_System_Char__System_Int32__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L166 metadata: - uid: PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int32@,System.ReadOnlyMemory{System.Char},System.Int32@) - commentId: M:PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int32@,System.ReadOnlyMemory{System.Char},System.Int32@) + uid: PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int64@,System.ReadOnlyMemory{System.Char},System.Int32@) + commentId: M:PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int64@,System.ReadOnlyMemory{System.Char},System.Int32@) - markdown: Attempts to read an encoded integer value from a polyline buffer, updating the specified delta and position. -- code: public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref int position) +- code: public static bool TryReadValue(ref long delta, ReadOnlyMemory buffer, ref int position) - h4: Parameters - parameters: - name: delta type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: Reference to the integer accumulator that will be updated with the decoded value. + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: Reference to the long accumulator that will be updated with the decoded value. - name: buffer type: - text: ReadOnlyMemory @@ -326,22 +324,22 @@ body: The decoded value is added to delta using zigzag decoding, which handles both positive and negative values.

-- api3: TryWriteValue(int, Span, ref int) - id: PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char__System_Int32__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L237 +- api3: TryWriteValue(long, Span, ref int) + id: PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int64_System_Span_System_Char__System_Int32__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L234 metadata: - uid: PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int32,System.Span{System.Char},System.Int32@) - commentId: M:PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int32,System.Span{System.Char},System.Int32@) + uid: PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int64,System.Span{System.Char},System.Int32@) + commentId: M:PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int64,System.Span{System.Char},System.Int32@) - markdown: Attempts to write an encoded integer value to a polyline buffer, updating the specified position. -- code: public static bool TryWriteValue(int delta, Span buffer, ref int position) +- code: public static bool TryWriteValue(long delta, Span buffer, ref int position) - h4: Parameters - parameters: - name: delta type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 description: >- - The integer value to encode and write to the buffer. This value typically represents the difference between consecutive + The long value to encode and write to the buffer. This value typically represents the difference between consecutive coordinate values in polyline encoding. - name: buffer @@ -394,7 +392,7 @@ body:

- Before writing, the method validates that sufficient space is available in the buffer by calling . + Before writing, the method validates that sufficient space is available in the buffer by calling . If the buffer does not have enough remaining capacity, the method returns false without modifying the buffer or position. @@ -402,12 +400,12 @@ body:

- This method is the inverse of and can be used to encode coordinate deltas for polyline serialization. + This method is the inverse of and can be used to encode coordinate deltas for polyline serialization.

- api3: ValidateBlockLength(ReadOnlySpan) id: PolylineAlgorithm_PolylineEncoding_ValidateBlockLength_System_ReadOnlySpan_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L438 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L436 metadata: uid: PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) @@ -441,7 +439,7 @@ body: description: Thrown when a block exceeds 7 characters or the polyline does not end with a valid block terminator. - api3: ValidateCharRange(ReadOnlySpan) id: PolylineAlgorithm_PolylineEncoding_ValidateCharRange_System_ReadOnlySpan_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L392 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L390 metadata: uid: PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) @@ -479,7 +477,7 @@ body: description: Thrown when an invalid character is found in the polyline segment. - api3: ValidateFormat(ReadOnlySpan) id: PolylineAlgorithm_PolylineEncoding_ValidateFormat_System_ReadOnlySpan_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncoding.cs#L370 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncoding.cs#L368 metadata: uid: PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions-1.yml b/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions-1.yml new file mode 100644 index 00000000..e9f6d504 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions-1.yml @@ -0,0 +1,121 @@ +### YamlMime:ApiPage +title: Class PolylineEncodingOptions +body: +- api1: Class PolylineEncodingOptions + id: PolylineAlgorithm_PolylineEncodingOptions_1 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L19 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions`1 + commentId: T:PolylineAlgorithm.PolylineEncodingOptions`1 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Per-call options for a chunked encoding operation. +- code: public sealed class PolylineEncodingOptions +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate type understood by the formatter. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions-1.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Pass an instance of this class to the chunked + + overload to control + + the delta baseline used at the start of each chunk. When is + + false the formatter's built-in baseline (or zero) is used, which is equivalent + + to the existing default behaviour. +- h2: Constructors +- api3: PolylineEncodingOptions() + id: PolylineAlgorithm_PolylineEncodingOptions_1__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L26 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions`1.#ctor + commentId: M:PolylineAlgorithm.PolylineEncodingOptions`1.#ctor +- markdown: >- + Initializes a new instance of with no + + previous coordinate (formatter default baseline will be used). +- code: public PolylineEncodingOptions() +- api3: PolylineEncodingOptions(TValue) + id: PolylineAlgorithm_PolylineEncodingOptions_1__ctor__0_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L35 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions`1.#ctor(`0) + commentId: M:PolylineAlgorithm.PolylineEncodingOptions`1.#ctor(`0) +- markdown: >- + Initializes a new instance of with the + + specified previous coordinate used to seed the delta baseline. +- code: public PolylineEncodingOptions(TValue previous) +- h4: Parameters +- parameters: + - name: previous + type: + - TValue + description: The last coordinate of the previous chunk, used to seed the delta baseline. +- h2: Properties +- api3: HasPrevious + id: PolylineAlgorithm_PolylineEncodingOptions_1_HasPrevious + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L46 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions`1.HasPrevious + commentId: P:PolylineAlgorithm.PolylineEncodingOptions`1.HasPrevious +- markdown: >- + Gets a value indicating whether a previous coordinate has been supplied to seed the delta + + baseline. When false the formatter's built-in baseline is used as the + + starting point (which defaults to zero when no baseline has been configured), equivalent to + + the existing default behaviour. +- code: public bool HasPrevious { get; } +- h4: Property Value +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean +- api3: Previous + id: PolylineAlgorithm_PolylineEncodingOptions_1_Previous + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L52 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions`1.Previous + commentId: P:PolylineAlgorithm.PolylineEncodingOptions`1.Previous +- markdown: >- + Gets the last coordinate of the previous chunk, used to seed the delta baseline. + + Only meaningful when is true. +- code: public TValue Previous { get; } +- h4: Property Value +- parameters: + - type: + - TValue +languageId: csharp +metadata: + description: Per-call options for a chunked encoding operation. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions.yml b/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions.yml deleted file mode 100644 index 1947eac1..00000000 --- a/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptions.yml +++ /dev/null @@ -1,127 +0,0 @@ -### YamlMime:ApiPage -title: Class PolylineEncodingOptions -body: -- api1: Class PolylineEncodingOptions - id: PolylineAlgorithm_PolylineEncodingOptions - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L29 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptions - commentId: T:PolylineAlgorithm.PolylineEncodingOptions -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Provides configuration options for polyline encoding operations. -- code: public sealed class PolylineEncodingOptions -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html -- h4: Inherited Members -- list: - - text: object.Equals(object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - - text: object.Equals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) - - text: object.GetHashCode() - url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode - - text: object.GetType() - url: https://learn.microsoft.com/dotnet/api/system.object.gettype - - text: object.ReferenceEquals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals - - text: object.ToString() - url: https://learn.microsoft.com/dotnet/api/system.object.tostring -- h2: Remarks -- markdown: >- -

- - This class allows you to configure various aspects of polyline encoding, including: - -

- -
  • The level for coordinate encoding
  • The for memory allocation strategy
  • The for diagnostic logging
- -

- - All properties have internal setters and should be configured through a builder or factory pattern. - -

-- h2: Properties -- api3: LoggerFactory - id: PolylineAlgorithm_PolylineEncodingOptions_LoggerFactory - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L41 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory - commentId: P:PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory -- markdown: Gets the logger factory used for diagnostic logging during encoding operations. -- code: public ILoggerFactory LoggerFactory { get; } -- h4: Property Value -- parameters: - - type: - - text: ILoggerFactory - url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory -- h4: Remarks -- markdown: >- - The default logger factory is , which does not log any messages. - - To enable logging, provide a custom implementation. -- api3: Precision - id: PolylineAlgorithm_PolylineEncodingOptions_Precision - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L60 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptions.Precision - commentId: P:PolylineAlgorithm.PolylineEncodingOptions.Precision -- markdown: Gets the precision level used for encoding coordinate values. -- code: public uint Precision { get; } -- h4: Property Value -- parameters: - - type: - - text: uint - url: https://learn.microsoft.com/dotnet/api/system.uint32 -- h4: Remarks -- markdown: >- -

- - The precision determines the number of decimal places to which each coordinate value (latitude or longitude) - - is multiplied and truncated (not rounded) before encoding. For example, a precision of 5 means each coordinate is multiplied by 10^5 - - and truncated to an integer before encoding. - -

- -

- - This setting does not directly correspond to a physical distance or accuracy in meters, but rather controls - - the granularity of the encoded values. - -

-- api3: StackAllocLimit - id: PolylineAlgorithm_PolylineEncodingOptions_StackAllocLimit - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L73 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptions.StackAllocLimit - commentId: P:PolylineAlgorithm.PolylineEncodingOptions.StackAllocLimit -- markdown: Gets the maximum buffer size (in characters) that can be allocated on the stack for encoding operations. -- code: public int StackAllocLimit { get; } -- h4: Property Value -- parameters: - - type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 -- h4: Remarks -- markdown: >- - When the required buffer size for encoding exceeds this limit, memory will be allocated on the heap instead of the stack. - - This setting specifically applies to stack allocation of character arrays (stackalloc char[]) used during polyline encoding, - - balancing performance and stack safety. -languageId: csharp -metadata: - description: Provides configuration options for polyline encoding operations. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml b/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml deleted file mode 100644 index 66ae63b8..00000000 --- a/api-reference/0.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml +++ /dev/null @@ -1,141 +0,0 @@ -### YamlMime:ApiPage -title: Class PolylineEncodingOptionsBuilder -body: -- api1: Class PolylineEncodingOptionsBuilder - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L15 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder - commentId: T:PolylineAlgorithm.PolylineEncodingOptionsBuilder -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Provides a builder for configuring options for polyline encoding operations. -- code: public sealed class PolylineEncodingOptionsBuilder -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html -- h4: Inherited Members -- list: - - text: object.Equals(object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - - text: object.Equals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) - - text: object.GetHashCode() - url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode - - text: object.GetType() - url: https://learn.microsoft.com/dotnet/api/system.object.gettype - - text: object.ReferenceEquals(object, object) - url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals - - text: object.ToString() - url: https://learn.microsoft.com/dotnet/api/system.object.tostring -- h2: Methods -- api3: Build() - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Build - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L38 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build -- markdown: Builds a new instance using the configured options. -- code: public PolylineEncodingOptions Build() -- h4: Returns -- parameters: - - type: - - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html - description: A configured instance. -- api3: Create() - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Create - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L28 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create -- markdown: Creates a new instance for the specified coordinate type. -- code: public static PolylineEncodingOptionsBuilder Create() -- h4: Returns -- parameters: - - type: - - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html - description: An instance for configuring polyline encoding options. -- api3: WithLoggerFactory(ILoggerFactory) - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithLoggerFactory_Microsoft_Extensions_Logging_ILoggerFactory_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L97 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) -- markdown: Configures the to be used for logging during polyline encoding operations. -- code: public PolylineEncodingOptionsBuilder WithLoggerFactory(ILoggerFactory loggerFactory) -- h4: Parameters -- parameters: - - name: loggerFactory - type: - - text: ILoggerFactory - url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory - description: The instance to use for logging. If null, a will be used instead. -- h4: Returns -- parameters: - - type: - - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html - description: The current instance for method chaining. -- api3: WithPrecision(uint) - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithPrecision_System_UInt32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L82 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithPrecision(System.UInt32) - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithPrecision(System.UInt32) -- markdown: Sets the coordinate encoding precision. -- code: public PolylineEncodingOptionsBuilder WithPrecision(uint precision) -- h4: Parameters -- parameters: - - name: precision - type: - - text: uint - url: https://learn.microsoft.com/dotnet/api/system.uint32 - description: The number of decimal places to use for encoding coordinate values. Default is 5. -- h4: Returns -- parameters: - - type: - - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html - description: The current instance for method chaining. -- api3: WithStackAllocLimit(int) - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithStackAllocLimit_System_Int32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/review-github-templates/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L61 - metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithStackAllocLimit(System.Int32) - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithStackAllocLimit(System.Int32) -- markdown: Configures the buffer size used for stack allocation during polyline encoding operations. -- code: public PolylineEncodingOptionsBuilder WithStackAllocLimit(int stackAllocLimit) -- h4: Parameters -- parameters: - - name: stackAllocLimit - type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: The maximum buffer size to use for stack allocation. Must be greater than or equal to 1. -- h4: Returns -- parameters: - - type: - - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html - description: The current instance for method chaining. -- h4: Remarks -- markdown: This method allows customization of the internal buffer size for encoding, which can impact performance and memory usage. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentOutOfRangeException - url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception - description: Thrown if stackAllocLimit is less than 1. -languageId: csharp -metadata: - description: Provides a builder for configuring options for polyline encoding operations. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineFormatter-2.yml b/api-reference/0.0/PolylineAlgorithm.PolylineFormatter-2.yml new file mode 100644 index 00000000..22a67648 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineFormatter-2.yml @@ -0,0 +1,229 @@ +### YamlMime:ApiPage +title: Class PolylineFormatter +body: +- api1: Class PolylineFormatter + id: PolylineAlgorithm_PolylineFormatter_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L23 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2 + commentId: T:PolylineAlgorithm.PolylineFormatter`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: A sealed, immutable formatter that implements . +- code: 'public sealed class PolylineFormatter : IPolylineFormatter' +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate or item type. + - name: TPolyline + description: The polyline surface type. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineFormatter + url: PolylineAlgorithm.PolylineFormatter-2.html +- h4: Implements +- list: + - text: IPolylineFormatter + url: PolylineAlgorithm.Abstraction.IPolylineFormatter-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Instances are constructed exclusively through . + + The sealed modifier allows the JIT to devirtualise and inline calls to the + + interface methods in the encoding/decoding hot loop. +- h2: Properties +- api3: Width + id: PolylineAlgorithm_PolylineFormatter_2_Width + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L46 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.Width + commentId: P:PolylineAlgorithm.PolylineFormatter`2.Width +- markdown: >- + Gets the number of values (columns) per encoded item. + + This is the required length of the passed to + + and the length of the span received in . +- code: public int Width { get; } +- h4: Property Value +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 +- h2: Methods +- api3: CreateItem(ReadOnlySpan) + id: PolylineAlgorithm_PolylineFormatter_2_CreateItem_System_ReadOnlySpan_System_Int64__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L84 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.CreateItem(System.ReadOnlySpan{System.Int64}) + commentId: M:PolylineAlgorithm.PolylineFormatter`2.CreateItem(System.ReadOnlySpan{System.Int64}) +- markdown: >- + Reconstructs a TValue from the given accumulated scaled integer values. + + Called once per decoded item in the decoding loop. Implementations are responsible for + + denormalizing the raw scaled integers (e.g. dividing by the precision factor and adding back + + any baseline) before constructing the item. +- code: public TValue CreateItem(ReadOnlySpan values) +- h4: Parameters +- parameters: + - name: values + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + - '>' + description: >- + The raw accumulated scaled integer values decoded from the polyline. Each element corresponds to + + the same column position as in . These are the direct output of the + + delta-accumulation loop in the decoder before any denormalization is applied. +- h4: Returns +- parameters: + - type: + - TValue + description: A TValue reconstructed from values. +- h4: Exceptions +- parameters: + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: >- + Thrown when no factory delegate was supplied via + + . +- api3: GetBaseline(int) + id: PolylineAlgorithm_PolylineFormatter_2_GetBaseline_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L49 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.GetBaseline(System.Int32) + commentId: M:PolylineAlgorithm.PolylineFormatter`2.GetBaseline(System.Int32) +- markdown: >- + Returns the baseline for the column at index, or 0 if none is configured. + + The encoder uses this as the starting point for the first item's delta computation: the initial + + delta for the column is scaled_first_value − baseline rather than scaled_first_value. +- code: public long GetBaseline(int index) +- h4: Parameters +- parameters: + - name: index + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The zero-based column index. Must be in the range [0, ). +- h4: Returns +- parameters: + - type: + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: The baseline value, or 0 when no baseline has been defined for the column. +- api3: GetValues(TValue, Span) + id: PolylineAlgorithm_PolylineFormatter_2_GetValues__0_System_Span_System_Int64__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L56 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.GetValues(`0,System.Span{System.Int64}) + commentId: M:PolylineAlgorithm.PolylineFormatter`2.GetValues(`0,System.Span{System.Int64}) +- markdown: >- + Extracts and scales all column values from item into the values span. + + Called once per item in the encoding loop. +- code: public void GetValues(TValue item, Span values) +- h4: Parameters +- parameters: + - name: item + type: + - TValue + description: The source item from which column values are extracted. + - name: values + type: + - text: Span + url: https://learn.microsoft.com/dotnet/api/system.span-1 + - < + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + - '>' + description: Output buffer that receives the scaled integer values. Its length must equal . +- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when values.Length does not equal . +- api3: Read(TPolyline) + id: PolylineAlgorithm_PolylineFormatter_2_Read__1_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L76 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.Read(`1) + commentId: M:PolylineAlgorithm.PolylineFormatter`2.Read(`1) +- markdown: Extracts the character buffer from a TPolyline for the decoder to read. +- code: public ReadOnlyMemory Read(TPolyline polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The polyline to read from. +- h4: Returns +- parameters: + - type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A of representing the encoded characters. +- api3: Write(ReadOnlyMemory) + id: PolylineAlgorithm_PolylineFormatter_2_Write_System_ReadOnlyMemory_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineFormatter.cs#L72 + metadata: + uid: PolylineAlgorithm.PolylineFormatter`2.Write(System.ReadOnlyMemory{System.Char}) + commentId: M:PolylineAlgorithm.PolylineFormatter`2.Write(System.ReadOnlyMemory{System.Char}) +- markdown: Creates a TPolyline from the encoded character buffer produced by the encoder. +- code: public TPolyline Write(ReadOnlyMemory encoded) +- h4: Parameters +- parameters: + - name: encoded + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The encoded polyline as a read-only memory of characters. +- h4: Returns +- parameters: + - type: + - TPolyline + description: A TPolyline wrapping or derived from encoded. +languageId: csharp +metadata: + description: A sealed, immutable formatter that implements . diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineItemFactory-1.yml b/api-reference/0.0/PolylineAlgorithm.PolylineItemFactory-1.yml new file mode 100644 index 00000000..6b583486 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineItemFactory-1.yml @@ -0,0 +1,54 @@ +### YamlMime:ApiPage +title: Delegate PolylineItemFactory +body: +- api1: Delegate PolylineItemFactory + id: PolylineAlgorithm_PolylineItemFactory_1 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineItemFactory.cs#L22 + metadata: + uid: PolylineAlgorithm.PolylineItemFactory`1 + commentId: T:PolylineAlgorithm.PolylineItemFactory`1 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: >- + Represents a factory method that reconstructs a T item from denormalized + + values decoded from a polyline. +- code: public delegate T PolylineItemFactory(ReadOnlySpan values) +- h4: Parameters +- parameters: + - name: values + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + - '>' + description: >- + The denormalized values reconstructed by the polyline decoder. Each element corresponds to the + + original value that was encoded, with the precision factor divided out and any + + baseline added back. The span length equals the number of columns defined via + + . +- h4: Returns +- parameters: + - type: + - T + description: A T instance reconstructed from values. +- h4: Type Parameters +- parameters: + - name: T + description: The coordinate or item type to create. +languageId: csharp +metadata: + description: >- + Represents a factory method that reconstructs a T item from denormalized + + values decoded from a polyline. diff --git a/api-reference/0.0/PolylineAlgorithm.PolylineOptions-2.yml b/api-reference/0.0/PolylineAlgorithm.PolylineOptions-2.yml new file mode 100644 index 00000000..7b80a134 --- /dev/null +++ b/api-reference/0.0/PolylineAlgorithm.PolylineOptions-2.yml @@ -0,0 +1,158 @@ +### YamlMime:ApiPage +title: Class PolylineOptions +body: +- api1: Class PolylineOptions + id: PolylineAlgorithm_PolylineOptions_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineOptions.cs#L23 + metadata: + uid: PolylineAlgorithm.PolylineOptions`2 + commentId: T:PolylineAlgorithm.PolylineOptions`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides unified configuration for a formatter-driven encoding or decoding operation. +- code: public sealed class PolylineOptions +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate or item type understood by the formatter. + - name: TPolyline + description: The polyline surface type understood by the formatter. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineOptions + url: PolylineAlgorithm.PolylineOptions-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Supply an and optional settings, + + then pass this instance to and/or + + . +- h2: Constructors +- api3: PolylineOptions(IPolylineFormatter, int, ILoggerFactory?) + id: PolylineAlgorithm_PolylineOptions_2__ctor_PolylineAlgorithm_Abstraction_IPolylineFormatter__0__1__System_Int32_Microsoft_Extensions_Logging_ILoggerFactory_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineOptions.cs#L41 + metadata: + uid: PolylineAlgorithm.PolylineOptions`2.#ctor(PolylineAlgorithm.Abstraction.IPolylineFormatter{`0,`1},System.Int32,Microsoft.Extensions.Logging.ILoggerFactory) + commentId: M:PolylineAlgorithm.PolylineOptions`2.#ctor(PolylineAlgorithm.Abstraction.IPolylineFormatter{`0,`1},System.Int32,Microsoft.Extensions.Logging.ILoggerFactory) +- markdown: Initializes a new instance of . +- code: public PolylineOptions(IPolylineFormatter formatter, int stackAllocLimit = 512, ILoggerFactory? loggerFactory = null) +- h4: Parameters +- parameters: + - name: formatter + type: + - text: IPolylineFormatter + url: PolylineAlgorithm.Abstraction.IPolylineFormatter-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' + description: >- + The unified formatter that handles all type-specific concerns: value extraction, item + + reconstruction, and polyline surface conversion. Must not be null. + - name: stackAllocLimit + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The maximum buffer size (in characters) for stack allocation. Defaults to 512. + optional: true + - name: loggerFactory + type: + - text: ILoggerFactory + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory + - '?' + description: >- + The logger factory for diagnostic logging. Pass null to use + + . + optional: true +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when formatter is null. +- h2: Properties +- api3: Formatter + id: PolylineAlgorithm_PolylineOptions_2_Formatter + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineOptions.cs#L58 + metadata: + uid: PolylineAlgorithm.PolylineOptions`2.Formatter + commentId: P:PolylineAlgorithm.PolylineOptions`2.Formatter +- markdown: >- + Gets the unified formatter that handles value extraction, item reconstruction, and polyline + + surface conversion. +- code: public IPolylineFormatter Formatter { get; } +- h4: Property Value +- parameters: + - type: + - text: IPolylineFormatter + url: PolylineAlgorithm.Abstraction.IPolylineFormatter-2.html + - < + - TValue + - ',' + - " " + - TPolyline + - '>' +- api3: LoggerFactory + id: PolylineAlgorithm_PolylineOptions_2_LoggerFactory + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineOptions.cs#L71 + metadata: + uid: PolylineAlgorithm.PolylineOptions`2.LoggerFactory + commentId: P:PolylineAlgorithm.PolylineOptions`2.LoggerFactory +- markdown: >- + Gets the logger factory used for diagnostic logging during encoding and decoding operations. + + Defaults to . +- code: public ILoggerFactory LoggerFactory { get; } +- h4: Property Value +- parameters: + - type: + - text: ILoggerFactory + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory +- api3: StackAllocLimit + id: PolylineAlgorithm_PolylineOptions_2_StackAllocLimit + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/copilot/extend-polylinealgorithm-multi-dimensional-support/src/PolylineAlgorithm/PolylineOptions.cs#L65 + metadata: + uid: PolylineAlgorithm.PolylineOptions`2.StackAllocLimit + commentId: P:PolylineAlgorithm.PolylineOptions`2.StackAllocLimit +- markdown: >- + Gets the maximum buffer size (in characters) that may be allocated on the stack for encoding. + + When the required buffer size exceeds this limit, memory is rented from + + instead. Defaults to 512. +- code: public int StackAllocLimit { get; } +- h4: Property Value +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 +languageId: csharp +metadata: + description: Provides unified configuration for a formatter-driven encoding or decoding operation. diff --git a/api-reference/0.0/PolylineAlgorithm.yml b/api-reference/0.0/PolylineAlgorithm.yml index b60dc3c0..3c1add37 100644 --- a/api-reference/0.0/PolylineAlgorithm.yml +++ b/api-reference/0.0/PolylineAlgorithm.yml @@ -16,10 +16,26 @@ body: url: PolylineAlgorithm.Extensions.html - h3: Classes - parameters: + - type: + text: FormatterBuilder + url: PolylineAlgorithm.FormatterBuilder-2.html + description: Provides a fluent builder for constructing a . - type: text: InvalidPolylineException url: PolylineAlgorithm.InvalidPolylineException.html description: Exception thrown when a polyline is determined to be malformed or invalid during processing. + - type: + text: PolylineDecoder + url: PolylineAlgorithm.PolylineDecoder-2.html + description: Decodes encoded polyline representations into sequences of geographic coordinates. + - type: + text: PolylineDecodingOptions + url: PolylineAlgorithm.PolylineDecodingOptions-1.html + description: Per-call options for a chunked decoding operation. + - type: + text: PolylineEncoder + url: PolylineAlgorithm.PolylineEncoder-2.html + description: Encodes sequences of geographic coordinates into encoded polyline representations. - type: text: PolylineEncoding url: PolylineAlgorithm.PolylineEncoding.html @@ -28,11 +44,24 @@ body: geographic coordinate values. - type: - text: PolylineEncodingOptions - url: PolylineAlgorithm.PolylineEncodingOptions.html - description: Provides configuration options for polyline encoding operations. + text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions-1.html + description: Per-call options for a chunked encoding operation. + - type: + text: PolylineFormatter + url: PolylineAlgorithm.PolylineFormatter-2.html + description: A sealed, immutable formatter that implements . - type: - text: PolylineEncodingOptionsBuilder - url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html - description: Provides a builder for configuring options for polyline encoding operations. + text: PolylineOptions + url: PolylineAlgorithm.PolylineOptions-2.html + description: Provides unified configuration for a formatter-driven encoding or decoding operation. +- h3: Delegates +- parameters: + - type: + text: PolylineItemFactory + url: PolylineAlgorithm.PolylineItemFactory-1.html + description: >- + Represents a factory method that reconstructs a T item from denormalized + + values decoded from a polyline. languageId: csharp diff --git a/api-reference/0.0/toc.yml b/api-reference/0.0/toc.yml index d1ffc58a..69f74afd 100644 --- a/api-reference/0.0/toc.yml +++ b/api-reference/0.0/toc.yml @@ -3,32 +3,40 @@ href: PolylineAlgorithm.yml items: - name: Classes + - name: FormatterBuilder + href: PolylineAlgorithm.FormatterBuilder-2.yml - name: InvalidPolylineException href: PolylineAlgorithm.InvalidPolylineException.yml + - name: PolylineDecoder + href: PolylineAlgorithm.PolylineDecoder-2.yml + - name: PolylineDecodingOptions + href: PolylineAlgorithm.PolylineDecodingOptions-1.yml + - name: PolylineEncoder + href: PolylineAlgorithm.PolylineEncoder-2.yml - name: PolylineEncoding href: PolylineAlgorithm.PolylineEncoding.yml - - name: PolylineEncodingOptions - href: PolylineAlgorithm.PolylineEncodingOptions.yml - - name: PolylineEncodingOptionsBuilder - href: PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml + - name: PolylineEncodingOptions + href: PolylineAlgorithm.PolylineEncodingOptions-1.yml + - name: PolylineFormatter + href: PolylineAlgorithm.PolylineFormatter-2.yml + - name: PolylineOptions + href: PolylineAlgorithm.PolylineOptions-2.yml + - name: Delegates + - name: PolylineItemFactory + href: PolylineAlgorithm.PolylineItemFactory-1.yml - name: PolylineAlgorithm.Abstraction href: PolylineAlgorithm.Abstraction.yml items: - - name: Classes - - name: AbstractPolylineDecoder - href: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml - - name: AbstractPolylineEncoder - href: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml - name: Interfaces - name: IPolylineDecoder href: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml - name: IPolylineEncoder href: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml + - name: IPolylineFormatter + href: PolylineAlgorithm.Abstraction.IPolylineFormatter-2.yml - name: PolylineAlgorithm.Extensions href: PolylineAlgorithm.Extensions.yml items: - name: Classes - - name: PolylineDecoderExtensions - href: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml - name: PolylineEncoderExtensions href: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs index f28c8141..43804520 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs @@ -7,11 +7,11 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; -using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm; using PolylineAlgorithm.Utility; /// -/// Benchmarks for . +/// Benchmarks for . /// public class PolylineDecoderBenchmark { private readonly Consumer _consumer = new(); @@ -40,17 +40,50 @@ public class PolylineDecoderBenchmark { /// /// String polyline decoder instance. /// - private readonly StringPolylineDecoder _stringDecoder = new(); + private readonly PolylineDecoder _stringDecoder = CreateStringDecoder(); /// /// Char array polyline decoder instance. /// - private readonly CharArrayPolylineDecoder _charArrayDecoder = new(); + private readonly PolylineDecoder _charArrayDecoder = CreateCharArrayDecoder(); /// - /// String polyline decoder instance. + /// Memory char polyline decoder instance. /// - private readonly MemoryCharPolylineDecoder _memoryCharDecoder = new(); + private readonly PolylineDecoder, (double Latitude, double Longitude)> _memoryCharDecoder = CreateMemoryDecoder(); + + private static PolylineFormatter<(double Latitude, double Longitude), T> BuildFormatter( + Func, T> write, Func> read) => + FormatterBuilder<(double Latitude, double Longitude), T>.Create() + .AddValue("lat", static c => c.Latitude) + .AddValue("lon", static c => c.Longitude) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(write, read) + .Build(); + + private static PolylineDecoder CreateStringDecoder() { + var fmt = BuildFormatter( + static m => new string(m.Span), + static s => s?.AsMemory() ?? Memory.Empty); + return new PolylineDecoder( + new PolylineOptions<(double Latitude, double Longitude), string>(fmt)); + } + + private static PolylineDecoder CreateCharArrayDecoder() { + var fmt = BuildFormatter( + static m => m.ToArray(), + static a => a?.AsMemory() ?? Memory.Empty); + return new PolylineDecoder( + new PolylineOptions<(double Latitude, double Longitude), char[]>(fmt)); + } + + private static PolylineDecoder, (double Latitude, double Longitude)> CreateMemoryDecoder() { + var fmt = BuildFormatter>( + static m => m, + static m => m); + return new PolylineDecoder, (double Latitude, double Longitude)>( + new PolylineOptions<(double Latitude, double Longitude), ReadOnlyMemory>(fmt)); + } /// /// Sets up benchmark data. @@ -91,34 +124,4 @@ public void PolylineDecoder_Decode_Memory() { .Decode(Memory) .Consume(_consumer); } - - private sealed class StringPolylineDecoder : AbstractPolylineDecoder { - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { - return (latitude, longitude); - } - - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) { - return polyline?.AsMemory() ?? Memory.Empty; - } - } - - private sealed class CharArrayPolylineDecoder : AbstractPolylineDecoder { - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { - return (latitude, longitude); - } - - protected override ReadOnlyMemory GetReadOnlyMemory(in char[] polyline) { - return polyline?.AsMemory() ?? Memory.Empty; - } - } - - private sealed class MemoryCharPolylineDecoder : AbstractPolylineDecoder, (double Latitude, double Longitude)> { - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { - return (latitude, longitude); - } - - protected override ReadOnlyMemory GetReadOnlyMemory(in ReadOnlyMemory polyline) { - return polyline; - } - } -} \ No newline at end of file +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs index e0b97c5c..2e8d09f1 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs @@ -1,19 +1,12 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Extensions; +using PolylineAlgorithm; using PolylineAlgorithm.Utility; -using System.Collections.Generic; /// -/// Benchmarks for . +/// Benchmarks for . /// public class PolylineEncoderBenchmark { private readonly Consumer _consumer = new(); @@ -25,11 +18,6 @@ public class PolylineEncoderBenchmark { public int CoordinatesCount { get; set; } #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - /// - /// Coordinates as list. - /// - public List<(double Latitude, double Longitude)> List { get; private set; } - /// /// Coordinates as array. /// @@ -45,15 +33,26 @@ public class PolylineEncoderBenchmark { /// /// Polyline encoder instance. /// - private readonly StringPolylineEncoder _encoder = new(); + private readonly PolylineEncoder<(double Latitude, double Longitude), string> _encoder = CreateEncoder(); + + private static PolylineEncoder<(double Latitude, double Longitude), string> CreateEncoder() { + PolylineFormatter<(double Latitude, double Longitude), string> formatter = + FormatterBuilder<(double Latitude, double Longitude), string>.Create() + .AddValue("lat", static c => c.Latitude) + .AddValue("lon", static c => c.Longitude) + .WithReaderWriter(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); + + return new PolylineEncoder<(double Latitude, double Longitude), string>( + new PolylineOptions<(double Latitude, double Longitude), string>(formatter)); + } /// /// Sets up benchmark data. /// [GlobalSetup] public void SetupData() { - List = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; - Array = [.. List]; + Array = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; Memory = Array.AsMemory(); } @@ -74,19 +73,4 @@ public void PolylineEncoder_Encode_Array() { var polyline = _encoder.Encode(Array); _consumer.Consume(polyline); } - - /// - /// Benchmark: encode coordinates from list. - /// - [Benchmark] - public void PolylineEncoder_Encode_List() { - var polyline = _encoder.Encode(List); - _consumer.Consume(polyline); - } - - private sealed class StringPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); - protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; - } } diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs deleted file mode 100644 index d7dcb13e..00000000 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.NetTopologySuite.Sample; - -using global::NetTopologySuite.Geometries; -using PolylineAlgorithm.Abstraction; -using System; - -/// -/// Polyline decoder using NetTopologySuite. -/// -internal sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder { - /// - /// Creates a NetTopologySuite point from latitude and longitude. - /// - /// Latitude value. - /// Longitude value. - /// Point instance. - protected override Point CreateCoordinate(double latitude, double longitude) { - // NetTopologySuite Point: x = longitude, y = latitude - return new Point(longitude, latitude); - } - - /// - /// Converts polyline string to read-only memory. - /// - /// Encoded polyline string. - /// ReadOnlyMemory of characters. - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) { - return polyline.AsMemory(); - } -} \ No newline at end of file diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs deleted file mode 100644 index c6719000..00000000 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.NetTopologySuite.Sample; - -using global::NetTopologySuite.Geometries; -using PolylineAlgorithm.Abstraction; - -/// -/// Polyline encoder using NetTopologySuite's Point type. -/// -internal sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder { - /// - /// Creates encoded polyline string from memory. - /// - /// Polyline memory. - /// Encoded polyline string. - protected override string CreatePolyline(ReadOnlyMemory polyline) { - if (polyline.IsEmpty) { - return string.Empty; - } - - return polyline.ToString(); - } - - /// - /// Gets latitude from point. - /// - /// Point instance. - /// Latitude value. - protected override double GetLatitude(Point current) { - ArgumentNullException.ThrowIfNull(current); - - // NetTopologySuite Point: Y = latitude - return current.Y; - } - - /// - /// Gets longitude from point. - /// - /// Point instance. - /// Longitude value. - protected override double GetLongitude(Point current) { - ArgumentNullException.ThrowIfNull(current); - - // NetTopologySuite Point: X = longitude - return current.X; - } -} \ No newline at end of file diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/Program.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/Program.cs index ca9a8a6f..766a0771 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/Program.cs +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/Program.cs @@ -4,10 +4,27 @@ // using NetTopologySuite.Geometries; -using PolylineAlgorithm.NetTopologySuite.Sample; +using PolylineAlgorithm; public class Program { public static void Main(string[] args) { + // Build a formatter for NetTopologySuite's Point type. + // NTS convention: Y = latitude, X = longitude. + PolylineFormatter formatter = + FormatterBuilder.Create() + .AddValue("lat", static p => p.Y) + .AddValue("lon", static p => p.X) + // The formatter automatically denormalizes scaled values, so v[0] = latitude, v[1] = longitude. + .WithValueFactory(static v => new Point(x: v[1], y: v[0])) + .WithReaderWriter( + static m => m.IsEmpty ? string.Empty : new string(m.Span), + static s => s.AsMemory()) + .Build(); + + PolylineOptions options = new(formatter); + PolylineEncoder encoder = new(options); + PolylineDecoder decoder = new(options); + // Sample route: Seattle → Bellevue → Redmond var points = new Point[] { @@ -16,9 +33,6 @@ public static void Main(string[] args) { new(x: -122.1215, y: 47.6740), // Redmond }; - var encoder = new NetTopologyPolylineEncoder(); - var decoder = new NetTopologyPolylineDecoder(); - // Encode string encoded = encoder.Encode(points); diff --git a/samples/PolylineAlgorithm.SensorData.Sample/Program.cs b/samples/PolylineAlgorithm.SensorData.Sample/Program.cs index e14977f9..c7fe8f62 100644 --- a/samples/PolylineAlgorithm.SensorData.Sample/Program.cs +++ b/samples/PolylineAlgorithm.SensorData.Sample/Program.cs @@ -3,10 +3,34 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. // +using PolylineAlgorithm; using PolylineAlgorithm.SensorData.Sample; +using System; public static class Program { + // 2020-01-01 00:00:00 UTC in Unix seconds. Used as the delta-encoding baseline for timestamps + // so that the first absolute delta stays within the int32 safe range of the polyline algorithm. + private const long TimestampBaseEpoch = 1_577_836_800L; + public static void Main(string[] args) { + // Build a formatter for SensorReading: timestamp (Unix seconds, precision 0) + temperature. + // SetBaseline keeps the first timestamp delta small; the formatter adds it back when decoding. + PolylineFormatter formatter = + FormatterBuilder.Create() + .AddValue("timestamp", static r => (double)r.Timestamp.ToUnixTimeSeconds(), precision: 0) + .SetBaseline(TimestampBaseEpoch) + .AddValue("temperature", static r => r.Temperature, precision: 5) + // The formatter automatically denormalizes: v[0] = Unix seconds, v[1] = temperature. + .WithValueFactory(static v => new SensorReading( + DateTimeOffset.FromUnixTimeSeconds((long)v[0]), + v[1])) + .WithReaderWriter(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); + + PolylineOptions options = new(formatter); + PolylineEncoder encoder = new(options); + PolylineDecoder decoder = new(options); + // Sample temperature readings from a sensor over six seconds var readings = new SensorReading[] { @@ -19,9 +43,6 @@ public static void Main(string[] args) { new(DateTimeOffset.UtcNow.AddSeconds(6), 22.3), }; - var encoder = new SensorDataEncoder(); - var decoder = new SensorDataDecoder(); - // Encode string encoded = encoder.Encode(readings); diff --git a/samples/PolylineAlgorithm.SensorData.Sample/SensorDataDecoder.cs b/samples/PolylineAlgorithm.SensorData.Sample/SensorDataDecoder.cs deleted file mode 100644 index 9c63d647..00000000 --- a/samples/PolylineAlgorithm.SensorData.Sample/SensorDataDecoder.cs +++ /dev/null @@ -1,107 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.SensorData.Sample; - -using PolylineAlgorithm.Abstraction; -using System.Collections.Generic; -using System.Threading; - -/// -/// Decodes a compact polyline string produced by back into a sequence -/// of values. -/// -/// -/// -/// This class demonstrates implementing for a custom -/// scalar type, following the same structural pattern as . -/// -/// -/// Each encoded pair consists of a delta-compressed Unix timestamp (seconds since Unix epoch, precision 0) -/// followed by a delta-compressed temperature value (at ). -/// Both are recovered and used to reconstruct the original . -/// -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("Sonar", "S4456:Parameter validation in yielding methods should be wrapped", Justification = "Inlined by design to demonstrate a simple iterator without a wrapper method.")] -internal sealed class SensorDataDecoder : IPolylineDecoder { - /// - /// Initializes a new instance of the class with default encoding options. - /// - public SensorDataDecoder() - : this(new PolylineEncodingOptions()) { } - - /// - /// Initializes a new instance of the class with the specified encoding options. - /// - /// - /// The to use for decoding operations. - /// The value must match the precision used during encoding. - /// - /// - /// Thrown when is . - /// - public SensorDataDecoder(PolylineEncodingOptions options) { - ArgumentNullException.ThrowIfNull(options); - - Options = options; - } - - /// - /// Gets the encoding options used by this decoder. - /// - public PolylineEncodingOptions Options { get; } - - /// - /// Decodes a polyline string back into a sequence of values. - /// - /// - /// The polyline-encoded string produced by . - /// - /// - /// A that can be used to cancel the decoding operation. - /// - /// - /// An of whose - /// and values - /// are recovered from the encoded string. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when is empty. - /// - /// - /// Thrown when requests cancellation. - /// - public IEnumerable Decode(string polyline, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(polyline); - - if (polyline.Length < 1) { - throw new ArgumentException("Encoded polyline must not be empty.", nameof(polyline)); - } - - ReadOnlyMemory memory = polyline.AsMemory(); - int position = 0; - // Mirror the encoder's base epoch so the first delta decodes back to the correct Unix seconds. - int accumulatedTimestamp = SensorDataEncoder.TimestampBaseEpochSeconds; - int accumulatedTemperature = 0; - - while (position < memory.Length) { - cancellationToken.ThrowIfCancellationRequested(); - - // Read Unix timestamp delta (precision 0) then temperature delta. - if (!PolylineEncoding.TryReadValue(ref accumulatedTimestamp, memory, ref position) - || !PolylineEncoding.TryReadValue(ref accumulatedTemperature, memory, ref position)) { - yield break; - } - - long unixSeconds = (long)PolylineEncoding.Denormalize(accumulatedTimestamp, precision: 0); - double temperature = PolylineEncoding.Denormalize(accumulatedTemperature, Options.Precision); - - yield return new SensorReading(DateTimeOffset.FromUnixTimeSeconds(unixSeconds), temperature); - } - } -} diff --git a/samples/PolylineAlgorithm.SensorData.Sample/SensorDataEncoder.cs b/samples/PolylineAlgorithm.SensorData.Sample/SensorDataEncoder.cs deleted file mode 100644 index be199e4d..00000000 --- a/samples/PolylineAlgorithm.SensorData.Sample/SensorDataEncoder.cs +++ /dev/null @@ -1,135 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.SensorData.Sample; - -using PolylineAlgorithm.Abstraction; -using System.Buffers; -using System.Threading; - -/// -/// Encodes a sequence of values into a compact polyline string -/// using the polyline delta-encoding algorithm applied to both the -/// and fields. -/// -/// -/// -/// This class demonstrates implementing for a custom -/// scalar type, following the same structural pattern as . -/// -/// -/// Because sensor readings carry two numeric dimensions (timestamp and temperature), the base class designed -/// for geographic coordinate pairs is not used. Instead, static -/// helpers are called directly to perform normalisation, delta computation, and character-level encoding. -/// -/// -/// Each reading is encoded as a pair of delta-compressed values: -/// the Unix timestamp in seconds (precision 0) followed by the temperature (at ). -/// -/// -internal sealed class SensorDataEncoder : IPolylineEncoder { - // 2020-01-01 00:00:00 UTC in Unix seconds. Used as the delta-encoding base for timestamps - // so that the first absolute delta stays within the int32 safe range of the polyline algorithm. - internal const int TimestampBaseEpochSeconds = 1_577_836_800; - - /// - /// Initializes a new instance of the class with default encoding options. - /// - public SensorDataEncoder() - : this(new PolylineEncodingOptions()) { } - - /// - /// Initializes a new instance of the class with the specified encoding options. - /// - /// - /// The to use for encoding operations. - /// - /// - /// Thrown when is . - /// - public SensorDataEncoder(PolylineEncodingOptions options) { - ArgumentNullException.ThrowIfNull(options); - - Options = options; - } - - /// - /// Gets the encoding options used by this encoder. - /// - public PolylineEncodingOptions Options { get; } - - /// - /// Encodes a sequence of values into a polyline string. - /// - /// - /// The sensor readings to encode. Each reading contributes a delta-compressed Unix timestamp - /// (seconds since Unix epoch, precision 0) and a delta-compressed temperature value. - /// Must contain at least one element. - /// - /// - /// A that can be used to cancel the encoding operation. - /// - /// - /// A polyline-encoded string representing the delta-compressed timestamp and temperature series. - /// - /// - /// Thrown when is empty. - /// - /// - /// Thrown when requests cancellation. - /// - public string Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) { - if (coordinates.Length < 1) { - throw new ArgumentException("Sequence must contain at least one element.", nameof(coordinates)); - } - - // Maximum number of ASCII characters required to encode a single 32-bit delta value - // using the polyline algorithm (ceil(32 bits / 5 bits per chunk) + sign bit = 7). - const int MaxEncodedCharsPerValue = 7; - - // Each reading encodes two values: Unix timestamp (precision 0) + temperature. - // The polyline algorithm uses signed int32 internally, limiting safe absolute values to ~1.07B. - // Current Unix time in seconds (~1.74B) exceeds this. We therefore delta-encode relative to - // 2020-01-01 00:00:00 UTC (= 1 577 836 800 s), keeping the initial delta well within range. - int previousTimestampNormalized = TimestampBaseEpochSeconds; - int previousTemperatureNormalized = 0; - int position = 0; - int length = coordinates.Length * 2 * MaxEncodedCharsPerValue; - - char[]? temp = length <= Options.StackAllocLimit - ? null - : ArrayPool.Shared.Rent(length); - - Span buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length); - - try { - for (int i = 0; i < coordinates.Length; i++) { - cancellationToken.ThrowIfCancellationRequested(); - - // Encode Unix timestamp in whole seconds (precision 0). - int normalizedTimestamp = PolylineEncoding.Normalize((double)coordinates[i].Timestamp.ToUnixTimeSeconds(), precision: 0); - int timestampDelta = normalizedTimestamp - previousTimestampNormalized; - - // Encode temperature at the configured precision. - int normalizedTemperature = PolylineEncoding.Normalize(coordinates[i].Temperature, Options.Precision); - int temperatureDelta = normalizedTemperature - previousTemperatureNormalized; - - if (!PolylineEncoding.TryWriteValue(timestampDelta, buffer, ref position) - || !PolylineEncoding.TryWriteValue(temperatureDelta, buffer, ref position)) { - throw new InvalidOperationException("Encoding buffer is too small to hold the encoded value."); - } - - previousTimestampNormalized = normalizedTimestamp; - previousTemperatureNormalized = normalizedTemperature; - } - - return buffer[..position].ToString(); - } finally { - if (temp is not null) { - ArrayPool.Shared.Return(temp); - } - } - } -} diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs deleted file mode 100644 index 1059abb9..00000000 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs +++ /dev/null @@ -1,204 +0,0 @@ -// -// 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.Internal; -using PolylineAlgorithm.Internal.Diagnostics; -using System.Runtime.CompilerServices; - -namespace PolylineAlgorithm.Abstraction; - -/// -/// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. -/// -/// -/// Derive from this class to implement a decoder for a specific polyline type. Override -/// and to provide type-specific behavior. -/// -/// The type that represents the encoded polyline input. -/// The type that represents a decoded geographic coordinate. -public abstract class AbstractPolylineDecoder : IPolylineDecoder { - private readonly ILogger> _logger; - - /// - /// Initializes a new instance of the class with default encoding options. - /// - protected AbstractPolylineDecoder() - : this(new PolylineEncodingOptions()) { } - - /// - /// Initializes a new instance of the class with the specified encoding options. - /// - /// - /// The to use for encoding operations. - /// - /// - /// Thrown when is . - /// - protected AbstractPolylineDecoder(PolylineEncodingOptions options) { - if (options is null) { - ExceptionGuard.ThrowArgumentNull(nameof(options)); - } - - Options = options; - _logger = Options - .LoggerFactory - .CreateLogger>(); - } - - /// - /// Gets the encoding options used by this polyline decoder. - /// - public PolylineEncodingOptions Options { get; } - - /// - /// Decodes an encoded into a sequence of instances, - /// with support for cancellation. - /// - /// - /// The instance containing the encoded polyline string to decode. - /// - /// - /// A that can be used to cancel the decoding operation. - /// - /// - /// An of representing the decoded latitude and longitude pairs. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when is empty. - /// - /// - /// Thrown when the polyline format is invalid or malformed at a specific position. - /// - /// - /// Thrown when is canceled during decoding. - /// - public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) { - const string OperationName = nameof(Decode); - - _logger?.LogOperationStartedDebug(OperationName); - - ValidateNullPolyline(polyline, _logger); - - ReadOnlyMemory 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); - } - } - - /// - /// Validates that the provided polyline is not . - /// - /// The polyline instance to validate. - /// An optional used to log a warning when validation fails. - /// - /// Thrown when is . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ValidateNullPolyline(TPolyline polyline, ILogger? logger) { - if (polyline is null) { - logger?.LogNullArgumentWarning(nameof(polyline)); - ExceptionGuard.ThrowArgumentNull(nameof(polyline)); - } - } - - /// - /// Validates that the polyline character sequence meets the minimum required length. - /// - /// The polyline character sequence to validate. - /// An optional used to log diagnostic messages when validation fails. - /// - /// Thrown when is shorter than the minimum allowed length. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ValidateSequence(ReadOnlyMemory 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); - } - } - - /// - /// Validates the format of the polyline character sequence, ensuring all characters are within the allowed range. - /// - /// - /// The read-only memory region of characters representing the polyline to validate. - /// - /// - /// An optional used to log a warning when format validation fails. - /// - /// - /// Thrown when the polyline contains characters outside the valid encoding range or has an invalid block structure. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual void ValidateFormat(ReadOnlyMemory sequence, ILogger? logger) { - try { - PolylineEncoding.ValidateFormat(sequence.Span); - } catch (ArgumentException ex) { - logger?.LogInvalidPolylineFormatWarning(ex); - - throw; - } - } - - /// - /// Extracts the underlying read-only memory region of characters from the specified polyline instance. - /// - /// - /// The instance from which to extract the character sequence. - /// - /// - /// A of representing the encoded polyline characters. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline); - - /// - /// Creates a instance from the specified latitude and longitude values. - /// - /// - /// The latitude component of the coordinate, in degrees. - /// - /// - /// The longitude component of the coordinate, in degrees. - /// - /// - /// A instance representing the specified geographic coordinate. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract TCoordinate CreateCoordinate(double latitude, double longitude); -} diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs deleted file mode 100644 index 1bcdb0ee..00000000 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs +++ /dev/null @@ -1,197 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Abstraction; - -using Microsoft.Extensions.Logging; -using PolylineAlgorithm; -using PolylineAlgorithm.Internal; -using PolylineAlgorithm.Internal.Diagnostics; -using System; -using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; - -/// -/// Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. -/// -/// -/// Derive from this class to implement an encoder for a specific coordinate and polyline type. Override -/// , , and to provide type-specific behavior. -/// -/// The type that represents a geographic coordinate to encode. -/// The type that represents the encoded polyline output. -public abstract class AbstractPolylineEncoder : IPolylineEncoder { - private readonly ILogger> _logger; - - /// - /// Initializes a new instance of the class with default encoding options. - /// - protected AbstractPolylineEncoder() - : this(new PolylineEncodingOptions()) { } - - /// - /// Initializes a new instance of the class with the specified encoding options. - /// - /// - /// The to use for encoding operations. - /// - /// Thrown when is - protected AbstractPolylineEncoder(PolylineEncodingOptions options) { - if (options is null) { - ExceptionGuard.ThrowArgumentNull(nameof(options)); - } - - Options = options; - _logger = Options - .LoggerFactory - .CreateLogger>(); - } - - /// - /// Gets the encoding options used by this polyline encoder. - /// - public PolylineEncodingOptions Options { get; } - - /// - /// Encodes a collection of instances into an encoded string. - /// - /// - /// The collection of objects to encode. - /// - /// - /// A that can be used to cancel the encoding operation. - /// - /// - /// An instance of representing the encoded coordinates. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when is an empty enumeration. - /// - /// - /// Thrown when the internal encoding buffer cannot accommodate the encoded value. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0051:Method is too long", Justification = "Method contains local methods. Actual method only 55 lines.")] - public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) { - const string OperationName = nameof(Encode); - - _logger - .LogOperationStartedDebug(OperationName); - - Debug.Assert(coordinates.Length >= 0, "Count must be non-negative."); - - ValidateEmptyCoordinates(ref coordinates, _logger); - - CoordinateDelta delta = new(); - - int position = 0; - int consumed = 0; - int length = GetMaxBufferLength(coordinates.Length); - - char[]? temp = length <= Options.StackAllocLimit - ? null - : ArrayPool.Shared.Rent(length); - - Span buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length); - - string encodedResult; - - try { - for (var i = 0; i < coordinates.Length; i++) { - cancellationToken.ThrowIfCancellationRequested(); - - delta - .Next( - PolylineEncoding.Normalize(GetLatitude(coordinates[i]), Options.Precision), - PolylineEncoding.Normalize(GetLongitude(coordinates[i]), Options.Precision) - ); - - if (!PolylineEncoding.TryWriteValue(delta.Latitude, buffer, ref position) - || !PolylineEncoding.TryWriteValue(delta.Longitude, buffer, ref position) - ) { - // This shouldn't happen, but if it does, log the error and throw an exception. - _logger - .LogOperationFailedDebug(OperationName); - _logger - .LogCannotWriteValueToBufferWarning(position, consumed); - - ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); - - } - - consumed++; - } - - encodedResult = buffer[..position].ToString(); - } finally { - if (temp is not null) { - ArrayPool.Shared.Return(temp); - } - } - - _logger - .LogOperationFinishedDebug(OperationName); - - return CreatePolyline(encodedResult.AsMemory()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int GetMaxBufferLength(int count) { - Debug.Assert(count > 0, "Count must be greater than zero."); - - int requestedBufferLength = count * 2 * Defaults.Polyline.Block.Length.Max; - - Debug.Assert(requestedBufferLength > 0, "Requested buffer length must be greater than zero."); - - return requestedBufferLength; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ValidateEmptyCoordinates(ref ReadOnlySpan coordinates, ILogger logger) { - if (coordinates.Length < 1) { - logger - .LogOperationFailedDebug(OperationName); - logger - .LogEmptyArgumentWarning(nameof(coordinates)); - - ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); - } - } - } - - /// - /// Creates a polyline instance from the provided read-only sequence of characters. - /// - /// A containing the encoded polyline characters. - /// - /// An instance of representing the encoded polyline. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract TPolyline CreatePolyline(ReadOnlyMemory polyline); - - /// - /// Extracts the longitude value from the specified coordinate. - /// - /// The coordinate from which to extract the longitude. - /// - /// The longitude value as a . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract double GetLongitude(TCoordinate current); - - /// - /// Extracts the latitude value from the specified coordinate. - /// - /// The coordinate from which to extract the latitude. - /// - /// The latitude value as a . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract double GetLatitude(TCoordinate current); -} - diff --git a/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs b/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs index 114f88fd..eb57148c 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs @@ -19,7 +19,7 @@ namespace PolylineAlgorithm.Abstraction; /// The coordinate type returned by the decoder. Typical implementations return a struct or class that /// contains latitude and longitude (for example a LatLng type or a ValueTuple<double,double>). /// -public interface IPolylineDecoder { +public interface IPolylineDecoder { /// /// Decodes the specified encoded polyline into an ordered sequence of geographic coordinates. /// The sequence preserves the original vertex order encoded by the . @@ -45,5 +45,5 @@ public interface IPolylineDecoder { /// /// Thrown when the provided requests cancellation. /// - IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default); + IEnumerable Decode(TPolyline polyline, PolylineDecodingOptions? options = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs index 9cea260b..724f4f73 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs @@ -73,5 +73,5 @@ public interface IPolylineEncoder { /// /// Thrown if the operation is canceled via . /// - TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default); + TPolyline Encode(ReadOnlySpan coordinates, PolylineEncodingOptions? options = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs b/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs new file mode 100644 index 00000000..c88a4aab --- /dev/null +++ b/src/PolylineAlgorithm/Abstraction/IPolylineFormatter.cs @@ -0,0 +1,77 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Abstraction; + +using System; + +/// +/// Defines how to extract and scale values from a for encoding, +/// reconstruct a from scaled values for decoding, +/// produce a from an encoded character buffer, and extract that buffer +/// back from a . +/// +/// The coordinate or item type. For example a struct with Latitude/Longitude. +/// The polyline surface type. For example or +/// of . +/// +/// Use to build a +/// that implements this interface. +/// +public interface IPolylineFormatter { + /// + /// Gets the number of values (columns) per encoded item. + /// This is the required length of the passed to + /// and the length of the span received in . + /// + int Width { get; } + + /// + /// Returns the baseline for the column at , or 0 if none is configured. + /// The encoder uses this as the starting point for the first item's delta computation: the initial + /// delta for the column is scaled_first_value − baseline rather than scaled_first_value. + /// + /// The zero-based column index. Must be in the range [0, ). + /// The baseline value, or 0 when no baseline has been defined for the column. + long GetBaseline(int index) => 0L; + + /// + /// Extracts and scales all column values from into the span. + /// Called once per item in the encoding loop. + /// + /// The source item from which column values are extracted. + /// + /// Output buffer that receives the scaled integer values. Its length must equal . + /// + void GetValues(TValue item, Span values); + + /// + /// Creates a from the encoded character buffer produced by the encoder. + /// + /// The encoded polyline as a read-only memory of characters. + /// A wrapping or derived from . + TPolyline Write(ReadOnlyMemory encoded); + + /// + /// Extracts the character buffer from a for the decoder to read. + /// + /// The polyline to read from. + /// A of representing the encoded characters. + ReadOnlyMemory Read(TPolyline polyline); + + /// + /// Reconstructs a from the given accumulated scaled integer values. + /// Called once per decoded item in the decoding loop. Implementations are responsible for + /// denormalizing the raw scaled integers (e.g. dividing by the precision factor and adding back + /// any baseline) before constructing the item. + /// + /// + /// The raw accumulated scaled integer values decoded from the polyline. Each element corresponds to + /// the same column position as in . These are the direct output of the + /// delta-accumulation loop in the decoder before any denormalization is applied. + /// + /// A reconstructed from . + TValue CreateItem(ReadOnlySpan values); +} diff --git a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs deleted file mode 100644 index ca48e099..00000000 --- a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Extensions; - -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Internal.Diagnostics; -using System; -using System.Collections.Generic; - -/// -/// Provides extension methods for the interface to facilitate decoding encoded polylines. -/// -public static class PolylineDecoderExtensions { - /// - /// Decodes an encoded polyline represented as a character array into a sequence of geographic coordinates. - /// - /// The coordinate type returned by the decoder. - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline as a character array to decode. The array is converted to a string internally. - /// - /// - /// An of containing the decoded coordinate pairs. - /// - /// - /// Thrown when or is . - /// - public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - if (polyline is null) { - ExceptionGuard.ThrowArgumentNull(nameof(polyline)); - } - - return decoder.Decode(new string(polyline)); - } - - /// - /// Decodes an encoded polyline represented as a read-only memory of characters into a sequence of geographic coordinates. - /// - /// The coordinate type returned by the decoder. - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline as a read-only memory of characters to decode. The memory is converted to a string internally. - /// - /// - /// An of containing the decoded coordinate pairs. - /// - /// - /// Thrown when is . - /// - public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - return decoder.Decode(polyline.ToString()); - } - - /// - /// Decodes an encoded polyline string into a sequence of geographic coordinates, - /// using a decoder that accepts of . - /// - /// The coordinate type returned by the decoder. - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline string to decode. The string is converted to internally. - /// - /// - /// An of containing the decoded coordinate pairs. - /// - /// - /// Thrown when or is . - /// - public static IEnumerable Decode(this IPolylineDecoder, TValue> decoder, string polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - if (polyline is null) { - ExceptionGuard.ThrowArgumentNull(nameof(polyline)); - } - - return decoder.Decode(polyline.AsMemory()); - } -} diff --git a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs index e9b66923..460eb99b 100644 --- a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs +++ b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs @@ -1,4 +1,4 @@ -// +// // Copyright © Pete Sramek. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -8,77 +8,41 @@ namespace PolylineAlgorithm.Extensions; using PolylineAlgorithm.Abstraction; using PolylineAlgorithm.Internal.Diagnostics; using System; -using System.Collections.Generic; -#if NET5_0_OR_GREATER -using System.Runtime.InteropServices; -#endif /// -/// Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. +/// Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. /// public static class PolylineEncoderExtensions { /// - /// Encodes a of instances into an encoded polyline. - /// - /// The type that represents a geographic coordinate to encode. - /// The type that represents the encoded polyline output. - /// - /// The instance used to perform the encoding operation. - /// - /// - /// The list of objects to encode. - /// - /// - /// A instance representing the encoded polyline for the provided coordinates. - /// - /// - /// Thrown when or is . - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We need a list as we do need to marshal it as span.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "We need a list as we do need to marshal it as span.")] - public static TPolyline Encode(this IPolylineEncoder encoder, List coordinates) { - if (encoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(encoder)); - } - - if (coordinates is null) { - ExceptionGuard.ThrowArgumentNull(nameof(coordinates)); - } - -#if NET5_0_OR_GREATER - return encoder.Encode(CollectionsMarshal.AsSpan(coordinates)); -#else - return encoder.Encode([.. coordinates]); -#endif - } - - - /// - /// Encodes an array of instances into an encoded polyline. + /// Encodes an array of instances into an encoded polyline. /// - /// The type that represents a geographic coordinate to encode. + /// The type that represents a geographic coordinate to encode. /// The type that represents the encoded polyline output. /// - /// The instance used to perform the encoding operation. + /// The instance used to perform the encoding operation. /// - /// - /// The array of objects to encode. + /// + /// The array of objects to encode. /// /// /// A instance representing the encoded polyline for the provided coordinates. /// /// - /// Thrown when or is . + /// Thrown when or is . /// - public static TPolyline Encode(this IPolylineEncoder encoder, TCoordinate[] coordinates) { + public static TPolyline Encode( + this IPolylineEncoder encoder, + TValue[] values, + PolylineEncodingOptions? options = null, + CancellationToken cancellationToken = default) { if (encoder is null) { ExceptionGuard.ThrowArgumentNull(nameof(encoder)); } - if (coordinates is null) { - ExceptionGuard.ThrowArgumentNull(nameof(coordinates)); + if (values is null) { + ExceptionGuard.ThrowArgumentNull(nameof(values)); } - return encoder.Encode(coordinates.AsSpan()); + return encoder.Encode(values.AsSpan(), options, cancellationToken); } } diff --git a/src/PolylineAlgorithm/FormatterBuilder.cs b/src/PolylineAlgorithm/FormatterBuilder.cs new file mode 100644 index 00000000..8ea32dea --- /dev/null +++ b/src/PolylineAlgorithm/FormatterBuilder.cs @@ -0,0 +1,199 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Internal; +using System; +using System.Collections.Generic; + +/// +/// Provides a fluent builder for constructing a . +/// +/// The coordinate or item type from which column values are extracted. +/// The polyline surface type produced and consumed by the formatter. +/// +/// +/// Use to obtain an instance, call once per column, +/// optionally chain to set a reference baseline for the most-recently added column, +/// optionally chain to register a factory for the decoding direction, +/// call to supply the polyline surface delegates (required), then call +/// to produce the immutable . +/// +/// +/// The builder is the only way to create a +/// — its constructor is internal. +/// +/// +public sealed class FormatterBuilder { + private readonly List> _rules = []; + private readonly HashSet _names = new(StringComparer.Ordinal); + private PolylineItemFactory? _create; + private Func, TPolyline>? _write; + private Func>? _read; + + private FormatterBuilder() { } + + /// + /// Creates a new instance. + /// + /// A fresh builder with no rules and no polyline delegates. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Factory method on a generic builder intentionally lives on the type so callers write FormatterBuilder.Create() without needing a separate non-generic factory class.")] + public static FormatterBuilder Create() => new(); + + /// + /// Adds a column with the specified value selector and precision. + /// + /// + /// A unique, non-null, non-empty name that identifies the column. Used for diagnostics only. + /// + /// + /// A delegate that extracts the column's raw value from an item of type + /// . + /// + /// + /// The number of decimal places to preserve. Each extracted value is multiplied by + /// 10^ before encoding. Defaults to 5. + /// + /// The current builder instance for method chaining. + /// + /// Thrown when or is . + /// + /// + /// Thrown when is empty, or a rule with the same name already exists. + /// + public FormatterBuilder AddValue(string name, Func selector, uint precision = 5) { + if (name is null) { + throw new ArgumentNullException(nameof(name)); + } + + if (name.Length == 0) { + throw new ArgumentException("Name cannot be empty.", nameof(name)); + } + + if (selector is null) { + throw new ArgumentNullException(nameof(selector)); + } + + if (!_names.Add(name)) { + throw new ArgumentException($"A rule with the name '{name}' has already been added.", nameof(name)); + } + + _rules.Add(new FormatterRule(name, (long)Pow10.GetFactor(precision), selector)); + + return this; + } + + /// + /// Sets a reference value (baseline) on the most-recently added column. + /// During encoding, the baseline is subtracted from the first item's scaled column value so that + /// the initial delta is scaled_first_value − baseline rather than scaled_first_value. + /// Use this when the absolute scaled value of the first data point for a column would otherwise + /// produce a very large initial encoded delta. + /// + /// + /// The reference value to subtract from the first item's scaled column value during encoding. + /// The decoder automatically adds this value back, so the reconstructed item matches the + /// original input. + /// + /// The current builder instance for method chaining. + /// + /// Thrown when no rules have been added yet. Call before + /// . + /// + public FormatterBuilder SetBaseline(long baseline) { + if (_rules.Count == 0) { + throw new InvalidOperationException("Cannot set a baseline when no rules have been added. Call AddValue first."); + } + + var last = _rules[^1]; + _rules[^1] = new FormatterRule(last.Name, last.Factor, last.Select, baseline); + + return this; + } + + /// + /// Registers a factory delegate used to reconstruct a from + /// denormalized values during decoding. + /// + /// + /// A delegate that accepts the denormalized values reconstructed from the + /// polyline and returns a . The formatter automatically divides + /// each accumulated scaled integer by its precision factor and adds back any baseline configured + /// via , so the span values match the original values supplied to the + /// encoder. The span length always equals the number of columns added via . + /// + /// The current builder instance for method chaining. + /// + /// Thrown when is . + /// + public FormatterBuilder WithValueFactory(PolylineItemFactory create) { + if (create is null) { + throw new ArgumentNullException(nameof(create)); + } + + _create = create; + + return this; + } + + /// + /// Supplies the polyline-surface delegates required to convert between the raw character buffer + /// and a . This call is mandatory before . + /// + /// + /// Converts the encoded of produced by the encoder + /// into a . + /// + /// + /// Extracts the encoded character buffer from a for the decoder to + /// consume. + /// + /// The current builder instance for method chaining. + /// + /// Thrown when or is . + /// + public FormatterBuilder WithReaderWriter( + Func, TPolyline> write, + Func> read) { + if (write is null) { + throw new ArgumentNullException(nameof(write)); + } + + if (read is null) { + throw new ArgumentNullException(nameof(read)); + } + + _write = write; + _read = read; + + return this; + } + + /// + /// Bakes all added rules and delegates into a sealed, immutable + /// . + /// + /// + /// An immutable whose configuration can + /// no longer be changed. + /// + /// + /// Thrown when no rules have been added, or when has not been called. + /// + public PolylineFormatter Build() { + if (_rules.Count == 0) { + throw new InvalidOperationException("At least one rule must be added before calling Build."); + } + + if (_write is null || _read is null) { + throw new InvalidOperationException( + $"Polyline surface delegates must be supplied before calling Build. " + + $"Call {nameof(WithReaderWriter)} first."); + } + + return new PolylineFormatter(_rules.ToArray(), _create, _write, _read); + } +} diff --git a/src/PolylineAlgorithm/Internal/CoordinateDelta.cs b/src/PolylineAlgorithm/Internal/CoordinateDelta.cs deleted file mode 100644 index ae217c57..00000000 --- a/src/PolylineAlgorithm/Internal/CoordinateDelta.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Internal; - -using System.Diagnostics; -using System.Runtime.InteropServices; - -/// -/// Represents the difference (delta) in latitude and longitude between consecutive geographic coordinates. -/// -/// -/// This struct computes and stores the change in coordinate values as integer deltas between successive coordinates. -/// -[DebuggerDisplay("{ToString(),nq}")] -[StructLayout(LayoutKind.Auto)] -internal struct CoordinateDelta { - private (int Latitude, int Longitude) _current; - - /// - /// Initializes a new instance of the struct with the default latitude and longitude deltas. - /// - public CoordinateDelta() { - _current = (default, default); - } - - /// - /// Gets the current delta in latitude between the most recent and previous coordinate. - /// - public int Latitude { get; private set; } - - /// - /// Gets the current delta in longitude between the most recent and previous coordinate. - /// - public int Longitude { get; private set; } - - /// - /// Updates the delta values based on the next latitude and longitude, and sets the current coordinate as next delta baseline. - /// - /// The next latitude value. - /// The next longitude value. - public void Next(int latitude, int longitude) { - Latitude = Delta(_current.Latitude, latitude); - Longitude = Delta(_current.Longitude, longitude); - - _current.Latitude = latitude; - _current.Longitude = longitude; - } - - /// - /// Calculates the delta between two coordinate values. - /// - /// - /// This method computes the difference between two integer coordinate values, handling cases where the values may be positive or negative. - /// - /// The previous coordinate value. - /// The next coordinate value. - /// The computed delta between and . - private static int Delta(int initial, int next) => next - initial; - - /// - /// Returns a string representation of the current coordinate delta. - /// - /// - /// A string in the format { Coordinate: { Latitude: [int], Longitude: [int] }, Delta: { Latitude: [int], Longitude: [int] } } representing the current coordinate and deltas to previous coordinate. - /// - public override readonly string ToString() => - $"{{ Coordinate: {{ Latitude: {_current.Latitude}, Longitude: {_current.Longitude} }}, " + - $"Delta: {{ Latitude: {Latitude}, Longitude: {Longitude} }} }}"; -} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/Defaults.cs b/src/PolylineAlgorithm/Internal/Defaults.cs index 44a734fa..429676bd 100644 --- a/src/PolylineAlgorithm/Internal/Defaults.cs +++ b/src/PolylineAlgorithm/Internal/Defaults.cs @@ -111,7 +111,7 @@ internal static class Length { /// /// The maximum number of characters allowed to represent an encoded value. /// - internal const int Max = 7; + internal const int Max = 13; } } } diff --git a/src/PolylineAlgorithm/Internal/FormatterRule.cs b/src/PolylineAlgorithm/Internal/FormatterRule.cs new file mode 100644 index 00000000..6862e9e6 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/FormatterRule.cs @@ -0,0 +1,54 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Internal; + +using System; + +/// +/// Represents a single column rule baked into a . +/// Stores the pre-calculated factor and an optional baseline alongside the user-supplied value selector. +/// +/// The source object type from which the column value is extracted. +internal sealed class FormatterRule { + /// + /// Initializes a new instance of . + /// + /// The column name used for diagnostics. + /// The pre-calculated scaling factor (10^precision). + /// The delegate that extracts the raw value from an item. + /// The optional baseline value applied to the first item only. + internal FormatterRule(string name, long factor, Func select, long? baseline = null) { + Name = name; + Factor = factor; + Select = select; + Baseline = baseline; + } + + /// + /// Gets the column name. Used for diagnostics and duplicate-name detection only. + /// + internal string Name { get; } + + /// + /// Gets the pre-calculated scaling factor (10^precision). + /// Stored as so that (long)(value * Factor) stays in 64-bit arithmetic + /// throughout the encoding hot loop without additional casting. + /// + internal long Factor { get; } + + /// + /// Gets the optional reference value used as the baseline for the first encoded point. + /// When set, the encoder subtracts this value from the first item's scaled column value so that + /// the initial delta is scaled_first_value − baseline rather than scaled_first_value. + /// + internal long? Baseline { get; } + + /// + /// Gets the delegate that extracts the column's raw value from an item of type + /// . Stored as a concrete delegate so the JIT can inline the call site. + /// + internal Func Select { get; } +} diff --git a/src/PolylineAlgorithm/PolylineDecoder.cs b/src/PolylineAlgorithm/PolylineDecoder.cs new file mode 100644 index 00000000..7d9a3193 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineDecoder.cs @@ -0,0 +1,144 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using Microsoft.Extensions.Logging; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Internal; +using PolylineAlgorithm.Internal.Diagnostics; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +/// +/// Decodes encoded polyline representations into sequences of geographic coordinates. +/// +/// The type that represents the encoded polyline input. +/// The type that represents a decoded geographic coordinate. +/// +/// Pass a that carries a +/// to the constructor. The formatter handles +/// all type-specific concerns; no subclassing is required. +/// +public class PolylineDecoder : IPolylineDecoder { + private readonly IPolylineFormatter _formatter; + private readonly ILogger> _logger; + + /// + /// Initializes a new instance of . + /// + /// + /// A that carries the formatter and settings. + /// Must not be . + /// + /// + /// Thrown when is . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards.")] + public PolylineDecoder(PolylineOptions options) { + if (options is null) { + ExceptionGuard.ThrowArgumentNull(nameof(options)); + } + + _formatter = options.Formatter; + _logger = options.LoggerFactory.CreateLogger>(); + } + + /// + /// Decodes an encoded into a sequence of + /// instances, applying per-call to + /// seed the accumulated-delta state. Use this overload to decode polylines that were produced by + /// chunked encoding. + /// + /// The encoded polyline to decode. Must not be . + /// + /// Per-call options that control the accumulated-delta seed. Pass or an + /// instance with set to + /// to start from zero (same as calling + /// ). + /// + /// A token that can be used to cancel the operation. + /// + /// An of representing the decoded + /// coordinates. + /// + /// + /// Thrown when is . + /// + /// + /// Thrown when the polyline format is invalid or malformed. + /// + /// + /// Thrown when is canceled during decoding. + /// + public IEnumerable Decode( + TPolyline polyline, + PolylineDecodingOptions? options = null, + CancellationToken cancellationToken = default) { + const string OperationName = nameof(Decode); + + _logger.LogOperationStartedDebug(OperationName); + + if (polyline is null) { + _logger.LogNullArgumentWarning(nameof(polyline)); + ExceptionGuard.ThrowArgumentNull(nameof(polyline)); + } + + ReadOnlyMemory sequence = _formatter.Read(polyline); + + if (sequence.Length < Defaults.Polyline.Block.Length.Min) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogPolylineCannotBeShorterThanWarning(sequence.Length, Defaults.Polyline.Block.Length.Min); + ExceptionGuard.ThrowInvalidPolylineLength(sequence.Length, Defaults.Polyline.Block.Length.Min); + } + + try { + PolylineEncoding.ValidateFormat(sequence.Span); + } catch (ArgumentException ex) { + _logger.LogInvalidPolylineFormatWarning(ex); + throw; + } + + int width = _formatter.Width; + long[] accumulated = new long[width]; + int position = 0; + + SeedAccumulated(accumulated, options); + + try { + while (position < sequence.Length) { + cancellationToken.ThrowIfCancellationRequested(); + + for (int j = 0; j < width; j++) { + if (!PolylineEncoding.TryReadValue(ref accumulated[j], sequence, ref position)) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogInvalidPolylineWarning(position); + ExceptionGuard.ThrowInvalidPolylineFormat(position); + } + } + + yield return _formatter.CreateItem(accumulated.AsSpan()); + } + } finally { + _logger.LogOperationFinishedDebug(OperationName); + } + } + + private void SeedAccumulated(long[] accumulated, PolylineDecodingOptions? options) { + if (options is not { HasPrevious: true }) { + return; + } + + int width = _formatter.Width; + long[] scaled = new long[width]; + _formatter.GetValues(options.Previous, scaled.AsSpan()); + + for (int j = 0; j < width; j++) { + accumulated[j] = scaled[j] - _formatter.GetBaseline(j); + } + } +} diff --git a/src/PolylineAlgorithm/PolylineDecodingOptions.cs b/src/PolylineAlgorithm/PolylineDecodingOptions.cs new file mode 100644 index 00000000..4ba3c7d9 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineDecodingOptions.cs @@ -0,0 +1,50 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +/// +/// Per-call options for a chunked decoding operation. +/// +/// The value type understood by the formatter. +/// +/// Pass an instance of this class to the chunked +/// overload to control +/// the accumulated-delta seed used at the start of each chunk. When is +/// zero-initialisation is used, which is the existing default behaviour. +/// +public sealed class PolylineDecodingOptions { + + /// + /// Initializes a new instance of with no + /// previous value (zero-initialised baseline will be used). + /// + public PolylineDecodingOptions() { } + + /// + /// Initializes a new instance of with the + /// specified previous value used to seed the accumulated-delta state. + /// + /// + /// The last value of the previous chunk, used to seed the accumulated-delta state. + /// + public PolylineDecodingOptions(TValue previous) { + Previous = previous; + HasPrevious = true; + } + + /// + /// Gets a value indicating whether a previous value has been supplied to seed the + /// accumulated-delta state. When zero-initialisation is used, which is + /// the existing default. + /// + public bool HasPrevious { get; } + + /// + /// Gets the last value of the previous chunk, used to seed the accumulated-delta state. + /// Only meaningful when is . + /// + public TValue Previous { get; } +} diff --git a/src/PolylineAlgorithm/PolylineEncoder.cs b/src/PolylineAlgorithm/PolylineEncoder.cs new file mode 100644 index 00000000..8c59d895 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineEncoder.cs @@ -0,0 +1,240 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using Microsoft.Extensions.Logging; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Internal; +using PolylineAlgorithm.Internal.Diagnostics; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + +/// +/// Encodes sequences of geographic coordinates into encoded polyline representations. +/// +/// The type that represents a geographic coordinate to encode. +/// The type that represents the encoded polyline output. +/// +/// Pass a that carries a +/// to the constructor. The formatter handles +/// all type-specific concerns; no subclassing is required. +/// +public class PolylineEncoder : IPolylineEncoder { + private readonly IPolylineFormatter _formatter; + private readonly PolylineOptions _options; + private readonly ILogger> _logger; + + /// + /// Initializes a new instance of . + /// + /// + /// A that carries the formatter and settings. + /// Must not be . + /// + /// + /// Thrown when is . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards.")] + public PolylineEncoder(PolylineOptions options) { + if (options is null) { + ExceptionGuard.ThrowArgumentNull(nameof(options)); + } + + _options = options; + _formatter = options.Formatter; + _logger = options.LoggerFactory.CreateLogger>(); + } + + /// + /// Encodes a collection of instances into an encoded + /// . + /// + /// The collection of coordinates to encode. + /// A token that can be used to cancel the operation. + /// + /// An instance of representing the encoded coordinates. + /// + /// + /// Thrown when is empty. + /// + /// + /// Thrown when the internal encoding buffer cannot accommodate the encoded value. + /// + /// + /// Thrown when is canceled. + /// + public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) { + const string OperationName = nameof(Encode); + + _logger.LogOperationStartedDebug(OperationName); + + Debug.Assert(coordinates.Length >= 0, "Count must be non-negative."); + + if (coordinates.Length < 1) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogEmptyArgumentWarning(nameof(coordinates)); + ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); + } + + int width = _formatter.Width; + int length = GetMaxBufferLength(coordinates.Length, width); + + char[]? temp = length <= _options.StackAllocLimit + ? null + : ArrayPool.Shared.Rent(length); + + Span buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length); + + int position = 0; + long[] previous = new long[width]; + long[] values = new long[width]; + + SeedPrevious(previous, null); + + try { + for (int i = 0; i < coordinates.Length; i++) { + cancellationToken.ThrowIfCancellationRequested(); + + _formatter.GetValues(coordinates[i], values.AsSpan()); + + for (int j = 0; j < width; j++) { + long current = values[j]; + long delta = current - previous[j]; + previous[j] = current; + + if (!PolylineEncoding.TryWriteValue(delta, buffer, ref position)) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogCannotWriteValueToBufferWarning(position, i); + ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); + } + } + } + + // Convert to string inside the try block so the buffer is still valid. + string encodedResult = buffer[..position].ToString(); + + _logger.LogOperationFinishedDebug(OperationName); + + return _formatter.Write(encodedResult.AsMemory()); + } finally { + if (temp is not null) { + ArrayPool.Shared.Return(temp); + } + } + } + + /// + /// Encodes a collection of instances into an encoded + /// , applying per-call to control the + /// delta baseline. Use this overload to encode large sequences in independent chunks that can be + /// concatenated into a single valid polyline. + /// + /// The collection of coordinates to encode. + /// + /// Per-call options that control the starting delta baseline. Pass or an + /// instance with set to + /// to use the formatter's default baseline (same as calling + /// ). + /// + /// A token that can be used to cancel the operation. + /// + /// An instance of representing the encoded coordinates. + /// + /// + /// Thrown when is empty. + /// + /// + /// Thrown when the internal encoding buffer cannot accommodate the encoded value. + /// + /// + /// Thrown when is canceled. + /// + public TPolyline Encode(ReadOnlySpan coordinates, PolylineEncodingOptions? options, CancellationToken cancellationToken) { + const string OperationName = nameof(Encode); + + _logger.LogOperationStartedDebug(OperationName); + + Debug.Assert(coordinates.Length >= 0, "Count must be non-negative."); + + if (coordinates.Length < 1) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogEmptyArgumentWarning(nameof(coordinates)); + ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); + } + + int width = _formatter.Width; + int length = GetMaxBufferLength(coordinates.Length, width); + + char[]? temp = length <= _options.StackAllocLimit + ? null + : ArrayPool.Shared.Rent(length); + + Span buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length); + + int position = 0; + long[] previous = new long[width]; + long[] values = new long[width]; + + SeedPrevious(previous, options); + + try { + for (int i = 0; i < coordinates.Length; i++) { + cancellationToken.ThrowIfCancellationRequested(); + + _formatter.GetValues(coordinates[i], values.AsSpan()); + + for (int j = 0; j < width; j++) { + long current = values[j]; + long delta = current - previous[j]; + previous[j] = current; + + if (!PolylineEncoding.TryWriteValue(delta, buffer, ref position)) { + _logger.LogOperationFailedDebug(OperationName); + _logger.LogCannotWriteValueToBufferWarning(position, i); + ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); + } + } + } + + string encodedResult = buffer[..position].ToString(); + + _logger.LogOperationFinishedDebug(OperationName); + + return _formatter.Write(encodedResult.AsMemory()); + } finally { + if (temp is not null) { + ArrayPool.Shared.Return(temp); + } + } + } + + private void SeedPrevious(long[] previous, PolylineEncodingOptions? options) { + int width = _formatter.Width; + + if (options is { HasPrevious: true }) { + _formatter.GetValues(options.Previous, previous.AsSpan()); + } else { + for (int j = 0; j < width; j++) { + previous[j] = _formatter.GetBaseline(j); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMaxBufferLength(int count, int valuesPerItem) { + Debug.Assert(count > 0, "Count must be greater than zero."); + Debug.Assert(valuesPerItem > 0, "Values per item must be greater than zero."); + + int requestedBufferLength = count * valuesPerItem * Defaults.Polyline.Block.Length.Max; + + Debug.Assert(requestedBufferLength > 0, "Requested buffer length must be greater than zero."); + + return requestedBufferLength; + } +} diff --git a/src/PolylineAlgorithm/PolylineEncoding.cs b/src/PolylineAlgorithm/PolylineEncoding.cs index 3d6de5e7..f91af382 100644 --- a/src/PolylineAlgorithm/PolylineEncoding.cs +++ b/src/PolylineAlgorithm/PolylineEncoding.cs @@ -50,15 +50,12 @@ public static class PolylineEncoding { /// Default is 5, which is standard for polyline encoding. /// /// - /// An integer representing the normalized value. Returns 0 if the input is 0.0. + /// A long integer representing the normalized value. Returns 0 if the input is 0.0. /// /// /// Thrown when is not a finite number (NaN or infinity). /// - /// - /// Thrown when the normalized result exceeds the range of a 32-bit signed integer during the conversion from double to int. - /// - public static int Normalize(double value, uint precision = 5) { + public static long Normalize(double value, uint precision = 5) { // Fast return if the value is zero, return 0 as the normalized value. if (value.Equals(default)) { return 0; @@ -69,9 +66,9 @@ public static int Normalize(double value, uint precision = 5) { ExceptionGuard.ThrowNotFiniteNumber(nameof(value)); } - // Fast return if precision is zero, return current value converted to Int32. + // Fast return if precision is zero, return current value converted to Int64. if (precision == default) { - return (int)Math.Truncate(value); + return (long)Math.Truncate(value); } uint factor = Pow10.GetFactor(precision); @@ -79,7 +76,7 @@ public static int Normalize(double value, uint precision = 5) { const double Epsilon = 1e-9; checked { - return (int)Math.Truncate((value * factor) + (Epsilon * Math.Sign(value))); + return (long)Math.Truncate((value * factor) + (Epsilon * Math.Sign(value))); } } @@ -108,7 +105,7 @@ public static int Normalize(double value, uint precision = 5) { /// /// /// - /// The integer value to denormalize. Typically produced by the method. + /// The long integer value to denormalize. Typically produced by the method. /// /// /// The number of decimal places used during normalization. Default is 5, matching standard polyline encoding precision. @@ -119,7 +116,7 @@ public static int Normalize(double value, uint precision = 5) { /// /// Thrown if the arithmetic operation overflows during conversion. /// - public static double Denormalize(int value, uint precision = 5) { + public static double Denormalize(long value, uint precision = 5) { if (value.Equals(default)) { return default; } @@ -155,7 +152,7 @@ public static double Denormalize(int value, uint precision = 5) { /// /// /// - /// Reference to the integer accumulator that will be updated with the decoded value. + /// Reference to the long accumulator that will be updated with the decoded value. /// /// /// The buffer containing polyline-encoded characters. @@ -166,7 +163,7 @@ public static double Denormalize(int value, uint precision = 5) { /// /// if a value was successfully read and decoded; if the buffer ended before a complete value was read. /// - public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref int position) { + public static bool TryReadValue(ref long delta, ReadOnlyMemory buffer, ref int position) { // Validate that the position is within the bounds of the buffer. if (position >= buffer.Length) { return false; @@ -174,14 +171,14 @@ public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref // Initialize variables for reading the value. int chunk = 0; - int sum = 0; + ulong sum = 0; int shifter = 0; ReadOnlySpan span = buffer.Span; // Read characters from the buffer until a termination condition is met or the end of the buffer is reached. while (position < buffer.Length) { chunk = span[position++] - Defaults.Algorithm.QuestionMark; - sum |= (chunk & Defaults.Algorithm.UnitSeparator) << shifter; + sum |= (ulong)(chunk & Defaults.Algorithm.UnitSeparator) << shifter; shifter += Defaults.Algorithm.ShiftLength; // If the chunk is less than the space character, it indicates the end of the value. @@ -190,7 +187,7 @@ public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref } } - delta += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; + delta += (sum & 1) == 1 ? (long)~(sum >> 1) : (long)(sum >> 1); // If the end of the buffer was reached without reading a complete value, return false. return chunk < Defaults.Algorithm.Space; @@ -220,7 +217,7 @@ public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref /// /// /// - /// The integer value to encode and write to the buffer. This value typically represents the difference between consecutive + /// The long value to encode and write to the buffer. This value typically represents the difference between consecutive /// coordinate values in polyline encoding. /// /// @@ -234,13 +231,13 @@ public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref /// if the value was successfully encoded and written to the buffer; if the buffer /// does not have sufficient remaining capacity to hold the encoded value. /// - public static bool TryWriteValue(int delta, Span buffer, ref int position) { + public static bool TryWriteValue(long delta, Span buffer, ref int position) { // Validate that the position and required space for write is within the bounds of the buffer. if (buffer[position..].Length < GetRequiredBufferSize(delta)) { return false; } - int rem = delta << 1; + ulong rem = (ulong)(delta << 1); // If the delta is negative, we need to invert the bits to get the correct representation. if (delta < 0) { @@ -248,7 +245,7 @@ public static bool TryWriteValue(int delta, Span buffer, ref int position) } // Write the value to the buffer in a way that encodes it using the specified algorithm. - while (rem >= Defaults.Algorithm.Space) { + while (rem >= (ulong)Defaults.Algorithm.Space) { buffer[position++] = (char)((Defaults.Algorithm.Space | (rem & Defaults.Algorithm.UnitSeparator)) @@ -284,19 +281,20 @@ public static bool TryWriteValue(int delta, Span buffer, ref int position) /// buffer overflow checks during the actual encoding process. /// /// - /// The method uses a internally to prevent overflow during the left-shift operation on large negative values. + /// The method uses internally to handle the full range of 64-bit signed values correctly + /// during the zigzag encoding step. /// /// /// - /// The integer delta value to calculate the encoded size for. This value typically represents the difference between + /// The long delta value to calculate the encoded size for. This value typically represents the difference between /// consecutive coordinate values in polyline encoding. /// /// /// The number of characters required to encode the specified delta value. The minimum return value is 1. /// /// - public static int GetRequiredBufferSize(int delta) { - long rem = (long)delta << 1; + public static int GetRequiredBufferSize(long delta) { + ulong rem = (ulong)(delta << 1); if (delta < 0) { rem = ~rem; @@ -304,7 +302,7 @@ public static int GetRequiredBufferSize(int delta) { int size = 1; - while (rem >= Defaults.Algorithm.Space) { + while (rem >= (ulong)Defaults.Algorithm.Space) { rem >>= Defaults.Algorithm.ShiftLength; size++; } diff --git a/src/PolylineAlgorithm/PolylineEncodingOptions.cs b/src/PolylineAlgorithm/PolylineEncodingOptions.cs index e99843de..ef3f9dca 100644 --- a/src/PolylineAlgorithm/PolylineEncodingOptions.cs +++ b/src/PolylineAlgorithm/PolylineEncodingOptions.cs @@ -5,79 +5,49 @@ namespace PolylineAlgorithm; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - /// -/// Provides configuration options for polyline encoding operations. +/// Per-call options for a chunked encoding operation. /// +/// The coordinate type understood by the formatter. /// -/// -/// This class allows you to configure various aspects of polyline encoding, including: -/// -/// -/// The level for coordinate encoding -/// The for memory allocation strategy -/// The for diagnostic logging -/// -/// -/// All properties have internal setters and should be configured through a builder or factory pattern. -/// +/// Pass an instance of this class to the chunked +/// overload to control +/// the delta baseline used at the start of each chunk. When is +/// the formatter's built-in baseline (or zero) is used, which is equivalent +/// to the existing default behaviour. /// -[DebuggerDisplay("StackAllocLimit: {StackAllocLimit}, Precision: {Precision}, LoggerFactoryType: {GetLoggerFactoryType()}")] -public sealed class PolylineEncodingOptions { +public sealed class PolylineEncodingOptions { + private readonly TValue _previous; + /// - /// Gets the logger factory used for diagnostic logging during encoding operations. + /// Initializes a new instance of with no + /// previous coordinate (formatter default baseline will be used). /// - /// - /// An instance. Defaults to . - /// - /// - /// The default logger factory is , which does not log any messages. - /// To enable logging, provide a custom implementation. - /// - public ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance; + public PolylineEncodingOptions() { } /// - /// Gets the precision level used for encoding coordinate values. + /// Initializes a new instance of with the + /// specified previous coordinate used to seed the delta baseline. /// - /// - /// The number of decimal places to use when encoding coordinate values. Defaults to 5. - /// - /// - /// - /// The precision determines the number of decimal places to which each coordinate value (latitude or longitude) - /// is multiplied and truncated (not rounded) before encoding. For example, a precision of 5 means each coordinate is multiplied by 10^5 - /// and truncated to an integer before encoding. - /// - /// - /// This setting does not directly correspond to a physical distance or accuracy in meters, but rather controls - /// the granularity of the encoded values. - /// - /// - public uint Precision { get; internal set; } = 5; + /// + /// The last coordinate of the previous chunk, used to seed the delta baseline. + /// + public PolylineEncodingOptions(TValue previous) { + _previous = previous; + HasPrevious = true; + } /// - /// Gets the maximum buffer size (in characters) that can be allocated on the stack for encoding operations. + /// Gets a value indicating whether a previous coordinate has been supplied to seed the delta + /// baseline. When the formatter's built-in baseline is used as the + /// starting point (which defaults to zero when no baseline has been configured), equivalent to + /// the existing default behaviour. /// - /// - /// The maximum number of characters for stack allocation using stackalloc char[]. Defaults to 512. - /// - /// - /// When the required buffer size for encoding exceeds this limit, memory will be allocated on the heap instead of the stack. - /// This setting specifically applies to stack allocation of character arrays (stackalloc char[]) used during polyline encoding, - /// balancing performance and stack safety. - /// - public int StackAllocLimit { get; internal set; } = 512; + public bool HasPrevious { get; } /// - /// Returns the type name of the logger factory for debugging purposes. + /// Gets the last coordinate of the previous chunk, used to seed the delta baseline. + /// Only meaningful when is . /// - /// - /// A string containing the type name of the current instance. - /// - [ExcludeFromCodeCoverage] - private string GetLoggerFactoryType() => LoggerFactory.GetType().Name; -} \ No newline at end of file + public TValue Previous => _previous; +} diff --git a/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs b/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs deleted file mode 100644 index 4258beca..00000000 --- a/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm; - -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using PolylineAlgorithm.Internal.Diagnostics; - -/// -/// Provides a builder for configuring options for polyline encoding operations. -/// -public sealed class PolylineEncodingOptionsBuilder { - private uint _precision = 5; - private int _stackAllocLimit = 512; - private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; - - private PolylineEncodingOptionsBuilder() { } - - /// - /// Creates a new instance for the specified coordinate type. - /// - /// - /// An instance for configuring polyline encoding options. - /// - public static PolylineEncodingOptionsBuilder Create() { - return new PolylineEncodingOptionsBuilder(); - } - - /// - /// Builds a new instance using the configured options. - /// - /// - /// A configured instance. - /// - public PolylineEncodingOptions Build() { - return new PolylineEncodingOptions { - Precision = _precision, - StackAllocLimit = _stackAllocLimit, - LoggerFactory = _loggerFactory, - }; - } - - /// - /// Configures the buffer size used for stack allocation during polyline encoding operations. - /// - /// - /// The maximum buffer size to use for stack allocation. Must be greater than or equal to 1. - /// - /// - /// The current instance for method chaining. - /// - /// - /// Thrown if is less than 1. - /// - /// - /// This method allows customization of the internal buffer size for encoding, which can impact performance and memory usage. - /// - public PolylineEncodingOptionsBuilder WithStackAllocLimit(int stackAllocLimit) { - const int minStackAllocLimit = 1; - - if (minStackAllocLimit > stackAllocLimit) { - ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(minStackAllocLimit, nameof(stackAllocLimit)); - } - - _stackAllocLimit = stackAllocLimit; - - return this; - } - - /// - /// Sets the coordinate encoding precision. - /// - /// - /// The number of decimal places to use for encoding coordinate values. Default is 5. - /// - /// - /// The current instance for method chaining. - /// - public PolylineEncodingOptionsBuilder WithPrecision(uint precision) { - _precision = precision; - - return this; - } - - /// - /// Configures the to be used for logging during polyline encoding operations. - /// - /// - /// The instance to use for logging. If , a will be used instead. - /// - /// - /// The current instance for method chaining. - /// - public PolylineEncodingOptionsBuilder WithLoggerFactory(ILoggerFactory loggerFactory) { - _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; - - return this; - } -} \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineFormatter.cs b/src/PolylineAlgorithm/PolylineFormatter.cs new file mode 100644 index 00000000..a183b6ad --- /dev/null +++ b/src/PolylineAlgorithm/PolylineFormatter.cs @@ -0,0 +1,104 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Internal; +using System; +using System.Runtime.CompilerServices; + +/// +/// A sealed, immutable formatter that implements . +/// +/// The coordinate or item type. +/// The polyline surface type. +/// +/// Instances are constructed exclusively through . +/// The modifier allows the JIT to devirtualise and inline calls to the +/// interface methods in the encoding/decoding hot loop. +/// +public sealed class PolylineFormatter : IPolylineFormatter { + private readonly FormatterRule[] _rules; + private readonly PolylineItemFactory? _create; + private readonly Func, TPolyline> _write; + private readonly Func> _read; + + /// + /// Initializes a new instance. Intentionally internal — use + /// to create instances. + /// + internal PolylineFormatter( + FormatterRule[] rules, + PolylineItemFactory? create, + Func, TPolyline> write, + Func> read) { + _rules = rules; + _create = create; + _write = write; + _read = read; + Width = rules.Length; + } + + /// + public int Width { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetBaseline(int index) => _rules[index].Baseline ?? 0L; + + /// + /// + /// Thrown when .Length does not equal . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetValues(TValue item, Span values) { + if (values.Length != Width) { + throw new ArgumentException( + $"Buffer length {values.Length} does not match the formatter width {Width}.", + nameof(values)); + } + + var rules = _rules; + for (var i = 0; i < rules.Length; i++) { + ref var rule = ref rules[i]; + values[i] = (long)(rule.Select(item) * rule.Factor); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPolyline Write(ReadOnlyMemory encoded) => _write(encoded); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory Read(TPolyline polyline) => _read(polyline); + + /// + /// + /// Thrown when no factory delegate was supplied via + /// . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue CreateItem(ReadOnlySpan values) { + if (_create is null) { + throw new InvalidOperationException( + $"Cannot reconstruct an item because no factory was registered. " + + $"Call {nameof(FormatterBuilder)}.{nameof(FormatterBuilder.WithValueFactory)} before building."); + } + + // Denormalize each accumulated scaled integer back to the original double: + // add back the baseline that was subtracted during encoding, then divide by the precision factor. + var rules = _rules; + int width = rules.Length; + double[] doubles = new double[width]; + for (var i = 0; i < width; i++) { + ref var rule = ref rules[i]; + doubles[i] = (values[i] + (rule.Baseline ?? 0L)) / (double)rule.Factor; + } + + return _create(doubles); + } +} diff --git a/src/PolylineAlgorithm/PolylineItemFactory.cs b/src/PolylineAlgorithm/PolylineItemFactory.cs new file mode 100644 index 00000000..7a694416 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineItemFactory.cs @@ -0,0 +1,22 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using System; + +/// +/// Represents a factory method that reconstructs a item from denormalized +/// values decoded from a polyline. +/// +/// The coordinate or item type to create. +/// +/// The denormalized values reconstructed by the polyline decoder. Each element corresponds to the +/// original value that was encoded, with the precision factor divided out and any +/// baseline added back. The span length equals the number of columns defined via +/// . +/// +/// A instance reconstructed from . +public delegate T PolylineItemFactory(ReadOnlySpan values); diff --git a/src/PolylineAlgorithm/PolylineOptions.cs b/src/PolylineAlgorithm/PolylineOptions.cs new file mode 100644 index 00000000..0bdce089 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineOptions.cs @@ -0,0 +1,72 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using PolylineAlgorithm.Abstraction; +using System; + +/// +/// Provides unified configuration for a formatter-driven encoding or decoding operation. +/// +/// The coordinate or item type understood by the formatter. +/// The polyline surface type understood by the formatter. +/// +/// Supply an and optional settings, +/// then pass this instance to and/or +/// . +/// +public sealed class PolylineOptions { + /// + /// Initializes a new instance of . + /// + /// + /// The unified formatter that handles all type-specific concerns: value extraction, item + /// reconstruction, and polyline surface conversion. Must not be . + /// + /// + /// The maximum buffer size (in characters) for stack allocation. Defaults to 512. + /// + /// + /// The logger factory for diagnostic logging. Pass to use + /// . + /// + /// + /// Thrown when is . + /// + public PolylineOptions( + IPolylineFormatter formatter, + int stackAllocLimit = 512, + ILoggerFactory? loggerFactory = null) { + if (formatter is null) { + throw new ArgumentNullException(nameof(formatter)); + } + + Formatter = formatter; + StackAllocLimit = stackAllocLimit; + LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + } + + /// + /// Gets the unified formatter that handles value extraction, item reconstruction, and polyline + /// surface conversion. + /// + public IPolylineFormatter Formatter { get; } + + /// + /// Gets the maximum buffer size (in characters) that may be allocated on the stack for encoding. + /// When the required buffer size exceeds this limit, memory is rented from + /// instead. Defaults to 512. + /// + public int StackAllocLimit { get; } + + /// + /// Gets the logger factory used for diagnostic logging during encoding and decoding operations. + /// Defaults to . + /// + public ILoggerFactory LoggerFactory { get; } +} diff --git a/src/PolylineAlgorithm/PublicAPI.Unshipped.txt b/src/PolylineAlgorithm/PublicAPI.Unshipped.txt index 7dc5c581..e69de29b 100644 --- a/src/PolylineAlgorithm/PublicAPI.Unshipped.txt +++ b/src/PolylineAlgorithm/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ -#nullable enable diff --git a/src/PolylineAlgorithm/README.md b/src/PolylineAlgorithm/README.md index 97c4bb98..03dd3cae 100644 --- a/src/PolylineAlgorithm/README.md +++ b/src/PolylineAlgorithm/README.md @@ -1,4 +1,4 @@ -# PolylineAlgorithm for .NET +# PolylineAlgorithm for .NET [![NuGet](https://img.shields.io/nuget/v/PolylineAlgorithm)](https://www.nuget.org/packages/PolylineAlgorithm) @@ -7,10 +7,12 @@ Google's Encoded Polyline Algorithm compresses sequences of geographic coordinat ## Features - Google-compliant polyline encoding/decoding for geographic coordinates -- Extensible APIs for custom coordinate and polyline types (`AbstractPolylineEncoder`, `AbstractPolylineDecoder`) +- Fluent `FormatterBuilder` for configuring custom coordinate and polyline types +- Immutable, sealed `PolylineFormatter` built via `FormatterBuilder` +- `PolylineEncoder` and `PolylineDecoder` — configurable via `PolylineOptions` - Extension methods for encoding from `List` and arrays (`PolylineEncoderExtensions`) - Robust input validation and descriptive exceptions -- Configurable with `PolylineEncodingOptions` (precision, buffer size, logging) +- Configurable stack-alloc buffer size and logging via `PolylineOptions` - Thread-safe, stateless APIs - Low-level utilities via static `PolylineEncoding` class (Normalize, Denormalize, TryReadValue, TryWriteValue, ValidateFormat, etc.) - Benchmarks and unit tests for correctness and performance @@ -31,19 +33,20 @@ Install-Package PolylineAlgorithm ## Quick Start -The library provides abstract base classes to build your own encoder and decoder for any coordinate and polyline type. +Use `FormatterBuilder` to configure your coordinate and polyline types, then construct `PolylineEncoder` and `PolylineDecoder` from the resulting `PolylineOptions`. -### Implement a custom encoder +### Build a formatter ```csharp using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; -public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - protected override double GetLatitude((double Latitude, double Longitude) coordinate) => coordinate.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) coordinate) => coordinate.Longitude; - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); -} +PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", static c => c.Lat) + .AddValue("lon", static c => c.Lon) + .WithCreate(static v => (v[0], v[1])) + .ForPolyline(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); ``` ### Encode coordinates @@ -51,55 +54,43 @@ public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude ```csharp using PolylineAlgorithm.Extensions; -var coordinates = new List<(double Latitude, double Longitude)> +PolylineOptions<(double Lat, double Lon), string> options = new(formatter); +PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + +var coordinates = new List<(double Lat, double Lon)> { (48.858370, 2.294481), (51.500729, -0.124625) }; -var encoder = new MyPolylineEncoder(); string encoded = encoder.Encode(coordinates); // extension method for List Console.WriteLine(encoded); // Output: "yseiHoc_MwacOjnwM" ``` -### Implement a custom decoder - -```csharp -using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; - -public sealed class MyPolylineDecoder : AbstractPolylineDecoder { - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); -} -``` - ### Decode polyline ```csharp -string encoded = "yseiHoc_MwacOjnwM"; +PolylineDecoder decoder = new(options); -var decoder = new MyPolylineDecoder(); -IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); +IEnumerable<(double Lat, double Lon)> decoded = decoder.Decode("yseiHoc_MwacOjnwM"); ``` ## Advanced Usage -Use `PolylineEncodingOptionsBuilder` to customize precision, buffer size, and logging, then pass the built options to the encoder or decoder constructor: +Pass a `stackAllocLimit` and an `ILoggerFactory` to `PolylineOptions` to customize buffer sizing and logging: ```csharp using Microsoft.Extensions.Logging; -PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(6) // 6 decimal places instead of the default 5 - .WithStackAllocLimit(1024) // increase stack-alloc buffer - .WithLoggerFactory(loggerFactory) // plug in your ILoggerFactory - .Build(); +PolylineOptions<(double Lat, double Lon), string> options = new( + formatter, + stackAllocLimit: 1024, + loggerFactory: loggerFactory); -var encoder = new MyPolylineEncoder(options); -var decoder = new MyPolylineDecoder(options); +PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); +PolylineDecoder decoder = new(options); ``` Use static methods on `PolylineEncoding` for low-level normalization, validation, and bit-level read/write operations. @@ -114,8 +105,8 @@ Use static methods on `PolylineEncoding` for low-level normalization, validation The decoder throws `InvalidPolylineException` with a descriptive message. Wrap calls in a try/catch in your application. - **What .NET versions are supported?** Any environment supporting `netstandard2.1` -- **How do I customize encoder options?** - Use `PolylineEncodingOptionsBuilder` and pass the built options to the encoder or decoder constructor. +- **How do I customize encoder/decoder options?** + Create a `PolylineOptions` with your `PolylineFormatter`, optional `stackAllocLimit`, and optional `ILoggerFactory`, then pass it to the `PolylineEncoder` or `PolylineDecoder` constructor. - **Where can I get help?** [GitHub issues](https://github.com/petesramek/polyline-algorithm-csharp/issues) diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs index c6734c6c..57c56b28 100644 --- a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs @@ -5,140 +5,290 @@ namespace PolylineAlgorithm.Tests.Abstraction; -using PolylineAlgorithm.Abstraction; using PolylineAlgorithm.Utility; using System; using System.Collections.Generic; +using System.Threading; /// -/// Tests for . +/// Tests for . /// [TestClass] public sealed class AbstractPolylineDecoderTests { - private sealed class TestStringDecoder : AbstractPolylineDecoder { - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); + // ------------------------------------------------------------------ + // Helpers + // ------------------------------------------------------------------ + + private static readonly Func, string> _write = m => new string(m.Span); + private static readonly Func> _read = s => s.AsMemory(); + + private static PolylineDecoder CreateDecoder() { + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", static c => c.Lat) + .AddValue("lon", static c => c.Lon) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); + + return new PolylineDecoder( + new PolylineOptions<(double Lat, double Lon), string>(formatter)); } - private sealed class TestStringDecoderWithOptions : AbstractPolylineDecoder { - public TestStringDecoderWithOptions(PolylineEncodingOptions options) - : base(options) { } + // ------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------ - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); + /// Tests that a null options argument throws . + [TestMethod] + public void Constructor_With_Null_Options_Throws_ArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => new PolylineDecoder(null!)); + Assert.AreEqual("options", ex.ParamName); } - /// - /// Tests that Decode with a null polyline throws . - /// + // ------------------------------------------------------------------ + // Decode — argument validation + // ------------------------------------------------------------------ + + /// Tests that a null polyline throws . [TestMethod] public void Decode_With_Null_Polyline_Throws_ArgumentNullException() { // Arrange - TestStringDecoder decoder = new(); + PolylineDecoder decoder = CreateDecoder(); // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly(() => decoder.Decode((string?)null!).ToList()); + ArgumentNullException ex = Assert.ThrowsExactly( + () => decoder.Decode(null!).ToList()); Assert.AreEqual("polyline", ex.ParamName); } - /// - /// Tests that Decode with an empty polyline throws . - /// + /// Tests that an empty polyline throws . [TestMethod] public void Decode_With_Empty_Polyline_Throws_InvalidPolylineException() { // Arrange - TestStringDecoder decoder = new(); + PolylineDecoder decoder = CreateDecoder(); // Act & Assert - Assert.ThrowsExactly(() => decoder.Decode(string.Empty).ToList()); + Assert.ThrowsExactly( + () => decoder.Decode(string.Empty).ToList()); } /// - /// Tests that Decode with a polyline containing an invalid character throws . + /// Tests that a polyline containing a character outside the valid range throws + /// . /// [TestMethod] public void Decode_With_Invalid_Character_Polyline_Throws_InvalidPolylineException() { // Arrange - TestStringDecoder decoder = new(); + PolylineDecoder decoder = CreateDecoder(); - // '!' (33) is below allowed range ('?' == 63) + // '!' (33) is below the allowed minimum '?' (63) // Act & Assert - Assert.ThrowsExactly(() => decoder.Decode("!").ToList()); + Assert.ThrowsExactly( + () => decoder.Decode("!").ToList()); } /// - /// Tests that Decode with a valid polyline returns the expected coordinates. + /// Tests that a polyline that contains only enough data for one of the required columns + /// (i.e., a partial item) throws . /// [TestMethod] + public void Decode_With_Truncated_Polyline_Throws_InvalidPolylineException() { + // Arrange — "?" encodes a single delta-0 value. A 2-column formatter reads lat + // successfully from "?" but then finds no bytes left for lon → invalid. + PolylineDecoder decoder = CreateDecoder(); + + // Act & Assert + Assert.ThrowsExactly( + () => decoder.Decode("?").ToList()); + } + + // ------------------------------------------------------------------ + // Decode — happy path + // ------------------------------------------------------------------ + + /// Tests that a valid polyline produces the expected coordinates. + [TestMethod] public void Decode_With_Valid_Polyline_Returns_Expected_Coordinates() { // Arrange - TestStringDecoder decoder = new(); + PolylineDecoder decoder = CreateDecoder(); string polyline = StaticValueProvider.Valid.GetPolyline(); (double Latitude, double Longitude)[] expected = [.. StaticValueProvider.Valid.GetCoordinates()]; // Act - (double Latitude, double Longitude)[] result = [.. decoder.Decode(polyline)]; + (double Lat, double Lon)[] result = [.. decoder.Decode(polyline)]; // Assert Assert.AreEqual(expected.Length, result.Length); for (int i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i].Latitude, result[i].Latitude, 1e-5); - Assert.AreEqual(expected[i].Longitude, result[i].Longitude, 1e-5); + Assert.AreEqual(expected[i].Latitude, result[i].Lat, 1e-5); + Assert.AreEqual(expected[i].Longitude, result[i].Lon, 1e-5); } } + // ------------------------------------------------------------------ + // Decode — cancellation + // ------------------------------------------------------------------ + + /// Tests that a pre-cancelled token throws . + [TestMethod] + public void Decode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { + // Arrange + PolylineDecoder decoder = CreateDecoder(); + string polyline = StaticValueProvider.Valid.GetPolyline(); + using CancellationTokenSource cts = new(); + cts.Cancel(); + + // Act & Assert + Assert.ThrowsExactly( + () => decoder.Decode(polyline, cancellationToken: cts.Token).ToList()); + } + + // ------------------------------------------------------------------ + // Decode — missing WithCreate factory + // ------------------------------------------------------------------ + /// - /// Tests that the options constructor with null throws . + /// Tests that decoding with a formatter that has no WithCreate factory throws + /// . /// [TestMethod] - public void Constructor_With_Null_Options_Throws_ArgumentNullException() { + public void Decode_Without_WithCreate_Throws_InvalidOperationException() { + // Arrange — formatter built without WithCreate + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", c => c.Lat) + .AddValue("lon", c => c.Lon) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineDecoder decoder = + new(new PolylineOptions<(double Lat, double Lon), string>(formatter)); + + string polyline = StaticValueProvider.Valid.GetPolyline(); + // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringDecoderWithOptions(null!)); - Assert.AreEqual("options", ex.ParamName); + Assert.ThrowsExactly( + () => decoder.Decode(polyline).ToList()); } + // ------------------------------------------------------------------ + // Chunked Decode — options overload + // ------------------------------------------------------------------ + /// - /// Tests that the Options property returns the configured options. + /// Tests that the chunked overload with null options produces the same result as the standard + /// overload. /// [TestMethod] - public void Options_With_Default_Returns_Default_Options() { + public void Decode_With_Null_Options_Produces_Same_Result_As_Standard() { // Arrange - TestStringDecoder decoder = new(); + PolylineDecoder decoder = CreateDecoder(); + string polyline = StaticValueProvider.Valid.GetPolyline(); + + // Act + List<(double Lat, double Lon)> standard = decoder.Decode(polyline).ToList(); + List<(double Lat, double Lon)> chunked = decoder.Decode(polyline, null, CancellationToken.None).ToList(); // Assert - Assert.IsNotNull(decoder.Options); - Assert.AreEqual(5u, decoder.Options.Precision); + Assert.AreEqual(standard.Count, chunked.Count); + for (int i = 0; i < standard.Count; i++) { + Assert.AreEqual(standard[i].Lat, chunked[i].Lat, 1e-5); + Assert.AreEqual(standard[i].Lon, chunked[i].Lon, 1e-5); + } } /// - /// Tests that the options constructor stores the provided options. + /// Tests that chunked decoding with a Previous coordinate seeds the accumulated state + /// correctly, producing a result different from standard decoding of the same polyline. /// [TestMethod] - public void Constructor_With_Options_Stores_Options() { - // Arrange - PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(7) - .Build(); + public void Decode_With_Previous_Seeds_Accumulated_State() { + // Arrange — build a chunked polyline where chunk B is relative to the last of chunk A + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", c => c.Lat) + .AddValue("lon", c => c.Lon) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineOptions<(double Lat, double Lon), string> options = new(formatter); + PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + PolylineDecoder decoder = new(options); + + (double Lat, double Lon)[] chunkA = [(38.5, -120.2), (40.7, -120.95)]; + (double Lat, double Lon)[] chunkB = [(43.252, -126.453)]; + + string polylineB = encoder.Encode( + chunkB.AsSpan(), + new PolylineEncodingOptions<(double Lat, double Lon)>(chunkA[^1]), + CancellationToken.None); // Act - TestStringDecoderWithOptions decoder = new(options); + (double Lat, double Lon) decodedWithSeed = decoder.Decode( + polylineB, + new PolylineDecodingOptions<(double Lat, double Lon)>(chunkA[^1]), + CancellationToken.None).First(); - // Assert - Assert.AreSame(options, decoder.Options); + // Assert — decoded value should match the original chunkB[0] + Assert.AreEqual(chunkB[0].Lat, decodedWithSeed.Lat, 1e-5); + Assert.AreEqual(chunkB[0].Lon, decodedWithSeed.Lon, 1e-5); } /// - /// Tests that Decode with a pre-cancelled token throws . + /// Tests chunked encode + chunked decode round-trip: splitting a sequence into two chunks, + /// encoding with chaining, then decoding each chunk independently with Previous seed produces + /// the original sequence. /// [TestMethod] - public void Decode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { + public void Decode_RoundTrip_Reproduces_Full_Sequence() { // Arrange - TestStringDecoder decoder = new(); - string polyline = StaticValueProvider.Valid.GetPolyline(); - using CancellationTokenSource cts = new(); - cts.Cancel(); + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", c => c.Lat) + .AddValue("lon", c => c.Lon) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); - // Act & Assert - Assert.ThrowsExactly(() => decoder.Decode(polyline, cts.Token).ToList()); + PolylineOptions<(double Lat, double Lon), string> options = new(formatter); + PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + PolylineDecoder decoder = new(options); + + (double Lat, double Lon)[] all = [ + (38.5, -120.2), + (40.7, -120.95), + (43.252, -126.453), + (47.6, -122.3), + ]; + + (double Lat, double Lon)[] chunkA = all[..2]; + (double Lat, double Lon)[] chunkB = all[2..]; + + // Encode chunked + string polylineA = encoder.Encode(chunkA.AsSpan()); + string polylineB = encoder.Encode( + chunkB.AsSpan(), + new PolylineEncodingOptions<(double Lat, double Lon)>(chunkA[^1]), + CancellationToken.None); + + // Decode chunked + List<(double Lat, double Lon)> decodedA = decoder.Decode(polylineA).ToList(); + List<(double Lat, double Lon)> decodedB = decoder.Decode( + polylineB, + new PolylineDecodingOptions<(double Lat, double Lon)>(decodedA[^1]), + CancellationToken.None).ToList(); + + List<(double Lat, double Lon)> combined = [.. decodedA, .. decodedB]; + + // Assert + Assert.AreEqual(all.Length, combined.Count); + for (int i = 0; i < all.Length; i++) { + Assert.AreEqual(all[i].Lat, combined[i].Lat, 1e-5); + Assert.AreEqual(all[i].Lon, combined[i].Lon, 1e-5); + } } } diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs index f537dda2..f026df90 100644 --- a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs @@ -5,88 +5,81 @@ namespace PolylineAlgorithm.Tests.Abstraction; -using PolylineAlgorithm.Abstraction; using PolylineAlgorithm.Utility; using System; +using System.Threading; /// -/// Tests for . +/// Tests for . /// [TestClass] public sealed class AbstractPolylineEncoderTests { - private sealed class TestStringEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - public TestStringEncoder() - : base() { } + // ------------------------------------------------------------------ + // Helpers + // ------------------------------------------------------------------ - public TestStringEncoder(PolylineEncodingOptions options) - : base(options) { } + private static readonly Func, string> _write = m => new string(m.Span); + private static readonly Func> _read = s => s.AsMemory(); - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); - protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; - } + private static PolylineEncoder<(double Lat, double Lon), string> CreateEncoder(int stackAllocLimit = 512) { + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", static c => c.Lat) + .AddValue("lon", static c => c.Lon) + .WithReaderWriter(_write, _read) + .Build(); - /// - /// Tests that the default constructor creates an instance with default options. - /// - [TestMethod] - public void Constructor_With_Default_Options_Creates_Instance() { - // Act - TestStringEncoder encoder = new(); - - // Assert - Assert.IsNotNull(encoder); - Assert.IsNotNull(encoder.Options); - Assert.AreEqual(5u, encoder.Options.Precision); - Assert.AreEqual(512, encoder.Options.StackAllocLimit); + return new PolylineEncoder<(double Lat, double Lon), string>( + new PolylineOptions<(double Lat, double Lon), string>(formatter, stackAllocLimit)); } - /// - /// Tests that the options constructor with null throws . - /// + // ------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------ + + /// Tests that a null options argument throws . [TestMethod] public void Constructor_With_Null_Options_Throws_ArgumentNullException() { // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringEncoder(null!)); + ArgumentNullException ex = Assert.ThrowsExactly( + () => new PolylineEncoder<(double, double), string>(null!)); Assert.AreEqual("options", ex.ParamName); } - /// - /// Tests that the options constructor stores the provided options. - /// + /// Tests that a valid options argument creates the instance without throwing. [TestMethod] - public void Constructor_With_Options_Stores_Options() { - // Arrange - PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(7) - .Build(); - + public void Constructor_With_Valid_Options_Creates_Instance() { // Act - TestStringEncoder encoder = new(options); + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); // Assert - Assert.AreSame(options, encoder.Options); + Assert.IsNotNull(encoder); } - /// - /// Tests that Encode with an empty span throws . - /// + // ------------------------------------------------------------------ + // Encode — argument validation + // ------------------------------------------------------------------ + + /// Tests that encoding an empty span throws . [TestMethod] public void Encode_With_Empty_Span_Throws_ArgumentException() { // Arrange - TestStringEncoder encoder = new(); + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); // Act & Assert - Assert.ThrowsExactly(() => encoder.Encode(ReadOnlySpan<(double, double)>.Empty)); + Assert.ThrowsExactly( + () => encoder.Encode(ReadOnlySpan<(double, double)>.Empty)); } - /// - /// Tests that Encode with a single valid coordinate returns a non-empty string. - /// + // ------------------------------------------------------------------ + // Encode — happy path + // ------------------------------------------------------------------ + + /// Tests that a single coordinate encodes to a non-empty string. [TestMethod] public void Encode_With_Single_Coordinate_Returns_Non_Empty_String() { // Arrange - TestStringEncoder encoder = new(); + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); (double, double)[] coordinates = [(0.0, 0.0)]; // Act @@ -97,14 +90,12 @@ public void Encode_With_Single_Coordinate_Returns_Non_Empty_String() { Assert.IsTrue(result.Length > 0); } - /// - /// Tests that Encode with known coordinates returns the expected polyline string. - /// + /// Tests that known coordinates produce the expected polyline string. [TestMethod] public void Encode_With_Known_Coordinates_Returns_Expected_Polyline() { // Arrange - TestStringEncoder encoder = new(); - (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); + (double Lat, double Lon)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; string expected = StaticValueProvider.Valid.GetPolyline(); // Act @@ -114,33 +105,37 @@ public void Encode_With_Known_Coordinates_Returns_Expected_Polyline() { Assert.AreEqual(expected, result); } - /// - /// Tests that Encode with a pre-cancelled token throws . - /// + // ------------------------------------------------------------------ + // Encode — cancellation + // ------------------------------------------------------------------ + + /// Tests that a pre-cancelled token throws . [TestMethod] public void Encode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { // Arrange - TestStringEncoder encoder = new(); + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); using CancellationTokenSource cts = new(); cts.Cancel(); (double, double)[] coordinates = [(0.0, 0.0), (1.0, 1.0)]; // Act & Assert - Assert.ThrowsExactly(() => encoder.Encode(coordinates.AsSpan(), cts.Token)); + Assert.ThrowsExactly( + () => encoder.Encode(coordinates.AsSpan(), cts.Token)); } + // ------------------------------------------------------------------ + // Encode — heap allocation path + // ------------------------------------------------------------------ + /// - /// Tests that Encode still produces the correct result when the buffer exceeds the stack allocation - /// limit, forcing heap allocation via . + /// Tests that a stack-alloc limit of 1 forces heap allocation via ArrayPool + /// but still produces the correct result. /// [TestMethod] - public void Encode_With_Small_Stack_Alloc_Limit_Uses_Heap_Allocation_And_Produces_Correct_Result() { - // Arrange — force heap path by making stackAllocLimit smaller than any real encoding needs - PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() - .WithStackAllocLimit(1) - .Build(); - TestStringEncoder encoder = new(options); - (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + public void Encode_With_Small_Stack_Alloc_Limit_Uses_Heap_And_Produces_Correct_Result() { + // Arrange — limit of 1 forces every encode to go through ArrayPool + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(stackAllocLimit: 1); + (double Lat, double Lon)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; string expected = StaticValueProvider.Valid.GetPolyline(); // Act @@ -149,4 +144,213 @@ public void Encode_With_Small_Stack_Alloc_Limit_Uses_Heap_Allocation_And_Produce // Assert Assert.AreEqual(expected, result); } + + // ------------------------------------------------------------------ + // Encode — baseline + // ------------------------------------------------------------------ + + /// + /// Tests that a non-zero baseline shifts the first delta so the result differs + /// from the same encode without a baseline. + /// + [TestMethod] + public void Encode_With_Baseline_Produces_Different_First_Delta() { + // Arrange + PolylineFormatter<(double, double), string> formatterWithBaseline = + FormatterBuilder<(double, double), string>.Create() + .AddValue("lat", c => c.Item1).SetBaseline(100000L) // scaled 1.0 at precision 5 + .AddValue("lon", c => c.Item2) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineFormatter<(double, double), string> formatterNoBaseline = + FormatterBuilder<(double, double), string>.Create() + .AddValue("lat", c => c.Item1) + .AddValue("lon", c => c.Item2) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineEncoder<(double, double), string> encoderWithBaseline = + new(new PolylineOptions<(double, double), string>(formatterWithBaseline)); + PolylineEncoder<(double, double), string> encoderNoBaseline = + new(new PolylineOptions<(double, double), string>(formatterNoBaseline)); + + (double, double)[] coordinates = [(1.0, 2.0)]; + + // Act + string resultWith = encoderWithBaseline.Encode(coordinates.AsSpan()); + string resultWithout = encoderNoBaseline.Encode(coordinates.AsSpan()); + + // Assert — the two encodings must differ because the first lat delta changes + Assert.AreNotEqual(resultWithout, resultWith); + } + + // ------------------------------------------------------------------ + // Round-trip + // ------------------------------------------------------------------ + + /// + /// Tests that encoding then decoding reproduces the original coordinates within + /// the precision of the encoding. + /// + [TestMethod] + public void Encode_RoundTrip_Produces_Original_Coordinates() { + // Arrange + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", c => c.Lat) + .AddValue("lon", c => c.Lon) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineOptions<(double Lat, double Lon), string> options = new(formatter); + PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + PolylineDecoder decoder = new(options); + + (double Lat, double Lon)[] original = [.. StaticValueProvider.Valid.GetCoordinates()]; + + // Act + string encoded = encoder.Encode(original.AsSpan()); + (double Lat, double Lon)[] decoded = [.. decoder.Decode(encoded)]; + + // Assert + Assert.AreEqual(original.Length, decoded.Length); + for (int i = 0; i < original.Length; i++) { + Assert.AreEqual(original[i].Lat, decoded[i].Lat, 1e-5); + Assert.AreEqual(original[i].Lon, decoded[i].Lon, 1e-5); + } + } + + // ------------------------------------------------------------------ + // Chunked Encode — options overload + // ------------------------------------------------------------------ + + /// + /// Tests that the chunked overload with null options produces the same result as the standard + /// overload (no baseline difference). + /// + [TestMethod] + public void Encode_Chunked_With_Null_Options_Produces_Same_Result_As_Standard() { + // Arrange + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); + (double, double)[] coordinates = [(38.5, -120.2), (40.7, -120.95), (43.252, -126.453)]; + + // Act + string standard = encoder.Encode(coordinates.AsSpan()); + string chunked = encoder.Encode(coordinates.AsSpan(), null, CancellationToken.None); + + // Assert + Assert.AreEqual(standard, chunked); + } + + /// + /// Tests that chunked encoding with a Previous coordinate seeds the delta baseline correctly, + /// producing a result different from standard encoding of the same coordinates. + /// + [TestMethod] + public void Encode_Chunked_With_Previous_Seeds_Delta_Baseline() { + // Arrange + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); + (double Lat, double Lon)[] chunkA = [(38.5, -120.2), (40.7, -120.95)]; + (double Lat, double Lon)[] chunkB = [(43.252, -126.453)]; + + // Act — encode chunk B starting from zero (standard) and from chunkA's last point + string standardB = encoder.Encode(chunkB.AsSpan()); + string chunkedB = encoder.Encode( + chunkB.AsSpan(), + new PolylineEncodingOptions<(double Lat, double Lon)>(chunkA[^1]), + CancellationToken.None); + + // Assert — the two polylines must differ because the first delta changes + Assert.AreNotEqual(standardB, chunkedB); + } + + /// + /// Tests that encoding in two chunks produces a polyline that, when concatenated, decodes to + /// the same coordinate sequence as encoding the full sequence at once. + /// + [TestMethod] + public void Encode_Chunked_Concatenated_Decodes_To_Full_Sequence() { + // Arrange + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("lat", c => c.Lat) + .AddValue("lon", c => c.Lon) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); + + PolylineOptions<(double Lat, double Lon), string> options = new(formatter); + PolylineEncoder<(double Lat, double Lon), string> encoder = new(options); + PolylineDecoder decoder = new(options); + + (double Lat, double Lon)[] all = [ + (38.5, -120.2), + (40.7, -120.95), + (43.252, -126.453), + (47.6, -122.3), + ]; + + (double Lat, double Lon)[] chunkA = all[..2]; + (double Lat, double Lon)[] chunkB = all[2..]; + + // Act + string polylineA = encoder.Encode(chunkA.AsSpan()); + string polylineB = encoder.Encode( + chunkB.AsSpan(), + new PolylineEncodingOptions<(double Lat, double Lon)>(chunkA[^1]), + CancellationToken.None); + + string concatenated = polylineA + polylineB; + string fullEncoding = encoder.Encode(all.AsSpan()); + + (double Lat, double Lon)[] decodedConcatenated = [.. decoder.Decode(concatenated)]; + (double Lat, double Lon)[] decodedFull = [.. decoder.Decode(fullEncoding)]; + + // Assert + Assert.AreEqual(concatenated, fullEncoding, + "Chunked-then-concatenated polyline should equal the full-sequence encoding."); + Assert.AreEqual(all.Length, decodedConcatenated.Length); + for (int i = 0; i < all.Length; i++) { + Assert.AreEqual(all[i].Lat, decodedConcatenated[i].Lat, 1e-5); + Assert.AreEqual(all[i].Lon, decodedConcatenated[i].Lon, 1e-5); + Assert.AreEqual(decodedFull[i].Lat, decodedConcatenated[i].Lat, 1e-5); + Assert.AreEqual(decodedFull[i].Lon, decodedConcatenated[i].Lon, 1e-5); + } + } + + /// + /// Tests that an empty span still throws when using the + /// chunked overload. + /// + [TestMethod] + public void Encode_Chunked_With_Empty_Span_Throws_ArgumentException() { + // Arrange + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); + + // Act & Assert + Assert.ThrowsExactly( + () => encoder.Encode( + ReadOnlySpan<(double, double)>.Empty, + new PolylineEncodingOptions<(double Lat, double Lon)>(), + CancellationToken.None)); + } + + /// + /// Tests that a pre-cancelled token throws in the + /// chunked overload. + /// + [TestMethod] + public void Encode_Chunked_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { + // Arrange + PolylineEncoder<(double Lat, double Lon), string> encoder = CreateEncoder(); + using CancellationTokenSource cts = new(); + cts.Cancel(); + (double, double)[] coordinates = [(0.0, 0.0), (1.0, 1.0)]; + + // Act & Assert + Assert.ThrowsExactly( + () => encoder.Encode(coordinates.AsSpan(), null, cts.Token)); + } } diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs deleted file mode 100644 index c44c914d..00000000 --- a/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs +++ /dev/null @@ -1,172 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Tests.Extensions; - -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Extensions; -using PolylineAlgorithm.Utility; -using System; -using System.Collections.Generic; - -/// -/// Tests for . -/// -[TestClass] -public sealed class PolylineDecoderExtensionsTests { - private sealed class TestStringDecoder : AbstractPolylineDecoder { - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); - } - - private sealed class TestMemoryDecoder : AbstractPolylineDecoder, (double Latitude, double Longitude)> { - protected override ReadOnlyMemory GetReadOnlyMemory(in ReadOnlyMemory polyline) => polyline; - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); - } - - // ----- Decode(char[]) for IPolylineDecoder ----- - - /// - /// Tests that Decode with a null decoder throws . - /// - [TestMethod] - public void Decode_With_Char_Array_Null_Decoder_Throws_ArgumentNullException() { - // Arrange - IPolylineDecoder? decoder = null; - char[] polyline = StaticValueProvider.Valid.GetPolyline().ToCharArray(); - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => decoder!.Decode(polyline).ToList()); - Assert.AreEqual("decoder", ex.ParamName); - } - - /// - /// Tests that Decode with a null char array throws . - /// - [TestMethod] - public void Decode_With_Char_Array_Null_Polyline_Throws_ArgumentNullException() { - // Arrange - TestStringDecoder decoder = new(); - char[]? polyline = null; - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => decoder.Decode(polyline!).ToList()); - Assert.AreEqual("polyline", ex.ParamName); - } - - /// - /// Tests that Decode with a valid char array returns expected coordinates. - /// - [TestMethod] - public void Decode_With_Char_Array_Valid_Polyline_Returns_Expected_Coordinates() { - // Arrange - TestStringDecoder decoder = new(); - char[] polyline = StaticValueProvider.Valid.GetPolyline().ToCharArray(); - (double Latitude, double Longitude)[] expected = [.. StaticValueProvider.Valid.GetCoordinates()]; - - // Act - (double Latitude, double Longitude)[] result = [.. decoder.Decode(polyline)]; - - // Assert - Assert.AreEqual(expected.Length, result.Length); - for (int i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i].Latitude, result[i].Latitude, 1e-5); - Assert.AreEqual(expected[i].Longitude, result[i].Longitude, 1e-5); - } - } - - // ----- Decode(ReadOnlyMemory) for IPolylineDecoder ----- - - /// - /// Tests that Decode with a null decoder throws . - /// - [TestMethod] - public void Decode_With_Memory_Null_Decoder_Throws_ArgumentNullException() { - // Arrange - IPolylineDecoder? decoder = null; - ReadOnlyMemory polyline = StaticValueProvider.Valid.GetPolyline().AsMemory(); - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => decoder!.Decode(polyline).ToList()); - Assert.AreEqual("decoder", ex.ParamName); - } - - /// - /// Tests that Decode with a valid memory returns expected coordinates. - /// - [TestMethod] - public void Decode_With_Memory_Valid_Polyline_Returns_Expected_Coordinates() { - // Arrange - TestStringDecoder decoder = new(); - ReadOnlyMemory polyline = StaticValueProvider.Valid.GetPolyline().AsMemory(); - (double Latitude, double Longitude)[] expected = [.. StaticValueProvider.Valid.GetCoordinates()]; - - // Act - (double Latitude, double Longitude)[] result = [.. decoder.Decode(polyline)]; - - // Assert - Assert.AreEqual(expected.Length, result.Length); - for (int i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i].Latitude, result[i].Latitude, 1e-5); - Assert.AreEqual(expected[i].Longitude, result[i].Longitude, 1e-5); - } - } - - // ----- Decode(string) for IPolylineDecoder, TValue> ----- - - /// - /// Tests that Decode with a null decoder throws . - /// - [TestMethod] - public void Decode_With_String_Null_Decoder_Throws_ArgumentNullException() { - // Arrange - IPolylineDecoder, (double, double)>? decoder = null; - string polyline = StaticValueProvider.Valid.GetPolyline(); - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => PolylineDecoderExtensions.Decode<(double, double)>(decoder!, polyline).ToList()); - Assert.AreEqual("decoder", ex.ParamName); - } - - /// - /// Tests that Decode with a null string throws . - /// - [TestMethod] - public void Decode_With_String_Null_Polyline_Throws_ArgumentNullException() { - // Arrange - TestMemoryDecoder decoder = new(); - string? polyline = null; - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => decoder.Decode(polyline!).ToList()); - Assert.AreEqual("polyline", ex.ParamName); - } - - /// - /// Tests that Decode with a valid string returns expected coordinates. - /// - [TestMethod] - public void Decode_With_String_Valid_Polyline_Returns_Expected_Coordinates() { - // Arrange - TestMemoryDecoder decoder = new(); - string polyline = StaticValueProvider.Valid.GetPolyline(); - (double Latitude, double Longitude)[] expected = [.. StaticValueProvider.Valid.GetCoordinates()]; - - // Act - (double Latitude, double Longitude)[] result = [.. decoder.Decode(polyline)]; - - // Assert - Assert.AreEqual(expected.Length, result.Length); - for (int i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i].Latitude, result[i].Latitude, 1e-5); - Assert.AreEqual(expected[i].Longitude, result[i].Longitude, 1e-5); - } - } -} diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs index da8a622e..c72a1568 100644 --- a/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs @@ -5,6 +5,7 @@ namespace PolylineAlgorithm.Tests.Extensions; +using PolylineAlgorithm; using PolylineAlgorithm.Abstraction; using PolylineAlgorithm.Extensions; using PolylineAlgorithm.Utility; @@ -16,59 +17,16 @@ namespace PolylineAlgorithm.Tests.Extensions; /// [TestClass] public sealed class PolylineEncoderExtensionsTests { - private sealed class TestStringEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); - protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; - } - - // ----- Encode(List) ----- - - /// - /// Tests that Encode with a null encoder throws . - /// - [TestMethod] - public void Encode_With_List_Null_Encoder_Throws_ArgumentNullException() { - // Arrange — use interface type so the extension method is resolved - IPolylineEncoder<(double, double), string>? encoder = null; - List<(double, double)> coordinates = [(0.0, 0.0)]; - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => encoder!.Encode(coordinates)); - Assert.AreEqual("encoder", ex.ParamName); - } - - /// - /// Tests that Encode with a null list throws . - /// - [TestMethod] - public void Encode_With_List_Null_Coordinates_Throws_ArgumentNullException() { - // Arrange - TestStringEncoder encoder = new(); - List<(double, double)>? coordinates = null; - - // Act & Assert - ArgumentNullException ex = Assert.ThrowsExactly( - () => encoder.Encode(coordinates!)); - Assert.AreEqual("coordinates", ex.ParamName); - } - - /// - /// Tests that Encode with a valid list returns the expected polyline. - /// - [TestMethod] - public void Encode_With_List_Valid_Coordinates_Returns_Expected_Polyline() { - // Arrange - TestStringEncoder encoder = new(); - List<(double Latitude, double Longitude)> coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; - string expected = StaticValueProvider.Valid.GetPolyline(); - - // Act - string result = encoder.Encode(coordinates); - - // Assert - Assert.AreEqual(expected, result); + private static PolylineEncoder<(double Latitude, double Longitude), string> CreateTestEncoder() { + PolylineFormatter<(double Latitude, double Longitude), string> formatter = + FormatterBuilder<(double Latitude, double Longitude), string>.Create() + .AddValue("lat", static c => c.Latitude) + .AddValue("lon", static c => c.Longitude) + .WithReaderWriter(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); + + return new PolylineEncoder<(double Latitude, double Longitude), string>( + new PolylineOptions<(double Latitude, double Longitude), string>(formatter)); } // ----- Encode(T[]) ----- @@ -95,7 +53,7 @@ public void Encode_With_Array_Null_Encoder_Throws_ArgumentNullException() { [TestMethod] public void Encode_With_Array_Null_Coordinates_Throws_ArgumentNullException() { // Arrange — call the extension method explicitly (same reasoning as above). - IPolylineEncoder<(double, double), string> encoder = new TestStringEncoder(); + IPolylineEncoder<(double, double), string> encoder = CreateTestEncoder(); (double, double)[]? coordinates = null; // Act & Assert @@ -110,7 +68,7 @@ public void Encode_With_Array_Null_Coordinates_Throws_ArgumentNullException() { [TestMethod] public void Encode_With_Array_Valid_Coordinates_Returns_Expected_Polyline() { // Arrange - TestStringEncoder encoder = new(); + var encoder = CreateTestEncoder(); (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; string expected = StaticValueProvider.Valid.GetPolyline(); diff --git a/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs deleted file mode 100644 index 1e915fe2..00000000 --- a/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Tests.Internal; - -using PolylineAlgorithm.Internal; -using System.Globalization; - -/// -/// Tests for . -/// -[TestClass] -public sealed class CoordinateDeltaTests { - /// - /// Tests that the default constructor initializes delta values to zero. - /// - [TestMethod] - public void Constructor_Default_Initializes_Latitude_And_Longitude_To_Zero() { - // Act - CoordinateDelta delta = new(); - - // Assert - Assert.AreEqual(0, delta.Latitude); - Assert.AreEqual(0, delta.Longitude); - } - - /// - /// Tests that a single call to Next computes the correct delta from the initial zero state. - /// - [TestMethod] - [DataRow(10, 20, 10, 20)] - [DataRow(-50, -100, -50, -100)] - [DataRow(0, 0, 0, 0)] - [DataRow(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] - [DataRow(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] - public void Next_Single_Call_From_Zero_Computes_Expected_Delta(int latitude, int longitude, int expectedLatitude, int expectedLongitude) { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(latitude, longitude); - - // Assert - Assert.AreEqual(expectedLatitude, delta.Latitude); - Assert.AreEqual(expectedLongitude, delta.Longitude); - } - - /// - /// Tests that two consecutive calls to Next compute the delta relative to the previous value. - /// - [TestMethod] - [DataRow(10, 20, 15, 30, 5, 10)] - [DataRow(100, 200, 50, 150, -50, -50)] - [DataRow(42, 84, 42, 84, 0, 0)] - [DataRow(-50, 100, 25, -75, 75, -175)] - public void Next_Sequential_Calls_Compute_Delta_From_Previous_Value( - int firstLatitude, int firstLongitude, - int secondLatitude, int secondLongitude, - int expectedLatitude, int expectedLongitude) { - // Arrange - CoordinateDelta delta = new(); - delta.Next(firstLatitude, firstLongitude); - - // Act - delta.Next(secondLatitude, secondLongitude); - - // Assert - Assert.AreEqual(expectedLatitude, delta.Latitude); - Assert.AreEqual(expectedLongitude, delta.Longitude); - } - - /// - /// Tests that ToString on a default instance returns a string containing expected structural keywords and a zero value. - /// - [TestMethod] - public void ToString_With_Default_Constructor_Returns_Formatted_String_With_Zeros() { - // Arrange - CoordinateDelta delta = new(); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains("Coordinate", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("Delta", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("Latitude", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("Longitude", StringComparison.Ordinal)); - Assert.Contains('0', result); - } - - /// - /// Tests that ToString reflects the delta values computed by Next. - /// - [TestMethod] - [DataRow(42, 84)] - [DataRow(-100, -200)] - [DataRow(int.MaxValue, int.MaxValue)] - [DataRow(int.MinValue, int.MinValue)] - public void ToString_After_Next_Contains_Expected_Values(int latitude, int longitude) { - // Arrange - CoordinateDelta delta = new(); - delta.Next(latitude, longitude); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains(latitude.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)); - Assert.IsTrue(result.Contains(longitude.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)); - } - - /// - /// Tests that ToString after multiple Next calls reflects the most recent delta values. - /// - [TestMethod] - public void ToString_After_Multiple_Next_Calls_Returns_Formatted_String_With_Latest_Values() { - // Arrange - CoordinateDelta delta = new(); - delta.Next(10, 20); - delta.Next(30, 50); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains("30", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("50", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("20", StringComparison.Ordinal)); - } - -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs index 26dafc74..e9abd8ca 100644 --- a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs @@ -5,421 +5,195 @@ namespace PolylineAlgorithm.Tests; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using PolylineAlgorithm; using System; /// -/// Tests for . +/// Tests for , +/// , and +/// write/read/create delegation. /// [TestClass] public sealed class PolylineEncodingOptionsBuilderTests { - /// - /// Tests that Create returns a new builder instance. - /// - [TestMethod] - public void Create_Returns_New_Builder() { - // Act - PolylineEncodingOptionsBuilder result = PolylineEncodingOptionsBuilder.Create(); + private static readonly Func, string> _write = m => new string(m.Span); + private static readonly Func> _read = s => s.AsMemory(); - // Assert - Assert.IsNotNull(result); - } + // --------------------------------------------------------------------------- + // FormatterBuilder.ForPolyline — argument validation + // --------------------------------------------------------------------------- - /// - /// Tests that Create returns different instances on multiple calls. - /// + /// Tests that ForPolyline with a null write delegate throws . [TestMethod] - public void Create_With_Multiple_Invocations_Returns_Different_Instances() { - // Act - PolylineEncodingOptionsBuilder first = PolylineEncodingOptionsBuilder.Create(); - PolylineEncodingOptionsBuilder second = PolylineEncodingOptionsBuilder.Create(); - - // Assert - Assert.AreNotSame(first, second); - } - - /// - /// Tests that Build returns options with default values. - /// - [TestMethod] - public void Build_With_Defaults_Returns_Options_With_Default_Values() { + public void ForPolyline_With_Null_Write_Throws_ArgumentNullException() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptions result = builder.Build(); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(5u, result.Precision); - Assert.AreEqual(512, result.StackAllocLimit); - Assert.IsNotNull(result.LoggerFactory); - Assert.IsInstanceOfType(result.LoggerFactory); + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => builder.WithReaderWriter(null!, _read)); + Assert.AreEqual("write", ex.ParamName); } - /// - /// Tests that Build returns options with configured precision. - /// + /// Tests that ForPolyline with a null read delegate throws . [TestMethod] - public void Build_With_Custom_Precision_Returns_Options_With_Custom_Precision() { + public void ForPolyline_With_Null_Read_Throws_ArgumentNullException() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(7); - - // Act - PolylineEncodingOptions result = builder.Build(); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); - // Assert - Assert.AreEqual(7u, result.Precision); + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => builder.WithReaderWriter(_write, null!)); + Assert.AreEqual("read", ex.ParamName); } - /// - /// Tests that Build returns options with configured stack alloc limit. - /// + /// Tests that ForPolyline returns the same builder instance for method chaining. [TestMethod] - public void Build_With_Custom_Stack_Alloc_Limit_Returns_Options_With_Custom_Stack_Alloc_Limit() { + public void ForPolyline_Returns_Same_Builder_For_Method_Chaining() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithStackAllocLimit(1024); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); // Act - PolylineEncodingOptions result = builder.Build(); + FormatterBuilder result = builder.WithReaderWriter(_write, _read); // Assert - Assert.AreEqual(1024, result.StackAllocLimit); + Assert.AreSame(builder, result); } - /// - /// Tests that Build returns options with configured logger factory. - /// + /// Tests that Build without a prior ForPolyline call throws . [TestMethod] - public void Build_With_Custom_Logger_Factory_Returns_Options_With_Custom_Logger_Factory() { + public void Build_Without_ForPolyline_Throws_InvalidOperationException() { // Arrange - ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithLoggerFactory(loggerFactory); - - // Act - PolylineEncodingOptions result = builder.Build(); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); - // Assert - Assert.AreSame(loggerFactory, result.LoggerFactory); - - // Cleanup - loggerFactory.Dispose(); + // Act & Assert + Assert.ThrowsExactly(() => builder.Build()); } - /// - /// Tests that Build returns options with all custom values. - /// - [TestMethod] - public void Build_With_All_Custom_Values_Returns_Options_With_All_Custom_Values() { - // Arrange - ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(10) - .WithStackAllocLimit(2048) - .WithLoggerFactory(loggerFactory); - - // Act - PolylineEncodingOptions result = builder.Build(); - - // Assert - Assert.AreEqual(10u, result.Precision); - Assert.AreEqual(2048, result.StackAllocLimit); - Assert.AreSame(loggerFactory, result.LoggerFactory); - - // Cleanup - loggerFactory.Dispose(); - } + // --------------------------------------------------------------------------- + // FormatterBuilder.WithCreate — argument validation + // --------------------------------------------------------------------------- - /// - /// Tests that Build can be called multiple times on the same builder. - /// + /// Tests that WithCreate with a null factory throws . [TestMethod] - public void Build_With_Multiple_Invocations_Returns_Different_Instances_With_Same_Values() { + public void WithCreate_With_Null_Factory_Throws_ArgumentNullException() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(6); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); - // Act - PolylineEncodingOptions first = builder.Build(); - PolylineEncodingOptions second = builder.Build(); - - // Assert - Assert.AreNotSame(first, second); - Assert.AreEqual(first.Precision, second.Precision); - Assert.AreEqual(first.StackAllocLimit, second.StackAllocLimit); + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => builder.WithValueFactory(null!)); + Assert.AreEqual("create", ex.ParamName); } - /// - /// Tests that WithStackAllocLimit sets the value and returns the builder. - /// + /// Tests that WithCreate returns the same builder instance for method chaining. [TestMethod] - public void WithStackAllocLimit_With_Valid_Value_Sets_Value_And_Returns_Self() { + public void WithCreate_Returns_Same_Builder_For_Method_Chaining() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + FormatterBuilder builder = FormatterBuilder.Create() + .AddValue("Value", static v => v); // Act - PolylineEncodingOptionsBuilder result = builder.WithStackAllocLimit(256); + FormatterBuilder result = builder.WithValueFactory(static v => v[0]); // Assert Assert.AreSame(builder, result); - PolylineEncodingOptions options = builder.Build(); - Assert.AreEqual(256, options.StackAllocLimit); - } - - /// - /// Tests that WithStackAllocLimit accepts minimum value of 1. - /// - [TestMethod] - public void WithStackAllocLimit_With_Minimum_Value_Sets_Value() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - builder.WithStackAllocLimit(1); - PolylineEncodingOptions result = builder.Build(); - - // Assert - Assert.AreEqual(1, result.StackAllocLimit); - } - - /// - /// Tests that WithStackAllocLimit throws ArgumentOutOfRangeException for zero. - /// - [TestMethod] - public void WithStackAllocLimit_With_Zero_Throws_ArgumentOutOfRangeException() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - var exception = Assert.ThrowsExactly(() => builder.WithStackAllocLimit(0)); - - // Assert - Assert.AreEqual("stackAllocLimit", exception.ParamName); } - /// - /// Tests that WithStackAllocLimit throws ArgumentOutOfRangeException for negative value. - /// - [TestMethod] - public void WithStackAllocLimit_With_Negative_Value_Throws_ArgumentOutOfRangeException() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - var exception = Assert.ThrowsExactly(() => builder.WithStackAllocLimit(-10)); + // --------------------------------------------------------------------------- + // PolylineFormatter — Write and Read delegation + // --------------------------------------------------------------------------- - // Assert - Assert.AreEqual("stackAllocLimit", exception.ParamName); - } - - /// - /// Tests that WithStackAllocLimit accepts large value. - /// + /// Tests that Write delegates to the supplied write function. [TestMethod] - public void WithStackAllocLimit_With_Large_Value_Sets_Value() { + public void Write_Delegates_To_Supplied_Write_Function() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptions result = builder - .WithStackAllocLimit(100000) + ReadOnlyMemory input = "hello".AsMemory(); + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) .Build(); - // Assert - Assert.AreEqual(100000, result.StackAllocLimit); - } - - /// - /// Tests that WithStackAllocLimit can be called multiple times. - /// - [TestMethod] - public void WithStackAllocLimit_With_Multiple_Calls_Last_Value_Wins() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - // Act - PolylineEncodingOptions result = builder.WithStackAllocLimit(100) - .WithStackAllocLimit(200) - .WithStackAllocLimit(300) - .Build(); + string result = formatter.Write(input); // Assert - Assert.AreEqual(300, result.StackAllocLimit); - } - - /// - /// Tests that WithPrecision sets the value and returns the builder. - /// - [TestMethod] - public void WithPrecision_With_Valid_Value_Sets_Value_And_Returns_Self() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptionsBuilder result = builder - .WithPrecision(8); - PolylineEncodingOptions options = builder.Build(); - - // Assert - Assert.AreSame(builder, result); - Assert.AreEqual(8u, options.Precision); + Assert.AreEqual("hello", result); } - /// - /// Tests that WithPrecision accepts zero value. - /// + /// Tests that Read delegates to the supplied read function. [TestMethod] - public void WithPrecision_With_Zero_Sets_Value() { + public void Read_Delegates_To_Supplied_Read_Function() { // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptions result = builder - .WithPrecision(0) + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) .Build(); - // Assert - Assert.AreEqual(0u, result.Precision); - } - - /// - /// Tests that WithPrecision accepts maximum uint value. - /// - [TestMethod] - public void WithPrecision_With_Max_Value_Sets_Value() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - // Act - PolylineEncodingOptions result = builder - .WithPrecision(uint.MaxValue) - .Build(); + ReadOnlyMemory result = formatter.Read("world"); // Assert - Assert.AreEqual(uint.MaxValue, result.Precision); + Assert.IsTrue("world".AsMemory().Span.SequenceEqual(result.Span)); } - /// - /// Tests that WithPrecision can be called multiple times. - /// - [TestMethod] - public void WithPrecision_With_Multiple_Calls_Last_Value_Wins() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptions result = builder - .WithPrecision(5) - .WithPrecision(7) - .WithPrecision(9) - .Build(); + // --------------------------------------------------------------------------- + // PolylineFormatter — CreateItem + // --------------------------------------------------------------------------- - // Assert - Assert.AreEqual(9u, result.Precision); - } - - /// - /// Tests that WithLoggerFactory sets the factory and returns the builder. - /// - [TestMethod] - public void WithLoggerFactory_With_Valid_Factory_Sets_Value_And_Returns_Self() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); - - // Act - PolylineEncodingOptionsBuilder result = builder - .WithLoggerFactory(loggerFactory); - PolylineEncodingOptions options = builder.Build(); - - // Assert - Assert.AreSame(builder, result); - Assert.AreSame(loggerFactory, options.LoggerFactory); - - // Cleanup - loggerFactory.Dispose(); - } - - /// - /// Tests that WithLoggerFactory with null uses NullLoggerFactory. - /// + /// Tests that CreateItem without a factory throws . [TestMethod] - public void WithLoggerFactory_With_Null_Uses_Null_LoggerFactory() { - // Arrange - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); - - // Act - PolylineEncodingOptions result = builder - .WithLoggerFactory(null!) + public void CreateItem_Without_Factory_Throws_InvalidOperationException() { + // Arrange — no WithCreate call + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) .Build(); - // Assert - Assert.IsNotNull(result.LoggerFactory); - Assert.IsInstanceOfType(result.LoggerFactory); + // Act & Assert + Assert.ThrowsExactly( + () => formatter.CreateItem(new long[] { 100000L }.AsSpan())); } - /// - /// Tests that WithLoggerFactory can replace a previously set factory. - /// + /// Tests that CreateItem with a factory correctly constructs the item. [TestMethod] - public void WithLoggerFactory_With_Replace_Previous_Factory_Updates_Value() { - // Arrange - using ILoggerFactory firstFactory = LoggerFactory.Create(_ => { }); - using ILoggerFactory secondFactory = LoggerFactory.Create(_ => { }); - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithLoggerFactory(firstFactory); - - // Act - PolylineEncodingOptions result = builder - .WithLoggerFactory(secondFactory) + public void CreateItem_With_Factory_Returns_Expected_Value() { + // Arrange — precision 5; the formatter automatically divides the accumulated scaled integer + // by the factor (1e5), so the factory receives the denormalized double directly. + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v, precision: 5) + .WithValueFactory(static v => v[0]) + .WithReaderWriter(_write, _read) .Build(); - // Assert - Assert.AreSame(secondFactory, result.LoggerFactory); - } - - /// - /// Tests that WithLoggerFactory can be set to null after setting a factory. - /// - [TestMethod] - public void WithLoggerFactory_With_Null_After_Factory_Uses_Null_LoggerFactory() { - // Arrange - using ILoggerFactory factory = LoggerFactory.Create(_ => { }); - PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() - .WithLoggerFactory(factory); - - // Act - builder.WithLoggerFactory(null!); - PolylineEncodingOptions result = builder.Build(); + // Act — accumulated value 3850000 → 3850000 / 100000.0 = 38.5 passed to factory + double result = formatter.CreateItem(new long[] { 3850000L }.AsSpan()); // Assert - Assert.IsInstanceOfType(result.LoggerFactory); + Assert.AreEqual(38.5, result, 1e-9); } - /// - /// Tests that builder supports method chaining for all methods. - /// + /// Tests that CreateItem with a multi-value factory returns the correct item. [TestMethod] - public void MethodChaining_With_All_Methods_Returns_Builder_For_Chaining() { + public void CreateItem_With_Multi_Value_Factory_Returns_Expected_Tuple() { // Arrange - using ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("Lat", static t => t.Lat, precision: 5) + .AddValue("Lon", static t => t.Lon, precision: 5) + .WithValueFactory(static v => (v[0], v[1])) + .WithReaderWriter(_write, _read) + .Build(); // Act - PolylineEncodingOptions result = PolylineEncodingOptionsBuilder.Create() - .WithPrecision(6) - .WithStackAllocLimit(1024) - .WithLoggerFactory(loggerFactory) - .Build(); + (double Lat, double Lon) result = formatter.CreateItem(new long[] { 3850000L, -12025000L }.AsSpan()); // Assert - Assert.IsNotNull(result); - Assert.AreEqual(6u, result.Precision); - Assert.AreEqual(1024, result.StackAllocLimit); - Assert.AreSame(loggerFactory, result.LoggerFactory); + Assert.AreEqual(38.5, result.Lat, 1e-9); + Assert.AreEqual(-120.25, result.Lon, 1e-9); } -} \ No newline at end of file +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs index d598e3c2..04063082 100644 --- a/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs @@ -20,10 +20,10 @@ public sealed class PolylineEncodingTests { [TestMethod] public void Normalize_With_Zero_Value_Returns_Zero() { // Act - int result = PolylineEncoding.Normalize(0.0); + long result = PolylineEncoding.Normalize(0.0); // Assert - Assert.AreEqual(0, result); + Assert.AreEqual(0L, result); } /// @@ -51,9 +51,9 @@ public void Normalize_With_NonFinite_Value_Throws_ArgumentOutOfRangeException(do [DataRow(37.789999, 5u, 3778999)] [DataRow(0.00001, 5u, 1)] [DataRow(-0.00001, 5u, -1)] - public void Normalize_With_Value_And_Precision_Returns_Expected_Normalized_Value(double value, uint precision, int expected) { + public void Normalize_With_Value_And_Precision_Returns_Expected_Normalized_Value(double value, uint precision, long expected) { // Act - int result = PolylineEncoding.Normalize(value, precision); + long result = PolylineEncoding.Normalize(value, precision); // Assert Assert.AreEqual(expected, result); @@ -87,7 +87,7 @@ public void Denormalize_With_Zero_Value_Returns_Zero() { [DataRow(37789034, 6u, 37.789034)] [DataRow(1, 5u, 0.00001)] [DataRow(-1, 5u, -0.00001)] - public void Denormalize_With_Value_And_Precision_Returns_Expected_Denormalized_Value(int value, uint precision, double expected) { + public void Denormalize_With_Value_And_Precision_Returns_Expected_Denormalized_Value(long value, uint precision, double expected) { // Act double result = PolylineEncoding.Denormalize(value, precision); @@ -106,7 +106,7 @@ public void Denormalize_With_Value_And_Precision_Returns_Expected_Denormalized_V public void TryReadValue_With_Position_At_Buffer_Length_Returns_False() { // Arrange ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; + long delta = 0; int position = buffer.Length; // Act @@ -114,7 +114,7 @@ public void TryReadValue_With_Position_At_Buffer_Length_Returns_False() { // Assert Assert.IsFalse(result); - Assert.AreEqual(0, delta); + Assert.AreEqual(0L, delta); } /// @@ -124,7 +124,7 @@ public void TryReadValue_With_Position_At_Buffer_Length_Returns_False() { public void TryReadValue_With_Position_Exceeds_Buffer_Length_Returns_False() { // Arrange ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; + long delta = 0; int position = buffer.Length + 1; // Act @@ -132,7 +132,7 @@ public void TryReadValue_With_Position_Exceeds_Buffer_Length_Returns_False() { // Assert Assert.IsFalse(result); - Assert.AreEqual(0, delta); + Assert.AreEqual(0L, delta); } /// @@ -143,7 +143,7 @@ public void TryReadValue_With_Positive_Single_Char_Reads_Value_And_Returns_True( // Arrange // Encode value 5: zigzag = 10 = 0x0A; char = 10 + 63 = 73 = 'I' ReadOnlyMemory buffer = "I".AsMemory(); // Single char encoding of value 5 - int delta = 0; + long delta = 0; int position = 0; // Act @@ -151,7 +151,7 @@ public void TryReadValue_With_Positive_Single_Char_Reads_Value_And_Returns_True( // Assert Assert.IsTrue(result); - Assert.AreEqual(5, delta); + Assert.AreEqual(5L, delta); Assert.AreEqual(1, position); } @@ -163,7 +163,7 @@ public void TryReadValue_With_Positive_Multi_Char_Reads_Value_And_Returns_True() // Arrange // _p~iF encodes latitude 38.5 (normalized = 3850000, zigzag = 7700000) ReadOnlyMemory buffer = "_p~iF".AsMemory(); - int delta = 0; + long delta = 0; int position = 0; // Act @@ -171,7 +171,7 @@ public void TryReadValue_With_Positive_Multi_Char_Reads_Value_And_Returns_True() // Assert Assert.IsTrue(result); - Assert.AreEqual(3850000, delta); + Assert.AreEqual(3850000L, delta); Assert.AreEqual(5, position); } @@ -183,7 +183,7 @@ public void TryReadValue_With_Negative_Value_Reads_Value_And_Returns_True() { // Arrange // ~ps|U encodes longitude -120.2 (normalized = -12020000, zigzag encodes negative) ReadOnlyMemory buffer = "~ps|U".AsMemory(); - int delta = 0; + long delta = 0; int position = 0; // Act @@ -191,7 +191,7 @@ public void TryReadValue_With_Negative_Value_Reads_Value_And_Returns_True() { // Assert Assert.IsTrue(result); - Assert.AreEqual(-12020000, delta); + Assert.AreEqual(-12020000L, delta); Assert.AreEqual(5, position); } @@ -202,7 +202,7 @@ public void TryReadValue_With_Negative_Value_Reads_Value_And_Returns_True() { public void TryReadValue_With_Existing_Delta_Accumulates_Delta() { // Arrange ReadOnlyMemory buffer = "I".AsMemory(); // encodes 5 - int delta = 10; // existing delta + long delta = 10; // existing delta int position = 0; // Act @@ -210,7 +210,7 @@ public void TryReadValue_With_Existing_Delta_Accumulates_Delta() { // Assert Assert.IsTrue(result); - Assert.AreEqual(15, delta); // 10 + 5 = 15 + Assert.AreEqual(15L, delta); // 10 + 5 = 15 } /// @@ -220,12 +220,12 @@ public void TryReadValue_With_Existing_Delta_Accumulates_Delta() { public void TryReadValue_With_Multiple_Values_Reads_Sequentially() { // Arrange - "_p~iF~ps|U" encodes lat 38.5 then delta lon -120.2 ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; + long delta = 0; int position = 0; // Act - read first value bool first = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); - int firstDelta = delta; + long firstDelta = delta; // read second value bool second = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); @@ -233,7 +233,7 @@ public void TryReadValue_With_Multiple_Values_Reads_Sequentially() { // Assert Assert.IsTrue(first); Assert.IsTrue(second); - Assert.AreEqual(3850000, firstDelta); + Assert.AreEqual(3850000L, firstDelta); Assert.AreEqual(10, position); // consumed all 10 chars } @@ -244,7 +244,7 @@ public void TryReadValue_With_Multiple_Values_Reads_Sequentially() { public void TryReadValue_With_Buffer_Ends_Mid_Value_Returns_False() { // Arrange - truncate a multi-char encoding ReadOnlyMemory buffer = "_p~".AsMemory(); // incomplete multi-char encoding - int delta = 0; + long delta = 0; int position = 0; // Act @@ -261,7 +261,7 @@ public void TryReadValue_With_Buffer_Ends_Mid_Value_Returns_False() { public void TryReadValue_Starting_From_Middle_Reads_Correctly() { // Arrange - "_p~iF~ps|U": start at position 5 to read the longitude value ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; + long delta = 0; int position = 5; // Act @@ -269,7 +269,7 @@ public void TryReadValue_Starting_From_Middle_Reads_Correctly() { // Assert Assert.IsTrue(result); - Assert.AreEqual(-12020000, delta); + Assert.AreEqual(-12020000L, delta); Assert.AreEqual(10, position); } @@ -491,7 +491,7 @@ public void TryWriteValue_With_Large_Negative_Value_Writes_Correctly() { [DataRow(16, 2)] [DataRow(3778903, 5)] [DataRow(-12241230, 5)] - public void GetRequiredBufferSize_Returns_Expected_Size(int delta, int expectedSize) { + public void GetRequiredBufferSize_Returns_Expected_Size(long delta, int expectedSize) { // Act int size = PolylineEncoding.GetRequiredBufferSize(delta); @@ -505,24 +505,24 @@ public void GetRequiredBufferSize_Returns_Expected_Size(int delta, int expectedS [TestMethod] public void GetRequiredBufferSize_With_Max_Int_Returns_Correct_Size() { // Act - int size = PolylineEncoding.GetRequiredBufferSize(int.MaxValue); + int size = PolylineEncoding.GetRequiredBufferSize(long.MaxValue); // Assert Assert.IsGreaterThan(0, size); - Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 + Assert.IsLessThanOrEqualTo(13, size); // Maximum size for int64 } /// - /// Tests that GetRequiredBufferSize returns a valid size for the minimum negative integer. + /// Tests that GetRequiredBufferSize returns a valid size for the minimum negative long. /// [TestMethod] public void GetRequiredBufferSize_With_Min_Int_Returns_Correct_Size() { // Act - int size = PolylineEncoding.GetRequiredBufferSize(int.MinValue); + int size = PolylineEncoding.GetRequiredBufferSize(long.MinValue); // Assert Assert.IsGreaterThan(0, size); - Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 + Assert.IsLessThanOrEqualTo(13, size); // Maximum size for int64 } /// @@ -531,7 +531,7 @@ public void GetRequiredBufferSize_With_Min_Int_Returns_Correct_Size() { [TestMethod] public void GetRequiredBufferSize_Consistent_With_TryWriteValue_Matches_Actual_Size() { // Arrange - const int delta = 3778903; + const long delta = 3778903L; int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); Span buffer = stackalloc char[expectedSize]; int position = 0; @@ -550,7 +550,7 @@ public void GetRequiredBufferSize_Consistent_With_TryWriteValue_Matches_Actual_S [TestMethod] public void GetRequiredBufferSize_With_Undersized_Buffer_Causes_TryWriteValue_To_Fail() { // Arrange - const int delta = 3778903; + const long delta = 3778903L; int requiredSize = PolylineEncoding.GetRequiredBufferSize(delta); Span buffer = stackalloc char[requiredSize - 1]; // One char too small int position = 0; @@ -609,7 +609,7 @@ public void ValidateFormat_With_Single_Terminator_Does_Not_Throw() { [TestMethod] public void ValidateFormat_With_Block_Too_Long_Throws_InvalidPolylineException() { // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("________?")); // 8-char block (max is 7) + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("_____________?")); // 13 continuation chars + terminator = block length 14, exceeds max of 13 } #endregion @@ -655,7 +655,8 @@ public void ValidateCharRange_With_Invalid_Character_Throws_InvalidPolylineExcep [TestMethod] [DataRow("?")] // single terminator [DataRow("_p~iF~ps|U")] // multiple blocks - [DataRow("______?")] // 6 continuation chars + terminator (maximum block length) + [DataRow("______?")] // 6 continuation chars + terminator (block length 7) + [DataRow("____________?")] // 12 continuation chars + terminator (maximum block length 13) [DataRow("??")] // consecutive terminators [DataRow("?__?_____?")] // mixed block lengths public void ValidateBlockLength_With_Valid_Polyline_Does_Not_Throw(string polyline) { @@ -667,11 +668,11 @@ public void ValidateBlockLength_With_Valid_Polyline_Does_Not_Throw(string polyli /// Tests that ValidateBlockLength throws for invalid block structures. /// [TestMethod] - [DataRow("________?")] // 8-char block (exceeds max of 7) + [DataRow("_____________?")] // 13 continuation chars + terminator = block length 14, exceeds max of 13 [DataRow("________")] // all continuation chars, no terminator [DataRow("")] // empty polyline has no terminator - [DataRow("?________?")] // valid block then too-long block - [DataRow("________?_?")] // first block too long + [DataRow("?_____________?")] // valid block then too-long block + [DataRow("_____________?_?")] // first block too long public void ValidateBlockLength_With_Invalid_Polyline_Throws_InvalidPolylineException(string polyline) { // Act & Assert Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); diff --git a/tests/PolylineAlgorithm.Tests/PolylineFormatterTests.cs b/tests/PolylineAlgorithm.Tests/PolylineFormatterTests.cs new file mode 100644 index 00000000..3f5f8650 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineFormatterTests.cs @@ -0,0 +1,508 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Tests; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using PolylineAlgorithm; +using System; + +/// +/// Tests for and +/// . +/// +[TestClass] +public sealed class PolylineFormatterTests { + // Shared ForPolyline delegates used to satisfy Build() throughout these tests. + private static readonly Func, string> _write = m => new string(m.Span); + private static readonly Func> _read = s => s.AsMemory(); + // --------------------------------------------------------------------------- + // FormatterBuilder.Create + // --------------------------------------------------------------------------- + + [TestMethod] + public void Create_Returns_New_Builder() { + // Act + FormatterBuilder<(double X, double Y), string> result = FormatterBuilder<(double X, double Y), string>.Create(); + + // Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void Create_With_Multiple_Invocations_Returns_Different_Instances() { + // Act + FormatterBuilder<(double X, double Y), string> first = FormatterBuilder<(double X, double Y), string>.Create(); + FormatterBuilder<(double X, double Y), string> second = FormatterBuilder<(double X, double Y), string>.Create(); + + // Assert + Assert.AreNotSame(first, second); + } + + // --------------------------------------------------------------------------- + // FormatterBuilder.AddValue — argument validation + // --------------------------------------------------------------------------- + + [TestMethod] + public void AddValue_With_Null_Name_Throws_ArgumentNullException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => builder.AddValue(null!, static t => t.X)); + Assert.AreEqual("name", ex.ParamName); + } + + [TestMethod] + public void AddValue_With_Empty_Name_Throws_ArgumentException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act & Assert + ArgumentException ex = Assert.ThrowsExactly( + () => builder.AddValue(string.Empty, static t => t.X)); + Assert.AreEqual("name", ex.ParamName); + } + + [TestMethod] + public void AddValue_With_Null_Selector_Throws_ArgumentNullException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => builder.AddValue("X", null!)); + Assert.AreEqual("selector", ex.ParamName); + } + + [TestMethod] + public void AddValue_With_Duplicate_Name_Throws_ArgumentException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + builder.AddValue("X", static t => t.X); + + // Act & Assert + ArgumentException ex = Assert.ThrowsExactly( + () => builder.AddValue("X", static t => t.Y)); + Assert.AreEqual("name", ex.ParamName); + } + + // --------------------------------------------------------------------------- + // FormatterBuilder.AddValue — happy path & chaining + // --------------------------------------------------------------------------- + + [TestMethod] + public void AddValue_Returns_Same_Builder_For_Method_Chaining() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act + FormatterBuilder<(double X, double Y), string> result = builder.AddValue("X", static t => t.X); + + // Assert + Assert.AreSame(builder, result); + } + + [TestMethod] + public void AddValue_With_Different_Names_Succeeds() { + // Arrange & Act + PolylineFormatter<(double X, double Y), string> formatter = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X) + .AddValue("Y", static t => t.Y) + .WithReaderWriter(_write, _read) + .Build(); + + // Assert + Assert.AreEqual(2, formatter.Width); + } + + // --------------------------------------------------------------------------- + // FormatterBuilder.SetBaseline — argument validation + // --------------------------------------------------------------------------- + + [TestMethod] + public void SetBaseline_With_No_Rules_Throws_InvalidOperationException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act & Assert + Assert.ThrowsExactly(() => builder.SetBaseline(1000L)); + } + + // --------------------------------------------------------------------------- + // FormatterBuilder.SetBaseline — happy path + // --------------------------------------------------------------------------- + + [TestMethod] + public void SetBaseline_Returns_Same_Builder_For_Method_Chaining() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X); + + // Act + FormatterBuilder<(double X, double Y), string> result = builder.SetBaseline(100L); + + // Assert + Assert.AreSame(builder, result); + } + + [TestMethod] + public void SetBaseline_Applies_Only_To_Last_Added_Rule() { + // Arrange & Act + PolylineFormatter<(double X, double Y), string> formatter = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X) + .AddValue("Y", static t => t.Y) + .SetBaseline(500L) + .WithReaderWriter(_write, _read) + .Build(); + + // Assert — only Y (index 1) has a baseline; X (index 0) returns 0 + Assert.AreEqual(0L, formatter.GetBaseline(0)); + Assert.AreEqual(500L, formatter.GetBaseline(1)); + } + + [TestMethod] + public void SetBaseline_Can_Be_Called_On_Each_Rule() { + // Arrange & Act + PolylineFormatter<(double X, double Y), string> formatter = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X).SetBaseline(100L) + .AddValue("Y", static t => t.Y).SetBaseline(200L) + .WithReaderWriter(_write, _read) + .Build(); + + // Assert + Assert.AreEqual(100L, formatter.GetBaseline(0)); + Assert.AreEqual(200L, formatter.GetBaseline(1)); + } + + // --------------------------------------------------------------------------- + // FormatterBuilder.Build — validation + // --------------------------------------------------------------------------- + + [TestMethod] + public void Build_With_No_Rules_Throws_InvalidOperationException() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create(); + + // Act & Assert + Assert.ThrowsExactly(() => builder.Build()); + } + + [TestMethod] + public void Build_With_Multiple_Invocations_Returns_Different_Instances() { + // Arrange + FormatterBuilder<(double X, double Y), string> builder = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X) + .WithReaderWriter(_write, _read); + + // Act + PolylineFormatter<(double X, double Y), string> first = builder.Build(); + PolylineFormatter<(double X, double Y), string> second = builder.Build(); + + // Assert + Assert.AreNotSame(first, second); + } + + // --------------------------------------------------------------------------- + // PolylineFormatter.Width + // --------------------------------------------------------------------------- + + [TestMethod] + public void Width_Equals_Number_Of_Added_Rules() { + // Arrange & Act + PolylineFormatter<(double X, double Y, double Z), string> formatter = FormatterBuilder<(double X, double Y, double Z), string>.Create() + .AddValue("X", static t => t.X) + .AddValue("Y", static t => t.Y) + .AddValue("Z", static t => t.Z) + .WithReaderWriter(_write, _read) + .Build(); + + // Assert + Assert.AreEqual(3, formatter.Width); + } + + [TestMethod] + public void Width_Is_One_For_Single_Rule() { + // Arrange & Act + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Assert + Assert.AreEqual(1, formatter.Width); + } + + // --------------------------------------------------------------------------- + // PolylineFormatter.GetBaseline + // --------------------------------------------------------------------------- + + [TestMethod] + public void GetBaseline_Returns_Zero_When_No_Baseline_Configured() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + long result = formatter.GetBaseline(0); + + // Assert + Assert.AreEqual(0L, result); + } + + [TestMethod] + public void GetBaseline_Returns_Configured_Baseline() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .SetBaseline(42L) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + long result = formatter.GetBaseline(0); + + // Assert + Assert.AreEqual(42L, result); + } + + [TestMethod] + public void GetBaseline_Returns_Negative_Baseline() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .SetBaseline(-1000L) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + long result = formatter.GetBaseline(0); + + // Assert + Assert.AreEqual(-1000L, result); + } + + // --------------------------------------------------------------------------- + // PolylineFormatter.GetValues + // --------------------------------------------------------------------------- + + [TestMethod] + public void GetValues_Scales_Single_Column_By_Factor() { + // Arrange — precision 5 → factor = 100000; use a value exact in double arithmetic + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v, precision: 5) + .WithReaderWriter(_write, _read) + .Build(); + + Span output = stackalloc long[1]; + + // Act — 38.5 * 100000 = 3850000 (exactly representable) + formatter.GetValues(38.5, output); + + // Assert + Assert.AreEqual(3850000L, output[0]); + } + + [TestMethod] + public void GetValues_Scales_Multiple_Columns_Independently() { + // Arrange + PolylineFormatter<(double Lat, double Lon), string> formatter = + FormatterBuilder<(double Lat, double Lon), string>.Create() + .AddValue("Lat", static t => t.Lat, precision: 5) + .AddValue("Lon", static t => t.Lon, precision: 5) + .WithReaderWriter(_write, _read) + .Build(); + + Span output = stackalloc long[2]; + + // Act — 38.5 * 100000 = 3850000; -120.25 * 100000 = -12025000 (both exact in double) + formatter.GetValues((38.5, -120.25), output); + + // Assert + Assert.AreEqual(3850000L, output[0]); + Assert.AreEqual(-12025000L, output[1]); + } + + [TestMethod] + public void GetValues_With_Wrong_Buffer_Length_Throws_ArgumentException() { + // Arrange — formatter has Width = 2 but buffer has length 1 + PolylineFormatter<(double X, double Y), string> formatter = FormatterBuilder<(double X, double Y), string>.Create() + .AddValue("X", static t => t.X) + .AddValue("Y", static t => t.Y) + .WithReaderWriter(_write, _read) + .Build(); + + long[] tooShort = new long[1]; + + // Act & Assert + ArgumentException ex = Assert.ThrowsExactly( + () => formatter.GetValues((1.0, 2.0), tooShort.AsSpan())); + Assert.AreEqual("values", ex.ParamName); + } + + [TestMethod] + public void GetValues_With_Oversized_Buffer_Throws_ArgumentException() { + // Arrange — formatter has Width = 1 but buffer has length 3 + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + long[] tooLong = new long[3]; + + // Act & Assert + ArgumentException ex = Assert.ThrowsExactly( + () => formatter.GetValues(1.0, tooLong.AsSpan())); + Assert.AreEqual("values", ex.ParamName); + } + + [TestMethod] + public void GetValues_With_Zero_Returns_Zero() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v, precision: 5) + .WithReaderWriter(_write, _read) + .Build(); + + Span output = stackalloc long[1]; + + // Act + formatter.GetValues(0.0, output); + + // Assert + Assert.AreEqual(0L, output[0]); + } + + [TestMethod] + public void GetValues_With_Negative_Value_Returns_Negative_Scaled_Long() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v, precision: 5) + .WithReaderWriter(_write, _read) + .Build(); + + Span output = stackalloc long[1]; + + // Act + formatter.GetValues(-90.0, output); + + // Assert + Assert.AreEqual(-9000000L, output[0]); + } + + [TestMethod] + public void GetValues_With_Custom_Precision_Scales_Correctly() { + // Arrange — precision 3 → factor = 1000 + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v, precision: 3) + .WithReaderWriter(_write, _read) + .Build(); + + Span output = stackalloc long[1]; + + // Act + formatter.GetValues(1.5, output); + + // Assert — 1.5 * 1000 = 1500 + Assert.AreEqual(1500L, output[0]); + } + + // --------------------------------------------------------------------------- + // PolylineOptions constructor validation + // --------------------------------------------------------------------------- + + [TestMethod] + public void PolylineOptions_With_Null_Formatter_Throws_ArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => _ = new PolylineOptions(null!)); + Assert.AreEqual("formatter", ex.ParamName); + } + + // --------------------------------------------------------------------------- + // PolylineOptions properties + // --------------------------------------------------------------------------- + + [TestMethod] + public void PolylineOptions_Stores_Formatter() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + PolylineOptions options = new(formatter); + + // Assert + Assert.AreSame(formatter, options.Formatter); + } + + [TestMethod] + public void PolylineOptions_Default_StackAllocLimit_Is_512() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + PolylineOptions options = new(formatter); + + // Assert + Assert.AreEqual(512, options.StackAllocLimit); + } + + [TestMethod] + public void PolylineOptions_Stores_Custom_StackAllocLimit() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + PolylineOptions options = new(formatter, stackAllocLimit: 1024); + + // Assert + Assert.AreEqual(1024, options.StackAllocLimit); + } + + [TestMethod] + public void PolylineOptions_Default_LoggerFactory_Is_NullLoggerFactory() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + // Act + PolylineOptions options = new(formatter); + + // Assert + Assert.IsInstanceOfType(options.LoggerFactory); + } + + [TestMethod] + public void PolylineOptions_Stores_Custom_LoggerFactory() { + // Arrange + PolylineFormatter formatter = FormatterBuilder.Create() + .AddValue("Value", static v => v) + .WithReaderWriter(_write, _read) + .Build(); + + using ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); + + // Act + PolylineOptions options = new(formatter, loggerFactory: loggerFactory); + + // Assert + Assert.AreSame(loggerFactory, options.LoggerFactory); + } +} diff --git a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs index d3d83b9e..a5ab9f71 100644 --- a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs @@ -5,7 +5,6 @@ namespace PolylineAlgorithm.Utility; -using PolylineAlgorithm.Abstraction; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -20,7 +19,19 @@ namespace PolylineAlgorithm.Utility; internal static class RandomValueProvider { private static readonly Random _random = new(DateTime.Now.Millisecond); private static readonly ConcurrentDictionary _cache = new(); - private static readonly PolylineEncoder _encoder = new(); + private static readonly PolylineEncoder<(double Latitude, double Longitude), string> _encoder = CreateEncoder(); + + private static PolylineEncoder<(double Latitude, double Longitude), string> CreateEncoder() { + PolylineFormatter<(double Latitude, double Longitude), string> formatter = + FormatterBuilder<(double Latitude, double Longitude), string>.Create() + .AddValue("lat", static c => c.Latitude) + .AddValue("lon", static c => c.Longitude) + .WithReaderWriter(static m => new string(m.Span), static s => s.AsMemory()) + .Build(); + + return new PolylineEncoder<(double Latitude, double Longitude), string>( + new PolylineOptions<(double Latitude, double Longitude), string>(formatter)); + } /// /// Gets a collection of random latitude/longitude tuples of the specified count. @@ -103,18 +114,4 @@ private readonly struct PolylineCoordinateCollectionPair(IEnumerable<(double Lat public string Polyline { get; } = polyline; } - private sealed class PolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - - protected override string CreatePolyline(ReadOnlyMemory polyline) { - return polyline.ToString(); - } - - protected override double GetLatitude((double Latitude, double Longitude) current) { - return current.Latitude; - } - - protected override double GetLongitude((double Latitude, double Longitude) current) { - return current.Longitude; - } - } } \ No newline at end of file