diff --git a/.editorconfig b/.editorconfig index 61536f81..e3771e59 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories +# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true # All files @@ -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 diff --git a/.github/actions/documentation/docfx-build/action.yml b/.github/actions/documentation/docfx-build/action.yml index 2b9ba79f..e21ced83 100644 --- a/.github/actions/documentation/docfx-build/action.yml +++ b/.github/actions/documentation/docfx-build/action.yml @@ -22,7 +22,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: @@ -41,7 +41,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/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..40229ad8 100644 --- a/.github/actions/versioning/extract-version/action.yml +++ b/.github/actions/versioning/extract-version/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: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' uses: actions/setup-dotnet@v4 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/promote-branch.yml b/.github/workflows/promote-branch.yml index 21d999f1..03df4b47 100644 --- a/.github/workflows/promote-branch.yml +++ b/.github/workflows/promote-branch.yml @@ -39,7 +39,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,7 +153,7 @@ 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: | @@ -192,7 +192,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: diff --git a/.github/workflows/publish-documentation.yml b/.github/workflows/publish-documentation.yml index 7f9af8e3..9134bbbe 100644 --- a/.github/workflows/publish-documentation.yml +++ b/.github/workflows/publish-documentation.yml @@ -1,4 +1,4 @@ -name: 'Publish documentation' +name: 'Publish documentation' on: workflow_dispatch: @@ -35,7 +35,7 @@ jobs: 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,7 +56,7 @@ 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 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..30379531 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ concurrency: cancel-in-progress: false env: - dotnet-sdk-version: '10.x' + dotnet-sdk-version: '9.x' build-configuration: 'Release' build-platform: 'Any CPU' git-version: '6.0.x' @@ -65,7 +65,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 +119,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 +136,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 +146,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 +182,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 +190,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 +199,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 +215,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 +242,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 diff --git a/.gitignore b/.gitignore index 88197d63..6105dc57 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,6 @@ __pycache__/ # BenchmarkDotNet artifacts **/BenchmarkDotNet.Artifacts/ + +# GitHub Copilot Testing folder +/.codetesting 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..274dcb87 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing to PolylineAlgorithm + +Thank you for your interest in improving this library! + +## 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..b235ecaa 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,213 @@ -# 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+ +- Immutable, strongly-typed coordinate and polyline data structures +- Predefined encoder and decoder types for quick usage, extensibility for custom coordinate types +- Robust input validation with descriptive exceptions for malformed/invalid data +- Simple, extensible encoding and decoding APIs (`IPolylineEncoder`, `IPolylineDecoder`, `AbstractPolylineEncoder`, `AbstractPolylineDecoder`) +- Default encoding and decoding implementations (`PolylineEncoder`, `PolylineDecoder`) +- Advanced configuration via `PolylineEncodingOptions` (buffer size, logging, etc.) +- Internal logging and diagnostic supports logging for CI/CD and developer diagnostics +- 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 +### PolylineEncoder and PolylineDecoder (predefined `Coordinate` and `Polyline` types) + +#### Encoding + +```csharp +using PolylineAlgorithm; + +var coordinates = new List +{ + new Coordinate(48.858370, 2.294481), + new Coordinate(51.500729, -0.124625) +}; + +var encoder = new PolylineEncoder(); +Polyline encoded = encoder.Encode(coordinates); + +Console.WriteLine(encoded.ToString()); +``` + +#### Decoding + +```csharp +using PolylineAlgorithm; + +Polyline polyline = Polyline.FromString("yseiHoc_MwacOjnwM"); + +var decoder = new PolylineDecoder(); +IEnumerable decoded = decoder.Decode(polyline); +``` + +### Custom encoder and decoder (user-defined coordinate and polyline types) + +#### Encoding + +Custom encoder implementation. + +```csharp +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + public PolylineEncoder() + : base() { } + + public PolylineEncoder(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; + +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); + +Console.WriteLine(encoded.ToString()); +``` + +#### Decoding + +Custom decoder implementation. + +```csharp +public sealed class MyPolylineDecoder : 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(ref string polyline) { + return polyline.AsMemory(); + } +} +``` + +Custom decoder usage. + +```csharp +using PolylineAlgorithm; + +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 for malformed polyline strings. Check exception handling in your application. + +**Q: How do I customize encoding options (e.g., buffer size, logging)?** +A: Use the PolylineEncodingOptionsBuilder to set custom options and pass to the PolylineEncoder 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. In case, you would like to improve guides you have to updage relevant markdown file in `/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..9f220428 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#L21 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 @@ -54,7 +54,7 @@ body: - 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#L27 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor @@ -62,7 +62,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#L39 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -84,11 +84,11 @@ body: - 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#L53 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 +98,10 @@ 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#L180 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. - code: protected abstract TCoordinate CreateCoordinate(double latitude, double longitude) - h4: Parameters - parameters: @@ -110,20 +109,17 @@ body: 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: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L73 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0) @@ -156,22 +152,62 @@ body: description: Thrown when polyline is empty. - type: - text: InvalidPolylineException - url: PolylineAlgorithm.InvalidPolylineException.html + url: PolylineAlgorithm.Diagnostics.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 +- 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#L85 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.Decode(`0,System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) +- markdown: Decodes an encoded polyline with cancellation support. +- code: public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The encoded polyline. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: Cancellation token. +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TCoordinate + - '>' + description: Decoded coordinates. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: '' + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: '' + - type: + - text: InvalidPolylineException + url: PolylineAlgorithm.Diagnostics.InvalidPolylineException.html + description: '' +- 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#L177 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) +- 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. - h4: Returns - parameters: - type: @@ -181,7 +217,29 @@ body: - text: char url: https://learn.microsoft.com/dotnet/api/system.char - '>' - description: A representing the encoded polyline data. +- 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#L166 + 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 polyline format for allowed characters. +- 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 + - '>' + - name: logger + type: + - text: ILogger + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger + - '?' languageId: csharp metadata: description: >- diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml index 45ae4d3e..da8b0825 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#L25 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 @@ -49,12 +49,18 @@ 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. - 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#L30 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor @@ -62,7 +68,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#L40 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -84,7 +90,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#L54 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options @@ -98,7 +104,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#L162 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) @@ -120,20 +126,23 @@ 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) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_ReadOnlySpan__0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L73 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}) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.ReadOnlySpan{`0}) - 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) - 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 - '>' @@ -142,10 +151,7 @@ body: - 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 +162,17 @@ body: - text: ArgumentException url: https://learn.microsoft.com/dotnet/api/system.argumentexception description: Thrown when coordinates is an empty enumeration. + - type: + - text: InternalBufferOverflowException + url: https://learn.microsoft.com/dotnet/api/system.io.internalbufferoverflowexception + description: '' + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: '' - 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#L182 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) @@ -178,7 +192,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#L172 metadata: uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml index 01478f20..aff84ab1 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml @@ -3,7 +3,7 @@ title: Interface IPolylineDecoder body: - api1: Interface IPolylineDecoder id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/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#L13 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineDecoder`2 @@ -15,7 +15,7 @@ body: - name: Assembly value: PolylineAlgorithm.dll - markdown: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. -- code: public interface IPolylineDecoder +- code: public interface IPolylineDecoder - h4: Type Parameters - parameters: - name: TPolyline @@ -23,12 +23,12 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L23 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) +- code: IEnumerable Decode(TPolyline polyline) - h4: Parameters - parameters: - name: polyline diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml index 424f9516..cd418379 100644 --- a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml @@ -3,7 +3,7 @@ title: Interface IPolylineEncoder body: - api1: Interface IPolylineEncoder id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2 - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/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#L10 metadata: uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2 commentId: T:PolylineAlgorithm.Abstraction.IPolylineEncoder`2 @@ -20,21 +20,27 @@ body: - parameters: - name: TCoordinate - name: TPolyline +- 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: 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) + id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2_Encode_System_ReadOnlySpan__0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L20 metadata: - uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) - commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.Collections.Generic.IEnumerable{`0}) + uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0}) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0}) - markdown: Encodes a sequence of geographic coordinates into an encoded polyline representation. -- code: TPolyline Encode(IEnumerable coordinates) +- code: TPolyline Encode(ReadOnlySpan coordinates) - 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 - '>' diff --git a/api-reference/1.0/PolylineAlgorithm.Coordinate.Validator.yml b/api-reference/1.0/PolylineAlgorithm.Coordinate.Validator.yml new file mode 100644 index 00000000..58a7d4d0 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Coordinate.Validator.yml @@ -0,0 +1,131 @@ +### YamlMime:ApiPage +title: Class Coordinate.Validator +body: +- api1: Class Coordinate.Validator + id: PolylineAlgorithm_Coordinate_Validator + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L151 + metadata: + uid: PolylineAlgorithm.Coordinate.Validator + commentId: T:PolylineAlgorithm.Coordinate.Validator +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides static methods for validating latitude and longitude values used in . +- code: public static class Coordinate.Validator +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: Coordinate.Validator + url: PolylineAlgorithm.Coordinate.Validator.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: >- + The Validator class ensures that latitude and longitude values are within their valid ranges: + + +
  • Latitude must be between -90 and 90 degrees.
  • Longitude must be between -180 and 180 degrees.
