From 75f423ec66fe2385f09f08db8bbbfa39c9fe7430 Mon Sep 17 00:00:00 2001 From: Pete Sramek <2333452+petesramek@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:06:40 +0200 Subject: [PATCH 1/2] Complete rewrite and v1.0 release preparation (#146) ## Complete rewrite and v1.0 release preparation This PR represents a full rewrite of the PolylineAlgorithm library, modernizing the API surface, improving extensibility, and preparing for a stable v1.0 release targeting .NET Standard 2.1+. --- ### Breaking Changes / New Public API - **New generic interfaces** `IPolylineEncoder` and `IPolylineDecoder` replace the old encoding abstraction, allowing callers to use any coordinate and polyline representation. - **Abstract base classes** `AbstractPolylineEncoder` and `AbstractPolylineDecoder` provide the shared encoding/decoding algorithm; consumers override only `GetLatitude`, `GetLongitude`, `CreatePolyline` (encoder) or `CreateCoordinate`, `GetReadOnlyMemory` (decoder). - **Extension methods** `PolylineEncoderExtensions` and `PolylineDecoderExtensions` add convenience overloads for `List` and arrays. - **`PolylineEncoding`** static utility class exposes low-level normalization, validation, encoding and decoding primitives. - **`PolylineEncodingOptions`** and **`PolylineEncodingOptionsBuilder`** expose configurable precision, stack-alloc buffer limit and `ILoggerFactory` integration. - **`InvalidPolylineException`** added for descriptive error reporting on malformed input. - All `Encode`/`Decode` methods accept an optional `CancellationToken`. ### Other Changes - Integrated `Microsoft.Extensions.Logging` support for diagnostics / CI audit trails. - Added `PolylineAlgorithm.NetTopologySuite.Sample` demonstrating integration with NetTopologySuite. - Comprehensive XML doc comments on all public APIs. - Updated README, CONTRIBUTING, AGENTS and all guide docs under `api-reference/guide/` (getting-started, advanced-scenarios, configuration, FAQ). - DocFX API reference updated for v1.0 under `api-reference/1.0/`. - Updated unit tests and benchmarks. --------- Signed-off-by: dependabot[bot] Co-authored-by: Pete Sramek Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .editorconfig | 6 +- .../documentation/docfx-build/action.yml | 6 +- .../documentation/docfx-metadata/action.yml | 4 +- .github/actions/git/push-changes/action.yml | 4 +- .../github/branch-protection/lock/action.yml | 37 + .../branch-protection/unlock/action.yml | 18 + .../actions/github/create-release/action.yml | 8 +- .../github/write-file-to-summary/action.yml | 4 +- .../actions/nuget/publish-package/action.yml | 4 +- .github/actions/source/compile/action.yml | 4 +- .github/actions/source/format/action.yml | 2 +- .../actions/testing/code-coverage/action.yml | 2 +- .../actions/testing/test-report/action.yml | 2 +- .../versioning/extract-version/action.yml | 17 +- .../versioning/format-version/action.yml | 2 +- .github/workflows/build.yml | 18 +- .github/workflows/bump-version.yml | 157 ++++ .github/workflows/promote-branch.yml | 24 +- .github/workflows/publish-documentation.yml | 109 ++- .github/workflows/pull-request.yml | 22 +- .github/workflows/release.yml | 127 +++- .gitignore | 8 +- AGENTS.md | 93 +++ CONTRIBUTING.md | 50 ++ Directory.Build.props | 25 + PolylineAlgorithm.slnx | 3 + README.md | 193 ++++- ....Abstraction.AbstractPolylineDecoder-2.yml | 115 ++- ....Abstraction.AbstractPolylineEncoder-2.yml | 71 +- ...gorithm.Abstraction.IPolylineDecoder-2.yml | 71 +- ...gorithm.Abstraction.IPolylineEncoder-2.yml | 131 +++- .../1.0/PolylineAlgorithm.Abstraction.yml | 21 +- .../1.0/PolylineAlgorithm.Coordinate.yml | 250 ------- .../PolylineAlgorithm.CoordinateValueType.yml | 45 -- ...m.Extensions.PolylineDecoderExtensions.yml | 129 ++-- ...m.Extensions.PolylineEncoderExtensions.yml | 87 ++- ...hm.Internal.Diagnostics.ExceptionGuard.yml | 313 ++++++++ ...PolylineAlgorithm.Internal.Diagnostics.yml | 15 + ...lineAlgorithm.InvalidPolylineException.yml | 36 +- .../1.0/PolylineAlgorithm.Polyline.yml | 315 -------- .../1.0/PolylineAlgorithm.PolylineDecoder.yml | 149 ---- .../1.0/PolylineAlgorithm.PolylineEncoder.yml | 161 ----- .../PolylineAlgorithm.PolylineEncoding.yml | 453 +++++++++--- ...ylineAlgorithm.PolylineEncodingOptions.yml | 83 ++- ...gorithm.PolylineEncodingOptionsBuilder.yml | 65 +- api-reference/1.0/PolylineAlgorithm.yml | 35 +- api-reference/1.0/toc.yml | 22 +- api-reference/api-reference.json | 24 +- .../docs-versioning/layout/_master.tmpl | 163 +++++ .../public/version-switcher.js | 86 +++ api-reference/guide/advanced-scenarios.md | 168 +++++ api-reference/guide/configuration.md | 75 ++ api-reference/guide/faq.md | 63 ++ api-reference/guide/getting-started.md | 77 +- api-reference/guide/introduction.md | 34 +- api-reference/guide/sample.md | 115 +++ api-reference/guide/toc.yml | 12 +- api-reference/index.md | 34 +- api-reference/toc.yml | 7 +- api-reference/versions.json | 4 + .../PolylineAlgorithm.Benchmarks.csproj | 14 +- .../PolylineBenchmark.cs | 165 ----- .../PolylineDecoderBenchmark.cs | 100 ++- .../PolylineEncoderBenchmark.cs | 75 +- .../PolylineEncodingBenchmark.cs | 38 + .../PolylineAlgorithm.Benchmarks/Program.cs | 8 +- .../Properties/CodeCoverage.cs | 8 + .../Properties/GlobalSuppressions.cs | 15 +- docs/README.md | 23 + docs/api-documentation.md | 136 ++++ docs/benchmarks.md | 132 ++++ docs/branch-strategy.md | 113 +++ docs/composite-actions.md | 307 ++++++++ docs/extensibility.md | 128 ++++ docs/local-development.md | 74 ++ docs/testing.md | 135 ++++ docs/versioning.md | 99 +++ docs/workflows.md | 178 +++++ global.json | 4 +- .../NetTopologyPolylineDecoder.cs | 38 +- .../NetTopologyPolylineEncoder.cs | 48 +- ...neAlgorithm.NetTopologySuite.Sample.csproj | 16 +- .../Properties/CodeCoverage.cs | 8 + .../Abstraction/AbstractPolylineDecoder.cs | 188 +++-- .../Abstraction/AbstractPolylineEncoder.cs | 182 ++--- .../Abstraction/IPolylineDecoder.cs | 39 +- .../Abstraction/IPolylineEncoder.cs | 73 +- src/PolylineAlgorithm/Coordinate.cs | 132 ---- src/PolylineAlgorithm/CoordinateValueType.cs | 24 - .../Extensions/PolylineDecoderExtensions.cs | 64 +- .../Extensions/PolylineEncoderExtensions.cs | 52 +- .../Internal/CoordinateDelta.cs | 72 ++ .../Internal/CoordinateVariance.cs | 80 -- src/PolylineAlgorithm/Internal/Defaults.cs | 55 +- .../Internal/Diagnostics/ExceptionGuard.cs | 325 +++++++++ .../Diagnostics/LogDebugExtensions.cs | 57 ++ .../Diagnostics/LogWarningExtensions.cs | 101 +++ .../Internal/Logging/LogInfoExtensions.cs | 22 - .../Internal/Logging/LogWarningExtensions.cs | 34 - src/PolylineAlgorithm/Internal/Pow10.cs | 40 + .../InvalidPolylineException.cs | 32 +- src/PolylineAlgorithm/Polyline.cs | 214 ------ .../PolylineAlgorithm.csproj | 30 +- src/PolylineAlgorithm/PolylineDecoder.cs | 29 - src/PolylineAlgorithm/PolylineEncoder.cs | 35 - src/PolylineAlgorithm/PolylineEncoding.cs | 495 +++++++++---- .../PolylineEncodingOptions.cs | 67 +- .../PolylineEncodingOptionsBuilder.cs | 65 +- .../ExceptionMessageResource.Designer.cs | 78 +- .../Properties/ExceptionMessageResource.resx | 40 +- .../Properties/GlobalSuppressions.cs | 1 + src/PolylineAlgorithm/PublicAPI.Unshipped.txt | 2 +- src/PolylineAlgorithm/README.md | 105 +++ .../AbstractPolylineDecoderTest.cs | 179 ----- .../AbstractPolylineEncoderTest.cs | 165 ----- .../AbstractPolylineDecoderTests.cs | 144 ++++ .../AbstractPolylineEncoderTests.cs | 152 ++++ .../PolylineAlgorithm.Tests/CoordinateTest.cs | 253 ------- .../PolylineDecoderExtensionsTests.cs | 172 +++++ .../PolylineEncoderExtensionsTests.cs | 123 ++++ .../Fakes/FakeLoggerFactory.cs | 41 -- .../Internal/CoordinateDeltaTests.cs | 136 ++++ .../Internal/CoordinateVarianceTest.cs | 94 --- .../Diagnostics/ExceptionGuardTests.cs | 609 ++++++++++++++++ .../Diagnostics/LogDebugExtensionsTests.cs | 222 ++++++ .../Diagnostics/LogWarningExtensionsTests.cs | 116 +++ .../Internal/LoggingTest.cs | 224 ------ .../Internal/Pow10Tests.cs | 49 ++ .../InvalidPolylineExceptionTest.cs | 31 - .../InvalidPolylineExceptionTests.cs | 44 ++ .../PolylineAlgorithm.Tests.csproj | 17 +- .../PolylineDecoderExtensionsTest.cs | 112 --- .../PolylineDecoderTest.cs | 85 --- .../PolylineEncoderExtensionsTest.cs | 77 -- .../PolylineEncoderTest.cs | 118 --- .../PolylineEncodingOptionsBuilderTests.cs | 425 +++++++++++ .../PolylineEncodingOptionsTest.cs | 42 -- .../PolylineEncodingTest.cs | 265 ------- .../PolylineEncodingTests.cs | 681 ++++++++++++++++++ .../PolylineOptionsBuilderTest.cs | 103 --- tests/PolylineAlgorithm.Tests/PolylineTest.cs | 287 -------- .../Properties/CodeCoverage.cs | 8 + .../Properties/GlobalSuppressions.cs | 4 +- .../Properties/MSTestSettings.cs | 4 +- .../PolylineAlgorithm.Utility.csproj | 11 - .../RandomValueProvider.cs | 23 +- .../StaticValueProvider.cs | 18 +- 147 files changed, 9038 insertions(+), 4969 deletions(-) create mode 100644 .github/actions/github/branch-protection/lock/action.yml create mode 100644 .github/actions/github/branch-protection/unlock/action.yml create mode 100644 .github/workflows/bump-version.yml create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 Directory.Build.props delete mode 100644 api-reference/1.0/PolylineAlgorithm.Coordinate.yml delete mode 100644 api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml create mode 100644 api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml create mode 100644 api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml delete mode 100644 api-reference/1.0/PolylineAlgorithm.Polyline.yml delete mode 100644 api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml delete mode 100644 api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml create mode 100644 api-reference/docs-versioning/layout/_master.tmpl create mode 100644 api-reference/docs-versioning/public/version-switcher.js create mode 100644 api-reference/guide/advanced-scenarios.md create mode 100644 api-reference/guide/configuration.md create mode 100644 api-reference/guide/faq.md create mode 100644 api-reference/guide/sample.md create mode 100644 api-reference/versions.json delete mode 100644 benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs create mode 100644 benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs create mode 100644 benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs create mode 100644 docs/README.md create mode 100644 docs/api-documentation.md create mode 100644 docs/benchmarks.md create mode 100644 docs/branch-strategy.md create mode 100644 docs/composite-actions.md create mode 100644 docs/extensibility.md create mode 100644 docs/local-development.md create mode 100644 docs/testing.md create mode 100644 docs/versioning.md create mode 100644 docs/workflows.md create mode 100644 samples/PolylineAlgorithm.NetTopologySuite.Sample/Properties/CodeCoverage.cs delete mode 100644 src/PolylineAlgorithm/Coordinate.cs delete mode 100644 src/PolylineAlgorithm/CoordinateValueType.cs create mode 100644 src/PolylineAlgorithm/Internal/CoordinateDelta.cs delete mode 100644 src/PolylineAlgorithm/Internal/CoordinateVariance.cs create mode 100644 src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs create mode 100644 src/PolylineAlgorithm/Internal/Diagnostics/LogDebugExtensions.cs create mode 100644 src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs delete mode 100644 src/PolylineAlgorithm/Internal/Logging/LogInfoExtensions.cs delete mode 100644 src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs create mode 100644 src/PolylineAlgorithm/Internal/Pow10.cs delete mode 100644 src/PolylineAlgorithm/Polyline.cs delete mode 100644 src/PolylineAlgorithm/PolylineDecoder.cs delete mode 100644 src/PolylineAlgorithm/PolylineEncoder.cs create mode 100644 src/PolylineAlgorithm/Properties/GlobalSuppressions.cs create mode 100644 src/PolylineAlgorithm/README.md delete mode 100644 tests/PolylineAlgorithm.Tests/AbstractPolylineDecoderTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/AbstractPolylineEncoderTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs create mode 100644 tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/CoordinateTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs create mode 100644 tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/Fakes/FakeLoggerFactory.cs create mode 100644 tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/Internal/CoordinateVarianceTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs create mode 100644 tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs create mode 100644 tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/Internal/LoggingTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineDecoderExtensionsTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncoderExtensionsTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncodingTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineOptionsBuilderTest.cs delete mode 100644 tests/PolylineAlgorithm.Tests/PolylineTest.cs create mode 100644 tests/PolylineAlgorithm.Tests/Properties/CodeCoverage.cs diff --git a/.editorconfig b/.editorconfig index 61536f81..75d9084f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -131,7 +131,7 @@ csharp_prefer_simple_using_statement = false csharp_prefer_system_threading_lock = true csharp_style_namespace_declarations = file_scoped csharp_style_prefer_method_group_conversion = false -csharp_style_prefer_primary_constructors = true +csharp_style_prefer_primary_constructors = false csharp_style_prefer_top_level_statements = false # Expression-level preferences @@ -270,3 +270,7 @@ dotnet_naming_style.underscore_camel_case.required_prefix = _ dotnet_naming_style.underscore_camel_case.required_suffix = dotnet_naming_style.underscore_camel_case.word_separator = dotnet_naming_style.underscore_camel_case.capitalization = camel_case + +# Public API analyzer + +dotnet_public_api_analyzer.require_api_files = true diff --git a/.github/actions/documentation/docfx-build/action.yml b/.github/actions/documentation/docfx-build/action.yml index 2b9ba79f..a24e6e75 100644 --- a/.github/actions/documentation/docfx-build/action.yml +++ b/.github/actions/documentation/docfx-build/action.yml @@ -21,12 +21,10 @@ inputs: runs: using: composite steps: - - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 - name: Dotnet Setup uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.dotnet-sdk-version }} + dotnet-version: ${{ inputs.dotnet_sdk_version }} - name: 'testing variables' shell: bash run: | @@ -41,7 +39,7 @@ runs: run: docfx build ${{ inputs.docfx-json-manifest }} shell: bash - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ inputs.artifact-name }} path: ${{ inputs.output-directory }} diff --git a/.github/actions/documentation/docfx-metadata/action.yml b/.github/actions/documentation/docfx-metadata/action.yml index bcfb67d2..bbcd6383 100644 --- a/.github/actions/documentation/docfx-metadata/action.yml +++ b/.github/actions/documentation/docfx-metadata/action.yml @@ -26,7 +26,7 @@ runs: using: composite steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Dotnet Setup uses: actions/setup-dotnet@v4 with: @@ -59,7 +59,7 @@ runs: mkdir -p ${{ inputs.output-directory }} cp -r ${{ inputs.temporary-directory }}/* ${{ inputs.output-directory }} - name: 'Upload artifact' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ inputs.artifact-name }} path: ${{ inputs.output-directory }} diff --git a/.github/actions/git/push-changes/action.yml b/.github/actions/git/push-changes/action.yml index a5799d2a..31ddd3f2 100644 --- a/.github/actions/git/push-changes/action.yml +++ b/.github/actions/git/push-changes/action.yml @@ -28,7 +28,7 @@ runs: using: "composite" steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Dotnet Setup uses: actions/setup-dotnet@v4 @@ -37,7 +37,7 @@ runs: - name: Download a single artifact if: ${{ inputs.artifact-name != '' }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: ${{ inputs.artifact-name }} path: ${{ inputs.working-directory }} diff --git a/.github/actions/github/branch-protection/lock/action.yml b/.github/actions/github/branch-protection/lock/action.yml new file mode 100644 index 00000000..7a493cce --- /dev/null +++ b/.github/actions/github/branch-protection/lock/action.yml @@ -0,0 +1,37 @@ +name: 'Lock branch' +author: 'Pete Sramek' +description: 'Apply branch protection to prevent direct pushes. Requires PRs with at least one approval.' +inputs: + branch: + description: 'Branch name to lock.' + required: true + +runs: + using: composite + steps: + - name: 'Lock branch ${{ inputs.branch }}' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + if ! gh api --method PUT /repos/${{ github.repository }}/branches/${{ inputs.branch }}/protection \ + --input - << 'EOF' + { + "required_status_checks": null, + "enforce_admins": false, + "required_pull_request_reviews": { + "dismiss_stale_reviews": true, + "require_code_owner_reviews": false, + "required_approving_review_count": 1 + }, + "restrictions": null, + "allow_force_pushes": false, + "allow_deletions": false, + "lock_branch": false + } + EOF + then + echo "::error::Failed to apply branch protection to '${{ inputs.branch }}'. Ensure the token has 'administration: write' permission and the branch exists." + exit 1 + fi + echo "🔒 Branch '${{ inputs.branch }}' is now protected." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/github/branch-protection/unlock/action.yml b/.github/actions/github/branch-protection/unlock/action.yml new file mode 100644 index 00000000..5bd37baa --- /dev/null +++ b/.github/actions/github/branch-protection/unlock/action.yml @@ -0,0 +1,18 @@ +name: 'Unlock branch' +author: 'Pete Sramek' +description: 'Remove branch protection to allow a workflow to push directly. Always re-lock after the operation.' +inputs: + branch: + description: 'Branch name to unlock.' + required: true + +runs: + using: composite + steps: + - name: 'Unlock branch ${{ inputs.branch }}' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api --method DELETE /repos/${{ github.repository }}/branches/${{ inputs.branch }}/protection || true + echo "🔓 Branch protection removed from '${{ inputs.branch }}'." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/github/create-release/action.yml b/.github/actions/github/create-release/action.yml index f714fc5b..0e0015ce 100644 --- a/.github/actions/github/create-release/action.yml +++ b/.github/actions/github/create-release/action.yml @@ -1,4 +1,4 @@ -name: 'Create GitHub release' +name: 'Create GitHub release' author: 'Pete Sramek' description: 'Create GitHub release.' inputs: @@ -17,7 +17,7 @@ runs: using: composite steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - run: | echo "release-version=${{ inputs.release-version }}" echo "is-preview=${{ inputs.is-preview }}" @@ -25,6 +25,10 @@ runs: echo "notes-start-tag=${{ inputs.notes-start-tag }}" echo "notes-start-tag-argument="${{ inputs.notes-start-tag != '' && '--notes-start-tag $(inputs.notes-start-tag)' || '' }}" shell: bash + - name: 'Create git tag ${{ env.release-version }}' + shell: bash + run: | + git tag -a ${{ env.release-version }} -m "${{ env.release-version }}" - name: 'Create GitHub release PolylineAlgorithm ${{ env.release-version }}' shell: bash env: diff --git a/.github/actions/github/write-file-to-summary/action.yml b/.github/actions/github/write-file-to-summary/action.yml index ac682f6d..c185ce4b 100644 --- a/.github/actions/github/write-file-to-summary/action.yml +++ b/.github/actions/github/write-file-to-summary/action.yml @@ -1,4 +1,4 @@ -name: 'Write file to step summary' +name: 'Write file to step summary' author: 'Pete Sramek' description: 'Writes file contents to step summary.' inputs: @@ -17,7 +17,7 @@ runs: using: composite steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Writing ${{ inputs.file }} to step summary shell: bash diff --git a/.github/actions/nuget/publish-package/action.yml b/.github/actions/nuget/publish-package/action.yml index 64b4885a..eec82f65 100644 --- a/.github/actions/nuget/publish-package/action.yml +++ b/.github/actions/nuget/publish-package/action.yml @@ -38,10 +38,10 @@ runs: exit 1 - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Download package artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: ${{ inputs.package-artifact-name }} diff --git a/.github/actions/source/compile/action.yml b/.github/actions/source/compile/action.yml index 2a59e696..0af331ee 100644 --- a/.github/actions/source/compile/action.yml +++ b/.github/actions/source/compile/action.yml @@ -50,7 +50,7 @@ runs: using: "composite" steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 @@ -68,7 +68,7 @@ runs: - name: 'Upload build artifacts' if: ${{ inputs.upload-build-artifacts == 'true' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ inputs.build-artifacts-name }} path: ${{ inputs.build-artifacts-glob-pattern }} diff --git a/.github/actions/source/format/action.yml b/.github/actions/source/format/action.yml index 5ce79d8f..de2536c2 100644 --- a/.github/actions/source/format/action.yml +++ b/.github/actions/source/format/action.yml @@ -32,7 +32,7 @@ runs: using: "composite" steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 diff --git a/.github/actions/testing/code-coverage/action.yml b/.github/actions/testing/code-coverage/action.yml index 1e42d2ca..8751a7b2 100644 --- a/.github/actions/testing/code-coverage/action.yml +++ b/.github/actions/testing/code-coverage/action.yml @@ -24,7 +24,7 @@ runs: using: composite steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 diff --git a/.github/actions/testing/test-report/action.yml b/.github/actions/testing/test-report/action.yml index 6dc55d81..40e66385 100644 --- a/.github/actions/testing/test-report/action.yml +++ b/.github/actions/testing/test-report/action.yml @@ -25,7 +25,7 @@ runs: using: composite steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 diff --git a/.github/actions/versioning/extract-version/action.yml b/.github/actions/versioning/extract-version/action.yml index faad6a35..ba604761 100644 --- a/.github/actions/versioning/extract-version/action.yml +++ b/.github/actions/versioning/extract-version/action.yml @@ -22,13 +22,13 @@ inputs: outputs: version: description: 'Version extracted from the branch name.' - value: ${{ steps.regex-match.outputs.match }} + value: ${{ steps.resolve-version.outputs.version }} runs: using: "composite" steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 @@ -43,14 +43,9 @@ runs: regex: ${{ inputs.version-format }} flags: 'g' - - name: 'Set extracted version output' - if: steps.regex-match.outputs.match != '' + - name: 'Resolve version' + id: resolve-version shell: bash run: | - echo "version=${{ steps.regex-match.outputs.match }}" >> $GITHUB_OUTPUT - - - name: 'Set default version output' - if: steps.regex-match.outputs.match == '' - shell: bash - run: | - echo "version=${{ inputs.default-version }}" >> $GITHUB_OUTPUT + VERSION="${{ steps.regex-match.outputs.match }}" + echo "version=${VERSION:-${{ inputs.default-version }}}" >> $GITHUB_OUTPUT diff --git a/.github/actions/versioning/format-version/action.yml b/.github/actions/versioning/format-version/action.yml index 2b3d11c0..70289ee0 100644 --- a/.github/actions/versioning/format-version/action.yml +++ b/.github/actions/versioning/format-version/action.yml @@ -45,7 +45,7 @@ runs: using: "composite" steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0044504..047d4afd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: release-version: ${{ steps.format-version.outputs.release-version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' uses: actions/setup-dotnet@v5 with: @@ -102,7 +102,7 @@ jobs: steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Format source code' uses: ./.github/actions/source/format @@ -121,7 +121,7 @@ jobs: steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Compile source code' uses: ./.github/actions/source/compile @@ -138,7 +138,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET' uses: actions/setup-dotnet@v5 @@ -184,7 +184,7 @@ jobs: package-artifact-name: ${{ env.package-artifact-name }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -192,7 +192,7 @@ jobs: dotnet-version: ${{ env.dotnet-sdk-version }} - name: Download Build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: build @@ -201,7 +201,7 @@ jobs: dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} - name: Upload Package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.package-artifact-name }} path: | @@ -217,7 +217,7 @@ jobs: environment: 'Development' steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -242,7 +242,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Generate assembly metadata' uses: ./.github/actions/documentation/docfx-metadata with: diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 00000000..5fa8e79f --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,157 @@ +name: 'Bump version' + +on: + workflow_dispatch: + inputs: + bump-type: + type: choice + options: + - 'minor' + - 'major' + description: 'Version bump type. Use ''minor'' to add features (keeps API files), ''major'' for breaking changes (resets API files).' + required: true + +permissions: + actions: read + contents: write + pull-requests: write + administration: write + +concurrency: + group: bump-version + cancel-in-progress: false + +env: + dotnet-sdk-version: '10.x' + +jobs: + detect-version: + name: 'Detect current version and calculate next' + runs-on: ubuntu-latest + outputs: + current-version: ${{ steps.detect.outputs.current-version }} + next-version: ${{ steps.calculate.outputs.next-version }} + target-branch: ${{ steps.calculate.outputs.target-branch }} + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + + - name: 'Detect highest release branch' + id: detect + run: | + git fetch origin + latest=$(git ls-remote --heads origin 'release/*' | grep -oP 'release/\K\d+\.\d+' | sort -V | tail -1) + if [[ -z "$latest" ]]; then + latest="0.0" + fi + echo "Detected current version: $latest" + echo "current-version=$latest" >> $GITHUB_OUTPUT + + - name: 'Calculate next version' + id: calculate + run: | + current="${{ steps.detect.outputs.current-version }}" + major="${current%%.*}" + minor="${current##*.}" + if [[ "${{ inputs.bump-type }}" == "major" ]]; then + next_major=$((major + 1)) + next_version="${next_major}.0" + else + next_minor=$((minor + 1)) + next_version="${major}.${next_minor}" + fi + echo "Next version: $next_version" + echo "next-version=$next_version" >> $GITHUB_OUTPUT + echo "target-branch=develop/${next_version}" >> $GITHUB_OUTPUT + + validate: + name: 'Validate bump' + needs: [detect-version] + runs-on: ubuntu-latest + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 1 + + - name: 'Validate workflow is running from main' + if: ${{ github.ref_name != 'main' }} + run: | + echo "This workflow must be run from the 'main' branch. Current branch: '${{ github.ref_name }}'." + exit 1 + + - name: 'Validate target branch does not exist' + run: | + set +e + git ls-remote --exit-code --heads origin "${{ needs.detect-version.outputs.target-branch }}" + if [[ $? -eq 0 ]]; then + echo "Target branch '${{ needs.detect-version.outputs.target-branch }}' already exists." + exit 1 + fi + set -e + + create-branch: + name: 'Create ${{ needs.detect-version.outputs.target-branch }}' + needs: [detect-version, validate] + runs-on: ubuntu-latest + env: + next-version: ${{ needs.detect-version.outputs.next-version }} + target-branch: ${{ needs.detect-version.outputs.target-branch }} + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Configure git' + run: | + git config user.name "$(git log -n 1 --pretty=format:%an)" + git config user.email "$(git log -n 1 --pretty=format:%ae)" + + - name: 'Create develop branch from main' + run: | + git checkout -b ${{ env.target-branch }} + + - name: 'Reset PublicAPI files for major bump' + if: ${{ inputs.bump-type == 'major' }} + run: | + find . \( -name "PublicAPI.Shipped.txt" -o -name "PublicAPI.Unshipped.txt" \) | while read file; do + printf '\xef\xbb\xbf#nullable enable\n' > "$file" + echo "Reset: $file" + done + + - name: 'Commit API file reset' + if: ${{ inputs.bump-type == 'major' }} + run: | + git add . + git diff --staged --quiet || git commit -m "Reset PublicAPI files for major version ${{ env.next-version }}" + + - name: 'Push develop branch' + run: | + git push --set-upstream origin ${{ env.target-branch }} + + - name: 'Write summary' + run: | + echo "## ✅ Branch created: \`${{ env.target-branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| **Bump type** | ${{ inputs.bump-type }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Previous version** | ${{ needs.detect-version.outputs.current-version }} |" >> $GITHUB_STEP_SUMMARY + echo "| **New version** | ${{ env.next-version }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Target branch** | \`${{ env.target-branch }}\` |" >> $GITHUB_STEP_SUMMARY + if [[ "${{ inputs.bump-type }}" == "major" ]]; then + echo "| **API files** | Reset (major bump) |" >> $GITHUB_STEP_SUMMARY + else + echo "| **API files** | Preserved (minor bump) |" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/promote-branch.yml b/.github/workflows/promote-branch.yml index 21d999f1..645d712c 100644 --- a/.github/workflows/promote-branch.yml +++ b/.github/workflows/promote-branch.yml @@ -21,6 +21,7 @@ permissions: id-token: write contents: write pull-requests: write + administration: write concurrency: group: 'promote-branch-${{ inputs.promotion-type }}-${{github.ref_name }}' @@ -30,6 +31,7 @@ env: dotnet-sdk-version: '10.x' is-development-branch: ${{ startsWith(github.ref_name, 'develop') }} is-maintenance-branch: ${{ startsWith(github.ref_name, 'support') }} + is-preview-branch: ${{ startsWith(github.ref_name, 'preview') }} jobs: versioning: @@ -39,7 +41,7 @@ jobs: friendly-version: ${{ steps.extract-version.outputs.version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' uses: actions/setup-dotnet@v5 with: @@ -153,16 +155,21 @@ jobs: target-branch: ${{ env.target-branch }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Check promotion type' if: ${{ (env.promotion-type != 'release') && (env.promotion-type != 'preview') }} run: | echo "Invalid promotion type ${{ inputs.promotion-type }}" exit 1 - - name: 'Validate current branch' - if: ${{ (env.is-development-branch == 'false') && (env.is-maintenance-branch == 'false') }} + - name: 'Validate source branch for preview promotion' + if: ${{ env.promotion-type == 'preview' && (env.is-development-branch == 'false') && (env.is-maintenance-branch == 'false') }} run: | - echo "Invalid current branch '${{ github.ref_name }}'" + echo "Preview promotion requires a 'develop/**' or 'support/**' source branch. Current branch: '${{ github.ref_name }}'" + exit 1 + - name: 'Validate source branch for release promotion' + if: ${{ env.promotion-type == 'release' && env.is-preview-branch == 'false' }} + run: | + echo "Release promotion requires a 'preview/**' source branch. Current branch: '${{ github.ref_name }}'" exit 1 - name: 'Validate default and current branch' if: ${{ env.base-branch == env.current-branch }} @@ -192,7 +199,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' uses: actions/setup-dotnet@v5 with: @@ -206,6 +213,11 @@ jobs: git switch ${{ needs.workflow-variables.outputs.base-branch }} git checkout -b ${{ needs.workflow-variables.outputs.target-branch }} origin/${{ needs.workflow-variables.outputs.target-branch }} || git checkout -b ${{ needs.workflow-variables.outputs.target-branch }} git push --set-upstream origin ${{ needs.workflow-variables.outputs.target-branch }} + - name: 'Lock target branch' + if: ${{ needs.workflow-variables.outputs.target-branch-exists == 'false' }} + uses: './.github/actions/github/branch-protection/lock' + with: + branch: ${{ needs.workflow-variables.outputs.target-branch }} - name: 'Create PR: "Promote ${{ env.current-branch }} to ${{ env.target-branch }}"' if: ${{ needs.workflow-variables.outputs.pull-request-exists == 'false' }} env: diff --git a/.github/workflows/publish-documentation.yml b/.github/workflows/publish-documentation.yml index 7f9af8e3..2a579943 100644 --- a/.github/workflows/publish-documentation.yml +++ b/.github/workflows/publish-documentation.yml @@ -1,7 +1,13 @@ -name: 'Publish documentation' +name: 'Publish documentation' on: workflow_dispatch: + push: + branches: + - 'release/**' + paths: + - 'src/**' + - 'api-reference/**' permissions: actions: read @@ -12,9 +18,24 @@ concurrency: group: publish-docs-${{ github.head_ref || github.ref }} cancel-in-progress: true +env: + dotnet-sdk-version: '10.x' + jobs: + validate-branch: + name: 'Validate branch' + runs-on: ubuntu-latest + steps: + - name: 'Ensure documentation is published from a release branch' + if: ${{ !startsWith(github.ref_name, 'release/') }} + run: | + echo "Documentation should only be published from 'release/**' branches." + echo "Current branch: '${{ github.ref_name }}'" + exit 1 + workflow-variables: name: 'Workflow variables' + needs: [validate-branch] runs-on: ubuntu-latest outputs: @@ -31,11 +52,11 @@ jobs: needs: [workflow-variables] runs-on: ubuntu-latest outputs: - friendly-version: ${{ steps.versioning.outputs.friendly-version }} + friendly-version: ${{ steps.versioning.outputs.version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # Ensure the full git history is available for versioning - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' @@ -56,14 +77,82 @@ jobs: friendly-version: ${{ needs.versioning.outputs.friendly-version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - - name: 'Generate documentation' - uses: ./.github/actions/documentation/docfx-build + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 with: - artifact-name: 'documentation' - docfx-json-manifest: './api-reference/api-reference.json' - output-directory: './api-reference/_docs' + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Install docfx' + shell: bash + run: dotnet tool update -g docfx + + - name: 'Regenerate API metadata for v${{ env.friendly-version }}' + shell: bash + run: | + rm -rf api-reference/${{ env.friendly-version }} + cd api-reference + docfx metadata assembly-metadata.json + mv temp ${{ env.friendly-version }} + + - name: 'Discover all versions' + id: discover-versions + shell: bash + run: | + versions=$(ls api-reference/ | grep -E '^\d+\.\d+$' | sort -V | paste -sd ',' -) + latest=$(ls api-reference/ | grep -E '^\d+\.\d+$' | sort -V | tail -1) + echo "versions=$versions" >> $GITHUB_OUTPUT + echo "latest=$latest" >> $GITHUB_OUTPUT + + - name: 'Update versions.json' + shell: bash + run: | + versions_array=$(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' '\n' | jq -R . | jq -s .) + jq --arg latest "${{ steps.discover-versions.outputs.latest }}" \ + --argjson versions "$versions_array" \ + '.latest = $latest | .versions = $versions' \ + api-reference/versions.json > /tmp/versions.json + mv /tmp/versions.json api-reference/versions.json + + - name: 'Update api-reference.json with version groups' + shell: bash + run: | + cp api-reference/api-reference.json /tmp/api-reference.json + for ver in $(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' ' '); do + jq --arg ver "$ver" --arg group "v${ver}" \ + '.build.content += [{"dest": "", "files": ["*.yml"], "group": $group, "src": $ver, "rootTocPath": "~/toc.html"}] | + .build.groups = (.build.groups // {}) | + .build.groups[$group] = {"dest": $ver}' \ + /tmp/api-reference.json > /tmp/api-reference-new.json + mv /tmp/api-reference-new.json /tmp/api-reference.json + done + mv /tmp/api-reference.json api-reference/api-reference.json + + - name: 'Update toc.yml with Reference dropdown' + shell: bash + run: | + latest="${{ steps.discover-versions.outputs.latest }}" + { + echo "### YamlMime:TableOfContent" + echo "- name: Guide" + echo " href: guide/" + echo "- name: Reference" + echo " dropdown: true" + echo " items:" + for ver in $(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' '\n' | sort -Vr); do + if [ "$ver" = "$latest" ]; then + echo " - name: v${ver} (latest)" + else + echo " - name: v${ver}" + fi + echo " href: ${ver}/PolylineAlgorithm.html" + done + } > api-reference/toc.yml + + - name: 'Build documentation' + shell: bash + run: docfx build api-reference/api-reference.json - name: 'Upload artifact' uses: actions/upload-pages-artifact@v4 @@ -82,4 +171,4 @@ jobs: - name: 'Deploy to GitHub Pages' id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 85c21862..8ea9622c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -55,7 +55,7 @@ jobs: release-version: ${{ steps.format-version.outputs.release-version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' uses: actions/setup-dotnet@v5 with: @@ -109,7 +109,7 @@ jobs: steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Compile source code' uses: ./.github/actions/source/compile @@ -126,7 +126,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET' uses: actions/setup-dotnet@v5 @@ -136,7 +136,7 @@ jobs: - name: 'Run tests' uses: ./.github/actions/testing/test with: - project-path: '**/PolylineAlgorithm.Tests.csproj' + project-path: './tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj' test-results-directory: '${{ runner.temp }}/${{ env.test-result-directory }}/' code-coverage-settings-file: '${{ github.workspace}}/code-coverage-settings.xml' @@ -172,7 +172,7 @@ jobs: package-artifact-name: ${{ env.package-artifact-name }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -180,7 +180,7 @@ jobs: dotnet-version: ${{ env.dotnet-sdk-version }} - name: Download Build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: build @@ -189,7 +189,7 @@ jobs: dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} - name: Upload Package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.package-artifact-name }} path: | @@ -205,7 +205,7 @@ jobs: environment: 'Development' steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -232,7 +232,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install .NET SDK uses: actions/setup-dotnet@v5 with: @@ -240,14 +240,14 @@ jobs: 8.x 10.x - name: Download Build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: build - name: Benchmark working-directory: ${{ vars.BENCHMARKDOTNET_WORKING_DIRECTORY }} run: dotnet run --configuration ${{ env.build-configuration }} /p:Platform=${{ env.build-platform }} --framework ${{ vars.DEFAULT_BUILD_FRAMEWORK }} --runtimes ${{ vars.BENCHMARKDOTNET_RUNTIMES }} --filter ${{ vars.BENCHMARKDOTNET_FILTER }} --artifacts ${{ runner.temp }}/benchmarks/ --exporters GitHub --memory --iterationTime 100 --join - name: Upload Benchmark Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: benchmark-${{ matrix.os }} path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d3066a1..ad996933 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,7 @@ permissions: pages: write id-token: write contents: write + administration: write concurrency: group: release-${{ github.head_ref || github.ref }} @@ -65,7 +66,7 @@ jobs: release-version: ${{ steps.format-version.outputs.release-version }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' uses: actions/setup-dotnet@v5 with: @@ -119,7 +120,7 @@ jobs: steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Compile source code' uses: ./.github/actions/source/compile @@ -136,7 +137,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Setup .NET' uses: actions/setup-dotnet@v5 @@ -146,7 +147,7 @@ jobs: - name: 'Run tests' uses: ./.github/actions/testing/test with: - project-path: '**/PolylineAlgorithm.Tests.csproj' + project-path: './tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj' test-results-directory: '${{ runner.temp }}/${{ env.test-result-directory }}/' code-coverage-settings-file: '${{ github.workspace}}/code-coverage-settings.xml' @@ -182,7 +183,7 @@ jobs: package-artifact-name: ${{ env.package-artifact-name }} steps: - name: 'Checkout ${{ github.base_ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -190,7 +191,7 @@ jobs: dotnet-version: ${{ env.dotnet-sdk-version }} - name: Download Build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: build @@ -199,7 +200,7 @@ jobs: dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} - name: Upload Package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.package-artifact-name }} path: | @@ -215,7 +216,7 @@ jobs: environment: 'NuGet' steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -242,7 +243,7 @@ jobs: notes-start-tag: ${{ needs.workflow-variables.outputs.notes-start-tag }} steps: - name: 'Checkout ${{ github.head_ref || github.ref }}' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Determine notes start tag' id: determine-notes-start-tag @@ -257,3 +258,111 @@ jobs: release-version: ${{ env.release-version }} is-preview: ${{ env.is-preview }} notes-start-tag: ${{ steps.determine-notes-start-tag.outputs.notes-start-tag }} + + merge-to-main: + name: 'Merge ${{ github.ref_name }} into main' + needs: [workflow-variables, release, versioning] + if: ${{ needs.workflow-variables.outputs.is-release == 'true' }} + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + current-branch: ${{ github.ref_name }} + current-version: ${{ needs.versioning.outputs.friendly-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 'Detect if current branch is the latest release' + id: detect-latest + run: | + git fetch origin + latest_version=$(git ls-remote --heads origin 'release/*' | grep -oP 'release/\K\d+\.\d+' | sort -V | tail -1) + current_version=$(echo "${{ env.current-version }}" | grep -oP '^\d+\.\d+') + echo "Latest release branch version: $latest_version" + echo "Current version (normalized): $current_version" + if [[ "$latest_version" == "$current_version" ]]; then + echo "is-latest=true" >> $GITHUB_OUTPUT + else + echo "is-latest=false" >> $GITHUB_OUTPUT + fi + + - name: 'Check if PR to main already exists' + id: check-pr + if: ${{ steps.detect-latest.outputs.is-latest == 'true' }} + run: | + pr_count=$(gh pr list --head "${{ env.current-branch }}" --base main --state open --limit 1 --json id --jq '. | length') + if [[ $pr_count -gt 0 ]]; then + echo "pr-exists=true" >> $GITHUB_OUTPUT + else + echo "pr-exists=false" >> $GITHUB_OUTPUT + fi + + - name: 'Create PR: Merge ${{ env.current-branch }} into main' + if: ${{ steps.detect-latest.outputs.is-latest == 'true' && steps.check-pr.outputs.pr-exists == 'false' }} + run: | + gh pr create \ + --title "Merge ${{ env.current-branch }} into main" \ + --fill \ + --base main \ + --head "${{ env.current-branch }}" + + - name: 'Write merge summary' + run: | + if [[ "${{ steps.detect-latest.outputs.is-latest }}" == "true" ]]; then + echo "✅ PR created to merge **${{ env.current-branch }}** into **main**." >> $GITHUB_STEP_SUMMARY + else + echo "⏭️ Skipped merge to main: **${{ env.current-branch }}** is not the highest release branch." >> $GITHUB_STEP_SUMMARY + fi + + create-support-branch: + name: 'Create support branch for ${{ github.ref_name }}' + needs: [workflow-variables, release, versioning] + if: ${{ needs.workflow-variables.outputs.is-release == 'true' }} + runs-on: ubuntu-latest + env: + current-version: ${{ needs.versioning.outputs.friendly-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 'Resolve support branch name' + id: resolve-support-branch + run: | + major_minor=$(echo "${{ env.current-version }}" | grep -oP '^\d+\.\d+') + echo "support-branch=support/$major_minor" >> $GITHUB_OUTPUT + + - name: 'Check if support branch already exists' + id: check-support-branch + run: | + git fetch origin + if git ls-remote --exit-code --heads origin "${{ steps.resolve-support-branch.outputs.support-branch }}" > /dev/null 2>&1; then + echo "support-branch-exists=true" >> $GITHUB_OUTPUT + else + echo "support-branch-exists=false" >> $GITHUB_OUTPUT + fi + + - name: 'Create support branch' + if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }} + run: | + git config user.name "$(git log -n 1 --pretty=format:%an)" + git config user.email "$(git log -n 1 --pretty=format:%ae)" + git checkout -b "${{ steps.resolve-support-branch.outputs.support-branch }}" + git push --set-upstream origin "${{ steps.resolve-support-branch.outputs.support-branch }}" + + - name: 'Lock support branch' + if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }} + uses: './.github/actions/github/branch-protection/lock' + with: + branch: ${{ steps.resolve-support-branch.outputs.support-branch }} + + - name: 'Write support branch summary' + run: | + if [[ "${{ steps.check-support-branch.outputs.support-branch-exists }}" == "false" ]]; then + echo "✅ Created and locked support branch **${{ steps.resolve-support-branch.outputs.support-branch }}**." >> $GITHUB_STEP_SUMMARY + else + echo "⏭️ Support branch **${{ steps.resolve-support-branch.outputs.support-branch }}** already exists." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitignore b/.gitignore index 88197d63..2118d126 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files @@ -262,3 +262,9 @@ __pycache__/ # BenchmarkDotNet artifacts **/BenchmarkDotNet.Artifacts/ + +# GitHub Copilot Testing folder +/.codetesting + +# DocFX build output +api-reference/_docs/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..cb8bb32a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,93 @@ +# Polyline Algorithm Agents Instructions + +## Purpose + +Instructions for automated agents (bots, CI, and code review tools) and contributors interacting with the Polyline Algorithm library. + +--- + +## General Guidelines + +- All contributions and automation **must adhere to code style** (`.editorconfig`, `dotnet format`). +- **Unit tests** are required for new features and bug fixes (`tests/` directory). +- **Benchmarks** must be updated for performance-impacting changes (`benchmarks/` directory). + +--- + +## Pull Requests + +Agents and contributors should: + +- **Attach benchmark results** for encoding/decoding performance changes +- Document **public API changes** in XML comments and verify updates at [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) +- Run format and static analysis tools before submitting (`dotnet format`, analyzers) +- Update **README.md** and `/samples` for public API changes + +--- + +## Error Handling and Logging + +- Throw **descriptive exceptions** for invalid input/edge cases +- Use internal logging helpers for operational status (`LogInfoExtensions`, `LogWarningExtensions`) + +--- + +## Encoding/Decoding Agents + +- Use abstraction interfaces (`IPolylineEncoder`, `IPolylineDecoder` if available) +- Prefer extension methods for collections and arrays +- Validate latitude/longitude ranges + +--- + +## Issue and PR Templates + +Agents should reference standardized templates from `.github`. Contributors must use them for new issues or PRs. + +--- + +## Extensibility + +- Add encoding schemes or coordinate types in **separate classes/files** +- Register via factory pattern if supporting multiple algorithms +- Do not mix logic between different polyline versions + +--- + +## Future-proofing + +- Support for precision or custom coordinate fields: update `PolylineEncodingOptions` with clear doc comments + +--- + +## Documentation + +- Keep XML doc comments up-to-date in source files +- API reference is auto-generated and hosted at + [https://petesramek.github.io/polyline-algorithm-csharp/](https://petesramek.github.io/polyline-algorithm-csharp/) +- After public API changes, verify docs render correctly on the website +- Add usage samples in XML comments and `/samples` directory + +--- + +## Agent File Format (for `.github/agents`) + +Each agent instruction file should specify: + +``` +# AGENT INSTRUCTIONS + +- Purpose and scope +- Required tools/commands +- Coding and testing requirements +- Logging/error handling expectations +- Documentation or samples to update +``` + +--- + +## Contact & Questions + +Questions or clarifications: open a GitHub issue and tag `@petesramek`. + +--- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a2492d27 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to PolylineAlgorithm + +Thank you for your interest in improving this library! + +## Developer Documentation + +In-depth developer guides are in the [`/docs`](./docs/README.md) folder: + +- [Local Development](./docs/local-development.md) — build, test, and format commands +- [Testing](./docs/testing.md) — how to write unit tests +- [Benchmarks](./docs/benchmarks.md) — how to write and run benchmarks +- [Composite Actions](./docs/composite-actions.md) — reusable CI actions catalogue +- [Workflows](./docs/workflows.md) — CI/CD pipeline overview +- [Branch Strategy](./docs/branch-strategy.md) — branch lifecycle and environments +- [Versioning](./docs/versioning.md) — branch naming and the version pipeline +- [API Documentation](./docs/api-documentation.md) — DocFX and the API reference site + +## Guidelines + +- **Follow code style:** Use `.editorconfig` and run `dotnet format`. +- **Add unit tests:** Place all tests in `/tests`, following naming conventions. +- **Benchmark updates:** Add or update `/benchmarks` for major changes. + +## Issue and PR Templates + +Please use the provided templates in `.github` for all new issues or pull requests. + +## API Documentation + +API reference is auto-generated from XML comments and published at +👉 [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +- All public classes, interfaces, and methods require XML doc comments. +- After merging, verify that documentation renders correctly. +- Add usage samples where applicable. + +## Submitting a Change + +1. Fork the repo and create a new branch. +2. Implement your changes, tests, and update doc comments. +3. Run `dotnet format`, and all tests/benchmarks. +4. Submit a pull request, using the provided template. + +## Contact + +For help or questions, open an issue and tag `@petesramek`. + +## License + +MIT License © Pete Sramek diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..96ebb124 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,25 @@ + + + + 14.0 + enable + enable + true + en + + + + latest + All + true + true + + + + + + + + + + diff --git a/PolylineAlgorithm.slnx b/PolylineAlgorithm.slnx index 5eac2683..29fa9445 100644 --- a/PolylineAlgorithm.slnx +++ b/PolylineAlgorithm.slnx @@ -1,4 +1,7 @@ + + + diff --git a/README.md b/README.md index 156a8f2a..9dfb8a18 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,188 @@ -# PolylineAlgorithm for .NET +# PolylineAlgorithm for .NET -[![NuGet](https://img.shields.io/nuget/v/PolylineAlgorithm.svg)](https://www.nuget.org/packages/PolylineAlgorithm/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Build](https://github.com/sramekpete/polyline-algorithm-csharp/actions/workflows/build.yml/badge.svg)](https://github.com/sramekpete/polyline-algorithm-csharp/actions/workflows/build.yml) +Lightweight .NET Standard 2.1 library implementing Google-compliant Encoded Polyline Algorithm with strong input validation, modern API patterns, and extensibility for custom coordinate types. -Lightweight .NET Standard 2.1 library implementing Google Encoded Polyline Algorithm. -Package should be primarily used as baseline for libraries that implement polyline encoding/decoding functionality. +## Table of Contents -More info about the algorithm can be found at [Google Developers](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](#api-reference) +- [Benchmarks](#benchmarks) +- [FAQ](#faq) +- [Contributing](#contributing) +- [Support](#support) +- [License](#license) -## Prerequisites +## Features -PolylineAlgorithm for .NET is available as a NuGet package targeting .NET Standard 2.1. +- Fully compliant Google Encoded Polyline Algorithm for .NET Standard 2.1+ +- Extensible encoding and decoding APIs for custom coordinate and polyline types (`IPolylineEncoder`, `IPolylineDecoder`, `AbstractPolylineEncoder`, `AbstractPolylineDecoder`) +- Extension methods for encoding from `List` and arrays (`PolylineEncoderExtensions`) +- Robust input validation with descriptive exceptions for malformed/invalid data +- Advanced configuration via `PolylineEncodingOptions` (precision, buffer size, logging) +- Logging and diagnostic support for CI/CD and developer diagnostics via `Microsoft.Extensions.Logging` +- Low-level utilities for normalization, validation, encoding and decoding via static `PolylineEncoding` class +- Thorough unit tests and benchmarks for correctness and performance +- Auto-generated API documentation ([API Reference](https://petesramek.github.io/polyline-algorithm-csharp/)) +- Support for .NET Core, .NET 5+, Xamarin, Unity, Blazor, and other platforms supporting `netstandard2.1` -.NET CLI: `dotnet add package PolylineAlgorithm` +## Installation -Package Manager Console: `Install-Package PolylineAlgorithm` +Using dotnet tool -## How to use it +```shell +dotnet add package PolylineAlgorithm +``` -In the majority of cases you would like to inherit `AbstractPolylineDecoder` and `AbstractPolylineEncoder` classes and implement abstract methods that are mainly responsible for extracting data from your coordinate and polyline types and creating new instances of them. +or via NuGet -In some cases you may want to implement your own decoder and encoder from scratch. -In that case you can use `PolylineEncoding` static class that offers static methods for encoding and decoding polyline segments. +```powershell +Install-Package PolylineAlgorithm +``` -## Documentation +## Usage -Documentation is can be found at [https://sramekpete.github.io/polyline-algorithm-csharp/](https://sramekpete.github.io/polyline-algorithm-csharp/). \ No newline at end of file +The library provides abstract base classes to implement your own encoder and decoder for any coordinate and polyline type. + +### Custom encoder and decoder + +#### Encoding + +Custom encoder implementation. + +```csharp +using PolylineAlgorithm; +using PolylineAlgorithm.Abstraction; + +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + public MyPolylineEncoder() + : base() { } + + public MyPolylineEncoder(PolylineEncodingOptions options) + : base(options) { } + + protected override double GetLatitude((double Latitude, double Longitude) coordinate) { + return coordinate.Latitude; + } + + protected override double GetLongitude((double Latitude, double Longitude) coordinate) { + return coordinate.Longitude; + } + + protected override string CreatePolyline(ReadOnlyMemory polyline) { + return polyline.ToString(); + } +} +``` + +Custom encoder usage. + +```csharp +using PolylineAlgorithm.Extensions; + +var coordinates = new List<(double Latitude, double Longitude)> +{ + (48.858370, 2.294481), + (51.500729, -0.124625) +}; + +var encoder = new MyPolylineEncoder(); +string encoded = encoder.Encode(coordinates); // extension method for List + +Console.WriteLine(encoded); +``` + +#### Decoding + +Custom decoder implementation. + +```csharp +using PolylineAlgorithm; +using PolylineAlgorithm.Abstraction; + +public sealed class MyPolylineDecoder : AbstractPolylineDecoder { + public MyPolylineDecoder() + : base() { } + + public MyPolylineDecoder(PolylineEncodingOptions options) + : base(options) { } + + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { + return (latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) { + return polyline.AsMemory(); + } +} +``` + +Custom decoder usage. + +```csharp +string encoded = "yseiHoc_MwacOjnwM"; + +var decoder = new MyPolylineDecoder(); +IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); +``` + +> **Note:** +> If you need low-level utilities for normalization, validation, encoding and decoding, use static methods from the `PolylineEncoding` class. + +## API Reference + +Full API docs and guides (auto-generated from source) are available at [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +## Benchmarks + +- See `/benchmarks` in the repo for performance evaluation. +- Contributors: Update benchmarks and document results for performance-impacting PRs. + +## FAQ + +**Q: What coordinate ranges are valid?** +A: Latitude must be -90..90; longitude -180..180. Out-of-range input throws `ArgumentOutOfRangeException`. + +**Q: Which .NET versions are supported?** +A: All platforms supporting `netstandard2.1` (including .NET Core and .NET 5+). + +**Q: What happens if I pass invalid or malformed input to the decoder?** +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. + +**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. + +**Q: Can the library be used in Unity, Xamarin, Blazor, or other .NET-compatible platforms?** +A: Yes! Any environment supporting `netstandard2.1` can use this library. + +**Q: Where can I report bugs or request features?** +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. + +**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. + +**Q: Does the library support streaming or incremental decoding of polylines?** +A: Currently, only batch encode/decode is supported. For streaming scenarios, implement custom logic using `PolylineEncoding` utility functions. + +## Contributing + +- Follow code style and PR instructions in [AGENTS.md](./AGENTS.md). +- Ensure all features are covered by tests and XML doc comments. +- For questions or suggestions, open an issue and use the provided templates. + +## Support + +Have a question, bug, or feature request? [Open an issue!](https://github.com/petesramek/polyline-algorithm-csharp/issues) + +--- + +## License + +MIT License © Pete Sramek diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml index bffca6fe..6349f59c 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml @@ -3,7 +3,7 @@ title: Class AbstractPolylineDecoder body: - api1: Class AbstractPolylineDecoder id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L23 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L22 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 @@ -14,15 +14,14 @@ body: url: PolylineAlgorithm.Abstraction.html - name: Assembly value: PolylineAlgorithm.dll -- markdown: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. +- 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 @@ -50,11 +49,14 @@ body: - text: object.ToString() url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Remarks -- markdown: This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats. +- 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L27 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L28 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor @@ -62,7 +64,7 @@ body: - code: protected AbstractPolylineDecoder() - api3: AbstractPolylineDecoder(PolylineEncodingOptions) id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L39 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L40 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -80,15 +82,15 @@ body: - type: - text: ArgumentNullException url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when options is null + 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L46 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/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 encoder. +- markdown: Gets the encoding options used by this polyline decoder. - code: public PolylineEncodingOptions Options { get; } - h4: Property Value - parameters: @@ -98,11 +100,11 @@ body: - 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L153 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/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 coordinate instance from the given latitude and longitude values. +- markdown: Creates a TCoordinate instance from the specified latitude and longitude values. - code: protected abstract TCoordinate CreateCoordinate(double latitude, double longitude) - h4: Parameters - parameters: @@ -110,31 +112,40 @@ body: type: - text: double url: https://learn.microsoft.com/dotnet/api/system.double - description: The latitude value. + 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 value. + description: The longitude component of the coordinate, in degrees. - h4: Returns - parameters: - type: - TCoordinate - description: A coordinate instance of type TCoordinate. -- api3: Decode(TPolyline) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Decode__0_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L66 + 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/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L81 metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0) -- markdown: Decodes an encoded TPolyline into a sequence of TCoordinate instances. -- code: public IEnumerable Decode(TPolyline polyline) + 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: @@ -158,20 +169,24 @@ body: - text: InvalidPolylineException url: PolylineAlgorithm.InvalidPolylineException.html description: Thrown when the polyline format is invalid or malformed at a specific position. -- api3: GetReadOnlyMemory(TPolyline) - id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_GetReadOnlyMemory__0_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L139 + - 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/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L187 metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0) -- markdown: Converts the provided polyline instance into a for decoding. -- code: protected abstract ReadOnlyMemory GetReadOnlyMemory(TPolyline polyline) + 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 containing the encoded polyline data to decode. + description: The TPolyline instance from which to extract the character sequence. - h4: Returns - parameters: - type: @@ -181,10 +196,38 @@ body: - text: char url: https://learn.microsoft.com/dotnet/api/system.char - '>' - description: A representing the encoded polyline data. + 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/develop/1.0/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: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. + description: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml index 45ae4d3e..ec0a65e9 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml @@ -3,7 +3,7 @@ title: Class AbstractPolylineEncoder body: - api1: Class AbstractPolylineEncoder id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L25 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L27 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 @@ -14,15 +14,14 @@ body: url: PolylineAlgorithm.Abstraction.html - name: Assembly value: PolylineAlgorithm.dll -- markdown: >- - Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. +- 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 @@ -49,12 +48,21 @@ body: 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: This abstract class serves as a base for specific polyline encoders, allowing customization of the encoding process. +- 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L29 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L33 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor @@ -62,7 +70,7 @@ body: - code: protected AbstractPolylineEncoder() - api3: AbstractPolylineEncoder(PolylineEncodingOptions) id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L39 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L43 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -84,7 +92,7 @@ body: - h2: Properties - api3: Options id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Options - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L46 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L57 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options @@ -98,7 +106,7 @@ body: - h2: Methods - api3: CreatePolyline(ReadOnlyMemory) id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_CreatePolyline_System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L199 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/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}) @@ -120,32 +128,38 @@ body: - type: - TPolyline description: An instance of TPolyline representing the encoded polyline. -- api3: Encode(IEnumerable) - id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_Collections_Generic_IEnumerable__0__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L64 +- 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/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L80 metadata: - uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) - commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) + 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: public TPolyline Encode(IEnumerable coordinates) +- 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: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - 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. - - Returns default if the input collection is empty or null. + description: An instance of TPolyline representing the encoded coordinates. - h4: Exceptions - parameters: - type: @@ -156,9 +170,13 @@ body: - 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L219 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L194 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) @@ -178,7 +196,7 @@ body: 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/preview/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L209 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L184 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) @@ -198,7 +216,4 @@ body: description: The longitude value as a . languageId: csharp metadata: - description: >- - Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. + description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml index 01478f20..704f92b2 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml @@ -1,9 +1,9 @@ ### YamlMime:ApiPage -title: Interface IPolylineDecoder +title: Interface IPolylineDecoder body: -- api1: Interface IPolylineDecoder +- api1: Interface IPolylineDecoder id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L13 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L22 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineDecoder`2 @@ -15,35 +15,76 @@ 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 - - name: TCoordinate + description: >- + The type that represents the encoded polyline input. Common implementations use , + + but custom wrapper types are allowed to carry additional metadata. + - name: TValue + description: >- + 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>). - h2: Methods -- api3: Decode(TPolyline) - id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2_Decode__0_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L23 +- api3: Decode(TPolyline, CancellationToken) + id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L48 metadata: - uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0) - commentId: M:PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0) -- markdown: Decodes the specified encoded polyline into a sequence of geographic coordinates. -- code: IEnumerable Decode(TPolyline polyline) + uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,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) - h4: Parameters - parameters: - name: polyline type: - TPolyline - description: The TPolyline instance containing the encoded polyline string to decode. + description: >- + The TPolyline instance containing the encoded polyline to decode. + + Implementations SHOULD validate the input and may throw + + or for invalid formats. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: >- + A to observe while decoding. If cancellation is requested, + + implementations SHOULD stop work and throw an . + optional: true - h4: Returns - parameters: - type: - text: IEnumerable url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 - < - - TCoordinate + - TValue - '>' - description: An of TCoordinate representing the decoded latitude and longitude pairs. + description: >- + An of TValue representing the decoded + + latitude/longitude pairs (or equivalent coordinates) in the same order they were encoded. +- h4: Remarks +- markdown: >- + Implementations commonly follow the Google Encoded Polyline Algorithm Format, but this interface + + does not mandate a specific encoding. Consumers should rely on a concrete decoder's documentation + + to understand the exact encoding supported. +- h4: Exceptions +- parameters: + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when the provided cancellationToken requests cancellation. languageId: csharp metadata: description: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml index 424f9516..979fe774 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml @@ -1,9 +1,9 @@ ### YamlMime:ApiPage -title: Interface IPolylineEncoder +title: Interface IPolylineEncoder body: -- api1: Interface IPolylineEncoder +- api1: Interface IPolylineEncoder id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L13 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L36 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineEncoder`2 @@ -14,36 +14,129 @@ body: url: PolylineAlgorithm.Abstraction.html - name: Assembly value: PolylineAlgorithm.dll -- markdown: Defines a contract for encoding a sequence of geographic coordinates into an encoded polyline string. -- code: public interface IPolylineEncoder +- markdown: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. +- code: public interface IPolylineEncoder - h4: Type Parameters - parameters: - - name: TCoordinate + - name: TValue + description: >- + The concrete coordinate representation used by the encoder (for example a struct or class containing + + Latitude and Longitude values). Implementations must document the expected shape, + + units (typically decimal degrees), and any required fields for TValue. + + Common shapes: + + - A struct or class with two double properties named Latitude and Longitude. + + - A tuple-like type (for example ValueTuple<double,double>) where the encoder documents + which element represents latitude and longitude. - name: TPolyline + description: >- + The encoded polyline representation returned by the encoder (for example string, + + ReadOnlyMemory<char>, or a custom wrapper type). Concrete implementations should document + + 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___ +- h2: Remarks +- markdown: >- + - This interface is intentionally minimal to allow different encoding strategies (Google encoded polyline, + precision/scale variants, or custom compressed formats) to be expressed behind a common contract. + - Implementations should document: + - Coordinate precision and rounding rules (for example 1e-5 for 5-decimal precision). + - Coordinate ordering and whether altitude or additional dimensions are supported. + - Thread-safety guarantees: whether instances are safe to reuse concurrently or must be instantiated per-call. + - 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(IEnumerable) - id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2_Encode_System_Collections_Generic_IEnumerable__0__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L23 +- 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/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L76 metadata: - uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) - commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) -- markdown: Encodes a sequence of geographic coordinates into an encoded polyline representation. -- code: TPolyline Encode(IEnumerable coordinates) + 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) +- 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) - h4: Parameters - parameters: - name: coordinates type: - - text: IEnumerable - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 - < - - TCoordinate + - TValue - '>' - description: The collection of TCoordinate instances to encode into a polyline. + description: >- + The collection of TValue instances to encode into a polyline. + + 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: 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. + + Implementations should observe this token and throw + + when cancellation is requested. For fast, in-memory encoders cancellation may be best-effort. + optional: true - h4: Returns - parameters: - type: - TPolyline - description: A TPolyline containing the encoded polyline string that represents the input coordinates. + description: >- + A TPolyline containing the encoded polyline that represents the input coordinates. + + The exact format and any delimiting/terminating characters are implementation-specific and must be + + documented by concrete encoder types. +- h4: Examples +- markdown: >- +
// Example pseudocode for typical usage with a string-based encoder:
+
+    var coords = new[] {
+        new Coordinate { Latitude = 47.6219, Longitude = -122.3503 },
+        new Coordinate { Latitude = 47.6220, Longitude = -122.3504 }
+    };
+
+    IPolylineEncoder<Coordinate,string> encoder = new GoogleEncodedPolylineEncoder();
+
+    string encoded = encoder.Encode(coords, CancellationToken.None);
+- h4: Remarks +- markdown: >- + - Implementations should validate input as appropriate and document any preconditions (for example + if coordinates must be within [-90,90] latitude and [-180,180] longitude). + - For large input sequences, implementations may provide streaming or incremental encoders; those + variants can still implement this interface by materializing the final encoded result. +- h4: Exceptions +- parameters: + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown if the operation is canceled via cancellationToken. languageId: csharp metadata: - description: Defines a contract for encoding a sequence of geographic coordinates into an encoded polyline string. + description: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.yml index ff580e3c..e9de48f7 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.yml @@ -11,25 +11,24 @@ body: - type: text: AbstractPolylineDecoder url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html - description: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. + 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 functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. + description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. - h3: Interfaces - parameters: - type: - text: IPolylineDecoder + text: IPolylineDecoder url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html description: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. - type: - text: IPolylineEncoder + text: IPolylineEncoder url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html - description: Defines a contract for encoding a sequence of geographic coordinates into an encoded polyline string. + description: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.Coordinate.yml b/api-reference/1.0/PolylineAlgorithm.Coordinate.yml deleted file mode 100644 index 7f5afdf3..00000000 --- a/api-reference/1.0/PolylineAlgorithm.Coordinate.yml +++ /dev/null @@ -1,250 +0,0 @@ -### YamlMime:ApiPage -title: Struct Coordinate -body: -- api1: Struct Coordinate - id: PolylineAlgorithm_Coordinate - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L22 - metadata: - uid: PolylineAlgorithm.Coordinate - commentId: T:PolylineAlgorithm.Coordinate -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Represents a geographic coordinate as a pair of latitude and longitude values. -- code: 'public readonly struct Coordinate : IEquatable' -- h4: Implements -- list: - - text: IEquatable - url: https://learn.microsoft.com/dotnet/api/system.iequatable-1 -- 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 struct is designed to be immutable and lightweight, providing a simple way to represent - - geographic coordinates in degrees. It includes validation for latitude and longitude ranges - - and provides methods for equality comparison and string representation. -- h2: Constructors -- api3: Coordinate() - id: PolylineAlgorithm_Coordinate__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L28 - metadata: - uid: PolylineAlgorithm.Coordinate.#ctor - commentId: M:PolylineAlgorithm.Coordinate.#ctor -- markdown: Initializes a new instance of the struct with default values (0) for and . -- code: public Coordinate() -- api3: Coordinate(double, double) - id: PolylineAlgorithm_Coordinate__ctor_System_Double_System_Double_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L46 - metadata: - uid: PolylineAlgorithm.Coordinate.#ctor(System.Double,System.Double) - commentId: M:PolylineAlgorithm.Coordinate.#ctor(System.Double,System.Double) -- markdown: Initializes a new instance of the struct with the specified latitude and longitude values. -- code: public Coordinate(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. Must be between -90 and 90. - - name: longitude - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The longitude component of the coordinate, in degrees. Must be between -180 and 180. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentOutOfRangeException - url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception - description: >- - Thrown when latitude is less than -90 or greater than 90, - - or when longitude is less than -180 or greater than 180. -- h2: Properties -- api3: Latitude - id: PolylineAlgorithm_Coordinate_Latitude - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L62 - metadata: - uid: PolylineAlgorithm.Coordinate.Latitude - commentId: P:PolylineAlgorithm.Coordinate.Latitude -- markdown: Gets the latitude component of the coordinate, in degrees. -- code: public double Latitude { get; } -- h4: Property Value -- parameters: - - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double -- api3: Longitude - id: PolylineAlgorithm_Coordinate_Longitude - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L67 - metadata: - uid: PolylineAlgorithm.Coordinate.Longitude - commentId: P:PolylineAlgorithm.Coordinate.Longitude -- markdown: Gets the longitude component of the coordinate, in degrees. -- code: public double Longitude { get; } -- h4: Property Value -- parameters: - - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double -- h2: Methods -- api3: Equals(object?) - id: PolylineAlgorithm_Coordinate_Equals_System_Object_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L82 - metadata: - uid: PolylineAlgorithm.Coordinate.Equals(System.Object) - commentId: M:PolylineAlgorithm.Coordinate.Equals(System.Object) -- markdown: Indicates whether this instance and a specified object are equal. -- code: public override bool Equals(object? obj) -- h4: Parameters -- parameters: - - name: obj - type: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - '?' - description: The object to compare with the current instance. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if obj and this instance are the same type and represent the same value; otherwise, false. -- api3: Equals(Coordinate) - id: PolylineAlgorithm_Coordinate_Equals_PolylineAlgorithm_Coordinate_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L102 - metadata: - uid: PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate) - commentId: M:PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate) -- markdown: Indicates whether the current object is equal to another object of the same type. -- code: public bool Equals(Coordinate other) -- h4: Parameters -- parameters: - - name: other - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: An object to compare with this object. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the current object is equal to the other parameter; otherwise, false. -- api3: GetHashCode() - id: PolylineAlgorithm_Coordinate_GetHashCode - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L85 - metadata: - uid: PolylineAlgorithm.Coordinate.GetHashCode - commentId: M:PolylineAlgorithm.Coordinate.GetHashCode -- markdown: Returns the hash code for this instance. -- code: public override int GetHashCode() -- h4: Returns -- parameters: - - type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: A 32-bit signed integer that is the hash code for this instance. -- api3: IsDefault() - id: PolylineAlgorithm_Coordinate_IsDefault - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L75 - metadata: - uid: PolylineAlgorithm.Coordinate.IsDefault - commentId: M:PolylineAlgorithm.Coordinate.IsDefault -- markdown: Determines whether this coordinate is the default value (both and are 0). -- code: public bool IsDefault() -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if both latitude and longitude are 0; otherwise, false. -- api3: ToString() - id: PolylineAlgorithm_Coordinate_ToString - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L93 - metadata: - uid: PolylineAlgorithm.Coordinate.ToString - commentId: M:PolylineAlgorithm.Coordinate.ToString -- markdown: 'Returns a string representation of this coordinate in the format: { Latitude: [double], Longitude: [double] }.' -- code: public override string ToString() -- h4: Returns -- parameters: - - type: - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - description: A string representation of the coordinate. -- h2: Operators -- api3: operator ==(Coordinate, Coordinate) - id: PolylineAlgorithm_Coordinate_op_Equality_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Coordinate_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L119 - metadata: - uid: PolylineAlgorithm.Coordinate.op_Equality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) - commentId: M:PolylineAlgorithm.Coordinate.op_Equality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) -- markdown: Determines whether two instances are equal. -- code: public static bool operator ==(Coordinate left, Coordinate right) -- h4: Parameters -- parameters: - - name: left - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: The first coordinate to compare. - - name: right - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: The second coordinate to compare. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if both coordinates are equal; otherwise, false. -- api3: operator !=(Coordinate, Coordinate) - id: PolylineAlgorithm_Coordinate_op_Inequality_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Coordinate_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L129 - metadata: - uid: PolylineAlgorithm.Coordinate.op_Inequality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) - commentId: M:PolylineAlgorithm.Coordinate.op_Inequality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) -- markdown: Determines whether two instances are not equal. -- code: public static bool operator !=(Coordinate left, Coordinate right) -- h4: Parameters -- parameters: - - name: left - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: The first coordinate to compare. - - name: right - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: The second coordinate to compare. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the coordinates are not equal; otherwise, false. -languageId: csharp -metadata: - description: Represents a geographic coordinate as a pair of latitude and longitude values. diff --git a/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml b/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml deleted file mode 100644 index 775098b4..00000000 --- a/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml +++ /dev/null @@ -1,45 +0,0 @@ -### YamlMime:ApiPage -title: Enum CoordinateValueType -body: -- api1: Enum CoordinateValueType - id: PolylineAlgorithm_CoordinateValueType - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/CoordinateValueType.cs#L11 - metadata: - uid: PolylineAlgorithm.CoordinateValueType - commentId: T:PolylineAlgorithm.CoordinateValueType -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: Represents the type of a geographic coordinate value. -- code: public enum CoordinateValueType -- h2: Fields -- parameters: - - name: Latitude - default: "1" - description: >+ - Represents a latitude value. - - - name: Longitude - default: "2" - description: >+ - Represents a longitude value. - - - name: None - default: "0" - description: >+ - Represents no specific type. This value is used when the type is not applicable or not specified. - -- h2: Remarks -- markdown: >- - This enumeration is used to specify whether a coordinate value represents latitude or - - longitude. Latitude values indicate the north-south position, while longitude values indicate the east-west - - position. -languageId: csharp -metadata: - description: Represents the type of a geographic coordinate value. diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml index 086af359..4b97f584 100644 --- a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml +++ b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml @@ -3,7 +3,7 @@ title: Class PolylineDecoderExtensions body: - api1: Class PolylineDecoderExtensions id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L16 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L16 metadata: uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions commentId: T:PolylineAlgorithm.Extensions.PolylineDecoderExtensions @@ -39,14 +39,14 @@ body: - text: object.ToString() url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Methods -- api3: Decode(IPolylineDecoder, string) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_String_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L32 +- 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/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L33 metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},System.String) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},System.String) -- markdown: Decodes an encoded polyline string into a sequence of geographic coordinates. -- code: public static IEnumerable Decode(this IPolylineDecoder decoder, string polyline) + 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 @@ -54,43 +54,47 @@ body: - text: IPolylineDecoder url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - < - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string - ',' - " " - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - 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. + - 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 - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TValue - '>' - description: An containing the decoded latitude and longitude pairs. + 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, char[]) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_Char___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L55 + 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/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L61 metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},System.Char[]) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},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) + 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 @@ -98,45 +102,52 @@ body: - text: IPolylineDecoder url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - < - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string - ',' - " " - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - 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 character array to decode. + - '>' + 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 - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TValue - '>' - description: An containing the decoded latitude and longitude pairs. + 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, ReadOnlyMemory) - id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L78 +- 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/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L86 metadata: - uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},System.ReadOnlyMemory{System.Char}) - commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode(PolylineAlgorithm.Abstraction.IPolylineDecoder{PolylineAlgorithm.Polyline,PolylineAlgorithm.Coordinate},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) + 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 @@ -144,39 +155,41 @@ body: - text: IPolylineDecoder url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html - < - - text: Polyline - url: PolylineAlgorithm.Polyline.html - - ',' - - " " - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - - '>' - 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. + - ',' + - " " + - 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 - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TValue - '>' - description: An containing the decoded latitude and longitude pairs. + 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. + 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/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml index 767322e4..aeba23f9 100644 --- a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml +++ b/api-reference/1.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/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L16 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L19 metadata: uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions commentId: T:PolylineAlgorithm.Extensions.PolylineEncoderExtensions @@ -39,14 +39,19 @@ body: - text: object.ToString() url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Methods -- api3: Encode(IPolylineEncoder, ICollection) - id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode_PolylineAlgorithm_Abstraction_IPolylineEncoder_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Polyline__System_Collections_Generic_ICollection_PolylineAlgorithm_Coordinate__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L32 +- 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/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L37 metadata: - uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode(PolylineAlgorithm.Abstraction.IPolylineEncoder{PolylineAlgorithm.Coordinate,PolylineAlgorithm.Polyline},System.Collections.Generic.ICollection{PolylineAlgorithm.Coordinate}) - commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode(PolylineAlgorithm.Abstraction.IPolylineEncoder{PolylineAlgorithm.Coordinate,PolylineAlgorithm.Polyline},System.Collections.Generic.ICollection{PolylineAlgorithm.Coordinate}) -- markdown: Encodes a collection of instances into an encoded polyline. -- code: public static Polyline Encode(this IPolylineEncoder encoder, ICollection coordinates) + 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) - h4: Parameters - parameters: - name: encoder @@ -54,43 +59,45 @@ body: - text: IPolylineEncoder url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TCoordinate - ',' - " " - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - TPolyline - '>' description: The instance used to perform the encoding operation. - name: coordinates type: - - text: ICollection - url: https://learn.microsoft.com/dotnet/api/system.collections.generic.icollection-1 + - text: List + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1 - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TCoordinate - '>' - description: The sequence of objects to encode. + description: The list of TCoordinate objects to encode. - h4: Returns - parameters: - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: A representing the encoded polyline string for the provided coordinates. + - 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 is null. -- api3: Encode(IPolylineEncoder, Coordinate[]) - id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode_PolylineAlgorithm_Abstraction_IPolylineEncoder_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Polyline__PolylineAlgorithm_Coordinate___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L55 + 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/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L73 metadata: - uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode(PolylineAlgorithm.Abstraction.IPolylineEncoder{PolylineAlgorithm.Coordinate,PolylineAlgorithm.Polyline},PolylineAlgorithm.Coordinate[]) - commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode(PolylineAlgorithm.Abstraction.IPolylineEncoder{PolylineAlgorithm.Coordinate,PolylineAlgorithm.Polyline},PolylineAlgorithm.Coordinate[]) -- markdown: Encodes an array of instances into an encoded polyline. -- code: public static Polyline Encode(this IPolylineEncoder encoder, Coordinate[] coordinates) + 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 @@ -98,33 +105,35 @@ body: - text: IPolylineEncoder url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html - < - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TCoordinate - ',' - " " - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - TPolyline - '>' description: The instance used to perform the encoding operation. - name: coordinates type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html + - TCoordinate - '[' - ']' - description: The array of objects to encode. + description: The array of TCoordinate objects to encode. - h4: Returns - parameters: - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: A representing the encoded polyline string for the provided coordinates. + - 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 is null. + description: Thrown when encoder or coordinates is null. languageId: csharp metadata: description: Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. diff --git a/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml new file mode 100644 index 00000000..45962074 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml @@ -0,0 +1,313 @@ +### YamlMime:ApiPage +title: Class ExceptionGuard +body: +- api1: Class ExceptionGuard + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L29 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard + commentId: T:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Internal.Diagnostics + url: PolylineAlgorithm.Internal.Diagnostics.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Centralizes exception throwing for common validation and error scenarios used across the library. +- code: public static class ExceptionGuard +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: ExceptionGuard + url: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.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: >- + Methods in this class are intentionally small and annotated so that they can act as single + + call sites for throwing exceptions (improving inlining and stack traces). Many members have + + attributes to avoid polluting callers' stack traces (__StackTraceHidden__ on supported targets) + + or to prevent inlining on older targets. +- h2: Methods +- api3: StackAllocLimitMustBeEqualOrGreaterThan(int, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_StackAllocLimitMustBeEqualOrGreaterThan_System_Int32_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L97 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(System.Int32,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(System.Int32,System.String) +- markdown: Throws an when a stack allocation limit is below the required minimum. +- code: >- + [DoesNotReturn] + + public static void StackAllocLimitMustBeEqualOrGreaterThan(int minValue, string paramName) +- h4: Parameters +- parameters: + - name: minValue + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Minimum required stack allocation limit. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the limit. +- api3: ThrowArgumentCannotBeEmptyEnumerationMessage(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowArgumentCannotBeEmptyEnumerationMessage_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L111 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(System.String) +- markdown: Throws an when an enumeration argument is empty but must contain at least one element. +- code: >- + [DoesNotReturn] + + public static void ThrowArgumentCannotBeEmptyEnumerationMessage(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the enumeration. +- api3: ThrowArgumentNull(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowArgumentNull_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L51 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentNull(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentNull(System.String) +- markdown: Throws an for a null argument. +- code: >- + [DoesNotReturn] + + public static void ThrowArgumentNull(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter that was null. +- api3: ThrowBufferOverflow(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowBufferOverflow_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L65 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowBufferOverflow(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowBufferOverflow(System.String) +- markdown: Throws an with a provided message. +- code: >- + [DoesNotReturn] + + public static void ThrowBufferOverflow(string message) +- h4: Parameters +- parameters: + - name: message + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Message that describes the overflow condition. +- api3: ThrowCoordinateValueOutOfRange(double, double, double, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowCoordinateValueOutOfRange_System_Double_System_Double_System_Double_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L82 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCoordinateValueOutOfRange(System.Double,System.Double,System.Double,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCoordinateValueOutOfRange(System.Double,System.Double,System.Double,System.String) +- markdown: Throws an when a coordinate value is outside the allowed range. +- code: >- + [DoesNotReturn] + + public static void ThrowCoordinateValueOutOfRange(double value, double min, double max, string paramName) +- h4: Parameters +- parameters: + - name: value + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The coordinate value that was out of range. + - name: min + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: Inclusive minimum allowed value. + - name: max + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: Inclusive maximum allowed value. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter containing the coordinate. +- api3: ThrowCouldNotWriteEncodedValueToBuffer() + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowCouldNotWriteEncodedValueToBuffer + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L124 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer +- markdown: Throws an when an encoded value could not be written to the destination buffer. +- code: >- + [DoesNotReturn] + + public static void ThrowCouldNotWriteEncodedValueToBuffer() +- api3: ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int, int, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_System_Int32_System_Int32_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L140 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(System.Int32,System.Int32,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(System.Int32,System.Int32,System.String) +- markdown: Throws an when a destination array is not large enough to contain the polyline data. +- code: >- + [DoesNotReturn] + + public static void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength, string paramName) +- h4: Parameters +- parameters: + - name: destinationLength + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The length of the destination array. + - name: polylineLength + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The required polyline length. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the destination array. +- api3: ThrowInvalidPolylineBlockTerminator() + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineBlockTerminator + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L212 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineBlockTerminator + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineBlockTerminator +- markdown: Throws an when the polyline block terminator is invalid. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineBlockTerminator() +- api3: ThrowInvalidPolylineCharacter(char, int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineCharacter_System_Char_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L171 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineCharacter(System.Char,System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineCharacter(System.Char,System.Int32) +- markdown: Throws an when an unexpected character is encountered in the polyline. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineCharacter(char character, int position) +- h4: Parameters +- parameters: + - name: character + type: + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + description: The invalid character. + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Position in the polyline where the character was found. +- api3: ThrowInvalidPolylineFormat(long) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineFormat_System_Int64_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L199 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineFormat(System.Int64) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineFormat(System.Int64) +- markdown: Throws an when the polyline format is malformed. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineFormat(long position) +- h4: Parameters +- parameters: + - name: position + type: + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: Approximate position where the polyline became malformed. +- api3: ThrowInvalidPolylineLength(int, int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineLength_System_Int32_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L156 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineLength(System.Int32,System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineLength(System.Int32,System.Int32) +- markdown: Throws an when the polyline length is invalid. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineLength(int length, int min) +- h4: Parameters +- parameters: + - name: length + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The invalid length. + - name: min + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The minimum required length. +- api3: ThrowNotFiniteNumber(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowNotFiniteNumber_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L37 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowNotFiniteNumber(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowNotFiniteNumber(System.String) +- markdown: Throws an when a numeric argument is not a finite value. +- code: >- + [DoesNotReturn] + + public static void ThrowNotFiniteNumber(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter that had a non-finite value. +- api3: ThrowPolylineBlockTooLong(int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowPolylineBlockTooLong_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L185 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowPolylineBlockTooLong(System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowPolylineBlockTooLong(System.Int32) +- markdown: Throws an when a polyline block is longer than allowed. +- code: >- + [DoesNotReturn] + + public static void ThrowPolylineBlockTooLong(int position) +- h4: Parameters +- parameters: + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Position in the polyline where the block exceeded the allowed length. +languageId: csharp +metadata: + description: Centralizes exception throwing for common validation and error scenarios used across the library. diff --git a/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml new file mode 100644 index 00000000..dabb499a --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml @@ -0,0 +1,15 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm.Internal.Diagnostics +body: +- api1: Namespace PolylineAlgorithm.Internal.Diagnostics + id: PolylineAlgorithm_Internal_Diagnostics + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics + commentId: N:PolylineAlgorithm.Internal.Diagnostics +- h3: Classes +- parameters: + - type: + text: ExceptionGuard + url: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.html + description: Centralizes exception throwing for common validation and error scenarios used across the library. +languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml b/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml index fd2c31fe..b776c617 100644 --- a/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml +++ b/api-reference/1.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/preview/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L19 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L17 metadata: uid: PolylineAlgorithm.InvalidPolylineException commentId: T:PolylineAlgorithm.InvalidPolylineException @@ -15,10 +15,7 @@ body: - name: Assembly value: PolylineAlgorithm.dll - markdown: Exception thrown when a polyline is determined to be malformed or invalid during processing. -- code: >- - [SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Internal use only.")] - - public sealed class InvalidPolylineException : Exception, ISerializable +- code: 'public sealed class InvalidPolylineException : Exception, ISerializable' - h4: Inheritance - inheritance: - text: object @@ -71,6 +68,35 @@ body: url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Remarks - markdown: This exception is used internally to indicate that a polyline string does not conform to the expected format or contains errors. +- h2: Constructors +- api3: InvalidPolylineException() + id: PolylineAlgorithm_InvalidPolylineException__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L22 + metadata: + uid: PolylineAlgorithm.InvalidPolylineException.#ctor + commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor +- markdown: Initializes a new instance of the class. +- code: public InvalidPolylineException() +- api3: InvalidPolylineException(string, Exception) + id: PolylineAlgorithm_InvalidPolylineException__ctor_System_String_System_Exception_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L43 + metadata: + uid: PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) + commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) +- markdown: Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. +- code: public InvalidPolylineException(string message, Exception innerException) +- h4: Parameters +- parameters: + - name: message + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: The error message that explains the reason for the exception. + - name: innerException + type: + - text: Exception + url: https://learn.microsoft.com/dotnet/api/system.exception + description: The exception that is the cause of the current exception, or a null reference if no inner exception is specified. languageId: csharp metadata: description: Exception thrown when a polyline is determined to be malformed or invalid during processing. diff --git a/api-reference/1.0/PolylineAlgorithm.Polyline.yml b/api-reference/1.0/PolylineAlgorithm.Polyline.yml deleted file mode 100644 index 5da10f97..00000000 --- a/api-reference/1.0/PolylineAlgorithm.Polyline.yml +++ /dev/null @@ -1,315 +0,0 @@ -### YamlMime:ApiPage -title: Struct Polyline -body: -- api1: Struct Polyline - id: PolylineAlgorithm_Polyline - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L20 - metadata: - uid: PolylineAlgorithm.Polyline - commentId: T:PolylineAlgorithm.Polyline -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: >- - Represents an immutable, read-only encoded polyline string. - - Provides methods for creation, inspection, and conversion of polyline data from various character sources. -- code: 'public readonly struct Polyline : IEquatable' -- h4: Implements -- list: - - text: IEquatable - url: https://learn.microsoft.com/dotnet/api/system.iequatable-1 -- 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 struct is designed to be lightweight and efficient, allowing for quick comparisons and memory-safe operations. -- h2: Constructors -- api3: Polyline() - id: PolylineAlgorithm_Polyline__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L28 - metadata: - uid: PolylineAlgorithm.Polyline.#ctor - commentId: M:PolylineAlgorithm.Polyline.#ctor -- markdown: Initializes a new, empty instance of the struct. -- code: public Polyline() -- h2: Properties -- api3: IsEmpty - id: PolylineAlgorithm_Polyline_IsEmpty - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L50 - metadata: - uid: PolylineAlgorithm.Polyline.IsEmpty - commentId: P:PolylineAlgorithm.Polyline.IsEmpty -- markdown: Gets a value indicating whether this is empty. -- code: public bool IsEmpty { get; } -- h4: Property Value -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean -- api3: Length - id: PolylineAlgorithm_Polyline_Length - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L55 - metadata: - uid: PolylineAlgorithm.Polyline.Length - commentId: P:PolylineAlgorithm.Polyline.Length -- markdown: Gets the length of the polyline in characters. -- code: public long Length { get; } -- h4: Property Value -- parameters: - - type: - - text: long - url: https://learn.microsoft.com/dotnet/api/system.int64 -- h2: Methods -- api3: CopyTo(char[]) - id: PolylineAlgorithm_Polyline_CopyTo_System_Char___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L69 - metadata: - uid: PolylineAlgorithm.Polyline.CopyTo(System.Char[]) - commentId: M:PolylineAlgorithm.Polyline.CopyTo(System.Char[]) -- markdown: Copies the characters of this polyline to the specified destination array. -- code: public void CopyTo(char[] destination) -- h4: Parameters -- parameters: - - name: destination - type: - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '[' - - ']' - description: The destination array to copy the characters to. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when destination is null. - - type: - - text: ArgumentException - url: https://learn.microsoft.com/dotnet/api/system.argumentexception - description: Thrown when the length of destination does not match the polyline's length. -- api3: Equals(Polyline) - id: PolylineAlgorithm_Polyline_Equals_PolylineAlgorithm_Polyline_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L110 - metadata: - uid: PolylineAlgorithm.Polyline.Equals(PolylineAlgorithm.Polyline) - commentId: M:PolylineAlgorithm.Polyline.Equals(PolylineAlgorithm.Polyline) -- markdown: Indicates whether the current object is equal to another object of the same type. -- code: public bool Equals(Polyline other) -- h4: Parameters -- parameters: - - name: other - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: An object to compare with this object. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the current object is equal to the other parameter; otherwise, false. -- api3: Equals(object) - id: PolylineAlgorithm_Polyline_Equals_System_Object_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L119 - metadata: - uid: PolylineAlgorithm.Polyline.Equals(System.Object) - commentId: M:PolylineAlgorithm.Polyline.Equals(System.Object) -- markdown: Indicates whether this instance and a specified object are equal. -- code: public override bool Equals(object obj) -- h4: Parameters -- parameters: - - name: obj - type: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - description: The object to compare with the current instance. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if obj and this instance are the same type and represent the same value; otherwise, false. -- api3: FromCharArray(char[]) - id: PolylineAlgorithm_Polyline_FromCharArray_System_Char___ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L168 - metadata: - uid: PolylineAlgorithm.Polyline.FromCharArray(System.Char[]) - commentId: M:PolylineAlgorithm.Polyline.FromCharArray(System.Char[]) -- markdown: Creates a from a Unicode character array. -- code: public static Polyline FromCharArray(char[] polyline) -- h4: Parameters -- parameters: - - name: polyline - type: - - text: char - url: https://learn.microsoft.com/dotnet/api/system.char - - '[' - - ']' - description: A character array representing an encoded polyline. -- h4: Returns -- parameters: - - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The instance corresponding to the specified character array. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when polyline is null. -- api3: FromMemory(ReadOnlyMemory) - id: PolylineAlgorithm_Polyline_FromMemory_System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L205 - metadata: - uid: PolylineAlgorithm.Polyline.FromMemory(System.ReadOnlyMemory{System.Char}) - commentId: M:PolylineAlgorithm.Polyline.FromMemory(System.ReadOnlyMemory{System.Char}) -- markdown: Creates a from a read-only memory region of characters. -- code: public static Polyline FromMemory(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 read-only memory region representing an encoded polyline. -- h4: Returns -- parameters: - - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The instance corresponding to the specified memory region. -- api3: FromString(string) - id: PolylineAlgorithm_Polyline_FromString_System_String_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L188 - metadata: - uid: PolylineAlgorithm.Polyline.FromString(System.String) - commentId: M:PolylineAlgorithm.Polyline.FromString(System.String) -- markdown: Creates a from a string. -- code: public static Polyline FromString(string polyline) -- h4: Parameters -- parameters: - - name: polyline - type: - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - description: A string representing an encoded polyline. -- h4: Returns -- parameters: - - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The instance corresponding to the specified string. -- h4: Exceptions -- parameters: - - type: - - text: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when polyline is null. -- api3: GetHashCode() - id: PolylineAlgorithm_Polyline_GetHashCode - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L124 - metadata: - uid: PolylineAlgorithm.Polyline.GetHashCode - commentId: M:PolylineAlgorithm.Polyline.GetHashCode -- markdown: Returns the hash code for this instance. -- code: public override int GetHashCode() -- h4: Returns -- parameters: - - type: - - text: int - url: https://learn.microsoft.com/dotnet/api/system.int32 - description: A 32-bit signed integer that is the hash code for this instance. -- api3: ToString() - id: PolylineAlgorithm_Polyline_ToString - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L87 - metadata: - uid: PolylineAlgorithm.Polyline.ToString - commentId: M:PolylineAlgorithm.Polyline.ToString -- markdown: Returns a string representation of the polyline. -- code: public override string ToString() -- h4: Returns -- parameters: - - type: - - text: string - url: https://learn.microsoft.com/dotnet/api/system.string - description: A string containing the characters of the polyline, or an empty string if the polyline is empty. -- h2: Operators -- api3: operator ==(Polyline, Polyline) - id: PolylineAlgorithm_Polyline_op_Equality_PolylineAlgorithm_Polyline_PolylineAlgorithm_Polyline_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L140 - metadata: - uid: PolylineAlgorithm.Polyline.op_Equality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) - commentId: M:PolylineAlgorithm.Polyline.op_Equality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) -- markdown: Determines whether two instances are equal. -- code: public static bool operator ==(Polyline left, Polyline right) -- h4: Parameters -- parameters: - - name: left - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The first polyline to compare. - - name: right - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The second polyline to compare. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the polylines are equal; otherwise, false. -- api3: operator !=(Polyline, Polyline) - id: PolylineAlgorithm_Polyline_op_Inequality_PolylineAlgorithm_Polyline_PolylineAlgorithm_Polyline_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Polyline.cs#L150 - metadata: - uid: PolylineAlgorithm.Polyline.op_Inequality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) - commentId: M:PolylineAlgorithm.Polyline.op_Inequality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) -- markdown: Determines whether two instances are not equal. -- code: public static bool operator !=(Polyline left, Polyline right) -- h4: Parameters -- parameters: - - name: left - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The first polyline to compare. - - name: right - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The second polyline to compare. -- h4: Returns -- parameters: - - type: - - text: bool - url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the polylines are not equal; otherwise, false. -languageId: csharp -metadata: - description: >- - Represents an immutable, read-only encoded polyline string. - - Provides methods for creation, inspection, and conversion of polyline data from various character sources. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml deleted file mode 100644 index 446fdd9c..00000000 --- a/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml +++ /dev/null @@ -1,149 +0,0 @@ -### YamlMime:ApiPage -title: Class PolylineDecoder -body: -- api1: Class PolylineDecoder - id: PolylineAlgorithm_PolylineDecoder - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L11 - metadata: - uid: PolylineAlgorithm.PolylineDecoder - commentId: T:PolylineAlgorithm.PolylineDecoder -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. -- code: 'public sealed class PolylineDecoder : AbstractPolylineDecoder, IPolylineDecoder' -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: AbstractPolylineDecoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html - - text: PolylineDecoder - url: PolylineAlgorithm.PolylineDecoder.html -- h4: Implements -- list: - - text: IPolylineDecoder - url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html -- h4: Inherited Members -- list: - - text: AbstractPolylineDecoder.Options - url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Options - - text: AbstractPolylineDecoder.Decode(Polyline) - url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Decode__0_ - - 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 -- h4: Extension Methods -- list: - - text: PolylineDecoderExtensions.Decode(IPolylineDecoder, string) - url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html#PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_String_ - - text: PolylineDecoderExtensions.Decode(IPolylineDecoder, char[]) - url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html#PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_Char___ - - text: PolylineDecoderExtensions.Decode(IPolylineDecoder, ReadOnlyMemory) - url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html#PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode_PolylineAlgorithm_Abstraction_IPolylineDecoder_PolylineAlgorithm_Polyline_PolylineAlgorithm_Coordinate__System_ReadOnlyMemory_System_Char__ -- h2: Remarks -- markdown: This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats. -- h2: Constructors -- api3: PolylineDecoder() - id: PolylineAlgorithm_PolylineDecoder__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L13 - metadata: - uid: PolylineAlgorithm.PolylineDecoder.#ctor - commentId: M:PolylineAlgorithm.PolylineDecoder.#ctor -- markdown: Initializes a new instance of the class with default encoding options. -- code: public PolylineDecoder() -- api3: PolylineDecoder(PolylineEncodingOptions) - id: PolylineAlgorithm_PolylineDecoder__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L17 - metadata: - uid: PolylineAlgorithm.PolylineDecoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) - commentId: M:PolylineAlgorithm.PolylineDecoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) -- markdown: Initializes a new instance of the class with the specified encoding options. -- code: public PolylineDecoder(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: Methods -- api3: CreateCoordinate(double, double) - id: PolylineAlgorithm_PolylineDecoder_CreateCoordinate_System_Double_System_Double_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L21 - metadata: - uid: PolylineAlgorithm.PolylineDecoder.CreateCoordinate(System.Double,System.Double) - commentId: M:PolylineAlgorithm.PolylineDecoder.CreateCoordinate(System.Double,System.Double) -- markdown: Creates a coordinate instance from the given latitude and longitude values. -- code: protected override Coordinate 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 value. - - name: longitude - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The longitude value. -- h4: Returns -- parameters: - - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: A coordinate instance of type . -- api3: GetReadOnlyMemory(Polyline) - id: PolylineAlgorithm_PolylineDecoder_GetReadOnlyMemory_PolylineAlgorithm_Polyline_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L26 - metadata: - uid: PolylineAlgorithm.PolylineDecoder.GetReadOnlyMemory(PolylineAlgorithm.Polyline) - commentId: M:PolylineAlgorithm.PolylineDecoder.GetReadOnlyMemory(PolylineAlgorithm.Polyline) -- markdown: Converts the provided polyline instance into a for decoding. -- code: protected override ReadOnlyMemory GetReadOnlyMemory(Polyline polyline) -- h4: Parameters -- parameters: - - name: polyline - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: The instance containing the encoded polyline data to decode. -- 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 representing the encoded polyline data. -languageId: csharp -metadata: - description: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml deleted file mode 100644 index 2cc3a5dd..00000000 --- a/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml +++ /dev/null @@ -1,161 +0,0 @@ -### YamlMime:ApiPage -title: Class PolylineEncoder -body: -- api1: Class PolylineEncoder - id: PolylineAlgorithm_PolylineEncoder - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L11 - metadata: - uid: PolylineAlgorithm.PolylineEncoder - commentId: T:PolylineAlgorithm.PolylineEncoder -- facts: - - name: Namespace - value: - text: PolylineAlgorithm - url: PolylineAlgorithm.html - - name: Assembly - value: PolylineAlgorithm.dll -- markdown: >- - Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. -- code: 'public sealed class PolylineEncoder : AbstractPolylineEncoder, IPolylineEncoder' -- h4: Inheritance -- inheritance: - - text: object - url: https://learn.microsoft.com/dotnet/api/system.object - - text: AbstractPolylineEncoder - url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html - - text: PolylineEncoder - url: PolylineAlgorithm.PolylineEncoder.html -- h4: Implements -- list: - - text: IPolylineEncoder - url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html -- h4: Inherited Members -- list: - - text: AbstractPolylineEncoder.Options - url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Options - - text: AbstractPolylineEncoder.Encode(IEnumerable) - url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_Collections_Generic_IEnumerable__0__ - - 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 -- h4: Extension Methods -- list: - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, ICollection) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode_PolylineAlgorithm_Abstraction_IPolylineEncoder_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Polyline__System_Collections_Generic_ICollection_PolylineAlgorithm_Coordinate__ - - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, Coordinate[]) - url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode_PolylineAlgorithm_Abstraction_IPolylineEncoder_PolylineAlgorithm_Coordinate_PolylineAlgorithm_Polyline__PolylineAlgorithm_Coordinate___ -- h2: Remarks -- markdown: This abstract class serves as a base for specific polyline encoders, allowing customization of the encoding process. -- h2: Constructors -- api3: PolylineEncoder() - id: PolylineAlgorithm_PolylineEncoder__ctor - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L13 - metadata: - uid: PolylineAlgorithm.PolylineEncoder.#ctor - commentId: M:PolylineAlgorithm.PolylineEncoder.#ctor -- markdown: Initializes a new instance of the class with default encoding options. -- code: public PolylineEncoder() -- api3: PolylineEncoder(PolylineEncodingOptions) - id: PolylineAlgorithm_PolylineEncoder__ctor_PolylineAlgorithm_PolylineEncodingOptions_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L17 - metadata: - uid: PolylineAlgorithm.PolylineEncoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) - commentId: M:PolylineAlgorithm.PolylineEncoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) -- markdown: Initializes a new instance of the class with the specified encoding options. -- code: public PolylineEncoder(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: Methods -- api3: CreatePolyline(ReadOnlyMemory) - id: PolylineAlgorithm_PolylineEncoder_CreatePolyline_System_ReadOnlyMemory_System_Char__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L31 - metadata: - uid: PolylineAlgorithm.PolylineEncoder.CreatePolyline(System.ReadOnlyMemory{System.Char}) - commentId: M:PolylineAlgorithm.PolylineEncoder.CreatePolyline(System.ReadOnlyMemory{System.Char}) -- markdown: Creates a polyline instance from the provided read-only sequence of characters. -- code: protected override Polyline 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: - - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: An instance of representing the encoded polyline. -- api3: GetLatitude(Coordinate) - id: PolylineAlgorithm_PolylineEncoder_GetLatitude_PolylineAlgorithm_Coordinate_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L21 - metadata: - uid: PolylineAlgorithm.PolylineEncoder.GetLatitude(PolylineAlgorithm.Coordinate) - commentId: M:PolylineAlgorithm.PolylineEncoder.GetLatitude(PolylineAlgorithm.Coordinate) -- markdown: Extracts the latitude value from the specified coordinate. -- code: protected override double GetLatitude(Coordinate coordinate) -- h4: Parameters -- parameters: - - name: coordinate - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html -- h4: Returns -- parameters: - - type: - - text: double - url: https://learn.microsoft.com/dotnet/api/system.double - description: The latitude value as a . -- api3: GetLongitude(Coordinate) - id: PolylineAlgorithm_PolylineEncoder_GetLongitude_PolylineAlgorithm_Coordinate_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L26 - metadata: - uid: PolylineAlgorithm.PolylineEncoder.GetLongitude(PolylineAlgorithm.Coordinate) - commentId: M:PolylineAlgorithm.PolylineEncoder.GetLongitude(PolylineAlgorithm.Coordinate) -- markdown: Extracts the longitude value from the specified coordinate. -- code: protected override double GetLongitude(Coordinate coordinate) -- h4: Parameters -- parameters: - - name: coordinate - type: - - text: Coordinate - url: PolylineAlgorithm.Coordinate.html -- 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 functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml index 8b249376..127dbf53 100644 --- a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml +++ b/api-reference/1.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/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L19 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L23 metadata: uid: PolylineAlgorithm.PolylineEncoding commentId: T:PolylineAlgorithm.PolylineEncoding @@ -48,136 +48,239 @@ body: coordinates. It also provides validation utilities to ensure values conform to expected ranges for latitude and longitude. - h2: Methods -- api3: Denormalize(int, CoordinateValueType) - id: PolylineAlgorithm_PolylineEncoding_Denormalize_System_Int32_PolylineAlgorithm_CoordinateValueType_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L91 +- api3: Denormalize(int, uint) + id: PolylineAlgorithm_PolylineEncoding_Denormalize_System_Int32_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L121 metadata: - uid: PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,PolylineAlgorithm.CoordinateValueType) - commentId: M:PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,PolylineAlgorithm.CoordinateValueType) -- markdown: Converts a normalized integer value to its denormalized double representation based on the specified type. -- code: public static double Denormalize(int value, CoordinateValueType type) + uid: PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,System.UInt32) + commentId: M:PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,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) - h4: Parameters - parameters: - name: value type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: The normalized integer value to be denormalized. Must be within the valid range for the specified type. - - name: type + description: The integer value to denormalize. Typically produced by the method. + - name: precision type: - - text: CoordinateValueType - url: PolylineAlgorithm.CoordinateValueType.html - description: The type that defines the valid range for value. + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: The number of decimal places used during normalization. Default is 5, matching standard polyline encoding precision. + optional: true - h4: Returns - parameters: - type: - text: double url: https://learn.microsoft.com/dotnet/api/system.double - description: The denormalized double representation of the input value. Returns 0.0 if value is 0. + description: The denormalized floating-point coordinate value. - h4: Remarks - markdown: >- - The denormalization process divides the input value by a predefined precision factor to - produce the resulting double. Ensure that value is validated against the specified type before calling this method. +

+ + This method reverses the normalization performed by . It takes an integer value and converts it + + to a double by dividing it by 10 raised to the power of the specified precision. If precision is 0, + + the value is returned as a double without division. + +

+ +

+ + The calculation is performed inside a checked block to ensure that any arithmetic overflow is detected + + and an is thrown. + +

+ +

+ + For example, with a precision of 5: + + +

  • A value of 3778903 becomes 37.78903
  • A value of -12241230 becomes -122.4123
+ +

+ +

+ + If the input value is 0, the method returns 0.0 immediately. + +

- h4: Exceptions - parameters: - type: - - text: ArgumentOutOfRangeException - url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception - description: Thrown when value is outside the valid range for the specified type. -- api3: GetCharCount(int) - id: PolylineAlgorithm_PolylineEncoding_GetCharCount_System_Int32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L222 + - 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L297 metadata: - uid: PolylineAlgorithm.PolylineEncoding.GetCharCount(System.Int32) - commentId: M:PolylineAlgorithm.PolylineEncoding.GetCharCount(System.Int32) -- markdown: >- - Determines the number of characters required to represent the specified integer value within predefined - - variance ranges. -- code: public static int GetCharCount(int variance) + uid: PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) + commentId: M:PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) +- markdown: Calculates the number of characters required to encode a delta value in polyline format. +- code: public static int GetRequiredBufferSize(int delta) - h4: Parameters - parameters: - - name: variance + - name: delta type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 description: >- - The integer value for which the character count is calculated. Must be within the range of a 32-bit signed + The integer delta value to calculate the encoded size for. This value typically represents the difference between - integer. + consecutive coordinate values in polyline encoding. - h4: Returns - parameters: - type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: >- - The number of characters required to represent the variance value, based on its magnitude. - - Returns a value between 1 and 6 inclusive. + description: The number of characters required to encode the specified delta value. The minimum return value is 1. - h4: Remarks - markdown: >- - The method uses predefined ranges to efficiently determine the character count. Smaller +

+ + 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 + + but only calculates the required buffer size without actually writing any data. + +

+ +

+ + The calculation process: - values require fewer characters, while larger values require more. This method is optimized for performance - using a switch expression. -- api3: Normalize(double, CoordinateValueType) - id: PolylineAlgorithm_PolylineEncoding_Normalize_System_Double_PolylineAlgorithm_CoordinateValueType_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L180 +

  1. Applies zigzag encoding: left-shifts the value by 1 bit, then inverts all bits if the original value was negative
  2. Counts how many 5-bit chunks are needed to represent the encoded value
  3. Each chunk requires one character, with a minimum of 1 character for any value
+ +

+ +

+ + This method is useful for pre-allocating buffers of the correct size before encoding polyline data, helping to avoid + + buffer overflow checks during the actual encoding process. + +

+ +

+ + The method uses a long internally to prevent overflow during the left-shift operation on large negative values. + +

+- h4: See Also +- list: + - - text: PolylineEncoding + url: PolylineAlgorithm.PolylineEncoding.html + - . + - text: TryWriteValue + url: PolylineAlgorithm.PolylineEncoding.html#PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char__System_Int32__ + - ( + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + - ',' + - " " + - text: Span + url: https://learn.microsoft.com/dotnet/api/system.span-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + - ',' + - " " + - ref + - " " + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + - ) +- api3: Normalize(double, uint) + id: PolylineAlgorithm_PolylineEncoding_Normalize_System_Double_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L61 metadata: - uid: PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,PolylineAlgorithm.CoordinateValueType) - commentId: M:PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,PolylineAlgorithm.CoordinateValueType) -- markdown: Normalizes a given numeric value based on the specified type and precision settings. -- code: public static int Normalize(double value, CoordinateValueType type) + 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) - h4: Parameters - parameters: - name: value type: - text: double url: https://learn.microsoft.com/dotnet/api/system.double - description: The numeric value to normalize. Must be a finite number. - - name: type + description: The numeric value to normalize. Must be a finite number (not NaN or infinity). + - name: precision type: - - text: CoordinateValueType - url: PolylineAlgorithm.CoordinateValueType.html - description: The type against which the value is validated. Determines the acceptable range for the value. + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: >- + The number of decimal places of precision to preserve in the normalized value. + + The value is multiplied by 10^precision before rounding. + + Default is 5, which is standard for polyline encoding. + optional: true - 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. + description: An integer representing the normalized value. Returns 0 if the input value is 0.0. - h4: Remarks - markdown: >- - This method validates the input value to ensure it is finite and within the acceptable range +

+ + This method converts a floating-point coordinate value into a normalized integer by multiplying it by 10 raised + + to the power of the specified precision, then truncating the result to an integer. + +

+ +

+ + For example, with the default precision of 5: - for the specified type. If the value is valid, it applies a normalization algorithm using a predefined precision - factor. +

  • A value of 37.78903 becomes 3778903
  • A value of -122.4123 becomes -12241230
+ +

+ +

+ + The method validates that the input value is finite (not NaN or infinity) before performing normalization. + + If the precision is 0, the value is rounded without multiplication. + +

- h4: Exceptions - parameters: - type: - text: ArgumentOutOfRangeException url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception - description: >- - Thrown when value is not a finite number or is outside the valid range for the specified - - type. -- api3: TryReadValue(ref int, ref ReadOnlyMemory, ref int) - id: PolylineAlgorithm_PolylineEncoding_TryReadValue_System_Int32__System_ReadOnlyMemory_System_Char___System_Int32__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L40 + 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L168 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@) -- markdown: Attempts to read a value from the specified buffer and updates the variance. -- code: public static bool TryReadValue(ref int variance, ref ReadOnlyMemory buffer, ref int position) + 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@) +- 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) - h4: Parameters - parameters: - - name: variance + - name: delta type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: A reference to the integer that will be updated based on the value read from the buffer. + description: Reference to the integer accumulator that will be updated with the decoded value. - name: buffer type: - text: ReadOnlyMemory @@ -186,38 +289,61 @@ body: - text: char url: https://learn.microsoft.com/dotnet/api/system.char - '>' - description: A reference to the read-only memory buffer containing the data to be processed. + description: The buffer containing polyline-encoded characters. - name: position type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: A reference to the current position within the buffer. The position is incremented as the method reads data. + description: Reference to the current position in the buffer. This value is updated as characters are read. - h4: Returns - parameters: - type: - text: bool url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if a value was successfully read and the end of the buffer was not reached; otherwise, false. + description: true if a value was successfully read and decoded; false if the buffer ended before a complete value was read. - h4: Remarks - markdown: >- - This method processes the buffer starting at the specified position and attempts to decode a value. - The decoded value is used to update the variance parameter. The method stops reading when a - termination condition is met or the end of the buffer is reached. -- api3: TryWriteValue(int, ref Span, ref int) - id: PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char___System_Int32__ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L133 +

+ + This method decodes a value from a polyline-encoded character buffer, starting at the given position. It reads + + characters sequentially, applying the polyline decoding algorithm, and updates the delta with + + the decoded value. The position is advanced as characters are processed. + +

+ +

+ + The decoding process continues until a character with a value less than the algorithm's space constant is encountered, + + which signals the end of the encoded value. If the buffer is exhausted before a complete value is read, the method returns false. + +

+ +

+ + 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L236 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@) -- markdown: Attempts to write a value derived from the specified variance into the provided buffer at the given position. -- code: public static bool TryWriteValue(int variance, ref Span buffer, ref int position) + 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@) +- 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) - h4: Parameters - parameters: - - name: variance + - name: delta type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: The integer value used to calculate the output to be written into the buffer. + description: >- + The integer value to encode and write to the buffer. This value typically represents the difference between consecutive + + coordinate values in polyline encoding. - name: buffer type: - text: Span @@ -226,26 +352,175 @@ body: - text: char url: https://learn.microsoft.com/dotnet/api/system.char - '>' - description: A reference to the span of characters where the value will be written. + description: The destination buffer where the encoded characters will be written. Must have sufficient capacity to hold the encoded value. - name: position type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 description: >- - A reference to the current position in the buffer where writing begins. This value is updated to reflect the new + Reference to the current position in the buffer. This value is updated as characters are written to reflect the new position - position after writing. + after encoding is complete. - h4: Returns - parameters: - type: - text: bool url: https://learn.microsoft.com/dotnet/api/system.boolean - description: true if the value was successfully written to the buffer; otherwise, false. + description: >- + true if the value was successfully encoded and written to the buffer; false if the buffer + + does not have sufficient remaining capacity to hold the encoded value. +- h4: Remarks +- markdown: >- +

+ + This method encodes an integer delta value into a polyline-encoded format and writes it to the provided character buffer, + + starting at the given position. It applies zigzag encoding followed by the polyline encoding algorithm to represent + + both positive and negative values efficiently. + +

+ +

+ + The encoding process first converts the value using zigzag encoding (left shift by 1, with bitwise inversion for negative values), + + then writes it as a sequence of characters. Each character encodes 5 bits of data, with continuation bits indicating whether + + more characters follow. The position is advanced as characters are written. + +

+ +

+ + 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. + +

+ +

+ + 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L437 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) +- markdown: Validates the block structure of a polyline segment, ensuring each encoded value does not exceed 7 characters and the polyline ends correctly. +- code: public static void ValidateBlockLength(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. +- h4: Remarks +- markdown: >- +

+ + Iterates through the polyline, counting the length of each block (a sequence of characters representing an encoded value). + + Throws an if any block exceeds 7 characters or if the polyline does not end with a valid block terminator. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L391 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) +- markdown: Validates that all characters in the polyline segment are within the allowed ASCII range for polyline encoding. +- code: public static void ValidateCharRange(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. +- h4: Remarks +- markdown: >- +

+ + Uses SIMD vectorization for efficient validation of large spans. Falls back to scalar checks for any block where an invalid character is detected. + +

+ +

+ + The valid range is from '?' (63) to '_' (95), inclusive. If an invalid character is found, an is thrown. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + 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/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L369 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) +- markdown: Validates the format of a polyline segment, ensuring all characters are valid and block structure is correct. +- code: public static void ValidateFormat(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. - h4: Remarks - markdown: >- - This method performs bounds checking to ensure that the buffer has sufficient space to +

+ + This method performs two levels of validation on the provided polyline segment: + +

+ +
  1. + Character Range Validation: Checks that every character in the polyline is within the valid ASCII range for polyline encoding ('?' [63] to '_' [95], inclusive). + Uses SIMD acceleration for efficient validation of large segments. +
  2. + Block Structure Validation: Ensures that each encoded value (block) does not exceed 7 characters and that the polyline ends with a valid block terminator. +
+

- accommodate the calculated value. If the buffer does not have enough space, the method returns false without modifying the buffer or position. + If an invalid character or block structure is detected, an is thrown with details about the error. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when an invalid character is found or the block structure is invalid. languageId: csharp metadata: description: >- diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml index 49c71468..2e3880ce 100644 --- a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml @@ -3,7 +3,7 @@ title: Class PolylineEncodingOptions body: - api1: Class PolylineEncodingOptions id: PolylineAlgorithm_PolylineEncodingOptions - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L18 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L29 metadata: uid: PolylineAlgorithm.PolylineEncodingOptions commentId: T:PolylineAlgorithm.PolylineEncodingOptions @@ -14,7 +14,7 @@ body: url: PolylineAlgorithm.html - name: Assembly value: PolylineAlgorithm.dll -- markdown: Options for configuring polyline encoding. +- markdown: Provides configuration options for polyline encoding operations. - code: public sealed class PolylineEncodingOptions - h4: Inheritance - inheritance: @@ -37,15 +37,28 @@ body: - text: object.ToString() url: https://learn.microsoft.com/dotnet/api/system.object.tostring - h2: Remarks -- markdown: This class allows you to set options such as buffer size and logger factory for encoding operations. +- 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/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L34 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L41 metadata: uid: PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory commentId: P:PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory -- markdown: Gets or sets the precision for encoding coordinates. +- markdown: Gets the logger factory used for diagnostic logging during encoding operations. - code: public ILoggerFactory LoggerFactory { get; } - h4: Property Value - parameters: @@ -53,22 +66,62 @@ body: - 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. -- api3: MaxBufferSize - id: PolylineAlgorithm_PolylineEncodingOptions_MaxBufferSize - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L26 +- 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/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L60 metadata: - uid: PolylineAlgorithm.PolylineEncodingOptions.MaxBufferSize - commentId: P:PolylineAlgorithm.PolylineEncodingOptions.MaxBufferSize -- markdown: Gets the maximum buffer size for encoding operations. -- code: public int MaxBufferSize { get; } + 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/develop/1.0/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: The default maximum buffer size is 64,000 bytes (64 KB). This can be adjusted based on the expected size of the polyline data, but should be enough for common cases. +- 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: Options for configuring polyline encoding. + description: Provides configuration options for polyline encoding operations. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml index 91c4c2a6..92e6d942 100644 --- a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml @@ -3,7 +3,7 @@ title: Class PolylineEncodingOptionsBuilder body: - api1: Class PolylineEncodingOptionsBuilder id: PolylineAlgorithm_PolylineEncodingOptionsBuilder - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L15 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L15 metadata: uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder commentId: T:PolylineAlgorithm.PolylineEncodingOptionsBuilder @@ -15,7 +15,7 @@ body: - name: Assembly value: PolylineAlgorithm.dll - markdown: Provides a builder for configuring options for polyline encoding operations. -- code: public class PolylineEncodingOptionsBuilder +- code: public sealed class PolylineEncodingOptionsBuilder - h4: Inheritance - inheritance: - text: object @@ -32,8 +32,6 @@ body: 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() @@ -41,7 +39,7 @@ body: - h2: Methods - api3: Build() id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Build - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L37 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L38 metadata: uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build @@ -55,7 +53,7 @@ body: description: A configured instance. - api3: Create() id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Create - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L27 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L28 metadata: uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create @@ -69,11 +67,11 @@ body: 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/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L72 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L97 metadata: uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) -- markdown: Sets the logger factory for logging during encoding operations. +- markdown: Configures the to be used for logging during polyline encoding operations. - code: public PolylineEncodingOptionsBuilder WithLoggerFactory(ILoggerFactory loggerFactory) - h4: Parameters - parameters: @@ -81,46 +79,63 @@ body: type: - text: ILoggerFactory url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory - description: The instance of a logger factory. + 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 builder instance. -- h4: Exceptions + 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/develop/1.0/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: ArgumentNullException - url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception - description: Thrown when loggerFactory is null. -- api3: WithMaxBufferSize(int) - id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithMaxBufferSize_System_Int32_ - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L54 + - 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/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L61 metadata: - uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithMaxBufferSize(System.Int32) - commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithMaxBufferSize(System.Int32) -- markdown: Sets the buffer size for encoding operations. -- code: public PolylineEncodingOptionsBuilder WithMaxBufferSize(int bufferSize) + 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: bufferSize + - name: stackAllocLimit type: - text: int url: https://learn.microsoft.com/dotnet/api/system.int32 - description: The maximum buffer size. Must be greater than 11. + 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 builder instance. + 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 when bufferSize is less than or equal to 11. + 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/1.0/PolylineAlgorithm.yml b/api-reference/1.0/PolylineAlgorithm.yml index ace2f946..b60dc3c0 100644 --- a/api-reference/1.0/PolylineAlgorithm.yml +++ b/api-reference/1.0/PolylineAlgorithm.yml @@ -20,20 +20,6 @@ body: 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.html - description: >- - Decodes encoded polyline strings into sequences of geographic coordinates. - - Implements the interface. - - type: - text: PolylineEncoder - url: PolylineAlgorithm.PolylineEncoder.html - description: >- - Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. - - Implements the interface. - type: text: PolylineEncoding url: PolylineAlgorithm.PolylineEncoding.html @@ -44,28 +30,9 @@ body: - type: text: PolylineEncodingOptions url: PolylineAlgorithm.PolylineEncodingOptions.html - description: Options for configuring polyline encoding. + description: Provides configuration options for polyline encoding operations. - type: text: PolylineEncodingOptionsBuilder url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html description: Provides a builder for configuring options for polyline encoding operations. -- h3: Structs -- parameters: - - type: - text: Coordinate - url: PolylineAlgorithm.Coordinate.html - description: Represents a geographic coordinate as a pair of latitude and longitude values. - - type: - text: Polyline - url: PolylineAlgorithm.Polyline.html - description: >- - Represents an immutable, read-only encoded polyline string. - - Provides methods for creation, inspection, and conversion of polyline data from various character sources. -- h3: Enums -- parameters: - - type: - text: CoordinateValueType - url: PolylineAlgorithm.CoordinateValueType.html - description: Represents the type of a geographic coordinate value. languageId: csharp diff --git a/api-reference/1.0/toc.yml b/api-reference/1.0/toc.yml index b69f5d3f..290b7c63 100644 --- a/api-reference/1.0/toc.yml +++ b/api-reference/1.0/toc.yml @@ -5,24 +5,12 @@ - name: Classes - name: InvalidPolylineException href: PolylineAlgorithm.InvalidPolylineException.yml - - name: PolylineDecoder - href: PolylineAlgorithm.PolylineDecoder.yml - - name: PolylineEncoder - href: PolylineAlgorithm.PolylineEncoder.yml - name: PolylineEncoding href: PolylineAlgorithm.PolylineEncoding.yml - name: PolylineEncodingOptions href: PolylineAlgorithm.PolylineEncodingOptions.yml - name: PolylineEncodingOptionsBuilder href: PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml - - name: Structs - - name: Coordinate - href: PolylineAlgorithm.Coordinate.yml - - name: Polyline - href: PolylineAlgorithm.Polyline.yml - - name: Enums - - name: CoordinateValueType - href: PolylineAlgorithm.CoordinateValueType.yml - name: PolylineAlgorithm.Abstraction href: PolylineAlgorithm.Abstraction.yml items: @@ -32,9 +20,9 @@ - name: AbstractPolylineEncoder href: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml - name: Interfaces - - name: IPolylineDecoder + - name: IPolylineDecoder href: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml - - name: IPolylineEncoder + - name: IPolylineEncoder href: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml - name: PolylineAlgorithm.Extensions href: PolylineAlgorithm.Extensions.yml @@ -44,3 +32,9 @@ href: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml - name: PolylineEncoderExtensions href: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml +- name: PolylineAlgorithm.Internal.Diagnostics + href: PolylineAlgorithm.Internal.Diagnostics.yml + items: + - name: Classes + - name: ExceptionGuard + href: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml diff --git a/api-reference/api-reference.json b/api-reference/api-reference.json index 78956c32..c5797921 100644 --- a/api-reference/api-reference.json +++ b/api-reference/api-reference.json @@ -14,37 +14,21 @@ "exclude": [ "_docs/**" ] - }, - { - "dest": "", - "files": [ "*.yml" ], - "group": "v1.0", - "src": "1.0", - "rootTocPath": "~/toc.html" - }, - { - "dest": "", - "files": [ "*.yml" ], - "group": "v1.1", - "src": "1.1", - "rootTocPath": "~/toc.html" } ], "resource": [ { "files": [ - "media/**" + "media/**", + "versions.json" ] } ], - "groups": { - "v1.0": { "dest": "1.0" }, - "v1.1": { "dest": "1.1" } - }, "output": "_docs", "template": [ "default", - "modern" + "modern", + "docs-versioning" ], "maxParallelism": 1, "globalMetadata": { diff --git a/api-reference/docs-versioning/layout/_master.tmpl b/api-reference/docs-versioning/layout/_master.tmpl new file mode 100644 index 00000000..68043875 --- /dev/null +++ b/api-reference/docs-versioning/layout/_master.tmpl @@ -0,0 +1,163 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{!include(/^public/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} + + + + + {{#redirect_url}} + + {{/redirect_url}} + {{^redirect_url}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + {{#_description}}{{/_description}} + {{#description}}{{/description}} + + + + + + {{#_noindex}}{{/_noindex}} + + {{#_disableNewTab}}{{/_disableNewTab}} + {{#_disableTocFilter}}{{/_disableTocFilter}} + {{#docurl}}{{/docurl}} + + + + + + + + + + + + + + + + + + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + {{/redirect_url}} + + + {{^redirect_url}} + +
+ {{^_disableNavbar}} + + {{/_disableNavbar}} +
+ +
+ {{^_disableToc}} +
+
+
+
Table of Contents
+ +
+
+ +
+
+
+ {{/_disableToc}} + +
+
+ {{^_disableToc}} + + {{/_disableToc}} + + {{^_disableBreadcrumb}} + + {{/_disableBreadcrumb}} +
+ +
+ {{!body}} +
+ + {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} + + {{^_disableNextArticle}} + + {{/_disableNextArticle}} + +
+ + {{^_disableAffix}} +
+ +
+ {{/_disableAffix}} +
+ + {{#_enableSearch}} +
+ {{/_enableSearch}} + +
+
+
+ {{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}} +
+
+
+ + + {{/redirect_url}} + diff --git a/api-reference/docs-versioning/public/version-switcher.js b/api-reference/docs-versioning/public/version-switcher.js new file mode 100644 index 00000000..325a0531 --- /dev/null +++ b/api-reference/docs-versioning/public/version-switcher.js @@ -0,0 +1,86 @@ +(function () { + 'use strict'; + + function getVersionFromPath(pathname) { + var match = pathname.match(/\/(\d+\.\d+)\//); + return match ? match[1] : null; + } + + function getPathAfterVersion(pathname) { + var match = pathname.match(/\/\d+\.\d+\/(.*)/); + return match ? match[1] : ''; + } + + function getSiteRoot() { + var meta = document.querySelector('meta[name="docfx:rel"]'); + return meta ? meta.getAttribute('content') : './'; + } + + function initVersionPicker(versions, latest) { + var select = document.getElementById('version-picker'); + if (!select) return; + + var currentVersion = getVersionFromPath(window.location.pathname); + var relativePath = getPathAfterVersion(window.location.pathname); + + versions.forEach(function (v) { + var option = document.createElement('option'); + option.value = v; + option.textContent = v === latest ? 'v' + v + ' (latest)' : 'v' + v; + if (v === currentVersion) { + option.selected = true; + } + select.appendChild(option); + }); + + if (!currentVersion) { + var placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = 'Select version'; + placeholder.selected = true; + placeholder.disabled = true; + select.insertBefore(placeholder, select.firstChild); + } + + select.addEventListener('change', function () { + var targetVersion = select.value; + if (!targetVersion) return; + + var newPathname; + if (currentVersion && relativePath) { + newPathname = window.location.pathname.replace( + '/' + currentVersion + '/', + '/' + targetVersion + '/' + ); + } else { + newPathname = window.location.pathname.replace(/\/$/, '') + '/' + targetVersion + '/'; + } + + window.location.pathname = newPathname; + }); + } + + function loadVersions() { + var root = getSiteRoot(); + var url = root + 'versions.json'; + + fetch(url) + .then(function (r) { + if (!r.ok) throw new Error('versions.json not found'); + return r.json(); + }) + .then(function (data) { + initVersionPicker(data.versions, data.latest); + }) + .catch(function () { + var container = document.getElementById('version-picker-container'); + if (container) container.style.display = 'none'; + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', loadVersions); + } else { + loadVersions(); + } +}()); diff --git a/api-reference/guide/advanced-scenarios.md b/api-reference/guide/advanced-scenarios.md new file mode 100644 index 00000000..7414d002 --- /dev/null +++ b/api-reference/guide/advanced-scenarios.md @@ -0,0 +1,168 @@ +# Advanced Usage + +PolylineAlgorithm is designed for extensibility and integration with advanced .NET scenarios. +This guide covers custom types, integrations, and best practices for power users. + +--- + +## Custom Coordinate and Polyline Types + +You can encode and decode custom coordinate or polyline representations by extending the abstract base classes: + +- `AbstractPolylineEncoder` +- `AbstractPolylineDecoder` + +### Example: Custom Encoder + +```csharp +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> +{ + public MyPolylineEncoder() : base() { } + + public MyPolylineEncoder(PolylineEncodingOptions options) + : base(options) { } + + 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(); +} +``` + +--- + +## Example: Custom Decoder + +```csharp +public sealed class MyPolylineDecoder : AbstractPolylineDecoder +{ + public MyPolylineDecoder() : base() { } + + public MyPolylineDecoder(PolylineEncodingOptions options) + : base(options) { } + + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) + => (latitude, longitude); + + protected override ReadOnlyMemory GetReadOnlyMemory(ref string polyline) + => polyline.AsMemory(); +} +``` + +--- + +# Registering Custom Encoder and Decoder with `IServiceCollection` + +For ASP.NET Core or DI-enabled .NET applications, you can easily register your custom polyline encoder and decoder as services with `IServiceCollection` by defining an extension method. This enables constructor injection and central DI management. + +--- + +## Example: Register Custom Polyline Encoder/Decoder + +Suppose you have the following custom encoder and decoder (see [Advanced Usage](./advanced.md)): + +```csharp +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> +{ + public MyPolylineEncoder(PolylineEncodingOptions options = null) + : base(options) { } + + // ... override required members ... +} + +public sealed class MyPolylineDecoder : AbstractPolylineDecoder +{ + public MyPolylineDecoder(PolylineEncodingOptions options = null) + : base(options) { } + + // ... override required members ... +} +``` + +--- + +## IServiceCollection Extension Method + +```csharp +using Microsoft.Extensions.DependencyInjection; +using PolylineAlgorithm; + +public static class PolylineServiceCollectionExtensions +{ + public static IServiceCollection AddMyPolylineEncoderDecoder( + this IServiceCollection services, + PolylineEncodingOptions options = null) + { + // Register encoder and decoder as singletons (adjust lifetime as needed) + services.AddSingleton>( + _ => new MyPolylineEncoder(options)); + services.AddSingleton>( + _ => new MyPolylineDecoder(options)); + return services; + } +} +``` + +--- + +## Usage + +In your application startup (e.g., `Program.cs` or `Startup.cs`): + +```csharp +using PolylineAlgorithm; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddMyPolylineEncoderDecoder( + PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1024) + .Build() +); + +// Now you can inject IPolylineEncoder<(double, double), string> and IPolylineDecoder +``` + +--- + +## Benefits + +- **Central DI management** for polyline components +- Plug-and-play integration with ASP.NET Core and modern .NET project styles +- Easily swap out or configure encoders/decoders for different environments + +--- + +> **Tip:** +> You can generalize the extension method for different encoder/decoder types or include multiple algorithms by adding extra parameters. + +--- + +## Integration Guidance + +- **Batch or incremental processing:** + For large datasets, control the stack allocation limit via `PolylineEncodingOptions.StackAllocLimit`. +- **Thread safety:** + Default encoders/decoders are stateless and thread-safe. If extending for mutable types, ensure synchronization. +- **Logging:** + Integrate with .NET's `ILoggerFactory` when diagnostics or audit trails are needed. + +--- + +## Best Practices + +- Always validate input data—leverage built-in validation or extend for custom rules. +- Document all public APIs using XML comments for seamless integration with the auto-generated docs. +- For non-standard coordinate systems or precision, clearly specify semantics in your custom encoder/decoder. + +--- + +## More Resources + +- [Configuration](./configuration.md) +- [FAQ](./faq.md) +- [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) diff --git a/api-reference/guide/configuration.md b/api-reference/guide/configuration.md new file mode 100644 index 00000000..86f432fa --- /dev/null +++ b/api-reference/guide/configuration.md @@ -0,0 +1,75 @@ +# Configuration + +PolylineAlgorithm offers flexible configuration for encoding and decoding polylines, allowing you to fine-tune performance, control validation, and integrate diagnostics and logging. + +--- + +## PolylineEncodingOptions + +Most configuration is handled via the `PolylineEncodingOptions` object, which you can build using the fluent `PolylineEncodingOptionsBuilder`. + +### Example: Customizing Stack Alloc Limit + +```csharp +using PolylineAlgorithm; + +var options = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1024) // Set stack allocation threshold (bytes) + .Build(); + +var encoder = new PolylineEncoder(options); +``` + +--- + +## Logging and Diagnostics + +PolylineAlgorithm supports internal logging for advanced scenarios and diagnostic purposes. + +- Use your preferred .NET logging framework (`ILoggerFactory`) +- Attach loggers for encoding/decoding diagnostics, especially in automated or agent-based environments + +--- + +## Validation + +Input validation is always enabled by default: + +- Latitude: must be between -90 and 90 +- Longitude: must be between -180 and 180 +- Invalid or malformed coordinates throw descriptive exceptions + +For custom validation (e.g., for custom coordinate types), extend the provided interfaces or abstract base classes. + +--- + +## Advanced Configuration Options + +When using `PolylineEncodingOptionsBuilder`, you may set: + +- **Stack alloc limit:** Configure the threshold below which buffers are stack-allocated vs. rented from `ArrayPool` +- **Logging hooks:** Integrate your logger for troubleshooting/instrumentation +- **(Future)** Custom precision, additional metadata (as needed) + +See the XML API documentation for all available builder methods. + +--- + +## Example: Full Custom Encoder with Options + +```csharp +var options = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(512) + .WithLoggerFactory(myLoggerFactory) + .Build(); + +var encoder = new PolylineEncoder(options); +``` + +--- + +## Further Reading + +- [Getting Started Guide](./guide.md) +- [Advanced Usage](./advanced.md) +- [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) diff --git a/api-reference/guide/faq.md b/api-reference/guide/faq.md new file mode 100644 index 00000000..186f97aa --- /dev/null +++ b/api-reference/guide/faq.md @@ -0,0 +1,63 @@ +# FAQ + +Frequently Asked Questions for PolylineAlgorithm + +--- + +## General + +**Q: What coordinate ranges are valid?** +A: Latitude must be between -90 and 90; longitude must be between -180 and 180. Passing out-of-range values throws `ArgumentOutOfRangeException`. + +**Q: Which .NET versions are supported?** +A: Any platform supporting `netstandard2.1`, including .NET Core, .NET 5+, Xamarin, Unity, and Blazor. + +**Q: Can the library be used in Unity, Xamarin, Blazor, or other .NET-compatible platforms?** +A: Yes! Any environment that supports `netstandard2.1` can use this library. + +--- + +## Usage & Extensibility + +**Q: How do I add a new polyline algorithm or coordinate type?** +A: Implement your own encoder/decoder using `AbstractPolylineEncoder` and `AbstractPolylineDecoder`. Add unit tests and XML doc comments, then submit a PR. + +**Q: How do I customize encoding options (e.g., buffer size, logging)?** +A: Use `PolylineEncodingOptionsBuilder` to set options, and pass the result to the encoder or decoder constructor. + +**Q: Is the library thread-safe?** +A: Yes, main encoding/decoding APIs are stateless and thread-safe. If you extend using shared mutable resources, ensure proper synchronization. + +**Q: What happens if I pass invalid or malformed input to the decoder?** +A: The decoder throws descriptive exceptions for malformed polyline strings. Ensure proper exception handling in your application. + +**Q: Does the library support streaming or incremental decoding of polylines?** +A: Currently, only batch encode/decode is supported. For streaming scenarios, implement your own logic using `PolylineEncoding` utilities. + +--- + +## Features & Support + +**Q: Is there support for elevation, timestamps, or third coordinate values?** +A: Not currently, and not planned for the core library. You may implement your own encoder/decoder using `PolylineEncoding` methods for extended coordinate data. + +**Q: How do I contribute documentation improvements?** +A: Update XML doc comments in the codebase and submit a pull request. To improve guides, update relevant markdown files in `/api-reference/guide`. + +**Q: Where can I report bugs or request features?** +A: Open a GitHub issue using the provided templates and tag `@petesramek`. + +--- + +## Documentation & Community + +**Q: Where can I find detailed API documentation?** +A: [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +**Q: How do I contribute?** +A: Read [CONTRIBUTING.md](../CONTRIBUTING.md), follow coding style and testing guidelines, and use issue/PR templates. + +**Q: Need more help?** +A: Open an issue in the [GitHub repository](https://github.com/petesramek/polyline-algorithm-csharp/issues). + +--- diff --git a/api-reference/guide/getting-started.md b/api-reference/guide/getting-started.md index 8b3a7945..54d558c4 100644 --- a/api-reference/guide/getting-started.md +++ b/api-reference/guide/getting-started.md @@ -1 +1,76 @@ -# Getting Started \ No newline at end of file +# Getting Started + +PolylineAlgorithm is a lightweight, Google-compliant polyline encoding/decoding library for .NET Standard 2.1 and above. +Follow these simple steps to get started, encode and decode polylines, and configure advanced features. + +--- + +## Installation + +Install via the .NET CLI: + +```shell +dotnet add package PolylineAlgorithm +``` + +Or via NuGet Package Manager: + +```powershell +Install-Package PolylineAlgorithm +``` + +--- + +## Basic Usage + +### Encoding Coordinates + +```csharp +using PolylineAlgorithm; + +var coordinates = new List +{ + new Coordinate(48.858370, 2.294481), // Eiffel Tower + new Coordinate(51.500729, -0.124625) // Big Ben +}; + +var encoder = new PolylineEncoder(); +Polyline encoded = encoder.Encode(coordinates); + +Console.WriteLine(encoded.ToString()); // Prints the encoded polyline string +``` + +### Decoding a Polyline + +```csharp +using PolylineAlgorithm; + +var decoder = new PolylineDecoder(); +Polyline polyline = Polyline.FromString("yseiHoc_MwacOjnwM"); // Sample encoded string + +IEnumerable decoded = decoder.Decode(polyline); + +// Show decoded coordinates +foreach(var coord in decoded) +{ + Console.WriteLine($"{coord.Latitude}, {coord.Longitude}"); +} +``` + +--- + +## Customizing and Advanced Features + +- Use `PolylineEncodingOptionsBuilder` to customize settings (buffer size, logging, etc.) +- Implement custom encoder/decoder types for advanced coordinate representations +- See [API reference](https://petesramek.github.io/polyline-algorithm-csharp/) for details + +--- + +## Need More Help? + +- [FAQ](./faq.md) +- [Examples](./examples.md) +- [Report an Issue](https://github.com/petesramek/polyline-algorithm-csharp/issues) + +--- diff --git a/api-reference/guide/introduction.md b/api-reference/guide/introduction.md index f6ecaa67..a0874834 100644 --- a/api-reference/guide/introduction.md +++ b/api-reference/guide/introduction.md @@ -1 +1,33 @@ -# Introduction \ No newline at end of file +# Introduction + +Welcome to **PolylineAlgorithm for .NET**, a modern library offering Google-compliant polyline encoding and decoding with strong input validation, extensible API design, and robust performance. + +## What is PolylineAlgorithm? + +PolylineAlgorithm provides tools for encoding a sequence of geographic coordinates into a compact string format used by Google Maps and other mapping platforms, and for decoding those strings back into coordinates. Its simple API and thorough documentation make it suitable for .NET Core, .NET 5+, Xamarin, Unity, Blazor, and any framework supporting `netstandard2.1`. + +## Key Benefits + +- **Standards compliance**: Implements Google’s Encoded Polyline Algorithm as specified +- **Immutable, strongly-typed objects**: Guarantees reliability and thread safety +- **Extensible**: Custom coordinate/polyline support via abstract base classes and interfaces +- **Robust input validation**: Throws descriptive exceptions for out-of-range or malformed input +- **Configuration and logging**: Advanced options and integration with `ILoggerFactory` +- **Full API documentation**: [Auto-generated API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) +- **Benchmarked and tested**: Includes unit and performance tests + +## Who Should Use This Library? + +- .NET developers needing polyline encoding/decoding in mapping, GIS, logistics, or spatial applications +- Teams requiring reliability, easy integration, and customizable algorithms +- Anyone seeking a lightweight, modern, and maintainable polyline solution + +## How To Get Started + +- See the [Getting Started Guide](./guide.md) for installation and basic usage +- Explore [Examples](./examples.md) and [FAQ](./faq.md) for real-world scenarios and solutions +- Read about advanced [configuration](./configuration.md) and [customization](./advanced.md) + +--- + +For technical details, see the [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/). diff --git a/api-reference/guide/sample.md b/api-reference/guide/sample.md new file mode 100644 index 00000000..336bc254 --- /dev/null +++ b/api-reference/guide/sample.md @@ -0,0 +1,115 @@ +# Sample Console Application: Using NetTopologySuite with PolylineAlgorithm + +This sample demonstrates how to encode and decode polylines using custom implementations (`NetTopologyPolylineEncoder` and `NetTopologyPolylineDecoder`) based on NetTopologySuite's `Point` type. + +--- + +## Prerequisites + +- Install the following NuGet packages: + - `PolylineAlgorithm` + - `NetTopologySuite` + +```shell +dotnet add package PolylineAlgorithm +dotnet add package NetTopologySuite +``` + +--- + +## Program.cs + +```csharp +using System; +using System.Collections.Generic; +using NetTopologySuite.Geometries; +using PolylineAlgorithm.Abstraction; + +class Program +{ + static void Main() + { + // Create some sample points (latitude, longitude) + var points = new List + { + new Point(48.858370, 2.294481), // Eiffel Tower + new Point(51.500729, -0.124625) // Big Ben + }; + + // Instantiate the custom encoder + var encoder = new NetTopologyPolylineEncoder(); + + // Encode the list of points to a polyline string + string encodedPolyline = encoder.Encode(points); + + Console.WriteLine("Encoded polyline string:"); + Console.WriteLine(encodedPolyline); + + // Instantiate the custom decoder + var decoder = new NetTopologyPolylineDecoder(); + + // Decode back to NetTopologySuite Point objects + IEnumerable decodedPoints = decoder.Decode(encodedPolyline); + + Console.WriteLine("\nDecoded coordinates:"); + foreach (var point in decodedPoints) + { + Console.WriteLine($"Latitude: {point.X}, Longitude: {point.Y}"); + } + } +} + +public sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder { + protected override Point CreateCoordinate(double latitude, double longitude) { + return new Point(latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(ref string polyline) { + return polyline.AsMemory(); + } +} + +public sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder { + protected override string CreatePolyline(ReadOnlyMemory polyline) { + if (polyline.IsEmpty) { + return string.Empty; + } + + return polyline.ToString(); + } + + protected override double GetLatitude(Point current) { + // Validate parameter + + return current.X; + } + + protected override double GetLongitude(Point current) { + // Validate parameter + + return current.Y; + } +} +``` + +--- + +## Expected Output + +```text +Encoded polyline string: +{sample output will be generated at runtime} + +Decoded coordinates: +Latitude: 48.85837, Longitude: 2.294481 +Latitude: 51.500729, Longitude: -0.124625 +``` + +--- + +## Notes + +- You can further extend this pattern to use any coordinate or geometry type supported by NetTopologySuite. +- The sample demonstrates real usage of a custom `PolylineEncoder`/`PolylineDecoder` in a typical .NET application. + +--- diff --git a/api-reference/guide/toc.yml b/api-reference/guide/toc.yml index d7e9ea8c..985c0293 100644 --- a/api-reference/guide/toc.yml +++ b/api-reference/guide/toc.yml @@ -1,4 +1,12 @@ -- name: Introduction +- name: Introduction href: introduction.md - name: Getting Started - href: getting-started.md \ No newline at end of file + href: getting-started.md +- name: Configuration + href: configuration.md +- name: Advanced Scenarios + href: advanced-scenarios.md +- name: Sample + href: sample.md +- name: FAQ + href: faq.md diff --git a/api-reference/index.md b/api-reference/index.md index 5403debe..8c5b8d16 100644 --- a/api-reference/index.md +++ b/api-reference/index.md @@ -1,26 +1,18 @@ -# PolylineAlgorithm for .NET +# PolylineAlgorithm API Reference -[![Build](https://github.com/sramekpete/polyline-algorithm-csharp/actions/workflows/build.yml/badge.svg)](https://github.com/sramekpete/polyline-algorithm-csharp/actions/workflows/build.yml) -[![NuGet](https://img.shields.io/nuget/v/PolylineAlgorithm.svg)](https://www.nuget.org/packages/PolylineAlgorithm/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +Welcome! This documentation provides guides, configuration options, examples, and FAQs for the PolylineAlgorithm library. +For detailed class and method docs, see the [auto-generated API documentation](https://petesramek.github.io/polyline-algorithm-csharp/). -Lightweight .NET Standard 2.1 library implementing Google Encoded Polyline Algorithm. -More info about the algorithm can be found at [Google Developers](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). +## Contents -## Prerequisites +- [Quick Start Guide](./guide.md) +- [Configuration Options](./configuration.md) +- [Advanced Usage](./advanced.md) +- [Examples](./examples.md) +- [FAQ](./faq.md) -PolylineAlgorithm for .NET is available as a NuGet package targeting .NET Standard 2.1. +## Links -**.NET CLI** - -`dotnet add package PolylineAlgorithm` - -**Package Manager Console** - -`Install-Package PolylineAlgorithm` - -## How to use it - -In the majority of cases you would like to inherit `AbstractPolylineDecoder` and `AbstractPolylineEncoder` classes and implement abstract methods that are mainly responsible for extracting data from your coordinate and polyline types and creating new instances of them. - -In some cases you may want to implement your own decoder and encoder from scratch. In that case you can use `PolylineEncoding` static class that offers static methods for encoding and decoding polyline segments. \ No newline at end of file +- [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/) +- [Contributing Guidelines](../CONTRIBUTING.md) +- [Changelog](./changelog.md) (if provided) diff --git a/api-reference/toc.yml b/api-reference/toc.yml index 114c10f6..df40958f 100644 --- a/api-reference/toc.yml +++ b/api-reference/toc.yml @@ -1,8 +1,3 @@ ### YamlMime:TableOfContent - name: Guide - href: guide/ -- name: Reference - dropdown: true - items: - - name: v1.0 - href: 1.0/PolylineAlgorithm.html \ No newline at end of file + href: guide/ \ No newline at end of file diff --git a/api-reference/versions.json b/api-reference/versions.json new file mode 100644 index 00000000..6b9556d1 --- /dev/null +++ b/api-reference/versions.json @@ -0,0 +1,4 @@ +{ + "latest": null, + "versions": [] +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj index 2859331b..99b5011f 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj @@ -2,12 +2,12 @@ Exe - net8.0;net9.0;net10.0; - 13.0 - enable - enable - true - en + net8.0;net9.0;net10.0 + + + + pdbonly + true @@ -15,7 +15,7 @@ - + diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs deleted file mode 100644 index 9d09883b..00000000 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs +++ /dev/null @@ -1,165 +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.Benchmarks; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using PolylineAlgorithm; -using PolylineAlgorithm.Utility; - -/// -/// Benchmarks for the struct. -/// -public class PolylineBenchmark { - private static readonly Consumer consumer = new(); - - [Params(1, 100, 1_000)] - public int Count; - -#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. - /// - /// Gets the character array representing the encoded polyline. - /// - public char[] CharArrayValue { get; private set; } - - /// - /// Gets the read-only memory representing the encoded polyline. - /// - public ReadOnlyMemory MemoryValue { get; private set; } - - /// - /// Gets the read-only memory representing the encoded polyline. - /// - public Polyline PolylineValue { get; private set; } - - /// - /// Gets the string value representing the encoded polyline. - /// - public string StringValue { get; private set; } - - /// - /// Gets the read-only memory representing the encoded polyline. - /// - public Polyline PolylineNotEqualValue { get; private set; } - - public char[] CopyToDestination { get; private set; } - -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - - - /// - /// Sets up the data for the benchmarks. - /// - [GlobalSetup] - public void SetupData() { - PolylineValue = Polyline.FromString(RandomValueProvider.GetPolyline(Count)); - PolylineNotEqualValue = Polyline.FromString(RandomValueProvider.GetPolyline(Count + Random.Shared.Next(1, 101))); - StringValue = PolylineValue.ToString(); - CharArrayValue = [.. StringValue]; - MemoryValue = CharArrayValue.AsMemory(); - - CopyToDestination = new char[PolylineValue.Length]; - } - - /// - /// Benchmarks the encoding of a list of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public Polyline Polyline_FromString() { - var polyline = Polyline - .FromString(StringValue); - - return polyline; - } - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public Polyline Polyline_FromCharArray() { - var polyline = Polyline - .FromCharArray(CharArrayValue); - - return polyline; - } - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public Polyline Polyline_FromMemory() { - var polyline = Polyline - .FromMemory(MemoryValue); - - return polyline; - } - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public string Polyline_ToString() { - var stringValue = PolylineValue - .ToString(); - - return stringValue; - } - - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public void Polyline_CopyTo() { - PolylineValue - .CopyTo(CopyToDestination); - - CopyToDestination - .Consume(consumer); - } - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public bool Polyline_Equals_SameValue() { - var equals = PolylineValue - .Equals(PolylineValue); - - return equals; - } - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public bool Polyline_Equals_DifferentValue() { - var equals = PolylineValue - .Equals(PolylineNotEqualValue); - - return equals; - } - - - /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. - /// - /// The encoded polyline. - [Benchmark] - public bool Polyline_Equals_DifferentType() { - var equals = PolylineValue - .Equals(StringValue); - - return equals; - } -} \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs index 718cdd8b..f28c8141 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.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. // @@ -7,46 +7,118 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; -using PolylineAlgorithm; +using PolylineAlgorithm.Abstraction; using PolylineAlgorithm.Utility; /// -/// Benchmarks for the class. +/// Benchmarks for . /// public class PolylineDecoderBenchmark { private readonly Consumer _consumer = new(); [Params(1, 100, 1_000)] - public int Count; + 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. /// - /// Gets the string value representing the encoded polyline. + /// Encoded polyline as string. /// - public Polyline Polyline { get; private set; } + public string String { get; private set; } + + /// + /// Encoded polyline as char array. + /// + public char[] CharArray { get; private set; } + + /// + /// Encoded polyline as read-only memory. + /// + public ReadOnlyMemory Memory { get; private set; } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. /// - /// The polyline decoder instance. + /// String polyline decoder instance. /// - public PolylineDecoder Decoder = new(); + private readonly StringPolylineDecoder _stringDecoder = new(); /// - /// Sets up the data for the benchmarks. + /// Char array polyline decoder instance. + /// + private readonly CharArrayPolylineDecoder _charArrayDecoder = new(); + + /// + /// String polyline decoder instance. + /// + private readonly MemoryCharPolylineDecoder _memoryCharDecoder = new(); + + /// + /// Sets up benchmark data. /// [GlobalSetup] public void SetupData() { - Polyline = Polyline.FromString(RandomValueProvider.GetPolyline(Count)); + String = RandomValueProvider.GetPolyline(CoordinatesCount); + CharArray = RandomValueProvider.GetPolyline(CoordinatesCount).ToCharArray(); + Memory = RandomValueProvider.GetPolyline(CoordinatesCount).AsMemory(); + } + + /// + /// Benchmark: decode from string. + /// + [Benchmark] + public void PolylineDecoder_Decode_String() { + _stringDecoder + .Decode(String) + .Consume(_consumer); } /// - /// Benchmarks the decoding of a polyline from a string. + /// Benchmark: decode from char array. /// [Benchmark] - public void PolylineDecoder_Decode() { - Decoder - .Decode(Polyline) + public void PolylineDecoder_Decode_CharArray() { + _charArrayDecoder + .Decode(CharArray) .Consume(_consumer); } + + /// + /// Benchmark: decode from memory. + /// + [Benchmark] + public void PolylineDecoder_Decode_Memory() { + _memoryCharDecoder + .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 71743ca8..e0b97c5c 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.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. // @@ -6,64 +6,87 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; -using PolylineAlgorithm; +using BenchmarkDotNet.Engines; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; using PolylineAlgorithm.Utility; using System.Collections.Generic; /// -/// Benchmarks for the class. +/// Benchmarks for . /// public class PolylineEncoderBenchmark { + private readonly Consumer _consumer = new(); + + /// + /// Number of coordinates for benchmarks. + /// [Params(1, 100, 1_000)] - public int Count; + 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. /// - /// Gets the enumeration of coordinates to be encoded. + /// Coordinates as list. + /// + public List<(double Latitude, double Longitude)> List { get; private set; } + + /// + /// Coordinates as array. /// - public IEnumerable Enumeration { get; private set; } + public (double Latitude, double Longitude)[] Array { get; private set; } /// - /// Gets the list of coordinates to be encoded. + /// Coordinates as read-only memory. /// - public List List { get; private set; } + public ReadOnlyMemory<(double Latitude, double Longitude)> Memory { get; private set; } + #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. /// - /// The polyline encoder instance. + /// Polyline encoder instance. /// - public PolylineEncoder Encoder = new(); + private readonly StringPolylineEncoder _encoder = new(); /// - /// Sets up the data for the benchmarks. + /// Sets up benchmark data. /// [GlobalSetup] public void SetupData() { - Enumeration = RandomValueProvider.GetCoordinates(Count).Select(c => new Coordinate(c.Latitude, c.Longitude)); - List = [.. Enumeration]; + List = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; + Array = [.. List]; + Memory = Array.AsMemory(); } /// - /// Benchmarks the encoding of a list of coordinates into a polyline. + /// Benchmark: encode coordinates from span. /// - /// The encoded polyline. [Benchmark] - public Polyline PolylineEncoder_Encode_List() { - var polyline = Encoder - .Encode(List!); + public void PolylineEncoder_Encode_Span() { + var polyline = _encoder.Encode(Memory.Span); + _consumer.Consume(polyline); + } - return polyline; + /// + /// Benchmark: encode coordinates from array. + /// + [Benchmark] + public void PolylineEncoder_Encode_Array() { + var polyline = _encoder.Encode(Array); + _consumer.Consume(polyline); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: encode coordinates from list. /// - /// The encoded polyline. [Benchmark] - public Polyline PolylineEncoder_Encode_Enumerator() { - var polyline = Encoder - .Encode(Enumeration!); + public void PolylineEncoder_Encode_List() { + var polyline = _encoder.Encode(List); + _consumer.Consume(polyline); + } - return 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; } -} \ No newline at end of file +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs new file mode 100644 index 00000000..861ad85f --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs @@ -0,0 +1,38 @@ +// +// 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 PolylineAlgorithm.Utility; + +/// +/// Benchmarks for the polyline encoding validation methods in . +/// +public class PolylineEncodingBenchmark { +#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. + private string polyline; +#pragma warning restore CS8618 + + /// + /// Number of coordinates for benchmarks. Set by BenchmarkDotNet. + /// + [Params(8, 64, 128, 1024, 4096, 20480, 102400)] + public int CoordinatesCount { get; set; } + + [GlobalSetup] + public void Setup() { + polyline = RandomValueProvider.GetPolyline(CoordinatesCount); + } + + [Benchmark(Baseline = true)] + public void ValidateCharRange() => PolylineEncoding.ValidateCharRange(polyline); + + [Benchmark] + public void ValidateBlockLength() => PolylineEncoding.ValidateBlockLength(polyline); + + [Benchmark] + public void ValidateFormat() => PolylineEncoding.ValidateFormat(polyline); +} \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs index e1dffccb..92c38c10 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs @@ -8,13 +8,13 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Running; /// -/// The main entry point for the benchmark application. +/// Main entry point for benchmarks. /// -internal class Program { +internal static class Program { /// - /// The main method that runs the benchmarks. + /// Runs the benchmarks. /// - /// The command-line arguments. + /// Command-line arguments. static void Main(string[] args) { BenchmarkSwitcher .FromAssembly(typeof(Program).Assembly) diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs new file mode 100644 index 00000000..04094932 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs @@ -0,0 +1,8 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics.CodeAnalysis; + +[assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs index 39178369..10dc609e 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs @@ -1,8 +1,15 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. using System.Diagnostics.CodeAnalysis; +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "Benchmarks.")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks need instance methods.")] \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..fadcbe7d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +# Developer Documentation + +Welcome to the developer documentation for **PolylineAlgorithm**. This section covers everything you need to contribute code, tests, benchmarks, and automation to the project. + +## Contents + +| Document | Description | +|---|---| +| [Local Development](./local-development.md) | Build, test, and format the codebase locally | +| [Testing](./testing.md) | How to write and run unit tests | +| [Benchmarks](./benchmarks.md) | How to write and run performance benchmarks | +| [Composite Actions](./composite-actions.md) | Reusable GitHub Actions used across workflows | +| [Workflows](./workflows.md) | CI/CD pipelines and how they connect | +| [Branch Strategy](./branch-strategy.md) | Branch lifecycle and environment mapping | +| [Versioning](./versioning.md) | Branch naming convention and the version pipeline | +| [API Documentation](./api-documentation.md) | How DocFX generates and publishes the API reference site | +| [Extensibility](./extensibility.md) | How to add new encoding algorithms | + +## Quick Links + +- [Contributing Guidelines](../CONTRIBUTING.md) +- [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/) +- [GitHub Issues](https://github.com/petesramek/polyline-algorithm-csharp/issues) diff --git a/docs/api-documentation.md b/docs/api-documentation.md new file mode 100644 index 00000000..d709fe74 --- /dev/null +++ b/docs/api-documentation.md @@ -0,0 +1,136 @@ +# API Documentation + +This document explains how API documentation is generated and published for PolylineAlgorithm. + +## Toolchain + +Documentation is built with [DocFX](https://dotnet.github.io/docfx/), a static site generator for .NET API references and Markdown guides. + +## Repository Layout + +``` +api-reference/ +├── api-reference.json # DocFX build manifest (site generation) +├── assembly-metadata.json # DocFX metadata manifest (API extraction only) +├── toc.yml # Top-level table of contents +├── index.md # Landing page +├── favicon.ico +├── media/ # Images and other static assets +├── guide/ # Markdown guide articles +│ ├── toc.yml +│ ├── introduction.md +│ ├── getting-started.md +│ ├── configuration.md +│ ├── advanced-scenarios.md +│ ├── sample.md +│ └── faq.md +├── 0.0/ # Auto-generated API metadata for version 0.0 +├── 1.0/ # Auto-generated API metadata for version 1.0 +│ └── *.yml # DocFX apiPage YAML files (one per type) +└── _docs/ # Build output (excluded from DocFX content, gitignored) +``` + +## Two DocFX Manifests + +### `assembly-metadata.json` — API Extraction + +Used by the `documentation/docfx-metadata` composite action during CI builds to extract XML documentation comments from source code and produce DocFX-compatible YAML files. + +```json +{ + "metadata": [{ + "src": [{ "src": "../src", "files": ["**/*.csproj"] }], + "dest": "temp", + "outputFormat": "apiPage" + }] +} +``` + +- **Input:** All `.csproj` files under `src/` +- **Output:** YAML files in `api-reference/temp/`, then copied to `api-reference//` +- **When it runs:** Automatically after every successful `build` workflow run. The resulting YAML files are committed to the repository under `api-reference//` (e.g., `api-reference/1.2/`). + +### `api-reference.json` — Site Build + +Used by the `documentation/docfx-build` composite action to build the full documentation site. + +```json +{ + "build": { + "content": [ + { "files": ["index.md", "toc.yml", "guide/*.{md,yml}"], "exclude": ["_docs/**"] }, + { "dest": "", "files": ["*.yml"], "group": "v1.0", "src": "1.0" }, + { "dest": "", "files": ["*.yml"], "group": "v1.1", "src": "1.1" } + ], + "output": "_docs", + "template": ["default", "modern"] + } +} +``` + +- **Input:** Markdown guide articles + versioned API YAML files +- **Output:** Static HTML site in `api-reference/_docs/` +- **When it runs:** During `release.yml` (automatic on every push to `preview/**` or `release/**`) and `publish-documentation.yml` (manual trigger). + +## Publishing Flow + +``` +src/ changed + │ + ▼ +[build.yml] → docfx metadata → commit YAML to api-reference// + │ + ▼ + [release.yml] or [publish-documentation.yml] + │ + ▼ + docfx build → api-reference/_docs/ + │ + ▼ + GitHub Pages → petesramek.github.io/polyline-algorithm-csharp +``` + +## Adding a New Version to the Site + +When bumping the version to `X.Y`: + +1. The `build` workflow automatically generates metadata YAML files into `api-reference/X.Y/` after the first build on the new branch. +2. Add a new entry to `api-reference.json` under `build.content`: + ```json + { "dest": "", "files": ["*.yml"], "group": "vX.Y", "src": "X.Y", "rootTocPath": "~/toc.html" } + ``` +3. Add a matching group definition: + ```json + "vX.Y": { "dest": "X.Y" } + ``` +4. Add the new version to `api-reference/toc.yml` so it appears in the navigation dropdown: + ```yaml + - name: vX.Y + href: X.Y/PolylineAlgorithm.html + ``` + +## Writing API Documentation + +All public types, interfaces, and members must have XML doc comments. DocFX picks these up automatically: + +```csharp +/// +/// Encodes a sequence of coordinates into a polyline string. +/// +/// The coordinates to encode. +/// An encoded polyline string. +public string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) { ... } +``` + +After merging a change, verify the rendered documentation at the [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/). + +## Local Preview + +To preview the documentation locally: + +```bash +dotnet tool update -g docfx +docfx build ./api-reference/api-reference.json --serve +``` + +Then open `http://localhost:8080` in your browser. diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 00000000..208cc0e2 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,132 @@ +# Benchmarks + +This guide explains the benchmark project structure and how to write and run performance benchmarks. + +## Project Structure + +All benchmarks live in the `benchmarks/` directory: + +``` +benchmarks/ +└── PolylineAlgorithm.Benchmarks/ + ├── PolylineEncoderBenchmark.cs # Benchmarks for AbstractPolylineEncoder + ├── PolylineDecoderBenchmark.cs # Benchmarks for PolylineDecoder + ├── PolylineEncodingBenchmark.cs # Benchmarks for PolylineEncoding helpers + ├── Program.cs # BenchmarkSwitcher entry point + └── PolylineAlgorithm.Benchmarks.csproj +``` + +The project targets `net8.0`, `net9.0`, and `net10.0` and references the main `PolylineAlgorithm` library along with the `PolylineAlgorithm.Utility` helper project. + +## Framework + +Benchmarks use [BenchmarkDotNet](https://benchmarkdotnet.org/). Key packages: + +| Package | Purpose | +|---|---| +| `BenchmarkDotNet` | Core benchmarking framework | + +## Writing a New Benchmark + +1. Create a new `.cs` file in `benchmarks/PolylineAlgorithm.Benchmarks/`. +2. Add the standard copyright header. +3. Annotate the class with `[MemoryDiagnoser]` if you want allocation tracking. +4. Use `[Params]` to parameterize input sizes. +5. Mark benchmark methods with `[Benchmark]`. Mark one with `[Benchmark(Baseline = true)]` when comparing variants. +6. Use `[GlobalSetup]` to prepare shared data once per parameter combination. + +Example: + +```csharp +// +// 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; + +/// +/// Benchmarks for . +/// +[MemoryDiagnoser] +public class MyEncoderBenchmark { + private readonly Consumer _consumer = new(); + + [Params(1, 100, 1_000)] + public int CoordinatesCount { get; set; } + + private (double Latitude, double Longitude)[] _data = []; + + [GlobalSetup] + public void Setup() { + _data = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; + } + + [Benchmark(Baseline = true)] + public void EncodeArray() => new MyEncoder().Encode(_data).Consume(_consumer); +} +``` + +## Running Benchmarks Locally + +Benchmarks **must** run in Release configuration to produce meaningful results: + +```bash +dotnet run \ + --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --filter '*' +``` + +### Useful CLI flags + +| Flag | Description | +|---|---| +| `--filter '*'` | Run all benchmarks | +| `--filter '*Encoder*'` | Run benchmarks whose name contains `Encoder` | +| `--runtimes net8.0 net9.0 net10.0` | Run on multiple runtimes | +| `--exporters GitHub` | Export results as GitHub Flavored Markdown | +| `--memory` | Enable memory diagnoser output | +| `--iterationTime 100` | Iteration time in milliseconds | +| `--join` | Merge results from multiple runs | +| `--artifacts ` | Output directory for results | + +### Example: multi-runtime run with GitHub export + +```bash +dotnet run \ + --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --runtimes net8.0 net9.0 net10.0 \ + --filter '*' \ + --exporters GitHub \ + --memory \ + --iterationTime 100 \ + --join \ + --artifacts /tmp/benchmarks +``` + +## Benchmarks in CI + +The `pull-request` workflow runs benchmarks on Ubuntu, Windows, and macOS when `vars.BENCHMARKDOTNET_RUN_OVERRIDE == 'true'` or when building a release branch. Results are uploaded as artifacts (`benchmark-`) and written to the workflow step summary as a Markdown table. + +Relevant workflow variables: + +| Variable | Description | +|---|---| +| `BENCHMARKDOTNET_WORKING_DIRECTORY` | Working directory for `dotnet run` | +| `BENCHMARKDOTNET_RUNTIMES` | Space-separated runtimes to bench | +| `BENCHMARKDOTNET_FILTER` | Filter expression passed to `--filter` | +| `DEFAULT_BUILD_FRAMEWORK` | Framework used with `--framework` | +| `BENCHMARKDOTNET_RUN_OVERRIDE` | Set to `true` to force benchmark run on PRs | + +## When to Add or Update Benchmarks + +- Add a new benchmark file when introducing a new encoding/decoding code path. +- Update an existing benchmark when changing the algorithmic implementation of an existing path. +- Attach benchmark results to pull requests that affect performance-sensitive code (see [CONTRIBUTING.md](../CONTRIBUTING.md)). diff --git a/docs/branch-strategy.md b/docs/branch-strategy.md new file mode 100644 index 00000000..fb13f779 --- /dev/null +++ b/docs/branch-strategy.md @@ -0,0 +1,113 @@ +# Branch Strategy + +This document describes the branch model, the purpose of each branch type, and how a change moves from a feature branch all the way to a stable release. + +## Branch Types + +| Pattern | Purpose | Protected | +|---|---|---| +| `main` | Latest stable source of truth | ✅ Yes | +| `develop/X.Y` | Active feature development sink for version X.Y | ✅ Yes (PR only) | +| `support/X.Y` | Maintenance / backport development sink for version X.Y | ✅ Yes (PR only) | +| `feature/-` | Individual feature work, merged into `develop/X.Y` via PR | ❌ No | +| `bugfix/-` | Bug fix work, merged into `support/X.Y` via PR | ❌ No | +| `preview/X.Y` | Pre-release stabilization | ✅ Yes (1 approval required) | +| `release/X.Y` | Release stabilization | ✅ Yes (1 approval required) | + +## Change Lifecycle + +``` +1. Feature work + └─ feature/123-my-feature + │ + │ PR → develop/X.Y + │ +2. Bug fix work + └─ bugfix/124-my-fix + │ + │ PR → support/X.Y + │ +3. Promote to preview + └─ promote-branch.yml (manual) → creates preview/X.Y + PR: develop/X.Y → preview/X.Y + │ + │ PR open → [pull-request.yml]: compile, test, pack, benchmark (optional) + │ PR merged → [release.yml]: compile, test, pack, publish-NuGet (pre-release), GitHub release, docs + │ +4. Promote to release + └─ promote-branch.yml (manual) → creates release/X.Y + PR: preview/X.Y → release/X.Y + │ + │ PR open → [pull-request.yml] + │ PR merged → [release.yml]: publish-NuGet (stable), GitHub release, docs, creates support/X.Y (first time only) + │ +5. Back-merge to main (automatic, highest version only) + └─ [release.yml] creates PR: release/X.Y → main (only when X.Y is the highest release branch) + │ + │ PR merged → main is updated to the latest stable source +``` + +## Rules Per Branch Type + +### `main` + +- Represents the latest stable release — always in sync with the highest released version. +- Direct pushes are not allowed (protected). +- Updated automatically via a PR created by `release.yml` whenever the highest `release/X.Y` branch publishes a stable release. +- Serves as the baseline for version bumps: new development versions are derived from the state of `main` at the point the previous release left off. +- The `build.yml` workflow does **not** trigger on `main` pushes (branch-ignore pattern excludes `preview/**` and `release/**`, and `main` does not match `src/**` changes by default in the context of the ignore rules — check the workflow for current specifics). + +### `develop/X.Y` + +- Naming convention: `develop/.` (e.g. `develop/1.2`). +- Protected: all changes are merged via pull request from `feature/**` branches. +- The `build.yml` CI pipeline runs on every push to `src/`. +- When ready for stabilization, use `promote-branch.yml` to create a `preview/X.Y` branch and open a PR. + +### `support/X.Y` + +- Naming convention: `support/.` (e.g. `support/1.0`). +- Auto-created when the first stable release from `release/X.Y` is published. +- Protected: all changes are merged via pull request from `bugfix/**` branches. +- Can be promoted to `preview/X.Y` for a patch release. + +### `feature/-` + +- Short-lived branch for individual feature work (e.g. `feature/123-async-decoder`). +- Merged into the appropriate `develop/X.Y` via pull request. +- Not protected — deleted after merging. + +### `bugfix/-` + +- Short-lived branch for bug fixes (e.g. `bugfix/124-decode-overflow`). +- Merged into the appropriate `support/X.Y` via pull request. +- Not protected — deleted after merging. + +### `preview/X.Y` + +- Created automatically by `promote-branch.yml`. +- Locked immediately: requires at least one PR approval before any merge. +- The `pull-request.yml` workflow runs on every PR targeting this branch. +- On merge, `release.yml` publishes a **pre-release** NuGet package. +- When all pre-release validation is done, promote to `release/X.Y`. + +### `release/X.Y` + +- Created automatically by `promote-branch.yml` from `preview/X.Y`. +- Locked immediately: requires at least one PR approval. +- On merge, `release.yml` publishes a **stable** NuGet package and a GitHub release. +- After the first stable release, a corresponding `support/X.Y` branch is auto-created. +- When `X.Y` is the highest release branch, `release.yml` automatically opens a PR to merge back into `main`, keeping `main` in sync with the latest stable state. + +## Version in Branch Names + +The `X.Y` in `preview/X.Y` and `release/X.Y` drives the version pipeline. See [Versioning](./versioning.md) for details. + +## Environments + +| GitHub Environment | Used by | NuGet feed | +|---|---|---| +| `Development` | `build.yml`, `pull-request.yml` | Azure Artifacts | +| `Production` | `release.yml` | NuGet.org | + +## Locking and Unlocking Branches + +`preview/**` and `release/**` branches are locked via the [`github/branch-protection/lock`](./composite-actions.md#githubbranch-protectionlock) composite action when created. `develop/X.Y` and `support/X.Y` branches must be manually configured as protected in repository settings (PR required, no direct pushes). The [`github/branch-protection/unlock`](./composite-actions.md#githubbranch-protectionunlock) action temporarily removes protection when a workflow needs to push directly (e.g., `bump-version.yml`). Branches are always re-locked immediately after. diff --git a/docs/composite-actions.md b/docs/composite-actions.md new file mode 100644 index 00000000..66243f45 --- /dev/null +++ b/docs/composite-actions.md @@ -0,0 +1,307 @@ +# Composite Actions + +All reusable GitHub Actions live under `.github/actions/`. They are referenced with `uses: './.github/actions/'` inside workflows. This document catalogs each action, its inputs, outputs, and typical use. + +## documentation/docfx-build + +**Path:** `.github/actions/documentation/docfx-build` +**Description:** Installs the `docfx` global tool, builds a DocFX site from a JSON manifest, and uploads the generated output as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `artifact-name` | ✅ | — | Name of the uploaded artifact | +| `docfx-json-manifest` | ✅ | — | Path to the `docfx.json` build manifest | +| `output-directory` | ✅ | — | Target directory where DocFX writes output | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version to use | + +**Used by:** `publish-documentation` workflow. + +--- + +## documentation/docfx-metadata + +**Path:** `.github/actions/documentation/docfx-metadata` +**Description:** Runs `docfx metadata` to extract API metadata from source (`.yml` files), copies the result to an output directory, and uploads it as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `artifact-name` | ✅ | — | Name of the uploaded artifact | +| `docfx-json-manifest` | ✅ | — | Path to the metadata-only `docfx.json` manifest | +| `temporary-directory` | ✅ | — | Temp folder DocFX writes raw metadata into | +| `output-directory` | ✅ | — | Final output directory for the metadata | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version to use | + +**Used by:** `build` workflow (to regenerate versioned assembly metadata). + +--- + +## git/push-changes + +**Path:** `.github/actions/git/push-changes` +**Description:** Optionally downloads an artifact, stages all changes in a working directory, and pushes them to the current (or a specified target) branch. Skips the commit if there are no staged changes. + +| Input | Required | Default | Description | +|---|---|---|---| +| `commit-message` | ✅ | — | Commit message | +| `artifact-name` | ❌ | `''` | Artifact to download before staging | +| `working-directory` | ❌ | `.` | Directory to stage and commit from | +| `target-branch` | ❌ | `''` | Branch to push to (creates it if absent) | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +**Used by:** `source/format`, `build` (assembly metadata), and other workflows that commit generated artefacts. + +--- + +## github/branch-protection/lock + +**Path:** `.github/actions/github/branch-protection/lock` +**Description:** Applies branch protection rules to a branch via the GitHub API: requires at least one PR approval, disables force pushes and deletions. + +| Input | Required | Description | +|---|---|---| +| `branch` | ✅ | Branch name to protect | + +**Requires:** `administration: write` permission on the workflow token. +**Used by:** `promote-branch` workflow (after creating a new `preview/**` or `release/**` branch). + +--- + +## github/branch-protection/unlock + +**Path:** `.github/actions/github/branch-protection/unlock` +**Description:** Removes all branch protection rules from a branch so a workflow can push directly to it. Always re-lock immediately after. + +| Input | Required | Description | +|---|---|---| +| `branch` | ✅ | Branch name to unprotect | + +**Requires:** `administration: write` permission on the workflow token. +**Used by:** Workflows that need to push commits directly to protected branches (e.g., `bump-version`). + +--- + +## github/create-release + +**Path:** `.github/actions/github/create-release` +**Description:** Creates a git tag and a GitHub release with auto-generated release notes. Supports pre-release flag and a notes-start-tag for scoping the changelog. + +| Input | Required | Default | Description | +|---|---|---|---| +| `release-version` | ✅ | — | SemVer string used for both the tag and the release name | +| `is-preview` | ✅ | — | `'true'` marks the release as pre-release | +| `notes-start-tag` | ❌ | `''` | Git tag from which to start auto-generated notes | + +**Used by:** `release` workflow. + +--- + +## github/write-file-to-summary + +**Path:** `.github/actions/github/write-file-to-summary` +**Description:** Appends the contents of a file (matched by glob) to the GitHub step summary (`GITHUB_STEP_SUMMARY`). + +| Input | Required | Default | Description | +|---|---|---|---| +| `file-glob-pattern` | ✅ | — | Glob pattern for the file(s) to append | +| `working-directory` | ❌ | `${{ github.workspace }}` | Directory to resolve the glob against | + +--- + +## nuget/publish-package + +**Path:** `.github/actions/nuget/publish-package` +**Description:** Downloads a NuGet package artifact and pushes it to either a public NuGet feed or an Azure Artifacts feed. Validates the `nuget-feed-server` value before proceeding. + +| Input | Required | Default | Description | +|---|---|---|---| +| `package-artifact-name` | ✅ | — | Name of the artifact containing `.nupkg` files | +| `nuget-feed-url` | ✅ | — | Feed endpoint URL | +| `nuget-feed-api-key` | ✅ | — | API key / PAT for the feed | +| `nuget-feed-server` | ✅ | — | `'NuGet'` or `'AzureArtifacts'` | +| `dotnet-sdk-version` | ❌ | `10.x` | .NET SDK version | +| `working-directory` | ❌ | `${{ github.workspace }}` | Directory containing `.nupkg` files | + +**Used by:** `build` (Development environment) and `release` (NuGet.org) workflows. + +--- + +## source/compile + +**Path:** `.github/actions/source/compile` +**Description:** Builds a project in Release configuration, injecting version properties, and uploads the binary output as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `assembly-version` | ✅ | — | `AssemblyVersion` MSBuild property | +| `assembly-informational-version` | ✅ | — | `AssemblyInformationalVersion` MSBuild property | +| `file-version` | ✅ | — | `FileVersion` MSBuild property | +| `treat-warnins-as-error` | ✅ | — | When `'true'`, runs `dotnet format analyzers --verify-no-changes` | +| `project-path` | ✅ | — | Glob pattern for project files | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | +| `build-configuration` | ❌ | `Release` | Build configuration | +| `build-platform` | ❌ | `Any CPU` | MSBuild platform | +| `upload-build-artifacts` | ❌ | `true` | Whether to upload binary output | +| `build-artifacts-name` | ❌ | `build` | Artifact name | + +**Used by:** `build` and `pull-request` workflows. + +--- + +## source/format + +**Path:** `.github/actions/source/format` +**Description:** Runs `dotnet format whitespace`, `dotnet format style`, and optionally `dotnet format analyzers` on the codebase, then pushes any changes back to the branch via `git/push-changes`. + +| Input | Required | Default | Description | +|---|---|---|---| +| `project-path` | ✅ | — | Path or glob for the project/solution | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | +| `format-whitespace` | ❌ | `true` | Run `dotnet format whitespace` | +| `format-style` | ❌ | `true` | Run `dotnet format style` | +| `format-analyzers` | ❌ | `false` | Run `dotnet format analyzers` | +| `format-analyzers-diagnostics-parameter` | ❌ | `''` | Extra `--diagnostics` argument | + +**Used by:** `build` workflow (`format` job). + +--- + +## testing/test + +**Path:** `.github/actions/testing/test` +**Description:** Runs `dotnet test` with optional TRX logging and code coverage collection, then uploads all test result files as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `project-path` | ✅ | — | Glob pattern for test project files | +| `test-results-directory` | ❌ | `test-results` | Directory where test outputs are written | +| `code-coverage-settings-file` | ❌ | `''` | Path to the coverage settings XML | +| `use-trf-logger` | ❌ | `true` | Enable TRX logger (`--report-trx`) | +| `collect-code-coverage` | ❌ | `true` | Enable code coverage (`--coverage`) | +| `code-coverage-output-format` | ❌ | `cobertura` | Coverage output format | +| `upload-test-artifacts` | ❌ | `true` | Upload collected test result files | +| `test-artifacts-name` | ❌ | `test-results` | Artifact name | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +**Used by:** `build` and `pull-request` workflows (`test` job). + +--- + +## testing/test-report + +**Path:** `.github/actions/testing/test-report` +**Description:** Installs `LiquidTestReports.Cli` and converts `.trx` files in the test result folder into a single Markdown report. + +| Input | Required | Default | Description | +|---|---|---|---| +| `test-result-folder` | ✅ | — | Folder containing `.trx` files | +| `test-report-filename` | ❌ | `test-report.md` | Output filename | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `test-report-file` | Full path to the generated Markdown report | + +**Used by:** `build` and `pull-request` workflows (report written to step summary). + +--- + +## testing/code-coverage + +**Path:** `.github/actions/testing/code-coverage` +**Description:** Merges multiple Cobertura coverage files using `dotnet-coverage`, then generates a Markdown summary report with `reportgenerator`. + +| Input | Required | Default | Description | +|---|---|---|---| +| `test-result-folder` | ✅ | — | Folder containing `*.cobertura.xml` files | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `code-coverage-report-file` | Path to the generated `Summary.md` | +| `code-coverage-merge-file` | Path to the merged Cobertura XML file | + +**Used by:** `build` and `pull-request` workflows (report written to step summary). + +--- + +## versioning/extract-version + +**Path:** `.github/actions/versioning/extract-version` +**Description:** Extracts a `MAJOR.MINOR` version from a branch name using a configurable regex (default `(\d+).(\d+)`). Falls back to a default version if no match is found. + +| Input | Required | Default | Description | +|---|---|---|---| +| `branch-name` | ✅ | — | Branch name to parse | +| `default-version` | ❌ | `0.0` | Fallback when no version is found | +| `version-format` | ❌ | `(\d+).(\d+)` | Regex to extract the version | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `version` | Extracted `MAJOR.MINOR` string | + +**Used by:** All versioning-aware workflows. + +--- + +## versioning/format-version + +**Path:** `.github/actions/versioning/format-version` +**Description:** Produces all version strings used for .NET assembly metadata and NuGet package versions from a base `MAJOR.MINOR` version plus context inputs. + +| Input | Required | Description | +|---|---|---| +| `version` | ✅ | Base `MAJOR.MINOR` version | +| `patch` | ✅ | GitHub run number (used as patch segment) | +| `build-number` | ✅ | Commit count ahead of `main` | +| `sha` | ✅ | Commit SHA (appended to informational version) | +| `pre-release-tag` | ✅ | Pre-release label (`preview`, branch slug, or empty for stable) | + +| Output | Description | +|---|---| +| `friendly-version` | `MAJOR.MINOR` (human-readable label) | +| `assembly-version` | `MAJOR.MINOR.patch.buildNumber` | +| `assembly-informational-version` | `MAJOR.MINOR.patch+sha` | +| `file-version` | Same as `assembly-version` | +| `release-version` | `MAJOR.MINOR.patch[-preTag.buildNumber]` (NuGet version) | + +See [Versioning](./versioning.md) for the full pipeline. + +--- + +## Creating a New Composite Action + +1. Create a new directory under `.github/actions///`. +2. Add an `action.yml` file with `runs.using: composite`. +3. Declare all `inputs` with `description` and `required`. +4. Declare all `outputs` (if any) with `value` expressions referencing step outputs. +5. Keep each action focused on a single responsibility. +6. Reference optional `.NET SDK` version via an `inputs.dotnet_sdk_version` input (default `10.x`) for consistency with existing actions. +7. Use `actions/checkout@v6` as the first step when the action needs file access. + +```yaml +name: 'My Action' +author: 'Pete Sramek' +description: 'Short description of what this action does.' +inputs: + my-input: + description: 'Description of the input.' + required: true + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' +outputs: + my-output: + description: 'Description of the output.' + value: ${{ steps.my-step.outputs.my-output }} +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'My step' + id: my-step + shell: bash + run: echo "my-output=value" >> $GITHUB_OUTPUT +``` diff --git a/docs/extensibility.md b/docs/extensibility.md new file mode 100644 index 00000000..6df0d90a --- /dev/null +++ b/docs/extensibility.md @@ -0,0 +1,128 @@ +# Extensibility + +This guide explains how to use PolylineAlgorithm with your own coordinate types and polyline representations. + +## Design Overview + +The library is built around two generic abstract base classes: + +| Class | Purpose | +|---|---| +| `AbstractPolylineEncoder` | Encodes a sequence of coordinates into an encoded polyline | +| `AbstractPolylineDecoder` | Decodes an encoded polyline into a sequence of coordinates | + +Both implement corresponding interfaces (`IPolylineEncoder` and `IPolylineDecoder`). + +Type parameters: + +| Parameter | Meaning | Examples | +|---|---|---| +| `TCoordinate` | Your coordinate type | `(double Lat, double Lon)`, a custom `GeoPoint` class | +| `TPolyline` | Your polyline representation | `string`, `char[]`, `ReadOnlyMemory`, a custom wrapper | + +## Adding a Custom Encoder + +Subclass `AbstractPolylineEncoder` and implement the three abstract methods: + +| Method | Signature | What to return | +|---|---|---| +| `GetLatitude` | `double GetLatitude(TCoordinate current)` | The latitude in decimal degrees | +| `GetLongitude` | `double GetLongitude(TCoordinate current)` | The longitude in decimal degrees | +| `CreatePolyline` | `TPolyline CreatePolyline(ReadOnlyMemory polyline)` | Your output type built from the encoded char buffer | + +Example — encode `(double Latitude, double Longitude)` tuples to `string`: + +```csharp +// +// 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; + +/// +/// Encodes geographic coordinate tuples into a Google-encoded polyline string. +/// +public sealed class TuplePolylineEncoder : 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; +} +``` + +To use custom encoding options (e.g. precision 6): + +```csharp +var options = new PolylineEncodingOptionsBuilder() + .WithPrecision(6) + .Build(); + +var encoder = new TuplePolylineEncoder(options); +``` + +## Adding a Custom Decoder + +Subclass `AbstractPolylineDecoder` and implement the two abstract methods: + +| Method | Signature | What to return | +|---|---|---| +| `GetReadOnlyMemory` | `ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline)` | A `ReadOnlyMemory` view over the encoded polyline | +| `CreateCoordinate` | `TCoordinate CreateCoordinate(double latitude, double longitude)` | An instance of your coordinate type | + +Example — decode a `string` polyline into `(double Latitude, double Longitude)` tuples: + +```csharp +// +// 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; + +/// +/// Decodes a Google-encoded polyline string into geographic coordinate tuples. +/// +public sealed class TuplePolylineDecoder : AbstractPolylineDecoder { + /// + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) + => polyline.AsMemory(); + + /// + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) + => (latitude, longitude); +} +``` + +## Encoding Options + +`PolylineEncodingOptions` controls shared behavior. Configure it via `PolylineEncodingOptionsBuilder`: + +```csharp +var options = new PolylineEncodingOptionsBuilder() + .WithPrecision(5) // decimal digits (default: 5) + .WithLoggerFactory(myLoggerFactory) // enables internal logging + .Build(); +``` + +Pass the options to the constructor of any encoder or decoder: + +```csharp +var encoder = new TuplePolylineEncoder(options); +var decoder = new TuplePolylineDecoder(options); +``` + +## Extension Methods + +The library provides extension methods for `IPolylineEncoder` and `IPolylineDecoder` to support common collection types (`IEnumerable`, arrays, `ReadOnlyMemory`). These are in `PolylineAlgorithm.Extensions`. Your custom implementations automatically benefit from these extension methods as long as you implement the interfaces. diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 00000000..48fa5f76 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,74 @@ +# Local Development + +This guide explains how to build, test, and format the PolylineAlgorithm codebase locally. + +## Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download) (or newer) +- A terminal / shell + +## Building + +Build the main library using the solution file: + +```bash +dotnet build PolylineAlgorithm.slnx +``` + +To build in Release configuration (required before running tests or benchmarks): + +```bash +dotnet build PolylineAlgorithm.slnx --configuration Release +``` + +## Running Tests + +Run all unit tests: + +```bash +dotnet test ./tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj --configuration Release +``` + +> **Note:** Always use Release configuration when running tests. The Debug configuration contains a `Debug.Assert` in `AbstractPolylineEncoderTest` that will crash the test runner. + +To collect code coverage at the same time: + +```bash +dotnet test ./tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj \ + --configuration Release \ + --coverage \ + --coverage-output-format cobertura \ + --coverage-settings ./code-coverage-settings.xml +``` + +## Running Benchmarks + +See [Benchmarks](./benchmarks.md) for full details. Quick run: + +```bash +dotnet run --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --filter '*' +``` + +## Formatting + +The project uses `dotnet format` for code style enforcement. Run all format steps before committing: + +```bash +# Fix whitespace +dotnet format whitespace + +# Fix code style +dotnet format style + +# Fix analyzer warnings (optional — run when you want to fix diagnostics) +dotnet format analyzers +``` + +The CI `format` job also runs `dotnet format` automatically on every push to non-release branches and pushes the formatted result back to the branch. + +## Editor Configuration + +Code style rules are stored in `.editorconfig` at the repository root. Any compliant IDE (Visual Studio, VS Code with C# Dev Kit, Rider) will pick these up automatically. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..5b74868a --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,135 @@ +# Testing + +This guide explains the test project structure, naming conventions, and how to write new tests. + +## Project Structure + +All tests live in the `tests/` directory: + +``` +tests/ +└── PolylineAlgorithm.Tests/ + ├── Abstraction/ # Tests for abstract base classes + │ ├── AbstractPolylineDecoderTests.cs + │ └── AbstractPolylineEncoderTests.cs + ├── Internal/ # Tests for internal helpers + │ ├── CoordinateDeltaTests.cs + │ ├── Pow10Tests.cs + │ └── Diagnostics/ + ├── Extensions/ # Tests for extension methods + ├── InvalidPolylineExceptionTests.cs + ├── PolylineEncodingTests.cs + ├── PolylineEncodingOptionsBuilderTests.cs + └── PolylineAlgorithm.Tests.csproj +``` + +## Test Framework + +Tests use **MSTest** with `Microsoft.Testing.Platform` runner. The project targets `net8.0`, `net9.0`, and `net10.0`. + +Key NuGet packages: + +| Package | Purpose | +|---|---| +| `MSTest` | Test attributes and assertions | +| `Microsoft.NET.Test.Sdk` | Test runner integration | +| `Microsoft.Testing.Extensions.CodeCoverage` | Code coverage collection | +| `Microsoft.Testing.Extensions.TrxReport` | TRX report generation | +| `Microsoft.Extensions.Diagnostics.Testing` | Logging test helpers | + +## Naming Conventions + +Follow the pattern: `{Subject}_{With_Context}_{Expected_Result}` — every word separated by an underscore. + +Examples: + +``` +Decode_With_Null_Polyline_Throws_ArgumentNullException +Normalize_With_Zero_Value_Returns_Zero +Normalize_With_Value_And_Precision_Returns_Expected_Normalized_Value +``` + +## Writing a New Test Class + +1. Create a new `.cs` file in the appropriate subdirectory of `tests/PolylineAlgorithm.Tests/`. +2. Use the `[TestClass]` attribute and mark it `sealed`. +3. Add the standard copyright header (copy from an existing file). +4. Annotate every public test method with `[TestMethod]` and an XML doc comment. + +Example structure: + +```csharp +// +// 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; + +/// +/// Tests for . +/// +[TestClass] +public sealed class MyClassTests { + /// + /// Tests that returns the expected result. + /// + [TestMethod] + public void MyMethod_With_Valid_Input_Returns_Expected() { + // Arrange + var sut = new MyClass(); + + // Act + var result = sut.MyMethod("input"); + + // Assert + Assert.AreEqual("expected", result); + } +} +``` + +## Data-Driven Tests + +Use `[DataRow]` to test multiple cases in a single method: + +```csharp +[TestMethod] +[DataRow(37.78903, 5u, 3778903)] +[DataRow(-122.4123, 5u, -12241230)] +public void Normalize_With_Value_And_Precision_Returns_Expected(double value, uint precision, int expected) { + int result = PolylineEncoding.Normalize(value, precision); + Assert.AreEqual(expected, result); +} +``` + +## Testing Abstract Base Classes + +Internal test implementations are defined as private sealed classes inside the test class. This avoids polluting the public namespace: + +```csharp +[TestClass] +public sealed class AbstractPolylineDecoderTests { + private sealed class TestStringDecoder : AbstractPolylineDecoder { + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); + protected override (double, double) CreateCoordinate(double lat, double lon) => (lat, lon); + } + + [TestMethod] + public void Decode_With_Null_Polyline_Throws_ArgumentNullException() { + var decoder = new TestStringDecoder(); + Assert.ThrowsExactly(() => decoder.Decode((string?)null!).ToList()); + } +} +``` + +## Running Tests Locally + +```bash +dotnet test ./tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj --configuration Release +``` + +Always use `--configuration Release`. See [Local Development](./local-development.md) for more options including code coverage. + +## Code Coverage + +Coverage is configured via `code-coverage-settings.xml` at the repository root. The CI pipeline uses `dotnet-coverage` to merge multiple coverage files and `reportgenerator` to produce a Markdown summary posted to the workflow step summary. diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 00000000..7db7a79e --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,99 @@ +# Versioning + +This document explains how version numbers are derived from branch names and how they flow through the build pipeline. + +## Branch Naming Convention + +The version is embedded in the branch name for `preview/**` and `release/**` branches: + +| Branch | Example | Extracted version | +|---|---|---| +| `preview/X.Y` | `preview/1.2` | `1.2` | +| `release/X.Y` | `release/1.2` | `1.2` | +| Any other branch | `develop/my-feature` | `0.0` (default fallback) | + +The regex used to extract the version is `(\d+).(\d+)` (configurable via the `version-format` input of `versioning/extract-version`). + +## Version Pipeline + +Each versioning-aware workflow runs two composite actions in sequence: + +``` +Branch name + │ + ▼ +[versioning/extract-version] + │ output: version = "X.Y" + ▼ +[versioning/format-version] + │ inputs: version, patch (run number), build-number, sha, pre-release-tag + ▼ + outputs: friendly-version, assembly-version, assembly-informational-version, + file-version, release-version +``` + +### Step 1 — Extract version (`versioning/extract-version`) + +Applies the regex against the branch name. Falls back to `0.0` for non-versioned branches. + +### Step 2 — Determine pre-release tag + +Before calling `format-version`, the workflow computes a pre-release tag: + +| Branch type | Pre-release tag | +|---|---| +| `release/**` | _(empty — stable release)_ | +| `preview/**` | `preview` | +| Any other | Branch name slug (e.g. `develop-my-feature`) | + +### Step 3 — Format version (`versioning/format-version`) + +Uses `version`, `patch` (= `github.run_number`), `build-number` (commits ahead of `main`), `sha`, and `pre-release-tag` to produce: + +| Output | Formula | Example | +|---|---|---| +| `friendly-version` | `X.Y` | `1.2` | +| `assembly-version` | `X.Y.patch.buildNumber` | `1.2.42.7` | +| `assembly-informational-version` | `X.Y.patch+sha` | `1.2.42+abc1234` | +| `file-version` | `X.Y.patch.buildNumber` | `1.2.42.7` | +| `release-version` (stable) | `X.Y.patch.buildNumber` | `1.2.42.7` | +| `release-version` (pre-release) | `X.Y.patch-preTag.buildNumber` | `1.2.42-preview.7` | + +## Where Versions Are Used + +| Version string | Used as | +|---|---| +| `assembly-version` | `/p:Version` and `/p:AssemblyVersion` in `dotnet build` / `dotnet pack` | +| `assembly-informational-version` | `/p:AssemblyInformationalVersion` in `dotnet build` | +| `file-version` | `/p:FileVersion` in `dotnet build` | +| `release-version` | `/p:PackageVersion` in `dotnet pack` (the NuGet package version) | +| `friendly-version` | Human-readable label in release names, artifact names, and documentation paths | + +## Build Number Calculation + +`build-number` counts the commits on the current branch that are not on `main`: + +```bash +git fetch --unshallow --filter=tree:0 +build_number=$(git rev-list --count origin/ ^origin/main) +``` + +This means two builds from the same branch will produce different `assembly-version` and `release-version` strings only if new commits are added. + +## Examples + +Given branch `preview/1.2`, run number `55`, 3 commits ahead of `main`, and SHA `abc1234f`: + +| Output | Value | +|---|---| +| `friendly-version` | `1.2` | +| `assembly-version` | `1.2.55.3` | +| `assembly-informational-version` | `1.2.55+abc1234f` | +| `file-version` | `1.2.55.3` | +| `release-version` | `1.2.55-preview.3` | + +Given branch `release/1.2`, same inputs: + +| Output | Value | +|---|---| +| `release-version` | `1.2.55.3` _(no pre-release tag)_ | diff --git a/docs/workflows.md b/docs/workflows.md new file mode 100644 index 00000000..4c864783 --- /dev/null +++ b/docs/workflows.md @@ -0,0 +1,178 @@ +# Workflows + +This document describes all six CI/CD workflows and explains how they connect. + +## Overview + +``` +Feature branch push + │ + ▼ + [build.yml] ──── format → compile → test → pack → publish-dev + assembly metadata + │ + │ promote-branch (manual) + ▼ + preview/X.Y branch + │ + ▼ + [pull-request.yml] ──── compile → test → pack → publish-dev + benchmark (optional) + │ + │ merge PR + ▼ + [release.yml] ──── compile → test → pack → publish-NuGet → GitHub Release → documentation + │ + │ publish-documentation (manual) + ▼ + GitHub Pages (petesramek.github.io/polyline-algorithm-csharp) +``` + +Version bumping runs independently via `bump-version.yml` (manual trigger). + +--- + +## build.yml + +**Trigger:** Push to any branch except `preview/**` and `release/**`, when files under `src/` change. + +**Jobs (in order):** + +| Job | Depends on | Description | +|---|---|---| +| `workflow-variables` | — | Sets `is-release` and `is-preview` flags | +| `versioning` | — | Extracts version from branch name, builds semver strings | +| `format` | — | Runs `dotnet format` and pushes changes back to the branch | +| `build` | `versioning`, `format` | Compiles source, uploads `build` artifact | +| `test` | `build` | Runs MSTest suite, generates test + coverage reports | +| `pack` | `versioning`, `build` | Creates `.nupkg` / `.snupkg`, uploads `package` artifact | +| `publish-package` | `pack` | Pushes package to Azure Artifacts (Development environment) | +| `generate-assembly-metadata` | `versioning`, `build` | Runs `docfx metadata`, commits versioned YAML to `api-reference/` | + +**Purpose:** Continuous validation of feature branches. Also keeps the `api-reference/` directory up-to-date with every successful build. + +--- + +## pull-request.yml + +**Trigger:** Pull requests targeting `preview/**` or `release/**` branches (`opened`, `synchronize`, `reopened`). + +**Jobs (in order):** + +| Job | Depends on | Description | +|---|---|---| +| `workflow-variables` | — | Sets `is-release` and `is-preview` flags | +| `versioning` | `workflow-variables` | Extracts version from **base** branch | +| `build` | `versioning` | Compiles source | +| `test` | `build` | Runs tests, generates reports | +| `pack` | `versioning`, `build` | Packages binaries | +| `publish-development-package` | `pack` | Pushes to Azure Artifacts (Development environment) | +| `benchmark` | `build` | BenchmarkDotNet run on Ubuntu, Windows, macOS — only when `BENCHMARKDOTNET_RUN_OVERRIDE=true` or building a release branch | + +**Purpose:** Validates the change before it merges into a stabilization branch. Benchmark results are uploaded as per-OS artifacts and appended to the step summary. + +--- + +## release.yml + +**Trigger:** Push to `preview/**` or `release/**` branches when files under `src/` change (i.e., after a PR is merged). + +**Jobs (in order):** + +| Job | Depends on | Description | +|---|---|---| +| `workflow-variables` | — | Sets `is-release` / `is-preview` flags | +| `validate-release` | `workflow-variables` | Fails fast if the branch is neither `preview/**` nor `release/**` | +| `versioning` | `workflow-variables`, `validate-release` | Extracts and formats version from the branch name | +| `build` | `versioning` | Compiles source | +| `test` | `build` | Runs tests | +| `pack` | `versioning`, `build` | Packages binaries | +| `publish-package` | `pack` | Publishes to NuGet.org (Production environment) | +| `create-release` | `versioning`, `publish-package` | Creates a git tag + GitHub release with auto-generated notes | +| `generate-docs` | `versioning` | Builds DocFX site | +| `publish-docs` | `generate-docs` | Deploys to GitHub Pages | + +**Purpose:** Full release pipeline. Triggered by merging a PR into `preview/**` (pre-release) or `release/**` (stable release). + +--- + +## bump-version.yml + +**Trigger:** Manual (`workflow_dispatch`). + +**Inputs:** + +| Input | Values | Description | +|---|---|---| +| `bump-type` | `minor` / `major` | Type of version bump | + +**What it does:** + +1. Reads the current version from the default branch. +2. Increments the `MINOR` or `MAJOR` component. +3. Unlocks the target branches (via `branch-protection/unlock`), commits the new version number, and re-locks them. +4. Creates a pull request for the version bump change. + +**Purpose:** Controlled version increment without manual editing of version files. + +--- + +## promote-branch.yml + +**Trigger:** Manual (`workflow_dispatch`). + +**Inputs:** + +| Input | Values | Description | +|---|---|---| +| `promotion-type` | `preview` / `release` | Target stabilization tier | +| `base-branch` | string | Branch to branch off from (default: `main`) | + +**Validation rules:** + +- `preview` promotion requires source to be a `develop/**` or `support/**` branch. +- `release` promotion requires source to be a `preview/**` branch. +- Source and target branches must be different. +- An open PR from source → target must not already exist. + +**What it does:** + +1. Extracts the version from the current branch name. +2. Derives the target branch name (`preview/X.Y` or `release/X.Y`). +3. Creates the target branch if it does not exist, then locks it. +4. Opens a pull request from the current branch into the target branch. + +**Purpose:** Promotes code from a development branch into a stabilization branch following the [branch strategy](./branch-strategy.md). + +--- + +## publish-documentation.yml + +**Trigger:** Manual (`workflow_dispatch`). + +**Jobs:** + +| Job | Description | +|---|---| +| `workflow-variables` | Captures `github.run_number` | +| `versioning` | Extracts version from the current branch | +| `generate-docs` | Runs `docfx build` from `api-reference/api-reference.json`, uploads result to `api-reference/_docs/`, then to `github-pages` artifact | +| `publish-docs` | Deploys `github-pages` artifact to GitHub Pages | + +**Purpose:** Re-publishes the documentation site on demand, without needing a code change. Useful after updating guide articles or fixing doc rendering issues. + +--- + +## Shared Environment Variables + +All workflows share these top-level `env` defaults: + +| Variable | Value | +|---|---| +| `dotnet-sdk-version` | `10.x` | +| `build-configuration` | `Release` | +| `build-platform` | `Any CPU` | +| `test-result-directory` | `test-results` | +| `nuget-packages-directory` | `nuget-packages` | + +## Concurrency + +Each workflow uses a `concurrency` group keyed on the branch or ref to prevent parallel runs from conflicting. Most use `cancel-in-progress: false` to avoid canceling in-flight releases; only `pull-request` uses `cancel-in-progress: true` to discard stale runs quickly. diff --git a/global.json b/global.json index d2d082a0..802ab217 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ -{ +{ "test": { "runner": "Microsoft.Testing.Platform" } -} +} \ No newline at end of file diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs index 10a034d0..82899203 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.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. // @@ -10,38 +10,26 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample; using System; /// -/// Represents a polyline decoder that converts encoded polyline strings into a collection of geographic coordinates using NetTopologySuite. +/// Polyline decoder using NetTopologySuite. /// public sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder { /// - /// Creates a coordinate instance from the given latitude and longitude values. + /// Creates a NetTopologySuite point from latitude and longitude. /// - /// - /// The latitude value. - /// - /// - /// The longitude value. - /// - /// - /// A coordinate instance of type . - /// + /// Latitude value. + /// Longitude value. + /// Point instance. protected override Point CreateCoordinate(double latitude, double longitude) { - return new Point(latitude, longitude); + // NetTopologySuite Point: x = longitude, y = latitude + return new Point(longitude, latitude); } /// - /// Converts the provided polyline string into a read-only memory of characters. + /// Converts polyline string to read-only memory. /// - /// - /// The encoded polyline string to be converted into a read-only memory of characters. - /// - /// - /// A containing the characters of the polyline string. - /// - /// - /// Thrown when the provided polyline string is null, empty, or consists only of whitespace characters. - /// - protected override ReadOnlyMemory GetReadOnlyMemory(string polyline) { + /// 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 index f9e71f50..649547bd 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.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. // @@ -9,18 +9,14 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample; using PolylineAlgorithm.Abstraction; /// -/// Encodes a collection of geographic coordinates into an encoded polyline string using NetTopologySuite's Point type. +/// Polyline encoder using NetTopologySuite's Point type. /// public sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder { /// - /// Creates a string representation of the provided polyline. + /// Creates encoded polyline string from memory. /// - /// - /// The polyline to encode as a string. - /// - /// - /// An encoded polyline string representation of the provided polyline. - /// + /// Polyline memory. + /// Encoded polyline string. protected override string CreatePolyline(ReadOnlyMemory polyline) { if (polyline.IsEmpty) { return string.Empty; @@ -30,32 +26,30 @@ protected override string CreatePolyline(ReadOnlyMemory polyline) { } /// - /// Extracts the latitude value from the specified coordinate. + /// Gets latitude from point. /// - /// - /// The coordinate from which to extract the latitude value. This should be a not null instance. - /// - /// - /// The latitude value as a . - /// + /// Point instance. + /// Latitude value. protected override double GetLatitude(Point current) { - // Validate parameter + if (current is null) { + throw new ArgumentNullException(nameof(current)); + } - return current.X; + // NetTopologySuite Point: Y = latitude + return current.Y; } /// - /// Extracts the longitude value from the specified coordinate. + /// Gets longitude from point. /// - /// - /// The coordinate from which to extract the longitude value. This should be a not null instance. - /// - /// - /// The longitude value as a . - /// + /// Point instance. + /// Longitude value. protected override double GetLongitude(Point current) { - // Validate parameter + if (current is null) { + throw new ArgumentNullException(nameof(current)); + } - return current.Y; + // NetTopologySuite Point: X = longitude + return current.X; } } \ No newline at end of file diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/PolylineAlgorithm.NetTopologySuite.Sample.csproj b/samples/PolylineAlgorithm.NetTopologySuite.Sample/PolylineAlgorithm.NetTopologySuite.Sample.csproj index 6aad5fb7..57d5f7c1 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/PolylineAlgorithm.NetTopologySuite.Sample.csproj +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/PolylineAlgorithm.NetTopologySuite.Sample.csproj @@ -2,18 +2,6 @@ netstandard2.1 - 13.0 - enable - enable - true - en - - - - All - latest - true - true @@ -21,11 +9,11 @@ - + - \ No newline at end of file + diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/Properties/CodeCoverage.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/Properties/CodeCoverage.cs new file mode 100644 index 00000000..04094932 --- /dev/null +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/Properties/CodeCoverage.cs @@ -0,0 +1,8 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics.CodeAnalysis; + +[assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs index b15766b5..1059abb9 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs @@ -3,24 +3,25 @@ // 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.Logging; -using PolylineAlgorithm.Properties; -using System; -using System.Diagnostics.CodeAnalysis; +using PolylineAlgorithm.Internal.Diagnostics; +using System.Runtime.CompilerServices; + +namespace PolylineAlgorithm.Abstraction; /// -/// Decodes encoded polyline strings into sequences of geographic coordinates. -/// Implements the interface. +/// Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. /// /// -/// This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats. +/// 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. /// @@ -34,121 +35,170 @@ protected AbstractPolylineDecoder() /// The to use for encoding operations. /// /// - /// Thrown when is + /// Thrown when is . /// protected AbstractPolylineDecoder(PolylineEncodingOptions options) { - Options = options ?? throw new ArgumentNullException(nameof(options)); + if (options is null) { + ExceptionGuard.ThrowArgumentNull(nameof(options)); + } + + Options = options; + _logger = Options + .LoggerFactory + .CreateLogger>(); } /// - /// Gets the encoding options used by this polyline encoder. + /// Gets the encoding options used by this polyline decoder. /// public PolylineEncodingOptions Options { get; } /// - /// Decodes an encoded into a sequence of instances. + /// 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. /// - public IEnumerable Decode(TPolyline polyline) { - var logger = Options - .LoggerFactory - .CreateLogger>(); + /// + /// Thrown when is canceled during decoding. + /// + public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) { + const string OperationName = nameof(Decode); - logger. - LogOperationStartedInfo(nameof(Decode)); + _logger?.LogOperationStartedDebug(OperationName); - ValidateNullPolyline(polyline, logger); + ValidateNullPolyline(polyline, _logger); - ReadOnlyMemory sequence = GetReadOnlyMemory(polyline); + ReadOnlyMemory sequence = GetReadOnlyMemory(in polyline); - ValidateEmptySequence(logger, sequence); + ValidateSequence(sequence, _logger); + ValidateFormat(sequence, _logger); int position = 0; - int latitude = 0; - int longitude = 0; - - while (true) { - // Check if we have reached the end of the sequence - if (sequence.Length == position) { - break; - } + int encodedLatitude = 0; + int encodedLongitude = 0; - // Read the next value from the polyline encoding - if (!PolylineEncoding.TryReadValue(ref latitude, ref sequence, ref position) - || !PolylineEncoding.TryReadValue(ref longitude, ref sequence, ref position) - ) { - logger - .LogInvalidPolylineWarning(position); - logger. - LogOperationFailedInfo(nameof(Decode)); + try { + while (position < sequence.Length) { + cancellationToken.ThrowIfCancellationRequested(); - InvalidPolylineException.Throw(position); - } - - yield return CreateCoordinate(PolylineEncoding.Denormalize(latitude, CoordinateValueType.Latitude), PolylineEncoding.Denormalize(longitude, CoordinateValueType.Longitude)); - } + if (!PolylineEncoding.TryReadValue(ref encodedLatitude, sequence, ref position) + || !PolylineEncoding.TryReadValue(ref encodedLongitude, sequence, ref position)) { + _logger?.LogOperationFailedDebug(OperationName); + _logger?.LogInvalidPolylineWarning(position); - logger - .LogOperationFinishedInfo(nameof(Decode)); + ExceptionGuard.ThrowInvalidPolylineFormat(position); + } + double decodedLatitude = PolylineEncoding.Denormalize(encodedLatitude, Options.Precision); + double decodedLongitude = PolylineEncoding.Denormalize(encodedLongitude, Options.Precision); - static void ValidateNullPolyline(TPolyline polyline, ILogger> logger) { - if (polyline is null) { - logger - .LogNullArgumentWarning(nameof(polyline)); + _logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, position); - throw new ArgumentNullException(nameof(polyline)); + 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)); } + } - static void ValidateEmptySequence(ILogger> logger, ReadOnlyMemory sequence) { - if (sequence.Length < Defaults.Polyline.Block.Length.Min) { - logger - .LogPolylineCannotBeShorterThanWarning(nameof(sequence), sequence.Length, Defaults.Polyline.Block.Length.Min); - logger. - LogOperationFailedInfo(nameof(Decode)); + /// + /// 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); - throw new ArgumentException(string.Format(ExceptionMessageResource.PolylineCannotBeShorterThanExceptionMessage, sequence.Length), nameof(polyline)); - } + 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; } } /// - /// Converts the provided polyline instance into a for decoding. + /// Extracts the underlying read-only memory region of characters from the specified polyline instance. /// /// - /// The instance containing the encoded polyline data to decode. + /// The instance from which to extract the character sequence. /// /// - /// A representing the encoded polyline data. + /// A of representing the encoded polyline characters. /// - protected abstract ReadOnlyMemory GetReadOnlyMemory(TPolyline polyline); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected abstract ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline); /// - /// Creates a coordinate instance from the given latitude and longitude values. + /// Creates a instance from the specified latitude and longitude values. /// /// - /// The latitude value. + /// The latitude component of the coordinate, in degrees. /// /// - /// The longitude value. + /// The longitude component of the coordinate, in degrees. /// /// - /// A coordinate instance of type . + /// A instance representing the specified geographic coordinate. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract TCoordinate CreateCoordinate(double latitude, double longitude); -} \ No newline at end of file +} diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs index 6ea869bd..1bcdb0ee 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs @@ -8,21 +8,25 @@ namespace PolylineAlgorithm.Abstraction; using Microsoft.Extensions.Logging; using PolylineAlgorithm; using PolylineAlgorithm.Internal; -using PolylineAlgorithm.Internal.Logging; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; using System; -using System.Collections; -using System.Collections.Generic; +using System.Buffers; using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; /// -/// Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. -/// Implements the interface. +/// Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. /// /// -/// This abstract class serves as a base for specific polyline encoders, allowing customization of the encoding process. +/// 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. /// @@ -37,7 +41,14 @@ protected AbstractPolylineEncoder() /// /// Thrown when is protected AbstractPolylineEncoder(PolylineEncodingOptions options) { - Options = options ?? throw new ArgumentNullException(nameof(options)); + if (options is null) { + ExceptionGuard.ThrowArgumentNull(nameof(options)); + } + + Options = options; + _logger = Options + .LoggerFactory + .CreateLogger>(); } /// @@ -51,9 +62,11 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// /// The collection of objects to encode. /// + /// + /// A that can be used to cancel the encoding operation. + /// /// /// An instance of representing the encoded coordinates. - /// Returns if the input collection is empty or null. /// /// /// Thrown when is . @@ -61,129 +74,92 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// /// Thrown when is an empty enumeration. /// - public TPolyline Encode(IEnumerable coordinates) { - var logger = Options - .LoggerFactory - .CreateLogger>(); - - logger - .LogOperationStartedInfo(nameof(Encode)); - - Debug.Assert(coordinates is not null, "Coordinates cannot be null."); - - ValidateNullCoordinates(coordinates, logger); + /// + /// 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); - int count = GetCount(coordinates); + _logger + .LogOperationStartedDebug(OperationName); - Debug.Assert(count >= 0, "Count must be non-negative."); + Debug.Assert(coordinates.Length >= 0, "Count must be non-negative."); - ValidateEmptyCoordinates(logger, count); + ValidateEmptyCoordinates(ref coordinates, _logger); - CoordinateVariance variance = new(); + CoordinateDelta delta = new(); int position = 0; int consumed = 0; - int length = GetMaxBufferLength(count); + int length = GetMaxBufferLength(coordinates.Length); - Span buffer = stackalloc char[length]; + char[]? temp = length <= Options.StackAllocLimit + ? null + : ArrayPool.Shared.Rent(length); - using var enumerator = coordinates.GetEnumerator(); + Span buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length); - while (enumerator.MoveNext()) { - variance - .Next( - PolylineEncoding.Normalize(GetLatitude(enumerator.Current), CoordinateValueType.Latitude), - PolylineEncoding.Normalize(GetLongitude(enumerator.Current), CoordinateValueType.Longitude) - ); + string encodedResult; - ValidateBuffer(logger, variance, position, buffer); + try { + for (var i = 0; i < coordinates.Length; i++) { + cancellationToken.ThrowIfCancellationRequested(); - if (!PolylineEncoding.TryWriteValue(variance.Latitude, ref buffer, ref position) - || !PolylineEncoding.TryWriteValue(variance.Longitude, ref buffer, ref position) - ) { - // This shouldn't happen, but if it does, log the error and throw an exception. - logger - .LogCannotWriteValueToBufferWarning(position, consumed); - logger - .LogOperationFailedInfo(nameof(Encode)); + delta + .Next( + PolylineEncoding.Normalize(GetLatitude(coordinates[i]), Options.Precision), + PolylineEncoding.Normalize(GetLongitude(coordinates[i]), Options.Precision) + ); - throw new InvalidOperationException(ExceptionMessageResource.CouldNotWriteEncodedValueToTheBuffer); - } - - consumed++; - } + 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); - logger - .LogOperationFinishedInfo(nameof(Encode)); + ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); - return CreatePolyline(buffer[..position].ToString().AsMemory()); + } - static int GetCount(IEnumerable coordinates) => coordinates switch { - ICollection collection => collection.Count, - _ => coordinates.Count(), - }; + consumed++; + } - static int GetRequiredLength(CoordinateVariance variance) => - PolylineEncoding.GetCharCount(variance.Latitude) + PolylineEncoding.GetCharCount(variance.Longitude); + encodedResult = buffer[..position].ToString(); + } finally { + if (temp is not null) { + ArrayPool.Shared.Return(temp); + } + } - static int GetRemainingBufferSize(int position, int length) { - Debug.Assert(length > 0, "Buffer length must be greater than zero."); - Debug.Assert(position >= 0, "Position must be non-negative."); - Debug.Assert(position < length, "Position must be less than buffer length."); - Debug.Assert(length - position >= 0, "Remaining length must be non-negative."); + _logger + .LogOperationFinishedDebug(OperationName); - return length - position; - } + return CreatePolyline(encodedResult.AsMemory()); - int GetMaxBufferLength(int count) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int GetMaxBufferLength(int count) { Debug.Assert(count > 0, "Count must be greater than zero."); - int requestedBufferLength = count * Defaults.Polyline.Block.Length.Max; + int requestedBufferLength = count * 2 * Defaults.Polyline.Block.Length.Max; - Debug.Assert(Options.MaxBufferLength > 0, "Max buffer length must be greater than zero."); Debug.Assert(requestedBufferLength > 0, "Requested buffer length must be greater than zero."); - if (requestedBufferLength > Options.MaxBufferLength) { - logger - .LogRequestedBufferSizeExceedsMaxBufferLengthWarning(requestedBufferLength, Options.MaxBufferLength); - - return Options.MaxBufferLength; - } - return requestedBufferLength; } - static void ValidateNullCoordinates(IEnumerable coordinates, ILogger> logger) { - if (coordinates is null) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ValidateEmptyCoordinates(ref ReadOnlySpan coordinates, ILogger logger) { + if (coordinates.Length < 1) { logger - .LogNullArgumentWarning(nameof(coordinates)); - logger - .LogOperationFailedInfo(nameof(Encode)); - - throw new ArgumentNullException(nameof(coordinates)); - } - } - - static void ValidateEmptyCoordinates(ILogger> logger, int count) { - if (count < 1) { + .LogOperationFailedDebug(OperationName); logger .LogEmptyArgumentWarning(nameof(coordinates)); - logger - .LogOperationFailedInfo(nameof(Encode)); - throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeEmptyEnumerationMessage, nameof(coordinates)); - } - } - - static void ValidateBuffer(ILogger> logger, CoordinateVariance variance, int position, Span buffer) { - if (GetRemainingBufferSize(position, buffer.Length) < GetRequiredLength(variance)) { - logger - .LogInternalBufferOverflowWarning(position, buffer.Length, GetRequiredLength(variance)); - logger - .LogOperationFailedInfo(nameof(Encode)); - - - throw new InternalBufferOverflowException(); + ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); } } } @@ -195,7 +171,7 @@ static void ValidateBuffer(ILogger /// An instance of representing the encoded polyline. /// - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract TPolyline CreatePolyline(ReadOnlyMemory polyline); /// @@ -205,7 +181,7 @@ static void ValidateBuffer(ILogger /// The longitude value as a . /// - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract double GetLongitude(TCoordinate current); /// @@ -215,7 +191,7 @@ static void ValidateBuffer(ILogger /// 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 363601ee..114f88fd 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.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. // @@ -6,19 +6,44 @@ namespace PolylineAlgorithm.Abstraction; using System.Collections.Generic; +using System.Threading; /// /// Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. /// -public interface IPolylineDecoder { +/// +/// The type that represents the encoded polyline input. Common implementations use , +/// but custom wrapper types are allowed to carry additional metadata. +/// +/// +/// 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 { /// - /// Decodes the specified encoded polyline into a sequence of geographic coordinates. + /// Decodes the specified encoded polyline into an ordered sequence of geographic coordinates. + /// The sequence preserves the original vertex order encoded by the . /// /// - /// The instance containing the encoded polyline string to decode. + /// The instance containing the encoded polyline to decode. + /// Implementations SHOULD validate the input and may throw + /// or for invalid formats. + /// + /// + /// A to observe while decoding. If cancellation is requested, + /// implementations SHOULD stop work and throw an . /// /// - /// An of representing the decoded latitude and longitude pairs. + /// An of representing the decoded + /// latitude/longitude pairs (or equivalent coordinates) in the same order they were encoded. /// - IEnumerable Decode(TPolyline polyline); -} + /// + /// Implementations commonly follow the Google Encoded Polyline Algorithm Format, but this interface + /// does not mandate a specific encoding. Consumers should rely on a concrete decoder's documentation + /// to understand the exact encoding supported. + /// + /// + /// Thrown when the provided requests cancellation. + /// + IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs index 8febc1bf..9cea260b 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs @@ -1,24 +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.Collections.Generic; - /// -/// Defines a contract for encoding a sequence of geographic coordinates into an encoded polyline string. +/// Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. +/// Implementations interpret the generic type and produce an encoded +/// representation of those coordinates as . /// -public interface IPolylineEncoder { +/// +/// The concrete coordinate representation used by the encoder (for example a struct or class containing +/// Latitude and Longitude values). Implementations must document the expected shape, +/// units (typically decimal degrees), and any required fields for . +/// Common shapes: +/// - A struct or class with two properties named Latitude and Longitude. +/// - A tuple-like type (for example ValueTuple<double,double>) where the encoder documents +/// which element represents latitude and longitude. +/// +/// +/// The encoded polyline representation returned by the encoder (for example , +/// ReadOnlyMemory<char>, or a custom wrapper type). Concrete implementations should document +/// the chosen representation and any memory / ownership expectations. +/// +/// +/// - This interface is intentionally minimal to allow different encoding strategies (Google encoded polyline, +/// precision/scale variants, or custom compressed formats) to be expressed behind a common contract. +/// - Implementations should document: +/// - Coordinate precision and rounding rules (for example 1e-5 for 5-decimal precision). +/// - Coordinate ordering and whether altitude or additional dimensions are supported. +/// - Thread-safety guarantees: whether instances are safe to reuse concurrently or must be instantiated per-call. +/// - Implementations are encouraged to be memory-efficient; the API accepts a +/// to avoid forced allocations when callers already have contiguous memory. +/// +public interface IPolylineEncoder { /// /// Encodes a sequence of geographic coordinates into an encoded polyline representation. + /// The order of coordinates in is preserved in the encoded result. /// /// - /// The collection of instances to encode into a polyline. + /// The collection of instances to encode into a polyline. + /// 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 . + /// + /// + /// A that can be used to cancel the encoding operation. + /// Implementations should observe this token and throw + /// when cancellation is requested. For fast, in-memory encoders cancellation may be best-effort. /// /// - /// A containing the encoded polyline string that represents the input coordinates. + /// A containing the encoded polyline that represents the input coordinates. + /// The exact format and any delimiting/terminating characters are implementation-specific and must be + /// documented by concrete encoder types. /// - TPolyline Encode(IEnumerable coordinates); -} + /// + /// + /// // Example pseudocode for typical usage with a string-based encoder: + /// var coords = new[] { + /// new Coordinate { Latitude = 47.6219, Longitude = -122.3503 }, + /// new Coordinate { Latitude = 47.6220, Longitude = -122.3504 } + /// }; + /// IPolylineEncoder<Coordinate,string> encoder = new GoogleEncodedPolylineEncoder(); + /// string encoded = encoder.Encode(coords, CancellationToken.None); + /// + /// + /// + /// - Implementations should validate input as appropriate and document any preconditions (for example + /// if coordinates must be within [-90,90] latitude and [-180,180] longitude). + /// - For large input sequences, implementations may provide streaming or incremental encoders; those + /// variants can still implement this interface by materializing the final encoded result. + /// + /// + /// Thrown if the operation is canceled via . + /// + TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Coordinate.cs b/src/PolylineAlgorithm/Coordinate.cs deleted file mode 100644 index 43156f29..00000000 --- a/src/PolylineAlgorithm/Coordinate.cs +++ /dev/null @@ -1,132 +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 PolylineAlgorithm.Properties; -using System; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.InteropServices; - -/// -/// Represents a geographic coordinate as a pair of latitude and longitude values. -/// -/// -/// This struct is designed to be immutable and lightweight, providing a simple way to represent -/// geographic coordinates in degrees. It includes validation for latitude and longitude ranges -/// and provides methods for equality comparison and string representation. -/// -[DebuggerDisplay("{ToString()}")] -[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] -public readonly struct Coordinate : IEquatable { - /// - /// Initializes a new instance of the struct with default values (0) for and . - /// - public Coordinate() { - Latitude = default; - Longitude = default; - } - - /// - /// Initializes a new instance of the struct with the specified latitude and longitude values. - /// - /// - /// The latitude component of the coordinate, in degrees. Must be between -90 and 90. - /// - /// - /// The longitude component of the coordinate, in degrees. Must be between -180 and 180. - /// - /// - /// Thrown when is less than -90 or greater than 90, - /// or when is less than -180 or greater than 180. - /// - public Coordinate(double latitude, double longitude) { - if (latitude < -90 || latitude > 90 || double.IsNaN(latitude) || double.IsInfinity(latitude)) { - throw new ArgumentOutOfRangeException(nameof(latitude), string.Format(ExceptionMessageResource.CoordinateValueMustBeBetweenValuesMessageFormat, "Latitude", -90, 90)); - } - - if (longitude < -180 || longitude > 180 || double.IsNaN(longitude) || double.IsInfinity(longitude)) { - throw new ArgumentOutOfRangeException(nameof(longitude), string.Format(ExceptionMessageResource.CoordinateValueMustBeBetweenValuesMessageFormat, "Longitude", -180, 180)); - } - - Latitude = latitude; - Longitude = longitude; - } - - /// - /// Gets the latitude component of the coordinate, in degrees. - /// - public double Latitude { get; } - - /// - /// Gets the longitude component of the coordinate, in degrees. - /// - public double Longitude { get; } - - /// - /// Determines whether this coordinate is the default value (both and are 0). - /// - /// - /// if both latitude and longitude are 0; otherwise, . - /// - public bool IsDefault() - => Latitude == default - && Longitude == default; - - #region Overrides - - /// - public override bool Equals(object? obj) => obj is Coordinate coordinate && Equals(coordinate); - - /// - public override int GetHashCode() => HashCode.Combine(Latitude, Longitude); - - /// - /// Returns a string representation of this coordinate in the format: { Latitude: [double], Longitude: [double] }. - /// - /// - /// A string representation of the coordinate. - /// - public override string ToString() { - return $"{{ {nameof(Latitude)}: {Latitude.ToString("G", CultureInfo.InvariantCulture)}, {nameof(Longitude)}: {Longitude.ToString("G", CultureInfo.InvariantCulture)} }}"; - } - - #endregion - - #region IEquatable implementation - - /// - public bool Equals(Coordinate other) { - return Latitude == other.Latitude && - Longitude == other.Longitude; - } - - #endregion - - #region Equality operators - - /// - /// Determines whether two instances are equal. - /// - /// The first coordinate to compare. - /// The second coordinate to compare. - /// - /// if both coordinates are equal; otherwise, . - /// - public static bool operator ==(Coordinate left, Coordinate right) => left.Equals(right); - - /// - /// Determines whether two instances are not equal. - /// - /// The first coordinate to compare. - /// The second coordinate to compare. - /// - /// if the coordinates are not equal; otherwise, . - /// - public static bool operator !=(Coordinate left, Coordinate right) => !(left == right); - - #endregion -} diff --git a/src/PolylineAlgorithm/CoordinateValueType.cs b/src/PolylineAlgorithm/CoordinateValueType.cs deleted file mode 100644 index f576e8dd..00000000 --- a/src/PolylineAlgorithm/CoordinateValueType.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace PolylineAlgorithm; - -/// -/// Represents the type of a geographic coordinate value. -/// -/// -/// This enumeration is used to specify whether a coordinate value represents latitude or -/// longitude. Latitude values indicate the north-south position, while longitude values indicate the east-west -/// position. -/// -public enum CoordinateValueType : int { - /// - /// Represents no specific type. This value is used when the type is not applicable or not specified. - /// - None = 0, - /// - /// Represents a latitude value. - /// - Latitude = 1, - /// - /// Represents a longitude value. - /// - Longitude = 2 -} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs index 403b94ea..ca48e099 100644 --- a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs +++ b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs @@ -1,85 +1,97 @@ -// +// // 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; using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Internal.Diagnostics; using System; using System.Collections.Generic; /// -/// Provides extension methods for the interface to facilitate decoding encoded polylines. +/// Provides extension methods for the interface to facilitate decoding encoded polylines. /// public static class PolylineDecoderExtensions { /// - /// Decodes an encoded polyline string into a sequence of geographic coordinates. + /// 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 instance used to perform the decoding operation. /// /// - /// The encoded polyline string to decode. + /// The encoded polyline as a character array to decode. The array is converted to a string internally. /// /// - /// An containing the decoded latitude and longitude pairs. + /// An of containing the decoded coordinate pairs. /// /// - /// Thrown when is . + /// Thrown when or is . /// - public static IEnumerable Decode(this IPolylineDecoder decoder, string polyline) { + public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) { if (decoder is null) { - throw new ArgumentNullException(nameof(decoder)); + ExceptionGuard.ThrowArgumentNull(nameof(decoder)); } - return decoder.Decode(Polyline.FromString(polyline)); + if (polyline is null) { + ExceptionGuard.ThrowArgumentNull(nameof(polyline)); + } + + return decoder.Decode(new string(polyline)); } /// - /// Decodes an encoded polyline represented as a character array into a sequence of geographic coordinates. + /// 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 instance used to perform the decoding operation. /// /// - /// The encoded polyline as a character array to decode. + /// The encoded polyline as a read-only memory of characters to decode. The memory is converted to a string internally. /// /// - /// An containing the decoded latitude and longitude pairs. + /// An of containing the decoded coordinate pairs. /// /// /// Thrown when is . /// - public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) { + public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) { if (decoder is null) { - throw new ArgumentNullException(nameof(decoder)); + ExceptionGuard.ThrowArgumentNull(nameof(decoder)); } - return decoder.Decode(Polyline.FromCharArray(polyline)); + return decoder.Decode(polyline.ToString()); } /// - /// Decodes an encoded polyline represented as a read-only memory of characters into a sequence of geographic coordinates. + /// 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 instance used to perform the decoding operation. /// /// - /// The encoded polyline as a read-only memory of characters to decode. + /// The encoded polyline string to decode. The string is converted to internally. /// /// - /// An containing the decoded latitude and longitude pairs. + /// An of containing the decoded coordinate pairs. /// /// - /// Thrown when is . + /// Thrown when or is . /// - public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) { + public static IEnumerable Decode(this IPolylineDecoder, TValue> decoder, string polyline) { if (decoder is null) { - throw new ArgumentNullException(nameof(decoder)); + ExceptionGuard.ThrowArgumentNull(nameof(decoder)); + } + + if (polyline is null) { + ExceptionGuard.ThrowArgumentNull(nameof(polyline)); } - return decoder.Decode(Polyline.FromMemory(polyline)); + return decoder.Decode(polyline.AsMemory()); } } diff --git a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs index 14cecfad..e9b66923 100644 --- a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs +++ b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs @@ -5,58 +5,80 @@ namespace PolylineAlgorithm.Extensions; -using PolylineAlgorithm; 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. /// public static class PolylineEncoderExtensions { /// - /// Encodes a collection of instances into an encoded polyline. + /// 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 sequence of objects to encode. + /// The list of objects to encode. /// /// - /// A representing the encoded polyline string for the provided coordinates. + /// A instance representing the encoded polyline for the provided coordinates. /// /// - /// Thrown when is . + /// Thrown when or is . /// - public static Polyline Encode(this IPolylineEncoder encoder, ICollection coordinates) { + [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) { - throw new ArgumentNullException(nameof(encoder)); + ExceptionGuard.ThrowArgumentNull(nameof(encoder)); } - return encoder.Encode(coordinates); + 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 the encoded polyline output. /// /// The instance used to perform the encoding operation. /// /// - /// The array of objects to encode. + /// The array of objects to encode. /// /// - /// A representing the encoded polyline string for the provided coordinates. + /// A instance representing the encoded polyline for the provided coordinates. /// /// - /// Thrown when is . + /// Thrown when or is . /// - public static Polyline Encode(this IPolylineEncoder encoder, Coordinate[] coordinates) { + public static TPolyline Encode(this IPolylineEncoder encoder, TCoordinate[] coordinates) { if (encoder is null) { - throw new ArgumentNullException(nameof(encoder)); + ExceptionGuard.ThrowArgumentNull(nameof(encoder)); + } + + if (coordinates is null) { + ExceptionGuard.ThrowArgumentNull(nameof(coordinates)); } - return encoder.Encode(coordinates); + return encoder.Encode(coordinates.AsSpan()); } } diff --git a/src/PolylineAlgorithm/Internal/CoordinateDelta.cs b/src/PolylineAlgorithm/Internal/CoordinateDelta.cs new file mode 100644 index 00000000..ae217c57 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/CoordinateDelta.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.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/CoordinateVariance.cs b/src/PolylineAlgorithm/Internal/CoordinateVariance.cs deleted file mode 100644 index e33f02af..00000000 --- a/src/PolylineAlgorithm/Internal/CoordinateVariance.cs +++ /dev/null @@ -1,80 +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; -using System.Diagnostics; -using System.Runtime.InteropServices; - -/// -/// Represents the difference (variance) in latitude and longitude between consecutive geographic coordinates. -/// This struct is used to compute and store the change in coordinate values as integer deltas. -/// -[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] -[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 16)] -internal struct CoordinateVariance { - private (int Latitude, int Longitude) _current; - - /// - /// Initializes a new instance of the struct with the default latitude and longitude deltas. - /// - public CoordinateVariance() { - _current =(default, default); - } - - /// - /// Gets the current variance in latitude between the most recent and previous coordinate. - /// - public int Latitude { get; private set; } - - /// - /// Gets the current variance in longitude between the most recent and previous coordinate. - /// - public int Longitude { get; private set; } - - /// - /// Updates the variance values based on the next latitude and longitude, and sets the current coordinate as next variance baseline. - /// - /// The next latitude value. - /// The next longitude value. - - public void Next(int latitude, int longitude) { - Latitude = Variance(_current.Latitude, latitude); - Longitude = Variance(_current.Longitude, longitude); - - _current.Latitude = latitude; - _current.Longitude = longitude; - } - - /// - /// Calculates the variance (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 variance between and . - - private static int Variance(int initial, int next) => (initial, next) switch { - (0, 0) => 0, - (0, _) => next, - (_, 0) => -initial, - ( < 0, < 0) => -(Math.Abs(next) - Math.Abs(initial)), - ( < 0, > 0) => next + Math.Abs(initial), - ( > 0, < 0) => -(Math.Abs(next) + initial), - ( > 0, > 0) => next - initial, - }; - - /// - /// Returns a string representation of the current coordinate variance. - /// - /// - /// A string in the format { Coordinate: { Latitude: [int], Longitude: [int] }, Variance: { Latitude: [int], Longitude: [int] } } representing the current coordinate and deltas to previous coordinate. - /// - public override readonly string ToString() - => $"{{ Coordinate: {{ Latitude: {Latitude}, Longitude: {Longitude} }}, Variance: {{ Latitude: {Latitude}, Longitude: {Longitude} }} }}"; -} diff --git a/src/PolylineAlgorithm/Internal/Defaults.cs b/src/PolylineAlgorithm/Internal/Defaults.cs index 3f62fac8..44a734fa 100644 --- a/src/PolylineAlgorithm/Internal/Defaults.cs +++ b/src/PolylineAlgorithm/Internal/Defaults.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. // @@ -9,8 +9,10 @@ namespace PolylineAlgorithm.Internal; /// /// Provides default values and constants used throughout the Polyline Algorithm. -/// Organizes defaults for algorithm parameters, polyline encoding, and geographic coordinates into nested static classes. /// +/// +/// Organizes defaults for algorithm parameters, polyline encoding, and geographic coordinates into nested static classes. +/// [ExcludeFromCodeCoverage] internal static class Defaults { /// @@ -18,7 +20,7 @@ internal static class Defaults { /// internal static class Logging { /// - /// Log level multiplier used to distinguish event identification for each log level + /// Log level multiplier used to distinguish event identification for each log level. /// internal const int LogLevelMultiplier = 100; } @@ -26,10 +28,6 @@ internal static class Logging { /// Contains default values and constants specific to the polyline encoding algorithm. /// internal static class Algorithm { - /// - /// The precision factor used to round coordinate values during polyline encoding. - /// - internal const int Precision = 100_000; /// /// The number of bits to shift during polyline encoding. @@ -52,6 +50,9 @@ internal static class Algorithm { internal const byte UnitSeparator = 31; } + /// + /// Contains default values and constants for geographic coordinate validation. + /// internal static class Coordinate { /// /// Provides constants representing latitude values, including the default, minimum, and maximum valid values. @@ -69,20 +70,6 @@ internal static class Latitude { /// The maximum valid latitude value. /// internal const double Max = 90.00000; - - /// - /// Contains constants related to normalized latitude values. - /// - internal static class Normalized { - /// - /// The minimum normalized latitude value. - /// - internal const int Min = (int)(Latitude.Min * Algorithm.Precision); - /// - /// The maximum normalized latitude value. - /// - internal const int Max = (int)(Latitude.Max * Algorithm.Precision); - } } /// @@ -101,20 +88,6 @@ internal static class Longitude { /// The maximum valid longitude value. /// internal const double Max = 180.00000; - - /// - /// Contains constants related to normalized longitude values. - /// - internal static class Normalized { - /// - /// The minimum normalized latitude value. - /// - internal const int Min = (int)(Longitude.Min * Algorithm.Precision); - /// - /// The maximum normalized latitude value. - /// - internal const int Max = (int)(Longitude.Max * Algorithm.Precision); - } } } @@ -127,19 +100,19 @@ internal static class Polyline { /// internal static class Block { /// - /// Contains constants related to the length of encoded coordinates in polyline encoding. + /// Contains constants related to the length of encoded values in polyline encoding. /// internal static class Length { /// - /// The minimum number of characters required to represent an encoded coordinate. + /// The minimum number of characters required to represent an encoded value. /// - internal const int Min = 2; + internal const int Min = 1; /// - /// The maximum number of characters allowed to represent an encoded coordinate. + /// The maximum number of characters allowed to represent an encoded value. /// - internal const int Max = 12; + internal const int Max = 7; } } } -} +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs b/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs new file mode 100644 index 00000000..bbc9839f --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs @@ -0,0 +1,325 @@ +namespace PolylineAlgorithm.Internal.Diagnostics; + +using PolylineAlgorithm; +using PolylineAlgorithm.Properties; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +#if NETSTANDARD2_1 || NET5_0 +using System.Runtime.CompilerServices; +#endif + +#if NET6_0_OR_GREATER +using System.Diagnostics; +#endif + +#if NET8_0_OR_GREATER +using System.Text; +#endif + +/// +/// Centralizes exception throwing for common validation and error scenarios used across the library. +/// +/// +/// Methods in this class are intentionally small and annotated so that they can act as single +/// call sites for throwing exceptions (improving inlining and stack traces). Many members have +/// attributes to avoid polluting callers' stack traces (__StackTraceHidden__ on supported targets) +/// or to prevent inlining on older targets. +/// +public static class ExceptionGuard { + /// + /// Throws an when a numeric argument is not a finite value. + /// + /// Name of the parameter that had a non-finite value. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowNotFiniteNumber(string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.GetArgumentValueMustBeFiniteNumber()); + } + + /// + /// Throws an for a null argument. + /// + /// Name of the parameter that was null. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowArgumentNull(string paramName) { + throw new ArgumentNullException(paramName); + } + + /// + /// Throws an with a provided message. + /// + /// Message that describes the overflow condition. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowBufferOverflow(string message) { + throw new OverflowException(message); + } + + /// + /// Throws an when a coordinate value is outside the allowed range. + /// + /// The coordinate value that was out of range. + /// Inclusive minimum allowed value. + /// Inclusive maximum allowed value. + /// Name of the parameter containing the coordinate. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowCoordinateValueOutOfRange(double value, double min, double max, string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.FormatCoordinateValueMustBeBetween(paramName, min, max)); + } + + /// + /// Throws an when a stack allocation limit is below the required minimum. + /// + /// Minimum required stack allocation limit. + /// Name of the parameter representing the limit. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void StackAllocLimitMustBeEqualOrGreaterThan(int minValue, string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.FormatStackAllocLimitMustBeEqualOrGreaterThan(minValue)); + } + + /// + /// Throws an when an enumeration argument is empty but must contain at least one element. + /// + /// Name of the parameter representing the enumeration. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowArgumentCannotBeEmptyEnumerationMessage(string paramName) { + throw new ArgumentException(ExceptionMessage.GetArgumentCannotBeEmpty(), paramName); + } + + /// + /// Throws an when an encoded value could not be written to the destination buffer. + /// +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowCouldNotWriteEncodedValueToBuffer() { + throw new InvalidOperationException(ExceptionMessage.GetCouldNotWriteEncodedValueToTheBuffer()); + } + + /// + /// Throws an when a destination array is not large enough to contain the polyline data. + /// + /// The length of the destination array. + /// The required polyline length. + /// Name of the parameter representing the destination array. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength, string paramName) { + + throw new ArgumentException(ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength), paramName); + } + + /// + /// Throws an when the polyline length is invalid. + /// + /// The invalid length. + /// The minimum required length. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowInvalidPolylineLength(int length, int min) { + throw new InvalidPolylineException(ExceptionMessage.FormatInvalidPolylineLength(length, min)); + } + + /// + /// Throws an when an unexpected character is encountered in the polyline. + /// + /// The invalid character. + /// Position in the polyline where the character was found. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowInvalidPolylineCharacter(char character, int position) { + throw new InvalidPolylineException(ExceptionMessage.FormatInvalidPolylineCharacter(character, position)); + } + + /// + /// Throws an when a polyline block is longer than allowed. + /// + /// Position in the polyline where the block exceeded the allowed length. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowPolylineBlockTooLong(int position) { + throw new InvalidPolylineException(ExceptionMessage.FormatPolylineBlockTooLong(position)); + } + + /// + /// Throws an when the polyline format is malformed. + /// + /// Approximate position where the polyline became malformed. +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowInvalidPolylineFormat(long position) { + throw new InvalidPolylineException(ExceptionMessage.FormatMalformedPolyline(position)); + } + + /// + /// Throws an when the polyline block terminator is invalid. + /// +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + public static void ThrowInvalidPolylineBlockTerminator() { + throw new InvalidPolylineException(ExceptionMessage.GetInvalidPolylineBlockTerminator()); + } + + /// + /// Helper that formats localized exception messages used by . + /// + /// + /// Keeps message formatting centralized so exception text remains consistent and can reuse + /// resource strings. This class is public because messages are only used by the guard methods. + /// + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Internal use only.")] + internal static class ExceptionMessage { +#if NET8_0_OR_GREATER + private static readonly CompositeFormat StackAllocLimitMustBeEqualOrGreaterThanFormat = CompositeFormat.Parse(ExceptionMessageResource.StackAllocLimitMustBeEqualOrGreaterThanFormat); + private static readonly CompositeFormat PolylineCannotBeShorterThanFormat = CompositeFormat.Parse(ExceptionMessageResource.PolylineCannotBeShorterThanFormat); + private static readonly CompositeFormat PolylineIsMalformedAtFormat = CompositeFormat.Parse(ExceptionMessageResource.PolylineIsMalformedAtFormat); + private static readonly CompositeFormat CoordinateValueMustBeBetweenFormat = CompositeFormat.Parse(ExceptionMessageResource.CoordinateValueMustBeBetweenValuesFormat); + private static readonly CompositeFormat PolylineBlockTooLongFormat = CompositeFormat.Parse(ExceptionMessageResource.PolylineBlockTooLongFormat); + private static readonly CompositeFormat InvalidPolylineCharacterFormat = CompositeFormat.Parse(ExceptionMessageResource.InvalidPolylineCharacterFormat); + private static readonly CompositeFormat DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat = CompositeFormat.Parse(ExceptionMessageResource.DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat); + private static readonly CompositeFormat InvalidPolylineLengthFormat = CompositeFormat.Parse(ExceptionMessageResource.InvalidPolylineLengthFormat); +#else + private static readonly string StackAllocLimitMustBeEqualOrGreaterThanFormat = ExceptionMessageResource.StackAllocLimitMustBeEqualOrGreaterThanFormat; + private static readonly string PolylineCannotBeShorterThanFormat = ExceptionMessageResource.PolylineCannotBeShorterThanFormat; + private static readonly string PolylineIsMalformedAtFormat = ExceptionMessageResource.PolylineIsMalformedAtFormat; + private static readonly string CoordinateValueMustBeBetweenFormat = ExceptionMessageResource.CoordinateValueMustBeBetweenValuesFormat; + private static readonly string PolylineBlockTooLongFormat = ExceptionMessageResource.PolylineBlockTooLongFormat; + private static readonly string InvalidPolylineCharacterFormat = ExceptionMessageResource.InvalidPolylineCharacterFormat; + private static readonly string DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat = ExceptionMessageResource.DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat; + private static readonly string InvalidPolylineLengthFormat = ExceptionMessageResource.InvalidPolylineLengthFormat; +#endif + + private static readonly string ArgumentCannotBeEmptyMessage = ExceptionMessageResource.ArgumentCannotBeEmptyMessage; + private static readonly string ArgumentValueMustBeFiniteNumberMessage = ExceptionMessageResource.ArgumentValueMustBeFiniteNumber; + private static readonly string CouldNotWriteEncodedValueToTheBufferMessage = ExceptionMessageResource.CouldNotWriteEncodedValueToTheBufferMessage; + private static readonly string InvalidPolylineBlockTerminatorMessage = ExceptionMessageResource.InvalidPolylineBlockTerminatorMessage; + + /// + /// Formats the message for stack allocation limit violations. + /// + public static string FormatStackAllocLimitMustBeEqualOrGreaterThan(int minValue) => + string.Format(CultureInfo.InvariantCulture, StackAllocLimitMustBeEqualOrGreaterThanFormat, minValue); + + /// + /// Formats the message when a polyline is shorter than the required minimum. + /// + public static string FormatPolylineCannotBeShorterThan(int length, int minLength) => + string.Format(CultureInfo.InvariantCulture, PolylineCannotBeShorterThanFormat, length, minLength); + + /// + /// Formats a message indicating the polyline is malformed at a given position. + /// + public static string FormatMalformedPolyline(long position) => + string.Format(CultureInfo.InvariantCulture, PolylineIsMalformedAtFormat, position); + + /// + /// Formats a message indicating a coordinate parameter must be within a range. + /// + public static string FormatCoordinateValueMustBeBetween(string name, double min, double max) => + string.Format(CultureInfo.InvariantCulture, CoordinateValueMustBeBetweenFormat, name, min, max); + + /// + /// Formats a message for polyline blocks that exceed allowed length. + /// + public static string FormatPolylineBlockTooLong(int position) => + string.Format(CultureInfo.InvariantCulture, PolylineBlockTooLongFormat, position); + + /// + /// Formats a message for invalid characters found in a polyline. + /// + public static string FormatInvalidPolylineCharacter(char character, int position) => + string.Format(CultureInfo.InvariantCulture, InvalidPolylineCharacterFormat, character, position); + + /// + /// Formats a message when a destination array is too small for the polyline. + /// + public static string FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength) => + string.Format(CultureInfo.InvariantCulture, DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat, destinationLength, polylineLength); + + /// + /// Formats an invalid polyline length message. + /// + public static string FormatInvalidPolylineLength(int length, int min) => + string.Format(CultureInfo.InvariantCulture, InvalidPolylineLengthFormat, length, min); + + /// + /// Gets the message used when a numeric argument must be finite. + /// + public static string GetArgumentValueMustBeFiniteNumber() => + ArgumentValueMustBeFiniteNumberMessage; + + /// + /// Gets the message used when the library could not write an encoded value to a buffer. + /// + public static string GetCouldNotWriteEncodedValueToTheBuffer() => + CouldNotWriteEncodedValueToTheBufferMessage; + + /// + /// Gets the message used when an argument collection must not be empty. + /// + public static string GetArgumentCannotBeEmpty() => + ArgumentCannotBeEmptyMessage; + + /// + /// Gets the message used when a polyline block terminator is invalid. + /// + public static string GetInvalidPolylineBlockTerminator() => + InvalidPolylineBlockTerminatorMessage; + } +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/Diagnostics/LogDebugExtensions.cs b/src/PolylineAlgorithm/Internal/Diagnostics/LogDebugExtensions.cs new file mode 100644 index 00000000..cc58c702 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Diagnostics/LogDebugExtensions.cs @@ -0,0 +1,57 @@ +// +// 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.Diagnostics; + +using Microsoft.Extensions.Logging; + +/// +/// Provides strongly-typed logging extension methods for debug-level diagnostics used throughout the library. +/// These methods are partial and annotated with so the .NET +/// source generator can produce efficient logging implementations at compile time. +/// +/// +/// Event IDs are derived from the debug value: (int)LogLevel.Debug * 100. +/// For the current value this yields a base of 100, +/// so individual event IDs range from 101 to 104. +/// +internal static partial class LogDebugExtensions { + private const LogLevel LOG_LEVEL = LogLevel.Debug; + private const int EVENT_ID_BASE = (int)LOG_LEVEL * Defaults.Logging.LogLevelMultiplier; + + /// + /// Logs a debug message when an operation has started. + /// + /// The used to write the log entry. + /// The name of the operation that has started. + [LoggerMessage(EVENT_ID_BASE + 1, LOG_LEVEL, "Operation {operationName} has started.")] + internal static partial void LogOperationStartedDebug(this ILogger logger, string operationName); + + /// + /// Logs a debug message when an operation has failed. + /// + /// The used to write the log entry. + /// The name of the operation that has failed. + [LoggerMessage(EVENT_ID_BASE + 2, LOG_LEVEL, "Operation {operationName} has failed.")] + internal static partial void LogOperationFailedDebug(this ILogger logger, string operationName); + + /// + /// Logs a debug message when an operation has finished successfully. + /// + /// The used to write the log entry. + /// The name of the operation that has finished. + [LoggerMessage(EVENT_ID_BASE + 3, LOG_LEVEL, "Operation {operationName} has finished.")] + internal static partial void LogOperationFinishedDebug(this ILogger logger, string operationName); + + /// + /// Logs a debug message containing the decoded coordinate values and position. + /// + /// The used to write the log entry. + /// The decoded latitude value. + /// The decoded longitude value. + /// The position in the polyline buffer at which the coordinate was decoded. + [LoggerMessage(EVENT_ID_BASE + 4, LOG_LEVEL, "Decoded coordinate: (Latitude: {latitude}, Longitude: {longitude}) at position {position}.")] + internal static partial void LogDecodedCoordinateDebug(this ILogger logger, double latitude, double longitude, int position); +} diff --git a/src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs b/src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs new file mode 100644 index 00000000..6f5e5d5f --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs @@ -0,0 +1,101 @@ +// +// 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.Diagnostics; + +using Microsoft.Extensions.Logging; + +/// +/// Provides strongly-typed logging extension methods for warning-level diagnostics used throughout the library. +/// These methods are partial and annotated with so the .NET +/// source generator can produce efficient logging implementations at compile time. +/// +/// +/// Event IDs are derived from the warning value: (int)LogLevel.Warning * 100. +/// For the current value this yields a base of 300, +/// so individual event IDs range from 301 to 308. +/// +internal static partial class LogWarningExtensions { + /// + /// The log level used by all methods in this class. + /// + private const LogLevel LOG_LEVEL = LogLevel.Warning; + + /// + /// Base value for event ids used by the annotations. + /// Computed as (int)LOG_LEVEL * 100. + /// + private const int EVENT_ID_BASE = (int)LOG_LEVEL * Defaults.Logging.LogLevelMultiplier; + + /// + /// Logs a warning when a method argument is null. + /// + /// The used to write the log entry. + /// The name of the argument that was null. + [LoggerMessage(EVENT_ID_BASE + 1, LOG_LEVEL, "Argument {argumentName} is null.")] + internal static partial void LogNullArgumentWarning(this ILogger logger, string argumentName); + + /// + /// Logs a warning when a method argument is empty (for example, an empty string or collection). + /// + /// The used to write the log entry. + /// The name of the argument that was empty. + [LoggerMessage(EVENT_ID_BASE + 2, LOG_LEVEL, "Argument {argumentName} is empty.")] + internal static partial void LogEmptyArgumentWarning(this ILogger logger, string argumentName); + + /// + /// Logs a warning when an internal buffer does not have enough space for a required write. + /// + /// The used to write the log entry. + /// The attempted write position in the buffer. + /// The current buffer length. + /// The additional space required at . + [LoggerMessage(EVENT_ID_BASE + 3, LOG_LEVEL, "Internal buffer has size of {bufferLength}. At position {position} is required additional {requiredSpace} space.")] + internal static partial void LogInternalBufferOverflowWarning(this ILogger logger, int position, int bufferLength, int requiredSpace); + + /// + /// Logs a warning when the library cannot write a value to an internal buffer at the requested position. + /// + /// The used to write the log entry. + /// The buffer position where the write was attempted. + /// The index of the current coordinate that prevented the write. + [LoggerMessage(EVENT_ID_BASE + 4, LOG_LEVEL, "Cannot write to internal buffer at position {position}. Current coordinate is at index {coordinateIndex}.")] + internal static partial void LogCannotWriteValueToBufferWarning(this ILogger logger, int position, int coordinateIndex); + + /// + /// Logs a warning when a polyline is shorter than the minimal required length. + /// + /// The used to write the log entry. + /// The actual length of the polyline. + /// The minimal required length for the operation. + [LoggerMessage(EVENT_ID_BASE + 5, LOG_LEVEL, "Polyline is too short. Minimal length is {minimumLength}. Actual length is {actualLength}.")] + internal static partial void LogPolylineCannotBeShorterThanWarning(this ILogger logger, int actualLength, int minimumLength); + + /// + /// Logs a warning when a requested buffer size exceeds the maximum allowed buffer length. + /// + /// The used to write the log entry. + /// The requested buffer length. + /// The maximum allowed buffer length. + [LoggerMessage(EVENT_ID_BASE + 6, LOG_LEVEL, "Requested buffer size of {requestedBufferLength} exceeds maximum allowed buffer length of {maxBufferLength}.")] + internal static partial void LogRequestedBufferSizeExceedsMaxBufferLengthWarning(this ILogger logger, int requestedBufferLength, int maxBufferLength); + + /// + /// Logs a warning when a polyline is detected as invalid or malformed at a specific position. + /// + /// The used to write the log entry. + /// The position within the polyline where the problem was detected. + [LoggerMessage(EVENT_ID_BASE + 7, LOG_LEVEL, "Polyline is invalid or malformed at position {position}.")] + internal static partial void LogInvalidPolylineWarning(this ILogger logger, int position); + + /// + /// Logs a general warning that a polyline is invalid or malformed. Includes an optional exception + /// to help diagnose formatting or parsing issues. + /// + /// The used to write the log entry. + /// The exception associated with the malformed polyline, if any. + [LoggerMessage(EVENT_ID_BASE + 8, LOG_LEVEL, "Polyline is invalid or malformed.")] + internal static partial void LogInvalidPolylineFormatWarning(this ILogger logger, Exception ex); +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/Logging/LogInfoExtensions.cs b/src/PolylineAlgorithm/Internal/Logging/LogInfoExtensions.cs deleted file mode 100644 index b3360a1b..00000000 --- a/src/PolylineAlgorithm/Internal/Logging/LogInfoExtensions.cs +++ /dev/null @@ -1,22 +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.Logging; - -using Microsoft.Extensions.Logging; - -internal static partial class LogInfoExtensions { - private const LogLevel LOG_LEVEL = LogLevel.Information; - private const int EVENT_ID_BASE = (int)LOG_LEVEL * Defaults.Logging.LogLevelMultiplier; - - [LoggerMessage(EVENT_ID_BASE + 1, LOG_LEVEL, "Operation {operationName} has started.")] - internal static partial void LogOperationStartedInfo(this ILogger logger, string operationName); - - [LoggerMessage(EVENT_ID_BASE + 2, LOG_LEVEL, "Operation {operationName} has failed.")] - internal static partial void LogOperationFailedInfo(this ILogger logger, string operationName); - - [LoggerMessage(EVENT_ID_BASE + 3, LOG_LEVEL, "Operation {operationName} has finished.")] - internal static partial void LogOperationFinishedInfo(this ILogger logger, string operationName); -} diff --git a/src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs b/src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs deleted file mode 100644 index 4a58057f..00000000 --- a/src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs +++ /dev/null @@ -1,34 +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.Logging; - -using Microsoft.Extensions.Logging; - -internal static partial class LogWarningExtensions { - private const LogLevel LOG_LEVEL = LogLevel.Warning; - private const int EVENT_ID_BASE = (int)LOG_LEVEL * Defaults.Logging.LogLevelMultiplier; - - [LoggerMessage(EVENT_ID_BASE + 1, LOG_LEVEL, "Argument {argumentName} is null.")] - internal static partial void LogNullArgumentWarning(this ILogger logger, string argumentName); - - [LoggerMessage(EVENT_ID_BASE + 2, LOG_LEVEL, "Argument {argumentName} is empty.")] - internal static partial void LogEmptyArgumentWarning(this ILogger logger, string argumentName); - - [LoggerMessage(EVENT_ID_BASE + 3, LOG_LEVEL, "Internal buffer has size of {bufferLength}. At position {position} is required additional {requiredSpace} space.")] - internal static partial void LogInternalBufferOverflowWarning(this ILogger logger, int position, int bufferLength, int requiredSpace); - - [LoggerMessage(EVENT_ID_BASE + 4, LOG_LEVEL, "Cannot write to internal buffer at position {position}. Current coordinate is at index {coordinateIndex}.")] - internal static partial void LogCannotWriteValueToBufferWarning(this ILogger logger, int position, int coordinateIndex); - - [LoggerMessage(EVENT_ID_BASE + 5, LOG_LEVEL, "Argument {argumentName} is too short. Minimal length is {minimumLength}. Actual length is {actualLength}.")] - internal static partial void LogPolylineCannotBeShorterThanWarning(this ILogger logger, string argumentName, int actualLength, int minimumLength); - - [LoggerMessage(EVENT_ID_BASE + 6, LOG_LEVEL, "Requested buffer size of {requestedBufferLength} exceeds maximum allowed buffer length of {maxBufferLength}.")] - internal static partial void LogRequestedBufferSizeExceedsMaxBufferLengthWarning(this ILogger logger, int requestedBufferLength, int maxBufferLength); - - [LoggerMessage(EVENT_ID_BASE + 7, LOG_LEVEL, "Polyline is invalid or malformed at position {position}.")] - internal static partial void LogInvalidPolylineWarning(this ILogger logger, int position); -} diff --git a/src/PolylineAlgorithm/Internal/Pow10.cs b/src/PolylineAlgorithm/Internal/Pow10.cs new file mode 100644 index 00000000..1ce24789 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Pow10.cs @@ -0,0 +1,40 @@ +namespace PolylineAlgorithm.Internal; + +using System; + +/// +/// Provides optimized calculation of powers of 10 for precision-based operations. +/// +/// +/// This class caches common powers of 10 (10^0 through 10^9) for efficient lookup, +/// falling back to for larger exponents. +/// +internal static class Pow10 { + /// + /// Pre-computed powers of 10 from 10^0 to 10^9. + /// + private static readonly uint[] _pow10 = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; + + /// + /// Returns the power of 10 for the specified precision level. + /// + /// + /// The exponent for the base 10 (i.e., the number of decimal places). + /// + /// + /// The value of 10 raised to the power of as a . + /// + /// + /// If is between 0 and 9 (inclusive), the method returns a cached value + /// for optimal performance. For other values, the result is computed using . + /// The calculation is performed in a checked context to ensure that arithmetic overflow is detected. + /// + /// + /// Thrown if the computed value exceeds . + /// + public static uint GetFactor(uint precision) { + checked { + return precision < _pow10.Length ? _pow10[precision] : (uint)Math.Pow(10, precision); + } + } +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/InvalidPolylineException.cs b/src/PolylineAlgorithm/InvalidPolylineException.cs index 36250da6..02f72e24 100644 --- a/src/PolylineAlgorithm/InvalidPolylineException.cs +++ b/src/PolylineAlgorithm/InvalidPolylineException.cs @@ -1,14 +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; -using PolylineAlgorithm.Properties; using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; /// /// Exception thrown when a polyline is determined to be malformed or invalid during processing. @@ -16,30 +14,32 @@ namespace PolylineAlgorithm; /// /// This exception is used internally to indicate that a polyline string does not conform to the expected format or contains errors. /// -[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Internal use only.")] [DebuggerDisplay($"{nameof(InvalidPolylineException)}: {{ToString()}}")] public sealed class InvalidPolylineException : Exception { + /// + /// Initializes a new instance of the class. + /// + public InvalidPolylineException() + : base() { } + /// /// Initializes a new instance of the class with a specified error message. /// /// /// The error message that describes the reason for the exception. /// - private InvalidPolylineException(string message) + internal InvalidPolylineException(string message) : base(message) { } /// - /// Throws an with a message indicating the position in the polyline where the error was detected. + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. /// - /// - /// The zero-based index in the polyline string where the error occurred. + /// + /// The error message that explains the reason for the exception. /// - /// - /// Always thrown to indicate that the polyline is malformed at the specified position. - /// - internal static void Throw(long position) { - Debug.Assert(position >= 0, "Position must be a non-negative value."); - - throw new InvalidPolylineException(string.Format(ExceptionMessageResource.PolylineStringIsMalformedMessage, position.ToString())); - } + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + /// + public InvalidPolylineException(string message, Exception innerException) + : base(message, innerException) { } } \ No newline at end of file diff --git a/src/PolylineAlgorithm/Polyline.cs b/src/PolylineAlgorithm/Polyline.cs deleted file mode 100644 index d5e51295..00000000 --- a/src/PolylineAlgorithm/Polyline.cs +++ /dev/null @@ -1,214 +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 PolylineAlgorithm.Properties; -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -/// -/// Represents an immutable, read-only encoded polyline string. -/// Provides methods for creation, inspection, and conversion of polyline data from various character sources. -/// -/// -/// This struct is designed to be lightweight and efficient, allowing for quick comparisons and memory-safe operations. -/// -[StructLayout(LayoutKind.Auto)] -[DebuggerDisplay("Value: {ToDebugString()}, IsEmpty: {IsEmpty}, Length: {Length}")] -public readonly struct Polyline : IEquatable { - private readonly ReadOnlyMemory _value; - - /// - /// Initializes a new, empty instance of the struct. - /// - public Polyline() { - _value = ReadOnlyMemory.Empty; - } - - /// - /// Initializes a new instance of the struct with the specified character sequence. - /// - /// - /// A read-only memory region of characters representing an encoded polyline. - /// - private Polyline(ReadOnlyMemory value) { - _value = value; - } - - /// - /// Gets the underlying read-only sequence of characters representing the polyline. - /// - internal readonly ReadOnlyMemory Value => _value; - - /// - /// Gets a value indicating whether this is empty. - /// - public readonly bool IsEmpty => Value.IsEmpty; - - /// - /// Gets the length of the polyline in characters. - /// - public readonly long Length => Value.Length; - - /// - /// Copies the characters of this polyline to the specified destination array. - /// - /// - /// The destination array to copy the characters to. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when the length of does not match the polyline's length. - /// - public void CopyTo(char[] destination) { - if (destination is null) { - throw new ArgumentNullException(nameof(destination)); - } - - if (destination.Length < Length) { - throw new ArgumentException(ExceptionMessageResource.DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthMessage, nameof(destination)); - } - - _value.CopyTo(destination); - } - - /// - /// Returns a string representation of the polyline. - /// - /// - /// A string containing the characters of the polyline, or an empty string if the polyline is empty. - /// - public override string ToString() { - if (IsEmpty) { - return string.Empty; - } - - return Value.ToString(); - } - - /// - /// Returns a debug-friendly string representation of the polyline. - /// - /// - /// A string that includes the polyline value, truncated if necessary, for debugging purposes. - /// - private string ToDebugString() { - if (IsEmpty) { - return string.Empty; - } - - return Value.Length <= 32 ? $"\"{Value}\"" : $"\"{Value[..10]}...{Value[^10..]}\""; - } - - /// - public bool Equals(Polyline other) { - if ((IsEmpty != other.IsEmpty) || (Length != other.Length)) { - return false; - } - - return Value.Span.SequenceEqual(other.Value.Span); - } - - /// - public override bool Equals(object obj) { - return obj is Polyline other && Equals(other); - } - - /// - public override int GetHashCode() { - return Value.GetHashCode(); - } - - /// - /// Determines whether two instances are equal. - /// - /// - /// The first polyline to compare. - /// - /// - /// The second polyline to compare. - /// - /// - /// if the polylines are equal; otherwise, . - /// - public static bool operator ==(Polyline left, Polyline right) { - return left.Equals(right); - } - - /// - /// Determines whether two instances are not equal. - /// - /// The first polyline to compare. - /// The second polyline to compare. - /// if the polylines are not equal; otherwise, . - public static bool operator !=(Polyline left, Polyline right) { - return !(left == right); - } - - #region Factory methods - - /// - /// Creates a from a Unicode character array. - /// - /// - /// A character array representing an encoded polyline. - /// - /// - /// The instance corresponding to the specified character array. - /// - /// - /// Thrown when is . - /// - public static Polyline FromCharArray(char[] polyline) { - if (polyline is null) { - throw new ArgumentNullException(nameof(polyline)); - } - - return FromMemory(polyline.AsMemory()); - } - - /// - /// Creates a from a string. - /// - /// - /// A string representing an encoded polyline. - /// - /// - /// The instance corresponding to the specified string. - /// - /// - /// Thrown when is . - /// - public static Polyline FromString(string polyline) { - if (polyline is null) { - throw new ArgumentNullException(nameof(polyline)); - } - - return FromMemory(polyline.AsMemory()); - } - - /// - /// Creates a from a read-only memory region of characters. - /// - /// - /// A read-only memory region representing an encoded polyline. - /// - /// - /// The instance corresponding to the specified memory region. - /// - public static Polyline FromMemory(ReadOnlyMemory polyline) { - if (polyline.IsEmpty) { - return new(); - } - - return new Polyline(polyline); - } - - #endregion -} diff --git a/src/PolylineAlgorithm/PolylineAlgorithm.csproj b/src/PolylineAlgorithm/PolylineAlgorithm.csproj index 54b8cc74..24504239 100644 --- a/src/PolylineAlgorithm/PolylineAlgorithm.csproj +++ b/src/PolylineAlgorithm/PolylineAlgorithm.csproj @@ -2,18 +2,6 @@ netstandard2.1 - 13.0 - enable - enable - true - en - - - - All - latest - true - true @@ -22,13 +10,13 @@ true - + pdbonly true snupkg - + True @@ -44,7 +32,7 @@ - + True \ @@ -55,7 +43,7 @@ all runtime; build; native; contentfiles; analyzers - + @@ -68,11 +56,11 @@ - - ExceptionMessageResource.resx - True - True - + + True + True + ExceptionMessageResource.resx + diff --git a/src/PolylineAlgorithm/PolylineDecoder.cs b/src/PolylineAlgorithm/PolylineDecoder.cs deleted file mode 100644 index 2e398d28..00000000 --- a/src/PolylineAlgorithm/PolylineDecoder.cs +++ /dev/null @@ -1,29 +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 PolylineAlgorithm.Abstraction; - -/// -public sealed class PolylineDecoder : AbstractPolylineDecoder { - /// - public PolylineDecoder() - : base() { } - - /// - public PolylineDecoder(PolylineEncodingOptions options) - : base(options) { } - - /// - protected override Coordinate CreateCoordinate(double latitude, double longitude) { - return new(latitude, longitude); - } - - /// - protected override ReadOnlyMemory GetReadOnlyMemory(Polyline polyline) { - return polyline.Value; - } -} \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineEncoder.cs b/src/PolylineAlgorithm/PolylineEncoder.cs deleted file mode 100644 index e945232c..00000000 --- a/src/PolylineAlgorithm/PolylineEncoder.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; - -using PolylineAlgorithm.Abstraction; - -/// -public sealed class PolylineEncoder : AbstractPolylineEncoder { - /// - public PolylineEncoder() - : base() { } - - /// - public PolylineEncoder(PolylineEncodingOptions options) - : base(options) { } - - /// - protected override double GetLatitude(Coordinate coordinate) { - return coordinate.Latitude; - } - - /// - protected override double GetLongitude(Coordinate coordinate) { - return coordinate.Longitude; - } - - /// - protected override Polyline CreatePolyline(ReadOnlyMemory polyline) { - return Polyline.FromMemory(polyline); - } -} - diff --git a/src/PolylineAlgorithm/PolylineEncoding.cs b/src/PolylineAlgorithm/PolylineEncoding.cs index 75100f5e..4b8d5312 100644 --- a/src/PolylineAlgorithm/PolylineEncoding.cs +++ b/src/PolylineAlgorithm/PolylineEncoding.cs @@ -4,9 +4,13 @@ // namespace PolylineAlgorithm; + using PolylineAlgorithm.Internal; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; + using System; +using System.Numerics; +using System.Runtime.InteropServices; /// /// Provides methods for encoding and decoding polyline data, as well as utilities for normalizing and de-normalizing @@ -18,28 +22,152 @@ namespace PolylineAlgorithm; /// longitude. public static class PolylineEncoding { /// - /// Attempts to read a value from the specified buffer and updates the variance. + /// Normalizes a geographic coordinate value to an integer representation based on the specified precision. + /// + /// + /// + /// This method converts a floating-point coordinate value into a normalized integer by multiplying it by 10 raised + /// to the power of the specified precision, then truncating the result to an integer. + /// + /// + /// For example, with the default precision of 5: + /// + /// A value of 37.78903 becomes 3778903 + /// A value of -122.4123 becomes -12241230 + /// + /// + /// + /// The method validates that the input value is finite (not NaN or infinity) before performing normalization. + /// If the precision is 0, the value is rounded without multiplication. + /// + /// + /// + /// The numeric value to normalize. Must be a finite number (not NaN or infinity). + /// + /// + /// The number of decimal places of precision to preserve in the normalized value. + /// The value is multiplied by 10^ before rounding. + /// Default is 5, which is standard for polyline encoding. + /// + /// + /// An 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) { + // Fast return if the value is zero, return 0 as the normalized value. + if (value.Equals(default)) { + return 0; + } + + // Validate that the value is finite and not NaN or Infinity. + if (!double.IsFinite(value)) { + ExceptionGuard.ThrowNotFiniteNumber(nameof(value)); + } + + // Fast return if precision is zero, return current value converted to Int32. + if (precision == default) { + return (int)Math.Truncate(value); + } + + uint factor = Pow10.GetFactor(precision); + + checked { + return (int)(Math.Truncate(value * 10 * factor) / 10); + } + + } + + /// + /// Converts a normalized integer coordinate value back to its floating-point representation based on the specified precision. + /// + /// + /// + /// This method reverses the normalization performed by . It takes an integer value and converts it + /// to a double by dividing it by 10 raised to the power of the specified precision. If is 0, + /// the value is returned as a double without division. + /// + /// + /// The calculation is performed inside a block to ensure that any arithmetic overflow is detected + /// and an is thrown. + /// + /// + /// For example, with a precision of 5: + /// + /// A value of 3778903 becomes 37.78903 + /// A value of -12241230 becomes -122.4123 + /// + /// + /// + /// If the input is 0, the method returns 0.0 immediately. + /// + /// + /// + /// The 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. + /// + /// + /// The denormalized floating-point coordinate value. + /// + /// + /// Thrown if the arithmetic operation overflows during conversion. + /// + public static double Denormalize(int value, uint precision = 5) { + if (value.Equals(default)) { + return default; + } + + // Fast return if precision is zero, return current value converted to Int32. + if (precision == default) { + return value; + } + + uint factor = Pow10.GetFactor(precision); + + checked { + + return value / (double)factor; + } + } + + /// + /// Attempts to read an encoded integer value from a polyline buffer, updating the specified delta and position. /// - /// This method processes the buffer starting at the specified position and attempts to decode a value. - /// The decoded value is used to update the parameter. The method stops reading when a - /// termination condition is met or the end of the buffer is reached. - /// - /// A reference to the integer that will be updated based on the value read from the buffer. + /// + /// + /// This method decodes a value from a polyline-encoded character buffer, starting at the given position. It reads + /// characters sequentially, applying the polyline decoding algorithm, and updates the with + /// the decoded value. The position is advanced as characters are processed. + /// + /// + /// The decoding process continues until a character with a value less than the algorithm's space constant is encountered, + /// which signals the end of the encoded value. If the buffer is exhausted before a complete value is read, the method returns . + /// + /// + /// The decoded value is added to using zigzag decoding, which handles both positive and negative values. + /// + /// + /// + /// Reference to the integer accumulator that will be updated with the decoded value. /// /// - /// A reference to the read-only memory buffer containing the data to be processed. + /// The buffer containing polyline-encoded characters. /// /// - /// A reference to the current position within the buffer. The position is incremented as the method reads data. + /// Reference to the current position in the buffer. This value is updated as characters are read. /// /// - /// if a value was successfully read and the end of the buffer was not reached; otherwise, . + /// if a value was successfully read and decoded; if the buffer ended before a complete value was read. /// - - public static bool TryReadValue(ref int variance, ref ReadOnlyMemory buffer, ref int position) { + public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref int position) { // Validate that the position is within the bounds of the buffer. - if (position == buffer.Length) { + if (position >= buffer.Length) { return false; } @@ -61,91 +189,69 @@ public static bool TryReadValue(ref int variance, ref ReadOnlyMemory buffe } } - variance += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; + delta += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; // If the end of the buffer was reached without reading a complete value, return false. return chunk < Defaults.Algorithm.Space; } - /// - /// Converts a normalized integer value to its denormalized double representation based on the specified type. - /// - /// The denormalization process divides the input value by a predefined precision factor to - /// produce the resulting double. Ensure that is validated against the specified before calling this method. - /// - /// The normalized integer value to be denormalized. Must be within the valid range for the specified . - /// - /// - /// The type that defines the valid range for . - /// - /// - /// The denormalized double representation of the input value. Returns if is . - /// - /// - /// Thrown when is outside the valid range for the specified . - /// - - public static double Denormalize(int value, CoordinateValueType type) { - // Validate that the type is not None, as it does not represent a valid coordinate value type. - if (type == CoordinateValueType.None) { - throw new ArgumentOutOfRangeException(nameof(type), string.Format(ExceptionMessageResource.ArgumentCannotBeCoordinateValueTypeMessageFormat, type.ToString())); - } - - // Validate that the value is finite and within the acceptable range for the specified type. - if (!ValidateValue(value, type)) { - throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(ExceptionMessageResource.ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat, type.ToString().ToLowerInvariant())); - } - - // Return fast if the value is zero, return 0.0 as the denormalized value. - if (value == 0) { - return 0.0; - } - - return Math.Truncate((double)value) / Defaults.Algorithm.Precision; - } /// - /// Attempts to write a value derived from the specified into the provided at the given . + /// Attempts to write an encoded integer value to a polyline buffer, updating the specified position. /// /// - /// This method performs bounds checking to ensure that the buffer has sufficient space to - /// accommodate the calculated value. If the buffer does not have enough space, the method returns without modifying the buffer or position. + /// + /// This method encodes an integer delta value into a polyline-encoded format and writes it to the provided character buffer, + /// starting at the given position. It applies zigzag encoding followed by the polyline encoding algorithm to represent + /// both positive and negative values efficiently. + /// + /// + /// The encoding process first converts the value using zigzag encoding (left shift by 1, with bitwise inversion for negative values), + /// then writes it as a sequence of characters. Each character encodes 5 bits of data, with continuation bits indicating whether + /// more characters follow. The position is advanced as characters are written. + /// + /// + /// 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 without modifying the buffer or position. + /// + /// + /// This method is the inverse of and can be used to encode coordinate deltas for polyline serialization. + /// /// - /// - /// The integer value used to calculate the output to be written into the buffer. + /// + /// The integer value to encode and write to the buffer. This value typically represents the difference between consecutive + /// coordinate values in polyline encoding. /// /// - /// A reference to the span of characters where the value will be written. + /// The destination buffer where the encoded characters will be written. Must have sufficient capacity to hold the encoded value. /// /// - /// A reference to the current position in the buffer where writing begins. This value is updated to reflect the new - /// position after writing. + /// Reference to the current position in the buffer. This value is updated as characters are written to reflect the new position + /// after encoding is complete. /// /// - /// if the value was successfully written to the buffer; otherwise, . + /// 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 variance, ref Span buffer, ref int position) { + public static bool TryWriteValue(int delta, Span buffer, ref int position) { // Validate that the position and required space for write is within the bounds of the buffer. - if (buffer.Length < position + GetCharCount(variance)) { + if (buffer[position..].Length < GetRequiredBufferSize(delta)) { return false; } - int rem = variance << 1; + int rem = delta << 1; - // If the variance is negative, we need to invert the bits to get the correct representation. - if (variance < 0) { + // If the delta is negative, we need to invert the bits to get the correct representation. + if (delta < 0) { rem = ~rem; } // Write the value to the buffer in a way that encodes it using the specified algorithm. while (rem >= Defaults.Algorithm.Space) { - buffer[position++] = (char)((Defaults.Algorithm.Space | rem & Defaults.Algorithm.UnitSeparator) + Defaults.Algorithm.QuestionMark); + buffer[position++] = + (char)((Defaults.Algorithm.Space + | (rem & Defaults.Algorithm.UnitSeparator)) + + Defaults.Algorithm.QuestionMark); rem >>= Defaults.Algorithm.ShiftLength; } @@ -156,97 +262,200 @@ public static bool TryWriteValue(int variance, ref Span buffer, ref int po } /// - /// Normalizes a given numeric value based on the specified type and precision settings. + /// Calculates the number of characters required to encode a delta value in polyline format. /// /// - /// This method validates the input value to ensure it is finite and within the acceptable range - /// for the specified type. If the value is valid, it applies a normalization algorithm using a predefined precision - /// factor. + /// + /// 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 + /// but only calculates the required buffer size without actually writing any data. + /// + /// + /// The calculation process: + /// + /// Applies zigzag encoding: left-shifts the value by 1 bit, then inverts all bits if the original value was negative + /// Counts how many 5-bit chunks are needed to represent the encoded value + /// Each chunk requires one character, with a minimum of 1 character for any value + /// + /// + /// + /// This method is useful for pre-allocating buffers of the correct size before encoding polyline data, helping to avoid + /// 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 numeric value to normalize. Must be a finite number. - /// - /// - /// The type against which the value is validated. Determines the acceptable range for the value. + /// + /// The integer delta value to calculate the encoded size for. This value typically represents the difference between + /// consecutive coordinate values in polyline encoding. /// /// - /// An integer representing the normalized value. Returns 0 if the input value is 0.0. + /// The number of characters required to encode the specified delta value. The minimum return value is 1. /// - /// - /// Thrown when is not a finite number or is outside the valid range for the specified - /// . - /// + /// + public static int GetRequiredBufferSize(int delta) { + long rem = (long)delta << 1; - public static int Normalize(double value, CoordinateValueType type) { - // Validate that the type is not None, as it does not represent a valid coordinate value type. - if (type == CoordinateValueType.None) { - throw new ArgumentOutOfRangeException(nameof(type), string.Format(ExceptionMessageResource.ArgumentCannotBeCoordinateValueTypeMessageFormat, type.ToString())); + if (delta < 0) { + rem = ~rem; } - // Validate that the value is finite and not NaN or Infinity. - if (double.IsNaN(value) || double.IsInfinity(value)) { - throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessageResource.ArgumentValueMustBeFiniteNumber); - } + int size = 1; - // Validate that the value is within the acceptable range for the specified type. - if (!ValidateValue(value, type)) { - throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(ExceptionMessageResource.ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat, type.ToString().ToLowerInvariant())); + while (rem >= Defaults.Algorithm.Space) { + rem >>= Defaults.Algorithm.ShiftLength; + size++; } - // Fast return if the value is zero, return 0 as the normalized value. - if (value == 0.0) { - return 0; - } + return size; + } + + #region Validation + + /// + /// The minimum valid character value for polyline encoding, corresponding to the ASCII value of '?' (63). + /// + private const ushort Min = Defaults.Algorithm.QuestionMark; + + /// + /// The maximum valid character value for polyline encoding, calculated as the sum of two question mark values ('?' + '?', or 63 + 63 = 126). + /// + private const ushort Max = (Defaults.Algorithm.QuestionMark + Defaults.Algorithm.QuestionMark); + + /// + /// The character value that marks the end of a polyline block, calculated as the sum of the question mark value and the space value ('?' + ' ', or 63 + 32 = 95). + /// + private const ushort End = (Defaults.Algorithm.QuestionMark + Defaults.Algorithm.Space); + + /// + /// SIMD vector containing the minimum valid character value for efficient range validation of polyline segments. + /// + private static readonly Vector MinVector = new(Min); + + /// + /// SIMD vector containing the maximum valid character value for efficient range validation of polyline segments. + /// + private static readonly Vector MaxVector = new(Max); - return (int)Math.Round(value * Defaults.Algorithm.Precision); + /// + /// Validates the format of a polyline segment, ensuring all characters are valid and block structure is correct. + /// + /// + /// + /// This method performs two levels of validation on the provided polyline segment: + /// + /// + /// + /// + /// Character Range Validation: Checks that every character in the polyline is within the valid ASCII range for polyline encoding ('?' [63] to '_' [95], inclusive). + /// Uses SIMD acceleration for efficient validation of large segments. + /// + /// + /// + /// + /// Block Structure Validation: Ensures that each encoded value (block) does not exceed 7 characters and that the polyline ends with a valid block terminator. + /// + /// + /// + /// + /// If an invalid character or block structure is detected, an is thrown with details about the error. + /// + /// + /// A span representing the polyline segment to validate. + /// + /// Thrown when an invalid character is found or the block structure is invalid. + /// + public static void ValidateFormat(ReadOnlySpan polyline) { + // 1. SIMD character check (reuse existing method) + ValidateCharRange(polyline); + // 2. Block structure check + ValidateBlockLength(polyline); } /// - /// Determines the number of characters required to represent the specified integer value within predefined - /// variance ranges. + /// Validates that all characters in the polyline segment are within the allowed ASCII range for polyline encoding. /// /// - /// The method uses predefined ranges to efficiently determine the character count. Smaller - /// values require fewer characters, while larger values require more. This method is optimized for performance - /// using a switch expression. + /// + /// Uses SIMD vectorization for efficient validation of large spans. Falls back to scalar checks for any block where an invalid character is detected. + /// + /// + /// The valid range is from '?' (63) to '_' (95), inclusive. If an invalid character is found, an is thrown. + /// /// - /// - /// The integer value for which the character count is calculated. Must be within the range of a 32-bit signed - /// integer. - /// - /// - /// The number of characters required to represent the value, based on its magnitude. - /// Returns a value between 1 and 6 inclusive. - /// + /// A span representing the polyline segment to validate. + /// + /// Thrown when an invalid character is found in the polyline segment. + /// + public static void ValidateCharRange(ReadOnlySpan polyline) { + int length = polyline.Length; + int vectorSize = Vector.Count; + + int i = 0; + for (; i <= length - vectorSize; i += vectorSize) { + var span = MemoryMarshal.Cast(polyline.Slice(i, vectorSize)); +#if NET5_0_OR_GREATER + var chars = new Vector(span); +#else + var chars = new Vector(span.ToArray()); +#endif + var belowMin = Vector.LessThan(chars, MinVector); + var aboveMax = Vector.GreaterThan(chars, MaxVector); + if (Vector.BitwiseOr(belowMin, aboveMax) != Vector.Zero) { + // Fallback to scalar check for this block + for (int j = 0; j < vectorSize; j++) { + char character = polyline[i + j]; + if (character < Min || character > Max) { + ExceptionGuard.ThrowInvalidPolylineCharacter(character, i + j); + } + } + } + } - public static int GetCharCount(int variance) => variance switch { - // DO NOT CHANGE THE ORDER. We are skipping inside exclusive ranges as those are covered by previous statements. - >= -16 and <= +15 => 1, - >= -512 and <= +511 => 2, - >= -16384 and <= +16383 => 3, - >= -524288 and <= +524287 => 4, - >= -16777216 and <= +16777215 => 5, - _ => 6, - }; + for (; i < length; i++) { + char character = polyline[i]; + if (character < Min || character > Max) { + ExceptionGuard.ThrowInvalidPolylineCharacter(character, i); + } + } + } /// - /// Validates whether the specified denormalized value falls within the acceptable range for the given value type. + /// Validates the block structure of a polyline segment, ensuring each encoded value does not exceed 7 characters and the polyline ends correctly. /// - /// - /// The denormalized value to validate. - /// - /// - /// The type of value to validate, such as latitude or longitude. - /// - /// - /// if the is within the valid range for the specified ; otherwise, . - /// - private static bool ValidateValue(T value, CoordinateValueType type) => (type, value) switch { - (CoordinateValueType.Latitude, int normalized) when normalized >= Defaults.Coordinate.Latitude.Normalized.Min && normalized <= Defaults.Coordinate.Latitude.Normalized.Max => true, - (CoordinateValueType.Longitude, int normalized) when normalized >= Defaults.Coordinate.Longitude.Normalized.Min && normalized <= Defaults.Coordinate.Longitude.Normalized.Max => true, - (CoordinateValueType.Latitude, double denormalized) when denormalized >= Defaults.Coordinate.Latitude.Min && denormalized <= Defaults.Coordinate.Latitude.Max => true, - (CoordinateValueType.Longitude, double denormalized) when denormalized >= Defaults.Coordinate.Longitude.Min && denormalized <= Defaults.Coordinate.Longitude.Max => true, - _ => false, - }; -} + /// + /// + /// Iterates through the polyline, counting the length of each block (a sequence of characters representing an encoded value). + /// Throws an if any block exceeds 7 characters or if the polyline does not end with a valid block terminator. + /// + /// + /// A span representing the polyline segment to validate. + /// + /// Thrown when a block exceeds 7 characters or the polyline does not end with a valid block terminator. + /// + public static void ValidateBlockLength(ReadOnlySpan polyline) { + int blockLen = 0; + bool foundBlockEnd = false; + + for (int i = 0; i < polyline.Length; i++) { + blockLen++; + + if (polyline[i] < End) { + foundBlockEnd = true; + if (blockLen > Defaults.Polyline.Block.Length.Max) { + ExceptionGuard.ThrowPolylineBlockTooLong(i - blockLen + 1); + } + blockLen = 0; + } else { + foundBlockEnd = false; + } + } + + if (!foundBlockEnd) { + ExceptionGuard.ThrowInvalidPolylineBlockTerminator(); + } + } + + #endregion +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineEncodingOptions.cs b/src/PolylineAlgorithm/PolylineEncodingOptions.cs index ae8d1a6f..e99843de 100644 --- a/src/PolylineAlgorithm/PolylineEncodingOptions.cs +++ b/src/PolylineAlgorithm/PolylineEncodingOptions.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,37 +8,76 @@ namespace PolylineAlgorithm; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; /// -/// Options for configuring polyline encoding. +/// Provides configuration options for polyline encoding operations. /// /// -/// This class allows you to set options such as buffer size and logger factory for encoding operations. +/// +/// 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. +/// /// -[DebuggerDisplay("MaxBufferSize: {MaxBufferSize}, MaxBufferLength: {MaxBufferLength}, LoggerFactoryType: {LoggerFactory.GetType().Name}")] +[DebuggerDisplay("StackAllocLimit: {StackAllocLimit}, Precision: {Precision}, LoggerFactoryType: {GetLoggerFactoryType()}")] public sealed class PolylineEncodingOptions { /// - /// Gets the maximum buffer size for encoding operations. + /// Gets the logger factory used for diagnostic logging during encoding operations. /// + /// + /// An instance. Defaults to . + /// /// - /// The default maximum buffer size is 64,000 bytes (64 KB). This can be adjusted based on the expected size of the polyline data, but should be enough for common cases. + /// The default logger factory is , which does not log any messages. + /// To enable logging, provide a custom implementation. /// - public int MaxBufferSize { get; internal set; } = 64_000; + public ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance; /// - /// Gets or sets the precision for encoding coordinates. + /// Gets the precision level used for encoding coordinate values. /// + /// + /// The number of decimal places to use when encoding coordinate values. Defaults to 5. + /// /// - /// The default logger factory is , which does not log any messages. + /// + /// 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 ILoggerFactory LoggerFactory { get; internal set; } = NullLoggerFactory.Instance; - + public uint Precision { get; internal set; } = 5; /// - /// Gets the maximum length of the encoded polyline string. + /// Gets the maximum buffer size (in characters) that can be allocated on the stack for encoding operations. /// + /// + /// The maximum number of characters for stack allocation using stackalloc char[]. Defaults to 512. + /// /// - /// The maximum length is calculated based on the buffer size divided by the size of a character. + /// 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. /// - internal int MaxBufferLength => MaxBufferSize / sizeof(char); + public int StackAllocLimit { get; internal set; } = 512; + + /// + /// Returns the type name of the logger factory for debugging purposes. + /// + /// + /// A string containing the type name of the current instance. + /// + [ExcludeFromCodeCoverage] + private string GetLoggerFactoryType() => LoggerFactory.GetType().Name; } \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs b/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs index a555b11f..4258beca 100644 --- a/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs +++ b/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.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. // @@ -7,13 +7,14 @@ namespace PolylineAlgorithm; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; /// /// Provides a builder for configuring options for polyline encoding operations. /// -public class PolylineEncodingOptionsBuilder { - private int _bufferSize = 64_000; +public sealed class PolylineEncodingOptionsBuilder { + private uint _precision = 5; + private int _stackAllocLimit = 512; private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; private PolylineEncodingOptionsBuilder() { } @@ -36,41 +37,65 @@ public static PolylineEncodingOptionsBuilder Create() { /// public PolylineEncodingOptions Build() { return new PolylineEncodingOptions { - MaxBufferSize = _bufferSize, - LoggerFactory = _loggerFactory + Precision = _precision, + StackAllocLimit = _stackAllocLimit, + LoggerFactory = _loggerFactory, }; } /// - /// Sets the buffer size for encoding operations. + /// 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 maximum buffer size. Must be greater than 11. + /// + /// The number of decimal places to use for encoding coordinate values. Default is 5. /// /// - /// The current builder instance. + /// The current instance for method chaining. /// - /// Thrown when is less than or equal to 11. - public PolylineEncodingOptionsBuilder WithMaxBufferSize(int bufferSize) { - _bufferSize = bufferSize > 11 ? bufferSize : throw new ArgumentOutOfRangeException(nameof(bufferSize), string.Format(ExceptionMessageResource.BufferSizeMustBeGreaterThanMessageFormat, 11)); + public PolylineEncodingOptionsBuilder WithPrecision(uint precision) { + _precision = precision; return this; } /// - /// Sets the logger factory for logging during encoding operations. + /// Configures the to be used for logging during polyline encoding operations. /// /// - /// The instance of a logger factory. + /// The instance to use for logging. If , a will be used instead. /// /// - /// The current builder instance. + /// The current instance for method chaining. /// - /// - /// Thrown when is . - /// public PolylineEncodingOptionsBuilder WithLoggerFactory(ILoggerFactory loggerFactory) { - _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; return this; } diff --git a/src/PolylineAlgorithm/Properties/ExceptionMessageResource.Designer.cs b/src/PolylineAlgorithm/Properties/ExceptionMessageResource.Designer.cs index 04d5a153..0c73cce3 100644 --- a/src/PolylineAlgorithm/Properties/ExceptionMessageResource.Designer.cs +++ b/src/PolylineAlgorithm/Properties/ExceptionMessageResource.Designer.cs @@ -19,7 +19,7 @@ namespace PolylineAlgorithm.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ExceptionMessageResource { @@ -61,92 +61,110 @@ internal ExceptionMessageResource() { } /// - /// Looks up a localized string similar to Argument cannot be CoordinateValueType.{0}.. + /// Looks up a localized string similar to Argument cannot be empty.. /// - internal static string ArgumentCannotBeCoordinateValueTypeMessageFormat { + internal static string ArgumentCannotBeEmptyMessage { get { - return ResourceManager.GetString("ArgumentCannotBeCoordinateValueTypeMessageFormat", resourceCulture); + return ResourceManager.GetString("ArgumentCannotBeEmptyMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Argument cannot be an empty enumeration.. + /// Looks up a localized string similar to Value must be a finite number.. /// - internal static string ArgumentCannotBeEmptyEnumerationMessage { + internal static string ArgumentValueMustBeFiniteNumber { get { - return ResourceManager.GetString("ArgumentCannotBeEmptyEnumerationMessage", resourceCulture); + return ResourceManager.GetString("ArgumentValueMustBeFiniteNumber", resourceCulture); } } /// - /// Looks up a localized string similar to Value is out of range for the specified type {0}.. + /// Looks up a localized string similar to {0} must be between {1} and {2}.. /// - internal static string ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat { + internal static string CoordinateValueMustBeBetweenValuesFormat { get { - return ResourceManager.GetString("ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat", resourceCulture); + return ResourceManager.GetString("CoordinateValueMustBeBetweenValuesFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Value must be a finite number.. + /// Looks up a localized string similar to Encoded values couldn't be written to the buffer.. /// - internal static string ArgumentValueMustBeFiniteNumber { + internal static string CouldNotWriteEncodedValueToTheBufferMessage { get { - return ResourceManager.GetString("ArgumentValueMustBeFiniteNumber", resourceCulture); + return ResourceManager.GetString("CouldNotWriteEncodedValueToTheBufferMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Buffer size must be greater than {0}.. + /// Looks up a localized string similar to Destination array length {0} must be greater than the polyline length {1}.. /// - internal static string BufferSizeMustBeGreaterThanMessageFormat { + internal static string DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat { get { - return ResourceManager.GetString("BufferSizeMustBeGreaterThanMessageFormat", resourceCulture); + return ResourceManager.GetString("DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat", resourceCulture); } } /// - /// Looks up a localized string similar to {0} must be between {1} and {2}.. + /// Looks up a localized string similar to Polyline does not end with a valid block terminator.. + /// + internal static string InvalidPolylineBlockTerminatorMessage { + get { + return ResourceManager.GetString("InvalidPolylineBlockTerminatorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Polyline contains invalid character '{0}' at position {1}.. /// - internal static string CoordinateValueMustBeBetweenValuesMessageFormat { + internal static string InvalidPolylineCharacterFormat { get { - return ResourceManager.GetString("CoordinateValueMustBeBetweenValuesMessageFormat", resourceCulture); + return ResourceManager.GetString("InvalidPolylineCharacterFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Encoded values couldn't be written to the buffer. Please, report a bug on GitHub with reproducible sample. Thank you, Pete.. + /// Looks up a localized string similar to Polyline must be at least {1} characters long, but was {0}.. /// - internal static string CouldNotWriteEncodedValueToTheBuffer { + internal static string InvalidPolylineLengthFormat { get { - return ResourceManager.GetString("CouldNotWriteEncodedValueToTheBuffer", resourceCulture); + return ResourceManager.GetString("InvalidPolylineLengthFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Destination array length must be greater than the polyline length.. + /// Looks up a localized string similar to Block at position {0} exceeds 7 characters.. /// - internal static string DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthMessage { + internal static string PolylineBlockTooLongFormat { get { - return ResourceManager.GetString("DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthMessage", resourceCulture); + return ResourceManager.GetString("PolylineBlockTooLongFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Argument cannot be shorten than 2 characters. Actual length: {0}.. + /// Looks up a localized string similar to Argument cannot be shorter than {1} characters. Value: {0}.. /// - internal static string PolylineCannotBeShorterThanExceptionMessage { + internal static string PolylineCannotBeShorterThanFormat { get { - return ResourceManager.GetString("PolylineCannotBeShorterThanExceptionMessage", resourceCulture); + return ResourceManager.GetString("PolylineCannotBeShorterThanFormat", resourceCulture); } } /// /// Looks up a localized string similar to Polyline is malformed at position {0}.. /// - internal static string PolylineStringIsMalformedMessage { + internal static string PolylineIsMalformedAtFormat { + get { + return ResourceManager.GetString("PolylineIsMalformedAtFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stack alloc limit must be equal or greater than {0}.. + /// + internal static string StackAllocLimitMustBeEqualOrGreaterThanFormat { get { - return ResourceManager.GetString("PolylineStringIsMalformedMessage", resourceCulture); + return ResourceManager.GetString("StackAllocLimitMustBeEqualOrGreaterThanFormat", resourceCulture); } } } diff --git a/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx b/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx index bf47eeee..9ec92bf4 100644 --- a/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx +++ b/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx @@ -117,34 +117,40 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Argument cannot be an empty enumeration. + + Argument cannot be empty. - + Polyline is malformed at position {0}. - - Argument cannot be shorten than 2 characters. Actual length: {0}. + + Argument cannot be shorter than {1} characters. Value: {0}. Value must be a finite number. - - Value is out of range for the specified type {0}. + + Stack alloc limit must be equal or greater than {0}. - - Argument cannot be CoordinateValueType.{0}. + + Destination array length {0} must be greater than the polyline length {1}. - - Buffer size must be greater than {0}. + + {0} must be between {1} and {2}. - - Destination array length must be greater than the polyline length. + + Encoded values couldn't be written to the buffer. - - {0} must be between {1} and {2}. + + Block at position {0} exceeds 7 characters. + + + Polyline contains invalid character '{0}' at position {1}. + + + Polyline must be at least {1} characters long, but was {0}. - - Encoded values couldn't be written to the buffer. Please, report a bug on GitHub with reproducible sample. Thank you, Pete. + + Polyline does not end with a valid block terminator. \ No newline at end of file diff --git a/src/PolylineAlgorithm/Properties/GlobalSuppressions.cs b/src/PolylineAlgorithm/Properties/GlobalSuppressions.cs new file mode 100644 index 00000000..a34c4d0f --- /dev/null +++ b/src/PolylineAlgorithm/Properties/GlobalSuppressions.cs @@ -0,0 +1 @@ +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "We do target multiple frameworks. Not all have this method available.")] \ No newline at end of file diff --git a/src/PolylineAlgorithm/PublicAPI.Unshipped.txt b/src/PolylineAlgorithm/PublicAPI.Unshipped.txt index 91b0e1a4..7dc5c581 100644 --- a/src/PolylineAlgorithm/PublicAPI.Unshipped.txt +++ b/src/PolylineAlgorithm/PublicAPI.Unshipped.txt @@ -1 +1 @@ -#nullable enable \ No newline at end of file +#nullable enable diff --git a/src/PolylineAlgorithm/README.md b/src/PolylineAlgorithm/README.md new file mode 100644 index 00000000..d1bf97e0 --- /dev/null +++ b/src/PolylineAlgorithm/README.md @@ -0,0 +1,105 @@ +# PolylineAlgorithm for .NET + +A modern, fully compliant Google Encoded Polyline Algorithm library for .NET Standard 2.1+, supporting strong input validation, extensibility for custom coordinate types, and robust performance. + +## Features + +- Google-compliant polyline encoding/decoding for geographic coordinates +- Extensible APIs for custom coordinate and polyline types (`AbstractPolylineEncoder`, `AbstractPolylineDecoder`) +- Extension methods for encoding from `List` and arrays (`PolylineEncoderExtensions`) +- Robust input validation and descriptive exceptions +- Configurable with `PolylineEncodingOptions` (precision, buffer size, logging) +- 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 +- Auto-generated API docs ([API Reference](https://petesramek.github.io/polyline-algorithm-csharp/)) +- Supports .NET Core, .NET 5+, Xamarin, Unity, Blazor via `netstandard2.1` + +## Installation + +```shell +dotnet add package PolylineAlgorithm +``` + +or via NuGet PMC: + +```powershell +Install-Package PolylineAlgorithm +``` + +## Quick Start + +The library provides abstract base classes to build your own encoder and decoder for any coordinate and polyline type. + +### Implement a custom encoder + +```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(); +} +``` + +### Encode coordinates + +```csharp +using PolylineAlgorithm.Extensions; + +var coordinates = new List<(double Latitude, double Longitude)> +{ + (48.858370, 2.294481), + (51.500729, -0.124625) +}; + +var encoder = new MyPolylineEncoder(); +string encoded = encoder.Encode(coordinates); // extension method for List + +Console.WriteLine(encoded); +``` + +### 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"; + +var decoder = new MyPolylineDecoder(); +IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); +``` + +## Advanced Usage + +- Pass a `PolylineEncodingOptions` (built via `PolylineEncodingOptionsBuilder`) to the encoder/decoder constructor for custom precision, stack-alloc limit, and logging. +- Use static methods on `PolylineEncoding` for low-level normalization, validation, and bit-level read/write operations. + +> See [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) for full documentation. + +## FAQ + +- **What coordinate ranges are valid?** + Latitude: -90..90, Longitude: -180..180 (throws `ArgumentOutOfRangeException` for invalid input) +- **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. +- **Where can I get help?** + [GitHub issues](https://github.com/petesramek/polyline-algorithm-csharp/issues) + +## License + +MIT License © Pete Sramek diff --git a/tests/PolylineAlgorithm.Tests/AbstractPolylineDecoderTest.cs b/tests/PolylineAlgorithm.Tests/AbstractPolylineDecoderTest.cs deleted file mode 100644 index 07c738d2..00000000 --- a/tests/PolylineAlgorithm.Tests/AbstractPolylineDecoderTest.cs +++ /dev/null @@ -1,179 +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; - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Utility; -using System; - -[TestClass] -public class AbstractPolylineDecoderTest { - private static readonly PolylineDecoder _decoder = new(); - - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - public static IEnumerable<(double, double)> NotANumberAndInfinityCoordinates => StaticValueProvider.Invalid.GetNotANumberAndInfinityCoordinates(); - - public static IEnumerable<(double, double)> MinAndMaxCoordinates => StaticValueProvider.Invalid.GetMinAndMaxCoordinates(); - - public static IEnumerable InvalidPolylines => StaticValueProvider.Invalid.GetInvalidPolylines().Select(p => [p]); - - [TestMethod] - public void Constructor_Parameterless_Ok() { - // Arrange && Act - var decoder = new PolylineDecoder(); - - // Assert - Assert.IsNotNull(decoder); - Assert.IsNotNull(decoder.Options); - } - - [TestMethod] - public void Constructor_Options_Instance_Ok() { - // Arrange - var options = new PolylineEncodingOptions(); - - // Act - var decoder = new PolylineDecoder(options); - - // Assert - Assert.IsNotNull(decoder); - Assert.AreSame(options, decoder.Options); - } - - [TestMethod] - public void Constructor_Null_Options_Throws_ArgumentNullException() { - // Arrange - static void New() => new PolylineDecoder(null!); - - // Act - var exception = Assert.ThrowsExactly(New); - - // Assert - Assert.AreEqual("options", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Decode_NullPolyline_Throws_ArgumentException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => _decoder.Decode(null!).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("polyline", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Decode_EmptyPolyline_Throws_ArgumentException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => _decoder.Decode(string.Empty).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("polyline", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Decode_WhitespacePolyline_Throws_ArgumentException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => _decoder.Decode(" ").ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("polyline", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(InvalidPolylines), DynamicDataSourceType.Property)] - public void Decode_InvalidPolyline_Throws_InvalidPolylineException(string polyline) { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - void Decode() => _decoder.Decode(polyline).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - - [TestMethod] - public void Decode_ShortPolyline_Throws_InvalidPolylineException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => _decoder.Decode("?").ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount))] - public void Encode_RandomValue_ValidInput_Ok(int count) { - // Arrange - string polyline = RandomValueProvider.GetPolyline(count); - IEnumerable<(double Latitude, double Longitude)> expected = RandomValueProvider.GetCoordinates(count); - - // Act - var result = _decoder.Decode(polyline); - - // Assert - CollectionAssert.AreEqual(expected.ToArray(), result.ToArray()); - } - - [TestMethod] - public void Decode_StaticValue_ValidInput_Ok() { - // Arrange - string polyline = StaticValueProvider.Valid.GetPolyline(); - IEnumerable<(double Latitude, double Longitude)> expected = StaticValueProvider.Valid.GetCoordinates(); - - // Act - var result = _decoder.Decode(polyline); - - // Assert - CollectionAssert.AreEqual(expected.ToArray(), result.ToArray()); - } - - public class PolylineDecoder : AbstractPolylineDecoder { - public PolylineDecoder() - : base() { } - - public PolylineDecoder(PolylineEncodingOptions options) - : base(options) { } - - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { - return (latitude, longitude); - } - - protected override ReadOnlyMemory GetReadOnlyMemory(string? polyline) { - return polyline.AsMemory(); - } - } -} diff --git a/tests/PolylineAlgorithm.Tests/AbstractPolylineEncoderTest.cs b/tests/PolylineAlgorithm.Tests/AbstractPolylineEncoderTest.cs deleted file mode 100644 index daf05237..00000000 --- a/tests/PolylineAlgorithm.Tests/AbstractPolylineEncoderTest.cs +++ /dev/null @@ -1,165 +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; - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Utility; -using System; - -[TestClass] -public class AbstractPolylineEncoderTest { - private static readonly PolylineEncoder _encoder = new(); - - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - public static IEnumerable<(double, double)> NotANumberAndInfinityCoordinates => StaticValueProvider.Invalid.GetNotANumberAndInfinityCoordinates(); - - public static IEnumerable<(double, double)> MinAndMaxCoordinates => StaticValueProvider.Invalid.GetMinAndMaxCoordinates(); - - - [TestMethod] - public void Constructor_Parameterless_Ok() { - // Arrange && Act - var encoder = new PolylineEncoder(); - - // Assert - Assert.IsNotNull(encoder); - Assert.IsNotNull(encoder.Options); - } - - [TestMethod] - public void Constructor_ValidOptions_Ok() { - // Arrange - var options = new PolylineEncodingOptions(); - - // Act - var encoder = new PolylineEncoder(options); - - // Assert - Assert.IsNotNull(encoder); - Assert.AreSame(options, encoder.Options); - } - - - [TestMethod] - public void Constructor_Null_Options_Throws_ArgumentNullException() { - // Arrange - static void New() => new PolylineEncoder(null!); - - // Act - var exception = Assert.ThrowsExactly(New); - - // Assert - Assert.AreEqual("options", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Encode_NullCoordinates_Throws_ArgumentException() { - // Arrange - static void Encode() => _encoder.Encode(null!); - - // Act - var exception = Assert.ThrowsExactly(Encode); - - // Assert - Assert.AreEqual("coordinates", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Encode_EmptyCoordinates_Throws_ArgumentException() { - // Arrange - static void Encode() => _encoder.Encode([]); - - // Act - var exception = Assert.ThrowsExactly(Encode); - - // Assert - Assert.AreEqual("coordinates", exception.ParamName); - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Encode_BufferTooSmall_Throws_InternalBufferOverflowException() { - // Arrange - PolylineEncoder _encoder = new(new PolylineEncodingOptions { MaxBufferSize = 12 }); - IEnumerable<(double Latitude, double Longitude)> coordinates = RandomValueProvider.GetCoordinates(2); - - // Act - var exception = Assert.ThrowsExactly(() => _encoder.Encode(coordinates)); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(NotANumberAndInfinityCoordinates))] - public void Encode_NotANumberAndInfinityCoordinate_Throws_ArgumentOutOfRangeException((double, double) coordinate) { - // Arrange - - // Act - var exception = Assert.ThrowsExactly(() => _encoder.Encode([coordinate])); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(MinAndMaxCoordinates))] - public void Encode_MinAndMaxCoordinate_Throws_ArgumentOutOfRangeException((double, double) coordinate) { - // Arrange - - // Act - var exception = Assert.ThrowsExactly(() => _encoder.Encode([coordinate])); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount))] - public void Encode_RandomValue_ValidInput_Ok(int count) { - // Arrange - IEnumerable<(double Latitude, double Longitude)> coordinates = RandomValueProvider.GetCoordinates(count); - string expected = RandomValueProvider.GetPolyline(count); - - // Act - var result = _encoder.Encode(coordinates); - - // Assert - Assert.AreEqual(expected.Length, result.Length); - Assert.IsTrue(expected.Equals(result)); - } - - [TestMethod] - public void Encode_StaticValue_ValidInput_Ok() { - // Arrange - IEnumerable<(double Latitude, double Longitude)> coordinates = StaticValueProvider.Valid.GetCoordinates(); - string expected = StaticValueProvider.Valid.GetPolyline(); - - // Act - var result = _encoder.Encode(coordinates); - - // Assert - Assert.AreEqual(expected.Length, result.Length); - Assert.IsTrue(expected.Equals(result)); - } - - public class PolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { - public PolylineEncoder() - : base() { } - - public PolylineEncoder(PolylineEncodingOptions options) - : base(options) { } - - protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); - protected override double GetLatitude((double Latitude, double Longitude) coordinate) => coordinate.Latitude; - protected override double GetLongitude((double Latitude, double Longitude) coordinate) => coordinate.Longitude; - } -} diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs new file mode 100644 index 00000000..c6734c6c --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.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.Tests.Abstraction; + +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Utility; +using System; +using System.Collections.Generic; + +/// +/// 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); + } + + private sealed class TestStringDecoderWithOptions : AbstractPolylineDecoder { + public TestStringDecoderWithOptions(PolylineEncodingOptions options) + : base(options) { } + + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); + } + + /// + /// Tests that Decode with a null polyline throws . + /// + [TestMethod] + public void Decode_With_Null_Polyline_Throws_ArgumentNullException() { + // Arrange + TestStringDecoder decoder = new(); + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => decoder.Decode((string?)null!).ToList()); + Assert.AreEqual("polyline", ex.ParamName); + } + + /// + /// Tests that Decode with an empty polyline throws . + /// + [TestMethod] + public void Decode_With_Empty_Polyline_Throws_InvalidPolylineException() { + // Arrange + TestStringDecoder decoder = new(); + + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode(string.Empty).ToList()); + } + + /// + /// Tests that Decode with a polyline containing an invalid character throws . + /// + [TestMethod] + public void Decode_With_Invalid_Character_Polyline_Throws_InvalidPolylineException() { + // Arrange + TestStringDecoder decoder = new(); + + // '!' (33) is below allowed range ('?' == 63) + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode("!").ToList()); + } + + /// + /// Tests that Decode with a valid polyline returns the expected coordinates. + /// + [TestMethod] + public void Decode_With_Valid_Polyline_Returns_Expected_Coordinates() { + // Arrange + TestStringDecoder 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); + } + } + + /// + /// Tests that the options constructor with null throws . + /// + [TestMethod] + public void Constructor_With_Null_Options_Throws_ArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringDecoderWithOptions(null!)); + Assert.AreEqual("options", ex.ParamName); + } + + /// + /// Tests that the Options property returns the configured options. + /// + [TestMethod] + public void Options_With_Default_Returns_Default_Options() { + // Arrange + TestStringDecoder decoder = new(); + + // Assert + Assert.IsNotNull(decoder.Options); + Assert.AreEqual(5u, decoder.Options.Precision); + } + + /// + /// Tests that the options constructor stores the provided options. + /// + [TestMethod] + public void Constructor_With_Options_Stores_Options() { + // Arrange + PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(7) + .Build(); + + // Act + TestStringDecoderWithOptions decoder = new(options); + + // Assert + Assert.AreSame(options, decoder.Options); + } + + /// + /// Tests that Decode with a pre-cancelled token throws . + /// + [TestMethod] + public void Decode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { + // Arrange + TestStringDecoder decoder = new(); + string polyline = StaticValueProvider.Valid.GetPolyline(); + using CancellationTokenSource cts = new(); + cts.Cancel(); + + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode(polyline, cts.Token).ToList()); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs new file mode 100644 index 00000000..f537dda2 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs @@ -0,0 +1,152 @@ +// +// 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.Abstraction; + +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Utility; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class AbstractPolylineEncoderTests { + private sealed class TestStringEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + public TestStringEncoder() + : base() { } + + public TestStringEncoder(PolylineEncodingOptions options) + : base(options) { } + + 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; + } + + /// + /// 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); + } + + /// + /// Tests that the options constructor with null throws . + /// + [TestMethod] + public void Constructor_With_Null_Options_Throws_ArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringEncoder(null!)); + Assert.AreEqual("options", ex.ParamName); + } + + /// + /// Tests that the options constructor stores the provided options. + /// + [TestMethod] + public void Constructor_With_Options_Stores_Options() { + // Arrange + PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(7) + .Build(); + + // Act + TestStringEncoder encoder = new(options); + + // Assert + Assert.AreSame(options, encoder.Options); + } + + /// + /// Tests that Encode with an empty span throws . + /// + [TestMethod] + public void Encode_With_Empty_Span_Throws_ArgumentException() { + // Arrange + TestStringEncoder encoder = new(); + + // Act & Assert + Assert.ThrowsExactly(() => encoder.Encode(ReadOnlySpan<(double, double)>.Empty)); + } + + /// + /// Tests that Encode with a single valid coordinate returns a non-empty string. + /// + [TestMethod] + public void Encode_With_Single_Coordinate_Returns_Non_Empty_String() { + // Arrange + TestStringEncoder encoder = new(); + (double, double)[] coordinates = [(0.0, 0.0)]; + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Length > 0); + } + + /// + /// Tests that Encode with known coordinates returns 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()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.AreEqual(expected, result); + } + + /// + /// Tests that Encode with a pre-cancelled token throws . + /// + [TestMethod] + public void Encode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() { + // Arrange + TestStringEncoder encoder = new(); + 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)); + } + + /// + /// Tests that Encode still produces the correct result when the buffer exceeds the stack allocation + /// limit, forcing heap allocation via . + /// + [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()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.AreEqual(expected, result); + } +} diff --git a/tests/PolylineAlgorithm.Tests/CoordinateTest.cs b/tests/PolylineAlgorithm.Tests/CoordinateTest.cs deleted file mode 100644 index b5d3eae1..00000000 --- a/tests/PolylineAlgorithm.Tests/CoordinateTest.cs +++ /dev/null @@ -1,253 +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; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -/// -/// Tests for the type. -/// -[TestClass] -public class CoordinateTest { - /// - /// Provides test data for the method. - /// - public static IEnumerable Constructor_Valid_Parameters => [ - [90, 180], - [-90, -180], - [90, -180], - [-90, 180], - ]; - - /// - /// Provides test data for the method. - /// - public static IEnumerable Constructor_Invalid_Parameters => [ - [double.MinValue, 0], - [double.MaxValue, 0], - [double.NaN, 0], - [double.PositiveInfinity, 0], - [double.NegativeInfinity, 0], - [0, double.MinValue], - [0, double.MaxValue], - [0, double.NaN], - [0, double.PositiveInfinity], - [0, double.NegativeInfinity] - ]; - - /// - /// Tests the parameterless constructor of the class. - /// - [TestMethod] - public void Constructor_Parameterless_Ok() { - // Arrange - bool @default = true; - double latitude = 0d; - double longitude = 0d; - - // Act - Coordinate result = new(); - - // Assert - Assert.AreEqual(@default, result.IsDefault()); - Assert.AreEqual(latitude, result.Latitude); - Assert.AreEqual(longitude, result.Longitude); - } - - /// - /// Tests the constructor with valid parameters. - /// - /// The latitude value. - /// The longitude value. - [TestMethod] - [DynamicData(nameof(Constructor_Valid_Parameters))] - public void Constructor_Valid_Parameters_Ok(double latitude, double longitude) { - // Arrange & Act - Coordinate coordinate = new(latitude, longitude); - - // Assert - Assert.IsFalse(coordinate.IsDefault()); - Assert.AreEqual(latitude, coordinate.Latitude); - Assert.AreEqual(longitude, coordinate.Longitude); - } - - /// - /// Tests the constructor with invalid parameters. - /// - /// The latitude value. - /// The longitude value. - [TestMethod] - [DynamicData(nameof(Constructor_Invalid_Parameters))] - public void Constructor_Invalid_Parameters_Ok(double latitude, double longitude) { - // Arrange - // Act - static void New(double latitude, double longitude) => new Coordinate(latitude, longitude); - - // Assert - Assert.ThrowsExactly(() => New(latitude, longitude)); - } - - /// - /// Tests the method. - /// - /// The latitude value. - /// The longitude value. - [TestMethod] - [DynamicData(nameof(Constructor_Valid_Parameters))] - public void Deconstruct_Equals_Parameters(double latitude, double longitude) { - // Arrange - // Act - Coordinate coordinate = new(latitude, longitude); - - // Assert - Assert.AreEqual(latitude, coordinate.Latitude); - Assert.AreEqual(longitude, coordinate.Longitude); - } - - /// - /// Tests the method with equal coordinates. - /// - /// The latitude value. - /// The longitude value. - [TestMethod] - [DynamicData(nameof(Constructor_Valid_Parameters))] - public void Equals_Coordinate_True(double latitude, double longitude) { - // Arrange - Coordinate @this = new(latitude, longitude); - Coordinate other = new(latitude, longitude); - - // Act & Assert - Assert.IsTrue(@this.Equals(other)); - } - - /// - /// Tests the method with unequal coordinates. - /// - /// The latitude value. - /// The longitude value. - [TestMethod] - [DynamicData(nameof(Constructor_Valid_Parameters))] - public void Equals_Coordinate_False(double latitude, double longitude) { - // Arrange - Coordinate @this = new(latitude, longitude); - Coordinate other = new(0, 0); - - // Act & Assert - Assert.IsFalse(@this.Equals(other)); - } - - /// - /// Tests the constructor with latitude out of range. - /// - [TestMethod] - public void Constructor_Latitude_OutOfRange_Throws() { - // Arrange & Act - static void OverMaxLatitude() => new Coordinate(91, 0); - static void UnderMinLatitude() => new Coordinate(-91, 0); - - // Assert - Assert.ThrowsExactly(UnderMinLatitude); - Assert.ThrowsExactly(OverMaxLatitude); - } - - /// - /// Tests the constructor with longitude out of range. - /// - [TestMethod] - public void Constructor_Longitude_OutOfRange_Throws() { - // Arrange & Act - static void UnderMinLongitude() => new Coordinate(0, -181); - static void OverMaxLongitude() => new Coordinate(0, 181); - - // Assert - Assert.ThrowsExactly(UnderMinLongitude); - Assert.ThrowsExactly(OverMaxLongitude); - } - - /// - /// Tests the constructor with boundary values. - /// - [TestMethod] - public void Constructor_Boundary_Values_Ok() { - // Arrange - const int MinLatitude = -90; - const int MinLongitude = -180; - const int MaxLatitude = 90; - const int MaxLongitude = 180; - - // Act - Coordinate min = new(MinLatitude, MinLongitude); - Coordinate max = new(MaxLatitude, MaxLongitude); - - // Assert - Assert.AreEqual(MinLatitude, min.Latitude); - Assert.AreEqual(-MaxLongitude, min.Longitude); - Assert.AreEqual(MaxLatitude, max.Latitude); - Assert.AreEqual(MaxLongitude, max.Longitude); - } - - /// - /// Tests the method with various cases. - /// - [TestMethod] - public void Equals_Object_True_And_False() { - // Arrange - Coordinate coordinate = new(10, 20); - object equalCoordinate = new Coordinate(10, 20); - object notEqualCoordinate = new Coordinate(0, 0); - object notCoordinate = "not a coordinate"; - object @null = null!; - - // Act & Assert - Assert.IsTrue(coordinate.Equals(equalCoordinate)); - Assert.IsFalse(coordinate.Equals(notEqualCoordinate)); - Assert.IsFalse(coordinate.Equals(notCoordinate)); - Assert.IsFalse(coordinate.Equals(@null)); - } - - /// - /// Tests the method for equal coordinates. - /// - [TestMethod] - public void GetHashCode_Equal_For_Equal_Coordinates() { - // Arrange - Coordinate first = new(10, 20); - Coordinate second = new(10, 20); - - // Act & Assert - Assert.AreEqual(first.GetHashCode(), second.GetHashCode()); - } - - /// - /// Tests the method for correct formatting. - /// - [TestMethod] - public void ToString_Format_Ok() { - /// Arrange - var coordinate = new Coordinate(12.34, 56.78); - - // Act & Assert - Assert.Contains("Latitude: 12.34", coordinate.ToString()); - Assert.Contains("Longitude: 56.78", coordinate.ToString()); - } - - /// - /// Tests the equality operators for the type. - /// - [TestMethod] - public void Equality_Operators_Ok() { - // Arrange - Coordinate coordinate = new(10, 20); - Coordinate equalCoordinate = new(10, 20); - Coordinate notEqualCoordinate = new(0, 0); - - // Act & Assert - Assert.IsTrue(coordinate == equalCoordinate); - Assert.IsFalse(coordinate != equalCoordinate); - Assert.IsTrue(coordinate != notEqualCoordinate); - Assert.IsFalse(coordinate == notEqualCoordinate); - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs new file mode 100644 index 00000000..c44c914d --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs @@ -0,0 +1,172 @@ +// +// 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 new file mode 100644 index 00000000..da8a622e --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs @@ -0,0 +1,123 @@ +// +// 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 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); + } + + // ----- Encode(T[]) ----- + + /// + /// Tests that Encode with a null encoder throws . + /// + [TestMethod] + public void Encode_With_Array_Null_Encoder_Throws_ArgumentNullException() { + // Arrange — call the extension method explicitly because IPolylineEncoder.Encode(ReadOnlySpan) + // would be preferred over the extension when calling through method syntax with an array argument. + IPolylineEncoder<(double, double), string>? encoder = null; + (double, double)[] coordinates = [(0.0, 0.0)]; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => PolylineEncoderExtensions.Encode<(double, double), string>(encoder!, coordinates)); + Assert.AreEqual("encoder", ex.ParamName); + } + + /// + /// Tests that Encode with a null array throws . + /// + [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(); + (double, double)[]? coordinates = null; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => PolylineEncoderExtensions.Encode<(double, double), string>(encoder, coordinates!)); + Assert.AreEqual("coordinates", ex.ParamName); + } + + /// + /// Tests that Encode with a valid array returns the expected polyline. + /// + [TestMethod] + public void Encode_With_Array_Valid_Coordinates_Returns_Expected_Polyline() { + // Arrange + TestStringEncoder encoder = new(); + (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expected, result); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Fakes/FakeLoggerFactory.cs b/tests/PolylineAlgorithm.Tests/Fakes/FakeLoggerFactory.cs deleted file mode 100644 index 4b7e71e0..00000000 --- a/tests/PolylineAlgorithm.Tests/Fakes/FakeLoggerFactory.cs +++ /dev/null @@ -1,41 +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.Fakes; - -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; - -internal class FakeLoggerFactory : ILoggerFactory { - private bool _isDisposed; - public FakeLoggerFactory(FakeLoggerProvider loggerProvider) { - Provider = loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider)); - } - - public ILoggerProvider Provider { get; private set; } - - public void AddProvider(ILoggerProvider provider) { - Provider = provider; - } - - public ILogger CreateLogger(string categoryName) { - return Provider.CreateLogger(categoryName); - } - - protected virtual void Dispose(bool disposing) { - if (!_isDisposed) { - if (disposing) { - - } - - _isDisposed = true; - } - } - - public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs new file mode 100644 index 00000000..1e915fe2 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs @@ -0,0 +1,136 @@ +// +// 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/Internal/CoordinateVarianceTest.cs b/tests/PolylineAlgorithm.Tests/Internal/CoordinateVarianceTest.cs deleted file mode 100644 index 84d1be42..00000000 --- a/tests/PolylineAlgorithm.Tests/Internal/CoordinateVarianceTest.cs +++ /dev/null @@ -1,94 +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; - -[TestClass] -public class CoordinateVarianceTests { - public static IEnumerable<(int Latitude, int Longitude)> Coordinates => [ - (0, 0), - (-10, -10), - (10, -10), - (-10, 10), - (10, 10) - ]; - - public static IEnumerable<((int Latitude, int Longitude) Initial, (int Latitude, int Longitude) Next, (int Latitude, int Longitude) Result)> Variances => [ - ((10, 10), (-20, -20), (-30, -30)), - ((-10, -10), (20, 20), (30, 30)), - ((0, 10), (10, -10), (10, -20)), - ((0, -10), (10, 10), (10, 20)), - ((10, 0), (10, -10), (0, -10)), - ((-10, 0), (10, 10), (20, 10)), - ((10, -10), (-10, 10), (-20, 20)), - ((-10, 10), (10, 10), (20, 0)), - ((10, 10), (10, 0), (0, -10)), - ((-10, -10), (-10, 0), (0, 10)), - ((10, 10), (0, 0), (-10, -10)), - ((-10, -10), (0, 0), (10, 10)), - ((10, -10), (0, 0), (-10, 10)), - ((-10, 10), (0, 0), (10, -10)), - ((0, 10), (0, 0), (0, -10)), - ((0, -10), (0, 0), (0, 10)), - ((10, 0), (0, 0), (-10, 0)), - ((-10, 0), (0, 0), (10, 0)) - ]; - - [TestMethod] - public void Constructor_Sets_Defaults() { - // Arrange & Act - CoordinateVariance variance = new(); - // Assert - Assert.AreEqual(0, variance.Latitude); - Assert.AreEqual(0, variance.Longitude); - } - - [TestMethod] - [DynamicData(nameof(Coordinates), DynamicDataSourceType.Property)] - public void Next_Calculates_Correct_Variance_From_Default_Variance(int latitude, int longitude) { - // Arrange - CoordinateVariance variance = new(); - var expected = (latitude, longitude); - - // Act - variance.Next(latitude, longitude); - - // Assert - Assert.AreEqual(expected.latitude, variance.Latitude); - Assert.AreEqual(expected.longitude, variance.Longitude); - } - - [TestMethod] - [DynamicData(nameof(Variances), DynamicDataSourceType.Property)] - public void Next_Calculates_Correct_Variance_From_Previous_Variance((int Latitude, int Longitude) initial, (int Latitude, int Longitude) next, (int Latitude, int Longitude) expected) { - // Arrange - CoordinateVariance variance = new(); - variance.Next(initial.Latitude, initial.Longitude); - - // Act - variance.Next(next.Latitude, next.Longitude); - - // Assert - Assert.AreEqual(expected.Latitude, variance.Latitude); - Assert.AreEqual(expected.Longitude, variance.Longitude); - } - - [TestMethod] - [DynamicData(nameof(Coordinates), DynamicDataSourceType.Property)] - public void ToString_Returns_Value_Containing_Variance(int latitude, int longitude) { - // Arrange - CoordinateVariance variance = new(); - variance.Next(latitude, longitude); - - // Act - string result = variance.ToString(); - - // Assert - Assert.Contains($"Latitude: {latitude}", result); - Assert.Contains($"Longitude: {longitude}", result); - } -} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs new file mode 100644 index 00000000..b49e719a --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs @@ -0,0 +1,609 @@ +// +// 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.Diagnostics; + +using PolylineAlgorithm.Internal.Diagnostics; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class ExceptionGuardTests { + /// + /// Tests that ThrowNotFiniteNumber throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + public void ThrowNotFiniteNumber_With_Param_Name_Throws_ArgumentOutOfRangeException() { + // Arrange + const string paramName = "value"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowNotFiniteNumber(paramName)); + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowArgumentNull throws ArgumentNullException with correct parameter name. + /// + [TestMethod] + public void ThrowArgumentNull_With_Param_Name_Throws_ArgumentNullException() { + // Arrange + const string paramName = "input"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowArgumentNull(paramName)); + Assert.AreEqual(paramName, ex.ParamName); + } + + /// + /// Tests that ThrowBufferOverflow throws OverflowException with correct message. + /// + [TestMethod] + public void ThrowBufferOverflow_With_Message_Throws_OverflowException() { + // Arrange + const string message = "Buffer overflow occurred."; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowBufferOverflow(message)); + Assert.AreEqual(message, ex.Message); + } + + /// + /// Tests that ThrowCoordinateValueOutOfRange throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + public void ThrowCoordinateValueOutOfRange_With_Parameters_Throws_ArgumentOutOfRangeException() { + // Arrange + const double value = 100.0; + const double min = -90.0; + const double max = 90.0; + const string paramName = "latitude"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowCoordinateValueOutOfRange(value, min, max, paramName)); + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that StackAllocLimitMustBeEqualOrGreaterThan throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + public void StackAllocLimitMustBeEqualOrGreaterThan_With_Parameters_Throws_ArgumentOutOfRangeException() { + // Arrange + const int minValue = 10; + const string paramName = "stackAllocLimit"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(minValue, paramName)); + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowArgumentCannotBeEmptyEnumerationMessage throws ArgumentException with correct parameter name. + /// + [TestMethod] + public void ThrowArgumentCannotBeEmptyEnumerationMessage_With_Param_Name_Throws_ArgumentException() { + // Arrange + const string paramName = "collection"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(paramName)); + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowCouldNotWriteEncodedValueToBuffer throws InvalidOperationException with correct message. + /// + [TestMethod] + public void ThrowCouldNotWriteEncodedValueToBuffer_Throws_InvalidOperationException() { + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer()); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength throws ArgumentException with correct parameter name. + /// + [TestMethod] + public void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_With_Parameters_Throws_ArgumentException() { + // Arrange + const int destinationLength = 5; + const int polylineLength = 10; + const string paramName = "destination"; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength, paramName)); + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowInvalidPolylineLength throws InvalidPolylineException with correct message. + /// + [TestMethod] + public void ThrowInvalidPolylineLength_With_Parameters_Throws_InvalidPolylineException() { + // Arrange + const int length = 5; + const int min = 10; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowInvalidPolylineLength(length, min)); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowInvalidPolylineCharacter throws InvalidPolylineException with correct message. + /// + [TestMethod] + public void ThrowInvalidPolylineCharacter_With_Parameters_Throws_InvalidPolylineException() { + // Arrange + const char character = '!'; + const int position = 15; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowInvalidPolylineCharacter(character, position)); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowPolylineBlockTooLong throws InvalidPolylineException with correct message. + /// + [TestMethod] + public void ThrowPolylineBlockTooLong_With_Position_Throws_InvalidPolylineException() { + // Arrange + const int position = 42; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowPolylineBlockTooLong(position)); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowInvalidPolylineFormat throws InvalidPolylineException with correct message. + /// + [TestMethod] + public void ThrowInvalidPolylineFormat_With_Position_Throws_InvalidPolylineException() { + // Arrange + const long position = 100L; + + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowInvalidPolylineFormat(position)); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that ThrowInvalidPolylineBlockTerminator throws InvalidPolylineException with correct message. + /// + [TestMethod] + public void ThrowInvalidPolylineBlockTerminator_Throws_InvalidPolylineException() { + // Act & Assert + var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowInvalidPolylineBlockTerminator()); + Assert.IsNotNull(ex.Message); + } + + /// + /// Tests that FormatStackAllocLimitMustBeEqualOrGreaterThan returns formatted message with specified value. + /// + [TestMethod] + public void FormatStackAllocLimitMustBeEqualOrGreaterThan_With_Min_Value_Returns_Formatted_Message() { + // Arrange + const int minValue = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatStackAllocLimitMustBeEqualOrGreaterThan(minValue); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatPolylineCannotBeShorterThan returns formatted message with specified values. + /// + [TestMethod] + public void FormatPolylineCannotBeShorterThan_With_Length_And_Min_Length_Returns_Formatted_Message() { + // Arrange + const int length = 5; + const int minLength = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineCannotBeShorterThan(length, minLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatMalformedPolyline returns formatted message with position. + /// + [TestMethod] + public void FormatMalformedPolyline_With_Position_Returns_Formatted_Message() { + // Arrange + const long position = 42L; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatMalformedPolyline(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("42", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatMalformedPolyline with zero position returns formatted message. + /// + [TestMethod] + public void FormatMalformedPolyline_With_Zero_Position_Returns_Formatted_Message() { + // Arrange + const long position = 0L; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatMalformedPolyline(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + } + + /// + /// Tests that FormatMalformedPolyline with negative position returns formatted message. + /// + [TestMethod] + public void FormatMalformedPolyline_With_Negative_Position_Returns_Formatted_Message() { + // Arrange + const long position = -10L; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatMalformedPolyline(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("-10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatMalformedPolyline with large position returns formatted message. + /// + [TestMethod] + public void FormatMalformedPolyline_With_Large_Position_Returns_Formatted_Message() { + // Arrange + const long position = long.MaxValue; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatMalformedPolyline(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains(long.MaxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); + } + + /// + /// Tests that FormatCoordinateValueMustBeBetween returns formatted message with all parameters. + /// + [TestMethod] + public void FormatCoordinateValueMustBeBetween_With_Parameters_Returns_Formatted_Message() { + // Arrange + const string name = "latitude"; + const double min = -90.0; + const double max = 90.0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatCoordinateValueMustBeBetween(name, min, max); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("latitude", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("-90", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("90", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatCoordinateValueMustBeBetween with positive values returns formatted message. + /// + [TestMethod] + public void FormatCoordinateValueMustBeBetween_With_Positive_Values_Returns_Formatted_Message() { + // Arrange + const string name = "longitude"; + const double min = 0.0; + const double max = 180.0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatCoordinateValueMustBeBetween(name, min, max); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("longitude", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("180", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatCoordinateValueMustBeBetween with fractional values returns formatted message. + /// + [TestMethod] + public void FormatCoordinateValueMustBeBetween_With_Fractional_Values_Returns_Formatted_Message() { + // Arrange + const string name = "value"; + const double min = 1.5; + const double max = 10.75; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatCoordinateValueMustBeBetween(name, min, max); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("value", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatPolylineBlockTooLong returns formatted message with position. + /// + [TestMethod] + public void FormatPolylineBlockTooLong_With_Position_Returns_Formatted_Message() { + // Arrange + const int position = 15; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineBlockTooLong(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("15", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatPolylineBlockTooLong with zero position returns formatted message. + /// + [TestMethod] + public void FormatPolylineBlockTooLong_With_Zero_Position_Returns_Formatted_Message() { + // Arrange + const int position = 0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineBlockTooLong(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + } + + /// + /// Tests that FormatPolylineBlockTooLong with large position returns formatted message. + /// + [TestMethod] + public void FormatPolylineBlockTooLong_With_Large_Position_Returns_Formatted_Message() { + // Arrange + const int position = int.MaxValue; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineBlockTooLong(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains(int.MaxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineCharacter returns formatted message with character and position. + /// + [TestMethod] + public void FormatInvalidPolylineCharacter_With_Character_And_Position_Returns_Formatted_Message() { + // Arrange + const char character = '!'; + const int position = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('!', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineCharacter with letter character returns formatted message. + /// + [TestMethod] + public void FormatInvalidPolylineCharacter_With_Letter_Character_Returns_Formatted_Message() { + // Arrange + const char character = 'Z'; + const int position = 5; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('Z', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains('5', StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineCharacter with special character returns formatted message. + /// + [TestMethod] + public void FormatInvalidPolylineCharacter_With_Special_Character_Returns_Formatted_Message() { + // Arrange + const char character = '@'; + const int position = 0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('@', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength returns formatted message. + /// + [TestMethod] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_With_Lengths_Returns_Formatted_Message() { + // Arrange + const int destinationLength = 5; + const int polylineLength = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with zero destination length returns formatted message. + /// + [TestMethod] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_With_Zero_Destination_Length_Returns_Formatted_Message() { + // Arrange + const int destinationLength = 0; + const int polylineLength = 100; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("100", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with large values returns formatted message. + /// + [TestMethod] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_With_Large_Values_Returns_Formatted_Message() { + // Arrange + const int destinationLength = 1000; + const int polylineLength = 2000; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("1000", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("2000", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineLength returns formatted message with length and min values. + /// + [TestMethod] + public void FormatInvalidPolylineLength_With_Length_And_Min_Returns_Formatted_Message() { + // Arrange + const int length = 5; + const int min = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineLength(length, min); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineLength with zero length returns formatted message. + /// + [TestMethod] + public void FormatInvalidPolylineLength_With_Zero_Length_Returns_Formatted_Message() { + // Arrange + const int length = 0; + const int min = 1; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineLength(length, min); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + Assert.IsTrue(result.Contains('1', StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineLength with negative values returns formatted message. + /// + [TestMethod] + public void FormatInvalidPolylineLength_With_Negative_Values_Returns_Formatted_Message() { + // Arrange + const int length = -5; + const int min = 0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineLength(length, min); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("-5", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains('0', StringComparison.Ordinal)); + } + + /// + /// Tests that GetArgumentValueMustBeFiniteNumber returns non-null message. + /// + [TestMethod] + public void GetArgumentValueMustBeFiniteNumber_Returns_Non_Null_Message() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetArgumentValueMustBeFiniteNumber(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetCouldNotWriteEncodedValueToTheBuffer returns non-null message. + /// + [TestMethod] + public void GetCouldNotWriteEncodedValueToTheBuffer_Returns_Non_Null_Message() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetCouldNotWriteEncodedValueToTheBuffer(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetArgumentCannotBeEmpty returns non-null message. + /// + [TestMethod] + public void GetArgumentCannotBeEmpty_Returns_Non_Null_Message() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetArgumentCannotBeEmpty(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetInvalidPolylineBlockTerminator returns non-null message. + /// + [TestMethod] + public void GetInvalidPolylineBlockTerminator_Returns_Non_Null_Message() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetInvalidPolylineBlockTerminator(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } +} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs new file mode 100644 index 00000000..0dff7193 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs @@ -0,0 +1,222 @@ +// +// 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.Diagnostics; + +using Microsoft.Extensions.Logging; +using PolylineAlgorithm.Internal.Diagnostics; +using System; +using System.Collections.Generic; +using System.Globalization; + +/// +/// Tests for . +/// +[TestClass] +public sealed class LogDebugExtensionsTests { + private sealed class TestLogger : ILogger { + public List<(LogLevel Level, EventId EventId, string Message, Exception? Exception)> Logs { get; } = []; + + public IDisposable BeginScope(TState state) + where TState : notnull => NullScope.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + Logs.Add((logLevel, eventId, formatter(state, exception), exception)); + } + + private sealed class NullScope : IDisposable { + public static NullScope Instance { get; } = new(); + public void Dispose() { } + } + } + + /// + /// Tests that LogOperationStartedDebug WithOperationName LogsStartedMessage. + /// + [TestMethod] + public void LogOperationStartedDebug_With_Operation_Name_Logs_Started_Message() { + var logger = new TestLogger(); + const string operationName = "TestOperation"; + + logger.LogOperationStartedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains($"Operation {operationName} has started.", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFailedDebug WithOperationName LogsFailedMessage. + /// + [TestMethod] + public void LogOperationFailedDebug_With_Operation_Name_Logs_Failed_Message() { + var logger = new TestLogger(); + const string operationName = "TestOperation"; + + logger.LogOperationFailedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains($"Operation {operationName} has failed.", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFinishedDebug WithOperationName LogsFinishedMessage. + /// + [TestMethod] + public void LogOperationFinishedDebug_With_Operation_Name_Logs_Finished_Message() { + var logger = new TestLogger(); + const string operationName = "TestOperation"; + + logger.LogOperationFinishedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains($"Operation {operationName} has finished.", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogDecodedCoordinateDebug WithCoordinatesAndPosition LogsDecodedCoordinateMessage. + /// + [TestMethod] + public void LogDecodedCoordinateDebug_With_Coordinates_And_Position_Logs_Decoded_Coordinate_Message() { + var logger = new TestLogger(); + const double latitude = 38.5; + const double longitude = -120.2; + const int position = 42; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains(string.Create(CultureInfo.InvariantCulture, $"Decoded coordinate: (Latitude: {latitude}, Longitude: {longitude}) at position {position}."), logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationStartedDebug WithNullOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationStartedDebug_With_Null_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + const string? operationName = null; + + logger.LogOperationStartedDebug(operationName!); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFailedDebug WithNullOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationFailedDebug_With_Null_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + const string? operationName = null; + + logger.LogOperationFailedDebug(operationName!); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFinishedDebug WithNullOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationFinishedDebug_With_Null_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + const string? operationName = null; + + logger.LogOperationFinishedDebug(operationName!); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogDecodedCoordinateDebug WithZeroCoordinates LogsMessage. + /// + [TestMethod] + public void LogDecodedCoordinateDebug_With_Zero_Coordinates_Logs_Message() { + var logger = new TestLogger(); + const double latitude = 0.0; + const double longitude = 0.0; + const int position = 0; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Decoded coordinate", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogDecodedCoordinateDebug WithNegativeCoordinates LogsMessage. + /// + [TestMethod] + public void LogDecodedCoordinateDebug_With_Negative_Coordinates_Logs_Message() { + var logger = new TestLogger(); + const double latitude = -90.0; + const double longitude = -180.0; + const int position = 100; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains(string.Create(CultureInfo.InvariantCulture, $"Latitude: {latitude}, Longitude: {longitude}"), logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationStartedDebug WithEmptyOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationStartedDebug_With_Empty_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationStartedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFailedDebug WithEmptyOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationFailedDebug_With_Empty_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationFailedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } + + /// + /// Tests that LogOperationFinishedDebug WithEmptyOperationName LogsMessage. + /// + [TestMethod] + public void LogOperationFinishedDebug_With_Empty_Operation_Name_Logs_Message() { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationFinishedDebug(operationName); + + Assert.HasCount(1, logger.Logs); + Assert.AreEqual(LogLevel.Debug, logger.Logs[0].Level); + Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); + } +} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs new file mode 100644 index 00000000..5f19cc8e --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs @@ -0,0 +1,116 @@ +// +// 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.Diagnostics; + +using Microsoft.Extensions.Logging; +using PolylineAlgorithm.Internal.Diagnostics; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +/// +/// Tests for . +/// +[TestClass] +public sealed class LogWarningExtensionsTests { + private sealed class TestLogger : ILogger { + public List<(LogLevel Level, EventId EventId, string Message, Exception? Exception)> Logs { get; } = []; + + public IDisposable BeginScope(TState state) + where TState : notnull => NullScope.Instance; + public bool IsEnabled(LogLevel logLevel) => true; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + Logs.Add((logLevel, eventId, formatter(state, exception), exception)); + } + + private sealed class NullScope : IDisposable { + public static NullScope Instance { get; } = new(); + public void Dispose() { } + } + } + + /// + /// Tests that LogNullArgumentWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogNullArgumentWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogNullArgumentWarning("foo"); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument foo is null.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogEmptyArgumentWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogEmptyArgumentWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogEmptyArgumentWarning("bar"); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument bar is empty.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogInternalBufferOverflowWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogInternalBufferOverflowWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogInternalBufferOverflowWarning(1, 2, 3); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Internal buffer has size of 2. At position 1 is required additional 3 space.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogCannotWriteValueToBufferWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogCannotWriteValueToBufferWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogCannotWriteValueToBufferWarning(4, 5); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Cannot write to internal buffer at position 4. Current coordinate is at index 5.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogPolylineCannotBeShorterThanWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogPolylineCannotBeShorterThanWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogPolylineCannotBeShorterThanWarning(6, 7); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is too short. Minimal length is 7. Actual length is 6.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogRequestedBufferSizeExceedsMaxBufferLengthWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogRequestedBufferSizeExceedsMaxBufferLengthWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogRequestedBufferSizeExceedsMaxBufferLengthWarning(8, 9); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Requested buffer size of 8 exceeds maximum allowed buffer length of 9.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogInvalidPolylineWarning LogsExpectedMessage. + /// + [TestMethod] + public void LogInvalidPolylineWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + logger.LogInvalidPolylineWarning(10); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is invalid or malformed at position 10.", StringComparison.Ordinal))); + } + + /// + /// Tests that LogInvalidPolylineFormatWarning LogsExpectedMessage. + /// + [TestMethod] + [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "No need to be strict in tests.")] + public void LogInvalidPolylineFormatWarning_Logs_Expected_Message() { + var logger = new TestLogger(); + var ex = new Exception("fail"); + logger.LogInvalidPolylineFormatWarning(ex); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is invalid or malformed.", StringComparison.Ordinal) && l.Exception == ex)); + } +} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Internal/LoggingTest.cs b/tests/PolylineAlgorithm.Tests/Internal/LoggingTest.cs deleted file mode 100644 index ba09fa8d..00000000 --- a/tests/PolylineAlgorithm.Tests/Internal/LoggingTest.cs +++ /dev/null @@ -1,224 +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 Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using PolylineAlgorithm.Internal.Logging; -using PolylineAlgorithm.Tests.Fakes; - -[TestClass] -public class LoggingTest { - private static readonly FakeLoggerProvider _loggerProvider = new(); - private static readonly ILoggerFactory _loggerFactory = new FakeLoggerFactory(_loggerProvider); - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("operationName")] - public void ILogger_LogOperationStartedInfo_Ok(string value) { - // Arrange - string operationName = value; - - // Act - _loggerFactory - .CreateLogger() - .LogOperationStartedInfo(operationName); - - // Assert - Assert.AreEqual(new EventId(201, nameof(LogInfoExtensions.LogOperationStartedInfo)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Information, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Operation {value ?? "(null)"} has started.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("operationName")] - public void ILogger_LogOperationFailedInfo_Ok(string value) { - // Arrange - string operationName = value; - - // Act - _loggerFactory - .CreateLogger() - .LogOperationFailedInfo(operationName); - - // Assert - Assert.AreEqual(new EventId(202, nameof(LogInfoExtensions.LogOperationFailedInfo)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Information, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Operation {value ?? "(null)"} has failed.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("operationName")] - public void ILogger_LogOperationFinishedInfo_Ok(string value) { - // Arrange - string operationName = value; - - // Act - _loggerFactory - .CreateLogger() - .LogOperationFinishedInfo(operationName); - - // Assert - Assert.AreEqual(new EventId(203, nameof(LogInfoExtensions.LogOperationFinishedInfo)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Information, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Operation {value ?? "(null)"} has finished.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("argumentName")] - public void ILogger_LogNullArgumentWarning_Ok(string value) { - // Arrange - string argumentName = value; - - // Act - _loggerFactory - .CreateLogger() - .LogNullArgumentWarning(argumentName); - - // Assert - Assert.AreEqual(new EventId(301, nameof(LogWarningExtensions.LogNullArgumentWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual($"Argument {value ?? "(null)"} is null.", _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("argumentName")] - public void ILogger_LogEmptyArgumentWarning_Ok(string value) { - // Arrange - string argumentName = value; - - // Act - _loggerFactory - .CreateLogger() - .LogEmptyArgumentWarning(argumentName); - - // Assert - Assert.AreEqual(new EventId(302, nameof(LogWarningExtensions.LogEmptyArgumentWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual($"Argument {value ?? "(null)"} is empty.", _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - public void ILogger_LogInternalBufferOverflowWarning_Ok() { - // Arrange - int position = 5; - int bufferLength = 10; - int requiredSpace = 15; - - // Act - _loggerFactory - .CreateLogger() - .LogInternalBufferOverflowWarning(position, bufferLength, requiredSpace); - - // Assert - Assert.AreEqual(new EventId(303, nameof(LogWarningExtensions.LogInternalBufferOverflowWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Internal buffer has size of {bufferLength}. At position {position} is required additional {requiredSpace} space.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - public void ILogger_LogCannotWriteValueToBufferWarning_Ok() { - // Arrange - int position = 5; - int index = 1; - - // Act - _loggerFactory - .CreateLogger() - .LogCannotWriteValueToBufferWarning(position, index); - - // Assert - Assert.AreEqual(new EventId(304, nameof(LogWarningExtensions.LogCannotWriteValueToBufferWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Cannot write to internal buffer at position {position}. Current coordinate is at index {index}.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("argumentName")] - public void ILogger_LogPolylineCannotBeShorterThanWarning_Ok(string value) { - // Arrange - string argumentName = value; - int actualLength = 10; - int minimumLength = 5; - - // Act - _loggerFactory - .CreateLogger() - .LogPolylineCannotBeShorterThanWarning(argumentName, actualLength, minimumLength); - - // Assert - Assert.AreEqual(new EventId(305, nameof(LogWarningExtensions.LogPolylineCannotBeShorterThanWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Argument {value} is too short. Minimal length is {minimumLength}. Actual length is {actualLength}.", - _loggerProvider.Collector.LatestRecord.Message); - } - - - [TestMethod] - public void ILogger_LogRequestedBufferSizeExceedsMaxBufferLengthWarning_Ok() { - // Arrange - int requestedBufferLength = 5; - int maxBufferLength = 10; - - // Act - _loggerFactory - .CreateLogger() - .LogRequestedBufferSizeExceedsMaxBufferLengthWarning(requestedBufferLength, maxBufferLength); - - // Assert - Assert.AreEqual(new EventId(306, nameof(LogWarningExtensions.LogRequestedBufferSizeExceedsMaxBufferLengthWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Requested buffer size of {requestedBufferLength} exceeds maximum allowed buffer length of {maxBufferLength}.", - _loggerProvider.Collector.LatestRecord.Message); - } - - [TestMethod] - public void ILogger_LogInvalidPolylineWarning_Ok() { - // Arrange - int position = 5; - - // Act - _loggerFactory - .CreateLogger() - .LogInvalidPolylineWarning(position); - - // Assert - Assert.AreEqual(new EventId(307, nameof(LogWarningExtensions.LogInvalidPolylineWarning)), _loggerProvider.Collector.LatestRecord.Id); - Assert.AreEqual(LogLevel.Warning, _loggerProvider.Collector.LatestRecord.Level); - Assert.AreEqual( - $"Polyline is invalid or malformed at position {position}.", - _loggerProvider.Collector.LatestRecord.Message); - } -} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs b/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs new file mode 100644 index 00000000..ba3fc860 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs @@ -0,0 +1,49 @@ +// +// 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; + +/// +/// Tests for . +/// +[TestClass] +public sealed class Pow10Tests { + /// + /// Tests that GetFactor returns the correct power-of-ten factor for the given precision. + /// + [TestMethod] + [DataRow(0u, 1u)] + [DataRow(1u, 10u)] + [DataRow(2u, 100u)] + [DataRow(3u, 1000u)] + [DataRow(4u, 10000u)] + [DataRow(5u, 100000u)] + [DataRow(6u, 1000000u)] + [DataRow(7u, 10000000u)] + [DataRow(8u, 100000000u)] + [DataRow(9u, 1000000000u)] + public void GetFactor_With_Valid_Precision_Returns_Expected_Value(uint precision, uint expected) { + // Act + uint result = Pow10.GetFactor(precision); + + // Assert + Assert.AreEqual(expected, result); + } + + /// + /// Tests that GetFactor throws when precision causes overflow. + /// + [TestMethod] + [DataRow(10u)] + [DataRow(15u)] + [DataRow(20u)] + [DataRow(uint.MaxValue)] + public void GetFactor_With_Overflowing_Precision_Throws_OverflowException(uint precision) { + // Act & Assert + Assert.ThrowsExactly(() => Pow10.GetFactor(precision)); + } +} diff --git a/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTest.cs b/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTest.cs deleted file mode 100644 index 532b27b8..00000000 --- a/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTest.cs +++ /dev/null @@ -1,31 +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; - -using PolylineAlgorithm; - -/// -/// Defines tests for the type. -/// -[TestClass] -public class InvalidPolylineExceptionTest { - /// - /// Tests the method with an invalid coordinate parameter, expecting an . - /// - [TestMethod] - public void Throw_Method_Invalid_Coordinate_Parameter_PolylineMalformedException_Throw() { - // Arrange - var position = Random.Shared.Next(); - static void ThrowAt(int position) => InvalidPolylineException.Throw(position); - - // Act - var exception = Assert.ThrowsExactly(() => ThrowAt(position)); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - Assert.Contains(position.ToString(), exception.Message); - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs b/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs new file mode 100644 index 00000000..9c0ffcc3 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs @@ -0,0 +1,44 @@ +// +// 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 System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class InvalidPolylineExceptionTests { + /// + /// Tests that the default constructor creates an instance with a null message. + /// + [TestMethod] + public void Constructor_With_Default_Creates_Instance() { + // Act + InvalidPolylineException ex = new(); + + // Assert + Assert.IsNotNull(ex); + Assert.IsNull(ex.InnerException); + } + + /// + /// Tests that the message and inner exception constructor stores both values. + /// + [TestMethod] + public void Constructor_With_Message_And_Inner_Exception_Stores_Both() { + // Arrange + const string message = "polyline is malformed"; + Exception inner = new InvalidOperationException("inner"); + + // Act + InvalidPolylineException ex = new(message, inner); + + // Assert + Assert.AreEqual(message, ex.Message); + Assert.AreSame(inner, ex.InnerException); + } +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj index 974f5cd5..ff88dcb7 100644 --- a/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj +++ b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj @@ -1,11 +1,7 @@  - net10.0 - 13.0 - enable - enable - true + net8.0;net9.0;net10.0; @@ -19,18 +15,13 @@ false - - All - latest - true - false - - + + + - diff --git a/tests/PolylineAlgorithm.Tests/PolylineDecoderExtensionsTest.cs b/tests/PolylineAlgorithm.Tests/PolylineDecoderExtensionsTest.cs deleted file mode 100644 index 70030036..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineDecoderExtensionsTest.cs +++ /dev/null @@ -1,112 +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; - -using PolylineAlgorithm.Extensions; -using PolylineAlgorithm.Utility; -using System.Collections.Generic; -using System.Linq; - -[TestClass] -public class PolylineDecoderExtensionsTest { - private readonly PolylineDecoder _decoder = new(); - - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - [TestMethod] - public void Decode_Null_Decoder_Null_String_Throws_ArgumentNullException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => PolylineDecoderExtensions.Decode(null!, string.Empty).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("decoder", exception.ParamName); - Assert.IsTrue(exception.Message.Contains("Value cannot be null.", StringComparison.Ordinal)); - } - - [TestMethod] - public void Decode_Null_Decoder_Null_CharArray_Throws_ArgumentNullException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => PolylineDecoderExtensions.Decode(null!, []).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("decoder", exception.ParamName); - Assert.IsTrue(exception.Message.Contains("Value cannot be null.", StringComparison.Ordinal)); - } - - [TestMethod] - public void Decode_Null_Decoder_Empty_Memory_Throws_ArgumentNullException() { - // Arrange -#pragma warning disable IDE0305 // Simplify collection initialization - static void Decode() => PolylineDecoderExtensions.Decode(null!, Memory.Empty).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Act - var exception = Assert.ThrowsExactly(Decode); - - // Assert - Assert.AreEqual("decoder", exception.ParamName); - Assert.IsTrue(exception.Message.Contains("Value cannot be null.", StringComparison.Ordinal)); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount), DynamicDataSourceType.Property)] - public void Decode_String_Returns_Expected_Coordinates(int count) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(count); - var expected = RandomValueProvider.GetCoordinates(count) - .Select(c => new Coordinate(c.Latitude, c.Longitude)) - .ToList(); - - // Act - var result = PolylineDecoderExtensions.Decode(_decoder, polyline).ToList(); - - // Assert - CollectionAssert.AreEqual(expected, result); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount), DynamicDataSourceType.Property)] - public void Decode_CharArray_Returns_Expected_Coordinates(int count) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(count).ToCharArray(); - var expected = RandomValueProvider.GetCoordinates(count) - .Select(c => new Coordinate(c.Latitude, c.Longitude)) - .ToList(); - - // Act - var result = PolylineDecoderExtensions.Decode(_decoder, polyline).ToList(); - - // Assert - CollectionAssert.AreEqual(expected, result); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount), DynamicDataSourceType.Property)] - public void Decode_Memory_Returns_Expected_Coordinates(int count) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(count).AsMemory(); - var expected = RandomValueProvider.GetCoordinates(count) - .Select(c => new Coordinate(c.Latitude, c.Longitude)) - .ToList(); - - // Act - var result = PolylineDecoderExtensions.Decode(_decoder, polyline).ToList(); - - // Assert - CollectionAssert.AreEqual(expected, result); - } - -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs b/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs deleted file mode 100644 index 698d99d1..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs +++ /dev/null @@ -1,85 +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; - -using PolylineAlgorithm; -using PolylineAlgorithm.Utility; - -/// -/// Defines tests for the type. -/// -[TestClass] -public class PolylineDecoderTest { - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - /// - /// The instance of the used for testing. - /// - public PolylineDecoder Decoder = new(); - - /// - /// Tests the method with an empty input, expecting an . - /// - [TestMethod] - public void Decode_Default_Polyline_Throws_ArgumentException() { - // Arrange - Polyline empty = new(); - - // Act -#pragma warning disable IDE0305 // Simplify collection initialization - void Execute(Polyline value) => Decoder.Decode(value).ToList(); -#pragma warning restore IDE0305 // Simplify collection initialization - - // Assert - Assert.ThrowsExactly(() => Execute(empty)); - } - - /// - /// Tests the method with an invalid input, expecting an . - /// - //[TestMethod] - //public void Decode_Invalid_Input_ThrowsException() { - // // Arrange - // Polyline value = Polyline.FromString(StaticValueProvider.GetPolyline()); - - // // Act - // void Execute(Polyline value) => Decoder.Decode(value).ToList(); - - // // Assert - // var exception = Assert.ThrowsExactly(() => Execute(value)); - //} - - /// - /// Tests the method with a valid input. - /// - /// Expected result to equal . - [TestMethod] - [DynamicData(nameof(CoordinateCount))] - public void Random_Value_Decode_Valid_Input_Ok(int count) { - // Arrange - IEnumerable expected = RandomValueProvider.GetCoordinates(count).Select(c => new Coordinate(c.Latitude, c.Longitude)); - Polyline value = Polyline.FromString(RandomValueProvider.GetPolyline(count)); - - // Act - var result = Decoder.Decode(value); - - // Assert - CollectionAssert.AreEqual(expected.ToArray(), result.ToArray()); - } - - [TestMethod] - public void Static_Value_Decode_Valid_Input_Ok() { - // Arrange - IEnumerable expected = StaticValueProvider.Valid.GetCoordinates().Select(c => new Coordinate(c.Latitude, c.Longitude)); - string value = StaticValueProvider.Valid.GetPolyline(); - - // Act - var result = Decoder.Decode(Polyline.FromString(value)); - - // Assert - CollectionAssert.AreEqual(expected.ToArray(), result.ToArray()); - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncoderExtensionsTest.cs b/tests/PolylineAlgorithm.Tests/PolylineEncoderExtensionsTest.cs deleted file mode 100644 index 02d6d35a..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineEncoderExtensionsTest.cs +++ /dev/null @@ -1,77 +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; - -using PolylineAlgorithm.Extensions; -using PolylineAlgorithm.Utility; -using System.Collections.Generic; -using System.Linq; - -[TestClass] -public class PolylineEncoderExtensionsTest { - private readonly PolylineEncoder _encoder = new(); - - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - [TestMethod] - public void Encode_Null_Encoder_Empty_List_Throws_ArgumentNullException() { - // Arrange - static void Encode() => PolylineEncoderExtensions.Encode(null!, new List()); - - // Act - var exception = Assert.ThrowsExactly(Encode); - - // Assert - Assert.AreEqual("encoder", exception.ParamName); - Assert.IsTrue(exception.Message.Contains("Value cannot be null.", StringComparison.Ordinal)); - } - - [TestMethod] - public void Encode_Null_Encoder_Null_CharArray_Throws_ArgumentNullException() { - // Arrange - static void Encode() => PolylineEncoderExtensions.Encode(null!, []); - - // Act - var exception = Assert.ThrowsExactly(Encode); - - // Assert - Assert.AreEqual("encoder", exception.ParamName); - Assert.IsTrue(exception.Message.Contains("Value cannot be null.", StringComparison.Ordinal)); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount), DynamicDataSourceType.Property)] - public void Encode_List_Returns_Expected_Coordinates(int count) { - // Arrange - var coordinates = RandomValueProvider.GetCoordinates(count) - .Select(c => new Coordinate(c.Latitude, c.Longitude)) - .ToList(); - var expected = RandomValueProvider.GetPolyline(count); - - // Act - var result = PolylineEncoderExtensions.Encode(_encoder, coordinates); - - // Assert - Assert.AreEqual(expected, result.ToString()); - } - - [TestMethod] - [DynamicData(nameof(CoordinateCount), DynamicDataSourceType.Property)] - public void Encode_Array_Returns_Expected_Coordinates(int count) { - // Arrange - var coordinates = RandomValueProvider.GetCoordinates(count) - .Select(c => new Coordinate(c.Latitude, c.Longitude)) - .ToArray(); - var expected = RandomValueProvider.GetPolyline(count); - - // Act - var result = PolylineEncoderExtensions.Encode(_encoder, coordinates); - - // Assert - Assert.AreEqual(expected, result.ToString()); - } - -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs b/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs deleted file mode 100644 index 2d2cd298..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs +++ /dev/null @@ -1,118 +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; - -using PolylineAlgorithm.Utility; - -/// -/// Defines tests for the type. -/// -[TestClass] -public class PolylineEncoderTest { - public static IEnumerable CoordinateCount => [[1], [10], [100], [1_000]]; - - /// - /// The instance of the used for testing. - /// - public PolylineEncoder Encoder = new(); - - /// - /// Tests the method with a null input, expecting an . - /// - [TestMethod] - public void Encode_NullInput_ThrowsException() { - // Arrange - IEnumerable @null = null!; - - // Act - void EncodeNullCoordinates() { - Encoder.Encode(@null); - } - - // Assert - Assert.ThrowsExactly(() => EncodeNullCoordinates()); - } - - /// - /// Tests the method with an empty input, expecting an . - /// - [TestMethod] - public void Encode_EmptyInput_ThrowsException() { - // Arrange - IEnumerable empty = []; - - // Act - void EncodeEmptyCoordinates() => Encoder.Encode(empty); - - // Assert - Assert.ThrowsExactly(() => EncodeEmptyCoordinates()); - } - - /// - /// Tests the method with a valid input. - /// - /// Expected result is that the encoded polyline matches . - [TestMethod] - [DynamicData(nameof(CoordinateCount))] - public void Encode_RandomValue_ValidInput_Ok(int count) { - // Arrange - IEnumerable valid = RandomValueProvider.GetCoordinates(count).Select(c => new Coordinate(c.Latitude, c.Longitude)); - Polyline expected = Polyline.FromString(RandomValueProvider.GetPolyline(count)); - - // Act - var result = Encoder.Encode(valid); - - // Assert - Assert.AreEqual(expected.IsEmpty, result.IsEmpty); - Assert.AreEqual(expected.Length, result.Length); - Assert.IsTrue(expected.Equals(result)); - } - - /// - /// Tests the method with a valid input. - /// - /// Expected result is that the encoded polyline matches . - [TestMethod] - public void Encode_StaticValue_ValidInput_Ok() { - // Arrange - IEnumerable valid = StaticValueProvider.Valid.GetCoordinates().Select(c => new Coordinate(c.Latitude, c.Longitude)); - Polyline expected = Polyline.FromString(StaticValueProvider.Valid.GetPolyline()); - - // Act - var result = Encoder.Encode(valid); - - // Assert - Assert.AreEqual(expected.Length == 0, result.IsEmpty); - Assert.AreEqual(expected.Length, result.Length); - Assert.IsTrue(expected.Equals(result)); - } - - /// - /// Tests the round-trip encoding and decoding of coordinates. - /// - [TestMethod] - public void EncodeDecode_RoundTrip_Ok() { - var coordinates = new List - { - new(10, 20), - new(-10, -20), - new(0, 0) - }; - - var encoder = new PolylineEncoder(); - var decoder = new PolylineDecoder(); - - var polyline = encoder.Encode(coordinates); - var decoded = decoder.Decode(polyline).ToList(); - - Assert.AreEqual(coordinates.Count, decoded.Count); - - for (int i = 0; i < coordinates.Count; i++) { - Assert.AreEqual(coordinates[i].Latitude, decoded[i].Latitude/*, 1e-6*/); - Assert.AreEqual(coordinates[i].Longitude, decoded[i].Longitude/*, 1e-6*/); - } - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs new file mode 100644 index 00000000..26dafc74 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs @@ -0,0 +1,425 @@ +// +// 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 . +/// +[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(); + + // Assert + Assert.IsNotNull(result); + } + + /// + /// Tests that Create returns different instances on multiple calls. + /// + [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() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(5u, result.Precision); + Assert.AreEqual(512, result.StackAllocLimit); + Assert.IsNotNull(result.LoggerFactory); + Assert.IsInstanceOfType(result.LoggerFactory); + } + + /// + /// Tests that Build returns options with configured precision. + /// + [TestMethod] + public void Build_With_Custom_Precision_Returns_Options_With_Custom_Precision() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(7); + + // Act + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(7u, result.Precision); + } + + /// + /// Tests that Build returns options with configured stack alloc limit. + /// + [TestMethod] + public void Build_With_Custom_Stack_Alloc_Limit_Returns_Options_With_Custom_Stack_Alloc_Limit() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1024); + + // Act + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(1024, result.StackAllocLimit); + } + + /// + /// Tests that Build returns options with configured logger factory. + /// + [TestMethod] + public void Build_With_Custom_Logger_Factory_Returns_Options_With_Custom_Logger_Factory() { + // Arrange + ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithLoggerFactory(loggerFactory); + + // Act + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreSame(loggerFactory, result.LoggerFactory); + + // Cleanup + loggerFactory.Dispose(); + } + + /// + /// 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(); + } + + /// + /// Tests that Build can be called multiple times on the same builder. + /// + [TestMethod] + public void Build_With_Multiple_Invocations_Returns_Different_Instances_With_Same_Values() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(6); + + // 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); + } + + /// + /// Tests that WithStackAllocLimit sets the value and returns the builder. + /// + [TestMethod] + public void WithStackAllocLimit_With_Valid_Value_Sets_Value_And_Returns_Self() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptionsBuilder result = builder.WithStackAllocLimit(256); + + // 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)); + + // Assert + Assert.AreEqual("stackAllocLimit", exception.ParamName); + } + + /// + /// Tests that WithStackAllocLimit accepts large value. + /// + [TestMethod] + public void WithStackAllocLimit_With_Large_Value_Sets_Value() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptions result = builder + .WithStackAllocLimit(100000) + .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(); + + // 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); + } + + /// + /// Tests that WithPrecision accepts zero value. + /// + [TestMethod] + public void WithPrecision_With_Zero_Sets_Value() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptions result = builder + .WithPrecision(0) + .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(); + + // Assert + Assert.AreEqual(uint.MaxValue, result.Precision); + } + + /// + /// 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(); + + // 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. + /// + [TestMethod] + public void WithLoggerFactory_With_Null_Uses_Null_LoggerFactory() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptions result = builder + .WithLoggerFactory(null!) + .Build(); + + // Assert + Assert.IsNotNull(result.LoggerFactory); + Assert.IsInstanceOfType(result.LoggerFactory); + } + + /// + /// Tests that WithLoggerFactory can replace a previously set factory. + /// + [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) + .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(); + + // Assert + Assert.IsInstanceOfType(result.LoggerFactory); + } + + /// + /// Tests that builder supports method chaining for all methods. + /// + [TestMethod] + public void MethodChaining_With_All_Methods_Returns_Builder_For_Chaining() { + // Arrange + using ILoggerFactory loggerFactory = LoggerFactory.Create(_ => { }); + + // Act + PolylineEncodingOptions result = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(6) + .WithStackAllocLimit(1024) + .WithLoggerFactory(loggerFactory) + .Build(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(6u, result.Precision); + Assert.AreEqual(1024, result.StackAllocLimit); + Assert.AreSame(loggerFactory, result.LoggerFactory); + } +} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsTest.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsTest.cs deleted file mode 100644 index 9b0fa679..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsTest.cs +++ /dev/null @@ -1,42 +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; - -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Logging.Testing; -using PolylineAlgorithm.Tests.Fakes; - -[TestClass] -public class PolylineEncodingOptionsTest { - [TestMethod] - public void Constructor_Parameterless_Ok() { - // Arrange && Act - var options = new PolylineEncodingOptions(); - - // Assert - Assert.AreEqual(64_000, options.MaxBufferSize); - Assert.AreEqual(64_000 / sizeof(char), options.MaxBufferLength); - Assert.IsInstanceOfType(options.LoggerFactory); - } - - [TestMethod] - public void Constructor_ValidOptions_Ok() { - // Arrange - var bufferSize = 32_000; - var loggerFactory = new FakeLoggerFactory(new FakeLoggerProvider()); - - // Act - var options = new PolylineEncodingOptions() { - MaxBufferSize = bufferSize, - LoggerFactory = loggerFactory - }; - - // Assert - Assert.AreEqual(bufferSize, options.MaxBufferSize); - Assert.AreEqual(bufferSize / sizeof(char), options.MaxBufferLength); - Assert.IsInstanceOfType(options.LoggerFactory); - } -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingTest.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingTest.cs deleted file mode 100644 index f851f409..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineEncodingTest.cs +++ /dev/null @@ -1,265 +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; - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PolylineAlgorithm; -using System; -using System.Collections.Generic; - -[TestClass] -public class PolylineEncodingTest { - #region Dynamic Data Properties - - public static IEnumerable<(int variance, string polyline)> VariancePolylinePairs => [ - (0,"?"), - (1,"A"), - (-1,"@"), - (16,"_@"), - (-16,"^"), - (511,"}^"), - (-511,"|^"), - (512,"__@"), - (-512,"~^"), - (16383,"}~^"), - (-16383,"|~^"), - (16384,"___@"), - (-16384,"~~^"), - (524287,"}~~^"), - (-524287,"|~~^"), - (524288,"____@"), - (-524288,"~~~^"), - (16777215,"}~~~^"), - (-16777215,"|~~~^") - ]; - - public static IEnumerable<(double denormalized, int normalized, CoordinateValueType)> DenormalizedNormalizedPairs => [ - (0,0, CoordinateValueType.Latitude), - (0,0, CoordinateValueType.Longitude), - (1.23456,123456, CoordinateValueType.Latitude), - (-1.23456,-123456, CoordinateValueType.Latitude), - (1.23456,123456, CoordinateValueType.Longitude), - (-1.23456,-123456, CoordinateValueType.Longitude), - (90,9000000, CoordinateValueType.Latitude), - (-90,-9000000, CoordinateValueType.Latitude), - (90,9000000, CoordinateValueType.Longitude), - (-90,-9000000, CoordinateValueType.Longitude), - (180,18000000, CoordinateValueType.Longitude), - (-180,-18000000, CoordinateValueType.Longitude) - ]; - - public static IEnumerable<(double denormalized, CoordinateValueType)> DenormalizedOutOfRangeValues => [ - (90.00001,CoordinateValueType.Latitude), - (-90.00001,CoordinateValueType.Latitude), - (180.00001,CoordinateValueType.Longitude), - (-180.00001,CoordinateValueType.Longitude), - (double.NaN,CoordinateValueType.Latitude), - (double.NaN,CoordinateValueType.Longitude), - (double.MinValue,CoordinateValueType.Latitude), - (double.MaxValue,CoordinateValueType.Latitude), - (double.MinValue,CoordinateValueType.Longitude), - (double.MaxValue,CoordinateValueType.Longitude), - (double.NegativeInfinity,CoordinateValueType.Latitude), - (double.PositiveInfinity,CoordinateValueType.Latitude), - (double.NegativeInfinity,CoordinateValueType.Longitude), - (double.PositiveInfinity,CoordinateValueType.Longitude), - ]; - - public static IEnumerable<(int normalized, CoordinateValueType)> NormalizedOutOfRangeValues => [ - (9000001,CoordinateValueType.Latitude), - (-9000001,CoordinateValueType.Latitude), - (18000001,CoordinateValueType.Longitude), - (-18000001,CoordinateValueType.Longitude), - (int.MinValue,CoordinateValueType.Latitude), - (int.MaxValue,CoordinateValueType.Latitude), - (int.MinValue,CoordinateValueType.Longitude), - (int.MaxValue,CoordinateValueType.Longitude), - ]; - - public static IEnumerable<(int variance, int charCount)> VarianceCharCountPairs => [ - (0, 1), - (15, 1), - (-16, 1), - (16, 2), - (-17,2), - (511,2), - (-512,2), - (512,3), - (-513,3), - (16383,3), - (-16384,3), - (16384,4), - (-16385,4), - (524287,4), - (-524288,4), - (524288,5), - (-524289,5), - (16777215,5), - (-16777216,5), - (16777216,6), - (-16777217,6), - (int.MaxValue,6), - (int.MinValue,6), - ]; - - #endregion - - [TestMethod] - [DynamicData(nameof(DenormalizedNormalizedPairs), DynamicDataSourceType.Property)] - public void Normalize_Equals_Expected(double denormalized, int expected, CoordinateValueType type) { - // Arrange & Act - int result = PolylineEncoding.Normalize(denormalized, type); - - // Assert - Assert.AreEqual(expected, result); - } - - - [TestMethod] - [DynamicData(nameof(DenormalizedNormalizedPairs), DynamicDataSourceType.Property)] - public void Denormalize_Equals_Expected(double expected, int normalized, CoordinateValueType type) { - // Arrange & Act - double result = PolylineEncoding.Denormalize(normalized, type); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DynamicData(nameof(VariancePolylinePairs), DynamicDataSourceType.Property)] - public void TryWriteValue_StaticBuffer_Returns_True_Equals_Expected(int variance, string expected) { - // Arrange - int position = 0; - Span buffer = stackalloc char[6]; - - // Act - bool result = PolylineEncoding.TryWriteValue(variance, ref buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(expected.Length, position); - Assert.AreEqual(expected, buffer[..position].ToString()); - } - - - [TestMethod] - [DynamicData(nameof(VariancePolylinePairs), DynamicDataSourceType.Property)] - public void TryWriteValue_DynamicBuffer_Returns_True_Equals_Expected(int variance, string expected) { - // Arrange - int position = 0; - int required = PolylineEncoding.GetCharCount(variance); - Span buffer = stackalloc char[required]; - - // Act - bool result = PolylineEncoding.TryWriteValue(variance, ref buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(required, position); - Assert.AreEqual(expected.Length, position); - Assert.AreEqual(expected, buffer[..position].ToString()); - } - - [TestMethod] - [DynamicData(nameof(VariancePolylinePairs), DynamicDataSourceType.Property)] - public void TryWriteValue_BufferTooSmall_Returns_False(int variance, string _) { - // Arrange - int position = 0; - int required = PolylineEncoding.GetCharCount(variance); - Span buffer = stackalloc char[required - 1]; - - // Act - bool result = PolylineEncoding.TryWriteValue(variance, ref buffer, ref position); - - // Assert - Assert.IsFalse(result); - } - - [TestMethod] - [DynamicData(nameof(VariancePolylinePairs), DynamicDataSourceType.Property)] - public void TryReadValue_Ok(int expected, string polyline) { - // Arrange - int position = 0; - int variance = 0; - var buffer = polyline.AsMemory(); - - // Act - bool result = PolylineEncoding.TryReadValue(ref variance, ref buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(buffer.Length, position); - Assert.AreEqual(expected, variance); - } - - [TestMethod] - public void TryReadValue_EmptyBuffer_Returns_False() { - // Arrange - int variance = 0; - int position = 0; - ReadOnlyMemory buffer = Memory.Empty; - - // Act - bool result = PolylineEncoding.TryReadValue(ref variance, ref buffer, ref position); - - // Assert - Assert.IsFalse(result); - } - - [TestMethod] - public void TryReadValue_MalformedBuffer_Returns_False() { - //Arrange - int position = 0; - int variance = 42; - int expected = variance; - // Buffer with a char that will never finish a value (simulate incomplete encoding) - char[] chars = [(char)127]; // 127 - 63 = 64, which is >= 32, so loop never breaks - ReadOnlyMemory buffer = chars.AsMemory(); - - // Act - bool result = PolylineEncoding.TryReadValue(ref variance, ref buffer, ref position); - - // Assert - Assert.IsFalse(result); - Assert.AreEqual(expected, variance); - } - - [TestMethod] - [DynamicData(nameof(DenormalizedOutOfRangeValues), DynamicDataSourceType.Property)] - public void Normalize_Throws_ArgumentOutOfRangeException(double value, CoordinateValueType type) { - // Arrange - static int Normalize(double value, CoordinateValueType type) => PolylineEncoding.Normalize(value, type); - - // Act - var exception = Assert.ThrowsExactly(() => Normalize(value, type)); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(NormalizedOutOfRangeValues), DynamicDataSourceType.Property)] - public void Denormalize_Throws_ArgumentOutOfRangeException(int value, CoordinateValueType type) { - // Arrange - static double Denormalize(int value, CoordinateValueType type) => PolylineEncoding.Denormalize(value, type); - - // Act - var exception = Assert.ThrowsExactly(() => Denormalize(value, type)); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - [DynamicData(nameof(VarianceCharCountPairs), DynamicDataSourceType.Property)] - public void GetCharCount_Equals_Expected(int variance, int expected) { - // Arrange & Act - var charCount = PolylineEncoding.GetCharCount(variance); - - // Assert - Assert.AreEqual(expected, charCount); - } -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs new file mode 100644 index 00000000..d598e3c2 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs @@ -0,0 +1,681 @@ +// +// 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 PolylineAlgorithm; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncodingTests { + #region Normalize Tests + + /// + /// Tests that Normalize returns zero when value is zero. + /// + [TestMethod] + public void Normalize_With_Zero_Value_Returns_Zero() { + // Act + int result = PolylineEncoding.Normalize(0.0); + + // Assert + Assert.AreEqual(0, result); + } + + /// + /// Tests that Normalize throws when value is not finite. + /// + [TestMethod] + [DataRow(double.NaN)] + [DataRow(double.PositiveInfinity)] + [DataRow(double.NegativeInfinity)] + public void Normalize_With_NonFinite_Value_Throws_ArgumentOutOfRangeException(double value) { + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, 5)); + } + + /// + /// Tests that Normalize returns the expected normalized integer for the given value and precision. + /// + [TestMethod] + [DataRow(37.78903, 0u, 37)] + [DataRow(-122.4123, 0u, -122)] + [DataRow(37.78903, 5u, 3778903)] + [DataRow(-122.4123, 5u, -12241230)] + [DataRow(37.78903, 1u, 377)] + [DataRow(37.789034, 6u, 37789034)] + [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) { + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(expected, result); + } + + #endregion + + #region Denormalize Tests + + /// + /// Tests that Denormalize returns zero when value is zero. + /// + [TestMethod] + public void Denormalize_With_Zero_Value_Returns_Zero() { + // Act + double result = PolylineEncoding.Denormalize(0); + + // Assert + Assert.AreEqual(0.0, result); + } + + /// + /// Tests that Denormalize returns the expected floating-point value for the given integer and precision. + /// + [TestMethod] + [DataRow(37, 0u, 37.0)] + [DataRow(-122, 0u, -122.0)] + [DataRow(3778903, 5u, 37.78903)] + [DataRow(-12241230, 5u, -122.4123)] + [DataRow(377, 1u, 37.7)] + [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) { + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(expected, result, 1e-7); + } + + #endregion + + #region TryReadValue Tests + + /// + /// Tests that TryReadValue returns false when position is at buffer length. + /// + [TestMethod] + public void TryReadValue_With_Position_At_Buffer_Length_Returns_False() { + // Arrange + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = buffer.Length; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, delta); + } + + /// + /// Tests that TryReadValue returns false when position exceeds buffer length. + /// + [TestMethod] + public void TryReadValue_With_Position_Exceeds_Buffer_Length_Returns_False() { + // Arrange + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = buffer.Length + 1; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, delta); + } + + /// + /// Tests that TryReadValue reads a positive single-character encoded value. + /// + [TestMethod] + 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; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(5, delta); + Assert.AreEqual(1, position); + } + + /// + /// Tests that TryReadValue reads a positive multi-character encoded value. + /// + [TestMethod] + 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; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(3850000, delta); + Assert.AreEqual(5, position); + } + + /// + /// Tests that TryReadValue reads a negative encoded value. + /// + [TestMethod] + 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; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(-12020000, delta); + Assert.AreEqual(5, position); + } + + /// + /// Tests that TryReadValue accumulates into existing delta. + /// + [TestMethod] + public void TryReadValue_With_Existing_Delta_Accumulates_Delta() { + // Arrange + ReadOnlyMemory buffer = "I".AsMemory(); // encodes 5 + int delta = 10; // existing delta + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(15, delta); // 10 + 5 = 15 + } + + /// + /// Tests that TryReadValue reads multiple values sequentially from the buffer. + /// + [TestMethod] + 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; + int position = 0; + + // Act - read first value + bool first = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + int firstDelta = delta; + + // read second value + bool second = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(first); + Assert.IsTrue(second); + Assert.AreEqual(3850000, firstDelta); + Assert.AreEqual(10, position); // consumed all 10 chars + } + + /// + /// Tests that TryReadValue returns false when buffer ends mid-value. + /// + [TestMethod] + 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; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that TryReadValue correctly reads from a non-zero starting position. + /// + [TestMethod] + 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; + int position = 5; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(-12020000, delta); + Assert.AreEqual(10, position); + } + + #endregion + + #region TryWriteValue Tests + + /// + /// Tests that TryWriteValue returns false when the buffer is too small. + /// + [TestMethod] + public void TryWriteValue_With_Buffer_Too_Small_Returns_False() { + // Arrange + Span buffer = []; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, position); + } + + /// + /// Tests that TryWriteValue returns false when the remaining buffer is too small. + /// + [TestMethod] + public void TryWriteValue_With_Remaining_Buffer_Too_Small_Returns_False() { + // Arrange - need 5 chars for 3850000, but only 3 remain + Span buffer = new char[3]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, position); + } + + /// + /// Tests that TryWriteValue correctly encodes zero. + /// + [TestMethod] + public void TryWriteValue_With_Zero_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(0, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('?', buffer[0]); // 0 + 63 = '?' + } + + /// + /// Tests that TryWriteValue correctly encodes a positive value. + /// + [TestMethod] + public void TryWriteValue_With_Positive_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(5, position); + Assert.AreEqual("_p~iF", new string(buffer[..5])); + } + + /// + /// Tests that TryWriteValue correctly encodes a negative value. + /// + [TestMethod] + public void TryWriteValue_With_Negative_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(-12020000, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(5, position); + Assert.AreEqual("~ps|U", new string(buffer[..5])); + } + + /// + /// Tests that TryWriteValue correctly encodes multiple values sequentially. + /// + [TestMethod] + public void TryWriteValue_With_Multiple_Values_Writes_Sequentially() { + // Arrange + Span buffer = new char[20]; + int position = 0; + + // Act + bool first = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); + bool second = PolylineEncoding.TryWriteValue(-12020000, buffer, ref position); + + // Assert + Assert.IsTrue(first); + Assert.IsTrue(second); + Assert.AreEqual("_p~iF~ps|U", new string(buffer[..10])); + } + + /// + /// Tests that TryWriteValue correctly encodes a small positive value. + /// + [TestMethod] + public void TryWriteValue_With_Small_Positive_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(5, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('I', buffer[0]); // zigzag(5) = 10; 10 + 63 = 73 = 'I' + } + + /// + /// Tests that TryWriteValue correctly encodes a small negative value. + /// + [TestMethod] + public void TryWriteValue_With_Small_Negative_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(-5, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('H', buffer[0]); // zigzag(-5) = 9; 9 + 63 = 72 = 'H' + } + + /// + /// Tests that TryWriteValue writes at the correct non-zero starting position. + /// + [TestMethod] + public void TryWriteValue_With_Non_Zero_Start_Position_Writes_At_Correct_Position() { + // Arrange + Span buffer = new char[20]; + int position = 5; + + // Act + bool result = PolylineEncoding.TryWriteValue(5, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(6, position); + Assert.AreEqual('I', buffer[5]); + } + + /// + /// Tests that TryWriteValue correctly encodes a large positive value. + /// + [TestMethod] + public void TryWriteValue_With_Large_Positive_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + const int delta = 3778903; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedSize, position); + } + + /// + /// Tests that TryWriteValue correctly encodes a large negative value. + /// + [TestMethod] + public void TryWriteValue_With_Large_Negative_Value_Writes_Correctly() { + // Arrange + Span buffer = new char[10]; + int position = 0; + const int delta = -12241230; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedSize, position); + } + + #endregion + + #region GetRequiredBufferSize Tests + + /// + /// Tests that GetRequiredBufferSize returns the expected character count for the given delta. + /// + [TestMethod] + [DataRow(0, 1)] + [DataRow(1, 1)] + [DataRow(-1, 1)] + [DataRow(15, 1)] + [DataRow(16, 2)] + [DataRow(3778903, 5)] + [DataRow(-12241230, 5)] + public void GetRequiredBufferSize_Returns_Expected_Size(int delta, int expectedSize) { + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(expectedSize, size); + } + + /// + /// Tests that GetRequiredBufferSize returns a valid size for the maximum positive integer. + /// + [TestMethod] + public void GetRequiredBufferSize_With_Max_Int_Returns_Correct_Size() { + // Act + int size = PolylineEncoding.GetRequiredBufferSize(int.MaxValue); + + // Assert + Assert.IsGreaterThan(0, size); + Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 + } + + /// + /// Tests that GetRequiredBufferSize returns a valid size for the minimum negative integer. + /// + [TestMethod] + public void GetRequiredBufferSize_With_Min_Int_Returns_Correct_Size() { + // Act + int size = PolylineEncoding.GetRequiredBufferSize(int.MinValue); + + // Assert + Assert.IsGreaterThan(0, size); + Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 + } + + /// + /// Tests that GetRequiredBufferSize is consistent with the actual bytes written by TryWriteValue. + /// + [TestMethod] + public void GetRequiredBufferSize_Consistent_With_TryWriteValue_Matches_Actual_Size() { + // Arrange + const int delta = 3778903; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); + Span buffer = stackalloc char[expectedSize]; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedSize, position); + } + + /// + /// Tests that an undersized buffer causes TryWriteValue to fail. + /// + [TestMethod] + public void GetRequiredBufferSize_With_Undersized_Buffer_Causes_TryWriteValue_To_Fail() { + // Arrange + const int delta = 3778903; + int requiredSize = PolylineEncoding.GetRequiredBufferSize(delta); + Span buffer = stackalloc char[requiredSize - 1]; // One char too small + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, position); + } + + #endregion + + #region ValidateFormat Tests + + /// + /// Tests that ValidateFormat succeeds with a valid polyline. + /// + [TestMethod] + public void ValidateFormat_With_Valid_Polyline_Does_Not_Throw() { + // Act & Assert + PolylineEncoding.ValidateFormat("_p~iF~ps|U_ulLnnqC_mqNvxq`@"); + } + + /// + /// Tests that ValidateFormat throws when polyline contains an invalid character. + /// + [TestMethod] + public void ValidateFormat_With_Invalid_Character_Throws_InvalidPolylineException() { + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("_p~iF!ps|U")); + } + + /// + /// Tests that ValidateFormat throws when polyline has invalid block structure. + /// + [TestMethod] + public void ValidateFormat_With_Invalid_Block_Structure_Throws_InvalidPolylineException() { + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("________")); // all continuation chars, no terminator + } + + /// + /// Tests that ValidateFormat succeeds with a single terminator character. + /// + [TestMethod] + public void ValidateFormat_With_Single_Terminator_Does_Not_Throw() { + // Act & Assert + PolylineEncoding.ValidateFormat("?"); + } + + /// + /// Tests that ValidateFormat throws when a block exceeds maximum length. + /// + [TestMethod] + public void ValidateFormat_With_Block_Too_Long_Throws_InvalidPolylineException() { + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("________?")); // 8-char block (max is 7) + } + + #endregion + + #region ValidateCharRange Tests + + /// + /// Tests that ValidateCharRange succeeds for valid polyline strings. + /// + [TestMethod] + [DataRow("_p~iF~ps|U_ulLnnqC_mqNvxq`@")] + [DataRow("?")] // min valid char (ASCII 63) + [DataRow("~")] // max valid char (ASCII 126) + [DataRow("")] // empty is valid + [DataRow("????????????????????????????????")] // long string to trigger SIMD path + public void ValidateCharRange_With_Valid_Polyline_Does_Not_Throw(string polyline) { + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange throws for polylines containing invalid characters. + /// + [TestMethod] + [DataRow(">")] // ASCII 62 (below min of 63) + [DataRow("\u007F")] // ASCII 127 (above max of 126) + [DataRow("_p~iF!ps|U")] // '!' in middle (ASCII 33) + [DataRow("_p~iF~ps|U!")] // '!' at end + [DataRow("????????!???????????????????????")] // invalid in SIMD section + [DataRow("????????????????\u007F")] // invalid in scalar remainder + public void ValidateCharRange_With_Invalid_Character_Throws_InvalidPolylineException(string polyline) { + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + #endregion + + #region ValidateBlockLength Tests + + /// + /// Tests that ValidateBlockLength succeeds for valid block structures. + /// + [TestMethod] + [DataRow("?")] // single terminator + [DataRow("_p~iF~ps|U")] // multiple blocks + [DataRow("______?")] // 6 continuation chars + terminator (maximum block length) + [DataRow("??")] // consecutive terminators + [DataRow("?__?_____?")] // mixed block lengths + public void ValidateBlockLength_With_Valid_Polyline_Does_Not_Throw(string polyline) { + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength throws for invalid block structures. + /// + [TestMethod] + [DataRow("________?")] // 8-char block (exceeds max of 7) + [DataRow("________")] // all continuation chars, no terminator + [DataRow("")] // empty polyline has no terminator + [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)); + } + + #endregion +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineOptionsBuilderTest.cs b/tests/PolylineAlgorithm.Tests/PolylineOptionsBuilderTest.cs deleted file mode 100644 index 25e125ed..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineOptionsBuilderTest.cs +++ /dev/null @@ -1,103 +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; - -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Logging.Testing; -using PolylineAlgorithm; -using PolylineAlgorithm.Tests.Fakes; - -[TestClass] -public class PolylineOptionsBuilderTest { - [TestMethod] - public void Create_Returns_Instance() { - // Arrange && Act - var builder = PolylineEncodingOptionsBuilder.Create(); - - // Assert - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Build_Returns_Instance_With_Default_Values() { - // Arrange - var builder = PolylineEncodingOptionsBuilder.Create(); - var bufferSize = 64_000; - var loggerFactory = NullLoggerFactory.Instance; - - // Act - var options = builder - .Build(); - - // Assert - Assert.IsNotNull(options); - Assert.AreEqual(bufferSize, options.MaxBufferSize); - Assert.AreEqual(bufferSize / sizeof(char), options.MaxBufferLength); - Assert.AreEqual(loggerFactory, options.LoggerFactory); - } - - [TestMethod] - public void WithBufferSize_Small_BufferSize_Parameter_Returns_Throws_ArgumentOutOfRangeException() { - // Arrange - static void WithSmallBufferSize() => PolylineEncodingOptionsBuilder.Create() - .WithMaxBufferSize(11); - - // Act - var exception = Assert.ThrowsExactly(WithSmallBufferSize); - - // Assert - Assert.IsNotNull(exception); - Assert.AreEqual("bufferSize", exception.ParamName); - Assert.Contains("Buffer size must be greater than 11.", exception.Message); - } - - [TestMethod] - public void Build_Returns_Instance_With_Expected_Buffer_Size() { - // Arrange - var builder = PolylineEncodingOptionsBuilder.Create(); - var expected = 32_000; - - // Act - var options = builder - .WithMaxBufferSize(expected) - .Build(); - - // Assert - Assert.IsNotNull(options); - Assert.AreEqual(expected, options.MaxBufferSize); - Assert.AreEqual(expected / sizeof(char), options.MaxBufferLength); - } - - [TestMethod] - public void Build_Returns_Instance_With_Expected_LoggerFactory() { - // Arrange - var builder = PolylineEncodingOptionsBuilder.Create(); - var expected = new FakeLoggerFactory(new FakeLoggerProvider()); - - // Act - var options = builder - .WithLoggerFactory(expected) - .Build(); - - // Assert - Assert.IsNotNull(options); - Assert.AreEqual(expected, options.LoggerFactory); - } - - [TestMethod] - public void WithLoggerFactory_Null_Parameter_Returns_Throws_ArgumentNullException() { - // Arrange - static void WithNullLoggerFactory() => PolylineEncodingOptionsBuilder.Create() - .WithLoggerFactory(null!); - - // Act - var exception = Assert.ThrowsExactly(WithNullLoggerFactory); - - // Assert - Assert.IsNotNull(exception); - Assert.AreEqual("loggerFactory", exception.ParamName); - } -} diff --git a/tests/PolylineAlgorithm.Tests/PolylineTest.cs b/tests/PolylineAlgorithm.Tests/PolylineTest.cs deleted file mode 100644 index 5098b342..00000000 --- a/tests/PolylineAlgorithm.Tests/PolylineTest.cs +++ /dev/null @@ -1,287 +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; - -using PolylineAlgorithm.Utility; -using System; - -/// -/// Tests for the type. -/// -[TestClass] -public class PolylineTest { - /// - /// Provides test data for the string parameter tests. - /// - public static IEnumerable LengthParameters => [ - [1], - [10], - [100], - [1_000] - ]; - - /// - /// Tests the parameterless constructor of the class. - /// - [TestMethod] - public void Constructor_Parameterless_Ok() { - // Arrange - int expectedLength = 0; - - // Act - Polyline polyline = new(); - - // Assert - Assert.AreEqual(expectedLength, polyline.Length); - Assert.IsTrue(polyline.IsEmpty); - Assert.IsTrue(polyline.Value.IsEmpty); - } - - /// - /// Tests the constructor with a null character array, expecting an . - /// - [TestMethod] - public void FromString_Null_String_ArgumentNullException() { - // Arrange - string value = null!; - static Polyline New(string value) => Polyline.FromString(value); - - // Act - var exception = Assert.ThrowsExactly(() => New(value)); - - // Assert - - } - - /// - /// Tests the constructor with a character array parameter. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void FromString_String_Parameter_Ok(int size) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(size); - bool isEmpty = polyline.Length == 0; - long length = polyline.Length; - - // Act - Polyline result = Polyline.FromString(polyline); - - // Assert - Assert.AreEqual(isEmpty, result.IsEmpty); - Assert.AreEqual(length, result.Length); - Assert.AreEqual(new string(polyline), result.ToString()); - } - - /// - /// Tests the constructor with a null character array, expecting an . - /// - [TestMethod] - public void FromCharArray_Null_CharArray_ArgumentNullException() { - // Arrange - char[] value = null!; - static Polyline New(char[] value) => Polyline.FromCharArray(value); - - // Act - var exception = Assert.ThrowsExactly(() => New(value)); - - // Assert - - } - - /// - /// Tests the constructor with a character array parameter. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void FromCharArray_CharArray_Parameter_Ok(int size) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(size).ToCharArray(); - bool isEmpty = polyline.Length == 0; - long length = polyline.Length; - - // Act - Polyline result = Polyline.FromCharArray(polyline); - - // Assert - Assert.AreEqual(isEmpty, result.IsEmpty); - Assert.AreEqual(length, result.Length); - Assert.AreEqual(new string(polyline), result.ToString()); - } - - /// - /// Tests the constructor with a memory parameter. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void FromMemory_Empty_Memory_Parameter_Ok(int size) { - // Arrange - var polyline = ReadOnlyMemory.Empty; - bool isEmpty = polyline.Length == 0; - long length = polyline.Length; - - // Act - Polyline result = Polyline.FromMemory(polyline); - - // Assert - Assert.AreEqual(isEmpty, result.IsEmpty); - Assert.AreEqual(length, result.Length); - Assert.AreEqual(polyline.ToString(), result.ToString()); - } - - /// - /// Tests the constructor with a memory parameter. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void FromMemory_Memory_Parameter_Ok(int size) { - // Arrange - var polyline = RandomValueProvider.GetPolyline(size).AsMemory(); - bool isEmpty = polyline.Length == 0; - long length = polyline.Length; - - // Act - Polyline result = Polyline.FromMemory(polyline); - - // Assert - Assert.AreEqual(isEmpty, result.IsEmpty); - Assert.AreEqual(length, result.Length); - Assert.AreEqual(polyline.ToString(), result.ToString()); - } - - /// - /// Tests the method. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void ToString_Returns_Correct_String(int size) { - // Arrange - Polyline polyline = Polyline.FromString(RandomValueProvider.GetPolyline(size)); - string expected = RandomValueProvider.GetPolyline(size); - - // Act - string result = polyline.ToString(); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests the method. - /// - /// The string value. - [TestMethod] - public void ToString_Returns_Empty_String() { - // Arrange - Polyline polyline = new(); - string expected = string.Empty; - - // Act - string result = polyline.ToString(); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests the method. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void CopyTo_Equals_Correct_CharArray(int size) { - // Arrange - Polyline polyline = Polyline.FromString(RandomValueProvider.GetPolyline(size)); - char[] expected = RandomValueProvider.GetPolyline(size).ToCharArray(); - char[] result = new char[polyline.Length]; - - // Act - polyline.CopyTo(result); - - // Assert - CollectionAssert.AreEqual(expected, result); - } - - /// - /// Tests the method. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void CopyTo_Smaller_Array_Destination_Parameter_Throws_ArgumentException(int size) { - // Arrange - Polyline polyline = Polyline.FromString(RandomValueProvider.GetPolyline(size)); - char[] destination = new char[polyline.Length - 1]; - void CopyTo() => polyline.CopyTo(destination); - - // Act - var exception = Assert.ThrowsExactly(CopyTo); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - /// - /// Tests the method. - /// - /// The string value. - [TestMethod] - [DynamicData(nameof(LengthParameters))] - public void CopyTo_Null_Destination_Parameter_Throws_ArgumentNullException(int size) { - // Arrange - Polyline polyline = Polyline.FromString(RandomValueProvider.GetPolyline(size)); - char[] destination = null!; - void CopyTo() => polyline.CopyTo(destination); - - // Act - var exception = Assert.ThrowsExactly(CopyTo); - - // Assert - Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); - } - - [TestMethod] - public void Equality_Operators_Correct_Results() { - // Arrange - Polyline polyline = Polyline.FromString(nameof(polyline)); - Polyline equal = Polyline.FromString(nameof(polyline)); - Polyline notEqual = Polyline.FromString(nameof(notEqual)); - Polyline empty = new(); - string typeNotEqual = "not a polyline"; - - // Act && Assert - Assert.IsTrue(polyline == equal); - Assert.IsTrue(polyline != notEqual); - - Assert.IsFalse(polyline != equal); - Assert.IsFalse(polyline == notEqual); - - Assert.IsTrue(polyline.Equals(equal)); - Assert.IsTrue(polyline.Equals((object)equal)); - - Assert.IsFalse(polyline.Equals(empty)); - Assert.IsFalse(polyline.Equals(notEqual)); - Assert.IsFalse(polyline.Equals((object)notEqual)); - Assert.IsFalse(polyline.Equals(typeNotEqual)); - } - - [TestMethod] - public void GetHasCode_Correct_Results() { - // Arrange - Polyline polyline = Polyline.FromString(nameof(polyline)); - Polyline equal = Polyline.FromString(nameof(polyline)); - Polyline notEqual = Polyline.FromString(nameof(notEqual)); - - // Act && Assert - Assert.AreEqual(polyline.GetHashCode(), equal.GetHashCode()); - Assert.AreNotEqual(polyline.GetHashCode(), notEqual.GetHashCode()); - } -} \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Properties/CodeCoverage.cs b/tests/PolylineAlgorithm.Tests/Properties/CodeCoverage.cs new file mode 100644 index 00000000..04094932 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Properties/CodeCoverage.cs @@ -0,0 +1,8 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics.CodeAnalysis; + +[assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Properties/GlobalSuppressions.cs b/tests/PolylineAlgorithm.Tests/Properties/GlobalSuppressions.cs index 5a25b345..975caf89 100644 --- a/tests/PolylineAlgorithm.Tests/Properties/GlobalSuppressions.cs +++ b/tests/PolylineAlgorithm.Tests/Properties/GlobalSuppressions.cs @@ -5,4 +5,6 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Ignored in test asemblies.")] \ No newline at end of file +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Tests.")] +[assembly: SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Tests.")] +[assembly: SuppressMessage("Roslynator", "RCS1196:Call extension method as instance method", Justification = "Tests.")] \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Properties/MSTestSettings.cs b/tests/PolylineAlgorithm.Tests/Properties/MSTestSettings.cs index f5a0ed8b..dafc934a 100644 --- a/tests/PolylineAlgorithm.Tests/Properties/MSTestSettings.cs +++ b/tests/PolylineAlgorithm.Tests/Properties/MSTestSettings.cs @@ -1,9 +1,9 @@ -// +// // Copyright © Pete Sramek. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // using PolylineAlgorithm.Tests.Properties; -[assembly: Parallelize(Scope = ExecutionScope.ClassLevel)] +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] [assembly: TestCategory(Category.Unit)] \ No newline at end of file diff --git a/utilities/PolylineAlgorithm.Utility/PolylineAlgorithm.Utility.csproj b/utilities/PolylineAlgorithm.Utility/PolylineAlgorithm.Utility.csproj index d75bf465..16e415f8 100644 --- a/utilities/PolylineAlgorithm.Utility/PolylineAlgorithm.Utility.csproj +++ b/utilities/PolylineAlgorithm.Utility/PolylineAlgorithm.Utility.csproj @@ -2,23 +2,12 @@ netstandard2.1 - 13.0 - enable - enable - true false - - All - latest - true - false - - diff --git a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs index 8236087d..d3d83b9e 100644 --- a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs @@ -1,32 +1,33 @@ -// +// // Copyright © Pete Sramek. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace PolylineAlgorithm.Utility; -using PolylineAlgorithm; using PolylineAlgorithm.Abstraction; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; /// /// Provides random generation and caching of coordinate collections and their encoded polyline representations. /// Useful for testing and benchmarking polyline algorithms with reproducible random data. /// +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "Internal use only.")] 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(); /// - /// Gets a collection of random instances of the specified count. + /// Gets a collection of random latitude/longitude tuples of the specified count. /// The same collection is cached and reused for the same count value. /// /// The number of coordinates to generate. - /// An enumerable collection of random objects. + /// An enumerable collection of random latitude/longitude tuples. public static IEnumerable<(double Latitude, double Longitude)> GetCoordinates(int count) { var entry = GetCaheEntry(count); @@ -38,11 +39,11 @@ internal static class RandomValueProvider { } /// - /// Gets a representing the encoded polyline for a random collection of coordinates of the specified count. + /// Gets the encoded polyline string for a random collection of coordinates of the specified count. /// The same polyline is cached and reused for the same count value. /// /// The number of coordinates to generate and encode. - /// A representing the encoded polyline. + /// A representing the encoded polyline. public static string GetPolyline(int count) { var entry = GetCaheEntry(count); @@ -63,12 +64,10 @@ private static PolylineCoordinateCollectionPair GetCaheEntry(int count) { var enumeration = Enumerable .Range(0, count) - .Select(i => (RandomLatitude(), RandomLongitude())) - .ToList(); - - entry = _cache.GetOrAdd(count, _ => new PolylineCoordinateCollectionPair(enumeration, _encoder.Encode(enumeration))); + .Select(_ => (RandomLatitude(), RandomLongitude())) + .ToArray(); - return entry; + return _cache.GetOrAdd(count, new PolylineCoordinateCollectionPair(enumeration, _encoder.Encode(enumeration))); } /// @@ -104,7 +103,7 @@ private readonly struct PolylineCoordinateCollectionPair(IEnumerable<(double Lat public string Polyline { get; } = polyline; } - private class PolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + private sealed class PolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { protected override string CreatePolyline(ReadOnlyMemory polyline) { return polyline.ToString(); diff --git a/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs b/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs index 578bb38b..d6d2813c 100644 --- a/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs @@ -15,10 +15,10 @@ internal static class Valid { /// /// A predefined polyline instance representing a fixed encoded polyline string. /// - private static readonly string _polyline = "???_gsia@_cidP??~fsia@?~fsia@~bidP?~bidP??_gsia@"; + private const string Polyline = "???_gsia@_cidP??~fsia@?~fsia@~bidP?~bidP??_gsia@"; /// - /// A predefined collection of instances representing a closed path around the globe. + /// A predefined collection of latitude/longitude tuples representing a closed path around the globe. /// private static readonly IEnumerable<(double Latitude, double Longitude)> _coordinates = [ new (0, 0), @@ -28,23 +28,23 @@ internal static class Valid { new (90, -180), new (0, -180), new (-90, -180), - new (-90, 0) + new (-90, 0), ]; /// - /// Gets the predefined collection of instances. + /// Gets the predefined collection of latitude/longitude tuples. /// - /// An containing the static coordinates. + /// An of latitude/longitude tuples containing the static coordinates. public static IEnumerable<(double Latitude, double Longitude)> GetCoordinates() { return _coordinates; } /// - /// Gets the predefined instance. + /// Gets the predefined encoded polyline string. /// - /// The static value. + /// The static encoded polyline string. public static string GetPolyline() { - return _polyline; + return Polyline; } } @@ -77,4 +77,4 @@ public static IEnumerable GetInvalidPolylines() { yield return (double.MaxValue, double.MaxValue); } } -} +} \ No newline at end of file From 93789b17277877ce8b0f30072efd0abbb217f1ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:09:31 +0000 Subject: [PATCH 2/2] fix: remove invalid 'administration' permission from release workflow Agent-Logs-Url: https://github.com/petesramek/polyline-algorithm-csharp/sessions/8b558566-827f-44ff-aa04-5d55f8859059 Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com> --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad996933..eed9050e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,6 @@ permissions: pages: write id-token: write contents: write - administration: write concurrency: group: release-${{ github.head_ref || github.ref }}