Skip to content

feat(camera): add 4-way AltitudeMode camera and marker controls across Kotlin, Java, and Compose - #86

Open
LoyalAbbas wants to merge 2 commits into
mainfrom
feat/camera_control_with_altitudemode
Open

LoyalAbbas wants to merge 2 commits into
mainfrom
feat/camera_control_with_altitudemode

Conversation

@LoyalAbbas

Copy link
Copy Markdown
Collaborator

Summary

Enhances CameraControlsActivity across Kotlin Views, Java Views, and Jetpack Compose to demonstrate all four AltitudeMode options (RELATIVE_TO_GROUND, RELATIVE_TO_MESH, CLAMP_TO_GROUND, and ABSOLUTE) at both the Empire State Building and Mount Rainier summit.

Key Changes

  • Unified AltitudeMode Pipeline: Consolidated camera and marker configuration into shared buildTargetCamera and buildTargetBeaconOptions / buildTargetBeaconConfig builders in DataModel across all three apps.
  • 4-Way AltitudeMode Support: Added interactive filter chips for Relative to Ground (+100m DTM), Relative to Mesh (+360m DSM), Clamp to Ground (0m DTM), and Absolute (212m MSL at ESB / 4,392m MSL at Mt. Rainier).
  • High-Altitude Mountain Target: Added a Fly to Mt. Rainier (Relative Alt) action that flies to the 4,392m peak and chains a 360° flyCameraAround orbit on arrival.
  • 3D Mesh & Marker Synchronization: Added extruded reference beacons with dynamic refresh on flight completion and onMapSteady so RELATIVE_TO_MESH accurately samples loaded 3D building/terrain tiles.
  • Camera Utilities: Updated toValidCamera() and Camera.copy() in common and maps3d-compose to preserve valid altitudeMode values.
  • Startup & Telemetry: Configured CameraControlsActivity to launch directly at the Empire State Building and display the active target and AltitudeMode in the camera telemetry panel.

AltitudeMode Comparison

Mode Reference Surface ESB Target / Beacon Mt. Rainier Target / Beacon
CLAMP_TO_GROUND Bare earth (DTM) 0m (street base) 0m (summit snowpack)
RELATIVE_TO_GROUND Bare earth (DTM) +100m (lower tower) +100m above terrain
ABSOLUTE Mean sea level (EGM96) 212m MSL (mid-tower) 4,392m MSL (summit)
RELATIVE_TO_MESH 3D surface + buildings (DSM) +360m (above spire) +360m above 3D mesh

Testing & Verification

  • Added unit tests (DataModelTest) across Kotlin Views, Java Views, and Jetpack Compose verifying camera framing, marker extrusion, and AltitudeMode preservation.
  • Ran ./gradlew spotlessApply across all modified modules.
  • Verified builds and unit tests pass:
    • ./gradlew :Maps3DSamples:ApiDemos:kotlin-app:assembleDebug :Maps3DSamples:ApiDemos:kotlin-app:testDebugUnitTest
    • ./gradlew :Maps3DSamples:ApiDemos:java-app:assembleDebug :Maps3DSamples:ApiDemos:java-app:testDebugUnitTest
    • ./gradlew :Maps3DSamples:ComposeDemos:app:assembleDebug :Maps3DSamples:ComposeDemos:app:testDebugUnitTest

@LoyalAbbas
LoyalAbbas requested review from dkhawk and kikoso and removed request for dkhawk October 16, 2026 05:36

@kikoso kikoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @LoyalAbbas ! This is a nice addition, and the comparison table in the description is very useful. A few comments inline. It is also quite a big PR (around 2.3k lines across the three apps), so would it make sense to move the maps3d-compose change to its own PR? Thanks!

<string name="fly_around">Fly around center</string>
<!-- Fly to the Empire State Building -->
<string name="fly_to">Fly to ESB 🏢</string>
<string name="fly_to_mountain">Fly to Mt. Rainier (Relative Alt)</string>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the button uses whatever mode is selected in the chips, so "(Relative Alt)" is misleading. What about matching the ESB button?

