Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 80 additions & 209 deletions .gemini/skills/android-maps3d-sdk/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.google.android.gms.maps3d.GoogleMap3D;
import com.google.android.gms.maps3d.Map3DView;
import com.google.android.gms.maps3d.OnMap3DViewReadyCallback;


public class MainActivity extends AppCompatActivity {

private Map3DView mapView;
private GoogleMap3D googleMap;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);

mapView = findViewById(R.id.map_view);
mapView.onCreate(savedInstanceState);

// Use DefaultLifecycleObserver to handle most lifecycle events automatically
getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) { mapView.onStart(); }
@Override
public void onResume(@NonNull LifecycleOwner owner) { mapView.onResume(); }
@Override
public void onPause(@NonNull LifecycleOwner owner) { mapView.onPause(); }
@Override
public void onStop(@NonNull LifecycleOwner owner) { mapView.onStop(); }
@Override
public void onDestroy(@NonNull LifecycleOwner owner) { mapView.onDestroy(); }
});

mapView.getMap3DViewAsync(new OnMap3DViewReadyCallback() {
@Override
public void onMap3DViewReady(@NonNull GoogleMap3D map) {
googleMap = map;

// Fails on cold starts because the viewport layout and binding matrix are not yet stable.
// The SDK requires a delay to bypass these readiness bugs before adding objects or setting camera.
// 1 second is usually sufficient.
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
setupMapElements();
}
}, 1000);


}

@Override
public void onError(@NonNull Exception e) {
Log.e("MainActivity", "Error loading map", e);
}
});
}

private void setupMapElements() {
if (googleMap == null) return;

Log.d("MainActivity", "Setting up map elements after delay");
// Add your map initialization logic here (markers, polylines, etc.)
}

@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map3d="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- Map3DView with common required and optional attributes.
Remove unneeded optional attributes as necessary. -->
<com.google.android.gms.maps3d.Map3DView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
map3d:mapId="your_map_id"
map3d:mode="hybrid"
map3d:centerLat="40.748392"
map3d:centerLng="-73.986060"
map3d:centerAlt="175"
map3d:heading="0"
map3d:tilt="45"
map3d:range="1000"
map3d:roll="0"
map3d:minAltitude="0"
map3d:maxAltitude="1000000"
map3d:minHeading="0"
map3d:maxHeading="360"
map3d:minTilt="0"
map3d:maxTilt="90" />

</FrameLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import com.google.android.gms.maps3d.GoogleMap3D;
import com.google.android.gms.maps3d.model.Marker;
import com.google.android.gms.maps3d.model.MarkerOptions;
import com.google.android.gms.maps3d.model.Polygon;
import com.google.android.gms.maps3d.model.PolygonOptions;
import com.google.android.gms.maps3d.model.Polyline;
import com.google.android.gms.maps3d.model.PolylineOptions;
import java.util.List;

/** Decorator wrapper around GoogleMap3D to track elements for automated cleanup. */
public class TrackedMap3D {

private final GoogleMap3D delegate;
private final List<Object> items;

public TrackedMap3D(GoogleMap3D delegate, List<Object> items) {
this.delegate = delegate;
this.items = items;
}

public Marker addMarker(MarkerOptions options) {
Marker marker = delegate.addMarker(options);
if (marker != null) items.add(marker);
return marker;
}

public Polyline addPolyline(PolylineOptions options) {
Polyline polyline = delegate.addPolyline(options);
if (polyline != null) items.add(polyline);
return polyline;
}

public Polygon addPolygon(PolygonOptions options) {
Polygon polygon = delegate.addPolygon(options);
if (polygon != null) items.add(polygon);
return polygon;
}

public void clearAll() {
for (Object item : items) {
if (item instanceof Marker) {
((Marker) item).remove();
} else if (item instanceof Polyline) {
((Polyline) item).remove();
} else if (item instanceof Polygon) {
((Polygon) item).remove();
}
}
items.clear();
}

// Forward other necessary methods to the delegate as needed
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 1. Declare the tracked map instance and items list
private TrackedMap3D trackedMap;
private final List<Object> mapItems = new ArrayList<>();

// 2. In onCreate, update the lifecycle observer to clean up on destroy
getLifecycle().addObserver(new DefaultLifecycleObserver() {
// ... other methods
@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
// Automatically clean up objects to avoid cruft
if (trackedMap != null) {
trackedMap.clearAll();
}
mapView.onDestroy();
}
});