+ + + If a value is out of range or not finite, an exception is thrown via . +- h2: Methods +- api3: ValidateLatitude(double) + id: PolylineAlgorithm_Coordinate_Validator_ValidateLatitude_System_Double_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L159 + metadata: + uid: PolylineAlgorithm.Coordinate.Validator.ValidateLatitude(System.Double) + commentId: M:PolylineAlgorithm.Coordinate.Validator.ValidateLatitude(System.Double) +- markdown: Validates that the specified latitude is within the valid range of -90 to 90 degrees. +- code: public static void ValidateLatitude(double latitude) +- h4: Parameters +- parameters: + - name: latitude + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The latitude value to validate. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentOutOfRangeException + url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception + description: Thrown if latitude is less than -90, greater than 90, or not a finite number. +- api3: ValidateLongitude(double) + id: PolylineAlgorithm_Coordinate_Validator_ValidateLongitude_System_Double_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L174 + metadata: + uid: PolylineAlgorithm.Coordinate.Validator.ValidateLongitude(System.Double) + commentId: M:PolylineAlgorithm.Coordinate.Validator.ValidateLongitude(System.Double) +- markdown: Validates that the specified longitude is within the valid range of -180 to 180 degrees. +- code: public static void ValidateLongitude(double longitude) +- h4: Parameters +- parameters: + - name: longitude + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The longitude value to validate. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentOutOfRangeException + url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception + description: Thrown if longitude is less than -180, greater than 180, or not a finite number. +- api3: ValidateValue(double, double, double, string) + id: PolylineAlgorithm_Coordinate_Validator_ValidateValue_System_Double_System_Double_System_Double_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L192 + metadata: + uid: PolylineAlgorithm.Coordinate.Validator.ValidateValue(System.Double,System.Double,System.Double,System.String) + commentId: M:PolylineAlgorithm.Coordinate.Validator.ValidateValue(System.Double,System.Double,System.Double,System.String) +- markdown: Validates that the specified value is finite and within the specified range. +- code: public static void ValidateValue(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 value to validate. + - name: min + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The minimum allowed value (inclusive). + - name: max + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The maximum allowed value (inclusive). + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: The name of the parameter being validated. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentOutOfRangeException + url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception + description: Thrown if value is not finite, less than min, or greater than max. +languageId: csharp +metadata: + description: Provides static methods for validating latitude and longitude values used in . diff --git a/api-reference/1.0/PolylineAlgorithm.Coordinate.yml b/api-reference/1.0/PolylineAlgorithm.Coordinate.yml index 7f5afdf3..27035645 100644 --- a/api-reference/1.0/PolylineAlgorithm.Coordinate.yml +++ b/api-reference/1.0/PolylineAlgorithm.Coordinate.yml @@ -3,7 +3,7 @@ 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L25 metadata: uid: PolylineAlgorithm.Coordinate commentId: T:PolylineAlgorithm.Coordinate @@ -41,18 +41,23 @@ body: geographic coordinates in degrees. It includes validation for latitude and longitude ranges and provides methods for equality comparison and string representation. + + +

Note: The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by .

- 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L34 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() +- h4: Remarks +- markdown: The default value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by . - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L52 metadata: uid: PolylineAlgorithm.Coordinate.#ctor(System.Double,System.Double) commentId: M:PolylineAlgorithm.Coordinate.#ctor(System.Double,System.Double) @@ -82,7 +87,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L62 metadata: uid: PolylineAlgorithm.Coordinate.Latitude commentId: P:PolylineAlgorithm.Coordinate.Latitude @@ -95,7 +100,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L67 metadata: uid: PolylineAlgorithm.Coordinate.Longitude commentId: P:PolylineAlgorithm.Coordinate.Longitude @@ -109,7 +114,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L83 metadata: uid: PolylineAlgorithm.Coordinate.Equals(System.Object) commentId: M:PolylineAlgorithm.Coordinate.Equals(System.Object) @@ -131,7 +136,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L86 metadata: uid: PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate) commentId: M:PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate) @@ -150,9 +155,35 @@ body: - 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(Coordinate, double) + id: PolylineAlgorithm_Coordinate_Equals_PolylineAlgorithm_Coordinate_System_Double_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L99 + metadata: + uid: PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate,System.Double) + commentId: M:PolylineAlgorithm.Coordinate.Equals(PolylineAlgorithm.Coordinate,System.Double) +- markdown: Determines whether two instances are approximately equal within a specified tolerance. +- code: public bool Equals(Coordinate other, double tolerance) +- h4: Parameters +- parameters: + - name: other + type: + - text: Coordinate + url: PolylineAlgorithm.Coordinate.html + description: The other coordinate to compare. + - name: tolerance + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The maximum allowed difference for latitude and longitude. +- h4: Returns +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean + description: true if both latitude and longitude differ by less than tolerance; 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L104 metadata: uid: PolylineAlgorithm.Coordinate.GetHashCode commentId: M:PolylineAlgorithm.Coordinate.GetHashCode @@ -166,7 +197,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L78 metadata: uid: PolylineAlgorithm.Coordinate.IsDefault commentId: M:PolylineAlgorithm.Coordinate.IsDefault @@ -178,9 +209,11 @@ body: - text: bool url: https://learn.microsoft.com/dotnet/api/system.boolean description: true if both latitude and longitude are 0; otherwise, false. +- h4: Remarks +- markdown: The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by this method. - api3: ToString() id: PolylineAlgorithm_Coordinate_ToString - src: https://github.com/petesramek/polyline-algorithm-csharp/blob/preview/1.0/src/PolylineAlgorithm/Coordinate.cs#L93 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L112 metadata: uid: PolylineAlgorithm.Coordinate.ToString commentId: M:PolylineAlgorithm.Coordinate.ToString @@ -195,7 +228,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L124 metadata: uid: PolylineAlgorithm.Coordinate.op_Equality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) commentId: M:PolylineAlgorithm.Coordinate.op_Equality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) @@ -221,7 +254,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Coordinate.cs#L134 metadata: uid: PolylineAlgorithm.Coordinate.op_Inequality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) commentId: M:PolylineAlgorithm.Coordinate.op_Inequality(PolylineAlgorithm.Coordinate,PolylineAlgorithm.Coordinate) diff --git a/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml b/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml index 775098b4..915d1643 100644 --- a/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml +++ b/api-reference/1.0/PolylineAlgorithm.CoordinateValueType.yml @@ -3,7 +3,7 @@ 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/CoordinateValueType.cs#L11 metadata: uid: PolylineAlgorithm.CoordinateValueType commentId: T:PolylineAlgorithm.CoordinateValueType @@ -28,7 +28,7 @@ body: description: >+ Represents a longitude value. - - name: None + - name: Unspecified default: "0" description: >+ Represents no specific type. This value is used when the type is not applicable or not specified. diff --git a/api-reference/1.0/PolylineAlgorithm.Diagnostics.InvalidPolylineException.yml b/api-reference/1.0/PolylineAlgorithm.Diagnostics.InvalidPolylineException.yml new file mode 100644 index 00000000..585ee5a2 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Diagnostics.InvalidPolylineException.yml @@ -0,0 +1,102 @@ +### YamlMime:ApiPage +title: Class InvalidPolylineException +body: +- api1: Class InvalidPolylineException + id: PolylineAlgorithm_Diagnostics_InvalidPolylineException + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs#L17 + metadata: + uid: PolylineAlgorithm.Diagnostics.InvalidPolylineException + commentId: T:PolylineAlgorithm.Diagnostics.InvalidPolylineException +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Diagnostics + url: PolylineAlgorithm.Diagnostics.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Exception thrown when a polyline is determined to be malformed or invalid during processing. +- code: 'public sealed class InvalidPolylineException : Exception, ISerializable' +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: Exception + url: https://learn.microsoft.com/dotnet/api/system.exception + - text: InvalidPolylineException + url: PolylineAlgorithm.Diagnostics.InvalidPolylineException.html +- h4: Implements +- list: + - text: ISerializable + url: https://learn.microsoft.com/dotnet/api/system.runtime.serialization.iserializable +- h4: Inherited Members +- list: + - text: Exception.GetBaseException() + url: https://learn.microsoft.com/dotnet/api/system.exception.getbaseexception + - text: Exception.GetObjectData(SerializationInfo, StreamingContext) + url: https://learn.microsoft.com/dotnet/api/system.exception.getobjectdata + - text: Exception.GetType() + url: https://learn.microsoft.com/dotnet/api/system.exception.gettype + - text: Exception.ToString() + url: https://learn.microsoft.com/dotnet/api/system.exception.tostring + - text: Exception.Data + url: https://learn.microsoft.com/dotnet/api/system.exception.data + - text: Exception.HelpLink + url: https://learn.microsoft.com/dotnet/api/system.exception.helplink + - text: Exception.HResult + url: https://learn.microsoft.com/dotnet/api/system.exception.hresult + - text: Exception.InnerException + url: https://learn.microsoft.com/dotnet/api/system.exception.innerexception + - text: Exception.Message + url: https://learn.microsoft.com/dotnet/api/system.exception.message + - text: Exception.Source + url: https://learn.microsoft.com/dotnet/api/system.exception.source + - text: Exception.StackTrace + url: https://learn.microsoft.com/dotnet/api/system.exception.stacktrace + - text: Exception.TargetSite + url: https://learn.microsoft.com/dotnet/api/system.exception.targetsite + - 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 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_Diagnostics_InvalidPolylineException__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs#L22 + metadata: + uid: PolylineAlgorithm.Diagnostics.InvalidPolylineException.#ctor + commentId: M:PolylineAlgorithm.Diagnostics.InvalidPolylineException.#ctor +- markdown: Initializes a new instance of the class. +- code: public InvalidPolylineException() +- api3: InvalidPolylineException(string, Exception) + id: PolylineAlgorithm_Diagnostics_InvalidPolylineException__ctor_System_String_System_Exception_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs#L43 + metadata: + uid: PolylineAlgorithm.Diagnostics.InvalidPolylineException.#ctor(System.String,System.Exception) + commentId: M:PolylineAlgorithm.Diagnostics.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.Diagnostics.yml b/api-reference/1.0/PolylineAlgorithm.Diagnostics.yml new file mode 100644 index 00000000..6fba5591 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Diagnostics.yml @@ -0,0 +1,15 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm.Diagnostics +body: +- api1: Namespace PolylineAlgorithm.Diagnostics + id: PolylineAlgorithm_Diagnostics + metadata: + uid: PolylineAlgorithm.Diagnostics + commentId: N:PolylineAlgorithm.Diagnostics +- h3: Classes +- parameters: + - type: + text: InvalidPolylineException + url: PolylineAlgorithm.Diagnostics.InvalidPolylineException.html + description: Exception thrown when a polyline is determined to be malformed or invalid during processing. +languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml index 086af359..7279835d 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#L17 metadata: uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions commentId: T:PolylineAlgorithm.Extensions.PolylineDecoderExtensions @@ -41,7 +41,7 @@ body: - 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 + 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) @@ -85,7 +85,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L56 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[]) @@ -131,7 +131,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L79 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}) diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml index 767322e4..5b05f48a 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#L16 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#L32 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}) + 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 collection of instances into an encoded polyline. -- code: public static Polyline Encode(this IPolylineEncoder encoder, ICollection coordinates) +- 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,43 @@ 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. - h4: Returns - parameters: - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - TPolyline description: A representing the encoded polyline string for the provided coordinates. +- h4: Type Parameters +- parameters: + - name: TCoordinate + - name: TPolyline - 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 +- 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#L66 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[]) + 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 instances into an encoded polyline. -- code: public static Polyline Encode(this IPolylineEncoder encoder, Coordinate[] coordinates) +- code: public static TPolyline Encode(this IPolylineEncoder encoder, TCoordinate[] coordinates) - h4: Parameters - parameters: - name: encoder @@ -98,27 +103,27 @@ 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. - h4: Returns - parameters: - type: - - text: Polyline - url: PolylineAlgorithm.Polyline.html + - TPolyline description: A representing the encoded polyline string for the provided coordinates. +- h4: Type Parameters +- parameters: + - name: TCoordinate + - name: TPolyline - h4: Exceptions - parameters: - type: diff --git a/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml b/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml index fd2c31fe..31640d0a 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#L23 metadata: uid: PolylineAlgorithm.InvalidPolylineException commentId: T:PolylineAlgorithm.InvalidPolylineException @@ -16,6 +16,8 @@ body: value: PolylineAlgorithm.dll - markdown: Exception thrown when a polyline is determined to be malformed or invalid during processing. - code: >- + [SuppressMessage("Roslynator", "RCS1194:Implement exception constructors", Justification = "Internal use only.")] + [SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Internal use only.")] public sealed class InvalidPolylineException : Exception, ISerializable diff --git a/api-reference/1.0/PolylineAlgorithm.Polyline.yml b/api-reference/1.0/PolylineAlgorithm.Polyline.yml index 5da10f97..c97a8826 100644 --- a/api-reference/1.0/PolylineAlgorithm.Polyline.yml +++ b/api-reference/1.0/PolylineAlgorithm.Polyline.yml @@ -3,7 +3,7 @@ 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L21 metadata: uid: PolylineAlgorithm.Polyline commentId: T:PolylineAlgorithm.Polyline @@ -42,7 +42,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L27 metadata: uid: PolylineAlgorithm.Polyline.#ctor commentId: M:PolylineAlgorithm.Polyline.#ctor @@ -51,7 +51,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L49 metadata: uid: PolylineAlgorithm.Polyline.IsEmpty commentId: P:PolylineAlgorithm.Polyline.IsEmpty @@ -64,21 +64,21 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L54 metadata: uid: PolylineAlgorithm.Polyline.Length commentId: P:PolylineAlgorithm.Polyline.Length -- markdown: Gets the length of the polyline in characters. -- code: public long Length { get; } +- markdown: Gets the length of the polyline in characters as an . +- code: public int Length { get; } - h4: Property Value - parameters: - type: - - text: long - url: https://learn.microsoft.com/dotnet/api/system.int64 + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L68 metadata: uid: PolylineAlgorithm.Polyline.CopyTo(System.Char[]) commentId: M:PolylineAlgorithm.Polyline.CopyTo(System.Char[]) @@ -105,11 +105,11 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L123 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. +- markdown: Determines whether the current instance is equal to another instance. - code: public bool Equals(Polyline other) - h4: Parameters - parameters: @@ -117,27 +117,28 @@ body: type: - text: Polyline url: PolylineAlgorithm.Polyline.html - description: An object to compare with this object. + description: The other to compare with. - 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) + description: true if the instances are equal; 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L132 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) +- 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: @@ -147,7 +148,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L188 metadata: uid: PolylineAlgorithm.Polyline.FromCharArray(System.Char[]) commentId: M:PolylineAlgorithm.Polyline.FromCharArray(System.Char[]) @@ -176,7 +177,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L225 metadata: uid: PolylineAlgorithm.Polyline.FromMemory(System.ReadOnlyMemory{System.Char}) commentId: M:PolylineAlgorithm.Polyline.FromMemory(System.ReadOnlyMemory{System.Char}) @@ -201,7 +202,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L208 metadata: uid: PolylineAlgorithm.Polyline.FromString(System.String) commentId: M:PolylineAlgorithm.Polyline.FromString(System.String) @@ -228,7 +229,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L137 metadata: uid: PolylineAlgorithm.Polyline.GetHashCode commentId: M:PolylineAlgorithm.Polyline.GetHashCode @@ -242,7 +243,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L86 metadata: uid: PolylineAlgorithm.Polyline.ToString commentId: M:PolylineAlgorithm.Polyline.ToString @@ -257,7 +258,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L162 metadata: uid: PolylineAlgorithm.Polyline.op_Equality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) commentId: M:PolylineAlgorithm.Polyline.op_Equality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) @@ -283,7 +284,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Polyline.cs#L172 metadata: uid: PolylineAlgorithm.Polyline.op_Inequality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) commentId: M:PolylineAlgorithm.Polyline.op_Inequality(PolylineAlgorithm.Polyline,PolylineAlgorithm.Polyline) diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml index 446fdd9c..98a08412 100644 --- a/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml +++ b/api-reference/1.0/PolylineAlgorithm.PolylineDecoder.yml @@ -3,7 +3,7 @@ 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L11 metadata: uid: PolylineAlgorithm.PolylineDecoder commentId: T:PolylineAlgorithm.PolylineDecoder @@ -37,6 +37,8 @@ body: 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: AbstractPolylineDecoder.Decode(Polyline, CancellationToken) + url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ - text: object.Equals(object) url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) - text: object.Equals(object, object) @@ -62,7 +64,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L13 metadata: uid: PolylineAlgorithm.PolylineDecoder.#ctor commentId: M:PolylineAlgorithm.PolylineDecoder.#ctor @@ -70,7 +72,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineDecoder.cs#L17 metadata: uid: PolylineAlgorithm.PolylineDecoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.PolylineDecoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -92,11 +94,10 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/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: @@ -104,33 +105,28 @@ body: 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 +- api3: GetReadOnlyMemory(in Polyline) + id: PolylineAlgorithm_PolylineDecoder_GetReadOnlyMemory_PolylineAlgorithm_Polyline__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/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) + uid: PolylineAlgorithm.PolylineDecoder.GetReadOnlyMemory(PolylineAlgorithm.Polyline@) + commentId: M:PolylineAlgorithm.PolylineDecoder.GetReadOnlyMemory(PolylineAlgorithm.Polyline@) +- code: protected override ReadOnlyMemory GetReadOnlyMemory(in 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: @@ -140,7 +136,6 @@ body: - text: char url: https://learn.microsoft.com/dotnet/api/system.char - '>' - description: A representing the encoded polyline data. languageId: csharp metadata: description: >- diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml index 2cc3a5dd..25c5add2 100644 --- a/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncoder.yml @@ -3,7 +3,7 @@ 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L11 metadata: uid: PolylineAlgorithm.PolylineEncoder commentId: T:PolylineAlgorithm.PolylineEncoder @@ -35,8 +35,8 @@ body: - 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: AbstractPolylineEncoder.Encode(ReadOnlySpan) + url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html#PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_ReadOnlySpan__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) @@ -51,16 +51,16 @@ body: 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___ + - 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, Coordinate[]) + 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. - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L13 metadata: uid: PolylineAlgorithm.PolylineEncoder.#ctor commentId: M:PolylineAlgorithm.PolylineEncoder.#ctor @@ -68,7 +68,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L17 metadata: uid: PolylineAlgorithm.PolylineEncoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) commentId: M:PolylineAlgorithm.PolylineEncoder.#ctor(PolylineAlgorithm.PolylineEncodingOptions) @@ -90,7 +90,7 @@ body: - 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/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}) @@ -115,7 +115,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L21 metadata: uid: PolylineAlgorithm.PolylineEncoder.GetLatitude(PolylineAlgorithm.Coordinate) commentId: M:PolylineAlgorithm.PolylineEncoder.GetLatitude(PolylineAlgorithm.Coordinate) @@ -135,7 +135,7 @@ body: 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 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoder.cs#L26 metadata: uid: PolylineAlgorithm.PolylineEncoder.GetLongitude(PolylineAlgorithm.Coordinate) commentId: M:PolylineAlgorithm.PolylineEncoder.GetLongitude(PolylineAlgorithm.Coordinate) diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validation.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validation.yml new file mode 100644 index 00000000..738205ac --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validation.yml @@ -0,0 +1,157 @@ +### YamlMime:ApiPage +title: Class PolylineEncoding.Validation +body: +- api1: Class PolylineEncoding.Validation + id: PolylineAlgorithm_PolylineEncoding_Validation + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L322 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validation + commentId: T:PolylineAlgorithm.PolylineEncoding.Validation +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- code: public static class PolylineEncoding.Validation +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncoding.Validation + url: PolylineAlgorithm.PolylineEncoding.Validation.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Methods +- api3: ValidateBlockLength(ReadOnlySpan) + id: PolylineAlgorithm_PolylineEncoding_Validation_ValidateBlockLength_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L419 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validation.ValidateBlockLength(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validation.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_Validation_ValidateCharRange_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L373 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validation.ValidateCharRange(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validation.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_Validation_ValidateFormat_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L351 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validation.ValidateFormat(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validation.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 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. +
+

+ + 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 diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validator.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validator.yml new file mode 100644 index 00000000..8f58348f --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.Validator.yml @@ -0,0 +1,157 @@ +### YamlMime:ApiPage +title: Class PolylineEncoding.Validator +body: +- api1: Class PolylineEncoding.Validator + id: PolylineAlgorithm_PolylineEncoding_Validator + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L321 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validator + commentId: T:PolylineAlgorithm.PolylineEncoding.Validator +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- code: public static class PolylineEncoding.Validator +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncoding.Validator + url: PolylineAlgorithm.PolylineEncoding.Validator.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Methods +- api3: ValidateBlockLength(ReadOnlySpan) + id: PolylineAlgorithm_PolylineEncoding_Validator_ValidateBlockLength_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L418 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validator.ValidateBlockLength(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validator.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_Validator_ValidateCharRange_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L372 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validator.ValidateCharRange(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validator.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_Validator_ValidateFormat_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L350 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Validator.ValidateFormat(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.Validator.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 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. +
+

+ + 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 diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml index 8b249376..5b61ad77 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 rounding the result using the specified rounding strategy. + +

+ +

+ + 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 GetCharCount. + + 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..673cd42f 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: Returns 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 precision for encoding values. +- 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 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 builder instance. +- 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: Returns 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.Pow10.yml b/api-reference/1.0/PolylineAlgorithm.Pow10.yml new file mode 100644 index 00000000..460a5871 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Pow10.yml @@ -0,0 +1,104 @@ +### YamlMime:ApiPage +title: Class Pow10 +body: +- api1: Class Pow10 + id: PolylineAlgorithm_Pow10 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Pow10.cs#L12 + metadata: + uid: PolylineAlgorithm.Pow10 + commentId: T:PolylineAlgorithm.Pow10 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides optimized calculation of powers of 10 for precision-based operations. +- code: public static class Pow10 +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: Pow10 + url: PolylineAlgorithm.Pow10.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: >- + This class caches common powers of 10 (10^0 through 10^9) for efficient lookup, + + falling back to for larger exponents. +- h2: Properties +- api3: UseCache + id: PolylineAlgorithm_Pow10_UseCache + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Pow10.cs#L29 + metadata: + uid: PolylineAlgorithm.Pow10.UseCache + commentId: P:PolylineAlgorithm.Pow10.UseCache +- markdown: Gets or sets a value indicating whether pre-computed powers of 10 should be used for optimization. +- code: public static bool UseCache { get; set; } +- h4: Property Value +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean +- h4: Remarks +- markdown: >- + When enabled, retrieves values from the cache for precision levels 0-9, + + providing faster performance. When disabled, all calculations use . +- h2: Methods +- api3: GetFactor(uint) + id: PolylineAlgorithm_Pow10_GetFactor_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Pow10.cs#L49 + metadata: + uid: PolylineAlgorithm.Pow10.GetFactor(System.UInt32) + commentId: M:PolylineAlgorithm.Pow10.GetFactor(System.UInt32) +- markdown: Returns the power of 10 for the specified precision level. +- code: public static uint GetFactor(uint precision) +- h4: Parameters +- parameters: + - name: precision + type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: The exponent for the base 10 (i.e., the number of decimal places). +- h4: Returns +- parameters: + - type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: The value of 10 raised to the power of precision as a +- h4: Remarks +- markdown: >- + If is true and precision is between 0 and 9 (inclusive), + + the method returns a cached value for optimal performance. For other values or if caching is disabled, + + the result is computed using . The calculation is performed in a checked context to + + ensure that arithmetic overflow is detected. +- h4: Exceptions +- parameters: + - type: + - text: OverflowException + url: https://learn.microsoft.com/dotnet/api/system.overflowexception + description: Thrown if the computed value exceeds . +languageId: csharp +metadata: + description: Provides optimized calculation of powers of 10 for precision-based operations. diff --git a/api-reference/1.0/PolylineAlgorithm.yml b/api-reference/1.0/PolylineAlgorithm.yml index ace2f946..49e94637 100644 --- a/api-reference/1.0/PolylineAlgorithm.yml +++ b/api-reference/1.0/PolylineAlgorithm.yml @@ -11,15 +11,14 @@ body: - type: text: PolylineAlgorithm.Abstraction url: PolylineAlgorithm.Abstraction.html + - type: + text: PolylineAlgorithm.Diagnostics + url: PolylineAlgorithm.Diagnostics.html - type: text: PolylineAlgorithm.Extensions url: PolylineAlgorithm.Extensions.html - h3: Classes - parameters: - - type: - text: InvalidPolylineException - url: PolylineAlgorithm.InvalidPolylineException.html - description: Exception thrown when a polyline is determined to be malformed or invalid during processing. - type: text: PolylineDecoder url: PolylineAlgorithm.PolylineDecoder.html @@ -44,11 +43,15 @@ 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. + - type: + text: Coordinate.Validator + url: PolylineAlgorithm.Coordinate.Validator.html + description: Provides static methods for validating latitude and longitude values used in . - h3: Structs - parameters: - type: @@ -62,10 +65,4 @@ body: 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..d9b212c0 100644 --- a/api-reference/1.0/toc.yml +++ b/api-reference/1.0/toc.yml @@ -3,8 +3,8 @@ href: PolylineAlgorithm.yml items: - name: Classes - - name: InvalidPolylineException - href: PolylineAlgorithm.InvalidPolylineException.yml + - name: Coordinate.Validator + href: PolylineAlgorithm.Coordinate.Validator.yml - name: PolylineDecoder href: PolylineAlgorithm.PolylineDecoder.yml - name: PolylineEncoder @@ -20,9 +20,6 @@ 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: @@ -36,6 +33,12 @@ href: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml - name: IPolylineEncoder href: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml +- name: PolylineAlgorithm.Diagnostics + href: PolylineAlgorithm.Diagnostics.yml + items: + - name: Classes + - name: InvalidPolylineException + href: PolylineAlgorithm.Diagnostics.InvalidPolylineException.yml - name: PolylineAlgorithm.Extensions href: PolylineAlgorithm.Extensions.yml items: diff --git a/api-reference/guide/advanced-scenarios.md b/api-reference/guide/advanced-scenarios.md new file mode 100644 index 00000000..e731319c --- /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() + .SetMaxBufferSize(10000) + .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 buffer sizes via `PolylineEncodingOptions`. +- **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..4a155cb2 --- /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 Buffer Size + +```csharp +using PolylineAlgorithm; + +var options = PolylineEncodingOptionsBuilder.Create() + .SetMaxBufferSize(10000) // Set custom buffer size + .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: + +- **Buffer sizes:** Configure allocation for large or streaming polylines +- **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() + .SetMaxBufferSize(5000) + // .EnableLogging(myLogger) // (if applicable) + .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..64357007 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-scenarions.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/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj index 2859331b..a7c3ce4e 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,13 @@ - + + + + + + + diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs index 9d09883b..8275f93e 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineBenchmark.cs @@ -11,13 +11,16 @@ namespace PolylineAlgorithm.Benchmarks; using PolylineAlgorithm.Utility; /// -/// Benchmarks for the struct. +/// Benchmarks for . /// public class PolylineBenchmark { - private static readonly Consumer consumer = new(); + private static readonly Consumer _consumer = new(); + /// + /// Number of coordinates for benchmarks. Set by BenchmarkDotNet. + /// [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. /// @@ -45,8 +48,12 @@ public class PolylineBenchmark { /// public Polyline PolylineNotEqualValue { get; private set; } + /// + /// Gets the destination array used for benchmarking the method. + /// This array is initialized in to match the length of the encoded polyline, + /// and is used as the target buffer for copying polyline data during benchmark runs. + /// 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. @@ -55,8 +62,8 @@ public class PolylineBenchmark { /// [GlobalSetup] public void SetupData() { - PolylineValue = Polyline.FromString(RandomValueProvider.GetPolyline(Count)); - PolylineNotEqualValue = Polyline.FromString(RandomValueProvider.GetPolyline(Count + Random.Shared.Next(1, 101))); + PolylineValue = Polyline.FromString(RandomValueProvider.GetPolyline(CoordinatesCount)); + PolylineNotEqualValue = Polyline.FromString(RandomValueProvider.GetPolyline(CoordinatesCount + Random.Shared.Next(1, 101))); StringValue = PolylineValue.ToString(); CharArrayValue = [.. StringValue]; MemoryValue = CharArrayValue.AsMemory(); @@ -65,101 +72,101 @@ public void SetupData() { } /// - /// Benchmarks the encoding of a list of coordinates into a polyline. + /// Benchmark: create polyline from string. /// /// The encoded polyline. [Benchmark] - public Polyline Polyline_FromString() { + public void Polyline_FromString() { var polyline = Polyline - .FromString(StringValue); + .FromString(StringValue); - return polyline; + _consumer.Consume(polyline); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: create polyline from char array. /// /// The encoded polyline. [Benchmark] - public Polyline Polyline_FromCharArray() { + public void Polyline_FromCharArray() { var polyline = Polyline - .FromCharArray(CharArrayValue); + .FromCharArray(CharArrayValue); - return polyline; + _consumer.Consume(polyline); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: create polyline from memory. /// /// The encoded polyline. [Benchmark] - public Polyline Polyline_FromMemory() { + public void Polyline_FromMemory() { var polyline = Polyline - .FromMemory(MemoryValue); + .FromMemory(MemoryValue); - return polyline; + _consumer.Consume(polyline); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: convert polyline to string. /// /// The encoded polyline. [Benchmark] - public string Polyline_ToString() { + public void Polyline_ToString() { var stringValue = PolylineValue - .ToString(); + .ToString(); - return stringValue; + _consumer.Consume(stringValue); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: copy polyline to array. /// /// The encoded polyline. [Benchmark] public void Polyline_CopyTo() { PolylineValue - .CopyTo(CopyToDestination); + .CopyTo(CopyToDestination); CopyToDestination - .Consume(consumer); + .Consume(_consumer); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: compare polyline with same value. /// /// The encoded polyline. [Benchmark] - public bool Polyline_Equals_SameValue() { + public void Polyline_Equals_SameValue() { var equals = PolylineValue - .Equals(PolylineValue); + .Equals(PolylineValue); - return equals; + _consumer.Consume(equals); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: compare polyline with different value. /// /// The encoded polyline. [Benchmark] - public bool Polyline_Equals_DifferentValue() { + public void Polyline_Equals_DifferentValue() { var equals = PolylineValue - .Equals(PolylineNotEqualValue); + .Equals(PolylineNotEqualValue); - return equals; + _consumer.Consume(equals); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: compare polyline with different type. /// /// The encoded polyline. [Benchmark] - public bool Polyline_Equals_DifferentType() { + public void Polyline_Equals_DifferentType() { var equals = PolylineValue - .Equals(StringValue); + .Equals(StringValue); - return equals; + _consumer.Consume(equals); } } \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs index 718cdd8b..fc8ba1e9 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs @@ -8,45 +8,94 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using PolylineAlgorithm; +using PolylineAlgorithm.Extensions; 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. + /// Polyline instance for benchmarks. /// public Polyline Polyline { get; private set; } + /// + /// Encoded polyline as string. + /// + 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. + /// Polyline decoder instance. /// - public PolylineDecoder Decoder = new(); + private readonly PolylineDecoder _decoder = new(); /// - /// Sets up the data for the benchmarks. + /// Sets up benchmark data. /// [GlobalSetup] public void SetupData() { - Polyline = Polyline.FromString(RandomValueProvider.GetPolyline(Count)); + Polyline = Polyline.FromString(RandomValueProvider.GetPolyline(CoordinatesCount)); + String = RandomValueProvider.GetPolyline(CoordinatesCount); + CharArray = RandomValueProvider.GetPolyline(CoordinatesCount).ToCharArray(); + Memory = RandomValueProvider.GetPolyline(CoordinatesCount).AsMemory(); } /// - /// Benchmarks the decoding of a polyline from a string. + /// Benchmark: decode polyline instance. /// [Benchmark] - public void PolylineDecoder_Decode() { - Decoder + public void PolylineDecoder_Decode_Polyline() { + _decoder .Decode(Polyline) .Consume(_consumer); } + + /// + /// Benchmark: decode from string. + /// + [Benchmark] + public void PolylineDecoder_Decode_String() { + _decoder + .Decode(String) + .Consume(_consumer); + } + + /// + /// Benchmark: decode from char array. + /// + [Benchmark] + public void PolylineDecoder_Decode_CharArray() { + _decoder + .Decode(CharArray) + .Consume(_consumer); + } + + /// + /// Benchmark: decode from memory. + /// + [Benchmark] + public void PolylineDecoder_Decode_Memory() { + _decoder + .Decode(Memory) + .Consume(_consumer); + } } \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs index 71743ca8..3993ecf8 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs @@ -6,64 +6,90 @@ namespace PolylineAlgorithm.Benchmarks; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; using PolylineAlgorithm; +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 IEnumerable Enumeration { get; private set; } + public List List { get; private set; } /// - /// Gets the list of coordinates to be encoded. + /// Coordinates as array. /// - public List List { get; private set; } + public Coordinate[] Array { get; private set; } + + /// + /// Coordinates 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 encoder instance. + /// Polyline encoder instance. /// - public PolylineEncoder Encoder = new(); + private readonly PolylineEncoder _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).Select(c => new Coordinate(c.Latitude, c.Longitude))]; + Array = [.. List]; + Memory = Array.AsMemory(); } /// - /// Benchmarks the encoding of a list of coordinates into a polyline. + /// Benchmark: encode coordinates from span. /// - /// The encoded polyline. + /// 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); + } + + /// + /// Benchmark: encode coordinates from array. + /// + /// Encoded polyline. + [Benchmark] + public void PolylineEncoder_Encode_Array() { + var polyline = _encoder + .Encode(Array!); - return polyline; + _consumer.Consume(polyline); } /// - /// Benchmarks the encoding of an enumeration of coordinates into a polyline. + /// Benchmark: encode coordinates from list. /// - /// The encoded polyline. + /// Encoded polyline. [Benchmark] - public Polyline PolylineEncoder_Encode_Enumerator() { - var polyline = Encoder - .Encode(Enumeration!); + public void PolylineEncoder_Encode_List() { + var polyline = _encoder + .Encode(List!); - return polyline; + _consumer.Consume(polyline); } } \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingValidationBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingValidationBenchmark.cs new file mode 100644 index 00000000..42d3c349 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingValidationBenchmark.cs @@ -0,0 +1,29 @@ +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using PolylineAlgorithm.Utility; + +public class PolylineEncodingValidationBenchmark { + private string polyline; + + /// + /// 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..98b7cc75 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("Performance", "CA1822:Mark members as static", Justification = "Benchmarks need instance methods.")] \ No newline at end of file +[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.")] diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs index 10a034d0..8e6fcc68 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs @@ -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(); } } diff --git a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs index f9e71f50..d829d27c 100644 --- a/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs +++ b/samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs @@ -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,31 +26,27 @@ 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; } /// - /// 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; } 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..12c4a447 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs @@ -3,152 +3,181 @@ // 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.Diagnostics; using PolylineAlgorithm.Internal; -using PolylineAlgorithm.Internal.Logging; -using PolylineAlgorithm.Properties; -using System; -using System.Diagnostics.CodeAnalysis; - -/// -/// Decodes encoded polyline strings into sequences of geographic coordinates. -/// Implements the interface. -/// -/// -/// This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats. -/// -public abstract class AbstractPolylineDecoder : IPolylineDecoder { - /// - /// Initializes a new instance of the class with default encoding options. - /// - protected AbstractPolylineDecoder() - : this(new PolylineEncodingOptions()) { } - - /// - /// Initializes a new instance of the class with the specified encoding options. - /// - /// - /// The to use for encoding operations. - /// - /// - /// Thrown when is - /// - protected AbstractPolylineDecoder(PolylineEncodingOptions options) { - Options = options ?? throw new ArgumentNullException(nameof(options)); - } - - /// - /// Gets the encoding options used by this polyline encoder. - /// - public PolylineEncodingOptions Options { get; } +using PolylineAlgorithm.Internal.Diagnostics; +using PolylineAlgorithm.Internal.Diagnostics; +using System.Runtime.CompilerServices; +namespace PolylineAlgorithm.Abstraction { /// - /// Decodes an encoded into a sequence of instances. + /// Decodes encoded polyline strings into sequences of geographic coordinates. + /// Implements the interface. /// - /// - /// The instance containing the encoded polyline string to decode. - /// - /// - /// 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>(); - - logger. - LogOperationStartedInfo(nameof(Decode)); - - ValidateNullPolyline(polyline, logger); - - ReadOnlyMemory sequence = GetReadOnlyMemory(polyline); - - ValidateEmptySequence(logger, sequence); - - 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; + /// + /// This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats. + /// + public abstract class AbstractPolylineDecoder : IPolylineDecoder { + private readonly ILogger> _logger; + + /// + /// Initializes a new instance of the class with default encoding options. + /// + protected AbstractPolylineDecoder() + : this(new PolylineEncodingOptions()) { } + + /// + /// Initializes a new instance of the class with the specified encoding options. + /// + /// + /// The to use for encoding operations. + /// + /// + /// Thrown when is + /// + protected AbstractPolylineDecoder(PolylineEncodingOptions options) { + if (options is null) { + ExceptionGuard.ThrowArgumentNull(nameof(options)); } - // 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)); + Options = options; + _logger = Options + .LoggerFactory + .CreateLogger>(); + } - InvalidPolylineException.Throw(position); + /// + /// Gets the encoding options used by this polyline decoder. + /// + public PolylineEncodingOptions Options { get; } + + /// + /// Decodes an encoded into a sequence of instances. + /// + /// + /// The instance containing the encoded polyline string to decode. + /// + /// + /// 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) + => Decode(polyline, CancellationToken.None); + + /// + /// Decodes an encoded polyline with cancellation support. + /// + /// The encoded polyline. + /// Cancellation token. + /// Decoded coordinates. + /// + /// + /// + public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken) { + const string OperationName = nameof(Decode); + + _logger?.LogOperationStartedDebug(OperationName); + + ValidateNullPolyline(polyline, _logger); + + ReadOnlyMemory sequence = GetReadOnlyMemory(in polyline); + + ValidateSequence(sequence, _logger); + ValidateFormat(sequence, _logger); + + int position = 0; + int encodedLatitude = 0; + int encodedLongitude = 0; + + try { + while (position < sequence.Length) { + cancellationToken.ThrowIfCancellationRequested(); + + if (!PolylineEncoding.TryReadValue(ref encodedLatitude, sequence, ref position) + || !PolylineEncoding.TryReadValue(ref encodedLongitude, sequence, ref position)) { + _logger?.LogOperationFailedDebug(OperationName); + _logger?.LogInvalidPolylineWarning(position); + + ExceptionGuard.ThrowInvalidPolylineFormat(position); + } + + double decodedLatitude = PolylineEncoding.Denormalize(encodedLatitude, Options.Precision); + double decodedLongitude = PolylineEncoding.Denormalize(encodedLongitude, Options.Precision); + + _logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, position); + + yield return CreateCoordinate(decodedLatitude, decodedLongitude); + } + } finally { + _logger?.LogOperationFinishedDebug(OperationName); } - - yield return CreateCoordinate(PolylineEncoding.Denormalize(latitude, CoordinateValueType.Latitude), PolylineEncoding.Denormalize(longitude, CoordinateValueType.Longitude)); } - logger - .LogOperationFinishedInfo(nameof(Decode)); - - - static void ValidateNullPolyline(TPolyline polyline, ILogger> logger) { + /// + /// Validates that the provided polyline is not . + /// Throws an if the polyline is . + /// Optionally logs a warning if a logger is provided. + /// + /// The polyline instance to validate. + /// Optional logger for diagnostic messages. + /// + /// Thrown when is . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateNullPolyline(TPolyline polyline, ILogger? logger) { if (polyline is null) { - logger - .LogNullArgumentWarning(nameof(polyline)); - - throw new ArgumentNullException(nameof(polyline)); + 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 sequence meets the minimum required length. + /// Throws an if the sequence is too short. + /// Optionally logs diagnostic messages if a logger is provided. + /// + /// The polyline character sequence to validate. + /// Optional logger for diagnostic messages. + /// + /// Thrown when is shorter than the minimum allowed length. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateSequence(ReadOnlyMemory polylineSequence, ILogger? logger) { + if (polylineSequence.Length < Defaults.Polyline.Block.Length.Min) { + logger?.LogOperationFailedDebug(nameof(Decode)); + logger?.LogPolylineCannotBeShorterThanWarning(polylineSequence.Length, Defaults.Polyline.Block.Length.Min); + + ExceptionGuard.ThrowInvalidPolylineLength(polylineSequence.Length, Defaults.Polyline.Block.Length.Min); + } + } - throw new ArgumentException(string.Format(ExceptionMessageResource.PolylineCannotBeShorterThanExceptionMessage, sequence.Length), nameof(polyline)); + /// + /// Validates the polyline format for allowed characters. + /// + [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. - /// - /// - /// The instance containing the encoded polyline data to decode. - /// - /// - /// A representing the encoded polyline data. - /// - 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. - /// - /// - /// The latitude value. - /// - /// - /// The longitude value. - /// - /// - /// A coordinate instance of type . - /// - protected abstract TCoordinate CreateCoordinate(double latitude, double longitude); + [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..816684f3 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs @@ -8,12 +8,12 @@ namespace PolylineAlgorithm.Abstraction; using Microsoft.Extensions.Logging; using PolylineAlgorithm; using PolylineAlgorithm.Internal; -using PolylineAlgorithm.Internal.Logging; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; +using PolylineAlgorithm.Internal.Diagnostics; using System; -using System.Collections; -using System.Collections.Generic; +using System.Buffers; using System.Diagnostics; +using System.Runtime.CompilerServices; /// /// Provides functionality to encode a collection of geographic coordinates into an encoded polyline string. @@ -23,6 +23,7 @@ namespace PolylineAlgorithm.Abstraction; /// This abstract class serves as a base for specific polyline encoders, allowing customization of the encoding process. /// public abstract class AbstractPolylineEncoder : IPolylineEncoder { + private readonly ILogger> _logger; /// /// Initializes a new instance of the class with default encoding options. /// @@ -37,7 +38,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>(); } /// @@ -53,7 +61,6 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// /// /// An instance of representing the encoded coordinates. - /// Returns if the input collection is empty or null. /// /// /// Thrown when is . @@ -61,129 +68,86 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// /// Thrown when is an empty enumeration. /// - public TPolyline Encode(IEnumerable coordinates) { - var logger = Options - .LoggerFactory - .CreateLogger>(); - - logger - .LogOperationStartedInfo(nameof(Encode)); + /// + /// + [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) { + const string OperationName = nameof(Encode); - Debug.Assert(coordinates is not null, "Coordinates cannot be null."); + _logger + .LogOperationStartedDebug(OperationName); - ValidateNullCoordinates(coordinates, logger); + Debug.Assert(coordinates.Length >= 0, "Count must be non-negative."); - int count = GetCount(coordinates); + ValidateEmptyCoordinates(ref coordinates, _logger); - Debug.Assert(count >= 0, "Count must be non-negative."); - - ValidateEmptyCoordinates(logger, count); - - 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) - ); + try { + for (var i = 0; i < coordinates.Length; i++) { - ValidateBuffer(logger, variance, position, buffer); + delta + .Next( + PolylineEncoding.Normalize(GetLatitude(coordinates[i]), Options.Precision), + PolylineEncoding.Normalize(GetLongitude(coordinates[i]), Options.Precision) + ); - 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)); + 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); - throw new InvalidOperationException(ExceptionMessageResource.CouldNotWriteEncodedValueToTheBuffer); - } + ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); - consumed++; + } + + consumed++; + } + } finally { + if (temp is not null) { + ArrayPool.Shared.Return(temp); + } } - logger - .LogOperationFinishedInfo(nameof(Encode)); + _logger + .LogOperationFinishedDebug(OperationName); return CreatePolyline(buffer[..position].ToString().AsMemory()); - static int GetCount(IEnumerable coordinates) => coordinates switch { - ICollection collection => collection.Count, - _ => coordinates.Count(), - }; - - static int GetRequiredLength(CoordinateVariance variance) => - PolylineEncoding.GetCharCount(variance.Latitude) + PolylineEncoding.GetCharCount(variance.Longitude); - - 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."); - - return length - position; - } - - 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.ThrwoArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); } } } @@ -195,7 +159,7 @@ static void ValidateBuffer(ILogger /// An instance of representing the encoded polyline. /// - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract TPolyline CreatePolyline(ReadOnlyMemory polyline); /// @@ -205,7 +169,7 @@ static void ValidateBuffer(ILogger /// The longitude value as a . /// - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract double GetLongitude(TCoordinate current); /// @@ -215,7 +179,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..3937353c 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs @@ -10,7 +10,7 @@ namespace PolylineAlgorithm.Abstraction; /// /// Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. /// -public interface IPolylineDecoder { +public interface IPolylineDecoder { /// /// Decodes the specified encoded polyline into a sequence of geographic coordinates. /// diff --git a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs index 8febc1bf..7fc93905 100644 --- a/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs @@ -4,9 +4,6 @@ // namespace PolylineAlgorithm.Abstraction; - -using System.Collections.Generic; - /// /// Defines a contract for encoding a sequence of geographic coordinates into an encoded polyline string. /// @@ -20,5 +17,5 @@ public interface IPolylineEncoder { /// /// A containing the encoded polyline string that represents the input coordinates. /// - TPolyline Encode(IEnumerable coordinates); + TPolyline Encode(ReadOnlySpan coordinates); } diff --git a/src/PolylineAlgorithm/Coordinate.cs b/src/PolylineAlgorithm/Coordinate.cs index 43156f29..62204e1f 100644 --- a/src/PolylineAlgorithm/Coordinate.cs +++ b/src/PolylineAlgorithm/Coordinate.cs @@ -5,10 +5,11 @@ namespace PolylineAlgorithm; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; using System; using System.Diagnostics; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -18,13 +19,18 @@ namespace PolylineAlgorithm; /// 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. +/// +/// Note: The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by . /// [DebuggerDisplay("{ToString()}")] -[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] +[StructLayout(LayoutKind.Auto)] public readonly struct Coordinate : IEquatable { /// /// Initializes a new instance of the struct with default values (0) for and . /// + /// + /// The default value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by . + /// public Coordinate() { Latitude = default; Longitude = default; @@ -44,13 +50,7 @@ public Coordinate() { /// 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)); - } + Validator.Validate(latitude, longitude); Latitude = latitude; Longitude = longitude; @@ -69,18 +69,37 @@ public Coordinate(double latitude, double longitude) { /// /// Determines whether this coordinate is the default value (both and are 0). /// + /// + /// The value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by this method. + /// /// /// if both latitude and longitude are 0; otherwise, . /// public bool IsDefault() - => Latitude == default - && Longitude == default; - - #region Overrides + => Latitude.Equals(default) + && Longitude.Equals(default); /// public override bool Equals(object? obj) => obj is Coordinate coordinate && Equals(coordinate); + /// + public bool Equals(Coordinate other) { + return Latitude.Equals(other.Latitude) && + Longitude.Equals(other.Longitude); + } + + /// + /// Determines whether two instances are approximately equal within a specified tolerance. + /// + /// The other coordinate to compare. + /// The maximum allowed difference for latitude and longitude. + /// + /// if both latitude and longitude differ by less than ; otherwise, . + /// + public bool Equals(Coordinate other, double tolerance) + => Math.Abs(Latitude - other.Latitude) < tolerance + && Math.Abs(Longitude - other.Longitude) < tolerance; + /// public override int GetHashCode() => HashCode.Combine(Latitude, Longitude); @@ -94,20 +113,6 @@ 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. /// @@ -128,5 +133,81 @@ public bool Equals(Coordinate other) { /// public static bool operator !=(Coordinate left, Coordinate right) => !(left == right); - #endregion + /// + /// Provides static methods for validating latitude and longitude values used in . + /// + /// + /// The Validator class ensures that latitude and longitude values are within their valid ranges: + /// + /// + /// Latitude must be between -90 and 90 degrees. + /// + /// + /// Longitude must be between -180 and 180 degrees. + /// + /// + /// If a value is out of range or not finite, an exception is thrown via . + /// + public static class Validator { + /// + /// Validates that the specified latitude is within the valid range of -90 to 90 degrees. + /// + /// The latitude value to validate. + /// + /// Thrown if is less than -90, greater than 90, or not a finite number. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateLatitude(double latitude) { + const double min = -90; + const double max = 90; + + ValidateValue(latitude, min, max, nameof(latitude)); + } + + /// + /// Validates that the specified longitude is within the valid range of -180 to 180 degrees. + /// + /// The longitude value to validate. + /// + /// Thrown if is less than -180, greater than 180, or not a finite number. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateLongitude(double longitude) { + const double min = -180; + const double max = 180; + + ValidateValue(longitude, min, max, nameof(longitude)); + } + + /// + /// Validates that the specified value is finite and within the specified range. + /// + /// The value to validate. + /// The minimum allowed value (inclusive). + /// The maximum allowed value (inclusive). + /// The name of the parameter being validated. + /// + /// Thrown if is not finite, less than , or greater than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateValue(double value, double min, double max, string paramName) { + if (!double.IsFinite(value) || value < min || value > max) { + ExceptionGuard.ThrowCoordinateValueOutOfRange(value, min, max, paramName); + } + } + + /// + /// Validates both latitude and longitude values for a coordinate. + /// + /// The latitude value to validate. + /// The longitude value to validate. + /// + /// Thrown if either or is out of range or not finite. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Validate(double latitude, double longitude) { + ValidateLatitude(latitude); + ValidateLongitude(longitude); + } + } } 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/InvalidPolylineException.cs b/src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs similarity index 50% rename from src/PolylineAlgorithm/InvalidPolylineException.cs rename to src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs index 36250da6..79a84bfa 100644 --- a/src/PolylineAlgorithm/InvalidPolylineException.cs +++ b/src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs @@ -3,12 +3,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. // -namespace PolylineAlgorithm; +namespace PolylineAlgorithm.Diagnostics; -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/Extensions/PolylineDecoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs index 403b94ea..2557e7a7 100644 --- a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs +++ b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs @@ -7,6 +7,7 @@ namespace PolylineAlgorithm.Extensions; using PolylineAlgorithm; using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Internal.Diagnostics; using System; using System.Collections.Generic; @@ -31,7 +32,7 @@ public static class PolylineDecoderExtensions { /// public static IEnumerable Decode(this IPolylineDecoder decoder, string polyline) { if (decoder is null) { - throw new ArgumentNullException(nameof(decoder)); + ExceptionGuard.ThrowArgumentNull(nameof(decoder)); } return decoder.Decode(Polyline.FromString(polyline)); @@ -54,7 +55,7 @@ public static IEnumerable Decode(this IPolylineDecoder 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.FromCharArray(polyline)); @@ -77,7 +78,7 @@ public static IEnumerable Decode(this IPolylineDecoder 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.FromMemory(polyline)); diff --git a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs index 14cecfad..113d3d09 100644 --- a/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs +++ b/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs @@ -1,62 +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.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 collection of instances into an encoded polyline. /// /// /// The instance used to perform the encoding operation. /// /// - /// The sequence of objects to encode. + /// The sequence of objects to encode. /// /// - /// A representing the encoded polyline string for the provided coordinates. + /// A representing the encoded polyline string for the provided coordinates. /// /// /// Thrown when 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 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 representing the encoded polyline string for the provided coordinates. /// /// /// Thrown when 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..830d90ef --- /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 is used to compute and store the change in coordinate values as integer deltas. +/// +[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} }} }}"; +} 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..28f32b2a 100644 --- a/src/PolylineAlgorithm/Internal/Defaults.cs +++ b/src/PolylineAlgorithm/Internal/Defaults.cs @@ -26,10 +26,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. @@ -69,20 +65,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 +83,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,18 +95,18 @@ 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 vakues 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; } } } diff --git a/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs b/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs new file mode 100644 index 00000000..d8c38362 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs @@ -0,0 +1,213 @@ +namespace PolylineAlgorithm.Internal.Diagnostics; + +using PolylineAlgorithm.Diagnostics; +using PolylineAlgorithm.Properties; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +#if NETSTANDARD2_1 || NET5_0 +using System.Runtime.CompilerServices; +using static PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard; +#endif + +#if NET6_0_OR_GREATER +using System.Diagnostics; +#endif + +#if NET8_0_OR_GREATER +using System.Text; +#endif + +internal static class ExceptionGuard { +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowNotFiniteNumber(string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.GetArgumentValueMustBeFiniteNumber()); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowArgumentNull(string paramName) { + throw new ArgumentNullException(paramName); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowBufferOverflow(string message) { + throw new OverflowException(message); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowCoordinateValueOutOfRange(double value, double min, double max, string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.FormatCoordinateValueMustBeBetween(paramName, min, max)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void StackAllocLimitMustBeEqualOrGreaterThan(int minValue, string paramName) { + throw new ArgumentOutOfRangeException(paramName, ExceptionMessage.FormatStackAllocLimitMustBeEqualOrGreaterThan(minValue)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrwoArgumentCannotBeEmptyEnumerationMessage(string paramName) { + throw new ArgumentException(ExceptionMessage.GetArgumentCannotBeEmpty(), paramName); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + internal static void ThrowCouldNotWriteEncodedValueToBuffer() { + throw new InvalidOperationException(ExceptionMessage.GetCouldNotWriteEncodedValueToTheBuffer()); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength, string paramName) { + + throw new ArgumentException(ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength), paramName); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowInvalidPolylineLength(int length, int min) { + throw new InvalidPolylineException(ExceptionMessage.FormatInvalidPolylineLength(length, min)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowInvalidPolylineCharacter(char character, int position) { + throw new InvalidPolylineException(ExceptionMessage.FormatInvalidPolylineCharacter(character, position)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowPolylineBlockTooLong(int position) { + throw new InvalidPolylineException(ExceptionMessage.FormatPolylineBlockTooLong(position)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowInvalidPolylineFormat(long position) { + throw new InvalidPolylineException(ExceptionMessage.FormatMalformedPolyline(position)); + } + +#if NET6_0_OR_GREATER + [StackTraceHidden] +#else + [MethodImpl(MethodImplOptions.NoInlining)] +#endif + [DoesNotReturn] + internal static void ThrowInvalidPolylineBlockTerminator() { + throw new InvalidPolylineException(ExceptionMessage.GetInvalidPolylineBlockTerminator()); + } + + [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 CouldNotWriteEncodedValueToTheBufferMessage = ExceptionMessageResource.CouldNotWriteEncodedValueToTheBufferMessage; + private static readonly string InvalidPolylineBlockTerminatorMessage = ExceptionMessageResource.InvalidPolylineBlockTerminatorMessage; + + internal static string FormatStackAllocLimitMustBeEqualOrGreaterThan(int minValue) => + string.Format(CultureInfo.InvariantCulture, StackAllocLimitMustBeEqualOrGreaterThanFormat, minValue); + + internal static string FormatPolylineCannotBeShorterThan(int length, int minLength) => + string.Format(CultureInfo.InvariantCulture, PolylineCannotBeShorterThanFormat, length, minLength); + + internal static string FormatMalformedPolyline(long position) => + string.Format(CultureInfo.InvariantCulture, PolylineIsMalformedAtFormat, position); + + internal static string FormatCoordinateValueMustBeBetween(string name, double min, double max) => + string.Format(CultureInfo.InvariantCulture, CoordinateValueMustBeBetweenFormat, name, min, max); + + internal static string FormatPolylineBlockTooLong(int position) => + string.Format(CultureInfo.InvariantCulture, PolylineBlockTooLongFormat, position); + + internal static string FormatInvalidPolylineCharacter(char character, int position) => + string.Format(CultureInfo.InvariantCulture, InvalidPolylineCharacterFormat, character, position); + + internal static string FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength) => + string.Format(CultureInfo.InvariantCulture, DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat, destinationLength, polylineLength); + + internal static string FormatInvalidPolylineLength(int length, int min) => + string.Format(CultureInfo.InvariantCulture, InvalidPolylineLengthFormat, length, min); + + internal static string GetArgumentValueMustBeFiniteNumber() => + ArgumentCannotBeEmptyMessage; + + internal static string GetCouldNotWriteEncodedValueToTheBuffer() => + CouldNotWriteEncodedValueToTheBufferMessage; + + internal static string GetArgumentCannotBeEmpty() => + ArgumentCannotBeEmptyMessage; + internal 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..31b8db72 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/Diagnostics/LogDebugExtensions.cs @@ -0,0 +1,25 @@ +// +// 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; + +internal static partial class LogDebugExtensions { + private const LogLevel LOG_LEVEL = LogLevel.Debug; + 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 LogOperationStartedDebug(this ILogger logger, string operationName); + + [LoggerMessage(EVENT_ID_BASE + 2, LOG_LEVEL, "Operation {operationName} has failed.")] + internal static partial void LogOperationFailedDebug(this ILogger logger, string operationName); + + [LoggerMessage(EVENT_ID_BASE + 3, LOG_LEVEL, "Operation {operationName} has finished.")] + internal static partial void LogOperationFinishedDebug(this ILogger logger, string operationName); + + [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/Logging/LogWarningExtensions.cs b/src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs similarity index 81% rename from src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs rename to src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs index 4a58057f..28d7c933 100644 --- a/src/PolylineAlgorithm/Internal/Logging/LogWarningExtensions.cs +++ b/src/PolylineAlgorithm/Internal/Diagnostics/LogWarningExtensions.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. // -namespace PolylineAlgorithm.Internal.Logging; +namespace PolylineAlgorithm.Internal.Diagnostics; using Microsoft.Extensions.Logging; @@ -23,12 +23,15 @@ internal static partial class LogWarningExtensions { [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 + 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); [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); + + [LoggerMessage(EVENT_ID_BASE + 8, LOG_LEVEL, "Polyline is invalid or malformed.")] + internal static partial void LogInvalidPolylineFormatWarning(this ILogger logger, Exception ex); } 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/Pow10.cs b/src/PolylineAlgorithm/Internal/Pow10.cs new file mode 100644 index 00000000..55834bec --- /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); + } + } +} diff --git a/src/PolylineAlgorithm/Polyline.cs b/src/PolylineAlgorithm/Polyline.cs index d5e51295..9c29145d 100644 --- a/src/PolylineAlgorithm/Polyline.cs +++ b/src/PolylineAlgorithm/Polyline.cs @@ -5,9 +5,10 @@ namespace PolylineAlgorithm; -using PolylineAlgorithm.Properties; +using PolylineAlgorithm.Internal.Diagnostics; using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; /// @@ -20,13 +21,11 @@ namespace PolylineAlgorithm; [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; + Value = ReadOnlyMemory.Empty; } /// @@ -36,13 +35,13 @@ public Polyline() { /// A read-only memory region of characters representing an encoded polyline. /// private Polyline(ReadOnlyMemory value) { - _value = value; + Value = value; } /// /// Gets the underlying read-only sequence of characters representing the polyline. /// - internal readonly ReadOnlyMemory Value => _value; + internal readonly ReadOnlyMemory Value { get; } /// /// Gets a value indicating whether this is empty. @@ -50,9 +49,9 @@ private Polyline(ReadOnlyMemory value) { public readonly bool IsEmpty => Value.IsEmpty; /// - /// Gets the length of the polyline in characters. + /// Gets the length of the polyline in characters as an . /// - public readonly long Length => Value.Length; + public readonly int Length => Value.Length; /// /// Copies the characters of this polyline to the specified destination array. @@ -68,14 +67,14 @@ private Polyline(ReadOnlyMemory value) { /// public void CopyTo(char[] destination) { if (destination is null) { - throw new ArgumentNullException(nameof(destination)); + ExceptionGuard.ThrowArgumentNull(nameof(destination)); } if (destination.Length < Length) { - throw new ArgumentException(ExceptionMessageResource.DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthMessage, nameof(destination)); + ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destination.Length, Length, nameof(destination)); } - _value.CopyTo(destination); + Value.CopyTo(destination); } /// @@ -89,7 +88,7 @@ public override string ToString() { return string.Empty; } - return Value.ToString(); + return new string(Value.Span); } /// @@ -98,15 +97,29 @@ public override string ToString() { /// /// A string that includes the polyline value, truncated if necessary, for debugging purposes. /// + [ExcludeFromCodeCoverage( +#if NET5_0_OR_GREATER + Justification = "Only used for debugging." +#endif + )] private string ToDebugString() { if (IsEmpty) { return string.Empty; } - return Value.Length <= 32 ? $"\"{Value}\"" : $"\"{Value[..10]}...{Value[^10..]}\""; + var span = Value.Span; + if (span.Length <= 32) + return $"\"{new string(span)}\""; + return $"\"{new string(span.Slice(0, 10))}...{new string(span.Slice(span.Length - 10))}\""; } - /// + /// + /// Determines whether the current instance is equal to another instance. + /// + /// The other to compare with. + /// + /// if the instances are equal; otherwise, . + /// public bool Equals(Polyline other) { if ((IsEmpty != other.IsEmpty) || (Length != other.Length)) { return false; @@ -116,13 +129,22 @@ public bool Equals(Polyline other) { } /// - public override bool Equals(object obj) { + public override bool Equals(object? obj) { return obj is Polyline other && Equals(other); } /// public override int GetHashCode() { - return Value.GetHashCode(); + // Content-based hash code for value equality + var span = Value.Span; + if (span.IsEmpty) + return 0; + unchecked { + int hash = 17; + for (int i = 0; i < span.Length; i++) + hash = hash * 31 + span[i]; + return hash; + } } /// @@ -151,8 +173,6 @@ public override int GetHashCode() { return !(left == right); } - #region Factory methods - /// /// Creates a from a Unicode character array. /// @@ -167,7 +187,7 @@ public override int GetHashCode() { /// public static Polyline FromCharArray(char[] polyline) { if (polyline is null) { - throw new ArgumentNullException(nameof(polyline)); + ExceptionGuard.ThrowArgumentNull(nameof(polyline)); } return FromMemory(polyline.AsMemory()); @@ -187,7 +207,7 @@ public static Polyline FromCharArray(char[] polyline) { /// public static Polyline FromString(string polyline) { if (polyline is null) { - throw new ArgumentNullException(nameof(polyline)); + ExceptionGuard.ThrowArgumentNull(nameof(polyline)); } return FromMemory(polyline.AsMemory()); @@ -209,6 +229,4 @@ public static Polyline FromMemory(ReadOnlyMemory polyline) { return new Polyline(polyline); } - - #endregion } diff --git a/src/PolylineAlgorithm/PolylineAlgorithm.csproj b/src/PolylineAlgorithm/PolylineAlgorithm.csproj index 54b8cc74..599857af 100644 --- a/src/PolylineAlgorithm/PolylineAlgorithm.csproj +++ b/src/PolylineAlgorithm/PolylineAlgorithm.csproj @@ -1,19 +1,7 @@  - netstandard2.1 - 13.0 - enable - enable - true - en - - - - All - latest - true - true + netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0; @@ -22,13 +10,13 @@ true - + pdbonly true snupkg - + True @@ -44,7 +32,7 @@ - + True \ @@ -55,7 +43,8 @@ all runtime; build; native; contentfiles; analyzers - + + @@ -68,11 +57,11 @@ - - ExceptionMessageResource.resx - True - True - + + True + True + ExceptionMessageResource.resx + diff --git a/src/PolylineAlgorithm/PolylineDecoder.cs b/src/PolylineAlgorithm/PolylineDecoder.cs index 2e398d28..9543b842 100644 --- a/src/PolylineAlgorithm/PolylineDecoder.cs +++ b/src/PolylineAlgorithm/PolylineDecoder.cs @@ -23,7 +23,7 @@ protected override Coordinate CreateCoordinate(double latitude, double longitude } /// - protected override ReadOnlyMemory GetReadOnlyMemory(Polyline polyline) { + protected override ReadOnlyMemory GetReadOnlyMemory(in Polyline polyline) { return polyline.Value; } } \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineEncoding.cs b/src/PolylineAlgorithm/PolylineEncoding.cs index 75100f5e..045445da 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 rounding the result using the specified strategy. + /// + /// + /// 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..33bd8cb6 100644 --- a/src/PolylineAlgorithm/PolylineEncodingOptions.cs +++ b/src/PolylineAlgorithm/PolylineEncodingOptions.cs @@ -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..24fa7572 100644 --- a/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs +++ b/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs @@ -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. Must be greater than 11. + /// + /// The maximum buffer size to use for stack allocation. Must be greater than or equal to 1. + /// + /// + /// Returns 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 precision for encoding values. + /// + /// + /// The number of decimal places to use for encoding values. Default is 5. /// /// /// The current builder instance. /// - /// 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. + /// Returns 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..e913e00c 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,119 @@ internal ExceptionMessageResource() { } /// - /// Looks up a localized string similar to Argument cannot be CoordinateValueType.{0}.. + /// Looks up a localized string similar to Argument cannot be an 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 {0} is out of range. Expected range between {1} and {2}.. /// - internal static string ArgumentCannotBeEmptyEnumerationMessage { + internal static string ArgumentOutOfRangeFormat { get { - return ResourceManager.GetString("ArgumentCannotBeEmptyEnumerationMessage", resourceCulture); + return ResourceManager.GetString("ArgumentOutOfRangeFormat", 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 Value must be a finite number.. /// - internal static string ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat { + internal static string ArgumentValueMustBeFiniteNumber { get { - return ResourceManager.GetString("ArgumentOutOfRangeForSpecifiedCoordinateValueTypeMessageFormat", resourceCulture); + return ResourceManager.GetString("ArgumentValueMustBeFiniteNumber", resourceCulture); } } /// - /// Looks up a localized string similar to Value must be a finite number.. + /// Looks up a localized string similar to {0} must be between {1} and {2}.. /// - internal static string ArgumentValueMustBeFiniteNumber { + internal static string CoordinateValueMustBeBetweenValuesFormat { get { - return ResourceManager.GetString("ArgumentValueMustBeFiniteNumber", resourceCulture); + return ResourceManager.GetString("CoordinateValueMustBeBetweenValuesFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Buffer size must be greater than {0}.. + /// Looks up a localized string similar to Encoded values couldn't be written to the buffer.. /// - internal static string BufferSizeMustBeGreaterThanMessageFormat { + internal static string CouldNotWriteEncodedValueToTheBufferMessage { get { - return ResourceManager.GetString("BufferSizeMustBeGreaterThanMessageFormat", resourceCulture); + return ResourceManager.GetString("CouldNotWriteEncodedValueToTheBufferMessage", resourceCulture); } } /// - /// Looks up a localized string similar to {0} must be between {1} and {2}.. + /// Looks up a localized string similar to Destination array length {0} must be greater than the polyline length {1}.. + /// + internal static string DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat { + get { + return ResourceManager.GetString("DestinationArrayLengthMustBeEqualOrGreaterThanPolylineLengthFormat", resourceCulture); + } + } + + /// + /// 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 shorten 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..d5108c8d 100644 --- a/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx +++ b/src/PolylineAlgorithm/Properties/ExceptionMessageResource.resx @@ -117,34 +117,43 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Argument cannot be an empty enumeration. + + Argument cannot be an empty. - + Polyline is malformed at position {0}. - - Argument cannot be shorten than 2 characters. Actual length: {0}. + + Argument cannot be shorten than {1} characters. Value: {0}. Value must be a finite number. - - Value is out of range for the specified type {0}. + + Value {0} is out of range. Expected range between {1} and {2}. - - Argument cannot be CoordinateValueType.{0}. + + Stack alloc limit must be equal or greater than {0}. - - Buffer size must be greater than {0}. + + Destination array length {0} must be greater than the polyline length {1}. - - Destination array length must be greater than the polyline length. - - + {0} must be between {1} and {2}. - - Encoded values couldn't be written to the buffer. Please, report a bug on GitHub with reproducible sample. Thank you, Pete. + + Encoded values couldn't be written to the buffer. + + + 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}. + + + 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..48f8c35a --- /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.")] diff --git a/src/PolylineAlgorithm/README.md b/src/PolylineAlgorithm/README.md new file mode 100644 index 00000000..16794e2a --- /dev/null +++ b/src/PolylineAlgorithm/README.md @@ -0,0 +1,79 @@ +# 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 +- Immutable, strongly-typed data structures: `Coordinate`, `Polyline` +- Predefined encoder/decoder types for easy usage +- Extensible APIs for custom coordinate and polyline types +- Robust input validation and descriptive exceptions +- Configurable with `PolylineEncodingOptions` (buffer, logging, etc.) +- Thread-safe, stateless APIs +- 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 + +### Encode coordinates + +```csharp +using PolylineAlgorithm; + +var coordinates = new List +{ + new Coordinate(48.858370, 2.294481), + new Coordinate(51.500729, -0.124625) +}; + +var encoder = new PolylineEncoder(); +Polyline encoded = encoder.Encode(coordinates); + +Console.WriteLine(encoded.ToString()); // Print encoded polyline string +``` + +### Decode polyline + +```csharp +using PolylineAlgorithm; + +var decoder = new PolylineDecoder(); +Polyline polyline = Polyline.FromString("yseiHoc_MwacOjnwM"); +IEnumerable decoded = decoder.Decode(polyline); +``` + +## Advanced Usage + +- Custom coordinate/polyline types are supported via `AbstractPolylineEncoder` and `AbstractPolylineDecoder`. +- Additional configuration via `PolylineEncodingOptionsBuilder`. + +> 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 to the encoder 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/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/CoordinateTests.cs b/tests/PolylineAlgorithm.Tests/CoordinateTests.cs new file mode 100644 index 00000000..13eb08be --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/CoordinateTests.cs @@ -0,0 +1,1328 @@ +// +// 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.Properties; +using System; + +/// +/// Tests for . +/// +[TestClass] +[TestCategory(Category.Unit)] +public sealed class CoordinateTests { + /// + /// Tests that default constructor creates coordinate with zero latitude and longitude. + /// + [TestMethod] + + public void Default_Constructor_Creates_Coordinate_With_Zero_Values() { + // Arrange & Act + Coordinate coordinate = new(); + + // Assert + Assert.AreEqual(0.0, coordinate.Latitude); + Assert.AreEqual(0.0, coordinate.Longitude); + } + + /// + /// Tests that parameterized constructor creates coordinate with specified values. + /// + [TestMethod] + [DataRow(0.0, 0.0)] + [DataRow(90.0, 0.0)] + [DataRow(-90.0, 0.0)] + [DataRow(90.0, 180.0)] + [DataRow(-90.0, 180.0)] + [DataRow(90.0, -180.0)] + [DataRow(-90.0, -180.0)] + [DataRow(0.0, 180.0)] + [DataRow(0.0, -180.0)] + public void Constructor_With_Valid_Values_Creates_Instance_With_Specified_Values(double latitude, double longitude) { + // Act + Coordinate coordinate = new(latitude, longitude); + + // Assert + Assert.AreEqual(latitude, coordinate.Latitude); + Assert.AreEqual(longitude, coordinate.Longitude); + } + + /// + /// Tests that constructor throws ArgumentOutOfRangeException when latitude is greater than 90. + /// + [TestMethod] + [DataRow(90.0000000001, 0.0, "latitude")] + [DataRow(-90.0000000001, 0.0, "latitude")] + [DataRow(double.NaN, 0.0, "latitude")] + [DataRow(double.PositiveInfinity, 0.0, "latitude")] + [DataRow(double.NegativeInfinity, 0.0, "latitude")] + [DataRow(0.0, 180.0000000001, "longitude")] + [DataRow(0.0, -180.0000000001, "longitude")] + [DataRow(0.0, double.NaN, "longitude")] + [DataRow(0.0, double.PositiveInfinity, "longitude")] + [DataRow(0.0, double.NegativeInfinity, "longitude")] + public void Constructor_With_Invalid_Values_Throws_ArgumentOutOfRangeException(double latitude, double longitude, string paramName) { + // Act + ArgumentOutOfRangeException exception = + Assert.ThrowsExactly(() => new Coordinate(latitude, longitude)); + + // Assert + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that IsDefault returns true for default constructed coordinate. + /// + [TestMethod] + public void IsDefault_With_Default_Coordinate_Instance_Returns_True() { + // Arrange + Coordinate coordinate = default; + + // Act + bool result = coordinate.IsDefault(); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that IsDefault returns true for coordinate with zero values. + /// + [TestMethod] + + public void IsDefault_With_Zero_Values_Returns_True() { + // Arrange + Coordinate coordinate = new(0.0, 0.0); + + // Act + bool result = coordinate.IsDefault(); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that IsDefault returns false for coordinate with non-zero latitude. + /// + [TestMethod] + [DataRow(1.0, 0.0)] + [DataRow(0.0, 1.0)] + [DataRow(1.0, 1.0)] + public void IsDefault_With_Non_Zero_Values_Returns_False(double latitude, double longitude) { + // Arrange + Coordinate coordinate = new(latitude, longitude); + + // Act + bool result = coordinate.IsDefault(); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns true when object is same coordinate. + /// + [TestMethod] + + public void Equals_With_Identical_Coordinate_As_Object_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + object coordinate2 = new Coordinate(45.5, -122.5); + + // Act + bool result = coordinate1.Equals(coordinate2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals returns false when object is null. + /// + [TestMethod] + + public void Equals_With_Null_Object_Returns_False() { + // Arrange + Coordinate coordinate = new(45.5, -122.5); + + // Act + bool result = coordinate.Equals((object?)null); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns false when object is of different type. + /// + [TestMethod] + + public void Equals_With_Different_Type_Returns_False() { + // Arrange + Coordinate coordinate = new(45.5, -122.5); + object otherObject = "not a coordinate"; + + // Act + bool result = coordinate.Equals(otherObject); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns false when object is coordinate with different values. + /// + [TestMethod] + + public void Equals_With_Different_Coordinate_As_Object_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + object coordinate2 = new Coordinate(45.5, -122.6); + + // Act + bool result = coordinate1.Equals(coordinate2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns true when both coordinates have same values. + /// + [TestMethod] + + public void Equals_With_Identical_Coordinate_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + + // Act + bool result = coordinate1.Equals(coordinate2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals returns false when latitudes differ. + /// + [TestMethod] + [DataRow(45.6, -122.5)] + [DataRow(45.5, -122.6)] + [DataRow(46.5, -121.5)] + public void Equals_With_Different_Values_Returns_False(double latitude, double longitude) { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(latitude, longitude); + + // Act + bool result = coordinate1.Equals(coordinate2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns true for default coordinates. + /// + [TestMethod] + public void Equals_With_Default_Coordinates_Returns_True() { + // Arrange + Coordinate coordinate1 = new(); + Coordinate coordinate2 = new(); + + // Act + bool result = coordinate1.Equals(coordinate2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals with tolerance returns true when coordinates are identical. + /// + [TestMethod] + [DataRow(45.5, -122.5, 0.1)] + [DataRow(45.5, -122.5, 0.001)] + [DataRow(45.5, -122.5, 0.0000001)] + [DataRow(45.5009, -122.5009, 0.001)] + [DataRow(45.50099, -122.50099, 0.001)] + [DataRow(45.4991, -122.4991, 0.001)] + public void Equals_Within_Tolerance_Returns_True(double latitude, double longitude, double tolerance) { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(latitude, longitude); + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals with tolerance returns false when latitude difference exceeds tolerance. + /// + [TestMethod] + [DataRow(45.7, -122.5, 0.1)] + [DataRow(45.5, -122.7, 0.1)] + [DataRow(45.7, -122.7, 0.1)] + [DataRow(45.502, -122.5, 0.001)] + [DataRow(45.5, -122.502, 0.001)] + [DataRow(45.5, -122.5000002, 0.0000001)] + [DataRow(45.5000002, -122.5, 0.0000001)] + public void Equals_With_Exceeding_Tolerance_Returns_False(double latitude, double longitude, double tolerance) { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(latitude, longitude); + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that GetHashCode returns same value for identical coordinates. + /// + [TestMethod] + + public void GetHashCode_With_Identical_Coordinates_Returns_Identical_Hashcode() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreEqual(hash1, hash2); + } + + /// + /// Tests that GetHashCode returns different values for different coordinates. + /// + [TestMethod] + + public void GetHashCode_With_Different_Coordinates_Returns_Different_Values() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.6, -122.6); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreNotEqual(hash1, hash2); + } + + /// + /// Tests that GetHashCode returns consistent value for same coordinate. + /// + [TestMethod] + + public void GetHashCode_Returns_Identical_Value() { + // Arrange + Coordinate coordinate = new(45.5, -122.5); + + // Act + int hash1 = coordinate.GetHashCode(); + int hash2 = coordinate.GetHashCode(); + + // Assert + Assert.AreEqual(hash1, hash2); + } + + /// + /// Tests that GetHashCode returns same value for default coordinates. + /// + [TestMethod] + + public void GetHashCode_With_Default_Coordinates_Returns_Identical_Value() { + // Arrange + Coordinate coordinate1 = new(); + Coordinate coordinate2 = new(0.0, 0.0); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreEqual(hash1, hash2); + } + + /// + /// Tests that ToString returns expected format with positive coordinates. + /// + [TestMethod] + + public void ToString_With_Positive_Coordinates_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(45.5, 122.5); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.AreEqual("{ Latitude: 45.5, Longitude: 122.5 }", result); + } + + /// + /// Tests that ToString returns expected format with negative coordinates. + /// + [TestMethod] + + public void ToString_With_Negative_Coordinates_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(-45.5, -122.5); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.AreEqual("{ Latitude: -45.5, Longitude: -122.5 }", result); + } + + /// + /// Tests that ToString returns expected format for default coordinate. + /// + [TestMethod] + + public void ToString_With_Default_Coordinate_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.AreEqual("{ Latitude: 0, Longitude: 0 }", result); + } + + /// + /// Tests that ToString returns expected format with mixed sign coordinates. + /// + [TestMethod] + + public void ToString_With_Mixed_Sign_Coordinates_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(45.5, -122.5); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.AreEqual("{ Latitude: 45.5, Longitude: -122.5 }", result); + } + + /// + /// Tests that ToString uses invariant culture formatting. + /// + [TestMethod] + + public void ToString_Uses_Invariant_Culture_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(45.123456789, -122.987654321); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.IsTrue(result.Contains("45.123456789", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("-122.987654321", StringComparison.Ordinal)); + } + + /// + /// Tests that ToString returns expected format with boundary values. + /// + [TestMethod] + + public void ToString_With_Boundary_Values_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(90.0, 180.0); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.AreEqual("{ Latitude: 90, Longitude: 180 }", result); + } + + /// + /// Tests that ValidateValue accepts valid value within specified range. + /// + [TestMethod] + + public void Validate_Value_With_Valid_Value_Does_Not_Throw() { + // Arrange + double value = 50.0; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + Coordinate.Validator.ValidateValue(value, min, max, paramName); + } + + /// + /// Tests that ValidateValue accepts value at minimum boundary. + /// + [TestMethod] + + public void ValidateValue_With_Minimum_Boundary_Does_Not_Throw() { + // Arrange + double value = 0.0; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + Coordinate.Validator.ValidateValue(value, min, max, paramName); + } + + /// + /// Tests that ValidateValue accepts value at maximum boundary. + /// + [TestMethod] + + public void ValidateValue_With_Maximum_Boundary_Does_Not_Throw() { + // Arrange + double value = 100.0; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + Coordinate.Validator.ValidateValue(value, min, max, paramName); + } + + /// + /// Tests that ValidateValue throws ArgumentOutOfRangeException when value is below minimum. + /// + [TestMethod] + + public void ValidateValue_With_Value_Below_Minimum_Throws_ArgumentOutOfRangeException() { + // Arrange + double value = -0.1; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateValue(value, min, max, paramName)); + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that ValidateValue throws ArgumentOutOfRangeException when value exceeds maximum. + /// + [TestMethod] + + public void ValidateValue_With_Value_Above_Maximum_Throws_ArgumentOutOfRangeException() { + // Arrange + double value = 100.1; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateValue(value, min, max, paramName)); + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that ValidateValue throws ArgumentOutOfRangeException when value is NaN. + /// + [TestMethod] + + public void ValidateValue_With_NaN_Throws_ArgumentOutOfRangeException() { + // Arrange + double value = double.NaN; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateValue(value, min, max, paramName)); + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that ValidateValue throws ArgumentOutOfRangeException when value is positive infinity. + /// + [TestMethod] + + public void ValidateValue_With_Positive_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double value = double.PositiveInfinity; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateValue(value, min, max, paramName)); + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that ValidateValue throws ArgumentOutOfRangeException when value is negative infinity. + /// + [TestMethod] + + public void ValidateValue_With_Negative_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double value = double.NegativeInfinity; + double min = 0.0; + double max = 100.0; + string paramName = "testParam"; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateValue(value, min, max, paramName)); + Assert.AreEqual(paramName, exception.ParamName); + } + + /// + /// Tests that ValidateValue accepts negative value within negative range. + /// + [TestMethod] + + public void ValidateValue_With_Negative_Value_In_Negative_Range_Does_Not_Throw() { + // Arrange + double value = -50.0; + double min = -100.0; + double max = -10.0; + string paramName = "testParam"; + + // Act & Assert + Coordinate.Validator.ValidateValue(value, min, max, paramName); + } + + /// + /// Tests that Validate accepts valid latitude and longitude. + /// + [TestMethod] + + public void Validate_With_Valid_Latitude_And_Longitude_Does_Not_Throw() { + // Arrange + double latitude = 45.5; + double longitude = -122.5; + + // Act & Assert + Coordinate.Validator.Validate(latitude, longitude); + } + + /// + /// Tests that Validate accepts boundary values. + /// + [TestMethod] + + public void Validate_With_Boundary_Values_Does_Not_Throw() { + // Arrange + double latitude = 90.0; + double longitude = 180.0; + + // Act & Assert + Coordinate.Validator.Validate(latitude, longitude); + } + + /// + /// Tests that Validate accepts minimum boundary values. + /// + [TestMethod] + + public void Validate_With_Minimum_Boundary_Values_Does_Not_Throw() { + // Arrange + double latitude = -90.0; + double longitude = -180.0; + + // Act & Assert + Coordinate.Validator.Validate(latitude, longitude); + } + + /// + /// Tests that Validate throws ArgumentOutOfRangeException when latitude is invalid. + /// + [TestMethod] + + public void Validate_With_Invalid_Latitude_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = 91.0; + double validLongitude = 0.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.Validate(invalidLatitude, validLongitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that Validate throws ArgumentOutOfRangeException when longitude is invalid. + /// + [TestMethod] + + public void Validate_With_Invalid_Longitude_Throws_ArgumentOutOfRangeException() { + // Arrange + double validLatitude = 0.0; + double invalidLongitude = 181.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.Validate(validLatitude, invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that Validate throws ArgumentOutOfRangeException when latitude is NaN. + /// + [TestMethod] + + public void Validate_With_Latitude_NaN_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = double.NaN; + double validLongitude = 0.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.Validate(invalidLatitude, validLongitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that Validate throws ArgumentOutOfRangeException when longitude is NaN. + /// + [TestMethod] + + public void Validate_With_Longitude_NaN_Throws_ArgumentOutOfRangeException() { + // Arrange + double validLatitude = 0.0; + double invalidLongitude = double.NaN; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.Validate(validLatitude, invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude accepts valid latitude within range. + /// + [TestMethod] + + public void ValidateLatitude_With_Valid_Latitude_Does_Not_Throw() { + // Arrange + double validLatitude = 45.5; + + // Act & Assert + Coordinate.Validator.ValidateLatitude(validLatitude); + } + + /// + /// Tests that ValidateLatitude accepts latitude at minimum boundary. + /// + [TestMethod] + + public void ValidateLatitude_With_Minimum_Boundary_Does_Not_Throw() { + // Arrange + double validLatitude = -90.0; + + // Act & Assert + Coordinate.Validator.ValidateLatitude(validLatitude); + } + + /// + /// Tests that ValidateLatitude accepts latitude at maximum boundary. + /// + [TestMethod] + + public void ValidateLatitude_With_Maximum_Boundary_Does_Not_Throw() { + // Arrange + double validLatitude = 90.0; + + // Act & Assert + Coordinate.Validator.ValidateLatitude(validLatitude); + } + + /// + /// Tests that ValidateLatitude throws ArgumentOutOfRangeException when latitude exceeds maximum. + /// + [TestMethod] + + public void ValidateLatitude_With_Latitude_Greater_Than_90_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = 91.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude throws ArgumentOutOfRangeException when latitude is below minimum. + /// + [TestMethod] + + public void ValidateLatitude_With_Latitude_Less_Than_Negative_90_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = -91.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude throws ArgumentOutOfRangeException when latitude is NaN. + /// + [TestMethod] + + public void ValidateLatitude_With_NaN_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = double.NaN; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude throws ArgumentOutOfRangeException when latitude is positive infinity. + /// + [TestMethod] + + public void ValidateLatitude_With_Positive_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = double.PositiveInfinity; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude throws ArgumentOutOfRangeException when latitude is negative infinity. + /// + [TestMethod] + + public void ValidateLatitude_With_Negative_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = double.NegativeInfinity; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude accepts valid longitude within range. + /// + [TestMethod] + + public void ValidateLongitude_With_Valid_Longitude_Does_Not_Throw() { + // Arrange + double validLongitude = -122.5; + + // Act & Assert + Coordinate.Validator.ValidateLongitude(validLongitude); + } + + /// + /// Tests that ValidateLongitude accepts longitude at minimum boundary. + /// + [TestMethod] + + public void ValidateLongitude_With_Minimum_Boundary_Does_Not_Throw() { + // Arrange + double validLongitude = -180.0; + + // Act & Assert + Coordinate.Validator.ValidateLongitude(validLongitude); + } + + /// + /// Tests that ValidateLongitude accepts longitude at maximum boundary. + /// + [TestMethod] + + public void ValidateLongitude_With_Maximum_Boundary_Does_Not_Throw() { + // Arrange + double validLongitude = 180.0; + + // Act & Assert + Coordinate.Validator.ValidateLongitude(validLongitude); + } + + /// + /// Tests that ValidateLongitude throws ArgumentOutOfRangeException when longitude exceeds maximum. + /// + [TestMethod] + + public void ValidateLongitude_With_Longitude_Greater_Than_180_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = 181.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude throws ArgumentOutOfRangeException when longitude is below minimum. + /// + [TestMethod] + + public void ValidateLongitude_With_Longitude_Less_Than_Negative_180_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = -181.0; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude throws ArgumentOutOfRangeException when longitude is NaN. + /// + [TestMethod] + + public void ValidateLongitude_With_NaN_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = double.NaN; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude throws ArgumentOutOfRangeException when longitude is positive infinity. + /// + [TestMethod] + + public void ValidateLongitude_With_Positive_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = double.PositiveInfinity; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude throws ArgumentOutOfRangeException when longitude is negative infinity. + /// + [TestMethod] + + public void ValidateLongitude_With_Negative_Infinity_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = double.NegativeInfinity; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that Equals with zero tolerance returns false even for identical coordinates. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Zero_Tolerance_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + double tolerance = 0.0; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals with zero tolerance returns false for non-identical coordinates. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Zero_Tolerance_And_Different_Coordinates_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5000001, -122.5); + double tolerance = 0.0; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals with difference exactly equal to tolerance returns true. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Difference_Exactly_Equal_To_Tolerance_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.501, -122.5); + double tolerance = 0.001; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals with negative tolerance behaves like absolute tolerance. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Negative_Tolerance_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + double tolerance = -0.001; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals with large tolerance returns true for widely different coordinates. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Large_Tolerance_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(50.0, -120.0); + double tolerance = 10.0; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals with tolerance works correctly at extreme latitude boundaries. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Extreme_Boundary_Latitudes_Returns_Expected_Result() { + // Arrange + Coordinate coordinate1 = new(90.0, 0.0); + Coordinate coordinate2 = new(89.9991, 0.0); + double tolerance = 0.001; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals with tolerance works correctly at extreme longitude boundaries. + /// + [TestMethod] + + public void Equals_With_Tolerance_With_Extreme_Boundary_Longitudes_Returns_Expected_Result() { + // Arrange + Coordinate coordinate1 = new(0.0, 180.0); + Coordinate coordinate2 = new(0.0, 179.9991); + double tolerance = 0.001; + + // Act + bool result = coordinate1.Equals(coordinate2, tolerance); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that GetHashCode returns different values for coordinates with slight differences. + /// + [TestMethod] + + public void GetHashCode_With_Slightly_Different_Coordinates_Returns_Different_Values() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5000001, -122.5); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreNotEqual(hash1, hash2); + } + + /// + /// Tests that GetHashCode works with boundary latitude values. + /// + [TestMethod] + + public void GetHashCode_With_Boundary_Latitude_Values_Returns_Value() { + // Arrange + Coordinate coordinate1 = new(90.0, 0.0); + Coordinate coordinate2 = new(-90.0, 0.0); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreNotEqual(hash1, hash2); + } + + /// + /// Tests that GetHashCode works with boundary longitude values. + /// + [TestMethod] + + public void GetHashCode_With_Boundary_Longitude_Values_Returns_Value() { + // Arrange + Coordinate coordinate1 = new(0.0, 180.0); + Coordinate coordinate2 = new(0.0, -180.0); + + // Act + int hash1 = coordinate1.GetHashCode(); + int hash2 = coordinate2.GetHashCode(); + + // Assert + Assert.AreNotEqual(hash1, hash2); + } + + /// + /// Tests that ToString handles very small non-zero values correctly. + /// + [TestMethod] + + public void ToString_With_Very_Small_Values_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(0.0000001, -0.0000001); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.IsTrue(result.Contains("1E-07", StringComparison.Ordinal) || result.Contains("0.0000001", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("-1E-07", StringComparison.Ordinal) || result.Contains("-0.0000001", StringComparison.Ordinal)); + } + + /// + /// Tests that ToString handles high precision decimal values correctly. + /// + [TestMethod] + + public void ToString_With_High_Precision_Values_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(45.123456789012345, -122.987654321098765); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.IsTrue(result.Contains("45.123456789", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("-122.98765432", StringComparison.Ordinal)); + } + + /// + /// Tests that ToString formats negative zero as negative zero. + /// + [TestMethod] + + public void ToString_With_Negative_Zero_Returns_Expected_Format() { + // Arrange + Coordinate coordinate = new(-0.0, -0.0); + + // Act + string result = coordinate.ToString(); + + // Assert + Assert.IsTrue(result.Contains("Latitude: ") && result.Contains("Longitude: ")); + } + + /// + /// Tests that ValidateLatitude accepts zero value. + /// + [TestMethod] + + public void ValidateLatitude_With_Zero_Does_Not_Throw() { + // Arrange + double validLatitude = 0.0; + + // Act & Assert + Coordinate.Validator.ValidateLatitude(validLatitude); + } + + /// + /// Tests that ValidateLatitude throws with value just beyond maximum boundary. + /// + [TestMethod] + + public void ValidateLatitude_With_Value_Just_Beyond_Maximum_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = 90.000001; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLatitude throws with value just beyond minimum boundary. + /// + [TestMethod] + + public void ValidateLatitude_With_Value_Just_Beyond_Minimum_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLatitude = -90.000001; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLatitude(invalidLatitude)); + Assert.AreEqual("latitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude accepts zero value. + /// + [TestMethod] + + public void ValidateLongitude_With_Zero_Does_Not_Throw() { + // Arrange + double validLongitude = 0.0; + + // Act & Assert + Coordinate.Validator.ValidateLongitude(validLongitude); + } + + /// + /// Tests that ValidateLongitude throws with value just beyond maximum boundary. + /// + [TestMethod] + + public void ValidateLongitude_With_Value_Just_Beyond_Maximum_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = 180.000001; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests that ValidateLongitude throws with value just beyond minimum boundary. + /// + [TestMethod] + + public void ValidateLongitude_With_Value_Just_Beyond_Minimum_Throws_ArgumentOutOfRangeException() { + // Arrange + double invalidLongitude = -180.000001; + + // Act & Assert + ArgumentOutOfRangeException exception = Assert.ThrowsExactly(() => + Coordinate.Validator.ValidateLongitude(invalidLongitude)); + Assert.AreEqual("longitude", exception.ParamName); + } + + /// + /// Tests the equality (==) operator returns true for identical coordinates. + /// + [TestMethod] + + public void Equality_Operator_With_Identical_Coordinates_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + + // Act + bool result = coordinate1 == coordinate2; + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests the equality (==) operator returns false for different coordinates. + /// + [TestMethod] + + public void Equality_Operator_With_Different_Coordinates_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.6, -122.5); + + // Act + bool result = coordinate1 == coordinate2; + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests the inequality (!=) operator returns false for identical coordinates. + /// + [TestMethod] + + public void Inequality_Operator_With_Identical_Coordinates_Returns_False() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.5, -122.5); + + // Act + bool result = coordinate1 != coordinate2; + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests the inequality (!=) operator returns true for different coordinates. + /// + [TestMethod] + + public void Inequality_Operator_With_Different_Coordinates_Returns_True() { + // Arrange + Coordinate coordinate1 = new(45.5, -122.5); + Coordinate coordinate2 = new(45.6, -122.5); + + // Act + bool result = coordinate1 != coordinate2; + + // Assert + Assert.IsTrue(result); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs new file mode 100644 index 00000000..f45659a5 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineDecoderExtensionsTests.cs @@ -0,0 +1,163 @@ +// +// 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; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; +using PolylineAlgorithm.Tests.Properties; +using System; +using System.Collections.Generic; +using System.Linq; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineDecoderExtensionsTests +{ + private sealed class TestPolylineDecoder : IPolylineDecoder + { + public Polyline? LastPolyline { get; private set; } + private readonly IEnumerable _coordinatesToReturn; + + public TestPolylineDecoder(IEnumerable coordinatesToReturn) + { + _coordinatesToReturn = coordinatesToReturn; + } + + public IEnumerable Decode(Polyline polyline) + { + LastPolyline = polyline; + return _coordinatesToReturn; + } + } + + /// + /// Tests that Decode with string parameter throws ArgumentNullException when decoder is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_StringWithNullDecoder_ThrowsArgumentNullException() + { + // Arrange + IPolylineDecoder? decoder = null; + string polyline = "test"; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => decoder!.Decode(polyline)); + Assert.AreEqual("decoder", exception.ParamName); + } + + /// + /// Tests that Decode with string parameter calls decoder with polyline from string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_StringWithValidDecoder_CallsDecoderWithPolylineFromString() + { + // Arrange + string polylineString = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; + Coordinate[] expectedCoordinates = + [ + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95), + new Coordinate(43.252, -126.453) + ]; + var decoder = new TestPolylineDecoder(expectedCoordinates); + + // Act + IEnumerable result = decoder.Decode(polylineString); + + // Assert + Assert.IsNotNull(result); + Coordinate[] coordinates = result.ToArray(); + Assert.AreEqual(3, coordinates.Length); + Assert.AreSame(decoder.LastPolyline, Polyline.FromString(polylineString)); + } + + /// + /// Tests that Decode with char array parameter throws ArgumentNullException when decoder is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_CharArrayWithNullDecoder_ThrowsArgumentNullException() + { + // Arrange + IPolylineDecoder? decoder = null; + char[] polyline = ['t', 'e', 's', 't']; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => decoder!.Decode(polyline)); + Assert.AreEqual("decoder", exception.ParamName); + } + + /// + /// Tests that Decode with char array parameter calls decoder with polyline from char array. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_CharArrayWithValidDecoder_CallsDecoderWithPolylineFromCharArray() + { + // Arrange + char[] polylineChars = ['_', 'p', '~', 'i', 'F', '~', 'p', 's', '|', 'U']; + Coordinate[] expectedCoordinates = + [ + new Coordinate(38.5, -120.2) + ]; + var decoder = new TestPolylineDecoder(expectedCoordinates); + + // Act + IEnumerable result = decoder.Decode(polylineChars); + + // Assert + Assert.IsNotNull(result); + Coordinate[] coordinates = result.ToArray(); + Assert.AreEqual(1, coordinates.Length); + Assert.AreEqual(decoder.LastPolyline, Polyline.FromCharArray(polylineChars)); + } + + /// + /// Tests that Decode with ReadOnlyMemory parameter throws ArgumentNullException when decoder is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_ReadOnlyMemoryWithNullDecoder_ThrowsArgumentNullException() + { + // Arrange + IPolylineDecoder? decoder = null; + ReadOnlyMemory polyline = "test".AsMemory(); + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => decoder!.Decode(polyline)); + Assert.AreEqual("decoder", exception.ParamName); + } + + /// + /// Tests that Decode with ReadOnlyMemory parameter calls decoder with polyline from memory. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Decode_ReadOnlyMemoryWithValidDecoder_CallsDecoderWithPolylineFromMemory() + { + // Arrange + ReadOnlyMemory polylineMemory = "_p~iF~ps|U".AsMemory(); + Coordinate[] expectedCoordinates = + [ + new Coordinate(38.5, -120.2) + ]; + var decoder = new TestPolylineDecoder(expectedCoordinates); + + // Act + IEnumerable result = decoder.Decode(polylineMemory); + + // Assert + Assert.IsNotNull(result); + Coordinate[] coordinates = result.ToArray(); + Assert.AreEqual(1, coordinates.Length); + Assert.AreEqual(decoder.LastPolyline, Polyline.FromMemory(polylineMemory)); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs new file mode 100644 index 00000000..14931071 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs @@ -0,0 +1,170 @@ +// +// 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; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; +using PolylineAlgorithm.Tests.Properties; +using System; +using System.Collections.Generic; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncoderExtensionsTests +{ + /// + /// Tests that Encode with List parameter throws ArgumentNullException when encoder is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ListWithNullEncoder_ThrowsArgumentNullException() + { + // Arrange + IPolylineEncoder? encoder = null; + List coordinates = [new Coordinate(38.5, -120.2)]; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => encoder!.Encode(coordinates)); + Assert.AreEqual("encoder", exception.ParamName); + } + + /// + /// Tests that Encode with List parameter throws ArgumentNullException when coordinates is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ListWithNullCoordinates_ThrowsArgumentNullException() + { + // Arrange + PolylineEncoder encoder = new(); + List? coordinates = null; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => encoder.Encode(coordinates!)); + Assert.AreEqual("coordinates", exception.ParamName); + } + + /// + /// Tests that Encode with List parameter calls encoder with coordinates from list. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ListWithValidEncoder_CallsEncoderWithCoordinatesFromList() + { + // Arrange + List coordinates = + [ + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95), + new Coordinate(43.252, -126.453) + ]; + Polyline expectedPolyline = Polyline.FromString("_p~iF~ps|U_ulLnnqC_mqNvxq`@"); + PolylineEncoder encoder = new(); + + // Act + Polyline result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expectedPolyline, result); + } + + /// + /// Tests that Encode with List parameter handles empty list. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ListWithEmptyList_CallsEncoderWithEmptySpan() + { + // Arrange + List coordinates = []; + Polyline expectedPolyline = Polyline.FromString(""); + PolylineEncoder encoder = new(); + + // Act + Polyline result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expectedPolyline, result); + } + + /// + /// Tests that Encode with array parameter throws ArgumentNullException when encoder is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ArrayWithNullEncoder_ThrowsArgumentNullException() + { + // Arrange + IPolylineEncoder? encoder = null; + Coordinate[] coordinates = [new Coordinate(38.5, -120.2)]; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => encoder!.Encode(coordinates)); + Assert.AreEqual("encoder", exception.ParamName); + } + + /// + /// Tests that Encode with array parameter throws ArgumentNullException when coordinates is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ArrayWithNullCoordinates_ThrowsArgumentNullException() + { + // Arrange + PolylineEncoder encoder = new(); + Coordinate[]? coordinates = null; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => PolylineEncoderExtensions.Encode(encoder, coordinates!)); + Assert.AreEqual("coordinates", exception.ParamName); + } + + /// + /// Tests that Encode with array parameter calls encoder with coordinates from array. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ArrayWithValidEncoder_CallsEncoderWithCoordinatesFromArray() + { + // Arrange + Coordinate[] coordinates = + [ + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95), + new Coordinate(43.252, -126.453) + ]; + Polyline expectedPolyline = Polyline.FromString("_p~iF~ps|U_ulLnnqC_mqNvxq`@"); + PolylineEncoder encoder = new(); + + // Act + Polyline result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expectedPolyline, result); + } + + /// + /// Tests that Encode with array parameter handles empty array. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Encode_ArrayWithEmptyArray_CallsEncoderWithEmptySpan() + { + // Arrange + Coordinate[] coordinates = []; + Polyline expectedPolyline = Polyline.FromString(""); + PolylineEncoder encoder = new(); + + // Act + Polyline result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expectedPolyline, 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..5b05c0eb --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs @@ -0,0 +1,302 @@ +// +// 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 PolylineAlgorithm.Tests.Properties; + +/// +/// Tests for . +/// +[TestClass] +public sealed class CoordinateDeltaTests { + /// + /// Tests that default constructor initializes delta values to zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Constructor_Default_Initializes_Latitude_And_Longitude_To_Zero() { + // Act + CoordinateDelta delta = new CoordinateDelta(); + + // Assert + Assert.AreEqual(0, delta.Latitude); + Assert.AreEqual(0, delta.Longitude); + } + + /// + /// Tests that Next with positive values calculates correct delta from zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Positive_Values_Calculates_Delta_From_Zero() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(10, 20); + + // Assert + Assert.AreEqual(10, delta.Latitude); + Assert.AreEqual(20, delta.Longitude); + } + + /// + /// Tests that Next with negative values calculates correct delta from zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Negative_Values_Calculates_Delta_From_Zero() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(-50, -100); + + // Assert + Assert.AreEqual(-50, delta.Latitude); + Assert.AreEqual(-100, delta.Longitude); + } + + /// + /// Tests that Next with zero values keeps delta at zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Zero_Values_Keeps_Delta_At_Zero() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(0, 0); + + // Assert + Assert.AreEqual(0, delta.Latitude); + Assert.AreEqual(0, delta.Longitude); + } + + /// + /// Tests that Next called multiple times calculates delta from previous value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_Called_Multiple_Times_Calculates_Delta_From_Previous_Value() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(10, 20); + delta.Next(15, 30); + + // Assert + Assert.AreEqual(5, delta.Latitude); + Assert.AreEqual(10, delta.Longitude); + } + + /// + /// Tests that Next with decreasing values calculates negative delta. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Decreasing_Values_Calculates_Negative_Delta() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(100, 200); + delta.Next(50, 150); + + // Assert + Assert.AreEqual(-50, delta.Latitude); + Assert.AreEqual(-50, delta.Longitude); + } + + /// + /// Tests that Next with same values as previous calculates zero delta. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Same_Values_As_Previous_Calculates_Zero_Delta() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(42, 84); + delta.Next(42, 84); + + // Assert + Assert.AreEqual(0, delta.Latitude); + Assert.AreEqual(0, delta.Longitude); + } + + /// + /// Tests that Next with maximum integer values calculates correct delta. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Maximum_Integer_Values_Calculates_Correct_Delta() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(int.MaxValue, int.MaxValue); + + // Assert + Assert.AreEqual(int.MaxValue, delta.Latitude); + Assert.AreEqual(int.MaxValue, delta.Longitude); + } + + /// + /// Tests that Next with minimum integer values calculates correct delta. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Minimum_Integer_Values_Calculates_Correct_Delta() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(int.MinValue, int.MinValue); + + // Assert + Assert.AreEqual(int.MinValue, delta.Latitude); + Assert.AreEqual(int.MinValue, delta.Longitude); + } + + /// + /// Tests that Next with mixed positive and negative values calculates correct delta. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Next_With_Mixed_Positive_And_Negative_Values_Calculates_Correct_Delta() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // Act + delta.Next(-50, 100); + delta.Next(25, -75); + + // Assert + Assert.AreEqual(75, delta.Latitude); + Assert.AreEqual(-175, delta.Longitude); + } + + /// + /// Tests that ToString with default constructor returns formatted string with zeros. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_With_Default_Constructor_Returns_Formatted_String_With_Zeros() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + + // 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.IsTrue(result.Contains('0')); + } + + /// + /// Tests that ToString after Next returns formatted string with correct values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_After_Next_Returns_Formatted_String_With_Correct_Values() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + delta.Next(42, 84); + + // Act + string result = delta.ToString(); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("42", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("84", StringComparison.Ordinal)); + } + + /// + /// Tests that ToString after multiple Next calls returns formatted string with latest values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_After_Multiple_Next_Calls_Returns_Formatted_String_With_Latest_Values() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + 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)); + } + + /// + /// Tests that ToString with negative values returns formatted string with negative signs. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_With_Negative_Values_Returns_Formatted_String_With_Negative_Signs() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + delta.Next(-100, -200); + + // Act + string result = delta.ToString(); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("-100", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains("-200", StringComparison.Ordinal)); + } + + /// + /// Tests that ToString with maximum integer values returns formatted string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_With_Maximum_Integer_Values_Returns_Formatted_String() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + delta.Next(int.MaxValue, int.MaxValue); + + // Act + string result = delta.ToString(); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains(int.MaxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); + } + + /// + /// Tests that ToString with minimum integer values returns formatted string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_With_Minimum_Integer_Values_Returns_Formatted_String() { + // Arrange + CoordinateDelta delta = new CoordinateDelta(); + delta.Next(int.MinValue, int.MinValue); + + // Act + string result = delta.ToString(); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains(int.MinValue.ToString(System.Globalization.CultureInfo.InvariantCulture), 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..ad80846e --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs @@ -0,0 +1,701 @@ +// +// 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.Diagnostics; +using PolylineAlgorithm.Internal.Diagnostics; +using PolylineAlgorithm.Tests.Properties; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class ExceptionGuardTests { + /// + /// Tests that ThrowNotFiniteNumber throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowNotFiniteNumber_WithParamName_ThrowsArgumentOutOfRangeException() { + // Arrange + string paramName = "value"; + + // Act & Assert + try { + ExceptionGuard.ThrowNotFiniteNumber(paramName); + Assert.Fail("Expected ArgumentOutOfRangeException was not thrown."); + } catch (ArgumentOutOfRangeException ex) { + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowArgumentNull throws ArgumentNullException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowArgumentNull_WithParamName_ThrowsArgumentNullException() { + // Arrange + string paramName = "input"; + + // Act & Assert + try { + ExceptionGuard.ThrowArgumentNull(paramName); + Assert.Fail("Expected ArgumentNullException was not thrown."); + } catch (ArgumentNullException ex) { + Assert.AreEqual(paramName, ex.ParamName); + } + } + + /// + /// Tests that ThrowBufferOverflow throws OverflowException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowBufferOverflow_WithMessage_ThrowsOverflowException() { + // Arrange + string message = "Buffer overflow occurred."; + + // Act & Assert + try { + ExceptionGuard.ThrowBufferOverflow(message); + Assert.Fail("Expected OverflowException was not thrown."); + } catch (OverflowException ex) { + Assert.AreEqual(message, ex.Message); + } + } + + /// + /// Tests that ThrowCoordinateValueOutOfRange throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowCoordinateValueOutOfRange_WithParameters_ThrowsArgumentOutOfRangeException() { + // Arrange + double value = 100.0; + double min = -90.0; + double max = 90.0; + string paramName = "latitude"; + + // Act & Assert + try { + ExceptionGuard.ThrowCoordinateValueOutOfRange(value, min, max, paramName); + Assert.Fail("Expected ArgumentOutOfRangeException was not thrown."); + } catch (ArgumentOutOfRangeException ex) { + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that StackAllocLimitMustBeEqualOrGreaterThan throws ArgumentOutOfRangeException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void StackAllocLimitMustBeEqualOrGreaterThan_WithParameters_ThrowsArgumentOutOfRangeException() { + // Arrange + int minValue = 10; + string paramName = "stackAllocLimit"; + + // Act & Assert + try { + ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(minValue, paramName); + Assert.Fail("Expected ArgumentOutOfRangeException was not thrown."); + } catch (ArgumentOutOfRangeException ex) { + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrwoArgumentCannotBeEmptyEnumerationMessage throws ArgumentException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrwoArgumentCannotBeEmptyEnumerationMessage_WithParamName_ThrowsArgumentException() { + // Arrange + string paramName = "collection"; + + // Act & Assert + try { + ExceptionGuard.ThrwoArgumentCannotBeEmptyEnumerationMessage(paramName); + Assert.Fail("Expected ArgumentException was not thrown."); + } catch (ArgumentException ex) { + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowCouldNotWriteEncodedValueToBuffer throws InvalidOperationException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowCouldNotWriteEncodedValueToBuffer_ThrowsInvalidOperationException() { + // Act & Assert + try { + ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer(); + Assert.Fail("Expected InvalidOperationException was not thrown."); + } catch (InvalidOperationException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength throws ArgumentException with correct parameter name. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithParameters_ThrowsArgumentException() { + // Arrange + int destinationLength = 5; + int polylineLength = 10; + string paramName = "destination"; + + // Act & Assert + try { + ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength, paramName); + Assert.Fail("Expected ArgumentException was not thrown."); + } catch (ArgumentException ex) { + Assert.AreEqual(paramName, ex.ParamName); + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowInvalidPolylineLength throws InvalidPolylineException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowInvalidPolylineLength_WithParameters_ThrowsInvalidPolylineException() { + // Arrange + int length = 5; + int min = 10; + + // Act & Assert + try { + ExceptionGuard.ThrowInvalidPolylineLength(length, min); + Assert.Fail("Expected InvalidPolylineException was not thrown."); + } catch (InvalidPolylineException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowInvalidPolylineCharacter throws InvalidPolylineException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowInvalidPolylineCharacter_WithParameters_ThrowsInvalidPolylineException() { + // Arrange + char character = '!'; + int position = 15; + + // Act & Assert + try { + ExceptionGuard.ThrowInvalidPolylineCharacter(character, position); + Assert.Fail("Expected InvalidPolylineException was not thrown."); + } catch (InvalidPolylineException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowPolylineBlockTooLong throws InvalidPolylineException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowPolylineBlockTooLong_WithPosition_ThrowsInvalidPolylineException() { + // Arrange + int position = 42; + + // Act & Assert + try { + ExceptionGuard.ThrowPolylineBlockTooLong(position); + Assert.Fail("Expected InvalidPolylineException was not thrown."); + } catch (InvalidPolylineException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowInvalidPolylineFormat throws InvalidPolylineException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowInvalidPolylineFormat_WithPosition_ThrowsInvalidPolylineException() { + // Arrange + long position = 100L; + + // Act & Assert + try { + ExceptionGuard.ThrowInvalidPolylineFormat(position); + Assert.Fail("Expected InvalidPolylineException was not thrown."); + } catch (InvalidPolylineException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that ThrowInvalidPolylineBlockTerminator throws InvalidPolylineException with correct message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ThrowInvalidPolylineBlockTerminator_ThrowsInvalidPolylineException() { + // Act & Assert + try { + ExceptionGuard.ThrowInvalidPolylineBlockTerminator(); + Assert.Fail("Expected InvalidPolylineException was not thrown."); + } catch (InvalidPolylineException ex) { + Assert.IsNotNull(ex.Message); + } + } + + /// + /// Tests that FormatStackAllocLimitMustBeEqualOrGreaterThan returns formatted message with specified value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatStackAllocLimitMustBeEqualOrGreaterThan_WithMinValue_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatPolylineCannotBeShorterThan_WithLengthAndMinLength_ReturnsFormattedMessage() { + // Arrange + int length = 5; + int minLength = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineCannotBeShorterThan(length, minLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5')); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatMalformedPolyline returns formatted message with position. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatMalformedPolyline_WithPosition_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatMalformedPolyline_WithZeroPosition_ReturnsFormattedMessage() { + // Arrange + long position = 0L; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatMalformedPolyline(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0')); + } + + /// + /// Tests that FormatMalformedPolyline with negative position returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatMalformedPolyline_WithNegativePosition_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatMalformedPolyline_WithLargePosition_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatCoordinateValueMustBeBetween_WithParameters_ReturnsFormattedMessage() { + // Arrange + string name = "latitude"; + double min = -90.0; + 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] + [TestCategory(Category.Unit)] + public void FormatCoordinateValueMustBeBetween_WithPositiveValues_ReturnsFormattedMessage() { + // Arrange + string name = "longitude"; + double min = 0.0; + 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')); + Assert.IsTrue(result.Contains("180", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatCoordinateValueMustBeBetween with fractional values returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatCoordinateValueMustBeBetween_WithFractionalValues_ReturnsFormattedMessage() { + // Arrange + string name = "value"; + double min = 1.5; + 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] + [TestCategory(Category.Unit)] + public void FormatPolylineBlockTooLong_WithPosition_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatPolylineBlockTooLong_WithZeroPosition_ReturnsFormattedMessage() { + // Arrange + int position = 0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatPolylineBlockTooLong(position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0')); + } + + /// + /// Tests that FormatPolylineBlockTooLong with large position returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatPolylineBlockTooLong_WithLargePosition_ReturnsFormattedMessage() { + // Arrange + 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] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineCharacter_WithCharacterAndPosition_ReturnsFormattedMessage() { + // Arrange + char character = '!'; + int position = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('!')); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineCharacter with letter character returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineCharacter_WithLetterCharacter_ReturnsFormattedMessage() { + // Arrange + char character = 'Z'; + int position = 5; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('Z')); + Assert.IsTrue(result.Contains('5')); + } + + /// + /// Tests that FormatInvalidPolylineCharacter with special character returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineCharacter_WithSpecialCharacter_ReturnsFormattedMessage() { + // Arrange + char character = '@'; + int position = 0; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineCharacter(character, position); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('@')); + Assert.IsTrue(result.Contains('0')); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithLengths_ReturnsFormattedMessage() { + // Arrange + int destinationLength = 5; + int polylineLength = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5')); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with zero destination length returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithZeroDestinationLength_ReturnsFormattedMessage() { + // Arrange + int destinationLength = 0; + int polylineLength = 100; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(destinationLength, polylineLength); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0')); + Assert.IsTrue(result.Contains("100", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with large values returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithLargeValues_ReturnsFormattedMessage() { + // Arrange + int destinationLength = 1000; + 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] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineLength_WithLengthAndMin_ReturnsFormattedMessage() { + // Arrange + int length = 5; + int min = 10; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineLength(length, min); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('5')); + Assert.IsTrue(result.Contains("10", StringComparison.Ordinal)); + } + + /// + /// Tests that FormatInvalidPolylineLength with zero length returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineLength_WithZeroLength_ReturnsFormattedMessage() { + // Arrange + int length = 0; + int min = 1; + + // Act + string result = ExceptionGuard.ExceptionMessage.FormatInvalidPolylineLength(length, min); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains('0')); + Assert.IsTrue(result.Contains('1')); + } + + /// + /// Tests that FormatInvalidPolylineLength with negative values returns formatted message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FormatInvalidPolylineLength_WithNegativeValues_ReturnsFormattedMessage() { + // Arrange + int length = -5; + 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')); + } + + /// + /// Tests that GetArgumentValueMustBeFiniteNumber returns non-null message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetArgumentValueMustBeFiniteNumber_ReturnsNonNullMessage() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetArgumentValueMustBeFiniteNumber(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetCouldNotWriteEncodedValueToTheBuffer returns non-null message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetCouldNotWriteEncodedValueToTheBuffer_ReturnsNonNullMessage() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetCouldNotWriteEncodedValueToTheBuffer(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetArgumentCannotBeEmpty returns non-null message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetArgumentCannotBeEmpty_ReturnsNonNullMessage() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetArgumentCannotBeEmpty(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } + + /// + /// Tests that GetInvalidPolylineBlockTerminator returns non-null message. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetInvalidPolylineBlockTerminator_ReturnsNonNullMessage() { + // Act + string result = ExceptionGuard.ExceptionMessage.GetInvalidPolylineBlockTerminator(); + + // Assert + Assert.IsNotNull(result); + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs new file mode 100644 index 00000000..a51c1d70 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs @@ -0,0 +1,214 @@ +// +// 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 PolylineAlgorithm.Tests.Properties; +using System; +using System.Collections.Generic; +using System.Linq; + +/// +/// Tests for . +/// +[TestClass] +public sealed class LogDebugExtensionsTests +{ + private sealed class TestLogger : ILogger + { + public List<(LogLevel Level, EventId EventId, string Message, Exception? Exception)> Logs { get; } = new(); + + public IDisposable BeginScope(TState state) => 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() { } + } + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationStartedDebug_WithOperationName_LogsStartedMessage() + { + var logger = new TestLogger(); + string operationName = "TestOperation"; + + logger.LogOperationStartedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, $"Operation {operationName} has started."); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFailedDebug_WithOperationName_LogsFailedMessage() + { + var logger = new TestLogger(); + string operationName = "TestOperation"; + + logger.LogOperationFailedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, $"Operation {operationName} has failed."); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFinishedDebug_WithOperationName_LogsFinishedMessage() + { + var logger = new TestLogger(); + string operationName = "TestOperation"; + + logger.LogOperationFinishedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, $"Operation {operationName} has finished."); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogDecodedCoordinateDebug_WithCoordinatesAndPosition_LogsDecodedCoordinateMessage() + { + var logger = new TestLogger(); + double latitude = 38.5; + double longitude = -120.2; + int position = 42; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, $"Decoded coordinate: (Latitude: {latitude}, Longitude: {longitude}) at position {position}."); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationStartedDebug_WithNullOperationName_LogsMessage() + { + var logger = new TestLogger(); + string? operationName = null; + + logger.LogOperationStartedDebug(operationName!); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFailedDebug_WithNullOperationName_LogsMessage() + { + var logger = new TestLogger(); + string? operationName = null; + + logger.LogOperationFailedDebug(operationName!); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFinishedDebug_WithNullOperationName_LogsMessage() + { + var logger = new TestLogger(); + string? operationName = null; + + logger.LogOperationFinishedDebug(operationName!); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogDecodedCoordinateDebug_WithZeroCoordinates_LogsMessage() + { + var logger = new TestLogger(); + double latitude = 0.0; + double longitude = 0.0; + int position = 0; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Decoded coordinate"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogDecodedCoordinateDebug_WithNegativeCoordinates_LogsMessage() + { + var logger = new TestLogger(); + double latitude = -90.0; + double longitude = -180.0; + int position = 100; + + logger.LogDecodedCoordinateDebug(latitude, longitude, position); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, $"Latitude: {latitude}, Longitude: {longitude}"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationStartedDebug_WithEmptyOperationName_LogsMessage() + { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationStartedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFailedDebug_WithEmptyOperationName_LogsMessage() + { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationFailedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } + + [TestMethod] + [TestCategory(Category.Unit)] + public void LogOperationFinishedDebug_WithEmptyOperationName_LogsMessage() + { + var logger = new TestLogger(); + string operationName = string.Empty; + + logger.LogOperationFinishedDebug(operationName); + + var log = logger.Logs.Single(); + Assert.AreEqual(LogLevel.Debug, log.Level); + StringAssert.Contains(log.Message, "Operation"); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs new file mode 100644 index 00000000..ada0eafa --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs @@ -0,0 +1,102 @@ +// +// 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 PolylineAlgorithm.Tests.Properties; +using System; +using System.Collections.Generic; + +/// +/// Tests for . +/// +[TestClass] +public sealed class LogWarningExtensionsTests +{ + private sealed class TestLogger : ILogger + { + public List<(LogLevel Level, EventId EventId, string Message, Exception? Exception)> Logs { get; } = new(); + + public IDisposable BeginScope(TState state) => 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 class NullScope : IDisposable + { + public static NullScope Instance { get; } = new(); + public void Dispose() { } + } + } + + [TestMethod] + public void LogNullArgumentWarning_LogsExpectedMessage() + { + var logger = new TestLogger(); + logger.LogNullArgumentWarning("foo"); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument foo is null."))); + } + + [TestMethod] + public void LogEmptyArgumentWarning_LogsExpectedMessage() + { + var logger = new TestLogger(); + logger.LogEmptyArgumentWarning("bar"); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument bar is empty."))); + } + + [TestMethod] + public void LogInternalBufferOverflowWarning_LogsExpectedMessage() + { + 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."))); + } + + [TestMethod] + public void LogCannotWriteValueToBufferWarning_LogsExpectedMessage() + { + 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."))); + } + + [TestMethod] + public void LogPolylineCannotBeShorterThanWarning_LogsExpectedMessage() + { + 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."))); + } + + [TestMethod] + public void LogRequestedBufferSizeExceedsMaxBufferLengthWarning_LogsExpectedMessage() + { + 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."))); + } + + [TestMethod] + public void LogInvalidPolylineWarning_LogsExpectedMessage() + { + var logger = new TestLogger(); + logger.LogInvalidPolylineWarning(10); + Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is invalid or malformed at position 10."))); + } + + [TestMethod] + public void LogInvalidPolylineFormatWarning_LogsExpectedMessage() + { + 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.") && l.Exception == ex)); + } +} 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..b6f190f0 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs @@ -0,0 +1,175 @@ +// +// 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 PolylineAlgorithm.Tests.Properties; + +/// +/// Tests for . +/// +[TestClass] +public sealed class Pow10Tests { + /// + /// Tests that GetFactor with precision 0 returns 1. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Zero_Returns_One() { + // Act + uint result = Pow10.GetFactor(0); + + // Assert + Assert.AreEqual(1u, result); + } + + /// + /// Tests that GetFactor with precision 1 returns 10. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_One_Returns_Ten() { + // Act + uint result = Pow10.GetFactor(1); + + // Assert + Assert.AreEqual(10u, result); + } + + /// + /// Tests that GetFactor with precision 2 returns 100. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Two_Returns_One_Hundred() { + // Act + uint result = Pow10.GetFactor(2); + + // Assert + Assert.AreEqual(100u, result); + } + + /// + /// Tests that GetFactor with precision 3 returns 1000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Three_Returns_One_Thousand() { + // Act + uint result = Pow10.GetFactor(3); + + // Assert + Assert.AreEqual(1000u, result); + } + + /// + /// Tests that GetFactor with precision 4 returns 10000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Four_Returns_Ten_Thousand() { + // Act + uint result = Pow10.GetFactor(4); + + // Assert + Assert.AreEqual(10000u, result); + } + + /// + /// Tests that GetFactor with precision 5 returns 100000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Five_Returns_One_Hundred_Thousand() { + // Act + uint result = Pow10.GetFactor(5); + + // Assert + Assert.AreEqual(100000u, result); + } + + /// + /// Tests that GetFactor with precision 6 returns 1000000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Six_Returns_One_Million() { + // Act + uint result = Pow10.GetFactor(6); + + // Assert + Assert.AreEqual(1000000u, result); + } + + /// + /// Tests that GetFactor with precision 7 returns 10000000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Seven_Returns_Ten_Million() { + // Act + uint result = Pow10.GetFactor(7); + + // Assert + Assert.AreEqual(10000000u, result); + } + + /// + /// Tests that GetFactor with precision 8 returns 100000000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Eight_Returns_One_Hundred_Million() { + // Act + uint result = Pow10.GetFactor(8); + + // Assert + Assert.AreEqual(100000000u, result); + } + + /// + /// Tests that GetFactor with precision 9 returns 1000000000. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Nine_Returns_One_Billion() { + // Act + uint result = Pow10.GetFactor(9); + + // Assert + Assert.AreEqual(1000000000u, result); + } + + /// + /// Tests that GetFactor with precision causing overflow throws OverflowException. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Precision_Causing_Overflow_Throws_OverflowException() { + // Act & Assert + Assert.ThrowsExactly(() => Pow10.GetFactor(15)); + } + + /// + /// Tests that GetFactor with large precision causing overflow throws OverflowException. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Large_Precision_Causing_Overflow_Throws_OverflowException() { + // Act & Assert + Assert.ThrowsExactly(() => Pow10.GetFactor(20)); + } + + /// + /// Tests that GetFactor with maximum uint precision throws OverflowException. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetFactor_With_Maximum_Uint_Precision_Throws_OverflowException() { + // Act & Assert + Assert.ThrowsExactly(() => Pow10.GetFactor(uint.MaxValue)); + } +} 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/PolylineAlgorithm.Tests.csproj b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj index 974f5cd5..72fdded9 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,14 @@ 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/PolylineDecoderTests.cs b/tests/PolylineAlgorithm.Tests/PolylineDecoderTests.cs new file mode 100644 index 00000000..981e8d53 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineDecoderTests.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; + +using PolylineAlgorithm.Tests.Properties; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineDecoderTests { + /// + /// Tests that default constructor creates decoder with default options. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_DefaultConstructor_CreatesDecoderWithDefaultOptions() { + // Arrange & Act + PolylineDecoder decoder = new PolylineDecoder(); + + // Assert + Assert.IsNotNull(decoder); + Assert.IsNotNull(decoder.Options); + } + + /// + /// Tests that default constructor creates decoder with default precision. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_DefaultConstructor_CreatesDecoderWithDefaultPrecision() { + // Arrange & Act + PolylineDecoder decoder = new PolylineDecoder(); + + // Assert + Assert.AreEqual(5u, decoder.Options.Precision); + } + + /// + /// Tests that default constructor creates decoder with default stack alloc limit. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_DefaultConstructor_CreatesDecoderWithDefaultStackAllocLimit() { + // Arrange & Act + PolylineDecoder decoder = new PolylineDecoder(); + + // Assert + Assert.AreEqual(512, decoder.Options.StackAllocLimit); + } + + /// + /// Tests that parameterized constructor creates decoder with specified options. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_WithOptions_CreatesDecoderWithSpecifiedOptions() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + Precision = 6, + StackAllocLimit = 1024 + }; + + // Act + PolylineDecoder decoder = new PolylineDecoder(options); + + // Assert + Assert.IsNotNull(decoder); + Assert.AreSame(options, decoder.Options); + } + + /// + /// Tests that parameterized constructor preserves custom precision. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_WithCustomPrecision_PreservesCustomPrecision() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + Precision = 7 + }; + + // Act + PolylineDecoder decoder = new PolylineDecoder(options); + + // Assert + Assert.AreEqual(7u, decoder.Options.Precision); + } + + /// + /// Tests that parameterized constructor preserves custom stack alloc limit. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_WithCustomStackAllocLimit_PreservesCustomStackAllocLimit() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + StackAllocLimit = 2048 + }; + + // Act + PolylineDecoder decoder = new PolylineDecoder(options); + + // Assert + Assert.AreEqual(2048, decoder.Options.StackAllocLimit); + } + + /// + /// Tests that parameterized constructor throws ArgumentNullException when options is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_WithNullOptions_ThrowsArgumentNullException() { + // Arrange + PolylineEncodingOptions? options = null; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly( + () => new PolylineDecoder(options!)); + Assert.AreEqual("options", exception.ParamName); + } + + /// + /// Tests that decodes a valid polyline to expected coordinates. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_Decode_ReturnsExpectedCoordinates() + { + // Arrange + PolylineDecoder decoder = new PolylineDecoder(); + Polyline polyline = Polyline.FromString("_p~iF~ps|U_ulLnnqC"); + Coordinate[] expected = + [ + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95) + ]; + + // Act + var result = decoder.Decode(polyline).ToArray(); + + // Assert + CollectionAssert.AreEqual(expected, result); + } + + /// + /// Tests that decodes a valid polyline to expected coordinates. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineDecoder_Decode_WithCancellationToken_ReturnsExpectedCoordinates() + { + // Arrange + PolylineDecoder decoder = new PolylineDecoder(); + Polyline polyline = Polyline.FromString("_p~iF~ps|U_ulLnnqC"); + Coordinate[] expected = + [ + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95) + ]; + CancellationToken token = CancellationToken.None; + + // Act + var result = decoder.Decode(polyline, token).ToArray(); + + // Assert + CollectionAssert.AreEqual(expected, result); + } +} 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/PolylineEncoderTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncoderTests.cs new file mode 100644 index 00000000..4cd40c8e --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncoderTests.cs @@ -0,0 +1,234 @@ +// +// 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.Properties; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncoderTests { + /// + /// Tests that default constructor creates encoder with default options. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_DefaultConstructor_CreatesEncoderWithDefaultOptions() { + // Arrange & Act + PolylineEncoder encoder = new PolylineEncoder(); + + // Assert + Assert.IsNotNull(encoder); + Assert.IsNotNull(encoder.Options); + } + + /// + /// Tests that default constructor creates encoder with default precision. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_DefaultConstructor_CreatesEncoderWithDefaultPrecision() { + // Arrange & Act + PolylineEncoder encoder = new PolylineEncoder(); + + // Assert + Assert.AreEqual(5u, encoder.Options.Precision); + } + + /// + /// Tests that default constructor creates encoder with default stack alloc limit. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_DefaultConstructor_CreatesEncoderWithDefaultStackAllocLimit() { + // Arrange & Act + PolylineEncoder encoder = new PolylineEncoder(); + + // Assert + Assert.AreEqual(512, encoder.Options.StackAllocLimit); + } + + /// + /// Tests that parameterized constructor creates encoder with specified options. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_WithOptions_CreatesEncoderWithSpecifiedOptions() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + Precision = 6, + StackAllocLimit = 1024 + }; + + // Act + PolylineEncoder encoder = new PolylineEncoder(options); + + // Assert + Assert.IsNotNull(encoder); + Assert.AreSame(options, encoder.Options); + } + + /// + /// Tests that parameterized constructor preserves custom precision. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_WithCustomPrecision_PreservesCustomPrecision() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + Precision = 7 + }; + + // Act + PolylineEncoder encoder = new PolylineEncoder(options); + + // Assert + Assert.AreEqual(7u, encoder.Options.Precision); + } + + /// + /// Tests that parameterized constructor preserves custom stack alloc limit. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_WithCustomStackAllocLimit_PreservesCustomStackAllocLimit() { + // Arrange + PolylineEncodingOptions options = new PolylineEncodingOptions { + StackAllocLimit = 2048 + }; + + // Act + PolylineEncoder encoder = new PolylineEncoder(options); + + // Assert + Assert.AreEqual(2048, encoder.Options.StackAllocLimit); + } + + /// + /// Tests that parameterized constructor throws ArgumentNullException when options is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_WithNullOptions_ThrowsArgumentNullException() { + // Arrange + PolylineEncodingOptions? options = null; + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly( + () => new PolylineEncoder(options!)); + Assert.AreEqual("options", exception.ParamName); + } + + /// + /// Tests that Encode encodes a collection of coordinates into a polyline string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_EncodesCoordinatesToPolyline() { + // Arrange + var encoder = new PolylineEncoder(); + var coordinates = new[] + { + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95), + new Coordinate(43.252, -126.453) + }; + + // Act + Polyline polyline = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual("_p~iF~ps|U_ulLnnqC_mqNvxq`@", polyline.ToString()); + } + + /// + /// Tests that Encode throws ArgumentNullException when coordinates is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_NullCoordinates_ThrowsArgumentException() { + // Arrange + var encoder = new PolylineEncoder(); + Coordinate[]? coordinates = null; + + // Act & Assert + Assert.ThrowsExactly(() => encoder.Encode(coordinates)); + } + + /// + /// Tests that Encode throws ArgumentException when coordinates is empty. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_EmptyCoordinates_ThrowsArgumentException() { + // Arrange + var encoder = new PolylineEncoder(); + var coordinates = Array.Empty(); + + // Act & Assert + Assert.ThrowsExactly(() => encoder.Encode(coordinates)); + } + + /// + /// Tests that Encode encodes a single coordinate correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_SingleCoordinate_EncodesCorrectly() { + // Arrange + var encoder = new PolylineEncoder(); + var coordinates = new[] { new Coordinate(38.5, -120.2) }; + + // Act + Polyline polyline = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual("_p~iF~ps|U", polyline.ToString()); + } + + /// + /// Tests that Encode encodes two coordinates correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_TwoCoordinates_EncodesCorrectly() { + // Arrange + var encoder = new PolylineEncoder(); + var coordinates = new[] + { + new Coordinate(38.5, -120.2), + new Coordinate(40.7, -120.95) + }; + + // Act + Polyline polyline = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual("_p~iF~ps|U_ulLnnqC", polyline.ToString()); + } + + /// + /// Tests that Encode works with negative and zero coordinates. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void PolylineEncoder_Encode_NegativeAndZeroCoordinates_EncodesCorrectly() { + // Arrange + var encoder = new PolylineEncoder(); + var coordinates = new[] + { + new Coordinate(0, 0), + new Coordinate(-45.12345, 179.99999) + }; + + // Act + Polyline polyline = encoder.Encode(coordinates); + + // Assert + Assert.IsFalse(string.IsNullOrEmpty(polyline.ToString())); + } +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs new file mode 100644 index 00000000..a5c3cf39 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingOptionsBuilderTests.cs @@ -0,0 +1,454 @@ +// +// 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.Tests.Properties; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncodingOptionsBuilderTests { + /// + /// Tests that Create returns a new builder instance. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Create_ReturnsNewBuilder() { + // Act + PolylineEncodingOptionsBuilder result = PolylineEncodingOptionsBuilder.Create(); + + // Assert + Assert.IsNotNull(result); + } + + /// + /// Tests that Create returns different instances on multiple calls. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Create_MultipleInvocations_ReturnsDifferentInstances() { + // Act + PolylineEncodingOptionsBuilder first = PolylineEncodingOptionsBuilder.Create(); + PolylineEncodingOptionsBuilder second = PolylineEncodingOptionsBuilder.Create(); + + // Assert + Assert.AreNotSame(first, second); + } + + /// + /// Tests that Build returns options with default values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Build_WithDefaults_ReturnsOptionsWithDefaultValues() { + // 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] + [TestCategory(Category.Unit)] + public void Build_WithCustomPrecision_ReturnsOptionsWithCustomPrecision() { + // 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] + [TestCategory(Category.Unit)] + public void Build_WithCustomStackAllocLimit_ReturnsOptionsWithCustomStackAllocLimit() { + // 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] + [TestCategory(Category.Unit)] + public void Build_WithCustomLoggerFactory_ReturnsOptionsWithCustomLoggerFactory() { + // Arrange + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { }); + 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] + [TestCategory(Category.Unit)] + public void Build_WithAllCustomValues_ReturnsOptionsWithAllCustomValues() { + // Arrange + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { }); + 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] + [TestCategory(Category.Unit)] + public void Build_MultipleInvocations_ReturnsDifferentInstancesWithSameValues() { + // 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] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_ValidValue_SetsValueAndReturnsSelf() { + // 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] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_MinimumValue_SetsValue() { + // 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] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_Zero_ThrowsArgumentOutOfRangeException() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act & Assert + try { + builder.WithStackAllocLimit(0); + Assert.Fail("Expected ArgumentOutOfRangeException was not thrown."); + } catch (ArgumentOutOfRangeException ex) { + Assert.AreEqual("stackAllocLimit", ex.ParamName); + } + } + + /// + /// Tests that WithStackAllocLimit throws ArgumentOutOfRangeException for negative value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_NegativeValue_ThrowsArgumentOutOfRangeException() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act & Assert + try { + builder.WithStackAllocLimit(-10); + Assert.Fail("Expected ArgumentOutOfRangeException was not thrown."); + } catch (ArgumentOutOfRangeException ex) { + Assert.AreEqual("stackAllocLimit", ex.ParamName); + } + } + + /// + /// Tests that WithStackAllocLimit accepts large value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_LargeValue_SetsValue() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithStackAllocLimit(100000); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(100000, result.StackAllocLimit); + } + + /// + /// Tests that WithStackAllocLimit can be called multiple times. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithStackAllocLimit_MultipleCalls_LastValueWins() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithStackAllocLimit(100) + .WithStackAllocLimit(200) + .WithStackAllocLimit(300); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(300, result.StackAllocLimit); + } + + /// + /// Tests that WithPrecision sets the value and returns the builder. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithPrecision_ValidValue_SetsValueAndReturnsSelf() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + PolylineEncodingOptionsBuilder result = builder.WithPrecision(8); + + // Assert + Assert.AreSame(builder, result); + PolylineEncodingOptions options = builder.Build(); + Assert.AreEqual(8u, options.Precision); + } + + /// + /// Tests that WithPrecision accepts zero value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithPrecision_Zero_SetsValue() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithPrecision(0); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(0u, result.Precision); + } + + /// + /// Tests that WithPrecision accepts maximum uint value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithPrecision_MaxValue_SetsValue() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithPrecision(uint.MaxValue); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(uint.MaxValue, result.Precision); + } + + /// + /// Tests that WithPrecision can be called multiple times. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithPrecision_MultipleCalls_LastValueWins() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithPrecision(5) + .WithPrecision(7) + .WithPrecision(9); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreEqual(9u, result.Precision); + } + + /// + /// Tests that WithLoggerFactory sets the factory and returns the builder. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithLoggerFactory_ValidFactory_SetsValueAndReturnsSelf() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { }); + + // Act + PolylineEncodingOptionsBuilder result = builder.WithLoggerFactory(loggerFactory); + + // Assert + Assert.AreSame(builder, result); + PolylineEncodingOptions options = builder.Build(); + Assert.AreSame(loggerFactory, options.LoggerFactory); + + // Cleanup + loggerFactory.Dispose(); + } + + /// + /// Tests that WithLoggerFactory with null uses NullLoggerFactory. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithLoggerFactory_Null_UsesNullLoggerFactory() { + // Arrange + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create(); + + // Act + builder.WithLoggerFactory(null!); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.IsNotNull(result.LoggerFactory); + Assert.IsInstanceOfType(result.LoggerFactory); + } + + /// + /// Tests that WithLoggerFactory can replace a previously set factory. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithLoggerFactory_ReplacePreviousFactory_UpdatesValue() { + // Arrange + ILoggerFactory firstFactory = LoggerFactory.Create(builder => { }); + ILoggerFactory secondFactory = LoggerFactory.Create(builder => { }); + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithLoggerFactory(firstFactory); + + // Act + builder.WithLoggerFactory(secondFactory); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.AreSame(secondFactory, result.LoggerFactory); + + // Cleanup + firstFactory.Dispose(); + secondFactory.Dispose(); + } + + /// + /// Tests that WithLoggerFactory can be set to null after setting a factory. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void WithLoggerFactory_NullAfterFactory_UsesNullLoggerFactory() { + // Arrange + ILoggerFactory factory = LoggerFactory.Create(builder => { }); + PolylineEncodingOptionsBuilder builder = PolylineEncodingOptionsBuilder.Create() + .WithLoggerFactory(factory); + + // Act + builder.WithLoggerFactory(null!); + PolylineEncodingOptions result = builder.Build(); + + // Assert + Assert.IsInstanceOfType(result.LoggerFactory); + + // Cleanup + factory.Dispose(); + } + + /// + /// Tests that builder supports method chaining for all methods. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void MethodChaining_AllMethods_ReturnsBuilderForChaining() { + // Arrange + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { }); + + // 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); + + // Cleanup + loggerFactory.Dispose(); + } +} 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..75b4e44b --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs @@ -0,0 +1,1426 @@ +// +// 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.Diagnostics; +using PolylineAlgorithm.Tests.Properties; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncodingTests { + #region Normalize Tests + + /// + /// Tests that Normalize returns zero when value is zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_ZeroValue_ReturnsZero() { + // Arrange + double value = 0.0; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(0, result); + } + + /// + /// Tests that Normalize throws when value is NaN. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_NaNValue_ThrowsArgumentOutOfRangeException() { + // Arrange + double value = double.NaN; + uint precision = 5; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); + } + + /// + /// Tests that Normalize throws when value is positive infinity. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_PositiveInfinity_ThrowsArgumentOutOfRangeException() { + // Arrange + double value = double.PositiveInfinity; + uint precision = 5; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); + } + + /// + /// Tests that Normalize throws when value is negative infinity. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_NegativeInfinity_ThrowsArgumentOutOfRangeException() { + // Arrange + double value = double.NegativeInfinity; + uint precision = 5; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); + } + + /// + /// Tests that Normalize with zero precision returns truncated value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_ZeroPrecision_ReturnsTruncatedValue() { + // Arrange + double value = 37.78903; + uint precision = 0; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(37, result); + } + + /// + /// Tests that Normalize with zero precision truncates negative values correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_ZeroPrecisionNegative_ReturnsTruncatedValue() { + // Arrange + double value = -122.4123; + uint precision = 0; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(-122, result); + } + + /// + /// Tests that Normalize with default precision normalizes positive value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_DefaultPrecisionPositive_ReturnsNormalizedValue() { + // Arrange + double value = 37.78903; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(3778903, result); + } + + /// + /// Tests that Normalize with default precision normalizes negative value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_DefaultPrecisionNegative_ReturnsNormalizedValue() { + // Arrange + double value = -122.4123; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(-12241230, result); + } + + /// + /// Tests that Normalize with precision 1 works correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_Precision1_ReturnsNormalizedValue() { + // Arrange + double value = 37.78903; + uint precision = 1; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(377, result); + } + + /// + /// Tests that Normalize with precision 6 works correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_Precision6_ReturnsNormalizedValue() { + // Arrange + double value = 37.789034; + uint precision = 6; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(37789034, result); + } + + /// + /// Tests that Normalize truncates fractional parts. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_ValueWithFractionalPart_TruncatesFractionalPart() { + // Arrange + double value = 37.789999; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(3778999, result); + } + + /// + /// Tests that Normalize handles very small values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_VerySmallValue_ReturnsNormalizedValue() { + // Arrange + double value = 0.00001; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(1, result); + } + + /// + /// Tests that Normalize handles negative very small values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Normalize_NegativeVerySmallValue_ReturnsNormalizedValue() { + // Arrange + double value = -0.00001; + uint precision = 5; + + // Act + int result = PolylineEncoding.Normalize(value, precision); + + // Assert + Assert.AreEqual(-1, result); + } + + #endregion + + #region Denormalize Tests + + /// + /// Tests that Denormalize returns zero when value is zero. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_ZeroValue_ReturnsZero() { + // Arrange + int value = 0; + uint precision = 5; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(0.0, result); + } + + /// + /// Tests that Denormalize with zero precision returns same value as double. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_ZeroPrecision_ReturnsSameValue() { + // Arrange + int value = 37; + uint precision = 0; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(37.0, result); + } + + /// + /// Tests that Denormalize with zero precision handles negative values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_ZeroPrecisionNegative_ReturnsSameValue() { + // Arrange + int value = -122; + uint precision = 0; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(-122.0, result); + } + + /// + /// Tests that Denormalize with default precision denormalizes positive value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_DefaultPrecisionPositive_ReturnsDenormalizedValue() { + // Arrange + int value = 3778903; + uint precision = 5; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(37.78903, result, 0.0000001); + } + + /// + /// Tests that Denormalize with default precision denormalizes negative value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_DefaultPrecisionNegative_ReturnsDenormalizedValue() { + // Arrange + int value = -12241230; + uint precision = 5; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(-122.4123, result, 0.0000001); + } + + /// + /// Tests that Denormalize with precision 1 works correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_Precision1_ReturnsDenormalizedValue() { + // Arrange + int value = 377; + uint precision = 1; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(37.7, result, 0.0000001); + } + + /// + /// Tests that Denormalize with precision 6 works correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_Precision6_ReturnsDenormalizedValue() { + // Arrange + int value = 37789034; + uint precision = 6; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(37.789034, result, 0.0000001); + } + + /// + /// Tests that Denormalize handles very small values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_VerySmallValue_ReturnsDenormalizedValue() { + // Arrange + int value = 1; + uint precision = 5; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(0.00001, result, 0.0000001); + } + + /// + /// Tests that Denormalize handles negative very small values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Denormalize_NegativeVerySmallValue_ReturnsDenormalizedValue() { + // Arrange + int value = -1; + uint precision = 5; + + // Act + double result = PolylineEncoding.Denormalize(value, precision); + + // Assert + Assert.AreEqual(-0.00001, result, 0.0000001); + } + + #endregion + + #region TryReadValue Tests + + /// + /// Tests that TryReadValue returns false when position is at buffer length. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_PositionAtBufferLength_ReturnsFalse() { + // 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] + [TestCategory(Category.Unit)] + public void TryReadValue_PositionExceedsBufferLength_ReturnsFalse() { + // 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 positive single-character value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_PositiveSingleChar_ReadsValueAndReturnsTrue() { + // Arrange + ReadOnlyMemory buffer = "?".AsMemory(); + int delta = 0; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(0, delta); + Assert.AreEqual(1, position); + } + + /// + /// Tests that TryReadValue reads multi-character positive value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_PositiveMultiChar_ReadsValueAndReturnsTrue() { + // Arrange + Span buffer = stackalloc char[10]; + int writePosition = 0; + int expectedDelta = 3778903; + + // First write the value to get the correct encoding + PolylineEncoding.TryWriteValue(expectedDelta, buffer, ref writePosition); + ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); + + int delta = 0; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedDelta, delta); + Assert.AreEqual(writePosition, position); + } + + /// + /// Tests that TryReadValue reads negative value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_NegativeValue_ReadsValueAndReturnsTrue() { + // Arrange + Span buffer = stackalloc char[10]; + int writePosition = 0; + int expectedDelta = -12241230; + + // First write the value to get the correct encoding + PolylineEncoding.TryWriteValue(expectedDelta, buffer, ref writePosition); + ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); + + int delta = 0; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedDelta, delta); + Assert.AreEqual(writePosition, position); + } + + /// + /// Tests that TryReadValue accumulates delta correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_WithExistingDelta_AccumulatesDelta() { + // Arrange + Span buffer = stackalloc char[10]; + int writePosition = 0; + int valueDelta = 3778903; + + // First write the value to get the correct encoding + PolylineEncoding.TryWriteValue(valueDelta, buffer, ref writePosition); + ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); + + int delta = 100; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(3779003, delta); + } + + /// + /// Tests that TryReadValue reads multiple values from buffer. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_MultipleValues_ReadsSequentially() { + // Arrange + Span buffer = stackalloc char[20]; + int writePosition = 0; + int expectedDelta1 = 3778903; + int expectedDelta2 = -12241230; + + // Write both values + PolylineEncoding.TryWriteValue(expectedDelta1, buffer, ref writePosition); + PolylineEncoding.TryWriteValue(expectedDelta2, buffer, ref writePosition); + ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); + + int delta1 = 0; + int position = 0; + + // Act + bool result1 = PolylineEncoding.TryReadValue(ref delta1, readBuffer, ref position); + int delta2 = 0; + bool result2 = PolylineEncoding.TryReadValue(ref delta2, readBuffer, ref position); + + // Assert + Assert.IsTrue(result1); + Assert.AreEqual(expectedDelta1, delta1); + Assert.IsTrue(result2); + Assert.AreEqual(expectedDelta2, delta2); + Assert.AreEqual(writePosition, position); + } + + /// + /// Tests that TryReadValue returns false when buffer ends mid-value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_BufferEndsMidValue_ReturnsFalse() { + // Arrange + Span fullBuffer = stackalloc char[10]; + int writePosition = 0; + PolylineEncoding.TryWriteValue(3778903, fullBuffer, ref writePosition); + + // Create incomplete buffer (truncate last character) + ReadOnlyMemory buffer = new string(fullBuffer[..(writePosition - 1)]).AsMemory(); + int delta = 0; + int position = 0; + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(buffer.Length, position); + } + + /// + /// Tests that TryReadValue reads value from middle of buffer. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryReadValue_StartingFromMiddle_ReadsCorrectly() { + // Arrange + Span buffer = stackalloc char[20]; + int writePosition = 0; + int expectedDelta1 = 3778903; + int expectedDelta2 = -12241230; + + // Write both values + PolylineEncoding.TryWriteValue(expectedDelta1, buffer, ref writePosition); + int secondValuePosition = writePosition; + PolylineEncoding.TryWriteValue(expectedDelta2, buffer, ref writePosition); + ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); + + int delta = 0; + int position = secondValuePosition; // Start from second value + + // Act + bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(expectedDelta2, delta); + Assert.AreEqual(writePosition, position); + } + + #endregion + + #region TryWriteValue Tests + + /// + /// Tests that TryWriteValue returns false when buffer is too small. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_BufferTooSmall_ReturnsFalse() { + // Arrange + Span buffer = stackalloc char[2]; + int delta = 3778903; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, position); + } + + /// + /// Tests that TryWriteValue returns false when remaining buffer is too small. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_RemainingBufferTooSmall_ReturnsFalse() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = 3778903; + int position = 8; // Only 2 chars remaining, need 5 + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(8, position); + } + + /// + /// Tests that TryWriteValue writes zero correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_ZeroValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = 0; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('?', buffer[0]); + } + + /// + /// Tests that TryWriteValue writes positive value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_PositiveValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = 3778903; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(position > 0); + + // Verify by reading back + ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); + int readDelta = 0; + int readPosition = 0; + bool readResult = PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); + + Assert.IsTrue(readResult); + Assert.AreEqual(delta, readDelta); + } + + /// + /// Tests that TryWriteValue writes negative value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_NegativeValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = -12241230; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(position > 0); + + // Verify by reading back + ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); + int readDelta = 0; + int readPosition = 0; + bool readResult = PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); + + Assert.IsTrue(readResult); + Assert.AreEqual(delta, readDelta); + } + + /// + /// Tests that TryWriteValue writes multiple values sequentially. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_MultipleValues_WritesSequentially() { + // Arrange + Span buffer = stackalloc char[20]; + int position = 0; + int delta1 = 3778903; + int delta2 = -12241230; + + // Act + bool result1 = PolylineEncoding.TryWriteValue(delta1, buffer, ref position); + int midPosition = position; + bool result2 = PolylineEncoding.TryWriteValue(delta2, buffer, ref position); + + // Assert + Assert.IsTrue(result1); + Assert.IsTrue(result2); + Assert.IsTrue(position > midPosition); + + // Verify by reading back both values + ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); + int readDelta1 = 0; + int readPosition = 0; + PolylineEncoding.TryReadValue(ref readDelta1, readBuffer, ref readPosition); + int readDelta2 = 0; + PolylineEncoding.TryReadValue(ref readDelta2, readBuffer, ref readPosition); + + Assert.AreEqual(delta1, readDelta1); + Assert.AreEqual(delta2, readDelta2); + } + + /// + /// Tests that TryWriteValue writes small positive value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_SmallPositiveValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = 1; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + + // Verify by reading back + ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); + int readDelta = 0; + int readPosition = 0; + PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); + Assert.AreEqual(delta, readDelta); + } + + /// + /// Tests that TryWriteValue writes small negative value correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_SmallNegativeValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = -1; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(1, position); + + // Verify by reading back + ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); + int readDelta = 0; + int readPosition = 0; + PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); + Assert.AreEqual(delta, readDelta); + } + + /// + /// Tests that TryWriteValue starts writing at specified position. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_NonZeroStartPosition_WritesAtCorrectPosition() { + // Arrange + Span buffer = stackalloc char[10]; + buffer[0] = 'X'; + buffer[1] = 'Y'; + int delta = 0; + int position = 2; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(3, position); + Assert.AreEqual('X', buffer[0]); + Assert.AreEqual('Y', buffer[1]); + Assert.AreEqual('?', buffer[2]); + } + + /// + /// Tests that TryWriteValue handles large positive values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_LargePositiveValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = int.MaxValue / 2; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(position > 0); + } + + /// + /// Tests that TryWriteValue handles large negative values. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void TryWriteValue_LargeNegativeValue_WritesCorrectly() { + // Arrange + Span buffer = stackalloc char[10]; + int delta = int.MinValue / 2; + int position = 0; + + // Act + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(position > 0); + } + + #endregion + + #region GetRequiredBufferSize Tests + + /// + /// Tests that GetRequiredBufferSize returns 1 for zero value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_ZeroValue_ReturnsOne() { + // Arrange + int delta = 0; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(1, size); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for small positive value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_SmallPositiveValue_ReturnsOne() { + // Arrange + int delta = 1; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(1, size); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for small negative value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_SmallNegativeValue_ReturnsOne() { + // Arrange + int delta = -1; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(1, size); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for large positive value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_LargePositiveValue_ReturnsCorrectSize() { + // Arrange + int delta = 3778903; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(5, size); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for large negative value. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_LargeNegativeValue_ReturnsCorrectSize() { + // Arrange + int delta = -12241230; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(5, size); + } + + /// + /// Tests that GetRequiredBufferSize handles maximum positive integer. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_MaxInt_ReturnsCorrectSize() { + // Arrange + int delta = int.MaxValue; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.IsTrue(size > 0); + Assert.IsTrue(size <= 7); // Maximum size for int32 + } + + /// + /// Tests that GetRequiredBufferSize handles minimum negative integer. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_MinInt_ReturnsCorrectSize() { + // Arrange + int delta = int.MinValue; + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.IsTrue(size > 0); + Assert.IsTrue(size <= 7); // Maximum size for int32 + } + + /// + /// Tests that GetRequiredBufferSize returns consistent size with TryWriteValue. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_ConsistentWithTryWriteValue_MatchesActualSize() { + // Arrange + 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 GetRequiredBufferSize with undersized buffer causes TryWriteValue to fail. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_UndersizedBuffer_CausesTryWriteValueToFail() { + // Arrange + 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); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for boundary value 15. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_BoundaryValue15_ReturnsOne() { + // Arrange + int delta = 15; // 15 << 1 = 30, which is less than 32 (Space) + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(1, size); + } + + /// + /// Tests that GetRequiredBufferSize returns correct size for boundary value 16. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetRequiredBufferSize_BoundaryValue16_ReturnsTwo() { + // Arrange + int delta = 16; // 16 << 1 = 32, which equals Space + + // Act + int size = PolylineEncoding.GetRequiredBufferSize(delta); + + // Assert + Assert.AreEqual(2, size); + } + + #endregion + + #region ValidateFormat Tests + + /// + /// Tests that ValidateFormat succeeds with a valid polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateFormat_ValidPolyline_DoesNotThrow() { + // Arrange + string polyline = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; + + // Act & Assert + PolylineEncoding.ValidateFormat(polyline); + } + + /// + /// Tests that ValidateFormat throws when polyline contains invalid character. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateFormat_InvalidCharacter_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "_p~iF!ps|U"; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + } + + /// + /// Tests that ValidateFormat throws when polyline has invalid block structure. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateFormat_InvalidBlockStructure_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "________"; // All continuation characters, no block terminator + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + } + + /// + /// Tests that ValidateFormat succeeds with empty polyline ending with terminator. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateFormat_EmptyPolylineWithTerminator_DoesNotThrow() { + // Arrange + string polyline = "?"; // Single terminator character + + // Act & Assert + PolylineEncoding.ValidateFormat(polyline); + } + + /// + /// Tests that ValidateFormat throws when block is too long. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateFormat_BlockTooLong_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "________?"; // 8 characters in block (max is 7) + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + } + + #endregion + + #region ValidateCharRange Tests + + /// + /// Tests that ValidateCharRange succeeds with all valid characters. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_AllValidCharacters_DoesNotThrow() { + // Arrange + string polyline = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; + + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange succeeds with minimum valid character. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_MinimumValidCharacter_DoesNotThrow() { + // Arrange + string polyline = "?"; // ASCII 63 (Min) + + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange succeeds with maximum valid character. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_MaximumValidCharacter_DoesNotThrow() { + // Arrange + string polyline = "~"; // ASCII 126 (Max) + + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange throws when character is below minimum. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_CharacterBelowMinimum_ThrowsInvalidPolylineException() { + // Arrange + string polyline = ">"; // ASCII 62 (below Min of 63) + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + /// + /// Tests that ValidateCharRange throws when character is above maximum. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_CharacterAboveMaximum_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "\u007F"; // ASCII 127 (above Max of 126) + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + /// + /// Tests that ValidateCharRange throws when invalid character is in middle of valid polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_InvalidCharacterInMiddle_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "_p~iF!ps|U"; // '!' is ASCII 33 (below Min) + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + /// + /// Tests that ValidateCharRange succeeds with empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_EmptyPolyline_DoesNotThrow() { + // Arrange + string polyline = ""; + + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange throws when invalid character is at end of polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_InvalidCharacterAtEnd_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "_p~iF~ps|U!"; // '!' at end + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + /// + /// Tests that ValidateCharRange succeeds with long polyline to trigger SIMD path. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_LongPolyline_DoesNotThrow() { + // Arrange + // Create a string long enough to trigger SIMD vectorization (typically 8-16 chars depending on platform) + string polyline = "????????????????????????????????"; + + // Act & Assert + PolylineEncoding.ValidateCharRange(polyline); + } + + /// + /// Tests that ValidateCharRange throws when invalid character is in SIMD-processed section. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_InvalidCharacterInSimdSection_ThrowsInvalidPolylineException() { + // Arrange + // Create a long string with invalid character to trigger SIMD path detection + string polyline = "????????!???????????????????????"; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + /// + /// Tests that ValidateCharRange throws when invalid character is in scalar remainder section. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateCharRange_InvalidCharacterInScalarRemainder_ThrowsInvalidPolylineException() { + // Arrange + // Create a string that leaves remainder after SIMD processing + string polyline = "????????????????\u007F"; // Valid chars + one invalid at end + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); + } + + #endregion + + #region ValidateBlockLength Tests + + /// + /// Tests that ValidateBlockLength succeeds with single block. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_SingleBlock_DoesNotThrow() { + // Arrange + string polyline = "?"; // Single terminator + + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength succeeds with multiple blocks. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_MultipleBlocks_DoesNotThrow() { + // Arrange + string polyline = "_p~iF~ps|U"; // Multiple blocks + + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength succeeds with maximum length block. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_MaximumLengthBlock_DoesNotThrow() { + // Arrange + string polyline = "______?"; // 6 continuation chars + terminator (max length is 7 total) + + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength throws when block exceeds maximum length. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_BlockExceedsMaximumLength_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "________?"; // 8 chars in block (exceeds max of 7) + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); + } + + /// + /// Tests that ValidateBlockLength throws when polyline does not end with block terminator. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_NoBlockTerminator_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "________"; // All continuation characters, no terminator + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); + } + + /// + /// Tests that ValidateBlockLength throws when empty polyline has no block terminator. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_EmptyPolyline_ThrowsInvalidPolylineException() { + // Arrange + string polyline = ""; + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); + } + + /// + /// Tests that ValidateBlockLength throws when block is too long in middle of polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_TooLongBlockInMiddle_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "?________?"; // Valid block, then too-long block + + // Act & Assert + Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); + } + + /// + /// Tests that ValidateBlockLength succeeds with consecutive terminators. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_ConsecutiveTerminators_DoesNotThrow() { + // Arrange + string polyline = "??"; // Two consecutive terminators (two 1-char blocks) + + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength succeeds with mixed block lengths. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_MixedBlockLengths_DoesNotThrow() { + // Arrange + string polyline = "?__?_____?"; // Blocks of length 1, 2, and 5 + + // Act & Assert + PolylineEncoding.ValidateBlockLength(polyline); + } + + /// + /// Tests that ValidateBlockLength throws when second-to-last block is too long. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ValidateBlockLength_SecondToLastBlockTooLong_ThrowsInvalidPolylineException() { + // Arrange + string polyline = "________?_?"; // First block is 8 chars (too long) + + // 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/PolylineTests.cs b/tests/PolylineAlgorithm.Tests/PolylineTests.cs new file mode 100644 index 00000000..5af91c18 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineTests.cs @@ -0,0 +1,751 @@ +// +// 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.Properties; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineTests { + /// + /// Tests that default constructor creates an empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Polyline_DefaultConstructor_CreatesEmptyPolyline() { + // Arrange & Act + Polyline polyline = new Polyline(); + + // Assert + Assert.IsTrue(polyline.IsEmpty); + Assert.AreEqual(0, polyline.Length); + } + + /// + /// Tests that IsEmpty returns true for default constructed polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void IsEmpty_DefaultConstructedPolyline_ReturnsTrue() { + // Arrange + Polyline polyline = new Polyline(); + + // Act + bool isEmpty = polyline.IsEmpty; + + // Assert + Assert.IsTrue(isEmpty); + } + + /// + /// Tests that IsEmpty returns false for non-empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void IsEmpty_NonEmptyPolyline_ReturnsFalse() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + + // Act + bool isEmpty = polyline.IsEmpty; + + // Assert + Assert.IsFalse(isEmpty); + } + + /// + /// Tests that Length returns zero for default constructed polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Length_DefaultConstructedPolyline_ReturnsZero() { + // Arrange + Polyline polyline = new Polyline(); + + // Act + int length = polyline.Length; + + // Assert + Assert.AreEqual(0, length); + } + + /// + /// Tests that Length returns correct value for non-empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Length_NonEmptyPolyline_ReturnsCorrectLength() { + // Arrange + string testString = "test"; + Polyline polyline = Polyline.FromString(testString); + + // Act + int length = polyline.Length; + + // Assert + Assert.AreEqual(testString.Length, length); + } + + /// + /// Tests that CopyTo throws ArgumentNullException when destination is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void CopyTo_NullDestination_ThrowsArgumentNullException() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + + // Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => polyline.CopyTo(null!)); + Assert.AreEqual("destination", exception.ParamName); + } + + /// + /// Tests that CopyTo throws ArgumentException when destination is too small. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void CopyTo_DestinationTooSmall_ThrowsArgumentException() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + char[] destination = new char[2]; + + // Act & Assert + ArgumentException exception = Assert.ThrowsExactly(() => polyline.CopyTo(destination)); + Assert.AreEqual("destination", exception.ParamName); + } + + /// + /// Tests that CopyTo copies characters to destination array of exact length. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void CopyTo_ExactLengthDestination_CopiesCharacters() { + // Arrange + string testString = "test"; + Polyline polyline = Polyline.FromString(testString); + char[] destination = new char[testString.Length]; + + // Act + polyline.CopyTo(destination); + + // Assert + Assert.AreEqual(testString, new string(destination)); + } + + /// + /// Tests that CopyTo copies characters to destination array larger than polyline length. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void CopyTo_LargerDestination_CopiesCharacters() { + // Arrange + string testString = "test"; + Polyline polyline = Polyline.FromString(testString); + char[] destination = new char[testString.Length + 5]; + + // Act + polyline.CopyTo(destination); + + // Assert + for (int i = 0; i < testString.Length; i++) { + Assert.AreEqual(testString[i], destination[i]); + } + } + + /// + /// Tests that CopyTo handles empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void CopyTo_EmptyPolyline_SucceedsWithEmptyOrLargerArray() { + // Arrange + Polyline polyline = new Polyline(); + char[] destination = Array.Empty(); + + // Act + polyline.CopyTo(destination); + + // Assert + Assert.AreEqual(0, destination.Length); + } + + /// + /// Tests that ToString returns empty string for empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_EmptyPolyline_ReturnsEmptyString() { + // Arrange + Polyline polyline = new Polyline(); + + // Act + string result = polyline.ToString(); + + // Assert + Assert.AreEqual(string.Empty, result); + } + + /// + /// Tests that ToString returns correct string for non-empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_NonEmptyPolyline_ReturnsCorrectString() { + // Arrange + string testString = "test"; + Polyline polyline = Polyline.FromString(testString); + + // Act + string result = polyline.ToString(); + + // Assert + Assert.AreEqual(testString, result); + } + + /// + /// Tests that ToString returns correct string for polyline with special characters. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void ToString_PolylineWithSpecialCharacters_ReturnsCorrectString() { + // Arrange + string testString = "~`@!#$%"; + Polyline polyline = Polyline.FromString(testString); + + // Act + string result = polyline.ToString(); + + // Assert + Assert.AreEqual(testString, result); + } + + /// + /// Tests that Equals returns true when comparing two identical polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_IdenticalPolylines_ReturnsTrue() { + // Arrange + Polyline polyline1 = Polyline.FromString("test"); + Polyline polyline2 = Polyline.FromString("test"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals returns false when comparing two different polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_DifferentPolylines_ReturnsFalse() { + // Arrange + Polyline polyline1 = Polyline.FromString("test1"); + Polyline polyline2 = Polyline.FromString("test2"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns true when comparing two empty polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_TwoEmptyPolylines_ReturnsTrue() { + // Arrange + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals returns false when comparing empty and non-empty polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_EmptyAndNonEmptyPolylines_ReturnsFalse() { + // Arrange + Polyline polyline1 = new Polyline(); + Polyline polyline2 = Polyline.FromString("test"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns false when comparing polylines with different lengths. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_PolylinesWithDifferentLengths_ReturnsFalse() { + // Arrange + Polyline polyline1 = Polyline.FromString("test"); + Polyline polyline2 = Polyline.FromString("testing"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals returns false when comparing polylines with same length but different content. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void Equals_SameLengthDifferentContent_ReturnsFalse() { + // Arrange + Polyline polyline1 = Polyline.FromString("abcd"); + Polyline polyline2 = Polyline.FromString("efgh"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals object overload returns true when comparing identical polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void EqualsObject_IdenticalPolylines_ReturnsTrue() { + // Arrange + Polyline polyline1 = Polyline.FromString("test"); + object polyline2 = Polyline.FromString("test"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that Equals object overload returns false when comparing different polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void EqualsObject_DifferentPolylines_ReturnsFalse() { + // Arrange + Polyline polyline1 = Polyline.FromString("test1"); + object polyline2 = Polyline.FromString("test2"); + + // Act + bool result = polyline1.Equals(polyline2); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals object overload returns false when comparing with null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void EqualsObject_NullObject_ReturnsFalse() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + + // Act + bool result = polyline.Equals(null); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that Equals object overload returns false when comparing with a different type. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void EqualsObject_DifferentType_ReturnsFalse() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + object other = "test"; + + // Act + bool result = polyline.Equals(other); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that GetHashCode returns zero for empty polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetHashCode_EmptyPolyline_ReturnsZero() { + // Arrange + Polyline polyline = new Polyline(); + + // Act + int hashCode = polyline.GetHashCode(); + + // Assert + Assert.AreEqual(0, hashCode); + } + + /// + /// Tests that GetHashCode returns consistent value for same polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetHashCode_SamePolyline_ReturnsConsistentValue() { + // Arrange + Polyline polyline = Polyline.FromString("test"); + + // Act + int hashCode1 = polyline.GetHashCode(); + int hashCode2 = polyline.GetHashCode(); + + // Assert + Assert.AreEqual(hashCode1, hashCode2); + } + + /// + /// Tests that GetHashCode returns same value for equal polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetHashCode_EqualPolylines_ReturnsSameHashCode() { + // Arrange + Polyline polyline1 = Polyline.FromString("test"); + Polyline polyline2 = Polyline.FromString("test"); + + // Act + int hashCode1 = polyline1.GetHashCode(); + int hashCode2 = polyline2.GetHashCode(); + + // Assert + Assert.AreEqual(hashCode1, hashCode2); + } + + /// + /// Tests that GetHashCode returns different values for different polylines. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetHashCode_DifferentPolylines_ReturnsDifferentHashCodes() { + // Arrange + Polyline polyline1 = Polyline.FromString("test1"); + Polyline polyline2 = Polyline.FromString("test2"); + + // Act + int hashCode1 = polyline1.GetHashCode(); + int hashCode2 = polyline2.GetHashCode(); + + // Assert + Assert.AreNotEqual(hashCode1, hashCode2); + } + + /// + /// Tests that GetHashCode handles single character polyline. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void GetHashCode_SingleCharacterPolyline_ReturnsNonZeroHashCode() { + // Arrange + Polyline polyline = Polyline.FromString("a"); + + // Act + int hashCode = polyline.GetHashCode(); + + // Assert + Assert.AreNotEqual(0, hashCode); + } + + /// + /// Tests that FromCharArray creates a polyline from a valid character array. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromCharArray_ValidCharArray_CreatesPolyline() { + // Arrange + char[] chars = { 't', 'e', 's', 't' }; + + // Act + Polyline polyline = Polyline.FromCharArray(chars); + + // Assert + Assert.AreEqual(4, polyline.Length); + Assert.AreEqual("test", polyline.ToString()); + } + + /// + /// Tests that FromCharArray creates an empty polyline from an empty character array. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromCharArray_EmptyCharArray_CreatesEmptyPolyline() { + // Arrange + char[] chars = Array.Empty(); + + // Act + Polyline polyline = Polyline.FromCharArray(chars); + + // Assert + Assert.IsTrue(polyline.IsEmpty); + Assert.AreEqual(0, polyline.Length); + } + + /// + /// Tests that FromCharArray throws ArgumentNullException when character array is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromCharArray_NullCharArray_ThrowsArgumentNullException() { + // Arrange & Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => Polyline.FromCharArray(null!)); + Assert.AreEqual("polyline", exception.ParamName); + } + + /// + /// Tests that FromString creates a polyline from a valid string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromString_ValidString_CreatesPolyline() { + // Arrange + string testString = "test"; + + // Act + Polyline polyline = Polyline.FromString(testString); + + // Assert + Assert.AreEqual(4, polyline.Length); + Assert.AreEqual(testString, polyline.ToString()); + } + + /// + /// Tests that FromString creates an empty polyline from an empty string. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromString_EmptyString_CreatesEmptyPolyline() { + // Arrange + string testString = string.Empty; + + // Act + Polyline polyline = Polyline.FromString(testString); + + // Assert + Assert.IsTrue(polyline.IsEmpty); + Assert.AreEqual(0, polyline.Length); + } + + /// + /// Tests that FromString throws ArgumentNullException when string is null. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromString_NullString_ThrowsArgumentNullException() { + // Arrange & Act & Assert + ArgumentNullException exception = Assert.ThrowsExactly(() => Polyline.FromString(null!)); + Assert.AreEqual("polyline", exception.ParamName); + } + + /// + /// Tests that FromString handles special characters correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromString_SpecialCharacters_CreatesPolyline() { + // Arrange + string testString = "~`@!#$%"; + + // Act + Polyline polyline = Polyline.FromString(testString); + + // Assert + Assert.AreEqual(testString.Length, polyline.Length); + Assert.AreEqual(testString, polyline.ToString()); + } + + /// + /// Tests that FromMemory creates a polyline from a valid memory region. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_ValidMemory_CreatesPolyline() { + // Arrange + char[] chars = { 't', 'e', 's', 't' }; + ReadOnlyMemory memory = new ReadOnlyMemory(chars); + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.AreEqual(4, polyline.Length); + Assert.AreEqual("test", polyline.ToString()); + } + + /// + /// Tests that FromMemory creates an empty polyline from an empty memory region. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_EmptyMemory_CreatesEmptyPolyline() { + // Arrange + ReadOnlyMemory memory = ReadOnlyMemory.Empty; + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.IsTrue(polyline.IsEmpty); + Assert.AreEqual(0, polyline.Length); + } + + /// + /// Tests that FromMemory creates an empty polyline from a default memory region. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_DefaultMemory_CreatesEmptyPolyline() { + // Arrange + ReadOnlyMemory memory = default; + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.IsTrue(polyline.IsEmpty); + Assert.AreEqual(0, polyline.Length); + } + + /// + /// Tests that FromMemory handles special characters correctly. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_SpecialCharacters_CreatesPolyline() { + // Arrange + string testString = "~`@!#$%"; + ReadOnlyMemory memory = testString.AsMemory(); + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.AreEqual(testString.Length, polyline.Length); + Assert.AreEqual(testString, polyline.ToString()); + } + + /// + /// Tests that FromMemory creates a polyline from a single character memory. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_SingleCharacter_CreatesPolyline() { + // Arrange + char[] chars = { 'a' }; + ReadOnlyMemory memory = new ReadOnlyMemory(chars); + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.AreEqual(1, polyline.Length); + Assert.AreEqual("a", polyline.ToString()); + } + + /// + /// Tests that FromMemory creates a polyline from a memory slice. + /// + [TestMethod] + [TestCategory(Category.Unit)] + public void FromMemory_MemorySlice_CreatesPolyline() { + // Arrange + string testString = "testing123"; + ReadOnlyMemory memory = testString.AsMemory(0, 4); + + // Act + Polyline polyline = Polyline.FromMemory(memory); + + // Assert + Assert.AreEqual(4, polyline.Length); + Assert.AreEqual("test", polyline.ToString()); + } + + [TestMethod] + [TestCategory("Unit")] + public void EqualityOperator_WithIdenticalPolylines_ReturnsTrue() { + // Arrange + var polyline1 = Polyline.FromString("abcde"); + var polyline2 = Polyline.FromString("abcde"); + + // Act + bool result = polyline1 == polyline2; + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + [TestCategory("Unit")] + public void EqualityOperator_WithDifferentPolylines_ReturnsFalse() { + // Arrange + var polyline1 = Polyline.FromString("abcde"); + var polyline2 = Polyline.FromString("xyz"); + + // Act + bool result = polyline1 == polyline2; + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + [TestCategory("Unit")] + public void InequalityOperator_WithIdenticalPolylines_ReturnsFalse() { + // Arrange + var polyline1 = Polyline.FromString("abcde"); + var polyline2 = Polyline.FromString("abcde"); + + // Act + bool result = polyline1 != polyline2; + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + [TestCategory("Unit")] + public void InequalityOperator_WithDifferentPolylines_ReturnsTrue() { + // Arrange + var polyline1 = Polyline.FromString("abcde"); + var polyline2 = Polyline.FromString("xyz"); + + // Act + bool result = polyline1 != polyline2; + + // Assert + Assert.IsTrue(result); + } +} 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/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..7f2e2819 100644 --- a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs @@ -16,6 +16,7 @@ namespace PolylineAlgorithm.Utility; /// Provides random generation and caching of coordinate collections and their encoded polyline representations. /// Useful for testing and benchmarking polyline algorithms with reproducible random data. /// +[System.Diagnostics.CodeAnalysis.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(); @@ -63,12 +64,10 @@ private static PolylineCoordinateCollectionPair GetCaheEntry(int count) { var enumeration = Enumerable .Range(0, count) - .Select(i => (RandomLatitude(), RandomLongitude())) - .ToList(); + .Select(_ => (RandomLatitude(), RandomLongitude())) + .ToArray(); - entry = _cache.GetOrAdd(count, _ => new PolylineCoordinateCollectionPair(enumeration, _encoder.Encode(enumeration))); - - 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..eab779c7 100644 --- a/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs @@ -15,7 +15,7 @@ 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. @@ -28,7 +28,7 @@ internal static class Valid { new (90, -180), new (0, -180), new (-90, -180), - new (-90, 0) + new (-90, 0), ]; /// @@ -40,11 +40,11 @@ internal static class Valid { } /// - /// Gets the predefined instance. + /// Gets the predefined instance. /// - /// The static value. + /// The static value. public static string GetPolyline() { - return _polyline; + return Polyline; } }