Open4
Androidでマップマッチングを行いたい
OsmAndのコード
public int calculateCurrentRoute(@NonNull Location currentLocation, double posTolerance,
@NonNull List<Location> routeNodes, int currentRoute,
boolean updateAndNotify) {
while (currentRoute + 1 < routeNodes.size()) {
// 現在地から現在のルートまでの距離を計算
double dist = currentLocation.distanceTo(routeNodes.get(currentRoute));
if (currentRoute > 0) {
// 現在地から前のルートまでの直交距離を計算
dist = RoutingHelperUtils.getOrthogonalDistance(currentLocation, routeNodes.get(currentRoute - 1),
routeNodes.get(currentRoute));
}
boolean processed = false;
// if we are still too far try to proceed many points
// if not then look ahead only 3 in order to catch sharp turns
boolean longDistance = dist >= 250;
// 直交距離が最小となる次のルートを探す
int newCurrentRoute = RoutingHelperUtils.lookAheadFindMinOrthogonalDistance(currentLocation, routeNodes, currentRoute, longDistance ? 15 : 8);
double newDist = RoutingHelperUtils.getOrthogonalDistance(currentLocation, routeNodes.get(newCurrentRoute),
routeNodes.get(newCurrentRoute + 1));
if (longDistance) { // ルートポイントから現在地までの距離が250m以上の場合
if (newDist < dist) { // 次のルートポイントまでの距離の方が近い場合
if (log.isDebugEnabled()) {
log.debug("Processed by distance : (new) " + newDist + " (old) " + dist); //$NON-NLS-1$//$NON-NLS-2$
}
// 通過済みにする
processed = true;
}
} else if (newDist < dist || newDist < posTolerance / 8) { // 右辺は交差点などで次のポイントが近い場合を補足
// 次のルートポイントまでの距離の方が近い or 前の方が近いけど、次のルートポイントまでの距離が極端に近い posTolerance / 8以下の場合
// newDist < posTolerance / 8 - 4-8 m (avoid distance 0 till next turn)
if (dist > posTolerance) { // 現在のルートポイントから十分に離れている場合
processed = true;
if (log.isDebugEnabled()) {
log.debug("Processed by distance : " + newDist + " " + dist); //$NON-NLS-1$//$NON-NLS-2$
}
} else {
if (currentLocation.hasBearing() && !deviceHasBearing) { // デバイスが方位情報を持っているかチェック
deviceHasBearing = true;
}
// lastFixedLocation.bearingTo - gives artefacts during u-turn, so we avoid for devices with bearing
if ((currentRoute > 0 || newCurrentRoute > 0) &&
(currentLocation.hasBearing() || (!deviceHasBearing && lastFixedLocation != null))) {
float bearingToRoute = currentLocation.bearingTo(routeNodes.get(currentRoute));
float bearingRouteNext = routeNodes.get(newCurrentRoute).bearingTo(routeNodes.get(newCurrentRoute + 1));
float bearingMotion = currentLocation.hasBearing() ? currentLocation.getBearing() : lastFixedLocation
.bearingTo(currentLocation);
double diff = Math.abs(MapUtils.degreesDiff(bearingMotion, bearingToRoute));
double diffToNext = Math.abs(MapUtils.degreesDiff(bearingMotion, bearingRouteNext));
if (diff > diffToNext) { //デバイスの進行方向と次のルートポイントの方向が一致している場合は通過
if (log.isDebugEnabled()) {
log.debug("Processed point bearing deltas : " + diff + " " + diffToNext);
}
processed = true;
}
}
}
}
if (processed) {
// that node already passed
currentRoute = newCurrentRoute + 1;
if (updateAndNotify) {
route.updateCurrentRoute(newCurrentRoute + 1);
app.getNotificationHelper().refreshNotification(NotificationType.NAVIGATION);
fireRoutingDataUpdateEvent();
}
} else {
break; // 通過済みじゃなければbreakしてその値をnewRouteにする
}
}
return currentRoute;
}
OsmAndの改良版&テストコード(仮)
fun findCurrentRoutePoint(location: Location): RoutePoint {
var currentRouteIdx = currentPointIdx
val currentLocationCoordinate = Coordinate(location.longitude, location.latitude)
while (currentRouteIdx + 1 < points.size) {
val distanceToCurrentRoutePoint =
calculateDistanceToRoutePoint(currentRouteIdx, currentLocationCoordinate)
val longDistanceFromCurrentRoutePoint = distanceToCurrentRoutePoint > 250
val (newRouteIdx, distanceToNewCurrentRoute) = findLookAhead(
location,
currentRouteIdx,
points,
if (longDistanceFromCurrentRoutePoint) 15 else 8
)
val shouldCheck =
distanceToNewCurrentRoute < distanceToCurrentRoutePoint || distanceToNewCurrentRoute < 8
println("currentRoute: ${points[currentRouteIdx].name}")
println("distanceToCurrentRoutePoint: $distanceToCurrentRoutePoint")
println("newRoute: ${points[newRouteIdx].name}")
println("distanceToNewCurrentRoute: $distanceToNewCurrentRoute")
if (!shouldCheck) break
if (longDistanceFromCurrentRoutePoint) {
currentRouteIdx = newRouteIdx
continue
}
// 近距離の場合のみ、bearingのチェックを行う
val shouldUpdateRoute = checkBearingForClosePoint(
currentLocationCoordinate,
location,
points,
currentRouteIdx,
newRouteIdx
)
if (shouldUpdateRoute) {
currentRouteIdx = newRouteIdx
} else {
break
}
}
val result = points[currentRouteIdx]
println("result: ${result.name}")
return points[currentRouteIdx]
}
@MockK
private lateinit var mockLocation: Location
private val routePoints = listOf(
RoutePoint(35.6895, 139.6917, "新宿"),
RoutePoint(35.692223, 139.7034052, "新宿3丁目"),
RoutePoint(35.6968759, 139.7063985, "新宿7丁目セブン"),
RoutePoint(35.7030794, 139.7392931, "飯田橋")
)
@Test
fun 位置情報の更新をテスト() = runTest {
every { mockLocation.latitude } returns 35.7003262
every { mockLocation.longitude } returns 139.7333708
every { mockLocation.accuracy } returns 10.0f
every { mockLocation.hasAccuracy() } returns true
every { mockLocation.bearing } returns 45f
val progress = RouteProgress(routePoints, "Test Route", "Test Description")
val currentRoutePoint = progress.findCurrentRoutePoint(mockLocation)
assertNotNull(currentRoutePoint)
assertEquals(35.7030794, currentRoutePoint.latitude, 0.0001)
assertEquals(139.7392931, currentRoutePoint.longitude, 0.0001)
}
幾何解析(Geometric)マップマッチングと位相幾何(Topological)解析の組み合わせ