// 3. In onMap3DViewReady, wrap the map
@Override
public void onMap3DViewReady(@NonNull GoogleMap3D map) {
trackedMap = new TrackedMap3D(map, mapItems);
// ...
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.maps3d.GoogleMap3D
import com.google.android.gms.maps3d.Map3DView
import com.google.android.gms.maps3d.OnMap3DViewReadyCallback
import com.google.android.gms.maps3d.model.camera
import com.google.android.gms.maps3d.model.latLngAltitude
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

private lateinit var mapView: Map3DView
private var googleMap: GoogleMap3D? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContentView(R.layout.activity_main)

mapView = findViewById(R.id.map_view)
mapView.onCreate(savedInstanceState)

// Use DefaultLifecycleObserver to handle most lifecycle events automatically
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { mapView.onStart() }
override fun onResume(owner: LifecycleOwner) { mapView.onResume() }
override fun onPause(owner: LifecycleOwner) { mapView.onPause() }
override fun onStop(owner: LifecycleOwner) { mapView.onStop() }
override fun onDestroy(owner: LifecycleOwner) { mapView.onDestroy() }
})

mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
override fun onMap3DViewReady(map: GoogleMap3D) {
googleMap = map

// Fails on cold starts because the viewport layout and binding matrix are not yet stable.
// Use a timer-based delay workaround.
lifecycleScope.launch {
// Wait for the viewport to fully inflate and bindings to stabilize.
// Samples use a delay to bypass readiness bugs (1 second recommended).
delay(1000)
setupMapElements()
}
}

override fun onError(e: Exception) {
Log.e("MainActivity", "Error loading map", e)
}
})
}

private fun setupMapElements() {
val map = googleMap ?: return

// Example: Set initial camera position
val initialCamera = camera {
center = latLngAltitude {
latitude = 40.0150
longitude = -105.2705
altitude = 5000.0
}
heading = 0.0
tilt = 45.0
roll = 0.0
range = 10000.0
}
map.setCamera(initialCamera)

// Add more initialization logic here (markers, polylines, etc.)
}

// onLowMemory and onSaveInstanceState still need to be forwarded manually
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map3d="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- Map3DView with common required and optional attributes.
Remove unneeded optional attributes as necessary. -->
<com.google.android.gms.maps3d.Map3DView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
map3d:mapId="your_map_id"
map3d:mode="hybrid"
map3d:centerLat="40.748392"
map3d:centerLng="-73.986060"
map3d:centerAlt="175"
map3d:heading="0"
map3d:tilt="45"
map3d:range="1000"
map3d:roll="0"
map3d:minAltitude="0"
map3d:maxAltitude="1000000"
map3d:minHeading="0"
map3d:maxHeading="360"
map3d:minTilt="0"
map3d:maxTilt="90" />

</FrameLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import com.google.android.gms.maps3d.GoogleMap3D
import com.google.android.gms.maps3d.model.Marker
import com.google.android.gms.maps3d.model.MarkerOptions
import com.google.android.gms.maps3d.model.Polygon
import com.google.android.gms.maps3d.model.PolygonOptions
import com.google.android.gms.maps3d.model.Polyline
import com.google.android.gms.maps3d.model.PolylineOptions

class TrackedMap3D(
val delegate: GoogleMap3D,
private val items: MutableList<Any> = mutableListOf()
) {
fun addMarker(options: MarkerOptions): Marker? {
val marker = delegate.addMarker(options)
if (marker != null) items.add(marker)
return marker
}

fun addPolyline(options: PolylineOptions): Polyline? {
val polyline = delegate.addPolyline(options)
if (polyline != null) items.add(polyline)
return polyline
}

fun addPolygon(options: PolygonOptions): Polygon? {
val polygon = delegate.addPolygon(options)
if (polygon != null) items.add(polygon)
return polygon
}

fun clearAll() {
items.forEach { item ->
when (item) {
is Marker -> item.remove()
is Polyline -> item.remove()
is Polygon -> item.remove()
}
}
items.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 1. Declare the tracked map instance
private var trackedMap: TrackedMap3D? = null

// 2. In onCreate, update the lifecycle observer to clean up on destroy
lifecycle.addObserver(object : DefaultLifecycleObserver {
// ... other methods
override fun onDestroy(owner: LifecycleOwner) {
// Automatically clean up objects to avoid cruft
trackedMap?.clearAll()
mapView.onDestroy()
}
})

// 3. In onMap3DViewReady, wrap the map
override fun onMap3DViewReady(map: GoogleMap3D) {
trackedMap = TrackedMap3D(map)
// ...
}
Loading
Loading