├── .gitignore ├── .travis.yml ├── Jenkinsfile ├── LICENSE ├── README.md ├── Sandwych.MapMatchingKit.sln ├── Shared.props ├── appveyor.yml ├── benchmark └── Sandwych.MapMatchingKit.BenchmarkApp │ ├── Program.cs │ ├── RoutersBenchmark.cs │ ├── Sandwych.MapMatchingKit.BenchmarkApp.csproj │ └── SpatialIndexBenchmark.cs ├── build.cake ├── build.ps1 ├── build.sh ├── data ├── map.qgs ├── osm-kunming-roads-network.geojson ├── samples.geojson └── samples.oneday.geojson ├── doc ├── .gitignore ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── intro.md │ └── toc.yml ├── docfx.json ├── images │ └── screenshots │ │ └── qgis.png ├── index.md └── toc.yml ├── examples └── Sandwych.MapMatchingKit.Examples.HelloWorldApp │ ├── Program.cs │ └── Sandwych.MapMatchingKit.Examples.HelloWorldApp.csproj ├── src ├── Sandwych.Hmm │ ├── Candidate.cs │ ├── Directory.Build.props │ ├── ForwardBackwardModel.cs │ ├── HmmUtils.cs │ ├── Sandwych.Hmm.csproj │ ├── SequenceState.cs │ ├── Transition.cs │ └── ViterbiModel.cs └── Sandwych.MapMatchingKit │ ├── Directory.Build.props │ ├── Markov │ ├── AbstractFilter.cs │ ├── AbstractStateCandidate.cs │ ├── Distributions.cs │ ├── IFilter.cs │ ├── ISample.cs │ ├── IStateCandidate.cs │ ├── IStateMemory.cs │ ├── KState.cs │ └── SampleCandidates.cs │ ├── Matching │ ├── IMatcherSample.cs │ ├── Matcher.cs │ ├── MatcherCandidate.cs │ ├── MatcherKState.cs │ ├── MatcherSample.cs │ ├── MatcherTransition.cs │ └── Minset.cs │ ├── Roads │ ├── Costs.cs │ ├── Heading.cs │ ├── IRoadMapBuilder.cs │ ├── Road.cs │ ├── RoadInfo.cs │ ├── RoadMap.cs │ ├── RoadMapBuilder.cs │ ├── RoadPoint.cs │ └── Route.cs │ ├── Sandwych.MapMatchingKit.csproj │ ├── Spatial │ ├── CartesianSpatialOperation.cs │ ├── GeodesicInterception.cs │ ├── GeographySpatialOperation.cs │ ├── Geometries │ │ ├── Coordinate2D.cs │ │ ├── Coordinate2DExtensions.cs │ │ ├── GeoAPILineStringExtensions.cs │ │ ├── GeoAPIMultiLineStringExtensions.cs │ │ ├── ICoordinate2D.cs │ │ ├── NtsCoordinateExtensions.cs │ │ ├── PointExtensions.cs │ │ └── Vector3D.cs │ ├── GeometryMath.cs │ ├── ISpatialOperation.cs │ ├── Index │ │ ├── AbstractNtsSpatialIndex.cs │ │ ├── AbstractSpatialIndex.cs │ │ ├── ISpatialIndex.cs │ │ ├── NtsIndexItemVisitor.cs │ │ ├── QuadtreeIndex.cs │ │ ├── RBush │ │ │ ├── Envelope.cs │ │ │ ├── ISpatialData.cs │ │ │ ├── ISpatialDatabase.cs │ │ │ ├── ISpatialIndex.cs │ │ │ ├── ProjectionComparer.cs │ │ │ ├── RBush.Node.cs │ │ │ ├── RBush.Utilities.cs │ │ │ ├── RBush.cs │ │ │ └── RBushSpatialIndex.cs │ │ └── RtreeIndex.cs │ └── Projection │ │ ├── AbstractWktCoordinateTransformation.cs │ │ ├── Epsg3395To4326CoordinateTransformation.cs │ │ ├── Epsg4326To3395CoordinateTransformation.cs │ │ ├── ICoordinateTransformation.cs │ │ └── WellKnownConstants.cs │ ├── Topology │ ├── AbstractGraph.cs │ ├── AbstractGraphEdge.cs │ ├── BadGraphPathException.cs │ ├── DijkstraRouter.cs │ ├── IEdgePoint.cs │ ├── IGraph.cs │ ├── IGraphEdge.cs │ ├── IGraphRouter.cs │ ├── IPath.cs │ ├── PrecomputedDijkstra │ │ ├── BoundedDijkstraShortestPathAlgorithm.cs │ │ ├── OutOfRadiusException.cs │ │ ├── PrecomputedDijkstraRouter.cs │ │ ├── PrecomputedDijkstraTable.cs │ │ ├── PrecomputedDijkstraTableGenerator.cs │ │ └── PrecomputedDijkstraTableRow.cs │ └── RouteMark.cs │ └── Utility │ ├── DequeExtensions.cs │ ├── DictionaryExtensions.cs │ ├── HashCodeHelper.cs │ ├── NullLogger.cs │ └── PriorityQueue.cs └── test ├── Sandwych.Hmm.Tests ├── ForwardBackwardAlgorithmTest.cs ├── Model.cs ├── Sandwych.Hmm.Tests.csproj └── ViterbiAlgorithmTest.cs └── Sandwych.MapMatchingKit.Tests ├── DistributionsTest.cs ├── Markov ├── FilterTest.cs ├── KStateTest.cs └── MockSample.cs ├── Matching ├── MatcherSampleTest.cs ├── MatcherTest.cs └── MinsetTest.cs ├── Model ├── GpsMeasurement.cs ├── Point.cs ├── RoadPath.cs └── RoadPosition.cs ├── Proj4net └── Proj4netTest.cs ├── Roads └── RoadPointTest.cs ├── Sandwych.MapMatchingKit.Tests.csproj ├── Spatial ├── AbstractSpatialOperationTest.cs ├── CartesianSpatialOperationTest.cs ├── GeographySpatialOperationTest.cs ├── Index │ ├── AbstractSpatialIndexTest.cs │ ├── QuadtreeIndexTest.cs │ ├── RBushIndexTest.cs │ └── RtreeIndexTest.cs └── Projection │ ├── Epsg3395To4326CoordinateTransformationTest.cs │ └── Epsg4326To3395CoordinateTransformationTest.cs ├── TestBase.cs └── Topology ├── AbstractRouterTest.cs ├── DijkstraRouterTest.cs ├── Models.cs ├── PrecomputedDijkstra ├── MyGraph.cs ├── PrecomputedDijkstraRouterTest.cs └── PrecomputedDijkstraTableTest.cs └── RouterTestData.cs /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | bin 3 | Bin 4 | obj 5 | BenchmarkDotNet.Artifacts 6 | _ReSharper* 7 | *.swp 8 | *.user 9 | *.patch 10 | *.hg 11 | *.sln.cache 12 | desktop.ini 13 | *.ReSharper 14 | *.orig 15 | *.suo 16 | *.itrace.csdef 17 | *.build.csdef 18 | packages 19 | src/*.testsettings 20 | src/TestResults 21 | src/*.vsmdi 22 | artifacts 23 | *.vsp 24 | *.vspx 25 | *.psess 26 | project.lock.json 27 | *.mdb 28 | .vs/ 29 | .build/ 30 | .testPublish/ 31 | .dotnetcli 32 | *.qgs~ 33 | *.output.csv 34 | /tools/ 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | mono: none 4 | dotnet: 2.1.401 5 | mono: latest 6 | dist: xenial 7 | 8 | env: 9 | global: 10 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 11 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 12 | 13 | branches: 14 | only: 15 | - master 16 | - release 17 | - dev 18 | - /^.*-wip$/ 19 | - /^(.*\/)?ci-.*$/ 20 | 21 | script: 22 | - ./build.sh "-target=Travis" 23 | 24 | cache: 25 | directories: 26 | - .packages 27 | - tools 28 | 29 | notifications: 30 | email: false 31 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | stage('compile') { 4 | node { 5 | checkout scm 6 | stash 'everything' 7 | sh 'dotnet restore' 8 | sh 'dotnet build src/Sandwych.Hmm -f netstandard1.1' 9 | sh 'dotnet build src/Sandwych.MapMatchingKit -f netstandard2.0' 10 | } 11 | } 12 | 13 | stage('test') { 14 | parallel unitTests: { 15 | test('Test') 16 | }, 17 | integrationTests: { 18 | test('IntegrationTest') 19 | }, 20 | failFast: false 21 | } 22 | 23 | def test(type) { 24 | node { 25 | unstash 'everything' 26 | sh 'dotnet test test/Sandwych.Hmm.Tests/Sandwych.Hmm.Tests.csproj' 27 | sh 'dotnet test test/Sandwych.MapMatchingKit.Tests/Sandwych.MapMatchingKit.Tests.csproj' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Shared.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2017 BinaryStars Technologies Yunnan LLC. and Contributors 5 | Wei "oldrev" Li 6 | https://github.com/oldrev/mapmatchingkit 7 | https://github.com/oldrev/mapmatchingkit/blob/master/LICENSE 8 | netstandard2.0 9 | $(VersionSuffix)-$(BuildNumber) 10 | true 11 | portable 12 | false 13 | false 14 | false 15 | false 16 | false 17 | false 18 | false 19 | false 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | init: 3 | - git config --global core.autocrlf true 4 | 5 | install: 6 | - ps: $env:BuildNumber= $env:APPVEYOR_BUILD_NUMBER 7 | - ps: $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = true 8 | - ps: $env:NUGET_XMLDOC_MODE = "skip" 9 | - ps: $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 10 | - ps: $env:CAKE_SETTINGS_SKIPVERIFICATION = true 11 | 12 | build_script: 13 | - ps: .\build.ps1 -Target "Appveyor-Build" 14 | 15 | test_script: 16 | - ps: .\build.ps1 -Target "Appveyor-Test" 17 | 18 | artifacts: 19 | - path: 'src\Sandwych.Hmm\**\*.nupkg' 20 | - path: 'src\Sandwych.MapMatchingKit\**\*.nupkg' 21 | 22 | deploy: 23 | - provider: NuGet 24 | on: 25 | branch: master 26 | server: https://www.nuget.org/api/v2/package 27 | api_key: 28 | secure: GYFVozbTBotULJAyI55cVaH4d4TS5HDZ7lHUZabYUkZzrbvueLp2jxYXE2Jpr05V 29 | skip_symbols: true 30 | artifact: /.*\.nupkg/ 31 | -------------------------------------------------------------------------------- /benchmark/Sandwych.MapMatchingKit.BenchmarkApp/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | using System.IO; 4 | 5 | namespace Sandwych.MapMatchingKit.BenchmarkApp 6 | { 7 | public class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | 12 | var switcher = new BenchmarkSwitcher(new[] 13 | { 14 | typeof(RoutersBenchmark), 15 | typeof(SpatialIndexBenchmark), 16 | }); 17 | 18 | switcher.Run(args); 19 | Console.ReadKey(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmark/Sandwych.MapMatchingKit.BenchmarkApp/Sandwych.MapMatchingKit.BenchmarkApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /benchmark/Sandwych.MapMatchingKit.BenchmarkApp/SpatialIndexBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Globalization; 7 | using BenchmarkDotNet.Attributes; 8 | using BenchmarkDotNet.Running; 9 | using NetTopologySuite.Features; 10 | using NetTopologySuite.IO; 11 | using GeoAPI.Geometries; 12 | using Sandwych.MapMatchingKit.Matching; 13 | using Sandwych.MapMatchingKit.Spatial.Geometries; 14 | using Sandwych.MapMatchingKit.Roads; 15 | using Sandwych.MapMatchingKit.Spatial; 16 | using Sandwych.MapMatchingKit.Topology; 17 | using Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra; 18 | using Sandwych.MapMatchingKit.Spatial.Index; 19 | using Sandwych.MapMatchingKit.Spatial.Index.RBush; 20 | 21 | namespace Sandwych.MapMatchingKit.BenchmarkApp 22 | { 23 | [RPlotExporter, HtmlExporter, RankColumn] 24 | [MemoryDiagnoser, ShortRunJob] 25 | public class SpatialIndexBenchmark 26 | { 27 | private const double MaxDistance = 1000D; 28 | private const double MaxGpsRadius = 100D; 29 | private string DataDirPath { get; set; } 30 | 31 | private ISpatialOperation Spatial = new GeographySpatialOperation(); 32 | private RtreeIndex _ntsRtreeIndex; 33 | private QuadtreeIndex _ntsQuadtreeIndex; 34 | private RBushSpatialIndex _rbushIndex; 35 | 36 | protected IReadOnlyList _geometries; 37 | 38 | static SpatialIndexBenchmark() 39 | { 40 | GeoAPI.NetTopologySuiteBootstrapper.Bootstrap(); 41 | } 42 | 43 | [GlobalSetup] 44 | public void Setup() 45 | { 46 | _geometries = this.MakeGeometries(); 47 | _ntsRtreeIndex = new RtreeIndex(_geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 48 | _ntsQuadtreeIndex = new QuadtreeIndex(_geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 49 | _rbushIndex = new RBushSpatialIndex(_geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 50 | } 51 | 52 | [Benchmark] 53 | public int NtsSTRTreeBenchmark() 54 | { 55 | return this.DoRadius(_ntsRtreeIndex); 56 | } 57 | 58 | [Benchmark(Baseline = true)] 59 | public int NtsQuadTreeBenchmark() 60 | { 61 | return this.DoRadius(_ntsQuadtreeIndex); 62 | } 63 | 64 | [Benchmark] 65 | public int RBushRtreeBenchmark() 66 | { 67 | return this.DoRadius(_rbushIndex); 68 | } 69 | 70 | private int DoRadius(ISpatialIndex index) 71 | { 72 | { 73 | var c = new Coordinate2D(11.343629, 48.083797); 74 | var r = 50; 75 | index.Radius(c, r); 76 | } 77 | { 78 | var c = new Coordinate2D(11.344827, 48.083752); 79 | var r = 10; 80 | index.Radius(c, r); 81 | } 82 | { 83 | var c = new Coordinate2D(11.344827, 48.083752); 84 | var r = 5; 85 | index.Radius(c, r); 86 | } 87 | return 0; 88 | } 89 | 90 | private IReadOnlyList MakeGeometries() 91 | { 92 | /* 93 | * (p2) (p3) ----- (e1) : (p1) -> (p2) ---------------------------------------------------- 94 | * - \ / --------- (e2) : (p3) -> (p1) ---------------------------------------------------- 95 | * | (p1) | ------ (e3) : (p4) -> (p1) ---------------------------------------------------- 96 | * - / \ --------- (e4) : (p1) -> (p5) ---------------------------------------------------- 97 | * (p4) (p5) ----- (e5) : (p2) -> (p4) ---------------------------------------------------- 98 | * --------------- (e6) : (p5) -> (p3) ---------------------------------------------------- 99 | */ 100 | String p1 = "11.3441505 48.0839963"; 101 | String p2 = "11.3421209 48.0850624"; 102 | String p3 = "11.3460348 48.0850108"; 103 | String p4 = "11.3427522 48.0832129"; 104 | String p5 = "11.3469701 48.0825356"; 105 | var reader = new WKTReader(); 106 | ILineString readAsLineString(string wkt) => reader.Read("SRID=4326;" + wkt) as ILineString; 107 | 108 | var geometries = new ILineString[] { 109 | readAsLineString("LINESTRING(" + p1 + "," + p2 + ")"), 110 | readAsLineString("LINESTRING(" + p3 + "," + p1 + ")"), 111 | readAsLineString("LINESTRING(" + p4 + "," + p1 + ")"), 112 | readAsLineString("LINESTRING(" + p1 + "," + p5 + ")"), 113 | readAsLineString("LINESTRING(" + p2 + "," + p4 + ")"), 114 | readAsLineString("LINESTRING(" + p5 + "," + p3 + ")"), 115 | }; 116 | 117 | var geoms = new List(); 118 | for (int i = 0; i < geometries.Length; i++) 119 | { 120 | var g = geometries[i]; 121 | g.UserData = i; 122 | geoms.Add(g); 123 | } 124 | 125 | return geoms; 126 | } 127 | 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | //#addin nuget:?package=Cake.DocFx&version=0.5.0 2 | //#tool "docfx.console" 3 | 4 | var solutionFile = "Sandwych.MapMatchingKit.sln"; 5 | 6 | var target = Argument("target", "Default"); 7 | var configuration = Argument("configuration", "Release"); 8 | 9 | Task("Restore-NuGet-Packages") 10 | .Does(() => 11 | { 12 | DotNetCoreRestore(solutionFile, new DotNetCoreRestoreSettings 13 | { 14 | Verbosity = DotNetCoreVerbosity.Minimal, 15 | }); 16 | }); 17 | 18 | 19 | Task("Build") 20 | .IsDependentOn("Restore-NuGet-Packages") 21 | .Does(() => 22 | { 23 | Information("Building Libraries..."); 24 | var libSettings = new DotNetCoreBuildSettings() { 25 | NoRestore = true, 26 | Configuration = configuration, 27 | }; 28 | 29 | if(!IsRunningOnWindows()) 30 | { 31 | libSettings.Framework = "netstandard2.0"; 32 | } 33 | 34 | var libProjects = GetFiles("./src/**/*.csproj"); 35 | foreach(var project in libProjects) 36 | { 37 | // .NET Core 38 | DotNetCoreBuild(project.ToString(), libSettings); 39 | } 40 | 41 | Information("Building Unit tests..."); 42 | var testSettings = new DotNetCoreBuildSettings() { 43 | NoRestore = true, 44 | Configuration = configuration, 45 | }; 46 | 47 | var testProjects = GetFiles("./test/**/*.csproj"); 48 | foreach(var project in testProjects) 49 | { 50 | // .NET Core 51 | DotNetCoreBuild(project.ToString(), testSettings); 52 | } 53 | 54 | }); 55 | 56 | 57 | Task("Run-Unit-Tests") 58 | .IsDependentOn("Build") 59 | .Does(() => 60 | { 61 | var settings = new DotNetCoreTestSettings 62 | { 63 | Framework = "netcoreapp2.1", 64 | NoBuild = true, 65 | NoRestore = true, 66 | Configuration = configuration, 67 | }; 68 | 69 | var projects = GetFiles("./test/**/*.Tests.csproj"); 70 | foreach(var project in projects) 71 | { 72 | DotNetCoreTest(project.ToString(), settings); 73 | } 74 | }); 75 | 76 | 77 | Task("Create-NuGet-Packages") 78 | .IsDependentOn("Run-Unit-Tests") 79 | .Does(() => 80 | { 81 | // Build libraries 82 | var projects = GetFiles("./src/**/*.csproj"); 83 | foreach(var project in projects) 84 | { 85 | var name = project.GetDirectory().FullPath; 86 | if(name.EndsWith("Tests") || name.EndsWith("Xunit")) 87 | { 88 | continue; 89 | } 90 | 91 | DotNetCorePack(project.FullPath, new DotNetCorePackSettings { 92 | NoBuild = true, 93 | NoRestore = true, 94 | IncludeSymbols = false, 95 | Configuration = configuration, 96 | }); 97 | } 98 | }); 99 | 100 | 101 | /* 102 | Task("Generate-Docs").Does(() => { 103 | DocFxBuild("./docs/docfx.json"); 104 | }); 105 | 106 | 107 | Task("View-Docs").Does(() => { 108 | DocFxBuild("./docs/docfx.json", new DocFxBuildSettings { 109 | Serve = true, 110 | }); 111 | }); 112 | */ 113 | 114 | 115 | Task("Travis") 116 | .IsDependentOn("Run-Unit-Tests"); 117 | 118 | 119 | Task("Appveyor-Build") 120 | .IsDependentOn("Create-NuGet-Packages"); 121 | 122 | 123 | Task("Appveyor-Test") 124 | .IsDependentOn("Run-Unit-Tests"); 125 | 126 | 127 | Task("Default") 128 | .IsDependentOn("Run-Unit-Tests") 129 | .Does(() => { 130 | Information("Executing the default task..."); 131 | }); 132 | 133 | 134 | RunTarget(target); 135 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # This is the Cake bootstrapper script for Linux and OS X. 5 | # This file was downloaded from https://github.com/cake-build/resources 6 | # Feel free to change this file to fit your needs. 7 | ########################################################################## 8 | 9 | # Define directories. 10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 11 | TOOLS_DIR=$SCRIPT_DIR/tools 12 | ADDINS_DIR=$TOOLS_DIR/Addins 13 | MODULES_DIR=$TOOLS_DIR/Modules 14 | NUGET_EXE=$TOOLS_DIR/nuget.exe 15 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe 16 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config 17 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum 18 | ADDINS_PACKAGES_CONFIG=$ADDINS_DIR/packages.config 19 | MODULES_PACKAGES_CONFIG=$MODULES_DIR/packages.config 20 | 21 | # Define md5sum or md5 depending on Linux/OSX 22 | MD5_EXE= 23 | if [[ "$(uname -s)" == "Darwin" ]]; then 24 | MD5_EXE="md5 -r" 25 | else 26 | MD5_EXE="md5sum" 27 | fi 28 | 29 | # Define default arguments. 30 | SCRIPT="build.cake" 31 | CAKE_ARGUMENTS=() 32 | 33 | # Parse arguments. 34 | for i in "$@"; do 35 | case $1 in 36 | -s|--script) SCRIPT="$2"; shift ;; 37 | --) shift; CAKE_ARGUMENTS+=("$@"); break ;; 38 | *) CAKE_ARGUMENTS+=("$1") ;; 39 | esac 40 | shift 41 | done 42 | 43 | # Make sure the tools folder exist. 44 | if [ ! -d "$TOOLS_DIR" ]; then 45 | mkdir "$TOOLS_DIR" 46 | fi 47 | 48 | # Make sure that packages.config exist. 49 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then 50 | echo "Downloading packages.config..." 51 | curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages 52 | if [ $? -ne 0 ]; then 53 | echo "An error occurred while downloading packages.config." 54 | exit 1 55 | fi 56 | fi 57 | 58 | # Download NuGet if it does not exist. 59 | if [ ! -f "$NUGET_EXE" ]; then 60 | echo "Downloading NuGet..." 61 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe 62 | if [ $? -ne 0 ]; then 63 | echo "An error occurred while downloading nuget.exe." 64 | exit 1 65 | fi 66 | fi 67 | 68 | # Restore tools from NuGet. 69 | pushd "$TOOLS_DIR" >/dev/null 70 | if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then 71 | find . -type d ! -name . ! -name 'Cake.Bakery' | xargs rm -rf 72 | fi 73 | 74 | mono "$NUGET_EXE" install -ExcludeVersion 75 | if [ $? -ne 0 ]; then 76 | echo "Could not restore NuGet tools." 77 | exit 1 78 | fi 79 | 80 | $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" 81 | 82 | popd >/dev/null 83 | 84 | # Restore addins from NuGet. 85 | if [ -f "$ADDINS_PACKAGES_CONFIG" ]; then 86 | pushd "$ADDINS_DIR" >/dev/null 87 | 88 | mono "$NUGET_EXE" install -ExcludeVersion 89 | if [ $? -ne 0 ]; then 90 | echo "Could not restore NuGet addins." 91 | exit 1 92 | fi 93 | 94 | popd >/dev/null 95 | fi 96 | 97 | # Restore modules from NuGet. 98 | if [ -f "$MODULES_PACKAGES_CONFIG" ]; then 99 | pushd "$MODULES_DIR" >/dev/null 100 | 101 | mono "$NUGET_EXE" install -ExcludeVersion 102 | if [ $? -ne 0 ]; then 103 | echo "Could not restore NuGet modules." 104 | exit 1 105 | fi 106 | 107 | popd >/dev/null 108 | fi 109 | 110 | # Make sure that Cake has been installed. 111 | if [ ! -f "$CAKE_EXE" ]; then 112 | echo "Could not find Cake.exe at '$CAKE_EXE'." 113 | exit 1 114 | fi 115 | 116 | # Start Cake 117 | exec mono "$CAKE_EXE" $SCRIPT "${CAKE_ARGUMENTS[@]}" 118 | -------------------------------------------------------------------------------- /data/samples.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "name": "samples", 4 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 5 | "features": [ 6 | { "type": "Feature", "properties": { "time": "2016-12-14-13.14.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.821005, 25.034135 ] } }, 7 | { "type": "Feature", "properties": { "time": "2016-12-14-13.14.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.81793, 25.033592 ] } }, 8 | { "type": "Feature", "properties": { "time": "2016-12-14-13.15.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.80768, 25.03842 ] } }, 9 | { "type": "Feature", "properties": { "time": "2016-12-14-13.17.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.78264, 25.03861 ] } }, 10 | { "type": "Feature", "properties": { "time": "2016-12-14-13.18.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.77932, 25.03863 ] } }, 11 | { "type": "Feature", "properties": { "time": "2016-12-14-13.13.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.8297, 25.03373 ] } }, 12 | { "type": "Feature", "properties": { "time": "2016-12-14-13.13.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.832732, 25.033612 ] } }, 13 | { "type": "Feature", "properties": { "time": "2016-12-14-13.14.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.826762, 25.034137 ] } }, 14 | { "type": "Feature", "properties": { "time": "2016-12-14-13.15.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.815035, 25.033675 ] } }, 15 | { "type": "Feature", "properties": { "time": "2016-12-14-13.15.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.810165, 25.03697 ] } }, 16 | { "type": "Feature", "properties": { "time": "2016-12-14-13.16.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.80475, 25.03897 ] } }, 17 | { "type": "Feature", "properties": { "time": "2016-12-14-13.16.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.795545, 25.039965 ] } }, 18 | { "type": "Feature", "properties": { "time": "2016-12-14-13.17.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.79262, 25.039225 ] } }, 19 | { "type": "Feature", "properties": { "time": "2016-12-14-13.16.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.798595, 25.039755 ] } }, 20 | { "type": "Feature", "properties": { "time": "2016-12-14-13.17.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.78605, 25.038575 ] } }, 21 | { "type": "Feature", "properties": { "time": "2016-12-14-13.19.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.75839, 25.038735 ] } }, 22 | { "type": "Feature", "properties": { "time": "2016-12-14-13.20.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.754492, 25.041025 ] } }, 23 | { "type": "Feature", "properties": { "time": "2016-12-14-13.20.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.750527, 25.043425 ] } }, 24 | { "type": "Feature", "properties": { "time": "2016-12-14-13.21.50.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.74514, 25.044767 ] } }, 25 | { "type": "Feature", "properties": { "time": "2016-12-14-13.13.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.836005, 25.03402 ] } }, 26 | { "type": "Feature", "properties": { "time": "2016-12-14-13.13.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.83912, 25.03497 ] } }, 27 | { "type": "Feature", "properties": { "time": "2016-12-14-13.15.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.812297, 25.034932 ] } }, 28 | { "type": "Feature", "properties": { "time": "2016-12-14-13.16.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.80176, 25.03935 ] } }, 29 | { "type": "Feature", "properties": { "time": "2016-12-14-13.17.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.789592, 25.038572 ] } }, 30 | { "type": "Feature", "properties": { "time": "2016-12-14-13.18.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.775902, 25.03862 ] } }, 31 | { "type": "Feature", "properties": { "time": "2016-12-14-13.18.51.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.76926, 25.038647 ] } }, 32 | { "type": "Feature", "properties": { "time": "2016-12-14-13.20.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.755835, 25.039272 ] } }, 33 | { "type": "Feature", "properties": { "time": "2016-12-14-13.21.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.748185, 25.044187 ] } }, 34 | { "type": "Feature", "properties": { "time": "2016-12-14-13.21.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.74681, 25.044732 ] } }, 35 | { "type": "Feature", "properties": { "time": "2016-12-14-13.19.06.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.76624, 25.038895 ] } }, 36 | { "type": "Feature", "properties": { "time": "2016-12-14-13.19.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.76373, 25.03928 ] } }, 37 | { "type": "Feature", "properties": { "time": "2016-12-14-13.19.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.761065, 25.039217 ] } }, 38 | { "type": "Feature", "properties": { "time": "2016-12-14-13.14.21.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.823905, 25.034345 ] } }, 39 | { "type": "Feature", "properties": { "time": "2016-12-14-13.18.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.77256, 25.038632 ] } }, 40 | { "type": "Feature", "properties": { "time": "2016-12-14-13.20.36.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.753065, 25.042877 ] } }, 41 | { "type": "Feature", "properties": { "time": "2016-12-14-13.21.35.000000" }, "geometry": { "type": "Point", "coordinates": [ 102.745895, 25.044715 ] } } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | .manifest 11 | -------------------------------------------------------------------------------- /doc/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | -------------------------------------------------------------------------------- /doc/api/index.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | Welcome to the API Documents of Sandwych.Hmm and Sandwych.MapMatchingKit projects. 3 | -------------------------------------------------------------------------------- /doc/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Add your introductions here! 2 | -------------------------------------------------------------------------------- /doc/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: intro.md 3 | -------------------------------------------------------------------------------- /doc/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../src/", 7 | "files": [ "**/*.cs" ], 8 | "exclude": [ 9 | "**/obj/**", 10 | "**/bin/**", 11 | "_site/**" 12 | ] 13 | } 14 | ], 15 | "dest": "api" 16 | } 17 | ], 18 | "build": { 19 | "content": [ 20 | { 21 | "files": [ 22 | "api/**.yml", 23 | "api/index.md" 24 | ] 25 | }, 26 | { 27 | "files": [ 28 | "articles/**.md", 29 | "articles/**/toc.yml", 30 | "toc.yml", 31 | "*.md" 32 | ], 33 | "exclude": [ 34 | "obj/**", 35 | "_site/**" 36 | ] 37 | } 38 | ], 39 | "resource": [ 40 | { 41 | "files": [ 42 | "images/**" 43 | ], 44 | "exclude": [ 45 | "obj/**", 46 | "_site/**" 47 | ] 48 | } 49 | ], 50 | "overwrite": [ 51 | { 52 | "files": [ 53 | "apidoc/**.md" 54 | ], 55 | "exclude": [ 56 | "obj/**", 57 | "_site/**" 58 | ] 59 | } 60 | ], 61 | "dest": "_site", 62 | "globalMetadataFiles": [], 63 | "fileMetadataFiles": [], 64 | "template": [ 65 | "default" 66 | ], 67 | "postProcessors": [], 68 | "noLangKeyword": false, 69 | "keepFileLink": false, 70 | "cleanupCacheHistory": false 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /doc/images/screenshots/qgis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oldrev/mapmatchingkit/2970e560d466a23921b40891624a96be4f7567fc/doc/images/screenshots/qgis.png -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # This is the **HOMEPAGE**. 2 | Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. 3 | ## Quick Start Notes: 4 | 1. Add images to the *images* folder if the file is referencing an image. 5 | -------------------------------------------------------------------------------- /doc/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | -------------------------------------------------------------------------------- /examples/Sandwych.MapMatchingKit.Examples.HelloWorldApp/Sandwych.MapMatchingKit.Examples.HelloWorldApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/Candidate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.Hmm 6 | { 7 | /// 8 | /// Stores addition information for each candidate. 9 | /// 10 | internal class Candidate 11 | { 12 | 13 | public TState State { get; } 14 | 15 | public TObservation Observation { get; } 16 | 17 | public TDescriptor TransitionDescriptor { get; } 18 | 19 | /// 20 | /// * Back pointer to previous state candidate in the most likely sequence. 21 | /// * Back pointers are chained using plain references. 22 | /// * This allows garbage collection of unreachable back pointers. 23 | /// 24 | public Candidate BackPointer { get; } 25 | 26 | public Candidate(in TState state, in Candidate backPointer, in TObservation observation, in TDescriptor transitionDescriptor) 27 | { 28 | this.State = state; 29 | this.BackPointer = backPointer; 30 | this.Observation = observation; 31 | this.TransitionDescriptor = transitionDescriptor; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Sandwych.Hmm 5 | A general purpose utility library implements Hidden Markov Models (HMM) 6 | A general purpose utility library implements Hidden Markov Models (HMM) for time-inhomogeneous Markov processes 7 | 8 | A general purpose utility library implements Hidden Markov Models (HMM) for time-inhomogeneous Markov processes for .NET. 9 | 10 | map-matching;markov;hmm;viterbi 11 | 0.1.1.1 12 | beta 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/HmmUtils.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015, BMW Car IT GmbH 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | 21 | namespace Sandwych.Hmm 22 | { 23 | 24 | /// 25 | /// Implementation utilities. 26 | /// 27 | public static class HmmUtils 28 | { 29 | 30 | public static int InitialHashMapCapacity(int maxElements) 31 | { 32 | // Default load factor of HashMaps is 0.75 33 | return (int)(maxElements / 0.75) + 1; 34 | } 35 | 36 | public static Dictionary LogToNonLogProbabilities(this IReadOnlyDictionary logProbabilities) 37 | { 38 | var result = new Dictionary(); 39 | foreach (var entry in logProbabilities) 40 | { 41 | result.Add(entry.Key, Math.Exp(entry.Value)); 42 | } 43 | return result; 44 | } 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// Note that this check must not be used for probability densities. 53 | public static bool ProbabilityInRange(this double probability, double delta) 54 | { 55 | return probability >= -delta && probability <= 1.0 + delta; 56 | } 57 | 58 | public static int CombineHashCodes(this IEnumerable hashCodes) 59 | { 60 | int hash = 5381; 61 | 62 | foreach (var hashCode in hashCodes) 63 | { 64 | hash = ((hash << 5) + hash) ^ hashCode; 65 | } 66 | return hash; 67 | } 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/Sandwych.Hmm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net45 5 | latest 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/SequenceState.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.Hmm 23 | { 24 | 25 | /// 26 | /// State of the most likely sequence with additional information. 27 | /// 28 | /// The state type 29 | /// The observation type 30 | /// The transition descriptor type 31 | public readonly struct SequenceState 32 | { 33 | public TState State { get; } 34 | 35 | /// 36 | /// Null if HMM was started with initial state probabilities and state is the initial state. 37 | /// 38 | public TObservation Observation { get; } 39 | 40 | 41 | /// 42 | /// Null if transition descriptor was not provided. 43 | /// 44 | public TDescriptor TransitionDescriptor { get; } 45 | 46 | /// 47 | /// Probability of this state given all observations. 48 | /// 49 | public double SmoothingProbability { get; } 50 | 51 | public SequenceState(in TState state, in TObservation observation, in TDescriptor transitionDescriptor, in double smoothingProbability = double.NaN) 52 | { 53 | this.State = state; 54 | this.Observation = observation; 55 | this.TransitionDescriptor = transitionDescriptor; 56 | this.SmoothingProbability = smoothingProbability; 57 | } 58 | 59 | bool HasSmoothingProbability => this.SmoothingProbability != double.NaN; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Sandwych.Hmm/Transition.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.Hmm 23 | { 24 | 25 | /// 26 | /// Represents the transition between two consecutive candidates. 27 | /// 28 | /// the state type 29 | public readonly struct Transition : IEquatable> 30 | { 31 | public TState FromCandidate { get; } 32 | 33 | public TState ToCandidate { get; } 34 | 35 | public Transition(in TState fromCandidate, in TState toCandidate) 36 | { 37 | this.FromCandidate = fromCandidate; 38 | this.ToCandidate = toCandidate; 39 | } 40 | 41 | public override int GetHashCode() 42 | { 43 | unchecked 44 | { 45 | int hash = 17; 46 | hash = hash * 31 + this.FromCandidate.GetHashCode(); 47 | hash = hash * 31 + this.ToCandidate.GetHashCode(); 48 | return hash; 49 | } 50 | } 51 | 52 | public bool Equals(Transition other) => 53 | this.FromCandidate.Equals(other.FromCandidate) && this.ToCandidate.Equals(other.ToCandidate); 54 | 55 | public override String ToString() 56 | { 57 | return $"Transition [from={this.FromCandidate}, to={this.ToCandidate}]"; 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Sandwych.MapMatchingKit 5 | A GPS map-matching solution for .NET platform. 6 | A GPS map-matching solution for .NET platform. 7 | A GPS map-matching solution for .NET platform. This solution is porting from the "barefoot" project which developed in Java. 8 | map;gis;map-matching;osm;spatial 9 | 0.1.5.1 10 | alpha 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/AbstractStateCandidate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | public abstract class AbstractStateCandidate : 8 | IStateCandidate 9 | where TCandidate : class, IStateCandidate 10 | { 11 | private TTransition _transition; 12 | 13 | public double Seqprob { get; set; } 14 | public double Filtprob { get; set; } 15 | public TCandidate Predecessor { get; set; } 16 | public bool HasTransition { get; private set; } 17 | public TSample Sample { get; } 18 | 19 | protected AbstractStateCandidate(in TSample sample) 20 | { 21 | this.Sample = sample; 22 | } 23 | 24 | public TTransition Transition 25 | { 26 | get 27 | { 28 | if (!this.HasTransition) 29 | { 30 | throw new InvalidOperationException(); 31 | } 32 | return _transition; 33 | } 34 | set 35 | { 36 | _transition = value; 37 | this.HasTransition = true; 38 | } 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/Distributions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | /** 3 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 4 | * Author: Stefan Holder (stefan.holder@bmw.de) 5 | * 6 | * Copyright (C) 2017 Wei "oldrev" Li (oldrev@gmail.com) 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | namespace Sandwych.MapMatchingKit.Markov 22 | { 23 | 24 | 25 | /// 26 | /// Implements various probability distributions. 27 | /// 28 | public static class Distributions 29 | { 30 | 31 | public static double NormalDistribution(in double sigma, in double x) => 32 | 1.0 / (Math.Sqrt(2.0 * Math.PI) * sigma) * Math.Exp(-0.5 * Math.Pow(x / sigma, 2)); 33 | 34 | /// 35 | /// Use this function instead of Math.log(normalDistribution(sigma, x)) to avoid an 36 | /// arithmetic underflow for very small probabilities. 37 | /// 38 | /// 39 | /// 40 | /// 41 | public static double LogNormalDistribution(in double sigma, in double x) => 42 | Math.Log(1.0 / (Math.Sqrt(2.0 * Math.PI) * sigma)) + (-0.5 * Math.Pow(x / sigma, 2)); 43 | 44 | /// 45 | /// 46 | /// 47 | /// beta =1/lambda with lambda being the standard exponential distribution rate parameter 48 | /// 49 | /// 50 | /// 51 | public static double ExponentialDistribution(in double beta, in double x) => 52 | 1.0 / beta * Math.Exp(-x / beta); 53 | 54 | /// 55 | /// Use this function instead of Math.log(exponentialDistribution(beta, x)) to avoid an 56 | /// arithmetic underflow for very small probabilities. 57 | /// 58 | /// 59 | /// beta = 1 / lambda with lambda being the standard exponential distribution rate parameter 60 | /// 61 | /// 62 | /// 63 | public static double LogExponentialDistribution(in double beta, in double x) => 64 | Math.Log(1.0 / beta) - (x / beta); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/IFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | public interface IFilter 8 | where TCandidate : class, IStateCandidate 9 | { 10 | ICollection Execute(IEnumerable predecessors, in TSample previous, in TSample sample); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/ISample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | public interface ISample 8 | { 9 | DateTimeOffset Time { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/IStateCandidate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | public interface IStateCandidate 8 | where TCandidate : class, IStateCandidate 9 | { 10 | double Seqprob { get; set; } 11 | double Filtprob { get; set; } 12 | TCandidate Predecessor { get; set; } 13 | TTransition Transition { get; set; } 14 | bool HasTransition { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/IStateMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | /// 8 | /// State memory in Hidden Markov Model (HMM) inference and organizes state vectors 9 | /// St, i.e. a set of state candidates representing possible states for some time 10 | /// t with a probability distribution, over time. 11 | /// 12 | /// Candidate inherits from {@link StateCandidate}. 13 | /// Transition inherits from {@link StateTransition}. 14 | /// Sample inherits from {@link Sample}. 15 | public interface IStateMemory 16 | where TCandidate : class, IStateCandidate 17 | where TSample : ISample 18 | 19 | { 20 | /// 21 | /// Indicates if the state is empty. 22 | /// Boolean indicating if the state is empty. 23 | /// 24 | bool IsEmpty { get; } 25 | 26 | 27 | /// 28 | /// Gets the size of the state, which is the number of state candidates organized in the data structure. 29 | /// Size of the state, which is the number of state candidates. 30 | /// 31 | int Count { get; } 32 | 33 | 34 | /// 35 | /// Sample object of the most recent update. 36 | /// Sample object of the most recent update or null if there hasn't been any update yet. 37 | /// 38 | TSample Sample { get; } 39 | 40 | 41 | /// 42 | /// Time of the last state update in milliseconds epoch time. 43 | /// 44 | DateTimeOffset Time { get; } 45 | 46 | 47 | /// 48 | /// Updates the state with a state vector which is a set of {@link StateCandidate} objects with 49 | /// its respective measurement, which is a sample object. 50 | /// 51 | /// vector State vector for update of the state. 52 | /// sample Sample measurement of the state vector. 53 | void Update(ICollection vector, in TSample sample); 54 | 55 | 56 | /// 57 | /// Gets state vector of the last update. 58 | /// 59 | /// State vector of the last update, or an empty set if there hasn't been any update yet. 60 | ICollection Vector(); 61 | 62 | /// 63 | /// Gets a state estimate which is the most likely state candidate of the last update, with 64 | /// respect to state candidate's filter probability (). 65 | /// 66 | /// State estimate, which is most likely state candidate. 67 | TCandidate Estimate(); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Markov/SampleCandidates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Markov 6 | { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/IMatcherSample.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Markov; 2 | using Sandwych.MapMatchingKit.Spatial.Geometries; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Matching 8 | { 9 | public interface IMatcherSample : ISample 10 | { 11 | long Id { get; } 12 | float Azimuth { get; } 13 | Coordinate2D Coordinate { get; } 14 | bool IsNaN { get; } 15 | bool HasAzimuth { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/MatcherCandidate.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Markov; 2 | using Sandwych.MapMatchingKit.Roads; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Matching 8 | { 9 | public sealed class MatcherCandidate : AbstractStateCandidate 10 | { 11 | private readonly RoadPoint _point; 12 | 13 | public ref readonly RoadPoint Point => ref _point; 14 | 15 | public MatcherCandidate(in MatcherSample sample, in RoadPoint point) : base(sample) 16 | { 17 | this._point = point; 18 | } 19 | 20 | public override int GetHashCode() => Point.GetHashCode(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/MatcherKState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Sandwych.MapMatchingKit.Markov; 5 | 6 | namespace Sandwych.MapMatchingKit.Matching 7 | { 8 | public class MatcherKState : KState 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/MatcherSample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using NetTopologySuite.Geometries; 6 | using Sandwych.MapMatchingKit.Markov; 7 | using Sandwych.MapMatchingKit.Spatial.Geometries; 8 | 9 | namespace Sandwych.MapMatchingKit.Matching 10 | { 11 | /// 12 | /// Measurement sample for Hidden Markov Model (HMM) map matching which is a position measurement, 13 | /// e.g. measured with a GPS device. 14 | /// 15 | public readonly struct MatcherSample : ISample 16 | { 17 | public long Id { get; } 18 | public DateTimeOffset Time { get; } 19 | public float Azimuth { get; } 20 | public Coordinate2D Coordinate { get; } 21 | public bool IsNaN => this.Id < 0; 22 | public bool HasAzimuth => !float.IsNaN(this.Azimuth); 23 | 24 | public MatcherSample(long id, long time, double lng, double lat, float azimuth = float.NaN) 25 | { 26 | this.Id = id; 27 | this.Time = DateTimeOffset.MinValue.AddMilliseconds(time); 28 | this.Coordinate = new Coordinate2D(lng, lat); 29 | this.Azimuth = NormAzimuth(azimuth); 30 | } 31 | 32 | public MatcherSample(long id, long time, in Coordinate2D point, float azimuth = float.NaN) 33 | { 34 | this.Id = id; 35 | this.Time = DateTimeOffset.MinValue.AddMilliseconds(time); 36 | this.Coordinate = point; 37 | this.Azimuth = NormAzimuth(azimuth); 38 | } 39 | 40 | public MatcherSample(long id, DateTimeOffset time, in Coordinate2D point, float azimuth = float.NaN) 41 | { 42 | this.Id = id; 43 | this.Time = time; 44 | this.Coordinate = point; 45 | this.Azimuth = NormAzimuth(azimuth); 46 | } 47 | 48 | public MatcherSample(long id, DateTimeOffset time, double x, double y, float azimuth = float.NaN) 49 | { 50 | this.Id = id; 51 | this.Time = time; 52 | this.Coordinate = new Coordinate2D(x, y); 53 | this.Azimuth = NormAzimuth(azimuth); 54 | } 55 | 56 | private static float NormAzimuth(float azimuth) => 57 | azimuth >= 360f ? azimuth - (360f * (int)(azimuth / 360f)) 58 | : azimuth < 0f ? azimuth - (360f * ((int)(azimuth / 360f) - 1f)) : azimuth; 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/MatcherTransition.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Roads; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Matching 7 | { 8 | public readonly struct MatcherTransition 9 | { 10 | public Route Route { get; } 11 | 12 | public MatcherTransition(Route route) 13 | { 14 | this.Route = route ?? throw new ArgumentNullException(nameof(route)); 15 | } 16 | 17 | public override int GetHashCode() => 18 | this.Route.GetHashCode(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Matching/Minset.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Roads; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Matching 8 | { 9 | 10 | /// 11 | /// Minimizes a set of matching candidates represented as to remove semantically 12 | /// redundant candidates. 13 | /// 14 | public static class Minset 15 | { 16 | /// 17 | /// Floating point precision for considering a {@link RoadPoint} be the same as a vertex, 18 | /// fraction is zero or one (default: 1E-8). 19 | /// 20 | public readonly static double Precision = 1E-8; 21 | 22 | private static double Round(double value) 23 | { 24 | return Math.Round(value / Precision) * Precision; 25 | } 26 | 27 | /// 28 | /// 29 | /// Removes semantically redundant matching candidates from a set of matching candidates (as 30 | /// object) and returns a minimized (reduced) subset. 31 | /// 32 | /// 33 | /// Given a position measurement, a matching candidate is each road in a certain radius of the 34 | /// measured position, and in particular that point on each road that is closest to the measured 35 | /// position. Hence, there are as many state candidates as roads in that area. The idea is to 36 | /// conserve only possible routes through the area and use each route with its closest point to 37 | /// the measured position as a matching candidate. Since roads are split into multiple segments, 38 | /// the number of matching candidates is significantly higher than the respective number of 39 | /// routes. To give an example, assume the following matching candidates as 40 | /// objects with a road id and a fraction: 41 | /// 42 | /// 43 | ///
    44 | ///
  • (ri, 0.5) 45 | ///
  • (rj, 0.0) 46 | ///
  • (rk, 0.0) 47 | ///
