Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ee09102
feat: add PolylineFormatter, FormatterBuilder, PolylineOptions and Fo…
Copilot Apr 7, 2026
2ebebaf
fix: add GetValues buffer-length validation and bounds documentation
Copilot Apr 7, 2026
7ea10cf
Add formatter interfaces, PolylineValueFormatter, PolylineFormatter s…
Copilot Apr 7, 2026
3482dac
Fix tests: rename PolylineFormatter<T> → PolylineValueFormatter<T>, u…
Copilot Apr 7, 2026
794cbf7
Fix baseline not applied in EncodeWithFormatter, long→int delta arith…
Copilot Apr 8, 2026
a377784
Fix remaining wrong PolylineOptions type args in NotSupportedExceptio…
Copilot Apr 8, 2026
9709806
Updated docs for version 0.0
Copilot Apr 8, 2026
6e3a7c6
feat: replace Abstract encoder/decoder with concrete PolylineEncoder/…
Copilot Apr 8, 2026
8f35f95
fix: suppress CA1062/CA1000/RS0016/RS0017/CS1574 warnings; begin test…
Copilot Apr 8, 2026
3b971cc
fix: update all callers of AbstractPolylineEncoder/Decoder and Polyli…
Copilot Apr 8, 2026
b4e6d43
fix: WithCreate receives denormalized doubles; replace sample wrapper…
Copilot Apr 8, 2026
2b4ca88
chore: remove api-reference/0.0 folder and clear PublicAPI.Unshipped.txt
Copilot Apr 8, 2026
c375141
change int to long for accumulated, Normalize, Denormalize, TryReadVa…
Copilot Apr 8, 2026
c1f5efd
fix confusing block-length test comments
Copilot Apr 8, 2026
56f5733
docs: remove misleading 'epoch' terminology from SetBaseline/GetBasel…
Copilot Apr 8, 2026
bf07a29
docs: update README to use new FormatterBuilder/PolylineFormatter API
Copilot Apr 9, 2026
7b34b08
feat: update PublicAPI.Unshipped.txt and rewrite both READMEs for new…
Copilot Apr 9, 2026
446f8c3
style: use static lambdas in test helper setup for encoder/decoder
Copilot Apr 9, 2026
7aa34a9
feat: add PolylineEncodingOptions/PolylineDecodingOptions with IChunk…
Copilot Apr 9, 2026
7499169
docs: clarify HasPrevious documentation in PolylineEncodingOptions
Copilot Apr 9, 2026
b895ea5
updates
petesramek Apr 13, 2026
d9e2472
Updated docs for version 0.0
petesramek Apr 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 68 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TCoordinate, TPolyline>` — configure coordinate fields, factories, and polyline I/O in one chain
- Sealed, immutable `PolylineFormatter<TCoordinate, TPolyline>` produced by the builder
- Type-safe `PolylineEncoder<TCoordinate, TPolyline>` and `PolylineDecoder<TPolyline, TCoordinate>` with no inheritance required
- `PolylineOptions<TCoordinate, TPolyline>` for stack-alloc limits and optional logging
- Extension methods for encoding directly from `List<T>` 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
Expand All @@ -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<string, (double Lat, double Lon)> 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<T>
// 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<TCoordinate, TPolyline>` configures how the library reads and writes your types:

Custom encoder implementation.
| Method | Purpose |
|---|---|
| `FormatterBuilder<TC,TP>.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>`: `TC(ReadOnlySpan<double> values)` — required for decoding |
| `.ForPolyline(write, read)` | How to convert `ReadOnlyMemory<char>` → `TP` and `TP` → `ReadOnlyMemory<char>` |
| `.Build()` | Returns an immutable `PolylineFormatter<TC,TP>` |

```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<char> 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<T>

Console.WriteLine(encoded);
Console.WriteLine(encoded); // yseiHoc_MwacOjnwM
```

#### Decoding

Custom decoder implementation.
### Decoding

```csharp
using PolylineAlgorithm;
using PolylineAlgorithm.Abstraction;

public sealed class MyPolylineDecoder : AbstractPolylineDecoder<string, (double Latitude, double Longitude)> {
protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude);
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in string polyline) => polyline.AsMemory();
}
PolylineOptions<(double Lat, double Lon), string> options = new(formatter);
PolylineDecoder<string, (double Lat, double Lon)> 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<string, (double Lat, double Lon)>(options);
```

> **Note:**
Expand Down Expand Up @@ -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<TC,TP>` 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.
Expand All @@ -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.
Expand Down
Loading
Loading