Skip to content

Commit fe802a4

Browse files
jamesarichdkhawk
andauthored
fix: render legacy bridge point markers visible (#1726)
* fix: render legacy bridge point markers visible 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. * 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. --------- Co-authored-by: Dale Hawkins <107309+dkhawk@users.noreply.github.com>
1 parent 7aa9b3d commit fe802a4

7 files changed

Lines changed: 223 additions & 5 deletions

File tree

data/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.maps.android.data.geojson
1717

1818
import android.content.Context
19+
import android.graphics.Color
1920
import com.google.android.gms.maps.GoogleMap
2021
import com.google.android.gms.maps.model.LatLng
2122
import com.google.android.gms.maps.model.LatLngBounds
@@ -258,7 +259,10 @@ public class GeoJsonLayer : Layer {
258259
is com.google.maps.android.data.renderer.model.PointGeometry -> {
259260
val pointStyle = feature.pointStyle ?: mDefaultPointStyle
260261
com.google.maps.android.data.renderer.model.PointStyle(
261-
color = 0,
262+
// The renderer derives the marker's alpha from the color's alpha channel, so encode the
263+
// legacy style's alpha into an otherwise-black color (hue 0 keeps the default marker look).
264+
// A transparent color here (e.g. 0) would render the marker invisible.
265+
color = Color.argb((pointStyle.getAlpha() * 255).toInt().coerceIn(0, 255), 0, 0, 0),
262266
anchorU = pointStyle.getAnchorU(),
263267
anchorV = pointStyle.getAnchorV(),
264268
heading = pointStyle.getRotation(),

data/src/main/java/com/google/maps/android/data/kml/KmlLayer.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.maps.android.data.kml
1717

1818
import android.content.Context
19+
import android.graphics.Color
1920
import com.google.android.gms.maps.GoogleMap
2021
import com.google.android.gms.maps.model.LatLng
2122
import com.google.android.gms.maps.model.LatLngBounds
@@ -387,7 +388,12 @@ public class KmlLayer : Layer {
387388
when (modelGeometry) {
388389
is com.google.maps.android.data.renderer.model.PointGeometry -> {
389390
com.google.maps.android.data.renderer.model.PointStyle(
390-
color = inline?.mMarkerColor?.toInt() ?: 0,
391+
// mMarkerColor is a hue (0..360), not an ARGB color — convert it before handing it to the
392+
// renderer, which derives the marker's hue and alpha from an ARGB value. Passing the raw
393+
// hue (or 0) made the alpha channel 0 and rendered every KML point marker invisible.
394+
color =
395+
inline?.mMarkerColor?.let { hue -> Color.HSVToColor(floatArrayOf(hue, 1f, 1f)) }
396+
?: Color.BLACK,
391397
iconUrl = inline?.getIconUrl(),
392398
)
393399
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
package com.google.maps.android.data.geojson
17+
18+
import com.google.android.gms.maps.GoogleMap
19+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
20+
import com.google.android.gms.maps.model.Marker
21+
import com.google.android.gms.maps.model.MarkerOptions
22+
import io.mockk.every
23+
import io.mockk.mockk
24+
import io.mockk.mockkStatic
25+
import io.mockk.slot
26+
import io.mockk.unmockkStatic
27+
import org.json.JSONObject
28+
import org.junit.After
29+
import org.junit.Assert.assertEquals
30+
import org.junit.Before
31+
import org.junit.Test
32+
import org.junit.runner.RunWith
33+
import org.robolectric.RobolectricTestRunner
34+
35+
/**
36+
* Regression test for GeoJSON point markers rendered through the legacy [GeoJsonLayer] bridge:
37+
* the bridge must not hand the renderer a fully transparent point color, which would make every
38+
* point marker invisible (the renderer derives marker alpha from the style color's alpha channel).
39+
*/
40+
@RunWith(RobolectricTestRunner::class)
41+
class GeoJsonLayerMarkerVisibilityTest {
42+
@Before
43+
fun setUp() {
44+
mockkStatic(BitmapDescriptorFactory::class)
45+
every { BitmapDescriptorFactory.defaultMarker(any()) } returns mockk()
46+
}
47+
48+
@After
49+
fun tearDown() {
50+
unmockkStatic(BitmapDescriptorFactory::class)
51+
}
52+
53+
@Test
54+
fun pointFeature_isRenderedFullyOpaque() {
55+
val mockMap = mockk<GoogleMap>(relaxed = true)
56+
val mockMarker = mockk<Marker>(relaxed = true)
57+
val optionsSlot = slot<MarkerOptions>()
58+
every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker
59+
60+
val geoJson =
61+
"""
62+
{
63+
"type": "FeatureCollection",
64+
"features": [
65+
{
66+
"type": "Feature",
67+
"properties": { "name": "A point" },
68+
"geometry": { "type": "Point", "coordinates": [-111.620, 41.942] }
69+
}
70+
]
71+
}
72+
""".trimIndent()
73+
74+
val layer = GeoJsonLayer(mockMap, JSONObject(geoJson))
75+
layer.addLayerToMap()
76+
77+
assertEquals(1.0f, optionsSlot.captured.alpha, 0.001f)
78+
}
79+
80+
@Test
81+
fun pointFeature_withCustomAlpha_isRenderedWithMatchingAlpha() {
82+
val mockMap = mockk<GoogleMap>(relaxed = true)
83+
val mockMarker = mockk<Marker>(relaxed = true)
84+
val optionsSlot = slot<MarkerOptions>()
85+
every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker
86+
87+
val geoJson =
88+
"""
89+
{
90+
"type": "FeatureCollection",
91+
"features": [
92+
{
93+
"type": "Feature",
94+
"properties": { "name": "A point" },
95+
"geometry": { "type": "Point", "coordinates": [-111.620, 41.942] }
96+
}
97+
]
98+
}
99+
""".trimIndent()
100+
101+
val layer = GeoJsonLayer(mockMap, JSONObject(geoJson))
102+
layer.features.first().pointStyle = GeoJsonPointStyle().apply { setAlpha(0.5f) }
103+
layer.addLayerToMap()
104+
105+
assertEquals(0.5f, optionsSlot.captured.alpha, 0.01f)
106+
}
107+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
package com.google.maps.android.data.kml
17+
18+
import android.content.Context
19+
import androidx.test.core.app.ApplicationProvider
20+
import com.google.android.gms.maps.GoogleMap
21+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
22+
import com.google.android.gms.maps.model.Marker
23+
import com.google.android.gms.maps.model.MarkerOptions
24+
import io.mockk.every
25+
import io.mockk.mockk
26+
import io.mockk.mockkStatic
27+
import io.mockk.slot
28+
import io.mockk.unmockkStatic
29+
import org.junit.After
30+
import org.junit.Assert.assertEquals
31+
import org.junit.Before
32+
import org.junit.Test
33+
import org.junit.runner.RunWith
34+
import org.robolectric.RobolectricTestRunner
35+
36+
/**
37+
* Regression test for KML point markers rendered through the legacy [KmlLayer] bridge: the bridge
38+
* must hand the renderer an ARGB color with a non-zero alpha channel. Passing the raw marker hue
39+
* (or 0) as if it were ARGB made the derived marker alpha 0, so every KML point was invisible.
40+
*/
41+
@RunWith(RobolectricTestRunner::class)
42+
class KmlLayerMarkerVisibilityTest {
43+
@Before
44+
fun setUp() {
45+
mockkStatic(BitmapDescriptorFactory::class)
46+
every { BitmapDescriptorFactory.defaultMarker(any()) } returns mockk()
47+
}
48+
49+
@After
50+
fun tearDown() {
51+
unmockkStatic(BitmapDescriptorFactory::class)
52+
}
53+
54+
@Test
55+
fun pointPlacemark_isRenderedFullyOpaque() {
56+
val mockMap = mockk<GoogleMap>(relaxed = true)
57+
val mockMarker = mockk<Marker>(relaxed = true)
58+
val optionsSlot = slot<MarkerOptions>()
59+
every { mockMap.addMarker(capture(optionsSlot)) } returns mockMarker
60+
61+
val kml =
62+
"""
63+
<?xml version="1.0" encoding="UTF-8"?>
64+
<kml xmlns="http://www.opengis.net/kml/2.2">
65+
<Document>
66+
<Placemark>
67+
<name>A point</name>
68+
<Point>
69+
<coordinates>-111.620,41.942,0</coordinates>
70+
</Point>
71+
</Placemark>
72+
</Document>
73+
</kml>
74+
""".trimIndent()
75+
76+
val context = ApplicationProvider.getApplicationContext<Context>()
77+
val layer = KmlLayer(mockMap, kml.byteInputStream(), context)
78+
layer.addLayerToMap()
79+
80+
assertEquals(1.0f, optionsSlot.captured.alpha, 0.001f)
81+
}
82+
}

demo/src/main/java/com/google/maps/android/utils/demo/MultiLayerDemoActivity.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ protected void startDemo(boolean isRestore) {
113113
feature ->
114114
Toast.makeText(
115115
MultiLayerDemoActivity.this,
116-
"GeoJSON polygon clicked: " + feature.getProperty("title"),
116+
"GeoJSON feature clicked: " + feature.getProperty("title"),
117117
Toast.LENGTH_SHORT)
118118
.show());
119119
} catch (IOException e) {
@@ -144,7 +144,7 @@ protected void startDemo(boolean isRestore) {
144144
Toast.LENGTH_SHORT)
145145
.show());
146146

147-
// KML Polygon
147+
// KML Polygon & Point
148148
KmlLayer kmlPolygonLayer =
149149
new KmlLayer(
150150
getMap(),
@@ -160,7 +160,7 @@ protected void startDemo(boolean isRestore) {
160160
feature ->
161161
Toast.makeText(
162162
MultiLayerDemoActivity.this,
163-
"KML polygon clicked: " + feature.getProperty("name"),
163+
"KML feature clicked: " + feature.getProperty("name"),
164164
Toast.LENGTH_SHORT)
165165
.show());
166166
} catch (XmlPullParserException e) {

demo/src/main/res/raw/south_london_square_geojson.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@
3333
]
3434
]
3535
}
36+
},
37+
{
38+
"type": "Feature",
39+
"properties": {
40+
"title": "South London Point GeoJSON"
41+
},
42+
"geometry": {
43+
"type": "Point",
44+
"coordinates": [
45+
-0.14,
46+
51.42
47+
]
48+
}
3649
}
3750
]
3851
}

demo/src/main/res/raw/south_london_square_kml.kml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@
1313
</outerBoundaryIs>
1414
</Polygon>
1515
</Placemark>
16+
<Placemark>
17+
<name>South London Point KML</name>
18+
<Point>
19+
<coordinates>-0.12,51.24,0</coordinates>
20+
</Point>
21+
</Placemark>
1622
</Document>
1723
</kml>

0 commit comments

Comments
 (0)