Skip to content

Commit 1ebef64

Browse files
committed
fix(routes): implement robust offline fallback route and fix blank screen for View-based Kotlin/Java RoutesActivity
1 parent d6a7a2f commit 1ebef64

5 files changed

Lines changed: 150 additions & 107 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2026 Google LLC
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+
17+
package com.example.maps3d.common
18+
19+
import com.google.android.gms.maps.model.LatLng
20+
21+
/**
22+
* Static pre-baked route coordinates crossing the mountains of Oahu, Hawaii along the Pali Highway.
23+
*
24+
* Serves as a robust local fallback in case the user does not have active network connectivity,
25+
* has quota limitations on the Routes API, or has not enabled the Routes API product in their
26+
* Google Cloud Console project.
27+
*/
28+
object OahuRouteData {
29+
30+
/**
31+
* A pre-baked list of coordinates representing a scenic mountain drive.
32+
*/
33+
@JvmStatic
34+
val FALLBACK_ROUTE: List<LatLng> = listOf(
35+
LatLng(21.307043, -157.858984), // Start: Honolulu
36+
LatLng(21.312821, -157.851219),
37+
LatLng(21.319562, -157.842987),
38+
LatLng(21.325890, -157.835012),
39+
LatLng(21.331210, -157.828910),
40+
LatLng(21.338760, -157.820123),
41+
LatLng(21.344980, -157.813990),
42+
LatLng(21.349020, -157.807890), // Nu'uanu Pali Lookout area
43+
LatLng(21.354910, -157.801210),
44+
LatLng(21.360890, -157.793450),
45+
LatLng(21.367120, -157.784980),
46+
LatLng(21.372900, -157.775120),
47+
LatLng(21.378120, -157.762100),
48+
LatLng(21.383910, -157.745120),
49+
LatLng(21.388910, -157.730100),
50+
LatLng(21.390177, -157.719454) // End: Kailua
51+
)
52+
}
-400 Bytes
Loading

