Skip to content

Commit 03e799c

Browse files
VysotskiVadimgithub-actions[bot]
authored andcommitted
Handle multiple matching in Map Matching API (#10517)
GitOrigin-RevId: f77a66a85936400de5a8ea8fc8719ccb4e99e4db
1 parent 7063f19 commit 03e799c

File tree

12 files changed

+188
-156
lines changed

12 files changed

+188
-156
lines changed

base/src/main/java/com/mapbox/navigation/base/route/NavigationRoute.kt

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,23 @@ class NavigationRoute private constructor(
250250
.toBuilder()
251251
.routeIndex("$index")
252252
.routeOptions(routeOptions)
253+
.waypoints(
254+
model.tracepoints()
255+
?.filterNotNull()
256+
?.filter {
257+
it.waypointIndex() != null && it.matchingsIndex() == index
258+
}
259+
?.map {
260+
DirectionsWaypoint.builder()
261+
.rawLocation(
262+
it.location()!!.coordinates().toDoubleArray(),
263+
)
264+
.name(it.name() ?: "")
265+
// TODO: NAVAND-1737 introduce distance in trace point
266+
// .distance(it.distance)
267+
.build()
268+
},
269+
)
253270
.build()
254271
}?.toMutableList()
255272
val directionsResponse = DirectionsResponse.builder()
@@ -258,21 +275,6 @@ class NavigationRoute private constructor(
258275
// .uuid(model.uuid())
259276
.code(model.code())
260277
.message(model.message())
261-
.waypoints(
262-
model.tracepoints()
263-
?.filterNotNull()
264-
?.filter { it.waypointIndex() != null }
265-
?.map {
266-
DirectionsWaypoint.builder()
267-
.rawLocation(
268-
it.location()!!.coordinates().toDoubleArray(),
269-
)
270-
.name(it.name() ?: "")
271-
// TODO: NAVAND-1737 introduce distance in trace point
272-
// .distance(it.distance)
273-
.build()
274-
},
275-
)
276278
.build()
277279
// TODO: NAVAND-1732 parse map matching response in NN without converting it
278280
// to directions response
@@ -517,7 +519,7 @@ class NavigationRoute private constructor(
517519
},).mapIndexed { index, routeInterface ->
518520
NavigationRoute(
519521
getDirectionsRoute(directionsResponse, index, routeOptions),
520-
getDirectionsWaypoint(directionsResponse, index, routeOptions),
522+
getDirectionsWaypoint(directionsResponse, index),
521523
routeOptions,
522524
routeInterface,
523525
ifNonNull(
@@ -678,7 +680,7 @@ internal fun RouteInterface.toNavigationRoute(
678680
return NavigationRoute(
679681
routeOptions = routeOptions,
680682
directionsRoute = getDirectionsRoute(directionsResponse, routeIndex, routeOptions),
681-
waypoints = getDirectionsWaypoint(directionsResponse, routeIndex, routeOptions),
683+
waypoints = getDirectionsWaypoint(directionsResponse, routeIndex),
682684
nativeRoute = this,
683685
expirationTimeElapsedSeconds = refreshTtl?.plus(responseTimeElapsedSeconds),
684686
// Continuous alternatives are always from Directions API
@@ -745,11 +747,6 @@ private fun getDirectionsRoute(
745747
private fun getDirectionsWaypoint(
746748
directionsResponse: DirectionsResponse,
747749
routeIndex: Int,
748-
routeOptions: RouteOptions,
749750
): List<DirectionsWaypoint>? {
750-
return if (routeOptions.waypointsPerRoute() == true) {
751-
directionsResponse.routes()[routeIndex].waypoints()
752-
} else {
753-
directionsResponse.waypoints()
754-
}
751+
return directionsResponse.routes()[routeIndex].waypoints() ?: directionsResponse.waypoints()
755752
}

base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteTest.kt

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
@file:OptIn(ExperimentalMapboxNavigationAPI::class)
1+
@file:OptIn(ExperimentalMapboxNavigationAPI::class, ExperimentalPreviewMapboxNavigationAPI::class)
22

33
package com.mapbox.navigation.base.route
44

55
import com.google.gson.JsonPrimitive
66
import com.mapbox.api.directions.v5.models.DirectionsResponse
77
import com.mapbox.api.directions.v5.models.DirectionsRoute
88
import com.mapbox.api.directions.v5.models.RouteOptions
9+
import com.mapbox.geojson.Point
910
import com.mapbox.navigation.base.ExperimentalMapboxNavigationAPI
11+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1012
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
1113
import com.mapbox.navigation.base.internal.route.updateExpirationTime
1214
import com.mapbox.navigation.testing.FileUtils
@@ -220,4 +222,77 @@ class NavigationRouteTest {
220222
},
221223
)
222224
}
225+
226+
@Test
227+
fun `map matched route`() {
228+
val requestUrl = FileUtils.loadJsonFixture("kochelsee_map_matching_request.txt")
229+
val responseJson = FileUtils.loadJsonFixture("kochelsee_map_matching_response.json")
230+
231+
val result = NavigationRoute.createMatchedRoutes(
232+
responseJson,
233+
requestUrl,
234+
)
235+
236+
assertNull(result.error)
237+
val matches = result.value!!
238+
239+
assertEquals(1, matches.size)
240+
val match = matches[0]
241+
242+
assertEquals(
243+
0.0,
244+
match.confidence,
245+
0.01,
246+
)
247+
assertEquals(
248+
2,
249+
match.navigationRoute.waypoints?.size,
250+
)
251+
assertEquals(
252+
1,
253+
match.navigationRoute.directionsRoute.legs()?.size,
254+
)
255+
assertEquals(
256+
2,
257+
match.navigationRoute.directionsRoute.legs()?.get(0)?.steps()?.size,
258+
)
259+
}
260+
261+
@Test
262+
fun `multiple matches`() {
263+
val requestUrl = FileUtils.loadJsonFixture(
264+
"kochelsee_multiple_matches_map_matching_request.txt",
265+
)
266+
val responseJson = FileUtils.loadJsonFixture(
267+
"kochelsee_multiple_matches_map_matching_response.json",
268+
)
269+
270+
val result = NavigationRoute.createMatchedRoutes(
271+
responseJson,
272+
requestUrl,
273+
)
274+
275+
assertNull(result.error)
276+
val matches = result.value!!
277+
278+
assertEquals(2, matches.size)
279+
280+
val firstMatch = matches[0]
281+
assertEquals(
282+
listOf(
283+
Point.fromLngLat(11.359198, 47.642065),
284+
Point.fromLngLat(11.351706, 47.632409),
285+
),
286+
firstMatch.navigationRoute.waypoints?.map { it.location() },
287+
)
288+
289+
val secondMatch = matches[1]
290+
assertEquals(
291+
listOf(
292+
Point.fromLngLat(11.353961, 47.631334),
293+
Point.fromLngLat(11.355391, 47.628011),
294+
),
295+
secondMatch.navigationRoute.waypoints?.map { it.location() },
296+
)
297+
}
223298
}

base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteWaypointsTest.kt

Lines changed: 16 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import org.junit.runners.Parameterized
1515

1616
@RunWith(Parameterized::class)
1717
class NavigationRouteWaypointsTest(
18-
private val waypointsPerRoute: Boolean?,
1918
private val responseWaypoints: List<DirectionsWaypoint>?,
2019
private val routeWaypoints: List<DirectionsWaypoint>?,
2120
private val expectedWaypoints: List<DirectionsWaypoint>?,
@@ -42,99 +41,32 @@ class NavigationRouteWaypointsTest(
4241
.build(),
4342
)
4443
return listOf(
45-
// #0
46-
arrayOf(null, null, null, null),
47-
arrayOf(false, null, null, null),
48-
arrayOf(true, null, null, null),
49-
// #3
50-
arrayOf(null, null, emptyList<DirectionsWaypoint>(), null),
51-
arrayOf(false, null, emptyList<DirectionsWaypoint>(), null),
44+
// response null, route null -> null
45+
arrayOf(null, null, null),
46+
// response null, route empty -> route has empty waypoints -> result empty (no fallback)
47+
arrayOf(null, emptyList<DirectionsWaypoint>(), emptyList<DirectionsWaypoint>()),
48+
// response null, route filled -> route
49+
arrayOf(null, filledWaypoints1, filledWaypoints1),
50+
// response empty, route null -> response (empty)
51+
arrayOf(emptyList<DirectionsWaypoint>(), null, emptyList<DirectionsWaypoint>()),
52+
// response empty, route empty -> route empty
5253
arrayOf(
53-
true,
54-
null,
5554
emptyList<DirectionsWaypoint>(),
5655
emptyList<DirectionsWaypoint>(),
57-
),
58-
// #6
59-
arrayOf(null, null, filledWaypoints1, null),
60-
arrayOf(false, null, filledWaypoints1, null),
61-
arrayOf(true, null, filledWaypoints1, filledWaypoints1),
62-
// #9
63-
arrayOf(
64-
null,
65-
emptyList<DirectionsWaypoint>(),
66-
null,
67-
emptyList<DirectionsWaypoint>(),
68-
),
69-
// #10
70-
arrayOf(
71-
false,
72-
emptyList<DirectionsWaypoint>(),
73-
null,
74-
emptyList<DirectionsWaypoint>(),
75-
),
76-
// #11
77-
arrayOf(
78-
true,
79-
emptyList<DirectionsWaypoint>(),
80-
null,
81-
null,
82-
),
83-
// #12
84-
arrayOf(
85-
null,
86-
emptyList<DirectionsWaypoint>(),
87-
emptyList<DirectionsWaypoint>(),
88-
emptyList<DirectionsWaypoint>(),
89-
),
90-
arrayOf(
91-
false,
9256
emptyList<DirectionsWaypoint>(),
93-
emptyList<DirectionsWaypoint>(),
94-
emptyList<DirectionsWaypoint>(),
95-
),
96-
arrayOf(
97-
true,
98-
emptyList<DirectionsWaypoint>(),
99-
emptyList<DirectionsWaypoint>(),
100-
emptyList<DirectionsWaypoint>(),
101-
),
102-
// #15
103-
arrayOf(
104-
null,
105-
emptyList<DirectionsWaypoint>(),
106-
filledWaypoints1,
107-
emptyList<DirectionsWaypoint>(),
108-
),
109-
arrayOf(
110-
false,
111-
emptyList<DirectionsWaypoint>(),
112-
filledWaypoints1,
113-
emptyList<DirectionsWaypoint>(),
114-
),
115-
arrayOf(true, emptyList<DirectionsWaypoint>(), filledWaypoints1, filledWaypoints1),
116-
// #18
117-
arrayOf(null, filledWaypoints2, null, filledWaypoints2),
118-
arrayOf(false, filledWaypoints2, null, filledWaypoints2),
119-
arrayOf(
120-
true,
121-
filledWaypoints2,
122-
null,
123-
null,
12457
),
125-
// #21
126-
arrayOf(null, filledWaypoints2, emptyList<DirectionsWaypoint>(), filledWaypoints2),
127-
arrayOf(false, filledWaypoints2, emptyList<DirectionsWaypoint>(), filledWaypoints2),
58+
// response empty, route filled -> route
59+
arrayOf(emptyList<DirectionsWaypoint>(), filledWaypoints1, filledWaypoints1),
60+
// response filled, route null -> response
61+
arrayOf(filledWaypoints2, null, filledWaypoints2),
62+
// response filled, route empty -> route has empty waypoints -> result empty (no fallback)
12863
arrayOf(
129-
true,
13064
filledWaypoints2,
13165
emptyList<DirectionsWaypoint>(),
13266
emptyList<DirectionsWaypoint>(),
13367
),
134-
// #24
135-
arrayOf(null, filledWaypoints2, filledWaypoints1, filledWaypoints2),
136-
arrayOf(false, filledWaypoints2, filledWaypoints1, filledWaypoints2),
137-
arrayOf(true, filledWaypoints2, filledWaypoints1, filledWaypoints1),
68+
// response filled, route filled -> route
69+
arrayOf(filledWaypoints2, filledWaypoints1, filledWaypoints1),
13870
)
13971
}
14072
}
@@ -163,7 +95,6 @@ class NavigationRouteWaypointsTest(
16395
Point.fromLngLat(3.3, 4.4),
16496
),
16597
)
166-
.waypointsPerRoute(waypointsPerRoute)
16798
.build(),
16899
RouterOrigin.OFFLINE,
169100
).first()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://api.mapbox.com/matching/v5/mapbox/driving-traffic/11.359198%2C47.642065%3B11.351664%2C47.637488%3B11.352736%2C47.63392%3B11.351706%2C47.632409?geometries=polyline6&overview=full&steps=true&annotations=duration,distance,speed,congestion,maxspeed,speed_source&access_token=***&waypoints=0;3

base/src/test/resources/kochelsee_map_matching_response.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://api.mapbox.com/matching/v5/mapbox/driving-traffic/11.359198%2C47.642065%3B11.351664%2C47.637488%3B11.352736%2C47.63392%3B11.351706%2C47.632409%3B11.353961%2C47.631334%3B11.355387%2C47.62796?geometries=polyline6&overview=full&steps=true&annotations=duration,distance,speed,congestion,maxspeed,speed_source&access_token=***&waypoints=0;5

base/src/test/resources/kochelsee_multiple_matches_map_matching_response.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
- Fixed waypoint handling when multiple matches are returned in `MapMatchingSuccessfulResult.matches`; waypoints are now assigned to the correct match.
2+
3+
- ⚠️ Breaking change (preview API): removed `MapMatchingSuccessfulResult#navigationRoutes`.
4+
Why: the `navigationRoutes` property encouraged incorrect usage — calling
5+
`mapboxNavigation.setNavigationRoutes(result.navigationRoutes)` treats each
6+
match as an alternative route. Matches are results of map-matching and are
7+
not true route alternatives; passing them together will make the
8+
navigator accept only first route rejecting the others.
9+
Migration guide: select navigation route from a single match `mapboxNavigation.setNavigationRoutes(listOf(result.matches.first().navigationRoute))`.

0 commit comments

Comments
 (0)