├── .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 |
--------------------------------------------------------------------------------