diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..cfa32c07 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,104 @@ +file_header_template = //\n// Copyright (c) Pete Sramek. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root for full license information.\n// +[*.cs] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.style = underscore_camel_case + +# Symbol specifications + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +# Naming styles + +dotnet_naming_style.underscore_camel_case.required_prefix = _ +dotnet_naming_style.underscore_camel_case.required_suffix = +dotnet_naming_style.underscore_camel_case.word_separator = +dotnet_naming_style.underscore_camel_case.capitalization = camel_case +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = inside_namespace:silent +csharp_prefer_simple_using_statement = false:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = false:silent +csharp_style_prefer_top_level_statements = false:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = when_on_single_line:silent +csharp_style_expression_bodied_local_functions = when_on_single_line:silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..e9f01f59 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,65 @@ +name: Benchmark + +on: + push: + branches: [ "main" ] + +jobs: + build: + name: Build with .NET 9 + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install .NET 9 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x + - name: Restore + run: dotnet restore + - name: Build + run: dotnet build --no-restore --configuration Release + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: build + path: | + **/bin/* + **/obj/* + + benchmark: + name: Benchmark on ${{ matrix.os }} + needs: [build] + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: | + 2.0.x + 2.1.x + 2.2.x + 3.0.x + 3.1.x + 5.x + 6.x + 7.x + 8.x + 9.x + - name: Download + uses: actions/download-artifact@v4 + with: + name: build + path: . + merge-multiple: true + - name: Benchmark + working-directory: ./benchmarks/PolylineAlgorithm.Benchmarks + run: dotnet run -c Release -f net9.0 --filter * \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 0bfc8aa7..b6bc1034 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,28 +1,105 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - name: .NET on: - push: - branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: + - 'release/**' + workflow_dispatch: + +concurrency: ${{ github.ref }} jobs: + version: + name: Version with GitVersion ${{ vars.GIT_VERSION }} + runs-on: ubuntu-latest + outputs: + ASSEMBLY_VERSION: ${{ steps.gitversion.outputs.assemblySemVer }}.${{ github.run_number }} + ASSEMBLY_INFORMATIONAL_VERSION: ${{ steps.gitversion.outputs.assemblySemVer }}.${{ github.run_number }}+${{ steps.gitversion.outputs.sha }} + FILE_VERSION: ${{ steps.gitversion.outputs.assemblySemVer }}.${{ github.run_number }} + PACKAGE_VERSION: ${{ steps.gitversion.outputs.assemblySemVer }}${{ steps.gitversion.outputs.preReleaseLabelWithDash }}.${{ github.run_number }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install GitVersion ${{ vars.GIT_VERSION }} + uses: gittools/actions/gitversion/setup@v3.1.11 + with: + versionSpec: ${{ vars.GIT_VERSION }} + preferLatestVersion: true + - name: Calculate version with GitVersion ${{ vars.GIT_VERSION }} + id: gitversion + uses: gittools/actions/gitversion/execute@v3.1.11 + with: + useConfigFile: true + configFilePath: ./.gitversion/version.yml + build: + name: Build with .NET ${{ vars.DOTNET_VERSION }} + needs: [version] + runs-on: ubuntu-latest + env: + ASSEMBLY_VERSION: ${{ needs.version.outputs.ASSEMBLY_VERSION }} + ASSEMBLY_INFORMATIONAL_VERSION: ${{ needs.version.outputs.ASSEMBLY_INFORMATIONAL_VERSION }} + FILE_VERSION: ${{ needs.version.outputs.FILE_VERSION }} + PACKAGE_VERSION: ${{ needs.version.outputs.PACKAGE_VERSION }} + steps: + - uses: actions/checkout@v3 + - run: 'echo Assembly Version: ${{ env.ASSEMBLY_VERSION }}' + - run: 'echo File Version: ${{ env.FILE_VERSION }}' + - run: 'echo Assembly Informational Version: ${{ env.ASSEMBLY_INFORMATIONAL_VERSION }}' + - run: 'echo Package Version: ${{ env.PACKAGE_VERSION }}' + - name: Install .NET ${{ vars.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ vars.DOTNET_VERSION }} + - name: Build with .NET ${{ vars.DOTNET_VERSION }} + run: dotnet build ./src/**/*.csproj --configuration ${{ vars.BUILD_CONFIGURATION }} /p:Platform=${{ vars.BUILD_PLATFORM }} -p:Version=${{ env.ASSEMBLY_VERSION }} -p:AssemblyInformationalVersion=${{ env.ASSEMBLY_INFORMATIONAL_VERSION }} -p:FileVersion=${{ env.FILE_VERSION }} + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: build + path: | + **/bin/* + **/obj/* + test: + name: Test with .NET ${{ vars.DOTNET_VERSION }} + needs: [build] runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup .NET ${{ vars.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ vars.DOTNET_VERSION }} + - name: Test with .NET ${{ vars.DOTNET_VERSION }} + run: dotnet test ./tests/**/*Tests.csproj --configuration ${{ vars.BUILD_CONFIGURATION }} /p:Platform=${{ vars.BUILD_PLATFORM }} --verbosity normal --settings unit-test.runsettings --logger trx --collect:"Code Coverage" --results-directory test-results + - name: Upload Test Results + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + **/test-results/* + pack: + name: Pack with .NET ${{ vars.DOTNET_VERSION }} + needs: [test] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal + - name: Install .NET ${{ vars.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ vars.DOTNET_VERSION }} + - name: Download Build + uses: actions/download-artifact@v4 + with: + name: build + - name: Pack with .NET ${{ vars.DOTNET_VERSION }} + run: dotnet pack ./src/**/*.csproj --no-build --no-restore --configuration ${{ vars.BUILD_CONFIGURATION }} /p:Platform=${{ vars.BUILD_PLATFORM }} + - name: Upload Package + uses: actions/upload-artifact@v4 + with: + name: package + path: | + **/*.nupkg \ No newline at end of file diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 1526b959..f7090a3f 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -29,7 +29,7 @@ jobs: - name: Dotnet Setup uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.x + dotnet-version: 9.0.x - run: dotnet tool update -g docfx - run: docfx ./docs/docfx.json diff --git a/.gitignore b/.gitignore index 3c4efe20..88197d63 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# BenchmarkDotNet artifacts +**/BenchmarkDotNet.Artifacts/ diff --git a/.gitversion/version (2).yml b/.gitversion/version (2).yml new file mode 100644 index 00000000..d8aaeb61 --- /dev/null +++ b/.gitversion/version (2).yml @@ -0,0 +1,125 @@ +# Global settings +assembly-versioning-format: '{Major}.{Minor}.{Patch}.{env:BUILD_NUMBER}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:BUILD_NUMBER}' +assembly-informational-format: "{FullSemVer}" +tag-prefix: '[vV]?' +version-in-branch-pattern: (?[vV]?\d+(\.\d+)?(\.\d+)?).* +major-version-bump-message: '' +minor-version-bump-message: '' +patch-version-bump-message: '' +no-bump-message: '' +tag-pre-release-weight: 60000 +commit-date-format: yyyy-MM-dd +merge-message-formats: {} +update-build-number: false +semantic-version-format: Strict +strategies: +- VersionInBranchName +- TrackReleaseBranches +mode: ContinuousDelivery +label: '' +increment: Patch +prevent-increment: + of-merged-branch: true + when-branch-merged: true + when-current-commit-tagged: true +track-merge: + target: false + message: false +commit-message-incrementing: Disabled +regex: '' +source-branches: [] +is-source-branch-for: [] +tracks-release-branches: false +is-release-branch: false +is-main-branch: false +ignore: + sha: [] +# Branch settings +branches: + # Release branch - branch tracks history for specific version + release: + mode: ContinuousDeployment + regex: ^(release[\/][\d]+\.[\d]+)$ + increment: Inherit + is-release-branch: true + # Preview branch - branch accumulates changes for specific version before it is first released + preview: + mode: ContinuousDeployment + regex: ^(release[\/][\d]+\.[\d]+\/preview)$ + increment: Inherit + source-branches: + - release + # Hotfix branch - branch hotfixes a critical bug for specific version after it was first released + hotfix: + mode: ContinuousDeployment + label: '{BranchName}' + increment: Inherit + regex: ^hotfix?[/-](?.+) + tracks-release-branches: true + source-branches: + - release + # Support branch - branch accumulates changes for specific version after it was first released + support: + mode: ContinuousDeployment + regex: ^(release[\/][\d]+\.[\d]+\/support)$ + increment: Inherit + source-branches: + - release +# Bugfixes branch - branch hotfixes bugs for specific version after it was first released + bugfix: + mode: ContinuousDeployment + label: '{BranchName}' + increment: Inherit + regex: ^bugfix?[/-](?.+) + tracks-release-branches: true + source-branches: + - support + # Feature branch - branch accumulates changes for specific version after it was first released + feature: + mode: ContinuousDeployment + label: '{BranchName}' + increment: Inherit + regex: ^feature?[/-](?.+)$ + tracks-release-branches: true + source-branches: + - preview + # PR branch - branch merges a source branch to a target branch + pull-request: + mode: ContinuousDeployment + label: pull-request + increment: Inherit + label-number-pattern: '[/-](?\d+)' + regex: ^(pull|pull\-requests|pr)[/-] + source-branches: + - main + - release + - preview + - hotfix + - support + - bugfix + - feature + # Main branch - branch tracks historical changes between releases + main: + mode: ManualDeployment + increment: Inherit + regex: ^main$ + tracks-release-branches: true + is-main-branch: true + source-branches: + - release + # Any other branch - branch tracks historical changes between releases + unknown: + mode: ManualDeployment + label: '{BranchName}' + increment: Inherit + regex: (?.+) + source-branches: + - main + - release + - preview + - hotfix + - support + - bugfix + - feature + - pull-request diff --git a/.gitversion/version (3).yml b/.gitversion/version (3).yml new file mode 100644 index 00000000..1bc7321e --- /dev/null +++ b/.gitversion/version (3).yml @@ -0,0 +1,113 @@ +mode: ContinuousDeployment + +# Formats +assembly-versioning-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +assembly-file-versioning-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +assembly-informational-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +commit-date-format: "yyyy-MM-dd" + +# Incrementing +increment: Patch +continuous-delivery-fallback-tag: ci +tag-prefix: "[vV]" + +# Padding +build-metadata-padding: 0 +commits-since-version-source-padding: 4 +legacy-semver-padding: 0 + +# Automatic bumping +commit-message-incrementing: Disabled +major-version-bump-message: '\+semver:\s?(breaking|major)' +minor-version-bump-message: '\+semver:\s?(feature|minor)' +patch-version-bump-message: '\+semver:\s?(fix|patch)' +no-bump-message: '\+semver:\s?(none|skip)' + +ignore: + sha: [] +branches: + main: + regex: ^master$|^main$ + mode: ContinuousDelivery + tag: '' + increment: Patch + prevent-increment-of-merged-branch-version: true + track-merge-target: false + source-branches: [ 'develop', 'release' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: true + pre-release-weight: 55000 + develop: + regex: ^dev(elop)?(ment)?$ + mode: ContinuousDeployment + tag: alpha + increment: Minor + prevent-increment-of-merged-branch-version: false + track-merge-target: true + source-branches: [] + tracks-release-branches: true + is-release-branch: false + is-mainline: false + pre-release-weight: 0 + release: + regex: ^release?[/-] + mode: ContinuousDelivery + tag: beta + increment: None + prevent-increment-of-merged-branch-version: true + track-merge-target: false + source-branches: [ 'develop', 'main', 'support', 'release' ] + tracks-release-branches: false + is-release-branch: true + is-mainline: false + pre-release-weight: 30000 + feature: + regex: ^feature?[/-] + mode: ContinuousDeployment + tag: useBranchName + increment: Inherit + prevent-increment-of-merged-branch-version: false + track-merge-target: false + source-branches: [ 'develop', 'main', 'release', 'feature', 'support', 'hotfix' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: false + pre-release-weight: 30000 + pull-request: + regex: ^(pull|pull\-requests|pr)[/-] + mode: ContinuousDelivery + tag: pull-request- + increment: Inherit + tag-number-pattern: '[/-](?\d+)[-/]' + source-branches: [ 'develop', 'main', 'release', 'feature', 'support', 'hotfix' ] + prevent-increment-of-merged-branch-version: false + tracks-release-branches: false + track-merge-target: true + is-release-branch: false + is-mainline: false + pre-release-weight: 30000 + hotfix: + regex: ^hotfix(es)?[/-] + mode: ContinuousDelivery + tag: beta + increment: Patch + prevent-increment-of-merged-branch-version: false + track-merge-target: false + source-branches: [ 'develop', 'main', 'support' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: false + pre-release-weight: 30000 + support: + regex: ^support[/-] + mode: ContinuousDelivery + tag: '' + increment: Patch + prevent-increment-of-merged-branch-version: true + track-merge-target: false + source-branches: [ 'main' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: true + pre-release-weight: 55000 \ No newline at end of file diff --git a/.gitversion/version.yml b/.gitversion/version.yml new file mode 100644 index 00000000..1ef165ab --- /dev/null +++ b/.gitversion/version.yml @@ -0,0 +1,110 @@ +assembly-versioning-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +assembly-file-versioning-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +assembly-informational-format: '{Major}.{Minor}.{CommitsSinceVersionSource}' +tag-prefix: '[vV]?' +version-in-branch-pattern: (?[vV]?\d+.\d+(\.\d+){0,1}) +update-build-number: false +semantic-version-format: Loose +strategies: +- VersionInBranchName +- Fallback +ignore: + sha: [] +mode: ContinuousDeployment +label: '' +increment: None +prevent-increment: + of-merged-branch: true + when-branch-merged: true + when-current-commit-tagged: true +track-merge-target: false +track-merge-message: false +commit-message-incrementing: Disabled +regex: '' +source-branches: [] +is-source-branch-for: [] +tracks-release-branches: false +is-release-branch: false +is-main-branch: false +branches: + main: + mode: ManualDeployment + label: '' + increment: Inherit + regex: ^master$|^main$ + is-main-branch: true + preview: + mode: ContinuousDeployment + label: preview + increment: Inherit + regex: ^release(s)?\/([vV]?\d+.\d+(\.\d+){0,1})\/preview$ + source-branches: + - release + release: + mode: ContinuousDeployment + label: '' + increment: Inherit + regex: ^release(s)?\/([vV]?\d+.\d+(\.\d+){0,1})$ + source-branches: + - preview + - support + is-release-branch: true + hotfix: + mode: ContinuousDelivery + label: hotfix + increment: Inherit + track-merge-target: false + regex: ^hotfix(es)?\/(?.+) + source-branches: + - release + support: + mode: ContinuousDelivery + label: beta + increment: Inherit + track-merge-target: false + regex: ^support\/(?.+) + source-branches: + - release + bugfix: + mode: ContinuousDelivery + label: bugfix + increment: Inherit + track-merge-target: false + regex: ^bugfix(es)?\/(?.+) + source-branches: + - support + feature: + mode: ManualDeployment + label: '{BranchName}' + increment: Inherit + track-merge-target: false + regex: ^feature(s)?\/(?.+) + source-branches: + - preview + - release + pull-request: + mode: ContinuousDelivery + label: pull-request- + increment: Inherit + track-merge-target: true + regex: ^(pull|pull\-requests|pr)[/-] + source-branches: + - preview + - release + - hotfix + - bugfix + - support + - feature + unknown: + mode: ManualDeployment + label: '{BranchName}' + increment: Inherit + regex: (?.+) + source-branches: + - main + - develop + - release + - feature + - pull-request + - hotfix + - support \ No newline at end of file diff --git a/DropoutCoder.PolylineAlgorithm.sln b/PolylineAlgorithm.sln similarity index 65% rename from DropoutCoder.PolylineAlgorithm.sln rename to PolylineAlgorithm.sln index a7673087..5d7b02ac 100644 --- a/DropoutCoder.PolylineAlgorithm.sln +++ b/PolylineAlgorithm.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm", "src\DropoutCoder.PolylineAlgorithm.csproj", "{882322A6-E758-4662-8D1C-7C555C8FC3F2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolylineAlgorithm", "src\PolylineAlgorithm\PolylineAlgorithm.csproj", "{882322A6-E758-4662-8D1C-7C555C8FC3F2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{51C886AF-D610-48A4-9D73-2DEB38742801}" EndProject @@ -10,18 +10,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{576FEFFC EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{7F9807A1-01FF-40C3-9342-3F3F35D632DA}" ProjectSection(SolutionItems) = preProject - nuget\DropoutCoder.PolylineAlgorithm.nuspec = nuget\DropoutCoder.PolylineAlgorithm.nuspec + nuget\PolylineAlgorithm.nuspec = nuget\PolylineAlgorithm.nuspec EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Tests", "tests\DropoutCoder.PolylineAlgorithm.Tests.csproj", "{30324A08-AA42-425D-87DA-8F9C6AF60454}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolylineAlgorithm.Tests", "tests\PolylineAlgorithm.Tests\PolylineAlgorithm.Tests.csproj", "{30324A08-AA42-425D-87DA-8F9C6AF60454}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C13E31F9-B8EF-4915-A1C8-BC33F6431EB9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{33C03F16-4313-4579-87E6-65892AF21D7D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks.csproj", "{9C7CBAD5-415B-4589-86E1-01C849F9C56C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj", "{D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolylineAlgorithm.Benchmarks", "benchmarks\PolylineAlgorithm.Benchmarks\PolylineAlgorithm.Benchmarks.csproj", "{9C7CBAD5-415B-4589-86E1-01C849F9C56C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -41,10 +39,6 @@ Global {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.Build.0 = Release|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,7 +47,6 @@ Global {882322A6-E758-4662-8D1C-7C555C8FC3F2} = {51C886AF-D610-48A4-9D73-2DEB38742801} {30324A08-AA42-425D-87DA-8F9C6AF60454} = {576FEFFC-B624-40C3-A8AF-4E5233802EA0} {9C7CBAD5-415B-4589-86E1-01C849F9C56C} = {33C03F16-4313-4579-87E6-65892AF21D7D} - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B} = {33C03F16-4313-4579-87E6-65892AF21D7D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93A268DC-0947-4FBB-B495-DDAD4B013D82} diff --git a/README.md b/README.md index 2695d8c8..42d218c2 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,35 @@ -# .NET Polyline Algorithm (.NET Standard 2.0) +# .NET Polyline Algorithm (.NET Standard 2.1) -Lightweight .NET Standard 2.0 library implementing Google Polyline Algorithm. Designed with respect to flexibility, but still with easy to use. +Lightweight high=performance .NET Standard 2.1 library implementing Google Polyline Algorithm. -## Getting started -### Prerequisites +## Prerequisites -.NET Polyline Algorithm is avalable as nuget package Cloudikka.PolylineAlgorithm targeting .NET Standard 2.0. +.NET Polyline Algorithm is avalable as a NuGet package PolylineAlgorithm targeting .NET Standard 2.1. Command line: -`Install-Package Cloudikka.PolylineAlgorithm` +`Install-Package PolylineAlgorithm` NuGet Package Manager: -`Cloudikka.PolylineAlgorithm` +`PolylineAlgorithm` -#### Warning - -Library is using ValueTuple Structure. ValueTuple struct is avalable in .NET Framework 4.7 and above. Incase your project is targeting lower version of .NET Framework you probably have to install System.ValueTuple NuGet package. (not tested yet) - -Command line: - -`Install-Package System.ValueTuple` - -NuGet Package Manager: - -`System.ValueTuple` - -### Hot to use it +## Hot to use it There are three ways how to use .NET Polyline Algorithm library based on your needs. For each is available Encode and Decode methods. -#### Static methods +### Static methods Whenever you just need to encode or decode Google polyline you can use static methods defined in static PolylineAlgorithm class. -##### Decoding +#### Decoding ```csharp string polyline = "polyline"; IEnumerable<(double, double)> coordinates = PolylineAlgorithm.Decode(polyline); ``` -##### Encoding +#### Encoding ```csharp IEnumerable<(double, double)> coordinates = new (double, double) [] { (35.635, 76.27182), (35.2435, 75.625), ... }; @@ -50,11 +37,11 @@ Whenever you just need to encode or decode Google polyline you can use static me ``` -#### Default instance +### Default instance If you need to use dependency injection, you would like to have instance to deliver the work for you. In that case you can use default instance of PolylineEncoding class, which implements IPolylineEncoding<(double Latitude, double Longitude)> interface. -##### Decoding +#### Decoding ```csharp string polyline = "polyline"; @@ -62,7 +49,7 @@ If you need to use dependency injection, you would like to have instance to deli IEnumerable<(double, double)> coordinates = encoding.Decode(polyline); ``` -##### Encoding +#### Encoding ```csharp IEnumerable<(double, double)> coordinates = new (double, double) [] { (35.635, 76.27182), (35.2435, 75.625), ... }; @@ -70,11 +57,11 @@ If you need to use dependency injection, you would like to have instance to deli string polyline = encoding.Encode(coordinates); ``` -#### Inherited base class +### Inherited base class There may be a scenario you need to pass and return different types to and from without a need to add another extra layer. In this case you can inherit PolylineEncodingBase class and override template methods CreateResult and GetCoordinates. -##### Inheriting +#### Inheriting ```csharp public class MyPolylineEncoding : PolylineEncodingBase { @@ -106,6 +93,87 @@ There may be a scenario you need to pass and return different types to and from string polyline = encoding.Encode(coordinates); ``` +### Performance + +#### Decode + +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2894) +Apple Silicon, 4 CPU, 4 logical and 4 physical cores +.NET SDK 9.0.102 + [Host] : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD + Job-CJKHIS : .NET 5.0.17 (5.0.1722.21314), Arm64 RyuJIT AdvSIMD + Job-GAHPKM : .NET 6.0.36 (6.0.3624.51421), Arm64 RyuJIT AdvSIMD + Job-KWIIGA : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD + Job-ZMWUEM : .NET 8.0.12 (8.0.1224.60305), Arm64 RyuJIT AdvSIMD + Job-EQIHVN : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD + + +``` +| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|---------------------------------- |--------- |-----------:|---------:|---------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| +| **Cloudikka_PolylineEncoding_Decode** | **.NET 5.0** | **1,768.2 ns** | **7.36 ns** | **6.88 ns** | **1.29** | **0.01** | **3** | **0.4959** | **0.0019** | **3120 B** | **3.82** | +| **PolylineDecoder_Decode** | **.NET 5.0** | **1,367.1 ns** | **7.91 ns** | **6.61 ns** | **1.00** | **0.01** | **2** | **0.1297** | **-** | **816 B** | **1.00** | +| **Polyliner_Decode** | **.NET 5.0** | **1,218.0 ns** | **3.79 ns** | **3.17 ns** | **0.89** | **0.00** | **1** | **0.5169** | **0.0057** | **3248 B** | **3.98** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Decode** | **.NET 6.0** | **1,763.8 ns** | **4.51 ns** | **4.00 ns** | **1.38** | **0.01** | **3** | **0.4959** | **0.0019** | **3120 B** | **3.82** | +| **PolylineDecoder_Decode** | **.NET 6.0** | **1,277.5 ns** | **4.43 ns** | **4.14 ns** | **1.00** | **0.00** | **2** | **0.1297** | **-** | **816 B** | **1.00** | +| **Polyliner_Decode** | **.NET 6.0** | **837.2 ns** | **2.49 ns** | **2.21 ns** | **0.66** | **0.00** | **1** | **0.5169** | **0.0057** | **3248 B** | **3.98** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Decode** | **.NET 7.0** | **1,387.6 ns** | **24.18 ns** | **22.62 ns** | **1.12** | **0.02** | **2** | **1.4915** | **-** | **3120 B** | **3.82** | +| **PolylineDecoder_Decode** | **.NET 7.0** | **1,237.5 ns** | **3.05 ns** | **2.86 ns** | **1.00** | **0.00** | **1** | **0.3891** | **-** | **816 B** | **1.00** | +| **Polyliner_Decode** | **.NET 7.0** | **1,230.6 ns** | **2.08 ns** | **1.84 ns** | **0.99** | **0.00** | **1** | **1.5526** | **-** | **3248 B** | **3.98** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Decode** | **.NET 8.0** | **766.0 ns** | **3.39 ns** | **3.17 ns** | **1.49** | **0.01** | **3** | **1.4915** | **-** | **3120 B** | **3.82** | +| **PolylineDecoder_Decode** | **.NET 8.0** | **513.9 ns** | **1.21 ns** | **1.14 ns** | **1.00** | **0.00** | **1** | **0.3901** | **-** | **816 B** | **1.00** | +| **Polyliner_Decode** | **.NET 8.0** | **704.4 ns** | **1.38 ns** | **1.29 ns** | **1.37** | **0.00** | **2** | **1.5526** | **-** | **3248 B** | **3.98** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Decode** | **.NET 9.0** | **782.5 ns** | **1.50 ns** | **1.33 ns** | **1.57** | **0.00** | **3** | **1.4915** | **-** | **3120 B** | **3.82** | +| **PolylineDecoder_Decode** | **.NET 9.0** | **498.7 ns** | **1.27 ns** | **1.19 ns** | **1.00** | **0.00** | **1** | **0.3901** | **-** | **816 B** | **1.00** | +| **Polyliner_Decode** | **.NET 9.0** | **705.2 ns** | **1.30 ns** | **1.15 ns** | **1.41** | **0.00** | **2** | **1.5526** | **-** | **3248 B** | **3.98** | + + +#### Encode + +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2894) +Apple Silicon, 4 CPU, 4 logical and 4 physical cores +.NET SDK 9.0.102 + [Host] : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD + Job-CJKHIS : .NET 5.0.17 (5.0.1722.21314), Arm64 RyuJIT AdvSIMD + Job-GAHPKM : .NET 6.0.36 (6.0.3624.51421), Arm64 RyuJIT AdvSIMD + Job-KWIIGA : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD + Job-ZMWUEM : .NET 8.0.12 (8.0.1224.60305), Arm64 RyuJIT AdvSIMD + Job-EQIHVN : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD + + +``` +| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|---------------------------------- |--------- |-----------:|---------:|---------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| +| **Cloudikka_PolylineEncoding_Encode** | **.NET 5.0** | **8,936.5 ns** | **26.81 ns** | **25.07 ns** | **8.18** | **0.04** | **3** | **2.2583** | **-** | **13.91 KB** | **7.95** | +| **PolylineEncoder_Encode** | **.NET 5.0** | **1,092.4 ns** | **4.73 ns** | **4.42 ns** | **1.00** | **0.01** | **1** | **0.2842** | **-** | **1.75 KB** | **1.00** | +| **Polyliner_Encode** | **.NET 5.0** | **1,291.0 ns** | **4.05 ns** | **3.16 ns** | **1.18** | **0.01** | **2** | **0.3643** | **0.0019** | **2.23 KB** | **1.28** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Encode** | **.NET 6.0** | **7,640.7 ns** | **21.80 ns** | **20.39 ns** | **7.56** | **0.04** | **3** | **2.2583** | **-** | **13.91 KB** | **7.95** | +| **PolylineEncoder_Encode** | **.NET 6.0** | **1,011.3 ns** | **4.33 ns** | **4.05 ns** | **1.00** | **0.01** | **1** | **0.2842** | **-** | **1.75 KB** | **1.00** | +| **Polyliner_Encode** | **.NET 6.0** | **1,080.0 ns** | **3.35 ns** | **3.13 ns** | **1.07** | **0.01** | **2** | **0.3643** | **0.0019** | **2.23 KB** | **1.28** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Encode** | **.NET 7.0** | **6,718.0 ns** | **13.77 ns** | **12.88 ns** | **6.65** | **0.02** | **3** | **6.8054** | **-** | **13.91 KB** | **7.95** | +| **PolylineEncoder_Encode** | **.NET 7.0** | **1,010.1 ns** | **3.32 ns** | **3.11 ns** | **1.00** | **0.00** | **1** | **0.8564** | **-** | **1.75 KB** | **1.00** | +| **Polyliner_Encode** | **.NET 7.0** | **1,075.9 ns** | **3.27 ns** | **2.73 ns** | **1.07** | **0.00** | **2** | **1.0929** | **-** | **2.23 KB** | **1.28** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Encode** | **.NET 8.0** | **4,969.5 ns** | **9.29 ns** | **8.24 ns** | **9.53** | **0.03** | **3** | **6.8054** | **-** | **13.91 KB** | **7.95** | +| **PolylineEncoder_Encode** | **.NET 8.0** | **521.4 ns** | **1.37 ns** | **1.28 ns** | **1.00** | **0.00** | **1** | **0.8564** | **-** | **1.75 KB** | **1.00** | +| **Polyliner_Encode** | **.NET 8.0** | **1,020.7 ns** | **2.90 ns** | **2.57 ns** | **1.96** | **0.01** | **2** | **1.0929** | **-** | **2.23 KB** | **1.28** | +| | | | | | | | | | | | | +| **Cloudikka_PolylineEncoding_Encode** | **.NET 9.0** | **3,809.8 ns** | **27.85 ns** | **26.06 ns** | **7.21** | **0.05** | **3** | **4.3335** | **-** | **8.86 KB** | **5.06** | +| **PolylineEncoder_Encode** | **.NET 9.0** | **528.2 ns** | **1.85 ns** | **1.73 ns** | **1.00** | **0.00** | **1** | **0.8564** | **-** | **1.75 KB** | **1.00** | +| **Polyliner_Encode** | **.NET 9.0** | **1,033.9 ns** | **6.30 ns** | **5.89 ns** | **1.96** | **0.01** | **2** | **1.0929** | **-** | **2.23 KB** | **1.28** | + + + ### Documentation Documentation is can be found at https://dropoutcoder.github.io/polyline-algorithm-csharp/api/index.html. diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj deleted file mode 100644 index 2d5b8cc1..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - - - diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs deleted file mode 100644 index 798fd3ae..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using DropoutCoder.PolylineAlgorithm.Encoding; - - [MemoryDiagnoser] - [MarkdownExporter] - public class PolylineEncodingBenchmark - { - private Consumer _consumer = new Consumer(); - - [Params(10_000, 100_000, 1_000_000, Priority = 2)] - public int N; - - public IEnumerable<(double, double)> Coordinates; - - public PolylineEncoding Encoding { get; private set; } - - public string Polyline; - - [GlobalSetup] - public void Setup() - { - Encoding = new PolylineEncoding(); - Coordinates = new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }; - Polyline = "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB"; - } - - [Benchmark] - public void Decode() => Encoding - .Decode(Polyline) - .Consume(_consumer); - - [Benchmark] - public void Encode() => Encoding - .Encode(Coordinates) - .Consume(_consumer); - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs deleted file mode 100644 index d6e3655e..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Benchmarks -{ - using BenchmarkDotNet.Running; - - internal class Program - { - static void Main(string[] args) - { - BenchmarkRunner - .Run(); - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs deleted file mode 100644 index 9854d042..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - /// - /// Defines global constant values - /// - internal static class Constants - { - #region Constants - - /// - /// Defines the coordinate precision - /// - public const double Precision = 1E5; - - /// - /// Defines the shift length - /// - public const int ShiftLength = 5; - - #endregion - - /// - /// Defines ASCII characters constant values - /// - internal static class ASCII - { - #region Constants - - /// - /// Defines the ASCII Question Mark - /// - public const int QuestionMark = 63; - - /// - /// Defines the ASCII Space - /// - public const int Space = 32; - - /// - /// Defines the ASCII Unit Separator - /// - public const int UnitSeparator = 31; - - #endregion - } - - /// - /// Defines coordinates constant values - /// - internal static class Coordinate - { - #region Constants - - /// - /// Defines the maximum value for latitude - /// - public const int MaxLatitude = 90; - - /// - /// Defines the maximum value for longitude - /// - public const int MaxLongitude = 180; - - /// - /// Defines the minimum value for latitude - /// - public const int MinLatitude = -MaxLatitude; - - /// - /// Defines the minimum value for longitude - /// - public const int MinLongitude = -MaxLongitude; - - #endregion - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs deleted file mode 100644 index 43289f5d..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs +++ /dev/null @@ -1,200 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using System; - - [MemoryDiagnoser] - public class DecodePerformanceBenchmark - { - private Consumer _consumer = new Consumer(); - public static IEnumerable<(int, char[])> Polylines() - { - yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI".ToCharArray()); - yield return (2, "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB".ToCharArray()); - yield return (3, "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH".ToCharArray()); - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V1((int, char[]) arg) => V1.Decode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V1_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V1.Decode(arg.Item2).Consume(_consumer)); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V2((int, char[]) arg) => V2.Decode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V2_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V2.Decode(arg.Item2).Consume(_consumer)); - - private class V1 - { - public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) - { - if (polyline is null || polyline.Length == 0) - { - throw new ArgumentException(nameof(polyline)); - } - - int index = 0; - int latitude = 0; - int longitude = 0; - - var result = new List<(double Latitude, double Longitude)>(); - - while (index < polyline.Length) - { - if (!TryCalculateNext(ref polyline, ref index, ref latitude)) - { - throw new InvalidOperationException(); - } - - if (!TryCalculateNext(ref polyline, ref index, ref longitude)) - { - throw new InvalidOperationException(); - } - - var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); - - if (!CoordinateValidator.IsValid(coordinate)) - { - throw new InvalidOperationException(); - } - - result.Add(coordinate); - } - - return result; - } - - private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value) - { - int chunk; - int sum = 0; - int shifter = 0; - - do - { - chunk = polyline[index++] - Constants.ASCII.QuestionMark; - sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; - shifter += Constants.ShiftLength; - } while (chunk >= Constants.ASCII.Space && index < polyline.Length); - - if (index >= polyline.Length && chunk >= Constants.ASCII.Space) - return false; - - value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; - - return true; - } - - private static double GetDoubleRepresentation(int value) - { - return Convert.ToDouble(value) / Constants.Precision; - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - - private class V2 - { - public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) - { - if (polyline is null || polyline.Length == 0) - { - throw new ArgumentException(nameof(polyline)); - } - - int offset = 0; - int latitude = 0; - int longitude = 0; - - while (offset < polyline.Length) - { - if (!TryCalculateNext(ref polyline, ref offset, ref latitude)) - { - throw new InvalidOperationException(); - } - - if (!TryCalculateNext(ref polyline, ref offset, ref longitude)) - { - throw new InvalidOperationException(); - } - - var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); - - if (!CoordinateValidator.IsValid(coordinate)) - { - throw new InvalidOperationException(); - } - - yield return (latitude, longitude); - } - } - - private static bool TryCalculateNext(ref char[] polyline, ref int offset, ref int value) - { - int chunk; - int sum = 0; - int shifter = 0; - - do - { - chunk = polyline[offset++] - Constants.ASCII.QuestionMark; - sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; - shifter += Constants.ShiftLength; - } while (chunk >= Constants.ASCII.Space && offset < polyline.Length); - - if (offset >= polyline.Length && chunk >= Constants.ASCII.Space) - return false; - - value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; - - return true; - } - - private static double GetDoubleRepresentation(int value) - { - return value / Constants.Precision; - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj deleted file mode 100644 index d799ba3b..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - - - - diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs deleted file mode 100644 index d7a75e02..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs +++ /dev/null @@ -1,222 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using Microsoft.Extensions.ObjectPool; - using System.Collections.Generic; - using System.Text; - - [MemoryDiagnoser] - public class EncodePerformanceBenchmark - { - private Consumer _consumer = new Consumer(); - - public IEnumerable<(int, IEnumerable<(double, double)>)> Coordinates() - { - yield return (1, new[] { (49.47383, 59.06250), (-58.37407, 25.31250), (52.99363, -120.93750), (-44.49024, -174.37500) }); - yield return (2, new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }); - yield return (3, new[] { (60.81071, -121.40005), (70.05664, -38.43130), (37.52379, -84.83755), (41.85003, 26.25620), (68.04709, 110.63120), (61.48922, 50.16245), (-4.46018, -58.11880), (-32.16061, -3.27505), (-50.89185, -55.30630), (-28.52070, 90.94370), (35.26009, 93.75620), (54.83622, 128.91245), (1.16022, 37.50620), (-44.26398, -131.24380), (-33.34325, 154.22495), (-59.65879, 90.94370), (-62.38215, 0.94370), (72.32117, 40.31870), (64.66910, 2.34995), (-61.04971, -84.83755), (77.10238, -91.86880), (-72.88859, -129.83755), (-69.24987, -24.36880), (77.41254, 119.06870), (-70.69409, 83.91245), (78.85650, 75.47495), (26.83989, 140.16245), (-24.75069, -108.74380), (30.53968, -145.30630), (79.12503, 145.78745), (-34.51006, 133.13120), (-73.29753, -60.93130), (-74.08712, 23.44370), (-76.57404, 100.78745), (-76.57404, 100.78745), (39.72082, 103.59995), (70.99412, 148.59995), (82.27591, 138.75620), (78.29964, -3.27505), (78.29964, -3.27505), (-8.65039, 47.34995) }); - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V1((int, IEnumerable<(double, double)>) arg) => V1.Encode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V1.Encode(arg.Item2).Consume(_consumer)); - - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V2((int, IEnumerable<(double, double)>) arg) => V2.Encode(arg.Item2).Consume(_consumer); - - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer)); - - private class V1 - { - public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - if (coordinates is null || !coordinates.Any()) - { - throw new ArgumentException(nameof(coordinates)); - } - - EnsureCoordinates(coordinates); - - int lastLatitude = 0; - int lastLongitude = 0; - var sb = new StringBuilder(); - - foreach (var coordinate in coordinates) - { - int latitude = GetIntegerRepresentation(coordinate.Latitude); - int longitude = GetIntegerRepresentation(coordinate.Longitude); - - sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray()); - sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray()); - - lastLatitude = latitude; - lastLongitude = longitude; - } - - return sb.ToString(); - } - - private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - var invalidCoordinates = coordinates - .Where(c => !CoordinateValidator.IsValid(c)); - - if (invalidCoordinates.Any()) - { - throw new AggregateException( - invalidCoordinates - .Select(c => - new ArgumentOutOfRangeException() - ) - ); - } - } - - private static IEnumerable GetEncodedCharacters(int value) - { - int shifted = value << 1; - if (value < 0) - shifted = ~shifted; - - int rem = shifted; - - while (rem >= Constants.ASCII.Space) - { - yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); - - rem >>= Constants.ShiftLength; - } - - yield return (char)(rem + Constants.ASCII.QuestionMark); - } - - private static int GetIntegerRepresentation(double value) - { - return (int)Math.Round(value * Constants.Precision); - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - - private class V2 - { - private static readonly ObjectPool _pool = new DefaultObjectPoolProvider().CreateStringBuilderPool(5, int.MaxValue); - - public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - if (coordinates is null || !coordinates.Any()) - { - throw new ArgumentException(nameof(coordinates)); - } - - EnsureCoordinates(coordinates); - - int previousLatitude = 0; - int previousLongitude = 0; - - var sb = _pool.Get(); - - foreach (var coordinate in coordinates) - { - int latitude = GetIntegerRepresentation(coordinate.Latitude); - int longitude = GetIntegerRepresentation(coordinate.Longitude); - - sb.Append(GetEncodedCharacters(latitude - previousLatitude).ToArray()); - sb.Append(GetEncodedCharacters(longitude - previousLongitude).ToArray()); - - previousLatitude = latitude; - previousLongitude = longitude; - } - - var result = sb.ToString(); - - _pool.Return(sb); - - return result; - } - - private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - var invalidCoordinates = coordinates - .Where(c => !CoordinateValidator.IsValid(c)); - - if (invalidCoordinates.Any()) - { - throw new AggregateException( - invalidCoordinates - .Select(c => - new ArgumentOutOfRangeException() - ) - ); - } - } - - private static IEnumerable GetEncodedCharacters(int value) - { - int shifted = value << 1; - if (value < 0) - shifted = ~shifted; - - int rem = shifted; - - while (rem >= Constants.ASCII.Space) - { - yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); - - rem >>= Constants.ShiftLength; - } - - yield return (char)(rem + Constants.ASCII.QuestionMark); - } - - private static int GetIntegerRepresentation(double value) - { - return (int)Math.Round(value * Constants.Precision); - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs deleted file mode 100644 index 1a591c1c..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Running; - - internal class Program - { - static void Main(string[] args) - { - BenchmarkRunner - .Run(); - BenchmarkRunner - .Run(); - } - } -} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/DecodeBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/DecodeBenchmark.cs new file mode 100644 index 00000000..735ea409 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/DecodeBenchmark.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Order; +using Cloudikka.PolylineAlgorithm.Encoding; +using PolylineAlgorithm; +using PolylinerNet; + +[RankColumn] +[MemoryDiagnoser] +[MarkdownExporter] +[Orderer(SummaryOrderPolicy.Declared)] +public class DecodeBenchmark { + private readonly Consumer _consumer = new(); + public static string String_Polyline { get; } = "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH"; + public static ReadOnlyMemory Memory_Polyline { get; } = String_Polyline.AsMemory(); + + + [Benchmark(Baseline = true)] + public void PolylineDecoder_Decode() { + ReadOnlySpan polyline = Memory_Polyline.Span; + var decoder = new PolylineDecoder(); + decoder.Decode(in polyline).Consume(_consumer); + } + + + [Benchmark] + public void Cloudikka_PolylineEncoding_Decode() { + string polyline = String_Polyline; + var polyliner = new PolylineEncoding(); + polyliner.Decode(polyline).Consume(_consumer); + } + + [Benchmark] + public void Polyliner_Decode() { + string polyline = String_Polyline; + var polyliner = new Polyliner(); + polyliner.Decode(polyline).Consume(_consumer); + } +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/EncodeBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/EncodeBenchmark.cs new file mode 100644 index 00000000..5073750e --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/EncodeBenchmark.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; +using Cloudikka.PolylineAlgorithm.Encoding; +using PolylineAlgorithm; +using PolylinerNet; +using System.Collections.Generic; + +[RankColumn] +[MemoryDiagnoser] +[MarkdownExporter] +[Orderer(SummaryOrderPolicy.Declared)] +public class EncodeBenchmark { + public static IEnumerable Coordinates_Enumerable { get; } = [new(60.81071, -121.40005), new(70.05664, -38.43130), new(37.52379, -84.83755), new(41.85003, 26.25620), new(68.04709, 110.63120), new(61.48922, 50.16245), new(-4.46018, -58.11880), new(-32.16061, -3.27505), new(-50.89185, -55.30630), new(-28.52070, 90.94370), new(35.26009, 93.75620), new(54.83622, 128.91245), new(1.16022, 37.50620), new(-44.26398, -131.24380), new(-33.34325, 154.22495), new(-59.65879, 90.94370), new(-62.38215, 0.94370), new(72.32117, 40.31870), new(64.66910, 2.34995), new(-61.04971, -84.83755), new(77.10238, -91.86880), new(-72.88859, -129.83755), new(-69.24987, -24.36880), new(77.41254, 119.06870), new(-70.69409, 83.91245), new(78.85650, 75.47495), new(26.83989, 140.16245), new(-24.75069, -108.74380), new(30.53968, -145.30630), new(79.12503, 145.78745), new(-34.51006, 133.13120), new(-73.29753, -60.93130), new(-74.08712, 23.44370), new(-76.57404, 100.78745), new(-76.57404, 100.78745), new(39.72082, 103.59995), new(70.99412, 148.59995), new(82.27591, 138.75620), new(78.29964, -3.27505), new(78.29964, -3.27505), new(-8.65039, 47.34995)]; + public static List PolylinePoint_List { get; } = [new(60.81071, -121.40005), new(70.05664, -38.43130), new(37.52379, -84.83755), new(41.85003, 26.25620), new(68.04709, 110.63120), new(61.48922, 50.16245), new(-4.46018, -58.11880), new(-32.16061, -3.27505), new(-50.89185, -55.30630), new(-28.52070, 90.94370), new(35.26009, 93.75620), new(54.83622, 128.91245), new(1.16022, 37.50620), new(-44.26398, -131.24380), new(-33.34325, 154.22495), new(-59.65879, 90.94370), new(-62.38215, 0.94370), new(72.32117, 40.31870), new(64.66910, 2.34995), new(-61.04971, -84.83755), new(77.10238, -91.86880), new(-72.88859, -129.83755), new(-69.24987, -24.36880), new(77.41254, 119.06870), new(-70.69409, 83.91245), new(78.85650, 75.47495), new(26.83989, 140.16245), new(-24.75069, -108.74380), new(30.53968, -145.30630), new(79.12503, 145.78745), new(-34.51006, 133.13120), new(-73.29753, -60.93130), new(-74.08712, 23.44370), new(-76.57404, 100.78745), new(-76.57404, 100.78745), new(39.72082, 103.59995), new(70.99412, 148.59995), new(82.27591, 138.75620), new(78.29964, -3.27505), new(78.29964, -3.27505), new(-8.65039, 47.34995)]; + public static IEnumerable<(double, double)> Tuple_Enumerable { get; } = [new(60.81071, -121.40005), new(70.05664, -38.43130), new(37.52379, -84.83755), new(41.85003, 26.25620), new(68.04709, 110.63120), new(61.48922, 50.16245), new(-4.46018, -58.11880), new(-32.16061, -3.27505), new(-50.89185, -55.30630), new(-28.52070, 90.94370), new(35.26009, 93.75620), new(54.83622, 128.91245), new(1.16022, 37.50620), new(-44.26398, -131.24380), new(-33.34325, 154.22495), new(-59.65879, 90.94370), new(-62.38215, 0.94370), new(72.32117, 40.31870), new(64.66910, 2.34995), new(-61.04971, -84.83755), new(77.10238, -91.86880), new(-72.88859, -129.83755), new(-69.24987, -24.36880), new(77.41254, 119.06870), new(-70.69409, 83.91245), new(78.85650, 75.47495), new(26.83989, 140.16245), new(-24.75069, -108.74380), new(30.53968, -145.30630), new(79.12503, 145.78745), new(-34.51006, 133.13120), new(-73.29753, -60.93130), new(-74.08712, 23.44370), new(-76.57404, 100.78745), new(-76.57404, 100.78745), new(39.72082, 103.59995), new(70.99412, 148.59995), new(82.27591, 138.75620), new(78.29964, -3.27505), new(78.29964, -3.27505), new(-8.65039, 47.34995)]; + + + [Benchmark(Baseline = true)] + public ReadOnlySpan PolylineEncoder_Encode() { + var encoder = new PolylineEncoder(); + return encoder.Encode(Coordinates_Enumerable); + } + + + [Benchmark] + public string Cloudikka_PolylineEncoding_Encode() { + var polyliner = new PolylineEncoding(); + return polyliner.Encode(Tuple_Enumerable); + } + + [Benchmark] + public string Polyliner_Encode() { + var polyliner = new Polyliner(); + return polyliner.Encode(PolylinePoint_List); + } +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj new file mode 100644 index 00000000..967519a1 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj @@ -0,0 +1,28 @@ + + + + Exe + net5.0;net6.0;net7.0;net8.0;net9.0 + 13.0 + enable + enable + true + en + + + + false + + + + + + + + + + + + + + diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs new file mode 100644 index 00000000..3bfa67bb --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; + +internal class Program { + static void Main(string[] args) { + var config = DefaultConfig.Instance + //.AddJob(Job.Default.WithRuntime(CoreRuntime.Core20)) + //.AddJob(Job.Default.WithRuntime(CoreRuntime.Core21)) + //.AddJob(Job.Default.WithRuntime(CoreRuntime.Core22)) + //.AddJob(Job.Default.WithRuntime(CoreRuntime.Core30)) + //.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core50)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core60)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core70)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core90)); + + BenchmarkSwitcher + .FromAssembly(typeof(Program).Assembly) + .Run(args, config); + } +} diff --git a/docs/api/.manifest b/docs/api/.manifest index 794f97a0..bbdf168e 100644 --- a/docs/api/.manifest +++ b/docs/api/.manifest @@ -1,23 +1,23 @@ { - "DropoutCoder.PolylineAlgorithm": "DropoutCoder.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.Encoding": "DropoutCoder.PolylineAlgorithm.Encoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1.Decode(System.String)": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1.Encode(System.Collections.Generic.IEnumerable{`0})": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.CreateResult(System.Double,System.Double)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.GetCoordinate(System.ValueTuple{System.Double,System.Double})": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.CreateResult(System.Double,System.Double)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Decode(System.String)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Encode(System.Collections.Generic.IEnumerable{`0})": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.GetCoordinate(`0)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.Decode(System.Char[])": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.Encode(System.Collections.Generic.IEnumerable{System.ValueTuple{System.Double,System.Double}})": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.Validation": "DropoutCoder.PolylineAlgorithm.Validation.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValid(System.ValueTuple{System.Double,System.Double})": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValidLatitude(System.Double)": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValidLongitude(System.Double)": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml" + "PolylineAlgorithm": "PolylineAlgorithm.yml", + "PolylineAlgorithm.Encoding": "PolylineAlgorithm.Encoding.yml", + "PolylineAlgorithm.Encoding.IPolylineEncoding`1": "PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", + "PolylineAlgorithm.Encoding.IPolylineEncoding`1.Decode(System.String)": "PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", + "PolylineAlgorithm.Encoding.IPolylineEncoding`1.Encode(System.Collections.Generic.IEnumerable{`0})": "PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", + "PolylineAlgorithm.Encoding.PolylineEncoding": "PolylineAlgorithm.Encoding.PolylineEncoding.yml", + "PolylineAlgorithm.Encoding.PolylineEncoding.CreateResult(System.Double,System.Double)": "PolylineAlgorithm.Encoding.PolylineEncoding.yml", + "PolylineAlgorithm.Encoding.PolylineEncoding.GetCoordinate(System.ValueTuple{System.Double,System.Double})": "PolylineAlgorithm.Encoding.PolylineEncoding.yml", + "PolylineAlgorithm.Encoding.PolylineEncodingBase`1": "PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", + "PolylineAlgorithm.Encoding.PolylineEncodingBase`1.CreateResult(System.Double,System.Double)": "PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", + "PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Decode(System.String)": "PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", + "PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Encode(System.Collections.Generic.IEnumerable{`0})": "PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", + "PolylineAlgorithm.Encoding.PolylineEncodingBase`1.GetCoordinate(`0)": "PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", + "PolylineAlgorithm.PolylineAlgorithm": "PolylineAlgorithm.PolylineAlgorithm.yml", + "PolylineAlgorithm.PolylineAlgorithm.Decode(System.Char[])": "PolylineAlgorithm.PolylineAlgorithm.yml", + "PolylineAlgorithm.PolylineAlgorithm.Encode(System.Collections.Generic.IEnumerable{System.ValueTuple{System.Double,System.Double}})": "PolylineAlgorithm.PolylineAlgorithm.yml", + "PolylineAlgorithm.Validation": "PolylineAlgorithm.Validation.yml", + "PolylineAlgorithm.Validation.CoordinateValidator": "PolylineAlgorithm.Validation.CoordinateValidator.yml", + "PolylineAlgorithm.Validation.CoordinateValidator.IsValid(System.ValueTuple{System.Double,System.Double})": "PolylineAlgorithm.Validation.CoordinateValidator.yml", + "PolylineAlgorithm.Validation.CoordinateValidator.IsValidLatitude(System.Double)": "PolylineAlgorithm.Validation.CoordinateValidator.yml", + "PolylineAlgorithm.Validation.CoordinateValidator.IsValidLongitude(System.Double)": "PolylineAlgorithm.Validation.CoordinateValidator.yml" } \ No newline at end of file diff --git a/nuget/DropoutCoder.PolylineAlgorithm.nuspec b/nuget/PolylineAlgorithm.Extensions.nuspec similarity index 90% rename from nuget/DropoutCoder.PolylineAlgorithm.nuspec rename to nuget/PolylineAlgorithm.Extensions.nuspec index c3c66c81..84930295 100644 --- a/nuget/DropoutCoder.PolylineAlgorithm.nuspec +++ b/nuget/PolylineAlgorithm.Extensions.nuspec @@ -14,6 +14,6 @@ $nugetprojecturl$ - + \ No newline at end of file diff --git a/nuget/PolylineAlgorithm.nuspec b/nuget/PolylineAlgorithm.nuspec new file mode 100644 index 00000000..84930295 --- /dev/null +++ b/nuget/PolylineAlgorithm.nuspec @@ -0,0 +1,19 @@ + + + + $nugetid$ + $nugettitle$ + $nugetversion$ + $nugetauthors$ + $nugetowners$ + $nugetdescription$ + $nugetsummary$ + $nugetcopyright$ + $nugettags$ + $nugetlicenceurl$ + $nugetprojecturl$ + + + + + \ No newline at end of file diff --git a/src/DropoutCoder.PolylineAlgorithm.csproj b/src/DropoutCoder.PolylineAlgorithm.csproj deleted file mode 100644 index 9972fb37..00000000 --- a/src/DropoutCoder.PolylineAlgorithm.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - net8.0 - - - - - - - - - - - - - - - True - True - ExceptionMessageResource.resx - - - - - ResXFileCodeGenerator - ExceptionMessageResource.Designer.cs - - - \ No newline at end of file diff --git a/src/Encoding/IPolylineEncoding.cs b/src/Encoding/IPolylineEncoding.cs deleted file mode 100644 index 82348ac5..00000000 --- a/src/Encoding/IPolylineEncoding.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Encoding -{ - using System.Collections.Generic; - - #region Interfaces - - /// - /// Defines base interface for all polyline encodings - /// - /// Desired type used to decode to and encode from - public interface IPolylineEncoding - { - #region Methods - - /// - /// Method performs decoding from polyline encoded to - /// - /// Encoded coordinates - /// The - IEnumerable Decode(string source); - - /// - /// Method performs encoding from generic type to polyline encoded - /// - /// Coordinates to encode - /// Polyline encoded result - string Encode(IEnumerable source); - - #endregion - } - - #endregion -} diff --git a/src/Encoding/PolylineEncoding.cs b/src/Encoding/PolylineEncoding.cs deleted file mode 100644 index 058d56e5..00000000 --- a/src/Encoding/PolylineEncoding.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Encoding -{ - /// - /// Defines default polyline encoding with generic - /// - public class PolylineEncoding : PolylineEncodingBase<(double Latitude, double Longitude)> - { - #region Methods - - /// - /// Method creates result from passed latitude and longitude arguments - /// - /// Latitude value - /// Longitude value - /// Returns created instance of - protected override (double Latitude, double Longitude) CreateResult(double latitude, double longitude) - { - return (latitude, longitude); - } - - /// - /// Method creates - /// - /// The - /// Returns created coordinate - protected override (double Latitude, double Longitude) GetCoordinate((double Latitude, double Longitude) source) - { - return source; - } - - #endregion - } -} diff --git a/src/Encoding/PolylineEncodingBase.cs b/src/Encoding/PolylineEncodingBase.cs deleted file mode 100644 index b3de4cfb..00000000 --- a/src/Encoding/PolylineEncodingBase.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Encoding -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// /// - /// Defines base class for all polyline encodings - /// - /// - public abstract class PolylineEncodingBase : IPolylineEncoding - { - #region Methods - - /// - /// Method performs decode operation and coversion to desired type - /// - /// The - /// The - public IEnumerable Decode(string source) - { - if (string.IsNullOrEmpty(source)) - { - throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(source)); - } - - char[] polyline = source.ToCharArray(); - - return PolylineAlgorithm.Decode(polyline) - .Select(c => CreateResult(c.Latitude, c.Longitude)); - } - - /// - /// Method performs conversion to coordinate tuple and encode operation. - /// - /// The - /// The - public string Encode(IEnumerable source) - { - if (source == null || !source.Any()) - { - throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(source)); - } - - var coordinates = source.Select(s => GetCoordinate(s)); - - return PolylineAlgorithm.Encode(coordinates); - } - - /// - /// Method creates result from passed latitude and longitude arguments - /// - /// Latitude value - /// Longitude value - /// Returns created instance of - protected abstract T CreateResult(double latitude, double longitude); - - /// - /// The GetCoordinates - /// - /// The - /// The - protected abstract (double Latitude, double Longitude) GetCoordinate(T source); - - #endregion - } -} diff --git a/src/PolylineAlgorithm.cs b/src/PolylineAlgorithm.cs deleted file mode 100644 index 1f700191..00000000 --- a/src/PolylineAlgorithm.cs +++ /dev/null @@ -1,216 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm -{ - using DropoutCoder.PolylineAlgorithm.Validation; - using Microsoft.Extensions.ObjectPool; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// Performs polyline algorithm decoding and encoding - /// - public static class PolylineAlgorithm - { - private static readonly ObjectPool _pool = new DefaultObjectPoolProvider().CreateStringBuilderPool(5, 250); - - #region Methods - - /// - /// Method decodes polyline encoded representation to coordinates. - /// - /// Encoded polyline char array to decode - /// Returns coordinates. - /// If polyline argument is null -or- empty char array. - /// If polyline representation is not in correct format. - public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) - { - // Checking null and at least one character - if (polyline == null || !polyline.Any()) - { - throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(polyline)); - } - - // Initialize local variables - int index = 0; - int latitude = 0; - int longitude = 0; - - // Looping through encoded polyline char array - while (index < polyline.Length) - { - // Attempting to calculate next latitude value. If failed exception is thrown - if (!TryCalculateNext(polyline, ref index, ref latitude)) - { - throw new InvalidOperationException(ExceptionMessageResource.PolylineCharArrayIsMalformed); - } - - // Attempting to calculate next longitude value. If failed exception is thrown - if (!TryCalculateNext(polyline, ref index, ref longitude)) - { - throw new InvalidOperationException(ExceptionMessageResource.PolylineCharArrayIsMalformed); - } - - var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); - - if (!CoordinateValidator.IsValid(coordinate)) - { - throw new InvalidOperationException(ExceptionMessageResource.PolylineCharArrayIsMalformed); - } - - yield return coordinate; - } - } - - /// - /// Method encodes coordinates to polyline encoded representation - /// - /// Coordinates to encode - /// Polyline encoded representation - /// If coordinates parameter is null or empty enumerable - /// If one or more coordinate is out of range - public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - if (coordinates == null || !coordinates.Any()) - { - throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullOrEmpty, nameof(coordinates)); - } - - // Ensuring coordinates are valid, otherwise throws an aggregate exception - EnsureCoordinates(coordinates); - - // Initializing local variables - int previousLatitude = 0; - int previousLongitude = 0; - var sb = _pool.Get(); - - // Looping over coordinates and building encoded result - foreach (var coordinate in coordinates) - { - int latitude = GetIntegerRepresentation(coordinate.Latitude); - int longitude = GetIntegerRepresentation(coordinate.Longitude); - - sb.Append(GetEncodedCharacters(latitude - previousLatitude).ToArray()); - sb.Append(GetEncodedCharacters(longitude - previousLongitude).ToArray()); - - previousLatitude = latitude; - previousLongitude = longitude; - } - - var result = sb.ToString(); - - _pool.Return(sb); - - return result; - } - - /// - /// Method performs coordinates validation. Throws exception, if invalid coordinate is found - /// - /// Coordinates to validate - /// If one or more coordinate is out of range -or- invalid - private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - // Selecting invalid coordinates - var invalidCoordinates = coordinates - .Where(c => !CoordinateValidator.IsValid(c)); - - // If any invalid coordinates exists throw an aggregate exception with inner argument out of range exception - if (invalidCoordinates.Any()) - { - throw new AggregateException( - ExceptionMessageResource.AggregateExceptionCoordinatesAreInvalidErrorMessage, - invalidCoordinates - .Select(c => - new ArgumentOutOfRangeException( - string.Format( - ExceptionMessageResource.ArgumentExceptionCoordinateIsOutOfRangeErrorMessageFormat, - c.Latitude, - c.Longitude - ) - ) - ) - ); - } - } - - /// - /// - /// - /// Rounded integer representation of precise double value - /// Returns value with specific precision. See - private static double GetDoubleRepresentation(int value) - { - return Convert.ToDouble(value) / Constants.Precision; - } - - /// - /// Method converts value to polyline encoded characters - /// - /// Difference between current and previous latitude or longitude value - private static IEnumerable GetEncodedCharacters(int value) - { - int shifted = value << 1; - if (value < 0) - shifted = ~shifted; - - int rem = shifted; - - while (rem >= Constants.ASCII.Space) - { - yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); - - rem >>= Constants.ShiftLength; - } - - yield return (char)(rem + Constants.ASCII.QuestionMark); - } - - /// - /// Method - /// - /// Precise double representation - /// - private static int GetIntegerRepresentation(double value) - { - return (int)Math.Round(value * Constants.Precision); - } - - /// - /// Tries to calculate next integer representation of encoded polyline part - /// - /// The - /// The - /// The - /// The - private static bool TryCalculateNext(char[] polyline, ref int index, ref int value) - { - // Local variable initialization - int chunk; - int sum = 0; - int shifter = 0; - - - do - { - chunk = polyline[index++] - Constants.ASCII.QuestionMark; - sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; - shifter += Constants.ShiftLength; - } while (chunk >= Constants.ASCII.Space && index < polyline.Length); - - if (index >= polyline.Length && chunk >= Constants.ASCII.Space) - return false; - - value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; - - return true; - } - - #endregion - } -} diff --git a/src/PolylineAlgorithm/Coordinate.cs b/src/PolylineAlgorithm/Coordinate.cs new file mode 100644 index 00000000..2f4c0048 --- /dev/null +++ b/src/PolylineAlgorithm/Coordinate.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +[DebuggerDisplay(@"Latitude: {Latitude}, Longitude: {Longitude}")] +[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] +public readonly struct Coordinate : IEquatable { + /// + /// Initialized default instance of . + /// + public Coordinate() { + Latitude = 0d; + Longitude = 0d; + } + + public Coordinate(double latitude, double longitude) { + Latitude = latitude; + Longitude = longitude; + } + + public readonly double Latitude { get; } + public readonly double Longitude { get; } + + public bool IsDefault + => Latitude == default + && Longitude == default; + + public bool IsValid + => CoordinateValidator.Latitude.IsInRange(Latitude) + && CoordinateValidator.Longitude.IsInRange(Longitude); + + + public override bool Equals(object? obj) { + return obj is Coordinate coordinate && Equals(coordinate); + } + + public bool Equals(Coordinate other) { + return Latitude == other.Latitude && + Longitude == other.Longitude; + } + + public override int GetHashCode() { + return HashCode.Combine(Latitude, Longitude); + } + + public static bool operator ==(Coordinate left, Coordinate right) { + return left.Equals(right); + } + + public static bool operator !=(Coordinate left, Coordinate right) { + return !(left == right); + } +} diff --git a/src/PolylineAlgorithm/CoordinateRange.cs b/src/PolylineAlgorithm/CoordinateRange.cs new file mode 100644 index 00000000..49963cbf --- /dev/null +++ b/src/PolylineAlgorithm/CoordinateRange.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using System; +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] +public readonly struct CoordinateRange : IEquatable { + public CoordinateRange(double min, double max) { + if (min >= max) { + throw new ArgumentException(); + } + + Min = min; + Max = max; + } + public readonly double Min { get; } + public readonly double Max { get; } + + public override bool Equals(object? obj) { + return obj is CoordinateRange range && Equals(range); + } + + public bool Equals(CoordinateRange other) { + return Min == other.Min && + Max == other.Max; + } + + public override int GetHashCode() { + return HashCode.Combine(Min, Max); + } + + public bool IsInRange(double value) => value >= Min && value <= Max; + + public static bool operator ==(CoordinateRange left, CoordinateRange right) { + return left.Equals(right); + } + + public static bool operator !=(CoordinateRange left, CoordinateRange right) { + return !(left == right); + } +} diff --git a/src/PolylineAlgorithm/CoordinateValidator.cs b/src/PolylineAlgorithm/CoordinateValidator.cs new file mode 100644 index 00000000..b7e82c15 --- /dev/null +++ b/src/PolylineAlgorithm/CoordinateValidator.cs @@ -0,0 +1,11 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +public static class CoordinateValidator { + public static CoordinateRange Latitude { get; } = new CoordinateRange(-90d, 90d); + public static CoordinateRange Longitude { get; } = new CoordinateRange(-180d, 180d); +} diff --git a/src/PolylineAlgorithm/IPolylineDecoder.cs b/src/PolylineAlgorithm/IPolylineDecoder.cs new file mode 100644 index 00000000..b280b63b --- /dev/null +++ b/src/PolylineAlgorithm/IPolylineDecoder.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using System; +using System.Collections.Generic; + +/// +/// Defines method to decode a polyline. +/// +public interface IPolylineDecoder { + /// + /// Converts an encoded polyline to a set of coordinates. + /// + /// An encoded polyline to decode. + /// A set of coordinates. + IEnumerable Decode(ref readonly ReadOnlySpan polyline); +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/IPolylineEncoder.cs b/src/PolylineAlgorithm/IPolylineEncoder.cs new file mode 100644 index 00000000..ed6a024d --- /dev/null +++ b/src/PolylineAlgorithm/IPolylineEncoder.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using System; +using System.Collections.Generic; + +/// +/// Defines method to encode a set of coordinates. +/// +public interface IPolylineEncoder { + /// + /// Converts a set of coordinates to an encoded polyline. + /// + /// A set of coordinates to encode. + /// An encoded polyline. + ReadOnlySpan Encode(IEnumerable coordinates); +} \ No newline at end of file diff --git a/src/Constants.cs b/src/PolylineAlgorithm/Internal/Constants.cs similarity index 76% rename from src/Constants.cs rename to src/PolylineAlgorithm/Internal/Constants.cs index b05a5029..f8babb76 100644 --- a/src/Constants.cs +++ b/src/PolylineAlgorithm/Internal/Constants.cs @@ -1,17 +1,13 @@ // -// Copyright (c) Petr Å rámek. All rights reserved. +// Copyright (c) Pete Sramek. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // -namespace DropoutCoder.PolylineAlgorithm -{ +namespace PolylineAlgorithm.Internal { /// /// Defines global constant values /// - internal static class Constants - { - #region Constants - + internal static class Constants { /// /// Defines the coordinate precision /// @@ -22,15 +18,10 @@ internal static class Constants /// public const int ShiftLength = 5; - #endregion - /// /// Defines ASCII characters constant values /// - internal static class ASCII - { - #region Constants - + internal static class ASCII { /// /// Defines the ASCII Question Mark /// @@ -45,17 +36,12 @@ internal static class ASCII /// Defines the ASCII Unit Separator /// public const int UnitSeparator = 31; - - #endregion } /// /// Defines coordinates constant values /// - internal static class Coordinate - { - #region Constants - + internal static class Coordinate { /// /// Defines the maximum value for latitude /// @@ -67,16 +53,14 @@ internal static class Coordinate public const int MaxLongitude = 180; /// - /// Defines the minimum value for latitude + /// Defines the maximum value for latitude /// public const int MinLatitude = -MaxLatitude; /// - /// Defines the minimum value for longitude + /// Defines the maximum value for longitude /// public const int MinLongitude = -MaxLongitude; - - #endregion } } } diff --git a/src/ExceptionMessageResource.Designer.cs b/src/PolylineAlgorithm/Internal/ExceptionMessageResource.Designer.cs similarity index 72% rename from src/ExceptionMessageResource.Designer.cs rename to src/PolylineAlgorithm/Internal/ExceptionMessageResource.Designer.cs index 63d7a666..cb483916 100644 --- a/src/ExceptionMessageResource.Designer.cs +++ b/src/PolylineAlgorithm/Internal/ExceptionMessageResource.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace DropoutCoder.PolylineAlgorithm { +namespace PolylineAlgorithm.Internal { using System; @@ -19,7 +19,7 @@ namespace DropoutCoder.PolylineAlgorithm { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ExceptionMessageResource { @@ -39,7 +39,7 @@ internal ExceptionMessageResource() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DropoutCoder.PolylineAlgorithm.ExceptionMessageResource", typeof(ExceptionMessageResource).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PolylineAlgorithm.Internal.ExceptionMessageResource", typeof(ExceptionMessageResource).Assembly); resourceMan = temp; } return resourceMan; @@ -70,29 +70,38 @@ internal static string AggregateExceptionCoordinatesAreInvalidErrorMessage { } /// - /// Looks up a localized string similar to Argument cannot be null -or- empty char array.. + /// Looks up a localized string similar to Argument cannot be an empty enumerable.. /// - internal static string ArgumentCannotBeNullOrEmpty { + internal static string ArgumentCannotBeEmptyEnumerable { get { - return ResourceManager.GetString("ArgumentCannotBeNullOrEmpty", resourceCulture); + return ResourceManager.GetString("ArgumentCannotBeEmptyEnumerable", resourceCulture); } } /// - /// Looks up a localized string similar to Latitude: {0}, Longitude: {1}) is invalid. Latitude must be in range between - 90 and +90. Longitude must be in range between -180 and +180.. + /// Looks up a localized string similar to Argument cannot be null -or- empty -or- whitespace string.. /// - internal static string ArgumentExceptionCoordinateIsOutOfRangeErrorMessageFormat { + internal static string ArgumentCannotBeNullEmptyOrWhitespace { get { - return ResourceManager.GetString("ArgumentExceptionCoordinateIsOutOfRangeErrorMessageFormat", resourceCulture); + return ResourceManager.GetString("ArgumentCannotBeNullEmptyOrWhitespace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Latitude {0} or longitude {1} is not valid. Latitude must be in range between -90 and +90. Longitude must be in range between -180 and +180.. + /// + internal static string CoordinateValidationExceptionCoordinateIsOutOfRangeErrorMessageFormat { + get { + return ResourceManager.GetString("CoordinateValidationExceptionCoordinateIsOutOfRangeErrorMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Polyline is malformed.. /// - internal static string PolylineCharArrayIsMalformed { + internal static string PolylineStringIsMalformed { get { - return ResourceManager.GetString("PolylineCharArrayIsMalformed", resourceCulture); + return ResourceManager.GetString("PolylineStringIsMalformed", resourceCulture); } } } diff --git a/src/ExceptionMessageResource.resx b/src/PolylineAlgorithm/Internal/ExceptionMessageResource.resx similarity index 91% rename from src/ExceptionMessageResource.resx rename to src/PolylineAlgorithm/Internal/ExceptionMessageResource.resx index 24b0415f..4ed158c8 100644 --- a/src/ExceptionMessageResource.resx +++ b/src/PolylineAlgorithm/Internal/ExceptionMessageResource.resx @@ -120,13 +120,16 @@ One or more coordinates are invalid. Check InnerExceptions property for details. - - Argument cannot be null -or- empty char array. + + Argument cannot be null -or- empty -or- whitespace string. - - Latitude: {0}, Longitude: {1}) is invalid. Latitude must be in range between - 90 and +90. Longitude must be in range between -180 and +180. + + Argument cannot be an empty enumerable. - + + Latitude {0} or longitude {1} is not valid. Latitude must be in range between -90 and +90. Longitude must be in range between -180 and +180. + + Polyline is malformed. \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/PolylineReader.cs b/src/PolylineAlgorithm/Internal/PolylineReader.cs new file mode 100644 index 00000000..2639b317 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/PolylineReader.cs @@ -0,0 +1,83 @@ +namespace PolylineAlgorithm.Internal; + +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Auto)] +internal ref struct PolylineReader { + private ReaderState _state = new(); + private ReadOnlySpan _polyline; + + public PolylineReader(ref readonly ReadOnlySpan polyline) { + _polyline = polyline; + } + + public readonly bool CanRead => _state.Position < _polyline.Length; + + public Coordinate Read() { + int latitude = _state.Latitude; + int longitude = _state.Longitude; + + DecodeNext(ref latitude, ref _polyline, ref _state); + DecodeNext(ref longitude, ref _polyline, ref _state); + + _state.SetLatitude(in latitude); + _state.SetLongitude(in longitude); + + Coordinate coordinate = new(Precise(ref latitude), Precise(ref longitude)); + + if (!coordinate.IsValid) { + throw new InvalidCoordinateException(coordinate); + } + + return coordinate; + + static double Precise(ref int value) { + return Convert.ToDouble(value) / Constants.Precision; + } + } + + static void DecodeNext(ref int value, ref ReadOnlySpan polyline, ref ReaderState state) { + // Initialize local variables + int chunk; + int sum = 0; + int shifter = 0; + + do { + chunk = polyline[state.Position] - Constants.ASCII.QuestionMark; + sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; + shifter += Constants.ShiftLength; + state.Advance(); + } while (chunk >= Constants.ASCII.Space && state.Position < polyline.Length); + + if (state.Position >= polyline.Length && chunk >= Constants.ASCII.Space) { + throw new InvalidOperationException(ExceptionMessageResource.PolylineStringIsMalformed); + } + + value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 12)] + private ref struct ReaderState { + public ReaderState() { + Position = Latitude = Longitude = 0; + } + + internal int Position { get; private set; } + + internal int Latitude { get; private set; } + + internal int Longitude { get; private set; } + + internal void Advance() { + Position += 1; + } + + internal void SetLatitude(ref readonly int latitude) { + Latitude = latitude; + } + + internal void SetLongitude(ref readonly int longitude) { + Longitude = longitude; + } + } +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/Internal/PolylineWriter.cs b/src/PolylineAlgorithm/Internal/PolylineWriter.cs new file mode 100644 index 00000000..9bccc521 --- /dev/null +++ b/src/PolylineAlgorithm/Internal/PolylineWriter.cs @@ -0,0 +1,77 @@ +namespace PolylineAlgorithm.Internal; + +using System; +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Auto)] +internal ref struct PolylineWriter(ref readonly Span buffer) { + private WriterState _state = new(); + private Span _buffer = buffer; + + public void Write(ref readonly Coordinate coordinate) { + var latitude = Round(coordinate.Latitude); + var longitude = Round(coordinate.Longitude); + + var latDiff = latitude - _state.ExchangeLatitude(ref latitude); + var longDiff = longitude - _state.ExchangeLongitude(ref longitude); + + EncodeNext(ref latDiff, ref _buffer, ref _state); + EncodeNext(ref longDiff, ref _buffer, ref _state); + + static int Round(double value) { + return Convert.ToInt32(Math.Round(value * Constants.Precision)); + } + } + + static void EncodeNext(ref int value, ref Span buffer, ref WriterState state) { + int shifted = value << 1; + + if (value < 0) { + shifted = ~shifted; + } + + int rem = shifted; + + while (rem >= Constants.ASCII.Space) { + buffer[state.Position] = Convert.ToChar((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); + state.Advance(); + rem >>= Constants.ShiftLength; + } + + buffer[state.Position] = Convert.ToChar(rem + Constants.ASCII.QuestionMark); + state.Advance(); + } + + public override readonly string ToString() { + return _buffer[.._state.Position].ToString(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 12)] + private ref struct WriterState { + public WriterState() { + Position = Latitude = Longitude = 0; + } + + internal int Position { get; private set; } + + internal int Latitude { get; private set; } + + internal int Longitude { get; private set; } + + internal void Advance() { + Position += 1; + } + + internal int ExchangeLatitude(ref int latitude) { + var current = Latitude; + Latitude = latitude; + return current; + } + + internal int ExchangeLongitude(ref int longitude) { + var current = Longitude; + Longitude = longitude; + return current; + } + } +} diff --git a/src/PolylineAlgorithm/InvalidCoordinateException.cs b/src/PolylineAlgorithm/InvalidCoordinateException.cs new file mode 100644 index 00000000..601521c4 --- /dev/null +++ b/src/PolylineAlgorithm/InvalidCoordinateException.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Internal; +using System.Diagnostics.CodeAnalysis; + +/// +/// Represents error that is caused by invalid coordinate. +/// +[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Main purpose is to report coordinate that is invalid, thus we have to have only one construtor.")] +public sealed class InvalidCoordinateException : Exception { + public InvalidCoordinateException(Coordinate coordinate, Exception? innerException = null) + : base(string.Format(ExceptionMessageResource.CoordinateValidationExceptionCoordinateIsOutOfRangeErrorMessageFormat, coordinate.Latitude, coordinate.Longitude), + innerException) + { + Coordinate = coordinate; + } + + /// + /// Coordinate that caused the exception. + /// + public Coordinate Coordinate { get; } +} diff --git a/src/PolylineAlgorithm/MalformedPolylineException.cs b/src/PolylineAlgorithm/MalformedPolylineException.cs new file mode 100644 index 00000000..0231a3a8 --- /dev/null +++ b/src/PolylineAlgorithm/MalformedPolylineException.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Internal; +using System; +using System.Diagnostics.CodeAnalysis; + +/// +/// Represents error that occurs during polyline encoding. +/// +[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Main purpose is to report position in which failure occurs, thus we have to have only one construtor.")] +public sealed class MalformedPolylineException : Exception { + /// + /// Initializes an instance + /// + /// + /// + public MalformedPolylineException(int position, Exception? innerException = null) + : base(string.Format(ExceptionMessageResource.PolylineStringIsMalformed, position), + innerException) + { + Position = position; + } + + /// + /// Position in polyline string at which error occurs. + /// + public int Position { get; } +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineAlgorithm.csproj b/src/PolylineAlgorithm/PolylineAlgorithm.csproj new file mode 100644 index 00000000..e2c54c2b --- /dev/null +++ b/src/PolylineAlgorithm/PolylineAlgorithm.csproj @@ -0,0 +1,50 @@ + + + + netstandard2.1 + 13.0 + enable + enable + true + en + + + + All + latest + true + true + true + + + + + True + True + ExceptionMessageResource.resx + + + + + + ResXFileCodeGenerator + ExceptionMessageResource.Designer.cs + + + + + + <_Parameter1>PolylineAlgorithm.Tests + + + <_Parameter1>PolylineAlgorithm.Benchmarks + + + + + + <_Parameter1>PolylineAlgorithm.Implementation.Benchmarks + + + + \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineDecoder.cs b/src/PolylineAlgorithm/PolylineDecoder.cs new file mode 100644 index 00000000..1bb83ef8 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineDecoder.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Internal; + +/// +/// A coordinate validator. +public class PolylineDecoder : IPolylineDecoder { + + /// + /// Thrown when argument is null -or- empty. + /// Thrown when is not in correct format. + public IEnumerable Decode(ref readonly ReadOnlySpan polyline) { + // Checking null and at least one character + if (polyline.IsEmpty) { + throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeNullEmptyOrWhitespace, nameof(polyline)); + } + + // Initialize local variables + int capacity = polyline.Length / 9; + var result = new List(capacity); + + PolylineReader reader = new(in polyline); + + // Looping through encoded polyline char array + while (reader.CanRead) { + var coordinate = reader.Read(); + + result.Add(coordinate); + } + + return result; + } +} \ No newline at end of file diff --git a/src/PolylineAlgorithm/PolylineEncoder.cs b/src/PolylineAlgorithm/PolylineEncoder.cs new file mode 100644 index 00000000..ebfddd81 --- /dev/null +++ b/src/PolylineAlgorithm/PolylineEncoder.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Internal; +using System; +using System.Collections.Generic; + +/// +/// Performs polyline algorithm decoding and encoding +/// +public class PolylineEncoder : IPolylineEncoder { + /// + /// Encodes coordinates to polyline representation + /// + /// Coordinates to encode + /// Polyline encoded representation + /// If coordinates parameter is null + /// If coordinates parameter is empty + /// If one or more coordinate is out of valid range + public ReadOnlySpan Encode(IEnumerable coordinates) { + if (coordinates is null) { + throw new ArgumentNullException(nameof(coordinates)); + } + + int count = GetCount(ref coordinates); + + if (count == 0) { + throw new ArgumentException(ExceptionMessageResource.ArgumentCannotBeEmptyEnumerable, nameof(coordinates)); + } + + // Initializing local variables + int capacity = count * 12; + Span buffer = new char[capacity]; + PolylineWriter writer = new(in buffer); + + // Looping over coordinates and building encoded result + foreach (var coordinate in coordinates) { + if(!coordinate.IsValid) { + throw new InvalidCoordinateException(coordinate); + } + + writer.Write(in coordinate); + } + + return writer.ToString(); + } + + static int GetCount(ref IEnumerable coordinates) => coordinates switch { + ICollection collection => collection.Count, + _ => coordinates.Count(), + }; +} diff --git a/src/PolylineAlgorithm/Validation/CoordinateRange.cs b/src/PolylineAlgorithm/Validation/CoordinateRange.cs new file mode 100644 index 00000000..5e8f6788 --- /dev/null +++ b/src/PolylineAlgorithm/Validation/CoordinateRange.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Validation; + +using PolylineAlgorithm.Internal; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +/// +/// Represents a range within coordinate value, latitude or longitude, is considered valid. +/// +[DebuggerDisplay(@"{ Min: {Min}, Max: {Max} }")] +[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] +public readonly struct CoordinateRange : IEquatable { + public CoordinateRange(double min, double max) { + if (min >= max) { + throw new ArgumentException(string.Format("", min.ToString(), max.ToString()), paramName: nameof(min)); + } + + Min = min; + Max = max; + } + + /// + /// Represents inclusive minimal value of the range. + /// + public readonly double Min { get; } + + /// + /// Represents inclusive maximal value of the range. + /// + public readonly double Max { get; } + + /// + /// Indicates whether the is within the range. + /// + /// A value to be validated is in range. + /// if is within the range; otherwise, . + public bool IsInRange(double value) => value >= Min && value <= Max; + + /// + /// Returns the formatted string respresentation of this instance. + /// + /// The formatted string respresentation of this instance. + /// { Min: [double], Max: [double] } + public override string ToString() { + return $"{{ {nameof(Min)}: {Min}, {nameof(Max)}: {Max} }}"; + } + + /// + public override bool Equals(object? obj) { + return obj is CoordinateRange range && Equals(range); + } + + /// + public bool Equals(CoordinateRange other) { + return Min == other.Min && + Max == other.Max; + } + + /// + public override int GetHashCode() { + return HashCode.Combine(Min, Max); + } + + public static bool operator ==(CoordinateRange left, CoordinateRange right) { + return left.Equals(right); + } + + public static bool operator !=(CoordinateRange left, CoordinateRange right) { + return !(left == right); + } +} diff --git a/src/PolylineAlgorithm/Validation/CoordinateValidator.cs b/src/PolylineAlgorithm/Validation/CoordinateValidator.cs new file mode 100644 index 00000000..c91111bf --- /dev/null +++ b/src/PolylineAlgorithm/Validation/CoordinateValidator.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Validation; + +using PolylineAlgorithm.Internal; +using System.Diagnostics.CodeAnalysis; + +/// +/// +/// +public sealed class CoordinateValidator { + /// + /// Represents default coordinate validator. This field is read-only. + /// + /// Validates latitude between -90 and 90; longitude between -180 and 180. + [SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "We just want to deal with it this way.")] + public static readonly CoordinateValidator Default = new(new CoordinateRange(Constants.Coordinate.MinLatitude, Constants.Coordinate.MaxLatitude), new CoordinateRange(Constants.Coordinate.MinLongitude, Constants.Coordinate.MaxLongitude)); + + /// + /// Initializes an instance of coordinate validator. + /// + /// A latitude range. + /// A longitude range. + public CoordinateValidator(CoordinateRange latitudeRange, CoordinateRange longitudeRange) { + Latitude = latitudeRange; + Longitude = longitudeRange; + } + + /// + /// A latitude validation range. + /// + public CoordinateRange Latitude { get; } + + /// + /// A longitude validation range. + /// + public CoordinateRange Longitude { get; } +} diff --git a/src/Validation/CoordinateValidator.cs b/src/Validation/CoordinateValidator.cs deleted file mode 100644 index f0cd2b69..00000000 --- a/src/Validation/CoordinateValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Validation -{ - /// - /// Performs coordinate validation - /// - public static class CoordinateValidator - { - #region Methods - - /// - /// Performs coordinate validation - /// - /// Coordinate to validate - /// Returns validation result. If valid then true, otherwise false. - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - /// - /// Performs latitude validation - /// - /// Latitude value to validate - /// Returns validation result. If valid then true, otherwise false. - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - /// - /// Performs longitude validation - /// - /// Longitude value to validate - /// Returns validation result. If valid then true, otherwise false. - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - - #endregion - } -} diff --git a/tests/Defaults.cs b/tests/Defaults.cs deleted file mode 100644 index bc54f777..00000000 --- a/tests/Defaults.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Defines default values and objects used for testing purposes - /// - public static class Defaults - { - /// - /// Defines default decoded values and objects udÅ›ed for testing purposes - /// - public static class Coordinate - { - #region Fields - - /// - /// Defines empty range of coordinates. Equals to decoded - /// - public static readonly IEnumerable<(double Latitude, double Longitude)> Empty = Enumerable.Empty<(double Latitude, double Longitude)>(); - - /// - /// Defines range of invalid coordinates. Equals to decoded - /// - public static readonly IEnumerable<(double Latitude, double Longitude)> Invalid = new[] { - (149.47383, 259.06250), - (-158.37407, 225.31250), - (152.99363, -220.93750), - (-144.49024, -274.37500) - }; - - /// - /// Defines range of valid coordinates. Equals to decoded - /// - public static readonly IEnumerable<(double Latitude, double Longitude)> Valid = new[] { - (49.47383, 59.06250), - (-58.37407, 25.31250), - (52.99363, -120.93750), - (-44.49024, -174.37500) - }; - - #endregion - } - - /// - /// Defines default encoded values and objects udÅ›ed for testing purposes - /// - public static class Polyline - { - #region Fields - - /// - /// Defines empty string of polyline encoded coordinates. Equals to encoded - /// - public static readonly string Empty = String.Empty; - - /// - /// Defines polyline encoded range of invalid coordinates. Equals to encoded - /// - public static readonly string Invalid = "mnc~Qsm_ja@"; - - /// - /// Defines polyline encoded range of valid coordinates. Equals to encoded - /// - public static readonly string Valid = "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI"; - - #endregion - } - } -} diff --git a/tests/DropoutCoder.PolylineAlgorithm.Tests.csproj b/tests/DropoutCoder.PolylineAlgorithm.Tests.csproj deleted file mode 100644 index eba33c6a..00000000 --- a/tests/DropoutCoder.PolylineAlgorithm.Tests.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/Encoding/PolylineEncodingBaseTest.cs b/tests/Encoding/PolylineEncodingBaseTest.cs deleted file mode 100644 index 94993c83..00000000 --- a/tests/Encoding/PolylineEncodingBaseTest.cs +++ /dev/null @@ -1,196 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Tests.Encoding -{ - using DropoutCoder.PolylineAlgorithm.Encoding; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Defines the - /// - [TestClass] - [TestCategory(nameof(PolylineEncodingBase<(double latitude, double longitude)>))] - public class PolylineEncodingBaseTest : PolylineEncodingBase<(double latitude, double longitude)> - { - #region Methods - - /// - /// The Decode_NullInput - /// - [TestMethod] - public void Decode_NullInput_ThrowsException() - { - // Arrange - var nullPolylineString = (string)null; - - // Act - void DecodeNullPolylineString() - { - this.Decode(nullPolylineString).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeNullPolylineString()); - } - - /// - /// The Decode_EmptyInput - /// - [TestMethod] - public void Decode_EmptyInput_ThrowsException() - { - // Arrange - var emptyPolylineString = Defaults.Polyline.Empty; - - // Act - void DecodeEmptyPolylineString() - { - this.Decode(emptyPolylineString).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeEmptyPolylineString()); - } - - /// - /// The Decode_InvalidInput - /// - [TestMethod] - public void Decode_InvalidInput_ThrowsException() - { - // Arrange - var invalidPolylineString = Defaults.Polyline.Invalid; - - // Act - void DecodeInvalidPolylineString() - { - this.Decode(Defaults.Polyline.Invalid).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeInvalidPolylineString()); - } - - /// - /// The Decode_ValidInput - /// - [TestMethod] - public void Decode_ValidInput_AreEquivalent() - { - // Arrange - var validPolylineString = Defaults.Polyline.Valid; - - // Act - var result = this.Decode(validPolylineString).ToArray(); - - // Assert - CollectionAssert.AreEquivalent(Defaults.Coordinate.Valid.ToList(), result.ToList()); - } - - /// - /// The Encode_NullInput - /// - [TestMethod] - public void Encode_NullInput_ThrowsException() - { - // Arrange - var nullCoordinates = (IEnumerable<(double, double)>)null; - - // Act - void EncodeNullCoordinateCollection() - { - this.Encode(nullCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeNullCoordinateCollection()); - } - - /// - /// The Encode_EmptyInput - /// - [TestMethod] - public void Encode_EmptyInput_ThrowsException() - { - // Arrange - var emptyCoordinates = Defaults.Coordinate.Empty; - - // Act - void EncodeEmptyCoordinateCollection() - { - this.Encode(emptyCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeEmptyCoordinateCollection()); - } - - /// - /// The Encode_InvalidInput - /// - [TestMethod] - public void Encode_InvalidInput_ThrowsException() - { - // Arrange - var invalidCoordinates = Defaults.Coordinate.Invalid; - - // Act - void EncodeInvalidCoordinateCollection() - { - this.Encode(invalidCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeInvalidCoordinateCollection()); - } - - /// - /// The Encode_ValidInput - /// - [TestMethod] - public void Encode_ValidInput_AreEqual() - { - // Arrange - var validCoordinateCollection = Defaults.Coordinate.Valid; - - // Act - var result = this.Encode(validCoordinateCollection); - - // Assert - Assert.AreEqual(Defaults.Polyline.Valid, result); - } - - #region Overriden methods - - /// - /// The CreateResult - /// - /// The - /// The - /// The - protected override (double latitude, double longitude) CreateResult(double latitude, double longitude) - { - return (latitude, longitude); - } - - /// - /// The GetCoordinate - /// - /// The - /// The - protected override (double Latitude, double Longitude) GetCoordinate((double latitude, double longitude) source) - { - return source; - } - - #endregion - - #endregion - } -} diff --git a/tests/Encoding/PolylineEncodingTest.cs b/tests/Encoding/PolylineEncodingTest.cs deleted file mode 100644 index d7fdf05f..00000000 --- a/tests/Encoding/PolylineEncodingTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Tests.Encoding -{ - using DropoutCoder.PolylineAlgorithm.Encoding; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using System.Linq; - - /// - /// Defines the - /// - [TestClass] - [TestCategory(nameof(PolylineEncoding))] - public class PolylineEncodingTest : PolylineEncoding - { - #region Methods - - /// - /// The CreateResult_AreEqual - /// - [TestMethod] - public void CreateResult_AreEqual() - { - // Arrange - var validCoordinate = Defaults.Coordinate.Valid.First(); - - // Act - var result = this.CreateResult(validCoordinate.Latitude, validCoordinate.Longitude); - - // Assert - Assert.AreEqual(validCoordinate, result); - } - - /// - /// The GetCoordinate_AreEqual - /// - [TestMethod] - public void GetCoordinate_AreEqual() - { - // Arrange - var validCoordinate = Defaults.Coordinate.Valid.First(); - - // Act - var result = this.GetCoordinate(validCoordinate); - - // Assert - Assert.AreEqual(validCoordinate, result); - } - - #endregion - } -} diff --git a/tests/PolylineAlgorithm.Tests/GlobalSuppressions.cs b/tests/PolylineAlgorithm.Tests/GlobalSuppressions.cs new file mode 100644 index 00000000..b1c53da8 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// +// Copyright (c) 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: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Ignore in test asemblies.")] diff --git a/tests/PolylineAlgorithm.Tests/GlobalUsings.cs b/tests/PolylineAlgorithm.Tests/GlobalUsings.cs new file mode 100644 index 00000000..bdba132f --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/GlobalUsings.cs @@ -0,0 +1,6 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/Internal/Category.cs b/tests/PolylineAlgorithm.Tests/Internal/Category.cs new file mode 100644 index 00000000..dd6daccc --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Category.cs @@ -0,0 +1,4 @@ +namespace PolylineAlgorithm.Tests.Internal; +internal static class Category { + public const string Unit = nameof(Unit); +} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Defaults.cs b/tests/PolylineAlgorithm.Tests/Internal/Defaults.cs new file mode 100644 index 00000000..38726128 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Internal/Defaults.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) 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 System.Collections.Generic; + +/// +/// Defines default values and objects used for testing purposes +/// +internal static class Defaults { + internal static readonly Random R = new(DateTime.Now.Millisecond); + + /// + /// Defines default decoded values and objects udÅ›ed for testing purposes + /// + public static class Coordinates { + /// + /// Defines empty range of coordinates. Equals to decoded + /// + public static readonly IEnumerable Empty = []; + + /// + /// Defines range of invalid coordinates. Equals to decoded + /// + public static readonly IEnumerable Invalid = [ + new(149.47383, 259.06250), + new(-158.37407, 225.31250), + new(152.99363, -220.93750), + new(-144.49024, -274.37500) + ]; + + /// + /// Defines range of valid coordinates. Equals to decoded + /// + public static readonly IEnumerable Valid = [ + new(49.47383, 59.06250), + new(-58.37407, 25.31250), + new(52.99363, -120.93750), + new(-44.49024, -174.37500) + ]; + } + + /// + /// Defines default encoded values and objects udÅ›ed for testing purposes + /// + public static class Polyline { + /// + /// Defines empty string of polyline encoded coordinates. Equals to encoded + /// + public static readonly string Empty = string.Empty; + + /// + /// Defines polyline encoded range of invalid coordinates. Equals to encoded + /// + public static readonly string Invalid = "mnc~Qsm_ja@"; + + /// + /// Defines polyline encoded range of valid coordinates. Equals to encoded + /// + public static readonly string Valid = "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI"; + } + + public static class MalformedPolylineException { + public static readonly int Position = R.Next(); + } + + public static class InvalidCoordinateException { + public static readonly Coordinate Coordinate = new(R.NextDouble(), R.NextDouble()); + } +} diff --git a/tests/PolylineAlgorithm.Tests/InvalidCoordinateExceptionTest.cs b/tests/PolylineAlgorithm.Tests/InvalidCoordinateExceptionTest.cs new file mode 100644 index 00000000..a8cff501 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/InvalidCoordinateExceptionTest.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) 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.Internal; + +/// +/// Performs tests for type. +/// +[TestClass] +public class InvalidCoordinateExceptionTest { + + /// + /// Method is testing constructor. and are passed as arguments. + /// + /// Expected: equals passed argument, + /// equals passed argument, + /// and in not , empty -or- whitespace. + [TestMethod] + public void Constructor_Ok() { + // Arrange + var coordinate = Defaults.InvalidCoordinateException.Coordinate; + var innerException = new Exception(); + + // Act + InvalidCoordinateException result = new(coordinate, innerException); + + // Assert + Assert.AreEqual(coordinate, result.Coordinate); + Assert.AreEqual(innerException, result.InnerException); + Assert.IsFalse(string.IsNullOrWhiteSpace(result.Message)); + } +} diff --git a/tests/PolylineAlgorithm.Tests/MalformedPolylineExceptionTest.cs b/tests/PolylineAlgorithm.Tests/MalformedPolylineExceptionTest.cs new file mode 100644 index 00000000..b2f119bc --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/MalformedPolylineExceptionTest.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) 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.Internal; + +/// +/// Defines tests for type. +/// +[TestClass] +public class MalformedPolylineExceptionTest { + [TestMethod] + public void Constructor_Ok() { + // Arrange + var position = Defaults.MalformedPolylineException.Position; + var innerException = new Exception(); + + // Act + MalformedPolylineException exception = new(position, innerException); + + // Assert + Assert.AreEqual(Defaults.MalformedPolylineException.Position, exception.Position); + Assert.IsFalse(string.IsNullOrWhiteSpace(exception.Message)); + Assert.IsNotNull(exception.InnerException); + } +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj new file mode 100644 index 00000000..a3d8adb5 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj @@ -0,0 +1,37 @@ + + + + net9.0 + 13.0 + enable + enable + true + + + + false + true + Default + + + + true + true + + + + All + latest + true + false + + + + + + + + + + + \ No newline at end of file diff --git a/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs b/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs new file mode 100644 index 00000000..47090b0f --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineDecoderTest.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) 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.Tests.Internal; + +/// +/// Defines tests for type. +/// +[TestClass] +public class PolylineDecoderTest { + public PolylineDecoder Decoder = new PolylineDecoder(); + + /// + /// Method is testing method. Empty is passed as an argument. + /// + /// Expected to throw . + [TestMethod] + public void Decode_EmptyInput_ThrowsException() { + // Arrange + ReadOnlyMemory empty = Defaults.Polyline.Empty.AsMemory(); + + // Act + void Execute(ReadOnlySpan value) => Decoder.Decode(in value); + + // Assert + Assert.ThrowsException(() => Execute(empty.Span)); + } + + /// + /// Method is testing method. containing invalid polyline is passed as an argument. + /// + /// Expected to throw . + [TestMethod] + public void Decode_InvalidInput_ThrowsException() { + // Arrange + ReadOnlyMemory invalid = Defaults.Polyline.Invalid.AsMemory(); + + // Act + void Execute(ReadOnlySpan value) => Decoder.Decode(in value); + + // Assert + Assert.ThrowsException(() => Execute(invalid.Span)); + } + + /// + /// Method is testing method. containing valid polyline is passed as an argument. + /// + /// Expected result to equal .. + [TestMethod] + public void Decode_ValidInput_Ok() { + // Arrange + ReadOnlySpan valid = Defaults.Polyline.Valid; + + // Act + var result = Decoder.Decode(in valid); + + // Assert + CollectionAssert.AreEqual(Defaults.Coordinates.Valid.ToArray(), result.ToArray()); + } +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs b/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs new file mode 100644 index 00000000..faeaf624 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/PolylineEncoderTest.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 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.Internal; + +/// +/// Defines tests for type. +/// +[TestClass] +public class PolylineEncoderTest { + /// + /// Subject under test. + /// + public PolylineEncoder Encoder = new(); + + /// + /// Method is testing method. is passed as parameter. + /// Expected result is . + /// + [TestMethod] + public void Encode_NullInput_ThrowsException() { + // Arrange + IEnumerable @null = (IEnumerable)null!; + + // Act + void EncodeNullCoordinates() { + Encoder.Encode(@null); + } + + // Assert + Assert.ThrowsException(() => EncodeNullCoordinates()); + } + + /// + /// Method is testing method. Empty enumeration is passed as parameter. + /// Expected result is . + /// + [TestMethod] + public void Encode_EmptyInput_ThrowsException() { + // Arrange + IEnumerable empty = Defaults.Coordinates.Empty; + + // Act + void EncodeEmptyCoordinates() { + Encoder.Encode(empty); + } + + // Assert + Assert.ThrowsException(() => EncodeEmptyCoordinates()); + } + + /// + /// Method is testing method. Enumeration containing only invalid values is passed as parameter. + /// Expected result is . + /// + [TestMethod] + public void Encode_InvalidInput_ThrowsException() { + // Arrange + IEnumerable invalid = Defaults.Coordinates.Invalid; + + // Act + void EncodeEmptyCoordinates() { + Encoder.Encode(invalid); + } + + // Assert + Assert.ThrowsException(() => EncodeEmptyCoordinates()); + } + + /// + /// Method is testing method. Enumeration containing only valid values is passed as parameter. + /// Expected result is result and are equal. + /// + [TestMethod] + public void Encode_ValidInput_Ok() { + // Arrange + IEnumerable valid = Defaults.Coordinates.Valid; + + // Act + var result = Encoder.Encode(valid); + + // Assert + Assert.AreEqual(Defaults.Polyline.Valid, result.ToString()); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Properties/AssemblyInfo.cs b/tests/PolylineAlgorithm.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..91d8cbeb --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// +// Copyright (c) Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +using PolylineAlgorithm.Tests.Internal; + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +[assembly: TestCategory(Category.Unit)] \ No newline at end of file diff --git a/tests/PolylineAlgorithmTest.cs b/tests/PolylineAlgorithmTest.cs deleted file mode 100644 index bb1bbce8..00000000 --- a/tests/PolylineAlgorithmTest.cs +++ /dev/null @@ -1,177 +0,0 @@ -// -// Copyright (c) Petr Šrámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Tests -{ - using DropoutCoder.PolylineAlgorithm; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Defines the - /// - [TestClass] - [TestCategory(nameof(PolylineAlgorithm))] - public class PolylineAlgorithmTest - { - #region Methods - - /// - /// Method is testing method. Empty [] is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Decode_EmptyInput_ThrowsException() - { - // Arrange - var emptyPolylineCharArray = Defaults.Polyline.Empty.ToCharArray(); - - // Act - void DecodeEmptyPolylineCharArray() - { - PolylineAlgorithm.Decode(emptyPolylineCharArray).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeEmptyPolylineCharArray()); - } - - /// - /// Method is testing method. [] with invalid coordinates is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Decode_InvalidInput_ThrowsException() - { - // Arrange - var invalidPolylineCharrArray = Defaults.Polyline.Invalid.ToCharArray(); - - // Act - void DecodeInvalidPolylineCharArray() - { - PolylineAlgorithm.Decode(invalidPolylineCharrArray).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeInvalidPolylineCharArray()); - } - - /// - /// Method is testing method. is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Decode_NullInput_ThrowsException() - { - // Arrange - var nullPolylineCharArray = (char[])null; - - // Act - void DecodeNullPolylineCharArray() - { - PolylineAlgorithm.Decode(nullPolylineCharArray).ToArray(); - } - - // Assert - Assert.ThrowsException(() => DecodeNullPolylineCharArray()); - } - - /// - /// Method is testing method. [] with valid coordinates is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Decode_ValidInput_AreEquivalent() - { - // Arrange - var validPolylineCharArray = Defaults.Polyline.Valid.ToCharArray(); - - // Act - var result = PolylineAlgorithm.Decode(validPolylineCharArray); - - // Assert - CollectionAssert.AreEquivalent(Defaults.Coordinate.Valid.ToList(), result.ToList()); - } - - /// - /// Method is testing method. Empty is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Encode_EmptyInput_ThrowsException() - { - // Arrange - var emptyCoordinates = Defaults.Coordinate.Empty; - - // Act - void EncodeEmptyCoordinates() - { - PolylineAlgorithm.Encode(emptyCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeEmptyCoordinates()); - } - - /// - /// The Encode_InvalidInput - /// - [TestMethod] - public void Encode_InvalidInput_ThrowsException() - { - // Arrange - var invalidCoordinates = Defaults.Coordinate.Invalid; - - // Act - void EncodeInvalidCoordinates() - { - PolylineAlgorithm.Encode(invalidCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeInvalidCoordinates()); - } - - /// - /// Method is testing method. is passed as parameter. - /// Expected result is . - /// - [TestMethod] - public void Encode_NullInput_ThrowsException() - { - // Arrange - var nullCoordinates = (IEnumerable<(double, double)>)null; - - // Act - void EncodeNullCoordinates() - { - PolylineAlgorithm.Encode(nullCoordinates); - } - - // Assert - Assert.ThrowsException(() => EncodeNullCoordinates()); - } - - /// - /// The Encode_ValidInput - /// - [TestMethod] - public void Encode_ValidInput_AreEqual() - { - // Arrange - var validCoordinates = Defaults.Coordinate.Valid; - - // Act - var result = PolylineAlgorithm.Encode(validCoordinates); - - // Assert - Assert.AreEqual(Defaults.Polyline.Valid, result); - } - - #endregion - } -} diff --git a/tests/Properties/AssemblyInfo.cs b/tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 63a08d67..00000000 --- a/tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using Test = Microsoft.VisualStudio.TestTools.UnitTesting; -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("30324a08-aa42-425d-87da-8f9c6af60454")] - -[assembly: Test.Parallelize(Scope = Test.ExecutionScope.MethodLevel)] diff --git a/tests/Validation/CoordinateValidatorTest.cs b/tests/Validation/CoordinateValidatorTest.cs deleted file mode 100644 index 1d3f6540..00000000 --- a/tests/Validation/CoordinateValidatorTest.cs +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright (c) Petr Å rámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Tests.Validation -{ - using DropoutCoder.PolylineAlgorithm.Validation; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Defines the - /// - [TestClass] - [TestCategory(nameof(CoordinateValidator))] - public class CoordinateValidatorTest - { - #region Methods - - /// - /// The IsValid_InvalidInput - /// - [TestMethod] - public void IsValid_InvalidInput_IsFalse() - { - // Act - var invalidCoordinateCollection = Defaults.Coordinate.Invalid; - - foreach (var item in invalidCoordinateCollection) - { - // Arrange - var result = CoordinateValidator.IsValid(item); - - // Assert - Assert.IsFalse(result); - } - } - - /// - /// The IsValid_ValidInput - /// - [TestMethod] - public void IsValid_ValidInput_IsTrue() - { - // Act - var validCoordinateCollection = Defaults.Coordinate.Valid; - - foreach (var item in validCoordinateCollection) - { - // Arrange - var result = CoordinateValidator.IsValid(item); - - // Assert - Assert.IsTrue(result); - } - } - - /// - /// The IsValidLatitude_InvalidInput - /// - [TestMethod] - public void IsValidLatitude_InvalidInput_IsFalse() - { - // Act - var invalidCoordinateCollection = Defaults.Coordinate.Invalid; - - foreach (var item in invalidCoordinateCollection) - { - // Act - var result = CoordinateValidator.IsValidLatitude(item.Latitude); - - // Arrange - Assert.IsFalse(result); - } - } - - /// - /// The IsValidLatitude_ValidInput - /// - [TestMethod] - public void IsValidLatitude_ValidInput_IsTrue() - { - // Arrange - var validCoordinateCollection = Defaults.Coordinate.Valid; - - foreach ((double Latitude, double Longitude) in validCoordinateCollection) - { - // Act - var result = CoordinateValidator.IsValidLatitude(Latitude); - - // Assert - Assert.IsTrue(result); - } - } - - /// - /// The IsValidLongitude_InvalidInput - /// - [TestMethod] - public void IsValidLongitude_InvalidInput_IsFalse() - { - // Arrange - var invalidCoordinateCollection = Defaults.Coordinate.Invalid; - - foreach (var item in invalidCoordinateCollection) - { - // Act - var result = CoordinateValidator.IsValidLongitude(item.Longitude); - - // Assert - Assert.IsFalse(result); - } - } - - /// - /// The IsValidLongitude_ValidInput - /// - [TestMethod] - public void IsValidLongitude_ValidInput_IsTrue() - { - // Arrange - var validCoordinateCollection = Defaults.Coordinate.Valid; - - foreach (var item in validCoordinateCollection) - { - // Act - var result = CoordinateValidator.IsValidLongitude(item.Longitude); - - // Assert - Assert.IsTrue(result); - } - } - - #endregion - } -} diff --git a/unit-test.runsettings b/unit-test.runsettings new file mode 100644 index 00000000..7d24c7e5 --- /dev/null +++ b/unit-test.runsettings @@ -0,0 +1,50 @@ + + + + + + + + + coverage + + + + .*\.dll$ + + + + + + + + ^System\.Diagnostics\.DebuggerHiddenAttribute$ + ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ + ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ + ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ + + + + + + + True + + True + + True + + False + + True + + True + + True + + + + + + + \ No newline at end of file