Maps3DSamples/ApiDemos/java-app/src/main/java/com/example/maps3djava/routes/RoutesActivity.java

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import com.example.maps3d.common.PositionAndHeading;
3434
import com.example.maps3d.common.RouteEngine;
35+
import com.example.maps3d.common.OahuRouteData;
3536
import com.example.maps3dcommon.R;
3637
import com.example.maps3djava.BuildConfig;
3738
import com.example.maps3djava.sampleactivity.SampleBaseActivity;
@@ -226,65 +227,63 @@ public void onMap3DViewReady(@NonNull GoogleMap3D googleMap3D) {
226227

227228
private void loadAndRenderRouteAsync(GoogleMap3D googleMap3D) {
228229
String apiKey = BuildConfig.MAPS3D_API_KEY;
229-
if (apiKey.isEmpty() || apiKey.contains("YOUR_API_KEY")) {
230-
Toast.makeText(this, "API Key is missing or invalid. Cannot fetch route.", Toast.LENGTH_LONG).show();
231-
return;
232-
}
233-
234230
LatLng origin = new LatLng(21.307043, -157.858984);
235231
LatLng destination = new LatLng(21.390177, -157.719454);
236232

237-
routeFetchFuture = executorService.submit(routeRepository.fetchRouteCallable(apiKey, origin, destination));
238233
executorService.execute(() -> {
234+
List<LatLng> decoded;
239235
try {
236+
if (apiKey.isEmpty() || apiKey.contains("YOUR_API_KEY")) {
237+
throw new Exception("Invalid or missing API Key");
238+
}
239+
routeFetchFuture = executorService.submit(routeRepository.fetchRouteCallable(apiKey, origin, destination));
240240
RouteData routeData = routeFetchFuture.get();
241-
242-
// Decode route coords in worker thread (CPU-heavy)
243-
List<LatLng> decoded = PolyUtil.decode(routeData.getEncodedPolyline());
244-
245-
// Update UI elements back on standard Main loop thread
246-
mainHandler.post(() -> {
247-
decodedRoute = decoded;
248-
cumulativeDistances = RouteEngine.calculateCumulativeDistances(decoded);
249-
totalDistance = cumulativeDistances[cumulativeDistances.length - 1];
250-
251-
// 1. Draw the blue route polyline
252-
List<LatLngAltitude> linePath = new ArrayList<>();
253-
for (LatLng point : decoded) {
254-
linePath.add(new LatLngAltitude(point.latitude, point.longitude, 0.0));
255-
}
256-
257-
PolylineOptions polyOptions = new PolylineOptions();
258-
polyOptions.setPath(linePath);
259-
polyOptions.setStrokeColor(Color.BLUE);
260-
polyOptions.setStrokeWidth(10.0);
261-
polyOptions.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND);
262-
polyOptions.setZIndex(5);
263-
routePolyline = googleMap3D.addPolyline(polyOptions);
264-
265-
// 2. Load the 3D Car model
266-
ModelOptions modelOpts = new ModelOptions();
267-
modelOpts.setId("vehicle_car_java");
268-
modelOpts.setPosition(new LatLngAltitude(decoded.get(0).latitude, decoded.get(0).longitude, 25.0));
269-
modelOpts.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
270-
modelOpts.setOrientation(new Orientation(0.0, -90.0, 0.0));
271-
modelOpts.setUrl("https://storage.googleapis.com/gmp-maps-demos/p3d-map/assets/red_car.glb");
272-
modelOpts.setScale(new Vector3D(50.0, 50.0, 50.0));
273-
vehicleModel = googleMap3D.addModel(modelOpts);
274-
275-
updateVehiclePositionAndCamera();
276-
277-
// Trigger play automatically once map is populated
278-
togglePlayback(true);
279-
});
241+
decoded = PolyUtil.decode(routeData.getEncodedPolyline());
280242
} catch (Exception e) {
281-
Log.e(getTAG(), "Failed to load or decode Honolulu route details", e);
243+
Log.w(getTAG(), "Routes API fetch failed (" + e.getLocalizedMessage() + "). Falling back to pre-baked Oahu mountain route.");
244+
decoded = OahuRouteData.getFALLBACK_ROUTE();
282245
mainHandler.post(() -> Toast.makeText(
283246
RoutesActivity.this,
284-
"Failed to load route details: " + e.getLocalizedMessage(),
247+
"Offline: Using local Oahu fallback route",
285248
Toast.LENGTH_LONG
286249
).show());
287250
}
251+
252+
final List<LatLng> finalDecoded = decoded;
253+
mainHandler.post(() -> {
254+
decodedRoute = finalDecoded;
255+
cumulativeDistances = RouteEngine.calculateCumulativeDistances(finalDecoded);
256+
totalDistance = cumulativeDistances[cumulativeDistances.length - 1];
257+
258+
// 1. Draw the blue route polyline
259+
List<LatLngAltitude> linePath = new ArrayList<>();
260+
for (LatLng point : finalDecoded) {
261+
linePath.add(new LatLngAltitude(point.latitude, point.longitude, 0.0));
262+
}
263+
264+
PolylineOptions polyOptions = new PolylineOptions();
265+
polyOptions.setPath(linePath);
266+
polyOptions.setStrokeColor(Color.BLUE);
267+
polyOptions.setStrokeWidth(10.0);
268+
polyOptions.setAltitudeMode(AltitudeMode.CLAMP_TO_GROUND);
269+
polyOptions.setZIndex(5);
270+
routePolyline = googleMap3D.addPolyline(polyOptions);
271+
272+
// 2. Load the 3D Car model
273+
ModelOptions modelOpts = new ModelOptions();
274+
modelOpts.setId("vehicle_car_java");
275+
modelOpts.setPosition(new LatLngAltitude(finalDecoded.get(0).latitude, finalDecoded.get(0).longitude, 25.0));
276+
modelOpts.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
277+
modelOpts.setOrientation(new Orientation(0.0, -90.0, 0.0));
278+
modelOpts.setUrl("https://storage.googleapis.com/gmp-maps-demos/p3d-map/assets/red_car.glb");
279+
modelOpts.setScale(new Vector3D(50.0, 50.0, 50.0));
280+
vehicleModel = googleMap3D.addModel(modelOpts);
281+
282+
updateVehiclePositionAndCamera();
283+
284+
// Trigger play automatically once map is populated
285+
togglePlayback(true);
286+
});
288287
});
289288
}
290289

-108 Bytes
Loading

Maps3DSamples/ApiDemos/kotlin-app/src/main/java/com/example/maps3dkotlin/routes/RoutesActivity.kt

