From ea62a6a51354c938096f2fe0581cbb15708040b5 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:32:32 -0500 Subject: [PATCH 1/2] fix: render legacy bridge point markers visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GeoJsonLayer and KmlLayer bridges built the renderer PointStyle with broken color values: GeoJsonLayer hardcoded color = 0, and KmlLayer passed KmlStyle.mMarkerColor — a hue in 0..360 — as if it were ARGB. In both cases the color's alpha channel was 0, and MapViewRenderer derives the marker's alpha from that channel, so every point feature added through the deprecated KML/GeoJSON layer classes rendered fully transparent. GeoJsonLayer now encodes the legacy point style's alpha into the color (hue 0 keeps the default marker look), and KmlLayer converts the marker hue to an opaque ARGB color via Color.HSVToColor, falling back to opaque black when no style is present. --- .../maps/android/data/geojson/GeoJsonLayer.kt | 6 +- .../google/maps/android/data/kml/KmlLayer.kt | 8 +- .../GeoJsonLayerMarkerVisibilityTest.kt | 79 ++++++++++++++++++ .../data/kml/KmlLayerMarkerVisibilityTest.kt | 82 +++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt create mode 100644 data/src/test/java/com/google/maps/android/data/kml/KmlLayerMarkerVisibilityTest.kt diff --git a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt index 07c2c4442..e589ed926 100644 --- a/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt +++ b/data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt @@ -16,6 +16,7 @@ package com.google.maps.android.data.geojson import android.content.Context +import android.graphics.Color import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds @@ -258,7 +259,10 @@ public class GeoJsonLayer : Layer { is com.google.maps.android.data.renderer.model.PointGeometry -> { val pointStyle = feature.pointStyle ?: mDefaultPointStyle com.google.maps.android.data.renderer.model.PointStyle( - color = 0, + // The renderer derives the marker's alpha from the color's alpha channel, so encode the + // legacy style's alpha into an otherwise-black color (hue 0 keeps the default marker look). + // A transparent color here (e.g. 0) would render the marker invisible. + color = Color.argb((pointStyle.getAlpha() * 255).toInt().coerceIn(0, 255), 0, 0, 0), anchorU = pointStyle.getAnchorU(), anchorV = pointStyle.getAnchorV(), heading = pointStyle.getRotation(), diff --git a/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt b/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt index 9cf5787b4..f6bcf221c 100644 --- a/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt +++ b/data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt @@ -16,6 +16,7 @@ package com.google.maps.android.data.kml import android.content.Context +import android.graphics.Color import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds @@ -387,7 +388,12 @@ public class KmlLayer : Layer { when (modelGeometry) { is com.google.maps.android.data.renderer.model.PointGeometry -> { com.google.maps.android.data.renderer.model.PointStyle( - color = inline?.mMarkerColor?.toInt() ?: 0, + // mMarkerColor is a hue (0..360), not an ARGB color — convert it before handing it to the + // renderer, which derives the marker's hue and alpha from an ARGB value. Passing the raw + // hue (or 0) made the alpha channel 0 and rendered every KML point marker invisible. + color = + inline?.mMarkerColor?.let { hue -> Color.HSVToColor(floatArrayOf(hue, 1f, 1f)) } + ?: Color.BLACK, iconUrl = inline?.getIconUrl(), ) } diff --git a/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt new file mode 100644 index 000000000..7cec12a34 --- /dev/null +++ b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.data.geojson + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkStatic +import org.json.JSONObject +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +/** + * Regression test for GeoJSON point markers rendered through the legacy [GeoJsonLayer] bridge: + * the bridge must not hand the renderer a fully transparent point color, which would make every + * point marker invisible (the renderer derives marker alpha from the style color's alpha channel). + */ +@RunWith(RobolectricTestRunner::class) +class GeoJsonLayerMarkerVisibilityTest { + @Before + fun setUp() { + mockkStatic(BitmapDescriptorFactory::class) + every { BitmapDescriptorFactory.defaultMarker(any()) } returns mockk() + } + + @After + fun tearDown() { + unmockkStatic(BitmapDescriptorFactory::class) + } + + @Test + fun pointFeature_isRenderedFullyOpaque() { + val mockMap = mockk(relaxed = true) + val mockMarker = mockk(relaxed = true) + val optionsSlot = slot() + every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker + + val geoJson = + """ + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "name": "A point" }, + "geometry": { "type": "Point", "coordinates": [-111.620, 41.942] } + } + ] + } + """.trimIndent() + + val layer = GeoJsonLayer(mockMap, JSONObject(geoJson)) + layer.addLayerToMap() + + assertEquals(1.0f, optionsSlot.captured.alpha, 0.001f) + } +} diff --git a/data/src/test/java/com/google/maps/android/data/kml/KmlLayerMarkerVisibilityTest.kt b/data/src/test/java/com/google/maps/android/data/kml/KmlLayerMarkerVisibilityTest.kt new file mode 100644 index 000000000..ae573a85a --- /dev/null +++ b/data/src/test/java/com/google/maps/android/data/kml/KmlLayerMarkerVisibilityTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.maps.android.data.kml + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkStatic +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +/** + * Regression test for KML point markers rendered through the legacy [KmlLayer] bridge: the bridge + * must hand the renderer an ARGB color with a non-zero alpha channel. Passing the raw marker hue + * (or 0) as if it were ARGB made the derived marker alpha 0, so every KML point was invisible. + */ +@RunWith(RobolectricTestRunner::class) +class KmlLayerMarkerVisibilityTest { + @Before + fun setUp() { + mockkStatic(BitmapDescriptorFactory::class) + every { BitmapDescriptorFactory.defaultMarker(any()) } returns mockk() + } + + @After + fun tearDown() { + unmockkStatic(BitmapDescriptorFactory::class) + } + + @Test + fun pointPlacemark_isRenderedFullyOpaque() { + val mockMap = mockk(relaxed = true) + val mockMarker = mockk(relaxed = true) + val optionsSlot = slot() + every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker + + val kml = + """ + + + + + A point + + -111.620,41.942,0 + + + + + """.trimIndent() + + val context = ApplicationProvider.getApplicationContext() + val layer = KmlLayer(mockMap, kml.byteInputStream(), context) + layer.addLayerToMap() + + assertEquals(1.0f, optionsSlot.captured.alpha, 0.001f) + } +} From 113048ecfb997b20f8171bbc60192fccb012d313 Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:52:38 -0600 Subject: [PATCH 2/2] test(data): add custom alpha test and demo support for KML/GeoJSON point visibility (#1726) - Add pointFeature_withCustomAlpha_isRenderedWithMatchingAlpha to GeoJsonLayerMarkerVisibilityTest to ensure custom alpha styling is properly propagated to the underlying MarkerOptions. - Add Point features to south_london_square_geojson.json and south_london_square_kml.kml and update MultiLayerDemoActivity so the demo app visually demonstrates KML and GeoJSON point marker rendering on-device. --- .../GeoJsonLayerMarkerVisibilityTest.kt | 28 +++++++++++++++++++ .../utils/demo/MultiLayerDemoActivity.java | 6 ++-- .../res/raw/south_london_square_geojson.json | 13 +++++++++ .../main/res/raw/south_london_square_kml.kml | 6 ++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt index 7cec12a34..0ea8e0a82 100644 --- a/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt +++ b/data/src/test/java/com/google/maps/android/data/geojson/GeoJsonLayerMarkerVisibilityTest.kt @@ -76,4 +76,32 @@ class GeoJsonLayerMarkerVisibilityTest { assertEquals(1.0f, optionsSlot.captured.alpha, 0.001f) } + + @Test + fun pointFeature_withCustomAlpha_isRenderedWithMatchingAlpha() { + val mockMap = mockk(relaxed = true) + val mockMarker = mockk(relaxed = true) + val optionsSlot = slot() + every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker + + val geoJson = + """ + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "name": "A point" }, + "geometry": { "type": "Point", "coordinates": [-111.620, 41.942] } + } + ] + } + """.trimIndent() + + val layer = GeoJsonLayer(mockMap, JSONObject(geoJson)) + layer.features.first().pointStyle = GeoJsonPointStyle().apply { setAlpha(0.5f) } + layer.addLayerToMap() + + assertEquals(0.5f, optionsSlot.captured.alpha, 0.01f) + } } diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MultiLayerDemoActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/MultiLayerDemoActivity.java index 3ab1e4bcd..a0e8747c6 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/MultiLayerDemoActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/MultiLayerDemoActivity.java @@ -113,7 +113,7 @@ protected void startDemo(boolean isRestore) { feature -> Toast.makeText( MultiLayerDemoActivity.this, - "GeoJSON polygon clicked: " + feature.getProperty("title"), + "GeoJSON feature clicked: " + feature.getProperty("title"), Toast.LENGTH_SHORT) .show()); } catch (IOException e) { @@ -144,7 +144,7 @@ protected void startDemo(boolean isRestore) { Toast.LENGTH_SHORT) .show()); - // KML Polygon + // KML Polygon & Point KmlLayer kmlPolygonLayer = new KmlLayer( getMap(), @@ -160,7 +160,7 @@ protected void startDemo(boolean isRestore) { feature -> Toast.makeText( MultiLayerDemoActivity.this, - "KML polygon clicked: " + feature.getProperty("name"), + "KML feature clicked: " + feature.getProperty("name"), Toast.LENGTH_SHORT) .show()); } catch (XmlPullParserException e) { diff --git a/demo/src/main/res/raw/south_london_square_geojson.json b/demo/src/main/res/raw/south_london_square_geojson.json index 021e97890..c79cb1873 100644 --- a/demo/src/main/res/raw/south_london_square_geojson.json +++ b/demo/src/main/res/raw/south_london_square_geojson.json @@ -33,6 +33,19 @@ ] ] } + }, + { + "type": "Feature", + "properties": { + "title": "South London Point GeoJSON" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -0.14, + 51.42 + ] + } } ] } \ No newline at end of file diff --git a/demo/src/main/res/raw/south_london_square_kml.kml b/demo/src/main/res/raw/south_london_square_kml.kml index 7df8d4964..59a112052 100644 --- a/demo/src/main/res/raw/south_london_square_kml.kml +++ b/demo/src/main/res/raw/south_london_square_kml.kml @@ -13,5 +13,11 @@ + + South London Point KML + + -0.12,51.24,0 + +