48 | /// 49 | /// 50 | /// where they are connected as ri → rj and ri 51 | /// → rk. Here, matching candidates rj and 52 | /// rk can be removed if we see routes as matching candidates. This is because 53 | /// both, rj and rk, are reachable from ri. 54 | /// 55 | /// 56 | /// Note: Of course, rj and rk may be seen as relevant 57 | /// matching candidates, however, in the present HMM map matching algorithm there is no 58 | /// optimization of matching candidates along the road, instead it only considers the closest 59 | /// point of a road as a matching candidate. 60 | /// 61 | ///
62 | /// candidates Set of matching candidates as objects. 63 | /// Minimized (reduced) set of matching candidates as objects 64 | public static HashSet Minimize(IEnumerable candidates) 65 | { 66 | var map = new Dictionary(candidates.Count()); 67 | var misses = new Dictionary(candidates.Count()); 68 | var removes = new List(candidates.Count()); 69 | 70 | foreach (var candidate in candidates) 71 | { 72 | map[candidate.Edge.Id] = candidate; 73 | misses[candidate.Edge.Id] = 0; 74 | } 75 | 76 | foreach (var candidate in candidates) 77 | { 78 | var successors = candidate.Edge.Successors; 79 | var id = candidate.Edge.Id; 80 | 81 | foreach (var successor in successors) 82 | { 83 | var mapContainsSuccessorId = map.ContainsKey(successor.Id); 84 | if (!mapContainsSuccessorId) 85 | { 86 | misses[id] = misses[id] + 1; 87 | } 88 | if (mapContainsSuccessorId && Round(map[successor.Id].Fraction) == 0) 89 | { 90 | removes.Add(successor.Id); 91 | misses[id] = misses[id] + 1; 92 | } 93 | } 94 | } 95 | 96 | foreach (var candidate in candidates) 97 | { 98 | var id = candidate.Edge.Id; 99 | if (map.ContainsKey(id) && !removes.Contains(id) && Round(candidate.Fraction) == 1 && misses[id] == 0) 100 | { 101 | removes.Add(id); 102 | } 103 | } 104 | 105 | foreach (var id in removes) 106 | { 107 | map.Remove(id); 108 | } 109 | 110 | return new HashSet(map.Values); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/Costs.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Topology; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Roads 7 | { 8 | public static class Costs 9 | { 10 | private const double HeuristicSpeed = 130.0; 11 | private const double HuristicPriority = 1.0; 12 | 13 | public static double DistanceCost(Road road) => road.Length; 14 | 15 | public static double TimeCost(Road road) => DistanceCost(road) * 3.6 / Math.Min(road.MaxSpeed, HeuristicSpeed); 16 | 17 | public static double TimePriorityCost(Road road) => TimeCost(road) * Math.Max(HuristicPriority, road.Priority); 18 | 19 | public static double ComputeCost(this TEdge edge, double fraction, Func costFunc) 20 | where TEdge : IGraphEdge 21 | => costFunc(edge) * fraction; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/Heading.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Roads 6 | { 7 | public enum Heading 8 | { 9 | Forward, 10 | Backward 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/IRoadMapBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Roads 6 | { 7 | public interface IRoadMapBuilder 8 | { 9 | IRoadMapBuilder AddRoad(RoadInfo road); 10 | IRoadMapBuilder AddRoads(IEnumerable roads); 11 | RoadMap Build(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/Road.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Topology; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using GeoAPI.Geometries; 6 | using NetTopologySuite.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Roads 9 | { 10 | 11 | /// 12 | /// Directed road wrapper of objects in a directed road map (). 13 | /// 14 | /// Note: Since objects are directional representations of 15 | /// objects, each is split into two objects. For that purpose, it uses 16 | /// the identifier i of each to define identifiers of the respective 17 | /// {@link Road} objects, where i * 2 is the identifier of the forward directed 18 | /// and i * 2 + 1 of the backward directed . 19 | /// 20 | /// 21 | public class Road : AbstractGraphEdge 22 | { 23 | /// 24 | /// Gets referred BaseRoad object. 25 | /// 26 | public RoadInfo RoadInfo { get; } 27 | 28 | /// 29 | /// Gets road Heading relative to its RoadInfo. 30 | /// 31 | public Heading Headeing { get; } 32 | 33 | /// 34 | /// Gets road's geometry as a from the road's source to its target. 35 | /// 36 | public ILineString Geometry { get; } 37 | 38 | 39 | /// 40 | /// Constructs Road object. 41 | /// 42 | /// RoadInfo object to be referred to 43 | /// Heading of the directed Road 44 | public Road(RoadInfo info, Heading heading) : 45 | base(heading == Heading.Forward ? info.Id * 2 : info.Id * 2 + 1, 46 | heading == Heading.Forward ? info.Source : info.Target, 47 | heading == Heading.Forward ? info.Target : info.Source) 48 | { 49 | this.RoadInfo = info; 50 | this.Headeing = heading; 51 | if (heading == Heading.Forward) 52 | { 53 | this.Geometry = info.Geometry; 54 | } 55 | else 56 | { 57 | this.Geometry = info.Geometry.Reverse() as ILineString; 58 | } 59 | } 60 | 61 | /// 62 | /// Gets road length in meters. 63 | /// 64 | public float Length => this.RoadInfo.Length; 65 | 66 | /// 67 | /// Gets road's maximum speed in kilometers per hour. 68 | /// 69 | public float MaxSpeed => this.Headeing == Heading.Forward ? this.RoadInfo.MaxSpeedForward : this.RoadInfo.MaxSpeedBackward; 70 | 71 | /// 72 | /// Gets road's priority factor, i.e. an additional cost factor for routing, and must be greater 73 | /// or equal to one.Higher priority factor means higher costs. 74 | /// 75 | public float Priority => this.RoadInfo.Priority; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/RoadInfo.cs: -------------------------------------------------------------------------------- 1 | using GeoAPI.Geometries; 2 | using NetTopologySuite.Geometries; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Roads 8 | { 9 | public class RoadInfo 10 | { 11 | public ILineString Geometry { get; } 12 | public long Id { get; } 13 | public byte[] ToWkb() => this.Geometry.AsBinary(); 14 | public string ToWkt() => this.Geometry.AsText(); 15 | public long Source { get; } 16 | public long Target { get; } 17 | public bool OneWay { get; } 18 | public short Type { get; } 19 | public float Priority { get; } 20 | public float MaxSpeedForward { get; } 21 | public float MaxSpeedBackward { get; } 22 | public float Length { get; } 23 | 24 | public RoadInfo(long id, long source, long target, bool oneway, short type, 25 | float priority, float maxspeedForward, float maxspeedBackward, float length, 26 | ILineString geometry) 27 | { 28 | this.Id = id; 29 | this.Source = source; 30 | this.Target = target; 31 | this.OneWay = oneway; 32 | this.Type = type; 33 | this.Priority = priority; 34 | this.MaxSpeedForward = maxspeedForward; 35 | this.MaxSpeedBackward = maxspeedBackward; 36 | this.Length = length; 37 | this.Geometry = geometry; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/RoadMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Collections.Generic; 5 | using Sandwych.MapMatchingKit.Topology; 6 | using Sandwych.MapMatchingKit.Spatial; 7 | using Sandwych.MapMatchingKit.Spatial.Geometries; 8 | using Sandwych.MapMatchingKit.Spatial.Index; 9 | 10 | namespace Sandwych.MapMatchingKit.Roads 11 | { 12 | /// 13 | /// 14 | /// Implementation of a road map with (directed) roads, i.e. objects. It provides a road 15 | /// network for routing that is derived from and spatial search of roads with a 16 | /// . 17 | /// 18 | /// 19 | /// Note: Since objects are directed representations of objects, 20 | /// identifiers have a special mapping, see . 21 | /// 22 | /// 23 | public sealed class RoadMap : AbstractGraph 24 | { 25 | public ISpatialIndex Index { get; } 26 | private readonly ISpatialOperation _spatial; 27 | 28 | public RoadMap(IEnumerable roads, ISpatialOperation spatial) : base(roads) 29 | { 30 | _spatial = spatial; 31 | 32 | // The original Barefoot is using Quad Tree for spatial indexing, however, in my experiment, NTS's STRtree is 33 | // much faster than NTS's Quadtree. 34 | //this.Index = new Spatial.Index.RBush.RBushSpatialIndex(roads.Select(x => x.RoadInfo), spatial, r => r.Geometry, r => r.Length); 35 | this.Index = new RtreeIndex(roads.Select(x => x.RoadInfo), spatial, r => r.Geometry, r => r.Length); 36 | } 37 | 38 | private IEnumerable Split(IEnumerable<(RoadInfo Item, double Fraction, Coordinate2D InterpolatedPoint)> points) 39 | { 40 | /* 41 | * This uses the road 42 | */ 43 | foreach (var point in points) 44 | { 45 | yield return new RoadPoint(this.EdgeMap[point.Item.Id * 2], point.Fraction, point.InterpolatedPoint, _spatial); 46 | 47 | var backwardRoadId = point.Item.Id * 2 + 1; 48 | if (this.EdgeMap.TryGetValue(backwardRoadId, out var road)) 49 | { 50 | yield return new RoadPoint(road, 1.0 - point.Fraction, point.InterpolatedPoint, _spatial); 51 | } 52 | } 53 | } 54 | 55 | public IEnumerable Radius(in Coordinate2D c, double r, int k = -1) => 56 | this.Split(this.Index.Radius(c, r, k)); 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/RoadMapBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Sandwych.MapMatchingKit.Spatial; 6 | 7 | namespace Sandwych.MapMatchingKit.Roads 8 | { 9 | 10 | public class RoadMapBuilder : IRoadMapBuilder 11 | { 12 | private readonly IDictionary _roads = new Dictionary(); 13 | private readonly ISpatialOperation _spatial; 14 | 15 | public RoadMapBuilder(ISpatialOperation spatial) 16 | { 17 | _spatial = spatial; 18 | } 19 | 20 | 21 | public IRoadMapBuilder AddRoad(RoadInfo road) 22 | { 23 | _roads.Add(road.Id, road); 24 | return this; 25 | } 26 | 27 | public IRoadMapBuilder AddRoads(IEnumerable roads) 28 | { 29 | foreach (var r in roads) 30 | { 31 | this.AddRoad(r); 32 | } 33 | return this; 34 | } 35 | 36 | public RoadMap Build() 37 | { 38 | return new RoadMap(this.GetAllRoads(), _spatial); 39 | } 40 | 41 | private IEnumerable GetAllRoads() 42 | { 43 | foreach (var roadInfo in _roads.Values) 44 | { 45 | if (roadInfo.OneWay) 46 | { 47 | yield return new Road(roadInfo, Heading.Forward); 48 | } 49 | else 50 | { 51 | yield return new Road(roadInfo, Heading.Forward); 52 | yield return new Road(roadInfo, Heading.Backward); 53 | } 54 | } 55 | } 56 | 57 | private static RoadMap ConstructEdges(RoadMap graph) 58 | { 59 | var map = new Dictionary>(); 60 | 61 | foreach (var edge in graph.EdgeMap.Values) 62 | { 63 | if (!map.ContainsKey(edge.Source)) 64 | { 65 | map[edge.Source] = new List() { edge }; 66 | } 67 | else 68 | { 69 | map[edge.Source].Add(edge); 70 | } 71 | } 72 | 73 | IList successors = null; 74 | foreach (var edges in map.Values) 75 | { 76 | for (int i = 1; i < edges.Count; ++i) 77 | { 78 | var prevEdge = edges[i - 1]; 79 | prevEdge.Neighbor = edges[i]; 80 | 81 | prevEdge.Successor = map.TryGetValue(prevEdge.Target, out successors) ? successors.First() : default; 82 | } 83 | 84 | var lastEdge = edges.Last(); 85 | lastEdge.Neighbor = edges.First(); 86 | lastEdge.Successor = map.TryGetValue(lastEdge.Target, out successors) ? successors.First() : default; 87 | } 88 | return graph; 89 | } 90 | 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/RoadPoint.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Topology; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using GeoAPI.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial; 7 | using Sandwych.MapMatchingKit.Spatial.Geometries; 8 | using Sandwych.MapMatchingKit.Utility; 9 | 10 | namespace Sandwych.MapMatchingKit.Roads 11 | { 12 | public readonly struct RoadPoint : IEdgePoint, IEquatable 13 | { 14 | public Road Edge { get; } 15 | 16 | public double Fraction { get; } 17 | 18 | public Coordinate2D Coordinate { get; } 19 | 20 | public float Azimuth { get; } 21 | 22 | public RoadPoint(in Road road, double fraction, float azimuth, ISpatialOperation spatial) 23 | { 24 | this.Edge = road; 25 | this.Fraction = fraction; 26 | this.Azimuth = azimuth; 27 | this.Coordinate = spatial.Interpolate(this.Edge.Geometry, this.Fraction); 28 | } 29 | 30 | public RoadPoint(in Road road, double fraction, in Coordinate2D coordinate, ISpatialOperation spatial) 31 | { 32 | this.Edge = road; 33 | this.Fraction = fraction; 34 | this.Coordinate = coordinate; 35 | this.Azimuth = (float)spatial.Azimuth(road.Geometry, fraction); 36 | } 37 | 38 | public RoadPoint(in Road road, double fraction, float azimuth) : this(road, fraction, azimuth, GeographySpatialOperation.Instance) 39 | { 40 | } 41 | 42 | public RoadPoint(in Road road, double fraction, ISpatialOperation spatial) 43 | { 44 | this.Edge = road; 45 | this.Fraction = fraction; 46 | this.Azimuth = (float)spatial.Azimuth(road.Geometry, fraction); 47 | this.Coordinate = spatial.Interpolate(this.Edge.Geometry, this.Fraction); 48 | } 49 | 50 | public RoadPoint(in Road road, double fraction) : this(road, fraction, GeographySpatialOperation.Instance) 51 | { 52 | 53 | } 54 | 55 | public override int GetHashCode() => 56 | HashCodeHelper.Combine(Edge.GetHashCode(), Fraction.GetHashCode()); 57 | 58 | 59 | public bool Equals(RoadPoint other) => 60 | this.Edge == other.Edge && Math.Abs(this.Fraction - other.Fraction) < 10E-6; 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Roads/Route.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Sandwych.MapMatchingKit.Topology; 6 | using NetTopologySuite.Geometries; 7 | using GeoAPI.Geometries; 8 | 9 | namespace Sandwych.MapMatchingKit.Roads 10 | { 11 | public sealed class Route : IPath, IEquatable 12 | { 13 | private static readonly IEnumerable EmptyEdges = new Road[] { }; 14 | private readonly IEnumerable _edges; 15 | private readonly RoadPoint _startPoint; 16 | private readonly RoadPoint _endPoint; 17 | 18 | public ref readonly RoadPoint StartPoint => ref _startPoint; 19 | 20 | public ref readonly RoadPoint EndPoint => ref _endPoint; 21 | 22 | public IEnumerable Edges => GetEdges(this.StartPoint, this.EndPoint, this._edges); 23 | 24 | public float Length { get; } 25 | 26 | public Route(in RoadPoint startPoint, in RoadPoint endPoint, IEnumerable edges) 27 | { 28 | _startPoint = startPoint; 29 | _endPoint = endPoint; 30 | _edges = edges; 31 | this.Length = ComputeLength(startPoint, endPoint, edges); 32 | } 33 | 34 | private static float ComputeLength(in RoadPoint startPoint, in RoadPoint endPoint, in IEnumerable edges) 35 | { 36 | var edges_ = GetEdges(startPoint, endPoint, edges); 37 | var totalLength = edges_.Sum(r => r.Length); 38 | var d = totalLength - (startPoint.Fraction * startPoint.Edge.Length) - ((1.0 - endPoint.Fraction) * endPoint.Edge.Length); 39 | return (float)d; 40 | } 41 | 42 | private static IEnumerable GetEdges(RoadPoint startPoint, RoadPoint endPoint, IEnumerable edges) 43 | { 44 | yield return startPoint.Edge; 45 | foreach (var edge in edges) 46 | { 47 | yield return edge; 48 | } 49 | yield return endPoint.Edge; 50 | } 51 | 52 | public double Cost(Func costFunc) 53 | { 54 | var value = Costs.ComputeCost(this.StartPoint.Edge, 1.0 - this.StartPoint.Fraction, costFunc); 55 | 56 | foreach (var e in _edges) 57 | { 58 | value += costFunc(e); 59 | } 60 | 61 | value -= Costs.ComputeCost(this.EndPoint.Edge, 1.0 - this.EndPoint.Fraction, costFunc); 62 | 63 | return value; 64 | } 65 | 66 | public ILineString ToGeometry() 67 | { 68 | var coords = this.Edges.Select(e => e.Geometry).SelectMany(e => e.Coordinates).ToArray(); 69 | var geom = new LineString(coords); 70 | return geom; 71 | } 72 | 73 | public bool Equals(Route other) 74 | { 75 | if (ReferenceEquals(this, other)) 76 | { 77 | return true; 78 | } 79 | 80 | if (!this.StartPoint.Equals(other.StartPoint)) 81 | { 82 | return false; 83 | } 84 | 85 | if (!this.EndPoint.Equals(other.EndPoint)) 86 | { 87 | return false; 88 | } 89 | 90 | return this.Edges.SequenceEqual(other.Edges); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Sandwych.MapMatchingKit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net461; 5 | latest 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/CartesianSpatialOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using NetTopologySuite.Algorithm.Distance; 6 | using NetTopologySuite.Geometries; 7 | using System.Numerics; 8 | using Sandwych.MapMatchingKit.Spatial.Geometries; 9 | 10 | namespace Sandwych.MapMatchingKit.Spatial 11 | { 12 | 13 | public sealed class CartesianSpatialOperation : ISpatialOperation 14 | { 15 | private const double TwoPi = Math.PI * 2; 16 | private const double Rad2Deg = 180.0 / Math.PI; 17 | 18 | private static readonly Lazy s_instance = new Lazy(() => new CartesianSpatialOperation(), true); 19 | 20 | public static ISpatialOperation Instance => s_instance.Value; 21 | 22 | public double Distance(in Coordinate2D a, in Coordinate2D b) => 23 | a.CartesianDistance(b); 24 | 25 | public double Length(ILineString path) => path.Length; 26 | 27 | public double Length(IMultiLineString path) => path.Length; 28 | 29 | public double Intercept(ILineString p, in Coordinate2D c) 30 | { 31 | var d = Double.MaxValue; 32 | var a = p.GetCoordinateN(0).ToCoordinate2D(); 33 | var s = 0D; 34 | var sf = 0D; 35 | var ds = 0D; 36 | 37 | for (int i = 1; i < p.NumPoints; ++i) 38 | { 39 | var b = p.GetCoordinateN(i).ToCoordinate2D(); 40 | 41 | ds = this.Distance(a, b); 42 | 43 | var f_ = this.Intercept(a, b, c); 44 | f_ = (f_ > 1) ? 1 : (f_ < 0) ? 0 : f_; 45 | var x = this.Interpolate(a, b, f_); 46 | double d_ = this.Distance(c, x); 47 | 48 | if (d_ < d) 49 | { 50 | sf = (f_ * ds) + s; 51 | d = d_; 52 | } 53 | 54 | s = s + ds; 55 | a = b; 56 | } 57 | return s == 0 ? 0 : sf / s; 58 | } 59 | 60 | public double Intercept(in Coordinate2D a, in Coordinate2D b, in Coordinate2D p) 61 | { 62 | var d_ab = a.CartesianDistance(b); 63 | var d_ap = a.CartesianDistance(p); 64 | var d_abp = GeometryMath.DistancePointLinePerpendicular(p, a, b); 65 | var d = Math.Sqrt(d_ap * d_ap - d_abp * d_abp); 66 | return d / d_ab; 67 | } 68 | 69 | public double Azimuth(in Coordinate2D a, in Coordinate2D b, double f) 70 | { 71 | var d = b - a; 72 | return 90d - (180d / Math.PI * Math.Atan2(d.Y, d.X)); 73 | } 74 | 75 | public double Azimuth(ILineString path, double f) 76 | { 77 | var l = this.Length(path); 78 | return this.Azimuth(path, l, f); 79 | } 80 | 81 | public double Azimuth(ILineString path, double l, double f) 82 | { 83 | var d = l * f; 84 | var s = 0.0; 85 | for (int i = 1; i < path.NumPoints; i++) 86 | { 87 | var a = path.GetCoordinate2DAt(i - 1); 88 | var b = path.GetCoordinate2DAt(i); 89 | var ds = a.CartesianDistance(b); 90 | if ((s + ds) >= d) 91 | { 92 | return this.Azimuth(a, b, (d - s) / ds); 93 | } 94 | s += ds; 95 | } 96 | return double.NaN; 97 | } 98 | 99 | public Coordinate2D Interpolate(ILineString path, double f) 100 | { 101 | var l = this.Length(path); 102 | return this.Interpolate(path, l, f); 103 | } 104 | 105 | public Coordinate2D Interpolate(ILineString path, double l, double f) 106 | { 107 | if (!(f >= 0 && f <= 1)) 108 | { 109 | throw new ArgumentOutOfRangeException(nameof(f)); 110 | } 111 | var p0 = path.GetCoordinateN(0).ToCoordinate2D(); 112 | 113 | var a = p0; 114 | double d = l * f; 115 | double s = 0, ds = 0; 116 | 117 | if (f < 0 + 1E-10) 118 | { 119 | return p0; 120 | } 121 | 122 | if (f > 1 - 1E-10) 123 | { 124 | return path.GetCoordinateN(path.NumPoints - 1).ToCoordinate2D(); 125 | } 126 | 127 | for (int i = 1; i < path.NumPoints; ++i) 128 | { 129 | var b = path.GetCoordinateN(i).ToCoordinate2D(); 130 | ds = this.Distance(a, b); 131 | 132 | if ((s + ds) >= d) 133 | { 134 | return this.Interpolate(a, b, (d - s) / ds); 135 | } 136 | 137 | s = s + ds; 138 | a = b; 139 | } 140 | 141 | return Coordinate2D.NaN; 142 | } 143 | 144 | public Coordinate2D Interpolate(in Coordinate2D a, in Coordinate2D b, double f) 145 | { 146 | var l = a.CartesianDistance(b); 147 | var d = l * f; 148 | return a + d; 149 | } 150 | 151 | 152 | public Envelope Envelope(in Coordinate2D c, double radius) 153 | { 154 | var minCorner = (X: c.X - radius, Y: c.Y - radius); 155 | var maxCorner = (X: c.X + radius, Y: c.Y + radius); 156 | return new Envelope(minCorner.X, maxCorner.X, minCorner.Y, maxCorner.Y); 157 | } 158 | 159 | public Envelope Envelope(ILineString line) 160 | { 161 | return line.EnvelopeInternal; 162 | } 163 | 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/Coordinate2DExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NetTopologySuite.Geometries; 5 | 6 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 7 | { 8 | public static class Coordinate2DExtensions 9 | { 10 | //Add workaround for C# 7.2's bug 11 | //(this in Coordinate2D self) 12 | 13 | public static GeoAPI.Geometries.Coordinate ToGeoAPICoordinate(this Coordinate2D self) => 14 | new GeoAPI.Geometries.Coordinate(self.X, self.Y); 15 | 16 | public static GeoAPI.Geometries.IPoint ToGeoAPIPoint(this Coordinate2D self) => 17 | new Point(self.X, self.Y); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/GeoAPILineStringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | 6 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 7 | { 8 | public static class GeoAPILineStringExtensions 9 | { 10 | public static Coordinate2D GetCoordinate2DAt(this ILineString line, int n) => 11 | line.GetCoordinateN(n).ToCoordinate2D(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/GeoAPIMultiLineStringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | using GeoAPI.Geometries; 6 | using NetTopologySuite.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 9 | { 10 | public static class GeoAPIMultiLineStringExtensions 11 | { 12 | public static ILineString ToLineString(this IMultiLineString mls) 13 | { 14 | var coords = mls.Geometries.Cast().SelectMany(g => g.Coordinates); 15 | return new LineString(coords.ToArray()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/ICoordinate2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 6 | { 7 | public interface ICoordinate2D : IComparable 8 | { 9 | double X { get; } 10 | double Y { get; } 11 | double this[int index] { get; } 12 | bool IsNan { get; } 13 | } 14 | 15 | public static class ICoordinate2DExtensions 16 | { 17 | 18 | public static int CompareTo(this T self, in T other) 19 | where T : ICoordinate2D 20 | { 21 | if (self.X < other.X) 22 | { 23 | return -1; 24 | } 25 | if (self.X > other.X) 26 | { 27 | return 1; 28 | } 29 | if (self.Y < other.Y) 30 | { 31 | return -1; 32 | } 33 | return self.Y > other.Y ? 1 : 0; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/NtsCoordinateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 6 | { 7 | public static class NtsCoordinateExtensions 8 | { 9 | public static Coordinate2D ToCoordinate2D(this GeoAPI.Geometries.Coordinate coord) => 10 | new Coordinate2D(coord.X, coord.Y); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/PointExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | 6 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 7 | { 8 | public static class PointExtensions 9 | { 10 | public static Coordinate2D ToCoordinate2D(this IPoint self) => 11 | new Coordinate2D(self.X, self.Y); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Geometries/Vector3D.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Utility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Spatial.Geometries 8 | { 9 | public readonly struct Vector3D : IEquatable 10 | { 11 | public double X { get; } 12 | public double Y { get; } 13 | public double Z { get; } 14 | 15 | public static Vector3D NaN => new Vector3D(double.NaN, double.NaN, double.NaN); 16 | public static Vector3D Zero => new Vector3D(0D, 0D, 0D); 17 | public static Vector3D One => new Vector3D(1D, 1D, 1D); 18 | 19 | public Vector3D(double x, double y, double z) : this() 20 | { 21 | this.X = x; 22 | this.Y = y; 23 | this.Z = z; 24 | } 25 | 26 | public Vector3D(double[] array) : this() 27 | { 28 | if (array == null) 29 | { 30 | throw new ArgumentNullException(nameof(array)); 31 | } 32 | 33 | if (array.Length != 3) 34 | { 35 | throw new ArgumentOutOfRangeException(nameof(array)); 36 | } 37 | 38 | this.X = array[0]; 39 | this.Y = array[1]; 40 | this.Z = array[2]; 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static Vector3D operator +(in Vector3D left, in Vector3D right) => 45 | new Vector3D(left.X + right.X, left.Y + right.Y, left.Z + right.Z); 46 | 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public static Vector3D operator -(in Vector3D left, in Vector3D right) => 49 | new Vector3D(left.X - right.X, left.Y - right.Y, left.Z - right.Z); 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static Vector3D operator -(in Vector3D left) => 53 | new Vector3D(-left.X, -left.Y, -left.Z); 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public static Vector3D operator *(in Vector3D left, double right) => 57 | new Vector3D(left.X * right, left.Y * right, left.Z * right); 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public static Vector3D operator /(in Vector3D left, double right) => 61 | new Vector3D(left.X / right, left.Y / right, left.Z / right); 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | public Vector3D Cross(in Vector3D other) => 65 | new Vector3D( 66 | Y * other.Z - Z * other.Y, 67 | Z * other.X - X * other.Z, 68 | X * other.Y - Y * other.X); 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public double Product(in Vector3D other) => 72 | X * other.X + Y * other.Y + Z * other.Z; 73 | 74 | 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | public static bool operator ==(in Vector3D a, in Vector3D b) => a.Equals(b); 77 | 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public static bool operator !=(in Vector3D a, in Vector3D b) => !a.Equals(b); 80 | 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | public bool Equals(Vector3D other) => 83 | this.X == other.X && this.Y == other.Y && this.Z == other.Z; 84 | 85 | 86 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 | public override bool Equals(object obj) => obj is Coordinate2D p && this.Equals(p); 88 | 89 | public double this[int index] 90 | { 91 | get 92 | { 93 | switch (index) 94 | { 95 | case 0: return X; 96 | case 1: return Y; 97 | case 2: return Z; 98 | default: 99 | throw new InvalidOperationException(); 100 | } 101 | } 102 | } 103 | 104 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 105 | public override int GetHashCode() => HashCodeHelper.Combine(X.GetHashCode(), Y.GetHashCode(), Z.GetHashCode()); 106 | 107 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 108 | public double[] ToArray() => new double[3] { X, Y, Z }; 109 | 110 | public override string ToString() => 111 | string.Format("Vector3D({0}, {1}, {2})", X, Y, Z); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/GeometryMath.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Spatial.Geometries; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Spatial 7 | { 8 | public static class GeometryMath 9 | { 10 | 11 | 12 | /// 13 | /// Computes the perpendicular distance from a point p 14 | /// to the (infinite) line containing the points AB 15 | /// Copy from NTS 16 | /// 17 | /// The point to compute the distance for. 18 | /// One point of the line. 19 | /// Another point of the line (must be different to A). 20 | /// The perpendicular distance from p to line AB. 21 | public static double DistancePointLinePerpendicular(Coordinate2D p, Coordinate2D A, Coordinate2D B) 22 | { 23 | // use comp.graphics.algorithms Frequently Asked Questions method 24 | /*(2) 25 | (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) 26 | s = ----------------------------- 27 | Curve^2 28 | Then the distance from C to Point = |s|*Curve. 29 | */ 30 | var len2 = ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)); 31 | var s = ((A.Y - p.Y) * (B.X - A.X) - (A.X - p.X) * (B.Y - A.Y)) / len2; 32 | 33 | 34 | return Math.Abs(s) * Math.Sqrt(len2); 35 | } 36 | 37 | 38 | 39 | /// 40 | /// Computes the distance from a point p to a line segment AB. 41 | /// Note: NON-ROBUST! 42 | /// 43 | /// The point to compute the distance for. 44 | /// One point of the line. 45 | /// Another point of the line (must be different to A). 46 | /// The distance from p to line segment AB. 47 | public static double DistancePointLine(Coordinate2D p, Coordinate2D A, Coordinate2D B) 48 | { 49 | // if start = end, then just compute distance to one of the endpoints 50 | if (A.X == B.X && A.Y == B.Y) 51 | { 52 | return p.CartesianDistance(A); 53 | } 54 | 55 | // otherwise use comp.graphics.algorithms Frequently Asked Questions method 56 | /*(1) AC dot AB 57 | r = --------- 58 | ||AB||^2 59 | r has the following meaning: 60 | r=0 Point = A 61 | r=1 Point = B 62 | r<0 Point is on the backward extension of AB 63 | r>1 Point is on the forward extension of AB 64 | 0= 1.0) 75 | { 76 | return p.CartesianDistance(B); 77 | } 78 | 79 | 80 | /*(2) 81 | (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) 82 | s = ----------------------------- 83 | Curve^2 84 | Then the distance from C to Point = |s|*Curve. 85 | This is the same calculation as {@link #distancePointLinePerpendicular}. 86 | Unrolled here for performance. 87 | */ 88 | 89 | var s = ((A.Y - p.Y) * (B.X - A.X) - (A.X - p.X) * (B.Y - A.Y)) / len2; 90 | 91 | return Math.Abs(s) * Math.Sqrt(len2); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/ISpatialOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using NetTopologySuite.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial 9 | { 10 | public interface ISpatialOperation 11 | { 12 | /// 13 | /// Gets the distance between two a and b. 14 | /// 15 | /// First point. 16 | /// Second point. 17 | /// Distance between points in meters. 18 | double Distance(in Coordinate2D a, in Coordinate2D b); 19 | 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | double Length(ILineString line); 26 | 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | double Length(IMultiLineString line); 33 | 34 | /// 35 | /// Gets interception point of a LineString intercepted by Point c. 36 | /// This is analog to . 37 | /// The fraction f refers to the full length of the LineString. 38 | /// 39 | /// Line to be intercepted. 40 | /// Point that intercepts straight line a to b. 41 | /// Interception point described as the linearly interpolated fraction f in the interval [0,1] of the line 42 | double Intercept(ILineString p, in Coordinate2D c); 43 | 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | double Intercept(in Coordinate2D a, in Coordinate2D b, in Coordinate2D c); 52 | 53 | /// 54 | /// 55 | /// 56 | /// 57 | /// 58 | /// 59 | /// 60 | Coordinate2D Interpolate(in Coordinate2D a, in Coordinate2D b, double f); 61 | 62 | /// 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | Coordinate2D Interpolate(ILineString path, double f); 69 | 70 | /// 71 | /// 72 | /// 73 | /// 74 | /// 75 | /// 76 | /// 77 | Coordinate2D Interpolate(ILineString path, double l, double f); 78 | 79 | /// 80 | /// 81 | /// 82 | /// 83 | /// 84 | /// 85 | /// 86 | double Azimuth(in Coordinate2D a, in Coordinate2D b, double f); 87 | 88 | /// 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// 95 | double Azimuth(ILineString path, double f); 96 | 97 | /// 98 | /// 99 | /// 100 | /// 101 | /// 102 | /// 103 | /// 104 | double Azimuth(ILineString path, double l, double f); 105 | 106 | /// 107 | /// 108 | /// 109 | /// 110 | /// 111 | /// 112 | Envelope Envelope(in Coordinate2D c, double radius); 113 | 114 | 115 | Envelope Envelope(ILineString line); 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/AbstractNtsSpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using GeoAPI.Geometries; 6 | using NetTopologySuite.Index; 7 | using Sandwych.MapMatchingKit.Spatial.Geometries; 8 | 9 | namespace Sandwych.MapMatchingKit.Spatial.Index 10 | { 11 | public abstract class AbstractNtsSpatialIndex : AbstractSpatialIndex 12 | { 13 | protected abstract NetTopologySuite.Index.ISpatialIndex Index { get; } 14 | 15 | protected AbstractNtsSpatialIndex(IEnumerable items, ISpatialOperation spatialService, 16 | Func geometryGetter, Func lengthGetter) 17 | : base(items, spatialService, geometryGetter, lengthGetter) 18 | { 19 | 20 | } 21 | 22 | protected override void Add(TItem item) 23 | { 24 | var geom = this.ItemGeometryGetter(item); 25 | var env = this.Spatial.Envelope(geom as ILineString); 26 | this.Index.Insert(env, item); 27 | } 28 | 29 | protected override void AddRange(IEnumerable items) 30 | { 31 | foreach (var item in items) 32 | { 33 | this.Add(item); 34 | } 35 | } 36 | 37 | public override IEnumerable Search(Envelope envelope) 38 | { 39 | return this.Index.Query(envelope); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/AbstractSpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using GeoAPI.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial.Index 9 | { 10 | public abstract class AbstractSpatialIndex : ISpatialIndex 11 | { 12 | protected ISpatialOperation Spatial { get; } 13 | protected Func ItemGeometryGetter { get; } 14 | protected Func ItemLengthGetter { get; } 15 | public AbstractSpatialIndex(IEnumerable items, ISpatialOperation spatialService, 16 | Func geometryGetter, Func lengthGetter) 17 | { 18 | this.ItemGeometryGetter = geometryGetter; 19 | this.ItemLengthGetter = lengthGetter; 20 | this.Spatial = spatialService; 21 | this.AddRange(items); 22 | } 23 | 24 | public virtual IEnumerable<(TItem Item, double Distance, Coordinate2D InterpolatedPoint)> Radius(in Coordinate2D c, double radius, int k = -1) 25 | { 26 | var neighbors = new List<(TItem Item, double Distance, Coordinate2D InterpolatedPoint)>(20); 27 | var env = this.Spatial.Envelope(c, radius); 28 | var candidates = this.Search(env); 29 | foreach (var candidate in candidates) 30 | { 31 | var geometry = this.ItemGeometryGetter(candidate); 32 | var f = this.Spatial.Intercept(geometry, c); 33 | var p = this.Spatial.Interpolate(geometry, this.ItemLengthGetter(candidate), f); 34 | var d = this.Spatial.Distance(p, c); 35 | 36 | if (d <= radius) 37 | { 38 | neighbors.Add((candidate, f, p)); 39 | } 40 | } 41 | 42 | if (k > 0) 43 | { 44 | return neighbors.OrderBy(i => i.Distance).Take(k); 45 | } 46 | else 47 | { 48 | return neighbors; 49 | } 50 | } 51 | 52 | protected abstract void Add(TItem item); 53 | protected abstract void AddRange(IEnumerable items); 54 | 55 | public abstract IEnumerable Search(Envelope envelope); 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/ISpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using GeoAPI.Geometries; 2 | using Sandwych.MapMatchingKit.Spatial.Geometries; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Spatial.Index 8 | { 9 | public interface ISpatialIndex 10 | { 11 | IEnumerable Search(Envelope envelope); 12 | 13 | /// 14 | /// Gets objects stored in the index that are within a certain radius or overlap a certain radius. 15 | /// 16 | /// Center point for radius search. 17 | /// Radius in meters 18 | /// maximum number of candidates 19 | /// Result set of object(s) that are within a the given radius or overlap the radius, limited by k. 20 | IEnumerable<(TItem Item, double Distance, Coordinate2D InterpolatedPoint)> Radius(in Coordinate2D c, double radius, int k = -1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/NtsIndexItemVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NetTopologySuite.Index; 5 | 6 | namespace Sandwych.MapMatchingKit.Spatial.Index 7 | { 8 | public sealed class NtsIndexItemVisitor : IItemVisitor 9 | { 10 | public Action Action { get; } 11 | 12 | public NtsIndexItemVisitor(Action action) 13 | { 14 | this.Action = action; 15 | } 16 | 17 | public void VisitItem(T item) 18 | { 19 | this.Action(item); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/QuadtreeIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NetTopologySuite.Index.Quadtree; 5 | using NetTopologySuite.Geometries; 6 | using GeoAPI.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial.Index 9 | { 10 | public class QuadtreeIndex : AbstractNtsSpatialIndex 11 | { 12 | private readonly NetTopologySuite.Index.ISpatialIndex _index = new Quadtree(); 13 | 14 | protected override NetTopologySuite.Index.ISpatialIndex Index => _index; 15 | 16 | public QuadtreeIndex(IEnumerable items, ISpatialOperation spatialService, 17 | Func geometryGetter, Func lengthGetter) 18 | : base(items, spatialService, geometryGetter, lengthGetter) 19 | { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/Envelope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RBush 4 | { 5 | public readonly struct Envelope 6 | { 7 | public double MinX { get; } 8 | public double MinY { get; } 9 | public double MaxX { get; } 10 | public double MaxY { get; } 11 | 12 | public double Area => Math.Max(this.MaxX - this.MinX, 0) * Math.Max(this.MaxY - this.MinY, 0); 13 | public double Margin => Math.Max(this.MaxX - this.MinX, 0) + Math.Max(this.MaxY - this.MinY, 0); 14 | 15 | public Envelope(double minX, double minY, double maxX, double maxY) 16 | { 17 | this.MinX = minX; 18 | this.MinY = minY; 19 | this.MaxX = maxX; 20 | this.MaxY = maxY; 21 | } 22 | 23 | public Envelope Extend(in Envelope other) => 24 | new Envelope( 25 | Math.Min(this.MinX, other.MinX), 26 | Math.Min(this.MinY, other.MinY), 27 | Math.Max(this.MaxX, other.MaxX), 28 | Math.Max(this.MaxY, other.MaxY)); 29 | 30 | public Envelope Clone() 31 | { 32 | return new Envelope(this.MinX, this.MinY, this.MaxX, this.MaxY); 33 | } 34 | 35 | public Envelope Intersection(in Envelope other) => 36 | new Envelope( 37 | Math.Max(this.MinX, other.MinX), 38 | Math.Max(this.MinY, other.MinY), 39 | Math.Min(this.MaxX, other.MaxX), 40 | Math.Min(this.MaxY, other.MaxY) 41 | ); 42 | 43 | public Envelope Enlargement(in Envelope other) 44 | { 45 | var clone = this.Clone(); 46 | clone.Extend(other); 47 | return clone; 48 | } 49 | 50 | public bool Contains(in Envelope other) 51 | { 52 | return 53 | this.MinX <= other.MinX && 54 | this.MinY <= other.MinY && 55 | this.MaxX >= other.MaxX && 56 | this.MaxY >= other.MaxY; 57 | } 58 | 59 | public bool Intersects(in Envelope other) 60 | { 61 | return 62 | this.MinX <= other.MaxX && 63 | this.MinY <= other.MaxY && 64 | this.MaxX >= other.MinX && 65 | this.MaxY >= other.MinY; 66 | } 67 | 68 | public static Envelope InfiniteBounds => 69 | new Envelope( 70 | double.NegativeInfinity, 71 | double.NegativeInfinity, 72 | double.PositiveInfinity, 73 | double.PositiveInfinity); 74 | 75 | public static Envelope EmptyBounds => 76 | new Envelope( 77 | double.PositiveInfinity, 78 | double.PositiveInfinity, 79 | double.NegativeInfinity, 80 | double.NegativeInfinity); 81 | } 82 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/ISpatialData.cs: -------------------------------------------------------------------------------- 1 | namespace RBush 2 | { 3 | public interface ISpatialData 4 | { 5 | ref readonly Envelope Envelope { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/ISpatialDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RBush 4 | { 5 | public interface ISpatialDatabase : ISpatialIndex 6 | { 7 | void Insert(T item); 8 | void Delete(T item); 9 | void Clear(); 10 | 11 | void BulkLoad(IEnumerable items); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/ISpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RBush 4 | { 5 | public interface ISpatialIndex 6 | { 7 | IEnumerable Search(); 8 | IEnumerable Search(in Envelope boundingBox); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/ProjectionComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RBush 5 | { 6 | /// 7 | /// Non-generic class to produce instances of the generic class, 8 | /// optionally using type inference. 9 | /// 10 | public static class ProjectionComparer 11 | { 12 | /// 13 | /// Creates an instance of ProjectionComparer using the specified projection. 14 | /// 15 | /// Type parameter for the elements to be compared 16 | /// Type parameter for the keys to be compared, after being projected from the elements 17 | /// Projection to use when determining the key of an element 18 | /// A comparer which will compare elements by projecting each element to its key, and comparing keys 19 | public static ProjectionComparer Create(Func projection) 20 | { 21 | return new ProjectionComparer(projection); 22 | } 23 | 24 | /// 25 | /// Creates an instance of ProjectionComparer using the specified projection. 26 | /// The ignored parameter is solely present to aid type inference. 27 | /// 28 | /// Type parameter for the elements to be compared 29 | /// Type parameter for the keys to be compared, after being projected from the elements 30 | /// Value is ignored - type may be used by type inference 31 | /// Projection to use when determining the key of an element 32 | /// A comparer which will compare elements by projecting each element to its key, and comparing keys 33 | public static ProjectionComparer Create 34 | (TSource ignored, 35 | Func projection) 36 | { 37 | return new ProjectionComparer(projection); 38 | } 39 | 40 | } 41 | 42 | /// 43 | /// Class generic in the source only to produce instances of the 44 | /// doubly generic class, optionally using type inference. 45 | /// 46 | public static class ProjectionComparer 47 | { 48 | /// 49 | /// Creates an instance of ProjectionComparer using the specified projection. 50 | /// 51 | /// Type parameter for the keys to be compared, after being projected from the elements 52 | /// Projection to use when determining the key of an element 53 | /// A comparer which will compare elements by projecting each element to its key, and comparing keys 54 | public static ProjectionComparer Create(Func projection) 55 | { 56 | return new ProjectionComparer(projection); 57 | } 58 | } 59 | 60 | /// 61 | /// Comparer which projects each element of the comparison to a key, and then compares 62 | /// those keys using the specified (or default) comparer for the key type. 63 | /// 64 | /// Type of elements which this comparer will be asked to compare 65 | /// Type of the key projected from the element 66 | public readonly struct ProjectionComparer : IComparer 67 | { 68 | readonly Func projection; 69 | readonly IComparer comparer; 70 | 71 | /// 72 | /// Creates a new instance using the specified projection, which must not be null. 73 | /// The default comparer for the projected type is used. 74 | /// 75 | /// Projection to use during comparisons 76 | public ProjectionComparer(Func projection) 77 | : this(projection, null) 78 | { 79 | } 80 | 81 | /// 82 | /// Creates a new instance using the specified projection, which must not be null. 83 | /// 84 | /// Projection to use during comparisons 85 | /// The comparer to use on the keys. May be null, in 86 | /// which case the default comparer will be used. 87 | public ProjectionComparer(Func projection, IComparer comparer) 88 | { 89 | this.comparer = comparer ?? Comparer.Default; 90 | this.projection = projection; 91 | } 92 | 93 | /// 94 | /// Compares x and y by projecting them to keys and then comparing the keys. 95 | /// Null values are not projected; they obey the 96 | /// standard comparer contract such that two null values are equal; any null value is 97 | /// less than any non-null value. 98 | /// 99 | public int Compare(TSource x, TSource y) 100 | { 101 | // Don't want to project from nullity 102 | if (x == null && y == null) 103 | { 104 | return 0; 105 | } 106 | if (x == null) 107 | { 108 | return -1; 109 | } 110 | if (y == null) 111 | { 112 | return 1; 113 | } 114 | return comparer.Compare(projection(x), projection(y)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/RBush.Node.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace RBush 6 | { 7 | public partial class RBush 8 | { 9 | internal sealed class Node : ISpatialData 10 | { 11 | private Envelope _envelope; 12 | 13 | public Node(List items, int height) 14 | { 15 | this.Height = height; 16 | this.Children = items; 17 | ResetEnvelope(); 18 | } 19 | 20 | public void Add(ISpatialData node) 21 | { 22 | Children.Add(node); 23 | _envelope = Envelope.Extend(node.Envelope); 24 | } 25 | 26 | public void ResetEnvelope() 27 | { 28 | _envelope = GetEnclosingEnvelope(Children); 29 | } 30 | 31 | public List Children { get; } 32 | public int Height { get; } 33 | public bool IsLeaf => Height == 1; 34 | public ref readonly Envelope Envelope => ref _envelope; 35 | 36 | 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/RBush.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RBush 7 | { 8 | public partial class RBush : ISpatialDatabase, ISpatialIndex 9 | where T : ISpatialData 10 | { 11 | private const int DefaultMaxEntries = 9; 12 | private const int MinimumMaxEntries = 4; 13 | private const int MinimumMinEntries = 2; 14 | private const double DefaultFillFactor = 0.4; 15 | 16 | private readonly int maxEntries; 17 | private readonly int minEntries; 18 | internal Node root; 19 | 20 | public RBush() : this(DefaultMaxEntries) { } 21 | public RBush(int maxEntries) 22 | : this(maxEntries, EqualityComparer.Default) { } 23 | public RBush(int maxEntries, EqualityComparer comparer) 24 | { 25 | this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); 26 | this.minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this.maxEntries * DefaultFillFactor)); 27 | 28 | this.Clear(); 29 | } 30 | 31 | public int Count { get; private set; } 32 | 33 | public void Clear() 34 | { 35 | this.root = new Node(new List(), 1); 36 | this.Count = 0; 37 | } 38 | 39 | public IEnumerable Search() => GetAllChildren(this.root); 40 | 41 | public IEnumerable Search(in Envelope boundingBox) 42 | { 43 | return DoSearch(boundingBox).Select(x => (T)x.Peek()); 44 | } 45 | 46 | public void Insert(T item) 47 | { 48 | Insert(item, this.root.Height); 49 | this.Count++; 50 | } 51 | 52 | public void BulkLoad(IEnumerable items) 53 | { 54 | var data = items.Cast().ToList(); 55 | if (data.Count == 0) return; 56 | 57 | if (this.root.IsLeaf && 58 | this.root.Children.Count + data.Count < maxEntries) 59 | { 60 | foreach (var i in data) 61 | Insert((T)i); 62 | return; 63 | } 64 | 65 | if (data.Count < this.minEntries) 66 | { 67 | foreach (var i in data) 68 | Insert((T)i); 69 | return; 70 | } 71 | 72 | var dataRoot = BuildTree(data); 73 | this.Count += data.Count; 74 | 75 | if (this.root.Children.Count == 0) 76 | this.root = dataRoot; 77 | else if (this.root.Height == dataRoot.Height) 78 | { 79 | if (this.root.Children.Count + dataRoot.Children.Count <= this.maxEntries) 80 | { 81 | foreach (var isd in dataRoot.Children) 82 | this.root.Add(dataRoot); 83 | } 84 | else 85 | SplitRoot(dataRoot); 86 | } 87 | else 88 | { 89 | if (this.root.Height < dataRoot.Height) 90 | { 91 | var tmp = this.root; 92 | this.root = dataRoot; 93 | dataRoot = tmp; 94 | } 95 | 96 | this.Insert(dataRoot, this.root.Height - dataRoot.Height); 97 | } 98 | } 99 | 100 | public void Delete(T item) 101 | { 102 | var candidates = DoSearch(item.Envelope); 103 | 104 | foreach (var c in candidates 105 | .Where(c => object.Equals(item, c.Peek()))) 106 | { 107 | var path = c.Pop(); 108 | (path.Peek() as Node).Children.Remove(item); 109 | while (!path.IsEmpty) 110 | { 111 | (path.Peek() as Node).ResetEnvelope(); 112 | path = path.Pop(); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RBush/RBushSpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using NetTopologySuite.Index; 6 | 7 | namespace Sandwych.MapMatchingKit.Spatial.Index.RBush 8 | { 9 | using Sandwych.MapMatchingKit.Spatial.Geometries; 10 | using System.Linq; 11 | using RB = global::RBush; 12 | 13 | public class RBushSpatialIndex : AbstractSpatialIndex 14 | { 15 | private class RBushIndexItem : RB.ISpatialData 16 | { 17 | private global::RBush.Envelope _envelope; 18 | 19 | public RBushIndexItem(TItem item, in global::RBush.Envelope envelope) 20 | { 21 | this.Item = item; 22 | this._envelope = envelope; 23 | } 24 | 25 | public TItem Item { get; } 26 | 27 | public ref readonly global::RBush.Envelope Envelope => ref _envelope; 28 | } 29 | 30 | private readonly RB.RBush _index = new RB.RBush(); 31 | 32 | public RBushSpatialIndex( 33 | IEnumerable items, ISpatialOperation spatialService, 34 | Func geometryGetter, Func lengthGetter) 35 | : base(items, spatialService, geometryGetter, lengthGetter) 36 | { 37 | } 38 | 39 | protected override void Add(TItem item) 40 | { 41 | var env = this.Spatial.Envelope(ItemGeometryGetter(item) as ILineString); 42 | var rbEnv = new RB.Envelope(env.MinX, env.MinY, env.MaxX, env.MaxY); 43 | var rbItem = new RBushIndexItem(item, rbEnv); 44 | _index.Insert(rbItem); 45 | } 46 | 47 | protected override void AddRange(IEnumerable items) 48 | { 49 | foreach (var item in items) 50 | { 51 | this.Add(item); 52 | } 53 | } 54 | 55 | public override IEnumerable Search(Envelope envelope) 56 | { 57 | var rbEnv = new RB.Envelope(envelope.MinX, envelope.MinY, envelope.MaxX, envelope.MaxY); 58 | return _index.Search(rbEnv).Select(e => e.Item); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Index/RtreeIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NetTopologySuite.Index.Strtree; 5 | using NetTopologySuite.Geometries; 6 | using GeoAPI.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial.Index 9 | { 10 | public class RtreeIndex : AbstractNtsSpatialIndex 11 | { 12 | private readonly NetTopologySuite.Index.ISpatialIndex _index = new STRtree(); 13 | 14 | protected override NetTopologySuite.Index.ISpatialIndex Index => _index; 15 | 16 | public RtreeIndex(IEnumerable items, ISpatialOperation spatialService, 17 | Func geometryGetter, Func lengthGetter) 18 | : base(items, spatialService, geometryGetter, lengthGetter) 19 | { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Projection/AbstractWktCoordinateTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using GeoAPI.CoordinateSystems; 6 | using Sandwych.MapMatchingKit.Spatial.Geometries; 7 | using ProjNet.Converters.WellKnownText; 8 | 9 | namespace Sandwych.MapMatchingKit.Spatial.Projection 10 | { 11 | public abstract class AbstractWktCoordinateTransformation : ICoordinateTransformation 12 | { 13 | protected string FromWkt { get; } 14 | protected string ToWkt { get; } 15 | protected ICoordinateSystem FromCrs { get; } 16 | protected ICoordinateSystem ToCrs { get; } 17 | protected GeoAPI.CoordinateSystems.Transformations.ICoordinateTransformation InternalCoordinateTransformation { get; } 18 | 19 | public AbstractWktCoordinateTransformation(string fromWkt, string toWkt) 20 | { 21 | this.FromWkt = fromWkt; 22 | this.ToWkt = toWkt; 23 | this.FromCrs = CoordinateSystemWktReader.Parse(fromWkt, Encoding.ASCII) as ICoordinateSystem; 24 | this.ToCrs = CoordinateSystemWktReader.Parse(toWkt, Encoding.ASCII) as ICoordinateSystem; 25 | var factory = new ProjNet.CoordinateSystems.Transformations.CoordinateTransformationFactory(); 26 | this.InternalCoordinateTransformation = factory.CreateFromCoordinateSystems(this.FromCrs, this.ToCrs); 27 | } 28 | 29 | public double[] Transform(double[] from) => 30 | this.InternalCoordinateTransformation.MathTransform.Transform(from); 31 | 32 | public Coordinate2D Transform(Coordinate2D from) 33 | { 34 | var outArray = this.Transform(from.ToArray()); 35 | return new Coordinate2D(outArray[0], outArray[1]); 36 | } 37 | 38 | public Coordinate2D[] BulkTransform(IEnumerable from) 39 | { 40 | var inArray = new double[2]; 41 | var output = new Coordinate2D[from.Count()]; 42 | int i = 0; 43 | foreach (var coord in from) 44 | { 45 | inArray[0] = coord.X; 46 | inArray[1] = coord.Y; 47 | var outCoord = this.Transform(inArray); 48 | output[i] = new Coordinate2D(outCoord[0], outCoord[1]); 49 | i++; 50 | } 51 | return output; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Projection/Epsg3395To4326CoordinateTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Spatial.Projection 6 | { 7 | public sealed class Epsg3395To4326CoordinateTransformation : AbstractWktCoordinateTransformation 8 | { 9 | public Epsg3395To4326CoordinateTransformation() : base(WellKnownConstants.Epsg3395, WellKnownConstants.Epsg4326) 10 | { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Projection/Epsg4326To3395CoordinateTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Spatial.Projection 6 | { 7 | public sealed class Epsg4326To3395CoordinateTransformation : AbstractWktCoordinateTransformation 8 | { 9 | 10 | public Epsg4326To3395CoordinateTransformation() : base(WellKnownConstants.Epsg4326, WellKnownConstants.Epsg3395) 11 | { 12 | 13 | } 14 | 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Projection/ICoordinateTransformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using NetTopologySuite.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Spatial.Projection 9 | { 10 | public interface ICoordinateTransformation 11 | { 12 | double[] Transform(double[] from); 13 | Coordinate2D Transform(Coordinate2D from); 14 | Coordinate2D[] BulkTransform(IEnumerable from); 15 | } 16 | 17 | public static class CoordinateTransformationExtensions 18 | { 19 | public static ILineString Transform(this ICoordinateTransformation self, ILineString line) 20 | { 21 | var newCoords = new Coordinate[line.NumPoints]; 22 | for (var i = 0; i < line.NumPoints; i++) 23 | { 24 | var pt = line.GetCoordinateN(i); 25 | var coord = newCoords[i]; 26 | var oldCoord = new Coordinate2D(pt.X, pt.Y); 27 | var transformedCoord = self.Transform(oldCoord); 28 | newCoords[i] = new Coordinate(transformedCoord.X, transformedCoord.Y); 29 | } 30 | return new LineString(newCoords); 31 | } 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Spatial/Projection/WellKnownConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Spatial.Projection 6 | { 7 | public static class WellKnownConstants 8 | { 9 | public const string Epsg3395 = @"PROJCS[""WGS 84 / World Mercator"",GEOGCS[""WGS 84"",DATUM[""WGS_1984"",SPHEROID[""WGS 84"",6378137,298.257223563,AUTHORITY[""EPSG"",""7030""]],AUTHORITY[""EPSG"",""6326""]],PRIMEM[""Greenwich"",0,AUTHORITY[""EPSG"",""8901""]],UNIT[""degree"",0.0174532925199433,AUTHORITY[""EPSG"",""9122""]],AUTHORITY[""EPSG"",""4326""]],PROJECTION[""Mercator_1SP""],PARAMETER[""Latitude_of_origin"", 0],PARAMETER[""central_meridian"",0],PARAMETER[""scale_factor"",1],PARAMETER[""false_easting"",0],PARAMETER[""false_northing"",0],UNIT[""metre"",1,AUTHORITY[""EPSG"",""9001""]],AXIS[""Easting"",EAST],AXIS[""Northing"",NORTH],AUTHORITY[""EPSG"",""3395""]]"; 10 | 11 | public const string Epsg4326 = @"GEOGCS[""WGS 84"",DATUM[""WGS_1984"",SPHEROID[""WGS 84"",6378137,298.257223563,AUTHORITY[""EPSG"",""7030""]],AUTHORITY[""EPSG"",""6326""]],PRIMEM[""Greenwich"",0,AUTHORITY[""EPSG"",""8901""]],UNIT[""degree"",0.01745329251994328,AUTHORITY[""EPSG"",""9122""]],AUTHORITY[""EPSG"",""4326""]]"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/AbstractGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | 6 | namespace Sandwych.MapMatchingKit.Topology 7 | { 8 | 9 | public abstract class AbstractGraph : QuickGraph.AdjacencyGraph, IGraph 10 | where TEdge : AbstractGraphEdge 11 | { 12 | private bool _constructed = false; 13 | private readonly Dictionary _edgeMap = new Dictionary(); 14 | 15 | public AbstractGraph(IEnumerable edges) 16 | { 17 | if (edges == null) 18 | { 19 | throw new ArgumentNullException(nameof(edges)); 20 | } 21 | 22 | this.AddVerticesAndEdgeRange(edges); 23 | foreach (var e in edges) 24 | { 25 | _edgeMap.Add(e.Id, e); 26 | } 27 | 28 | this.Construct(); 29 | } 30 | 31 | public TEdge GetEdge(long id) => _edgeMap[id]; 32 | 33 | public IReadOnlyDictionary EdgeMap => _edgeMap; 34 | 35 | protected virtual void Construct() 36 | { 37 | if (_constructed) 38 | { 39 | throw new InvalidOperationException(); 40 | } 41 | 42 | var map = new Dictionary>(); 43 | 44 | foreach (var edge in this.EdgeMap.Values) 45 | { 46 | if (!map.ContainsKey(edge.Source)) 47 | { 48 | map[edge.Source] = new List() { edge }; 49 | } 50 | else 51 | { 52 | map[edge.Source].Add(edge); 53 | } 54 | } 55 | 56 | IList successors = null; 57 | foreach (var edges in map.Values) 58 | { 59 | for (int i = 1; i < edges.Count; ++i) 60 | { 61 | var prevEdge = edges[i - 1]; 62 | prevEdge.Neighbor = edges[i]; 63 | 64 | prevEdge.Successor = map.TryGetValue(prevEdge.Target, out successors) ? successors.First() : default; 65 | } 66 | 67 | var lastEdge = edges.Last(); 68 | lastEdge.Neighbor = edges.First(); 69 | lastEdge.Successor = map.TryGetValue(lastEdge.Target, out successors) ? successors.First() : default; 70 | } 71 | 72 | _constructed = true; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/AbstractGraphEdge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | 8 | /// 9 | /// Abstract edge in a directed . 10 | /// 11 | /// Implementation of in a directed graph. 12 | /// (Use the curiously recurring template pattern (CRTP) for type-safe use of customized edge type.) 13 | /// 14 | public abstract class AbstractGraphEdge : IGraphEdge, IEquatable 15 | where TEdge : class, IGraphEdge 16 | { 17 | public long Id { get; } 18 | 19 | public long Source { get; } 20 | 21 | public long Target { get; } 22 | 23 | public TEdge Neighbor { get; internal set; } 24 | 25 | public TEdge Successor { get; internal set; } 26 | 27 | 28 | public AbstractGraphEdge(long id, long source, long target) 29 | { 30 | this.Id = id; 31 | this.Source = source; 32 | this.Target = target; 33 | } 34 | 35 | public virtual IEnumerable Successors 36 | { 37 | get 38 | { 39 | var s = this.Successor; 40 | var i = s; 41 | while (i != null) 42 | { 43 | if (i == null) 44 | { 45 | yield return null; 46 | } 47 | else 48 | { 49 | var next = i; 50 | i = i.Neighbor == s ? null : i.Neighbor; 51 | yield return next; 52 | } 53 | } 54 | } 55 | } 56 | 57 | public override int GetHashCode() => this.Id.GetHashCode(); 58 | 59 | public virtual bool Equals(TEdge other) 60 | { 61 | if (object.ReferenceEquals(this, other)) 62 | { 63 | return true; 64 | } 65 | 66 | return this.Id == other.Id; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/BadGraphPathException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | public class BadGraphPathException : Exception 8 | { 9 | public BadGraphPathException() : base() 10 | { 11 | 12 | } 13 | 14 | public BadGraphPathException(string message) : base(message) 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/IEdgePoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | public interface IEdgePoint 8 | where TEdge : IGraphEdge, IEquatable 9 | { 10 | TEdge Edge { get; } 11 | double Fraction { get; } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/IGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | public interface IGraph : QuickGraph.IVertexAndEdgeListGraph 8 | where TEdge : IGraphEdge 9 | { 10 | TEdge GetEdge(long id); 11 | IReadOnlyDictionary EdgeMap { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/IGraphEdge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | /// 8 | /// The interface of edge in a directed . 9 | /// 10 | /// Implementation of in a directed graph. 11 | /// (Use the curiously recurring template pattern (CRTP) for type-safe use of customized edge type.) 12 | /// 13 | public interface IGraphEdge : QuickGraph.IEdge, IEquatable 14 | where T : IGraphEdge 15 | { 16 | /// 17 | /// Gets the edge's identifier. 18 | /// 19 | long Id { get; } 20 | 21 | /// 22 | /// Gets the edge's neighbor. 23 | /// 24 | T Neighbor { get; } 25 | 26 | /// 27 | /// Gets the edge's successor. 28 | /// 29 | T Successor { get; } 30 | 31 | /// 32 | /// Gets the edge's successor edges. 33 | /// 34 | IEnumerable Successors { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/IGraphRouter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | 6 | namespace Sandwych.MapMatchingKit.Topology 7 | { 8 | public interface IGraphRouter 9 | where TEdge : IGraphEdge 10 | where P : IEdgePoint, IEquatable

11 | { 12 | 13 | IEnumerable Route(P source, P target, 14 | Func cost, Func bound = null, double max = double.NaN); 15 | 16 | IReadOnlyDictionary> Route(P source, IEnumerable

targets, 17 | Func cost, Func bound = null, double max = double.NaN); 18 | 19 | IReadOnlyDictionary)> Route(IEnumerable

sources, IEnumerable

targets, 20 | Func cost, Func bound = null, double max = double.NaN); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/IPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology 6 | { 7 | public interface IPath 8 | where TEdge : IGraphEdge 9 | where TPoint : IEdgePoint 10 | { 11 | ref readonly TPoint StartPoint { get; } 12 | ref readonly TPoint EndPoint { get; } 13 | IEnumerable Edges { get; } 14 | float Length { get; } 15 | double Cost(Func costFunc); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/BoundedDijkstraShortestPathAlgorithm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using QuickGraph; 6 | using QuickGraph.Algorithms.ShortestPath; 7 | 8 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 9 | { 10 | 11 | public class BoundedDijkstraShortestPathAlgorithm 12 | where TVertex : IEquatable 13 | where TEdge : IEdge 14 | { 15 | private readonly HashSet _visitedVertices; 16 | private readonly Dictionary _vertexPredecessors; 17 | 18 | public Func BoundingCost { get; } 19 | public DijkstraShortestPathAlgorithm Algorithm { get; } 20 | public double MaxRadius { get; } 21 | public IEnumerable VisitedVertices => _visitedVertices; 22 | public IReadOnlyDictionary Predecessors => _vertexPredecessors; 23 | 24 | public BoundedDijkstraShortestPathAlgorithm( 25 | IVertexAndEdgeListGraph graph, Func cost, 26 | Func bound, double maxRadius) 27 | { 28 | if (maxRadius < 0D) 29 | { 30 | throw new ArgumentOutOfRangeException(nameof(maxRadius)); 31 | } 32 | 33 | var nVertices = graph.VertexCount; 34 | 35 | _visitedVertices = new HashSet(); 36 | _vertexPredecessors = new Dictionary(nVertices); 37 | 38 | this.Algorithm = new DijkstraShortestPathAlgorithm(graph, cost); 39 | this.Algorithm.ExamineVertex += this.ExamineVertex; 40 | this.Algorithm.TreeEdge += this.OnTreeEdge; 41 | this.BoundingCost = bound; 42 | this.MaxRadius = maxRadius; 43 | } 44 | 45 | private void Initialize() 46 | { 47 | _vertexPredecessors.Clear(); 48 | _visitedVertices.Clear(); 49 | } 50 | 51 | public void Compute(TVertex rootVertex) 52 | { 53 | this.Initialize(); 54 | 55 | this.Algorithm.Compute(rootVertex); 56 | } 57 | 58 | public double GetDistance(TVertex vertex) => 59 | this.Algorithm.Distances[vertex]; 60 | 61 | public bool TryGetDistance(TVertex vertex, out double distance) => 62 | this.Algorithm.TryGetDistance(vertex, out distance); 63 | 64 | private void ExamineVertex(TVertex vertex) 65 | { 66 | if (this.BoundingCost != null && !double.IsNaN(this.MaxRadius)) 67 | { 68 | if (this.TryGetPath(vertex, out var path) && path.Sum(this.BoundingCost) > this.MaxRadius) 69 | { 70 | throw new OutOfRadiusException(); 71 | } 72 | } 73 | 74 | this._visitedVertices.Add(vertex); 75 | } 76 | 77 | private void OnTreeEdge(TEdge e) 78 | { 79 | _vertexPredecessors[e.Target] = e; 80 | } 81 | 82 | public bool TryGetPath(TVertex vertex, out IEnumerable path) => 83 | this._vertexPredecessors.TryGetPath(vertex, out path); 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/OutOfRadiusException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 6 | { 7 | 8 | public class OutOfRadiusException : Exception 9 | { 10 | 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/PrecomputedDijkstraRouter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Extensions.Logging; 6 | using Sandwych.MapMatchingKit.Roads; 7 | using Sandwych.MapMatchingKit.Utility; 8 | 9 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 10 | { 11 | public class PrecomputedDijkstraRouter : IGraphRouter 12 | where TEdge : class, IGraphEdge, IEquatable 13 | where P : IEdgePoint, IEquatable

14 | { 15 | private static readonly TEdge[] EmptyPath = new TEdge[] { }; 16 | public ILogger Logger { get; set; } = NullLogger.Instance; 17 | 18 | private readonly IGraph _graph; 19 | private readonly PrecomputedDijkstraTable _precomputedTable; 20 | private readonly Func _cost; 21 | private readonly Func _boundingCost; 22 | private readonly double _maxRadius; 23 | 24 | public PrecomputedDijkstraRouter(IGraph graph, Func cost, Func boundingCost, double max) 25 | { 26 | _graph = graph; 27 | _cost = cost; 28 | _boundingCost = boundingCost; 29 | _maxRadius = max; 30 | _precomputedTable = this.CreateTable(); 31 | } 32 | 33 | public IReadOnlyDictionary> Route(P source, IEnumerable

targets, Func cost, 34 | Func bound = null, double max = double.NaN) 35 | { 36 | var dict = new Dictionary>(targets.Count()); 37 | foreach (var target in targets) 38 | { 39 | var path = this.Route(source, target, cost, bound, max); 40 | if (path != null && path.Count() > 0) 41 | { 42 | dict.Add(target, path); 43 | } 44 | } 45 | return dict; 46 | } 47 | 48 | public IReadOnlyDictionary)> Route(IEnumerable

sources, IEnumerable

targets, Func cost = null, 49 | Func bound = null, double max = double.NaN) 50 | { 51 | throw new NotSupportedException(); 52 | } 53 | 54 | public IEnumerable Route(P source, P target, Func cost, Func bound = null, double max = double.NaN) 55 | { 56 | try 57 | { 58 | var edges = RouteInternal(source, target, cost, bound, max); 59 | if (bound != null && !double.IsNaN(max)) 60 | { 61 | var boundingDistance = edges.Sum(bound); 62 | boundingDistance -= source.Edge.ComputeCost(source.Fraction, bound); 63 | boundingDistance -= target.Edge.ComputeCost(1D - target.Fraction, bound); 64 | if (boundingDistance > max) 65 | { 66 | return EmptyPath; 67 | } 68 | } 69 | return edges; 70 | } 71 | catch (BadGraphPathException bgpe) 72 | { 73 | this.Logger.LogError(bgpe.Message); 74 | return EmptyPath; 75 | } 76 | } 77 | 78 | private IEnumerable RouteInternal( 79 | P source, P target, Func cost, Func bound = null, double max = Double.NaN) 80 | { 81 | IEnumerable edges = EmptyPath; 82 | //On same road && forward 83 | if (source.Edge.Equals(target.Edge) && source.Fraction <= target.Fraction) 84 | { 85 | edges = GetPathOnSameRoad(source.Edge); 86 | } 87 | else if (source.Edge.Target.Equals(target.Edge.Source)) //is neighborhood 88 | { 89 | edges = GetPathOnNeighborhoodRoad(source.Edge, target.Edge); 90 | } 91 | else 92 | { 93 | var t = _precomputedTable.GetPathByVertex(source.Edge.Target, target.Edge.Source); 94 | return CombineHeadTailEdges(source.Edge, target.Edge, t.Path); 95 | } 96 | 97 | return edges; 98 | } 99 | 100 | private PrecomputedDijkstraTable CreateTable() 101 | { 102 | var generator = new PrecomputedDijkstraTableGenerator(); 103 | var rows = generator.ComputeRows(_graph, _cost, _boundingCost, _maxRadius); 104 | return new PrecomputedDijkstraTable(rows); 105 | } 106 | 107 | private static IEnumerable GetPathOnSameRoad(TEdge sourceEdge) 108 | { 109 | yield return sourceEdge; 110 | yield break; 111 | } 112 | 113 | private static IEnumerable GetPathOnNeighborhoodRoad(TEdge sourceEdge, TEdge targetEdge) 114 | { 115 | yield return sourceEdge; 116 | yield return targetEdge; 117 | yield break; 118 | } 119 | 120 | private static IEnumerable CombineHeadTailEdges(TEdge sourceEdge, TEdge targetEdge, IEnumerable bodyPath) 121 | { 122 | yield return sourceEdge; 123 | foreach (var e in bodyPath) 124 | { 125 | yield return e; 126 | } 127 | yield return targetEdge; 128 | } 129 | 130 | } 131 | 132 | } 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/PrecomputedDijkstraTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using QuickGraph; 5 | using System.Text; 6 | 7 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 8 | { 9 | 10 | public sealed class PrecomputedDijkstraTable : Dictionary<(TVertex, TVertex), PrecomputedDijkstraTableRow> 11 | where TEdge : class, IEdge 12 | where TVertex : IEquatable 13 | { 14 | private readonly static IEnumerable EmptyPath = new TEdge[] { }; 15 | 16 | public PrecomputedDijkstraTable() : base() 17 | { 18 | 19 | } 20 | 21 | public PrecomputedDijkstraTable(IEnumerable> rows) : 22 | this(rows.Count()) 23 | { 24 | foreach (var row in rows) 25 | { 26 | var pair = row.ToKeyValuePair(); 27 | this.Add(pair.Key, pair.Value); 28 | } 29 | } 30 | 31 | public PrecomputedDijkstraTable(int capacity) : base(capacity) 32 | { 33 | } 34 | 35 | public bool HasPathByVertex(TVertex sourceVertex, TVertex targetVertex) 36 | { 37 | if (IsSameVertex(sourceVertex, targetVertex)) 38 | { 39 | return false; 40 | } 41 | else 42 | { 43 | return this.ContainsKey((sourceVertex, targetVertex)); 44 | } 45 | } 46 | 47 | public bool HasPathByEdge(TEdge sourceEdge, TEdge targetEdge) 48 | { 49 | if (IsSameEdge(sourceEdge, targetEdge)) 50 | { 51 | throw new InvalidOperationException(); 52 | } 53 | else if (IsNeighbor(sourceEdge, targetEdge)) 54 | { 55 | return true; 56 | } 57 | else 58 | { 59 | return this.HasPathByVertex(sourceEdge.Target, targetEdge.Source); 60 | } 61 | } 62 | 63 | public (IEnumerable Path, double Distance) GetPathByVertex(TVertex sourceVertex, TVertex targetVertex, double maxDistance = double.PositiveInfinity) 64 | { 65 | if (IsSameVertex(sourceVertex, targetVertex)) 66 | { 67 | throw new ArgumentException(); 68 | } 69 | 70 | if (this.TryGetValue((sourceVertex, targetVertex), out var firstRow) && firstRow.Distance <= maxDistance) 71 | { 72 | return (this.GetPathFrom(firstRow), firstRow.Distance); 73 | } 74 | else 75 | { 76 | return (EmptyPath, double.NaN); 77 | } 78 | } 79 | 80 | private IEnumerable GetPathFrom(PrecomputedDijkstraTableRow startRow) 81 | { 82 | var row = startRow; 83 | //now we got the first edge, then we started from the next row 84 | yield return row.SourceEdge; 85 | var currentStart = row.NextVertex; 86 | var sourceVertex = startRow.SourceVertex; 87 | var targetVertex = startRow.TargetVertex; 88 | 89 | while (!currentStart.Equals(targetVertex)) 90 | { 91 | if (this.TryGetValue((currentStart, targetVertex), out row)) 92 | { 93 | yield return row.SourceEdge; 94 | currentStart = row.NextVertex; 95 | } 96 | else 97 | { 98 | var msg = string.Format( 99 | "Bad precomputed Dijkstra path: [sourceVertex={0}, targetVertex={1}, currentVertex={2}]", 100 | sourceVertex, targetVertex, currentStart); 101 | throw new BadGraphPathException(msg); 102 | } 103 | } 104 | } 105 | 106 | private static bool IsSameVertex(TVertex sourceVertex, TVertex targetVertex) => 107 | sourceVertex.Equals(targetVertex); 108 | 109 | private static bool IsNeighbor(TEdge sourceEdge, TEdge targetEdge) => 110 | sourceEdge.Target.Equals(targetEdge.Source); 111 | 112 | private static bool IsSameEdge(TEdge sourceEdge, TEdge targetEdge) => 113 | sourceEdge.Source.Equals(targetEdge.Source) && sourceEdge.Target.Equals(targetEdge.Target); 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/PrecomputedDijkstraTableGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using QuickGraph; 5 | using Microsoft.Extensions.Logging; 6 | using Sandwych.MapMatchingKit.Utility; 7 | 8 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 9 | { 10 | public class PrecomputedDijkstraTableGenerator 11 | where TEdge : class, IEdge 12 | where TVertex : IEquatable 13 | 14 | { 15 | public ILogger Logger { get; set; } = NullLogger.Instance; 16 | 17 | public IEnumerable> ComputeRows( 18 | IVertexAndEdgeListGraph graph, 19 | Func cost, Func bound, double maxRadius) 20 | { 21 | var pquery = graph.Vertices.AsParallel(); 22 | var allRows = pquery.SelectMany(rootVertex => this.ComputeRowsSingleSource(graph, rootVertex, cost, bound, maxRadius)); 23 | var rowsList = allRows.ToArray(); 24 | 25 | if (this.Logger.IsEnabled(LogLevel.Debug)) 26 | { 27 | this.Logger.LogDebug("Rows count: {0}", rowsList.Length); 28 | } 29 | 30 | return rowsList; 31 | } 32 | 33 | private IEnumerable> ComputeRowsSingleSource( 34 | IVertexAndEdgeListGraph graph, 35 | TVertex sourceVertex, 36 | Func cost, Func bound, double maxRadius) 37 | { 38 | var dijkstra = new BoundedDijkstraShortestPathAlgorithm(graph, cost, bound, maxRadius); 39 | 40 | try 41 | { 42 | dijkstra.Compute(sourceVertex); 43 | } 44 | catch (OutOfRadiusException) 45 | { 46 | } 47 | 48 | var pred = dijkstra.Predecessors; 49 | foreach (var u in dijkstra.VisitedVertices) 50 | { 51 | if (u.Equals(sourceVertex)) 52 | { 53 | continue; 54 | } 55 | 56 | var v = u; 57 | while (!pred[v].Source.Equals(sourceVertex)) 58 | { 59 | v = pred[v].Source; 60 | } 61 | graph.TryGetEdge(sourceVertex, v, out var sourceEdge); 62 | var targetEdge = pred[u]; 63 | var row = new PrecomputedDijkstraTableRow(sourceEdge, targetEdge, dijkstra.GetDistance(u)); 64 | yield return row; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/PrecomputedDijkstra/PrecomputedDijkstraTableRow.cs: -------------------------------------------------------------------------------- 1 | using QuickGraph; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra 7 | { 8 | public class PrecomputedDijkstraTableRow 9 | where TVertex : IEquatable 10 | where TEdge : class, IEdge 11 | { 12 | public TEdge SourceEdge { get; } 13 | public TEdge TargetEdge { get; } 14 | public double Distance { get; } 15 | 16 | public TVertex SourceVertex => this.SourceEdge.Source; 17 | public TVertex TargetVertex => this.TargetEdge.Target; 18 | public TVertex NextVertex => this.SourceEdge.Target; 19 | 20 | public PrecomputedDijkstraTableRow(TEdge s, TEdge t, double distance) 21 | { 22 | this.SourceEdge = s ?? throw new ArgumentNullException(nameof(s)); 23 | this.TargetEdge = t ?? throw new ArgumentNullException(nameof(t)); 24 | this.Distance = distance; 25 | } 26 | 27 | public KeyValuePair<(TVertex, TVertex), PrecomputedDijkstraTableRow> ToKeyValuePair() => 28 | new KeyValuePair<(TVertex, TVertex), PrecomputedDijkstraTableRow>((this.SourceVertex, this.TargetVertex), this); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Topology/RouteMark.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Utility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Topology 7 | { 8 | ///

9 | /// Route mark representation. 10 | /// 11 | internal readonly struct RouteMark : IComparable>, IEquatable> 12 | where TEdge : class, IGraphEdge 13 | { 14 | public TEdge MarkedEdge { get; } 15 | public TEdge PredecessorEdge { get; } 16 | public double Cost { get; } 17 | public double BoundingCost { get; } 18 | 19 | private readonly static RouteMark s_empty = new RouteMark(null, null, double.NaN, double.NaN); 20 | public static ref readonly RouteMark Empty => ref s_empty; 21 | public bool IsEmpty => double.IsNaN(this.Cost); 22 | 23 | /// 24 | /// Constructor of an entry. 25 | /// 26 | /// {@link AbstractEdge} defining the route mark. 27 | /// Predecessor {@link AbstractEdge}. 28 | /// Cost value to this route mark. 29 | /// Bounding cost value to this route mark. 30 | public RouteMark(TEdge markedEdge, TEdge predecessorEdge, double cost, double boundingCost) 31 | { 32 | this.MarkedEdge = markedEdge; 33 | this.PredecessorEdge = predecessorEdge; 34 | this.Cost = cost; 35 | this.BoundingCost = boundingCost; 36 | } 37 | 38 | public int CompareTo(RouteMark other) 39 | { 40 | if (this.IsEmpty || other.IsEmpty) 41 | { 42 | throw new InvalidOperationException(); 43 | } 44 | 45 | if (this.Cost < other.Cost) 46 | { 47 | return -1; 48 | } 49 | else if (this.Cost > other.Cost) 50 | { 51 | return 1; 52 | } 53 | else 54 | { 55 | return 0; 56 | } 57 | } 58 | 59 | public override int GetHashCode() => 60 | PredecessorEdge != null ? HashCodeHelper.Combine(MarkedEdge.GetHashCode(), PredecessorEdge.GetHashCode(), Cost.GetHashCode()) 61 | : HashCodeHelper.Combine(MarkedEdge.GetHashCode(), Cost.GetHashCode()); 62 | 63 | public bool Equals(RouteMark other) 64 | { 65 | if (this.IsEmpty || other.IsEmpty) 66 | { 67 | throw new InvalidOperationException(); 68 | } 69 | return this.CompareTo(other) == 0; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Utility/DequeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Nito.Collections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Utility 7 | { 8 | public static class DequeExtensions 9 | { 10 | public static T PeekFirst(this Deque self) => 11 | self[0]; 12 | 13 | public static T PeekLast(this Deque self) => 14 | self[self.Count - 1]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Utility/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.MapMatchingKit.Utility 6 | { 7 | public static class DictionaryExtensions 8 | { 9 | public static TValue GetOrNull(this IReadOnlyDictionary self, in TKey key) where TValue : class 10 | => self.TryGetValue(key, out var value) ? value : null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Utility/HashCodeHelper.cs: -------------------------------------------------------------------------------- 1 | //From QuickGraph 2 | //Licensed in MS-PL 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | 9 | namespace Sandwych.MapMatchingKit.Utility 10 | { 11 | public static class HashCodeHelper 12 | { 13 | const Int32 FNV1_prime_32 = 16777619; 14 | const Int32 FNV1_basis_32 = unchecked((int)2166136261); 15 | const Int64 FNV1_prime_64 = 1099511628211; 16 | const Int64 FNV1_basis_64 = unchecked((int)14695981039346656037); 17 | 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static Int32 GetHashCode(Int64 x) 20 | { 21 | return Combine((Int32)x, (Int32)(((UInt64)x) >> 32)); 22 | } 23 | 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | private static Int32 Fold(Int32 hash, byte value) 26 | { 27 | return (hash * FNV1_prime_32) ^ (Int32)value; 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | private static Int32 Fold(Int32 hash, Int32 value) 32 | { 33 | return Fold(Fold(Fold(Fold(hash, 34 | (byte)value), 35 | (byte)(((UInt32)value) >> 8)), 36 | (byte)(((UInt32)value) >> 16)), 37 | (byte)(((UInt32)value) >> 24)); 38 | } 39 | 40 | /// 41 | /// Combines two hashcodes in a strong way. 42 | /// 43 | /// 44 | /// 45 | /// 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public static Int32 Combine(Int32 x, Int32 y) 48 | { 49 | return Fold(Fold(FNV1_basis_32, x), y); 50 | } 51 | 52 | /// 53 | /// Combines three hashcodes in a strong way. 54 | /// 55 | /// 56 | /// 57 | /// 58 | /// 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public static Int32 Combine(Int32 x, Int32 y, Int32 z) 61 | { 62 | return Fold(Fold(Fold(FNV1_basis_32, x), y), z); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Utility/NullLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Sandwych.MapMatchingKit.Utility 8 | { 9 | /// 10 | /// An empty scope without any logic 11 | /// 12 | public class NullScope : IDisposable 13 | { 14 | public static NullScope Instance { get; } = new NullScope(); 15 | 16 | private NullScope() 17 | { 18 | } 19 | 20 | /// 21 | public void Dispose() 22 | { 23 | } 24 | } 25 | 26 | /// 27 | /// Minimalistic logger that does nothing. 28 | /// 29 | public class NullLogger : ILogger 30 | { 31 | public static NullLogger Instance { get; } = new NullLogger(); 32 | 33 | private NullLogger() 34 | { 35 | } 36 | 37 | /// 38 | public IDisposable BeginScope(TState state) 39 | { 40 | return NullScope.Instance; 41 | } 42 | 43 | /// 44 | public bool IsEnabled(LogLevel logLevel) 45 | { 46 | return false; 47 | } 48 | 49 | /// 50 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 51 | { 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Sandwych.MapMatchingKit/Utility/PriorityQueue.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the Apache 2.0 License. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | // Copy from https://github.com/Reactive-Extensions/Rx.NET/blob/master/Rx.NET/Source/src/System.Reactive/Internal/PriorityQueue.cs 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | 11 | namespace Sandwych.MapMatchingKit.Utility 12 | { 13 | public sealed class PriorityQueue where T : IComparable 14 | { 15 | private readonly struct IndexedItem 16 | { 17 | public T Value { get; } 18 | public int Id { get; } 19 | 20 | public IndexedItem(in T value, int id) 21 | { 22 | this.Value = value; 23 | this.Id = id; 24 | } 25 | 26 | public int CompareTo(in IndexedItem other) 27 | { 28 | var c = Value.CompareTo(other.Value); 29 | if (c == 0) 30 | { 31 | c = Id.CompareTo(other.Id); 32 | } 33 | 34 | return c; 35 | } 36 | 37 | } 38 | 39 | private static int _count = int.MinValue; 40 | private IndexedItem[] _items; 41 | private int _size; 42 | 43 | public PriorityQueue() 44 | : this(16) 45 | { 46 | } 47 | 48 | public PriorityQueue(int capacity) 49 | { 50 | _items = new IndexedItem[capacity]; 51 | _size = 0; 52 | } 53 | 54 | private bool IsHigherPriority(int left, int right) 55 | { 56 | ref var leftItem = ref _items[left]; 57 | ref var rightItem = ref _items[right]; 58 | return leftItem.CompareTo(rightItem) < 0; 59 | } 60 | 61 | private void Percolate(int index) 62 | { 63 | if (index >= _size || index < 0) 64 | { 65 | return; 66 | } 67 | 68 | var parent = (index - 1) / 2; 69 | if (parent < 0 || parent == index) 70 | { 71 | return; 72 | } 73 | 74 | if (IsHigherPriority(index, parent)) 75 | { 76 | var temp = _items[index]; 77 | _items[index] = _items[parent]; 78 | _items[parent] = temp; 79 | this.Percolate(parent); 80 | } 81 | } 82 | 83 | private void Heapify() => this.Heapify(0); 84 | 85 | private void Heapify(int index) 86 | { 87 | if (index >= _size || index < 0) 88 | { 89 | return; 90 | } 91 | 92 | var left = 2 * index + 1; 93 | var right = 2 * index + 2; 94 | var first = index; 95 | 96 | if (left < _size && this.IsHigherPriority(left, first)) 97 | { 98 | first = left; 99 | } 100 | 101 | if (right < _size && this.IsHigherPriority(right, first)) 102 | { 103 | first = right; 104 | } 105 | 106 | if (first != index) 107 | { 108 | var temp = _items[index]; 109 | _items[index] = _items[first]; 110 | _items[first] = temp; 111 | this.Heapify(first); 112 | } 113 | } 114 | 115 | public int Count => _size; 116 | 117 | public T Peek() 118 | { 119 | if (_size == 0) 120 | { 121 | throw new InvalidOperationException("Heap is empty"); 122 | } 123 | 124 | return _items[0].Value; 125 | } 126 | 127 | private void RemoveAt(int index) 128 | { 129 | _items[index] = _items[--_size]; 130 | 131 | this.Heapify(); 132 | this.ShrinkWhenRequired(); 133 | } 134 | 135 | private void ShrinkWhenRequired() 136 | { 137 | if (_size < _items.Length / 4) 138 | { 139 | var temp = _items; 140 | _items = new IndexedItem[_items.Length / 2]; 141 | Array.Copy(temp, 0, _items, 0, _size); 142 | } 143 | } 144 | 145 | public T Dequeue() 146 | { 147 | var result = this.Peek(); 148 | this.RemoveAt(0); 149 | return result; 150 | } 151 | 152 | public void Enqueue(T item) 153 | { 154 | this.GrowWhenRequired(); 155 | 156 | var index = _size++; 157 | _count++; 158 | _items[index] = new IndexedItem(item, _count); 159 | this.Percolate(index); 160 | } 161 | 162 | private void GrowWhenRequired() 163 | { 164 | if (_size >= _items.Length) 165 | { 166 | var temp = _items; 167 | _items = new IndexedItem[_items.Length * 2]; 168 | Array.Copy(temp, _items, temp.Length); 169 | } 170 | } 171 | 172 | public bool Remove(in T item) 173 | { 174 | for (var i = 0; i < _size; ++i) 175 | { 176 | if (_items[i].Value.CompareTo(item) == 0) 177 | { 178 | RemoveAt(i); 179 | return true; 180 | } 181 | } 182 | 183 | return false; 184 | } 185 | 186 | } //class PriorityQueue 187 | 188 | } 189 | -------------------------------------------------------------------------------- /test/Sandwych.Hmm.Tests/ForwardBackwardAlgorithmTest.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016, BMW AG 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | using Xunit; 22 | 23 | namespace Sandwych.Hmm.Tests 24 | { 25 | 26 | public class ForwardBackwardAlgorithmTest 27 | { 28 | /** 29 | * Example taken from https://en.wikipedia.org/wiki/Forward%E2%80%93backward_algorithm. 30 | */ 31 | [Fact] 32 | public void testForwardBackward() 33 | { 34 | List candidates = new List(); 35 | candidates.Add(Rain.T); 36 | candidates.Add(Rain.F); 37 | 38 | var initialStateProbabilities = new Dictionary(); 39 | initialStateProbabilities.Add(Rain.T, 0.5); 40 | initialStateProbabilities.Add(Rain.F, 0.5); 41 | 42 | var emissionProbabilitiesForUmbrella = new Dictionary(); 43 | emissionProbabilitiesForUmbrella.Add(Rain.T, 0.9); 44 | emissionProbabilitiesForUmbrella.Add(Rain.F, 0.2); 45 | 46 | var emissionProbabilitiesForNoUmbrella = new Dictionary(); 47 | emissionProbabilitiesForNoUmbrella.Add(Rain.T, 0.1); 48 | emissionProbabilitiesForNoUmbrella.Add(Rain.F, 0.8); 49 | 50 | var transitionProbabilities = new Dictionary, double>(); 51 | transitionProbabilities.Add(new Transition(Rain.T, Rain.T), 0.7); 52 | transitionProbabilities.Add(new Transition(Rain.T, Rain.F), 0.3); 53 | transitionProbabilities.Add(new Transition(Rain.F, Rain.T), 0.3); 54 | transitionProbabilities.Add(new Transition(Rain.F, Rain.F), 0.7); 55 | 56 | var fw = new ForwardBackwardModel(); 57 | fw.Start(candidates, initialStateProbabilities); 58 | fw.NextStep(Umbrella.T, candidates, emissionProbabilitiesForUmbrella, 59 | transitionProbabilities); 60 | fw.NextStep(Umbrella.T, candidates, emissionProbabilitiesForUmbrella, 61 | transitionProbabilities); 62 | fw.NextStep(Umbrella.F, candidates, emissionProbabilitiesForNoUmbrella, 63 | transitionProbabilities); 64 | fw.NextStep(Umbrella.T, candidates, emissionProbabilitiesForUmbrella, 65 | transitionProbabilities); 66 | fw.NextStep(Umbrella.T, candidates, emissionProbabilitiesForUmbrella, 67 | transitionProbabilities); 68 | 69 | var result = fw.ComputeSmoothingProbabilities(); 70 | Assert.Equal(6, result.Count); 71 | var DELTA = 4; //1e-4; 72 | Assert.Equal(0.6469, result[0][Rain.T], DELTA); 73 | Assert.Equal(0.3531, result[0][Rain.F], DELTA); 74 | Assert.Equal(0.8673, result[1][Rain.T], DELTA); 75 | Assert.Equal(0.1327, result[1][Rain.F], DELTA); 76 | Assert.Equal(0.8204, result[2][Rain.T], DELTA); 77 | Assert.Equal(0.1796, result[2][Rain.F], DELTA); 78 | Assert.Equal(0.3075, result[3][Rain.T], DELTA); 79 | Assert.Equal(0.6925, result[3][Rain.F], DELTA); 80 | Assert.Equal(0.8204, result[4][Rain.T], DELTA); 81 | Assert.Equal(0.1796, result[4][Rain.F], DELTA); 82 | Assert.Equal(0.8673, result[5][Rain.T], DELTA); 83 | Assert.Equal(0.1327, result[5][Rain.F], DELTA); 84 | } 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /test/Sandwych.Hmm.Tests/Model.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.Hmm.Tests 6 | { 7 | public readonly struct Rain 8 | { 9 | public readonly static Rain T = new Rain("Rain"); 10 | public readonly static Rain F = new Rain("Sun"); 11 | 12 | private readonly string _value; 13 | 14 | public Rain(string value) 15 | { 16 | _value = value; 17 | } 18 | 19 | public override String ToString() => _value; 20 | public override int GetHashCode() => _value.GetHashCode(); 21 | 22 | } 23 | 24 | public readonly struct Umbrella 25 | { 26 | public readonly static Umbrella T = new Umbrella("Umbrella"); 27 | public readonly static Umbrella F = new Umbrella("No umbrella"); 28 | 29 | private readonly string _value; 30 | 31 | public Umbrella(string value) 32 | { 33 | _value = value; 34 | } 35 | 36 | public override String ToString() => _value; 37 | public override int GetHashCode() => _value.GetHashCode(); 38 | } 39 | 40 | public class Descriptor 41 | { 42 | public readonly static Descriptor R2R = new Descriptor("R2R"); 43 | public readonly static Descriptor R2S = new Descriptor("R2S"); 44 | public readonly static Descriptor S2R = new Descriptor("S2R"); 45 | public readonly static Descriptor S2S = new Descriptor("S2S"); 46 | 47 | private readonly string _value; 48 | public Descriptor(string value) 49 | { 50 | _value = value; 51 | } 52 | 53 | public override String ToString() => _value; 54 | public override int GetHashCode() => _value.GetHashCode(); 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /test/Sandwych.Hmm.Tests/Sandwych.Hmm.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | Library 7 | latest 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/DistributionsTest.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using Sandwych.MapMatchingKit.Markov; 19 | using System; 20 | using Xunit; 21 | 22 | namespace Sandwych.MapMatchingKit.Tests 23 | { 24 | public class DistributionsTest 25 | { 26 | //private static double Delta = 1e-8; 27 | private const int Precision = 8; 28 | 29 | 30 | [Fact] 31 | public void TestLogNormalDistribution() 32 | { 33 | Assert.Equal(Math.Log(Distributions.NormalDistribution(5, 6)), 34 | Distributions.LogNormalDistribution(5, 6), Precision); 35 | } 36 | 37 | [Fact] 38 | public void TestLogExponentialDistribution() 39 | { 40 | Assert.Equal(Math.Log(Distributions.ExponentialDistribution(5, 6)), 41 | Distributions.LogExponentialDistribution(5, 6), Precision); 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Markov/MockSample.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Markov; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Tests.Markov 7 | { 8 | public readonly struct MockSample : ISample 9 | { 10 | public DateTimeOffset Time { get; } 11 | 12 | public MockSample(long time) 13 | { 14 | this.Time = DateTimeOffset.MinValue.AddMilliseconds(time); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Matching/MatcherSampleTest.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Matching; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | using Sandwych.MapMatchingKit.Spatial.Geometries; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Matching 9 | { 10 | public class MatcherSampleTest : TestBase 11 | { 12 | [Fact] 13 | public void TestAzimuth() 14 | { 15 | { 16 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), -0.1f); 17 | Assert.Equal(359.9, sample.Azimuth, 1); 18 | } 19 | { 20 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), -359.9f); 21 | Assert.Equal(0.1, sample.Azimuth, 1); 22 | } 23 | { 24 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), -360.1f); 25 | Assert.Equal(359.9, sample.Azimuth, 1); 26 | } 27 | { 28 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), 360f); 29 | Assert.Equal(0.0, sample.Azimuth, 1); 30 | } 31 | { 32 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), 360.1f); 33 | Assert.Equal(0.1, sample.Azimuth, 1); 34 | } 35 | { 36 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), 720.1f); 37 | Assert.Equal(0.1, sample.Azimuth, 1); 38 | } 39 | { 40 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), -719.9f); 41 | Assert.Equal(0.1, sample.Azimuth, 1); 42 | } 43 | { 44 | var sample = new MatcherSample(0, 0L, new Coordinate2D(1, 1), -720.1f); 45 | Assert.Equal(359.9, sample.Azimuth, 1); 46 | } 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Model/GpsMeasurement.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015, BMW Car IT GmbH 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.MapMatchingKit.Tests.Model 23 | { 24 | /** 25 | * Example type for location coordinates. 26 | */ 27 | public readonly struct GpsMeasurement 28 | { 29 | 30 | public long Time { get; } 31 | 32 | public Point Position { get; } 33 | 34 | public GpsMeasurement(in long time, in Point position) 35 | { 36 | this.Time = time; 37 | this.Position = position; 38 | } 39 | 40 | public GpsMeasurement(in long time, in double lon, in double lat) : this(time, new Point(lon, lat)) 41 | { 42 | } 43 | 44 | public override String ToString() 45 | { 46 | return "GpsMeasurement [time=" + this.Time + ", position=" + this.Position + "]"; 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Model/Point.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015, BMW Car IT GmbH 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.MapMatchingKit.Tests.Model 23 | { 24 | /** 25 | * Represents a spatial point. 26 | */ 27 | public readonly struct Point 28 | { 29 | 30 | public double X { get; } 31 | public double Y { get; } 32 | 33 | public Point(double x, double y) 34 | { 35 | this.X = x; 36 | this.Y = y; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return "Point [x=" + X + ", y=" + Y + "]"; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Model/RoadPath.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.MapMatchingKit.Tests.Model 23 | { 24 | /** 25 | * Represents the road path between two consecutive road positions. 26 | */ 27 | public class RoadPath : IEquatable 28 | { 29 | // The following members are used to check whether the correct road paths are retrieved 30 | // from the most likely sequence. 31 | public RoadPosition From { get; } 32 | public RoadPosition To { get; } 33 | 34 | public RoadPath(RoadPosition from, RoadPosition to) 35 | { 36 | this.From = from; 37 | this.To = to; 38 | } 39 | 40 | public bool Equals(RoadPath other) 41 | { 42 | return this.From.Equals(other.From) && this.To.Equals(other.To); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Model/RoadPosition.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015, BMW Car IT GmbH 3 | * Author: Stefan Holder (stefan.holder@bmw.de) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Text; 21 | 22 | namespace Sandwych.MapMatchingKit.Tests.Model 23 | { 24 | /** 25 | * Default type to represent the position of a vehicle on the road network. 26 | * It is also possible to use a custom road position class instead. 27 | */ 28 | public class RoadPosition : IEquatable 29 | { 30 | /** 31 | * ID of the edge, on which the vehicle is positioned. 32 | */ 33 | public long EdgeId { get; } 34 | 35 | /** 36 | * Position on the edge from beginning as a number in the interval [0,1]. 37 | */ 38 | public double Fraction { get; } 39 | 40 | public Point Position { get; } 41 | 42 | public RoadPosition(in long edgeId, in double fraction, in Point position) 43 | { 44 | if (fraction < 0.0 || fraction > 1.0) 45 | { 46 | throw new InvalidOperationException(); 47 | } 48 | 49 | this.EdgeId = edgeId; 50 | this.Fraction = fraction; 51 | this.Position = position; 52 | } 53 | 54 | public RoadPosition(in long edgeId, in double fraction, in double x, in double y) : this(edgeId, fraction, new Point(x, y)) 55 | { 56 | } 57 | 58 | public override string ToString() 59 | { 60 | return "RoadPosition [edgeId=" + EdgeId + ", fraction=" + Fraction 61 | + ", position=" + Position + "]"; 62 | } 63 | 64 | public bool Equals(RoadPosition other) 65 | { 66 | if (object.ReferenceEquals(this, other)) 67 | { 68 | return true; 69 | } 70 | 71 | return this.EdgeId == other.EdgeId && this.Fraction == other.Fraction; 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Proj4net/Proj4netTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | using ProjNet; 6 | using ProjNet.Converters; 7 | using GeoAPI.CoordinateSystems; 8 | 9 | //https://gis.stackexchange.com/questions/246062/epsg-4326-to-3857-works-on-x-axis-but-not-y-with-proj4net 10 | 11 | namespace Sandwych.MapMatchingKit.Tests.Proj4net 12 | { 13 | public class Proj4netTest 14 | { 15 | [Fact] 16 | public void TestEpsg3395AndEpsg4326() 17 | { 18 | var epsg3395 = @"PROJCS[""WGS 84 / World Mercator"",GEOGCS[""WGS 84"",DATUM[""WGS_1984"",SPHEROID[""WGS 84"",6378137,298.257223563,AUTHORITY[""EPSG"",""7030""]],AUTHORITY[""EPSG"",""6326""]],PRIMEM[""Greenwich"",0,AUTHORITY[""EPSG"",""8901""]],UNIT[""degree"",0.0174532925199433,AUTHORITY[""EPSG"",""9122""]],AUTHORITY[""EPSG"",""4326""]],PROJECTION[""Mercator_1SP""],PARAMETER[""Latitude_of_origin"", 0],PARAMETER[""central_meridian"",0],PARAMETER[""scale_factor"",1],PARAMETER[""false_easting"",0],PARAMETER[""false_northing"",0],UNIT[""metre"",1,AUTHORITY[""EPSG"",""9001""]],AXIS[""Easting"",EAST],AXIS[""Northing"",NORTH],AUTHORITY[""EPSG"",""3395""]]"; 19 | 20 | var epsg4326 = @"GEOGCS[""WGS 84"",DATUM[""WGS_1984"",SPHEROID[""WGS 84"",6378137,298.257223563,AUTHORITY[""EPSG"",""7030""]],AUTHORITY[""EPSG"",""6326""]],PRIMEM[""Greenwich"",0,AUTHORITY[""EPSG"",""8901""]],UNIT[""degree"",0.01745329251994328,AUTHORITY[""EPSG"",""9122""]],AUTHORITY[""EPSG"",""4326""]]"; 21 | 22 | var srcCRS = ProjNet.Converters.WellKnownText.CoordinateSystemWktReader.Parse(epsg4326, Encoding.ASCII) as ICoordinateSystem; 23 | var tgtCRS = ProjNet.Converters.WellKnownText.CoordinateSystemWktReader.Parse(epsg3395, Encoding.ASCII) as ICoordinateSystem; 24 | 25 | 26 | var ctFac = new ProjNet.CoordinateSystems.Transformations.CoordinateTransformationFactory(); 27 | var trans = ctFac.CreateFromCoordinateSystems(srcCRS, tgtCRS); 28 | 29 | var longitude = 102.709887; 30 | var latitude = 25.054263; 31 | var fromPT = new double[] { longitude, latitude }; 32 | var toPT = trans.MathTransform.Transform(fromPT); 33 | Assert.Equal(11433612.32, toPT[0], 2); 34 | Assert.Equal(2864322.39, toPT[1], 2); 35 | } 36 | 37 | [Fact] 38 | public void TestEpsg3857AndEpsg4326() 39 | { 40 | var epsg3857 = @"PROJCS[""WGS 84 / Pseudo - Mercator"", GEOGCS[""WGS 84"", DATUM[""WGS_1984"", SPHEROID[""WGS 84"", 6378137, 298.257223563, AUTHORITY[""EPSG"", ""7030""]], AUTHORITY[""EPSG"", ""6326""]], PRIMEM[""Greenwich"", 0, AUTHORITY[""EPSG"", ""8901""]], UNIT[""degree"", 0.0174532925199433, AUTHORITY[""EPSG"", ""9122""]], AUTHORITY[""EPSG"", ""4326""]], PROJECTION[""Mercator_1SP""], PARAMETER[""Latitude_of_origin"", 0], PARAMETER[""central_meridian"", 0], PARAMETER[""scale_factor"", 1], PARAMETER[""false_easting"", 0], PARAMETER[""false_northing"", 0], UNIT[""metre"", 1, AUTHORITY[""EPSG"", ""9001""]], AXIS[""X"", EAST], AXIS[""Y"", NORTH], EXTENSION[""PROJ4"", "" + proj = merc + a = 6378137 + b = 6378137 + lat_ts = 0.0 + lon_0 = 0.0 + x_0 = 0.0 + y_0 = 0 + k = 1.0 + units = m + nadgrids = @null + wktext + no_defs""], AUTHORITY[""EPSG"", ""3857""]]"; 41 | var epsg4326 = @"GEOGCS[""WGS 84"",DATUM[""WGS_1984"",SPHEROID[""WGS 84"",6378137,298.257223563,AUTHORITY[""EPSG"",""7030""]],AUTHORITY[""EPSG"",""6326""]],PRIMEM[""Greenwich"",0,AUTHORITY[""EPSG"",""8901""]],UNIT[""degree"",0.01745329251994328,AUTHORITY[""EPSG"",""9122""]],AUTHORITY[""EPSG"",""4326""]]"; 42 | 43 | var srcCRS = ProjNet.Converters.WellKnownText.CoordinateSystemWktReader.Parse(epsg4326, Encoding.ASCII) as ICoordinateSystem; 44 | var tgtCRS = ProjNet.Converters.WellKnownText.CoordinateSystemWktReader.Parse(epsg3857, Encoding.ASCII) as ICoordinateSystem; 45 | 46 | 47 | var ctFac = new ProjNet.CoordinateSystems.Transformations.CoordinateTransformationFactory(); 48 | var trans = ctFac.CreateFromCoordinateSystems(srcCRS, tgtCRS); 49 | 50 | var longitude = 102.709887; 51 | var latitude = 25.054263; 52 | var fromPT = new double[] { longitude, latitude }; 53 | var toPT = trans.MathTransform.Transform(fromPT); 54 | Assert.Equal(11433612.32, toPT[0], 2); 55 | //TODO & FIXME: ProjNet did not works well with EPSG 4326 56 | //Assert.Equal(2882411.08, toPT[1], 2); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Roads/RoadPointTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | 6 | namespace Sandwych.MapMatchingKit.Tests.Roads 7 | { 8 | public class RoadPointTest 9 | { 10 | public void CartesianTest() 11 | { 12 | 13 | } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Sandwych.MapMatchingKit.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | Library 7 | latest 8 | Sandwych.MapMatchingKit.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/AbstractSpatialOperationTest.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Spatial; 2 | using Sandwych.MapMatchingKit.Spatial.Geometries; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Spatial 9 | { 10 | public abstract class AbstractSpatialOperationTest : TestBase 11 | { 12 | protected ISpatialOperation Geography { get; } = new GeographySpatialOperation(); 13 | protected abstract ISpatialOperation Spatial { get; } 14 | 15 | protected (Coordinate2D, double, double) Intercept(Coordinate2D a, Coordinate2D b, Coordinate2D c) 16 | { 17 | int iter = 1000; 18 | 19 | var res = (a, Spatial.Distance(a, c), 0d); 20 | 21 | for (int f = 1; f <= iter; ++f) 22 | { 23 | 24 | var p = Spatial.Interpolate(a, b, (double)f / iter); 25 | double s = Spatial.Distance(p, c); 26 | 27 | if (s < res.Item2) 28 | { 29 | res.Item1 = p; 30 | res.Item2 = s; 31 | res.Item3 = (double)f / iter; 32 | } 33 | } 34 | return res; 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Index/AbstractSpatialIndexTest.cs: -------------------------------------------------------------------------------- 1 | using GeoAPI.Geometries; 2 | using NetTopologySuite.Geometries; 3 | using NetTopologySuite.IO; 4 | using Sandwych.MapMatchingKit.Spatial; 5 | using Sandwych.MapMatchingKit.Spatial.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Index; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using Xunit; 12 | 13 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Index 14 | { 15 | public abstract class AbstractSpatialIndexTest : TestBase 16 | { 17 | private readonly IReadOnlyList _geometries; 18 | protected ISpatialOperation Spatial { get; } 19 | protected IReadOnlyList Geometries => _geometries; 20 | protected abstract ISpatialIndex CreateSpatialIndex(); 21 | 22 | public AbstractSpatialIndexTest() 23 | { 24 | this.Spatial = new GeographySpatialOperation(); 25 | _geometries = this.MakeGeometries(); 26 | } 27 | 28 | private IReadOnlyList MakeGeometries() 29 | { 30 | /* 31 | * (p2) (p3) ----- (e1) : (p1) -> (p2) ---------------------------------------------------- 32 | * - \ / --------- (e2) : (p3) -> (p1) ---------------------------------------------------- 33 | * | (p1) | ------ (e3) : (p4) -> (p1) ---------------------------------------------------- 34 | * - / \ --------- (e4) : (p1) -> (p5) ---------------------------------------------------- 35 | * (p4) (p5) ----- (e5) : (p2) -> (p4) ---------------------------------------------------- 36 | * --------------- (e6) : (p5) -> (p3) ---------------------------------------------------- 37 | */ 38 | String p1 = "11.3441505 48.0839963"; 39 | String p2 = "11.3421209 48.0850624"; 40 | String p3 = "11.3460348 48.0850108"; 41 | String p4 = "11.3427522 48.0832129"; 42 | String p5 = "11.3469701 48.0825356"; 43 | var reader = new WKTReader(); 44 | ILineString readAsLineString(string wkt) => reader.Read("SRID=4326;" + wkt) as ILineString; 45 | 46 | var geometries = new ILineString[] { 47 | readAsLineString("LINESTRING(" + p1 + "," + p2 + ")"), 48 | readAsLineString("LINESTRING(" + p3 + "," + p1 + ")"), 49 | readAsLineString("LINESTRING(" + p4 + "," + p1 + ")"), 50 | readAsLineString("LINESTRING(" + p1 + "," + p5 + ")"), 51 | readAsLineString("LINESTRING(" + p2 + "," + p4 + ")"), 52 | readAsLineString("LINESTRING(" + p5 + "," + p3 + ")"), 53 | }; 54 | 55 | var geoms = new List(); 56 | for (int i = 0; i < geometries.Length; i++) 57 | { 58 | var g = geometries[i]; 59 | g.UserData = i; 60 | geoms.Add(g); 61 | } 62 | 63 | return geoms; 64 | } 65 | 66 | private void AssertIndexRadius(Coordinate2D c, double r, int expectedNeighborsCount) 67 | { 68 | var lines = this.Geometries; 69 | var index = this.CreateSpatialIndex(); 70 | 71 | var neighbors = new HashSet(); 72 | for (int i = 0; i < lines.Count; ++i) 73 | { 74 | var line = lines[i]; 75 | var f = this.Spatial.Intercept(line, c); 76 | var p = this.Spatial.Interpolate(line, f); 77 | var d = this.Spatial.Distance(c, p); 78 | 79 | if (d <= r) 80 | { 81 | neighbors.Add(i); 82 | } 83 | } 84 | 85 | Assert.Equal(expectedNeighborsCount, neighbors.Count); 86 | 87 | var points = index.Radius(c, r); 88 | 89 | Assert.Equal(neighbors.Count, points.Count()); 90 | foreach (var pointId in points.Select(p => (int)p.Item1.UserData)) 91 | { 92 | Assert.Contains(pointId, neighbors); 93 | } 94 | } 95 | 96 | [Fact] 97 | public void TestIndexRadius() 98 | { 99 | { 100 | var c = new Coordinate2D(11.343629, 48.083797); 101 | var r = 50; 102 | var nc = 4; 103 | AssertIndexRadius(c, r, nc); 104 | } 105 | { 106 | var c = new Coordinate2D(11.344827, 48.083752); 107 | var r = 10; 108 | var nc = 1; 109 | AssertIndexRadius(c, r, nc); 110 | } 111 | { 112 | var c = new Coordinate2D(11.344827, 48.083752); 113 | var r = 5; 114 | var nc = 0; 115 | AssertIndexRadius(c, r, nc); 116 | } 117 | 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Index/QuadtreeIndexTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using Sandwych.MapMatchingKit.Spatial; 6 | using Sandwych.MapMatchingKit.Spatial.Index; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Index 9 | { 10 | public class QuadtreeIndexTest : AbstractSpatialIndexTest 11 | { 12 | protected override ISpatialIndex CreateSpatialIndex() => 13 | new QuadtreeIndex(this.Geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Index/RBushIndexTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using Sandwych.MapMatchingKit.Spatial; 6 | using Sandwych.MapMatchingKit.Spatial.Index; 7 | using Sandwych.MapMatchingKit.Spatial.Index.RBush; 8 | 9 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Index 10 | { 11 | public class RBushIndexTest : AbstractSpatialIndexTest 12 | { 13 | protected override ISpatialIndex CreateSpatialIndex() => 14 | new RBushSpatialIndex(this.Geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Index/RtreeIndexTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using GeoAPI.Geometries; 5 | using Sandwych.MapMatchingKit.Spatial; 6 | using Sandwych.MapMatchingKit.Spatial.Index; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Index 9 | { 10 | public class RtreeIndexTest : AbstractSpatialIndexTest 11 | { 12 | protected override ISpatialIndex CreateSpatialIndex() => 13 | new RtreeIndex(this.Geometries, this.Spatial, x => x, x => this.Spatial.Length(x)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Projection/Epsg3395To4326CoordinateTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | using Sandwych.MapMatchingKit.Spatial.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Projection; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Projection 9 | { 10 | public class Epsg4326To3395CoordinateTransformationTest 11 | { 12 | [Fact] 13 | public void TransformTest() 14 | { 15 | var transform = new Epsg4326To3395CoordinateTransformation(); 16 | var fromCoord = new Coordinate2D(102.709887, 25.054263); 17 | var toCoord = transform.Transform(fromCoord); 18 | Assert.Equal(11433612.32, toCoord.X, 2); 19 | Assert.Equal(2864322.39, toCoord.Y, 2); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Spatial/Projection/Epsg4326To3395CoordinateTransformationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | using Sandwych.MapMatchingKit.Spatial.Geometries; 6 | using Sandwych.MapMatchingKit.Spatial.Projection; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Spatial.Projection 9 | { 10 | public class Epsg3395To4326CoordinateTransformationTest 11 | { 12 | [Fact] 13 | public void TransformTest() 14 | { 15 | var transform = new Epsg3395To4326CoordinateTransformation(); 16 | var fromCoord = new Coordinate2D(11433612.32, 2864322.39); 17 | var toCoord = transform.Transform(fromCoord); 18 | Assert.Equal(102.709887, toCoord.X, 6); 19 | Assert.Equal(25.054263, toCoord.Y, 6); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using GeoAPI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace Sandwych.MapMatchingKit.Tests 8 | { 9 | public abstract class TestBase 10 | { 11 | protected TestBase() 12 | { 13 | NetTopologySuiteBootstrapper.Bootstrap(); 14 | } 15 | 16 | protected static void AssertEquals(double actual, double expected, double delta) => 17 | Assert.InRange(actual, expected - delta, expected + delta); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Topology/DijkstraRouterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Sandwych.MapMatchingKit.Topology; 6 | using Xunit; 7 | 8 | namespace Sandwych.MapMatchingKit.Tests.Topology 9 | { 10 | public class DijkstraRouterTest : AbstractRouterTest 11 | { 12 | protected override IGraphRouter CreateRouter( 13 | Graph graph, Func cost, Func bound, double max) => 14 | new DijkstraRouter(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Topology/Models.cs: -------------------------------------------------------------------------------- 1 | using Sandwych.MapMatchingKit.Topology; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Sandwych.MapMatchingKit.Tests.Topology 7 | { 8 | public class Road : AbstractGraphEdge 9 | { 10 | public float Weight { get; } 11 | 12 | public Road(long id, long source, long target, float weight) : base(id, source, target) 13 | { 14 | this.Weight = weight; 15 | } 16 | 17 | public override int GetHashCode() => 18 | (this.Id, this.Weight).GetHashCode(); 19 | } 20 | 21 | public class Graph : AbstractGraph 22 | { 23 | public Graph(IEnumerable roads) : base(roads) 24 | { 25 | 26 | } 27 | } 28 | 29 | public readonly struct RoadPoint : IEdgePoint, IEquatable 30 | { 31 | public Road Edge { get; } 32 | public double Fraction { get; } 33 | 34 | public RoadPoint(Road road, double fraction) 35 | { 36 | this.Edge = road; 37 | this.Fraction = fraction; 38 | } 39 | 40 | public override int GetHashCode() => 41 | (this.Edge, this.Fraction).GetHashCode(); 42 | 43 | public bool Equals(RoadPoint other) => 44 | this.Edge == other.Edge && Math.Abs(this.Fraction - other.Fraction) < 10E-6; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Topology/PrecomputedDijkstra/MyGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using QuickGraph; 5 | 6 | namespace Sandwych.MapMatchingKit.Tests.Topology.PrecomputedDijkstra 7 | { 8 | public static class MyGraph 9 | { 10 | private static readonly AdjacencyGraph> graph; 11 | private static readonly Dictionary, double> edgeCost; 12 | 13 | public static AdjacencyGraph> GraphInstance => graph; 14 | public static IReadOnlyDictionary, double> EdgeCosts => edgeCost; 15 | 16 | static MyGraph() 17 | { 18 | graph = new AdjacencyGraph>(); 19 | 20 | // Add some vertices to the graph 21 | graph.AddVertex("A"); 22 | graph.AddVertex("B"); 23 | graph.AddVertex("C"); 24 | graph.AddVertex("D"); 25 | graph.AddVertex("E"); 26 | graph.AddVertex("F"); 27 | graph.AddVertex("G"); 28 | graph.AddVertex("H"); 29 | graph.AddVertex("I"); 30 | graph.AddVertex("J"); 31 | 32 | // Create the edges 33 | Edge a_b = new Edge("A", "B"); 34 | Edge a_d = new Edge("A", "D"); 35 | Edge b_a = new Edge("B", "A"); 36 | Edge b_c = new Edge("B", "C"); 37 | Edge b_e = new Edge("B", "E"); 38 | Edge c_b = new Edge("C", "B"); 39 | Edge c_f = new Edge("C", "F"); 40 | Edge c_j = new Edge("C", "J"); 41 | Edge d_e = new Edge("D", "E"); 42 | Edge d_g = new Edge("D", "G"); 43 | Edge e_d = new Edge("E", "D"); 44 | Edge e_f = new Edge("E", "F"); 45 | Edge e_h = new Edge("E", "H"); 46 | Edge f_i = new Edge("F", "I"); 47 | Edge f_j = new Edge("F", "J"); 48 | Edge g_d = new Edge("G", "D"); 49 | Edge g_h = new Edge("G", "H"); 50 | Edge h_g = new Edge("H", "G"); 51 | Edge h_i = new Edge("H", "I"); 52 | Edge i_f = new Edge("I", "F"); 53 | Edge i_j = new Edge("I", "J"); 54 | Edge i_h = new Edge("I", "H"); 55 | Edge j_f = new Edge("J", "F"); 56 | 57 | // Add the edges 58 | graph.AddEdge(a_b); 59 | graph.AddEdge(a_d); 60 | graph.AddEdge(b_a); 61 | graph.AddEdge(b_c); 62 | graph.AddEdge(b_e); 63 | graph.AddEdge(c_b); 64 | graph.AddEdge(c_f); 65 | graph.AddEdge(c_j); 66 | graph.AddEdge(d_e); 67 | graph.AddEdge(d_g); 68 | graph.AddEdge(e_d); 69 | graph.AddEdge(e_f); 70 | graph.AddEdge(e_h); 71 | graph.AddEdge(f_i); 72 | graph.AddEdge(f_j); 73 | graph.AddEdge(g_d); 74 | graph.AddEdge(g_h); 75 | graph.AddEdge(h_g); 76 | graph.AddEdge(h_i); 77 | graph.AddEdge(i_f); 78 | graph.AddEdge(i_h); 79 | graph.AddEdge(i_j); 80 | graph.AddEdge(j_f); 81 | 82 | // Define some weights to the edges 83 | edgeCost = new Dictionary, double>(graph.EdgeCount); 84 | edgeCost.Add(a_b, 4); 85 | edgeCost.Add(a_d, 1); 86 | edgeCost.Add(b_a, 74); 87 | edgeCost.Add(b_c, 2); 88 | edgeCost.Add(b_e, 12); 89 | edgeCost.Add(c_b, 12); 90 | edgeCost.Add(c_f, 74); 91 | edgeCost.Add(c_j, 12); 92 | edgeCost.Add(d_e, 32); 93 | edgeCost.Add(d_g, 22); 94 | edgeCost.Add(e_d, 66); 95 | edgeCost.Add(e_f, 76); 96 | edgeCost.Add(e_h, 33); 97 | edgeCost.Add(f_i, 11); 98 | edgeCost.Add(f_j, 21); 99 | edgeCost.Add(g_d, 12); 100 | edgeCost.Add(g_h, 10); 101 | edgeCost.Add(h_g, 2); 102 | edgeCost.Add(h_i, 72); 103 | edgeCost.Add(i_f, 31); 104 | edgeCost.Add(i_h, 18); 105 | edgeCost.Add(i_j, 7); 106 | edgeCost.Add(j_f, 8); 107 | } 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Topology/PrecomputedDijkstra/PrecomputedDijkstraRouterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Sandwych.MapMatchingKit.Topology; 6 | using Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra; 7 | using Xunit; 8 | using Xunit.Extensions; 9 | 10 | namespace Sandwych.MapMatchingKit.Tests.Topology.PrecomputedDijkstra 11 | { 12 | public class PrecomputedDijkstraRouterTest : TestBase 13 | { 14 | private readonly Graph _map; 15 | 16 | public PrecomputedDijkstraRouterTest() 17 | { 18 | var roads = new Road[] { 19 | new Road(0, 0, 1, 100), 20 | new Road(1, 1, 0, 100), 21 | new Road(2, 0, 2, 160), 22 | new Road(3, 2, 0, 160), 23 | new Road(4, 1, 2, 50), 24 | new Road(5, 2, 1, 50), 25 | new Road(6, 1, 3, 200), 26 | new Road(7, 3, 1, 200), 27 | new Road(8, 2, 3, 100), 28 | new Road(9, 3, 2, 100), 29 | new Road(10, 2, 4, 40), 30 | new Road(11, 4, 2, 40), 31 | new Road(12, 3, 4, 100), 32 | new Road(13, 4, 3, 100), 33 | new Road(14, 3, 5, 200), 34 | new Road(15, 5, 3, 200), 35 | new Road(16, 4, 5, 60), 36 | new Road(17, 5, 4, 60), 37 | }; 38 | _map = new Graph(roads); 39 | } 40 | 41 | private (IReadOnlyDictionary> expected, 42 | IReadOnlyDictionary> actual) 43 | FindPaths( 44 | RoadPoint source, IEnumerable targets, 45 | Func cost, Func bound, double maxRadius) 46 | { 47 | var dijkstraRouter = new DijkstraRouter(); 48 | var precomputedRouter = new PrecomputedDijkstraRouter(_map, cost, bound, maxRadius); 49 | var expectedPath = dijkstraRouter.Route(source, targets, cost, bound, maxRadius); 50 | var actualPath = precomputedRouter.Route(source, targets, cost, bound, maxRadius); 51 | return (expectedPath, actualPath); 52 | } 53 | 54 | [Fact] 55 | public void TestSimpleShortestPath() 56 | { 57 | var maxRadius = 200D; 58 | var start = new RoadPoint(_map.GetEdge(0), 0.3); 59 | var targets = new RoadPoint[] { 60 | new RoadPoint(_map.GetEdge(10), 0.5), 61 | new RoadPoint(_map.GetEdge(17), 0.6), 62 | }; 63 | Func cost = r => r.Weight; 64 | 65 | var paths = this.FindPaths(start, targets, cost, cost, maxRadius); 66 | 67 | Assert.Single(paths.actual); 68 | Assert.Equal(paths.expected, paths.actual); 69 | } 70 | 71 | [Fact] 72 | public void SelfLoopTest1() 73 | { 74 | var max = 200D; 75 | var start = new RoadPoint(_map.GetEdge(0), 0.3); 76 | var targets = new RoadPoint[] { 77 | new RoadPoint(_map.GetEdge(0), 0.2), 78 | }; 79 | Func cost = r => r.Weight; 80 | 81 | var dijkstraRouter = new DijkstraRouter(); 82 | var precomputedRouter = new PrecomputedDijkstraRouter(_map, cost, cost, max); 83 | 84 | var expectedPath = dijkstraRouter.Route(start, targets, cost, cost, max); 85 | var actualPath = precomputedRouter.Route(start, targets, cost, cost, max); 86 | 87 | Assert.Equal(expectedPath, actualPath); 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/Sandwych.MapMatchingKit.Tests/Topology/PrecomputedDijkstra/PrecomputedDijkstraTableTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | using QuickGraph; 6 | using Xunit; 7 | using Sandwych.MapMatchingKit.Topology.PrecomputedDijkstra; 8 | 9 | namespace Sandwych.MapMatchingKit.Tests.Topology.PrecomputedDijkstra 10 | { 11 | public class PrecomputedDijkstraTableTest 12 | { 13 | [Theory] 14 | [InlineData("A", "B")] 15 | [InlineData("A", "D")] 16 | [InlineData("A", "J")] 17 | [InlineData("C", "E")] 18 | public void TestGetPathByVertex(string sourceVertex, string targetVertex) 19 | { 20 | var maxRadius = 500D; 21 | 22 | var naiveDijkstra = new BoundedDijkstraShortestPathAlgorithm>( 23 | MyGraph.GraphInstance, e => MyGraph.EdgeCosts[e], e => MyGraph.EdgeCosts[e], maxRadius); 24 | naiveDijkstra.Compute(sourceVertex); 25 | 26 | var generator = new PrecomputedDijkstraTableGenerator>(); 27 | var rows = generator.ComputeRows(MyGraph.GraphInstance, e => MyGraph.EdgeCosts[e], e => MyGraph.EdgeCosts[e], maxRadius); 28 | 29 | var table = new PrecomputedDijkstraTable>(rows); 30 | var t = table.GetPathByVertex(sourceVertex, targetVertex); 31 | var actualDistance = t.Path.Sum(e => MyGraph.EdgeCosts[e]); 32 | 33 | Assert.True(naiveDijkstra.TryGetPath(targetVertex, out var expectedPath)); 34 | Assert.NotNull(t.Path); 35 | Assert.NotEmpty(t.Path); 36 | var expectedDistance = expectedPath.Sum(e => MyGraph.EdgeCosts[e]); 37 | Assert.Equal(expectedPath, t.Path); 38 | Assert.Equal(expectedDistance, actualDistance, 8); 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------