Lines changed: 52 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.core.view.WindowCompat
2626
import androidx.lifecycle.lifecycleScope
2727
import com.example.maps3d.common.toHeading
2828
import com.example.maps3d.common.RouteEngine
29+
import com.example.maps3d.common.OahuRouteData
2930
import com.example.maps3dcommon.R
3031
import com.example.maps3dkotlin.BuildConfig
3132
import com.example.maps3dkotlin.sampleactivity.SampleBaseActivity
@@ -215,79 +216,70 @@ class RoutesActivity : SampleBaseActivity() {
215216
*/
216217
private suspend fun loadAndRenderRoute(googleMap3D: GoogleMap3D) {
217218
val apiKey = BuildConfig.MAPS3D_API_KEY
218-
if (apiKey.isEmpty() || apiKey.contains("YOUR_API_KEY")) {
219-
withContext(Dispatchers.Main) {
220-
Toast.makeText(
221-
this@RoutesActivity,
222-
"API Key is missing or invalid. Cannot fetch route.",
223-
Toast.LENGTH_LONG
224-
).show()
225-
}
226-
return
227-
}
228-
229-
// Waypoints corresponding to standard Honolulu coordinate locations
230219
val origin = LatLng(21.307043, -157.858984)
231220
val destination = LatLng(21.390177, -157.719454)
221+
var decoded: List<LatLng>
232222

233223
try {
234-
val routeData = routeRepository.fetchRoute(apiKey, origin, destination)
235-
236-
// CPU-heavy decoding of standard Google encoded polyline format
237-
val decoded = PolyUtil.decode(routeData.encodedPolyline)
238-
239-
withContext(Dispatchers.Main) {
240-
decodedRoute = decoded
241-
cumulativeDistances = RouteEngine.calculateCumulativeDistances(decoded)
242-
totalDistance = cumulativeDistances.last()
243-
244-
// 1. Draw the blue Polyline representational trail
245-
routePolyline = googleMap3D.addPolyline(polylineOptions {
246-
path = decoded.map { latLngAltitude { latitude = it.latitude; longitude = it.longitude; altitude = 0.0 } }
247-
strokeColor = Color.BLUE
248-
strokeWidth = 10.0
249-
altitudeMode = AltitudeMode.CLAMP_TO_GROUND
250-
zIndex = 5
251-
})
252-
253-
// 2. Place the 3D model of the Red Car at starting coordinate
254-
vehicleModel = googleMap3D.addModel(modelOptions {
255-
id = "vehicle_car"
256-
position = latLngAltitude {
257-
latitude = decoded.first().latitude
258-
longitude = decoded.first().longitude
259-
altitude = 25.0 // Hover altitude above terrain
260-
}
261-
altitudeMode = AltitudeMode.RELATIVE_TO_GROUND
262-
orientation = orientation {
263-
heading = 0.0
264-
tilt = -90.0
265-
roll = 0.0
266-
}
267-
url = "https://storage.googleapis.com/gmp-maps-demos/p3d-map/assets/red_car.glb"
268-
scale = vector3D {
269-
x = 50.0
270-
y = 50.0
271-
z = 50.0
272-
}
273-
})
274-
275-
// Position camera directly behind the starting model position
276-
updateVehiclePositionAndCamera()
277-
278-
// Auto-play to start
279-
togglePlayback(true)
224+
if (apiKey.isEmpty() || apiKey.contains("YOUR_API_KEY")) {
225+
throw Exception("Invalid or missing API Key")
280226
}
227+
val routeData = routeRepository.fetchRoute(apiKey, origin, destination)
228+
decoded = PolyUtil.decode(routeData.encodedPolyline)
281229
} catch (e: Exception) {
282-
Log.e(TAG, "Failed to fetch/draw route", e)
230+
Log.w(TAG, "Routes API fetch failed: ${e.localizedMessage}. Falling back to pre-baked Oahu mountain route.")
231+
decoded = OahuRouteData.FALLBACK_ROUTE
283232
withContext(Dispatchers.Main) {
284233
Toast.makeText(
285234
this@RoutesActivity,
286-
"Error loading route details: ${e.localizedMessage}",
235+
"Offline: Using local Oahu fallback route",
287236
Toast.LENGTH_LONG
288237
).show()
289238
}
290239
}
240+
241+
withContext(Dispatchers.Main) {
242+
decodedRoute = decoded
243+
cumulativeDistances = RouteEngine.calculateCumulativeDistances(decoded)
244+
totalDistance = cumulativeDistances.last()
245+
246+
// 1. Draw the blue Polyline representational trail
247+
routePolyline = googleMap3D.addPolyline(polylineOptions {
248+
path = decoded.map { latLngAltitude { latitude = it.latitude; longitude = it.longitude; altitude = 0.0 } }
249+
strokeColor = Color.BLUE
250+
strokeWidth = 10.0
251+
altitudeMode = AltitudeMode.CLAMP_TO_GROUND
252+
zIndex = 5
253+
})
254+
255+
// 2. Place the 3D model of the Red Car at starting coordinate
256+
vehicleModel = googleMap3D.addModel(modelOptions {
257+
id = "vehicle_car"
258+
position = latLngAltitude {
259+
latitude = decoded.first().latitude
260+
longitude = decoded.first().longitude
261+
altitude = 25.0 // Hover altitude above terrain
262+
}
263+
altitudeMode = AltitudeMode.RELATIVE_TO_GROUND
264+
orientation = orientation {
265+
heading = 0.0
266+
tilt = -90.0
267+
roll = 0.0
268+
}
269+
url = "https://storage.googleapis.com/gmp-maps-demos/p3d-map/assets/red_car.glb"
270+
scale = vector3D {
271+
x = 50.0
272+
y = 50.0
273+
z = 50.0
274+
}
275+
})
276+
277+
// Position camera directly behind the starting model position
278+
updateVehiclePositionAndCamera()
279+
280+
// Auto-play to start
281+
togglePlayback(true)
282+
}
291283
}
292284

293285
/**

0 commit comments

Comments
 (0)