├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── bmw │ └── mapmatchingutils │ ├── Distributions.java │ ├── HmmProbabilities.java │ └── TimeStep.java └── test └── java └── com └── bmw └── mapmatchingutils ├── DistributionsTest.java ├── OfflineMapMatcherTest.java ├── OfflineMapMatcherTest.png └── types ├── GpsMeasurement.java ├── Point.java ├── RoadPath.java └── RoadPosition.java /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /.settings/ 3 | target/ 4 | /.classpath 5 | /.project 6 | /.cache 7 | /.idea 8 | *.iml 9 | *~ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG 2 | Author: Stefan Holder (stefan.holder@bmw.de) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | This project has dependencies to: 17 | Apache Maven, under The Apache Software License, Version 2.0 18 | JUnit under Eclipse Public License - v 1.0 19 | hmm-lib, under The Apache Software License, Version 2.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This project demonstrates how to use the [hmm-lib](https://github.com/bmwcarit/hmm-lib) for 4 | matching a sequence of GPS coordinates to roads (called offline map matching) but does not provide 5 | integration to any particular map. 6 | 7 | This map matching approach is based on Hidden Markov Models (HMM) and described in the following 8 | paper: 9 | *Newson, Paul, and John Krumm. "Hidden Markov map matching through noise and sparseness." 10 | Proceedings of the 17th ACM SIGSPATIAL International Conference on Advances in Geographic 11 | Information Systems. ACM, 2009.* 12 | 13 | # Integation with other libraries 14 | To make map matching work with an actual map the following needs to be further implemented: 15 | * compute map matching candidates (i.e. possible road positions) for each GPS position 16 | * compute distances between GPS positions and map matching candidates 17 | * compute shortest routes between subsequent map matching candidates 18 | 19 | These computations are not provided by this project because they are usually dependent on the 20 | underlying map. Moreover, this lets the user choose his own favorite geospatial/routing libraries. 21 | 22 | # License 23 | 24 | This library is licensed under the 25 | [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html). 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 20 | 4.0.0 21 | com.bmw.mapmatchingutils 22 | map-matching-utils 23 | 1.0.0-SNAPSHOT 24 | jar 25 | 26 | map-matching-utils 27 | Utilities for HMM based map matching 28 | https://github.com/bmwcarit/offline-map-matching 29 | 30 | 31 | 32 | BMW Car IT GmbH, BMW AG 33 | http://www.bmw-carit.com, http://www.bmw.com 34 | 35 | 36 | 37 | 38 | 39 | Apache License, Version 2.0 40 | http://www.apache.org/licenses/LICENSE-2.0.txt 41 | repo 42 | 43 | 44 | 45 | 46 | UTF-8 47 | UTF-8 48 | 49 | 50 | 51 | scm:git:git@github.com:bmwcarit/offline-map-matching.git 52 | scm:git:git@github.com:bmwcarit/offline-map-matching.git 53 | git@github.com:bmwcarit/offline-map-matching 54 | 55 | 56 | 57 | 58 | 59 | maven-compiler-plugin 60 | 3.1 61 | 62 | 1.7 63 | 1.7 64 | 65 | 66 | 67 | 68 | 69 | 70 | junit 71 | junit 72 | test 73 | 4.13.1 74 | 75 | 76 | com.bmw.hmm 77 | hmm-lib 78 | 1.0.0 79 | 80 | 81 | 82 | 83 | hmm-lib-snapshots 84 | https://raw.github.com/bmwcarit/hmm-lib/mvn-snapshots 85 | 86 | true 87 | always 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/com/bmw/mapmatchingutils/Distributions.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils; 19 | 20 | import static java.lang.Math.PI; 21 | import static java.lang.Math.exp; 22 | import static java.lang.Math.log; 23 | import static java.lang.Math.pow; 24 | import static java.lang.Math.sqrt; 25 | 26 | 27 | /** 28 | * Implements various probability distributions. 29 | */ 30 | public class Distributions { 31 | 32 | static double normalDistribution(double sigma, double x) { 33 | return 1.0 / (sqrt(2.0 * PI) * sigma) * exp(-0.5 * pow(x / sigma, 2)); 34 | } 35 | 36 | /** 37 | * Use this function instead of Math.log(normalDistribution(sigma, x)) to avoid an 38 | * arithmetic underflow for very small probabilities. 39 | */ 40 | public static double logNormalDistribution(double sigma, double x) { 41 | return Math.log(1.0 / (sqrt(2.0 * PI) * sigma)) + (-0.5 * pow(x / sigma, 2)); 42 | } 43 | 44 | /** 45 | * @param beta =1/lambda with lambda being the standard exponential distribution rate parameter 46 | */ 47 | static double exponentialDistribution(double beta, double x) { 48 | return 1.0 / beta * exp(-x / beta); 49 | } 50 | 51 | /** 52 | * Use this function instead of Math.log(exponentialDistribution(beta, x)) to avoid an 53 | * arithmetic underflow for very small probabilities. 54 | * 55 | * @param beta =1/lambda with lambda being the standard exponential distribution rate parameter 56 | */ 57 | static double logExponentialDistribution(double beta, double x) { 58 | return log(1.0 / beta) - (x / beta); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/bmw/mapmatchingutils/HmmProbabilities.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils; 19 | 20 | 21 | /** 22 | * Based on Newson, Paul, and John Krumm. "Hidden Markov map matching through noise and sparseness." 23 | * Proceedings of the 17th ACM SIGSPATIAL International Conference on Advances in Geographic 24 | * Information Systems. ACM, 2009. 25 | */ 26 | public class HmmProbabilities { 27 | 28 | private final double sigma; 29 | private final double beta; 30 | 31 | /** 32 | * Sets default values for sigma and beta. 33 | */ 34 | public HmmProbabilities() { 35 | /* 36 | * Sigma taken from Newson&Krumm. 37 | * Beta empirically computed from the Microsoft ground truth data for shortest route 38 | * lengths and 60 s sampling interval but also works for other sampling intervals. 39 | */ 40 | this(4.07, 0.00959442); 41 | } 42 | 43 | /** 44 | * @param sigma standard deviation of the normal distribution [m] used for modeling the 45 | * GPS error 46 | * @param beta beta parameter of the exponential distribution for 1 s sampling interval, used 47 | * for modeling transition probabilities 48 | */ 49 | public HmmProbabilities(double sigma, double beta) { 50 | this.sigma = sigma; 51 | this.beta = beta; 52 | } 53 | 54 | /** 55 | * Returns the logarithmic emission probability density. 56 | * 57 | * @param distance Absolute distance [m] between GPS measurement and map matching candidate. 58 | */ 59 | public double emissionLogProbability(double distance) { 60 | return Distributions.logNormalDistribution(sigma, distance); 61 | } 62 | 63 | /** 64 | * Returns the logarithmic transition probability density for the given transition 65 | * parameters. 66 | * 67 | * @param routeLength Length of the shortest route [m] between two consecutive map matching 68 | * candidates. 69 | * @param linearDistance Linear distance [m] between two consecutive GPS measurements. 70 | * @param timeDiff time difference [s] between two consecutive GPS measurements. 71 | */ 72 | public double transitionLogProbability(double routeLength, double linearDistance, 73 | double timeDiff) { 74 | Double transitionMetric = normalizedTransitionMetric(routeLength, linearDistance, timeDiff); 75 | return Distributions.logExponentialDistribution(beta, transitionMetric); 76 | } 77 | 78 | /** 79 | * Returns a transition metric for the transition between two consecutive map matching 80 | * candidates. 81 | * 82 | * In contrast to Newson & Krumm the absolute distance difference is divided by the quadratic 83 | * time difference to make the beta parameter of the exponential distribution independent of the 84 | * sampling interval. 85 | */ 86 | private double normalizedTransitionMetric(double routeLength, double linearDistance, 87 | double timeDiff) { 88 | if (timeDiff < 0.0) { 89 | throw new IllegalStateException( 90 | "Time difference between subsequent location measurements must be >= 0."); 91 | } 92 | return Math.abs(linearDistance - routeLength) / (timeDiff * timeDiff); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/bmw/mapmatchingutils/TimeStep.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils; 19 | 20 | import java.util.Collection; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | import com.bmw.hmm.Transition; 25 | 26 | 27 | /** 28 | * Contains everything the hmm-lib needs to process a new time step including emisson and 29 | * observation probabilities. 30 | * 31 | * @param road position type, which corresponds to the HMM state. 32 | * @param location measurement type, which corresponds to the HMM observation. 33 | * @param road path object 34 | */ 35 | public class TimeStep { 36 | 37 | /** 38 | * Observation made at this time step. 39 | */ 40 | public final O observation; 41 | 42 | /** 43 | * State candidates at this time step. 44 | */ 45 | public final Collection candidates; 46 | 47 | public final Map emissionLogProbabilities = new HashMap<>(); 48 | public final Map, Double> transitionLogProbabilities = new HashMap<>(); 49 | 50 | /** 51 | * Road paths between all candidates pairs of the previous and the current time step. 52 | */ 53 | public final Map, D> roadPaths = new HashMap<>(); 54 | 55 | 56 | public TimeStep(O observation, Collection candidates) { 57 | if (observation == null || candidates == null) { 58 | throw new NullPointerException("observation and candidates must not be null."); 59 | } 60 | this.observation = observation; 61 | this.candidates = candidates; 62 | } 63 | 64 | public void addEmissionLogProbability(S candidate, double emissionLogProbability) { 65 | if (emissionLogProbabilities.containsKey(candidate)) { 66 | throw new IllegalArgumentException("Candidate has already been added."); 67 | } 68 | emissionLogProbabilities.put(candidate, emissionLogProbability); 69 | } 70 | 71 | /** 72 | * Does not need to be called for non-existent transitions. 73 | */ 74 | public void addTransitionLogProbability(S fromPosition, S toPosition, 75 | double transitionLogProbability) { 76 | final Transition transition = new Transition<>(fromPosition, toPosition); 77 | if (transitionLogProbabilities.containsKey(transition)) { 78 | throw new IllegalArgumentException("Transition has already been added."); 79 | } 80 | transitionLogProbabilities.put(transition, transitionLogProbability); 81 | } 82 | 83 | /** 84 | * Does not need to be called for non-existent transitions. 85 | */ 86 | public void addRoadPath(S fromPosition, S toPosition, D roadPath) { 87 | final Transition transition = new Transition<>(fromPosition, toPosition); 88 | if (roadPaths.containsKey(transition)) { 89 | throw new IllegalArgumentException("Transition has already been added."); 90 | } 91 | roadPaths.put(transition, roadPath); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/DistributionsTest.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | import org.junit.Test; 23 | 24 | import com.bmw.mapmatchingutils.Distributions; 25 | 26 | public class DistributionsTest { 27 | 28 | private static double DELTA = 1e-8; 29 | 30 | @Test 31 | public void testLogNormalDistribution() { 32 | assertEquals(Math.log(Distributions.normalDistribution(5, 6)), 33 | Distributions.logNormalDistribution(5, 6), DELTA); 34 | } 35 | 36 | @Test 37 | public void testLogExponentialDistribution() { 38 | assertEquals(Math.log(Distributions.exponentialDistribution(5, 6)), 39 | Distributions.logExponentialDistribution(5, 6), DELTA); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/OfflineMapMatcherTest.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils; 19 | 20 | import com.bmw.hmm.SequenceState; 21 | import com.bmw.hmm.Transition; 22 | import com.bmw.hmm.ViterbiAlgorithm; 23 | import com.bmw.mapmatchingutils.types.GpsMeasurement; 24 | import com.bmw.mapmatchingutils.types.Point; 25 | import com.bmw.mapmatchingutils.types.RoadPath; 26 | import com.bmw.mapmatchingutils.types.RoadPosition; 27 | import org.junit.BeforeClass; 28 | import org.junit.Test; 29 | 30 | import java.util.*; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertFalse; 34 | 35 | /** 36 | * This class demonstrate how to use the hmm-lib for map matching. The methods 37 | * of this class can be used as a template to implement map matching for an actual map. 38 | * 39 | * The test scenario is depicted in ./OfflineMapMatcherTest.png. 40 | * All road segments can be driven in both directions. The orientation of road segments 41 | * is needed to determine the fractions of a road positions. 42 | */ 43 | public class OfflineMapMatcherTest { 44 | 45 | private final HmmProbabilities hmmProbabilities = new HmmProbabilities(); 46 | 47 | private final static Map> candidateMap = 48 | new HashMap<>(); 49 | 50 | private final static Map, Double> routeLengths = new HashMap<>(); 51 | 52 | private final static GpsMeasurement gps1 = new GpsMeasurement(seconds(0), 10, 10); 53 | private final static GpsMeasurement gps2 = new GpsMeasurement(seconds(1), 30, 20); 54 | private final static GpsMeasurement gps3 = new GpsMeasurement(seconds(2), 30, 40); 55 | private final static GpsMeasurement gps4 = new GpsMeasurement(seconds(3), 10, 70); 56 | 57 | private final static RoadPosition rp11 = new RoadPosition(1, 1.0 / 5.0, 20.0, 10.0); 58 | private final static RoadPosition rp12 = new RoadPosition(2, 1.0 / 5.0, 60.0, 10.0); 59 | private final static RoadPosition rp21 = new RoadPosition(1, 2.0 / 5.0, 20.0, 20.0); 60 | private final static RoadPosition rp22 = new RoadPosition(2, 2.0 / 5.0, 60.0, 20.0); 61 | private final static RoadPosition rp31 = new RoadPosition(1, 5.0 / 6.0, 20.0, 40.0); 62 | private final static RoadPosition rp32 = new RoadPosition(3, 1.0 / 4.0, 30.0, 50.0); 63 | private final static RoadPosition rp33 = new RoadPosition(2, 5.0 / 6.0, 60.0, 40.0); 64 | private final static RoadPosition rp41 = new RoadPosition(4, 2.0 / 3.0, 20.0, 70.0); 65 | private final static RoadPosition rp42 = new RoadPosition(5, 2.0 / 3.0, 60.0, 70.0); 66 | 67 | @BeforeClass 68 | public static void setUpClass() { 69 | candidateMap.put(gps1, Arrays.asList(rp11, rp12)); 70 | candidateMap.put(gps2, Arrays.asList(rp21, rp22)); 71 | candidateMap.put(gps3, Arrays.asList(rp31, rp32, rp33)); 72 | candidateMap.put(gps4, Arrays.asList(rp41, rp42)); 73 | 74 | addRouteLength(rp11, rp21, 10.0); 75 | addRouteLength(rp11, rp22, 110.0); 76 | addRouteLength(rp12, rp21, 110.0); 77 | addRouteLength(rp12, rp22, 10.0); 78 | 79 | addRouteLength(rp21, rp31, 20.0); 80 | addRouteLength(rp21, rp32, 40.0); 81 | addRouteLength(rp21, rp33, 80.0); 82 | addRouteLength(rp22, rp31, 80.0); 83 | addRouteLength(rp22, rp32, 60.0); 84 | addRouteLength(rp22, rp33, 20.0); 85 | 86 | addRouteLength(rp31, rp41, 30.0); 87 | addRouteLength(rp31, rp42, 70.0); 88 | addRouteLength(rp32, rp41, 30.0); 89 | addRouteLength(rp32, rp42, 50.0); 90 | addRouteLength(rp33, rp41, 70.0); 91 | addRouteLength(rp33, rp42, 30.0); 92 | } 93 | 94 | private static Date seconds(int seconds) { 95 | Calendar c = new GregorianCalendar(2014, 1, 1); 96 | c.add(Calendar.SECOND, seconds); 97 | return c.getTime(); 98 | } 99 | 100 | private static void addRouteLength(RoadPosition from, RoadPosition to, double routeLength) { 101 | routeLengths.put(new Transition(from, to), routeLength); 102 | } 103 | 104 | /* 105 | * Returns the Cartesian distance between two points. 106 | * For real map matching applications, one would compute the great circle distance between 107 | * two GPS points. 108 | */ 109 | private double computeDistance(Point p1, Point p2) { 110 | final double xDiff = p1.x - p2.x; 111 | final double yDiff = p1.y - p2.y; 112 | return Math.sqrt(xDiff * xDiff + yDiff * yDiff); 113 | } 114 | 115 | /* 116 | * For real map matching applications, candidates would be computed using a radius query. 117 | */ 118 | private Collection computeCandidates(GpsMeasurement gpsMeasurement) { 119 | return candidateMap.get(gpsMeasurement); 120 | } 121 | 122 | private void computeEmissionProbabilities( 123 | TimeStep timeStep) { 124 | for (RoadPosition candidate : timeStep.candidates) { 125 | final double distance = 126 | computeDistance(candidate.position, timeStep.observation.position); 127 | timeStep.addEmissionLogProbability(candidate, 128 | hmmProbabilities.emissionLogProbability(distance)); 129 | } 130 | } 131 | 132 | private void computeTransitionProbabilities( 133 | TimeStep prevTimeStep, 134 | TimeStep timeStep) { 135 | final double linearDistance = computeDistance(prevTimeStep.observation.position, 136 | timeStep.observation.position); 137 | final double timeDiff = (timeStep.observation.time.getTime() - 138 | prevTimeStep.observation.time.getTime()) / 1000.0; 139 | 140 | for (RoadPosition from : prevTimeStep.candidates) { 141 | for (RoadPosition to : timeStep.candidates) { 142 | 143 | // For real map matching applications, route lengths and road paths would be 144 | // computed using a router. The most efficient way is to use a single-source 145 | // multi-target router. 146 | final double routeLength = routeLengths.get(new Transition<>(from, to)); 147 | timeStep.addRoadPath(from, to, new RoadPath(from, to)); 148 | 149 | final double transitionLogProbability = hmmProbabilities.transitionLogProbability( 150 | routeLength, linearDistance, timeDiff); 151 | timeStep.addTransitionLogProbability(from, to, transitionLogProbability); 152 | } 153 | } 154 | } 155 | 156 | @Test 157 | public void testMapMatching() { 158 | final List gpsMeasurements = Arrays.asList(gps1, gps2, gps3, gps4); 159 | 160 | ViterbiAlgorithm viterbi = 161 | new ViterbiAlgorithm<>(); 162 | TimeStep prevTimeStep = null; 163 | for (GpsMeasurement gpsMeasurement : gpsMeasurements) { 164 | final Collection candidates = computeCandidates(gpsMeasurement); 165 | final TimeStep timeStep = 166 | new TimeStep<>(gpsMeasurement, candidates); 167 | computeEmissionProbabilities(timeStep); 168 | if (prevTimeStep == null) { 169 | viterbi.startWithInitialObservation(timeStep.observation, timeStep.candidates, 170 | timeStep.emissionLogProbabilities); 171 | } else { 172 | computeTransitionProbabilities(prevTimeStep, timeStep); 173 | viterbi.nextStep(timeStep.observation, timeStep.candidates, 174 | timeStep.emissionLogProbabilities, timeStep.transitionLogProbabilities, 175 | timeStep.roadPaths); 176 | } 177 | prevTimeStep = timeStep; 178 | } 179 | 180 | List> roadPositions = 181 | viterbi.computeMostLikelySequence(); 182 | 183 | assertFalse(viterbi.isBroken()); 184 | List> expected = new ArrayList<>(); 185 | expected.add(new SequenceState<>(rp11, gps1, (RoadPath) null)); 186 | expected.add(new SequenceState<>(rp21, gps2, new RoadPath(rp11, rp21))); 187 | expected.add(new SequenceState<>(rp31, gps3, new RoadPath(rp21, rp31))); 188 | expected.add(new SequenceState<>(rp41, gps4, new RoadPath(rp31, rp41))); 189 | assertEquals(expected, roadPositions); 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/OfflineMapMatcherTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmwcarit/offline-map-matching/5b6ad51d9856f56194e392806d1c8866717ac535/src/test/java/com/bmw/mapmatchingutils/OfflineMapMatcherTest.png -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/types/GpsMeasurement.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils.types; 19 | 20 | import java.util.Date; 21 | 22 | /** 23 | * Example type for location coordinates. 24 | */ 25 | public class GpsMeasurement { 26 | 27 | public final Date time; 28 | 29 | public Point position; 30 | 31 | public GpsMeasurement(Date time, Point position) { 32 | this.time = time; 33 | this.position = position; 34 | } 35 | 36 | public GpsMeasurement(Date time, double lon, double lat) { 37 | this(time, new Point(lon, lat)); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "GpsMeasurement [time=" + time + ", position=" + position + "]"; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/types/Point.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils.types; 19 | 20 | /** 21 | * Represents a spatial point. 22 | */ 23 | public class Point { 24 | 25 | final public double x; 26 | final public double y; 27 | 28 | public Point(double x, double y) { 29 | this.x = x; 30 | this.y = y; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Point [x=" + x + ", y=" + y + "]"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/types/RoadPath.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils.types; 19 | 20 | import java.util.Objects; 21 | 22 | /** 23 | * Represents the road path between two consecutive road positions. 24 | */ 25 | public class RoadPath { 26 | 27 | // The following members are used to check whether the correct road paths are retrieved 28 | // from the most likely sequence. 29 | 30 | public final RoadPosition from; 31 | public final RoadPosition to; 32 | 33 | public RoadPath(RoadPosition from, RoadPosition to) { 34 | this.from = from; 35 | this.to = to; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(from, to); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) 46 | return true; 47 | if (obj == null) 48 | return false; 49 | if (getClass() != obj.getClass()) 50 | return false; 51 | RoadPath other = (RoadPath) obj; 52 | if (from == null) { 53 | if (other.from != null) 54 | return false; 55 | } else if (!from.equals(other.from)) 56 | return false; 57 | if (to == null) { 58 | if (other.to != null) 59 | return false; 60 | } else if (!to.equals(other.to)) 61 | return false; 62 | return true; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/bmw/mapmatchingutils/types/RoadPosition.java: -------------------------------------------------------------------------------- 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 | package com.bmw.mapmatchingutils.types; 19 | 20 | 21 | 22 | /** 23 | * Default type to represent the position of a vehicle on the road network. 24 | * It is also possible to use a custom road position class instead. 25 | */ 26 | public class RoadPosition { 27 | 28 | /** 29 | * ID of the edge, on which the vehicle is positioned. 30 | */ 31 | public final long edgeId; 32 | 33 | /** 34 | * Position on the edge from beginning as a number in the interval [0,1]. 35 | */ 36 | public final double fraction; 37 | 38 | public final Point position; 39 | 40 | public RoadPosition(long edgeId, double fraction, Point position) { 41 | if (fraction < 0.0 || fraction > 1.0) { 42 | throw new IllegalArgumentException(); 43 | } 44 | 45 | this.edgeId = edgeId; 46 | this.fraction = fraction; 47 | this.position = position; 48 | } 49 | 50 | public RoadPosition(long edgeId, double fraction, double x, double y) { 51 | this(edgeId, fraction, new Point(x, y)); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "RoadPosition [edgeId=" + edgeId + ", fraction=" + fraction 57 | + ", position=" + position + "]"; 58 | } 59 | 60 | } 61 | --------------------------------------------------------------------------------