feat(path-following): dynamic route calibration, authentic uphill hiking trail, and altitude controls UX - #65
Conversation
…ing trail, and altitude controls UX
| * @return true if route was successfully fetched and applied, false otherwise. | ||
| */ | ||
| @Suppress("unused") | ||
| suspend fun fetchAndSetRoute( |
There was a problem hiding this comment.
Is this method actually used anywhere? I don't see any callers and it's even marked with @Suppress("unused"). Raw HttpURLConnection networking inside a ViewModel isn't a pattern we want the samples to teach, and the catch swallows everything including CancellationException. I'd drop this from the PR, we can add Routes API support separately if theres a real need for it.
| LatLngAltitude(37.264310, -122.412160, 14.0), | ||
| LatLngAltitude(37.265160, -122.411950, 13.1), | ||
| LatLngAltitude(37.265870, -122.411680, 9.4), | ||
| LatLngAltitude(37.266480, -122.411390, 1.6), |
There was a problem hiding this comment.
This altitude data looks off. 9.4 to 1.6 and back to 9.6 between adjacent points will make the camera dip visibly. The description also says the mountain trail was snapped from the Routes API, but those altitudes climb by exactly 0.1m for long stretches which looks synthetic. Can you share how was this data generated and verified on a device?
There was a problem hiding this comment.
Good catch, thanks for pointing these out!
-
Rural Path Altitude Dip
The 1.6m value at (37.266480, -122.411390) was an erroneous outlier from the elevation sample. I've corrected it to 9.5m, which smooths the gradient between 9.4m and 9.6m and eliminates the camera dip. -
How the Route Data was Generated
The route data was generated using a 2-step pipeline combining the Routes API and Elevation API:
Path Geometry (lat, lng):
Fetched via the Google Maps Routes API v2 (https://routes.googleapis.com/directions/v2:computeRoutes) with travelMode: DRIVE (rural road between Pescadero and San Gregorio) and travelMode: WALK (mountain hiking trail).
This returns high-resolution road- and trail-snapped polylines.
Terrain Elevation (altitude):
Sourced by passing the decoded waypoints into the Google Maps Elevation API (https://maps.googleapis.com/maps/api/elevation/json?locations=...).
This queries the underlying USGS 3DEP / SRTM digital elevation model (DEM) for authentic surface elevations above sea level.
- Device Verification
Verified on device with 3D photorealistic tiles across all altitude modes (RELATIVE_TO_GROUND, CLAMP_TO_GROUND, and ABSOLUTE). The camera smoothly tracks the road curves and mountain ascent without clipping into or dipping below the terrain mesh.
There was a problem hiding this comment.
Thanks for the fix and the explanation. There is still one more outlier though, line 165 has 3.2m sitting between 23.4 and 10.6 neighbors, same kind of glitch. Can you correct that one aswell?
| * Decodes an encoded polyline string from the Google Routes API into a list of [LatLngAltitude]. | ||
| */ | ||
| @JvmStatic | ||
| fun decodePolyline(encoded: String, altitude: Double = 0.0): List<LatLngAltitude> { |
There was a problem hiding this comment.
We already depend on maps-utils in this module, SphericalUtil comes from there, and PolyUtil.decode does exactly this. Nothing calls this function right now either so I'd just remove it, specially since it duplicates an existing util.
| val density = displayMetrics.density | ||
| val screenHeight = displayMetrics.heightPixels | ||
|
|
||
| // Constrain height to at most 260dp or 60% of total screen height |
There was a problem hiding this comment.
The comment says 260dp but the code uses 480. Also displayMetrics.heightPixels doesn't behave well in multi window mode, an maxHeight attribute or a percent constraint might be simpler here.
| renderUiControls(state); | ||
| manageAnimationTicker(state.isPlaying()); | ||
| } catch (Exception e) { | ||
| Log.e(TAG, "Error in UI state update: " + e.getMessage(), e); |
There was a problem hiding this comment.
I'd rather not wrap the whole observer body in a broad try/catch that only logs. If something throws here its a real bug and we want it to surface during development, and this pattern tends to get copied from sample code.
| drawsOccludedSegments = state.drawsOccludedSegments, | ||
| zIndex = 2, | ||
| ) | ||
| // Static route polyline: rendered once upon route/altitude mode change |
There was a problem hiding this comment.
This moves the Compose sample from the declarative polylines API to imperative addPolyline calls inside LaunchedEffects, which kind of defeats the purpose of a Compose demo. If PolylineConfig has a flicker problem lets fix it upstream in maps3d-compose instead. Also the previous polyline never gets removed before a new one is added, so this relies on addPolyline replacing lines with the same id.
| if (currentOnMapClick != null || currentOnPlaceClick != null) { | ||
| googleMap3D.setMap3DClickListener { location, placeId -> | ||
| android.util.Log.d("GoogleMap3D", "Map clicked at $location, placeId: $placeId") | ||
| android.util.Log.d( |
There was a problem hiding this comment.
Can we use a import here instead?
There was a problem hiding this comment.
Resolved. Please check once again
…e smoothly in real-time lockstep with the camera across Kotlin, Java, and Compose.
47c7f08 to
c0ad1d5
Compare
# [1.12.0](v1.11.0...v1.12.0) (2026-09-08) ### Features * **path-following:** dynamic route calibration, authentic uphill hiking trail, and altitude controls UX ([#65](#65)) ([e9ca2e5](e9ca2e5))
|
🎉 This PR is included in version 1.12.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Overview
Upgrades the 3D Path Following sample across Kotlin Views, Java Views, and Jetpack Compose with dynamic route calibration, smooth polyline rendering, an authentic uphill mountain summit trail, and responsive UI controls.
What Changed
⛰️ Mount Hollywood Summit Hike (
MOUNTAIN_PATH)🎯 Dynamic Calibration (
RouteProfile)5xspeed preset chip is dynamically calibrated to match the route's maximum speed ceiling (up to 40 m/s for rural/mountain, 20 m/s for urban).🚀 Flicker-Free Polyline Rendering
setPath()rather than re-instantiated on every frame, eliminating flickering and GPU thrashing.🌐 Decoupled Altitude Modes & Info Dialog
RELATIVE_TO_GROUND/RELATIVE_TO_MESHuse path offset directly as surface clearance rather than adding sea-level altitudes.📱 Responsive UI & Clean Code
SetTextI18n,ClickableViewAccessibility).Duration.milliseconds,String.toColorInt(),Enum.entries, and ComposeLocalWindowInfo.3-Way Parity Matrix
Verification
./gradlew :Maps3DSamples:ApiDemos:common:testDebugUnitTestkotlin-app,java-app,ComposeDemos:app) compile with 0 errors & 0 warnings.