Suggested change
<string name="fly_to_mountain">Fly to Mt. Rainier (Relative Alt)</string>
<string name="fly_to_mountain">Fly to Mt. Rainier ⛰️</string>

tilt = source.tilt.toTilt()
roll = source.roll.toRoll()
range = source.range.toRange()
if (source.altitudeMode == AltitudeMode.ABSOLUTE ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We repeat this same three-mode check four times (here, in copy, and in the two maps3d-compose copies). Could we extract a small isValidCameraAltitudeMode() helper? It would also be a good place to document why CLAMP_TO_GROUND is left out for cameras.

if (oldConfig != config) {
// Config changed, update by adding again with same ID!
// Config changed, remove previous marker and recreate with updated options
marker.remove()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to understand it: the previous comment said that adding again with the same ID updates the marker, so why do we need the remove() now? Since the beacon key includes the mode and the refresh token, this sample never reaches this branch, so I would move this library change to its own PR (with a test) instead of hiding it inside a feat(camera).

RELATIVE_CLEARANCE_METERS to AltitudeMode.RELATIVE_TO_GROUND
}
return markerOptions {
id = "${markerIdPrefix}_${mode}_$refreshToken"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need the refreshToken at all. Recreating the marker with a new ID after every flight and on onMapSteady reads like a workaround for RELATIVE_TO_MESH not resampling once the tiles load. If that's the case, could we add a comment explaining it (and maybe raise it with the SDK team)? Otherwise developers will copy this pattern thinking is required.

altitudeMode = AltitudeMode.RELATIVE_TO_MESH
}

AltitudeMode.CLAMP_TO_GROUND -> camera {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Camera does not accept CLAMP_TO_GROUND, this branch ends up as a RELATIVE_TO_GROUND camera at 0m, while the telemetry panel still shows Mode: CLAMP_TO_GROUND. Should we show the mode the camera actually uses, or make the clamp chip affect only the beacon? Otherwise it may confuse people learning the API.

activeTargetResId = targetResId
val isMountain = (targetResId == R.string.camera_target_mt_rainier)

// Clear NYC bounds restriction if active so the camera can travel to Washington State

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this duplicates the logic of the toggle_restriction click listener. What about extracting a small setCameraRestricted(enabled: Boolean) and using it in both places?

class DataModelTest {

@Test
fun mtRainierCamera_relativeToGround_usesUnifiedClearanceAltitude() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these tests just assert that the builder returns the constants we passed in, so they will break when we tweak the framing values rather than catch real bugs. My two cents: keep the ones that check the mode mapping (e.g. CLAMP_TO_GROUND falling back to RELATIVE_TO_GROUND, isExtruded) and drop the rest. That would also trim a few hundred lines across the three apps.

- Update Mt. Rainier button label to "Fly to Mt. Rainier ⛰️" and remove unused clamp string resource
- Extract isValidCameraAltitudeMode() helper in common and maps3d-compose Utilities
- Revert Map3DState.kt marker.remove() change in maps3d-compose
- Refresh beacon marker only on onMapSteady when RELATIVE_TO_MESH is active
- Show actual camera altitude mode (RELATIVE_TO_GROUND) in telemetry when CLAMP_TO_GROUND is selected
- Extract setCameraRestricted(enabled) helper in Kotlin and Java CameraControlsActivity
- Trim redundant constant assertions in DataModelTest across Kotlin, Java, and Compose
@LoyalAbbas

Copy link
Copy Markdown
Collaborator Author

Hi @LoyalAbbas ! This is a nice addition, and the comparison table in the description is very useful. A few comments inline. It is also quite a big PR (around 2.3k lines across the three apps), so would it make sense to move the maps3d-compose change to its own PR? Thanks!

Thanks for the review @kikoso ! I reverted the Map3DState.kt change completely and trimmed ~350 lines of redundant tests to shrink the PR. The only remaining maps3d-compose change is 23 lines in Utilities.kt (toValidCamera() / Camera.copy()) so GoogleMap3D(camera = ...) preserves altitudeMode for the Compose sample. Happy to split those 23 lines into a separate PR first if you still prefer!

@LoyalAbbas
LoyalAbbas requested a review from kikoso October 19, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants