I migrated a google map html interop from an older...
# compose-web
r
I migrated a google map html interop from an older
HTMLView
fom here to
WebElementView
with 1.9.0-rc-01. However, now my markers aren't showing on the correct position when I click on a map. It is "kind of close", but not quite. When panning the map, the marker is pinned to the same geolocation, but when zooming, it moves up and down. (Code in thread - so even when markerPos does not change, it appears to float up and down as I zoom in and out) Any ideas why?
Screen Recording 2025-09-09 at 13.58.51.mov
my implementation:
Copy code
package googlemaps

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.WebElementView
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
import ui.common.maps.ClickedLatLng
import ui.common.maps.MapPos
import ui.common.maps.Waypoints

@Composable
fun WebGoogleMapView(
    lat: Double,
    lng: Double,
    markerPos: MapPos?,
    waypoints: Waypoints?,
    zoomLevel: Double = 10.0,
    interactionEnabled: Boolean, // TODO: P2: handle interactionEnabled
    mapType: String = "roadmap", // "satellite", "hybrid", "terrain"
    onClick: (ClickedLatLng) -> Unit,
    modifier: Modifier = Modifier,
) {
    val mapRef = remember { mutableStateOf<GMap?>(null) }
    val markers = remember {
        mutableListOf<GAdvancedMarkerElement>()
    }
    val waypointMarkers = remember { mutableListOf<GAdvancedMarkerElement>() }

    val polylineRef = remember { mutableStateOf<GPolyline?>(null) }

    LaunchedEffect(lat, lng) {
        mapRef.value?.setCenter(GLatLng(lat, lng))
    }

    WebElementView(
        modifier = modifier,
        factory = {
            (document.createElement("div")
                    as HTMLElement)
                .apply { id = "map" }
        },
        update = { element ->
            val map = mapRef.value
            if (map == null) {
                mapRef.value =
                    GMap(element, createGMapOptions(lat, lng, zoomLevel, mapType)).apply {
                        addListener("click", handler = { event ->
                            onClick(
                                ClickedLatLng(
                                    lat = event.latLng.lat(),
                                    long = event.latLng.lng()
                                )
                            )
                        })
                    }
            } else {
                for (marker in markers) {
                    marker.setMap(null)
                }
                markers.clear()

                if (markerPos != null) {
                    val newMarker = GAdvancedMarkerElement(
                        createMarkerOptions(markerPos.lat, markerPos.long)
                    ).apply {
                        setMap(map)
                    }
                    markers.add(newMarker)
                }

                waypoints?.start?.let {
                    val pos = it.pos
                    val text = it.text
                    val textMarker =
                        GAdvancedMarkerElement(createTextMarkerOptions(pos.lat, pos.long, text))
                    textMarker.setMap(map)
                }
                waypoints?.end?.let {
                    val pos = it.pos
                    val text = it.text
                    val textMarker =
                        GAdvancedMarkerElement(createTextMarkerOptions(pos.lat, pos.long, text))
                    textMarker.setMap(map)
                }

                for (wm in waypointMarkers) {
                    wm.setMap(null)
                }
                waypointMarkers.clear()

                val positions = waypoints?.positions.orEmpty()
                if (positions.isNotEmpty()) {
                    val adaptedPositions = listOfNotNull(
                        positions.firstOrNull(),
                        positions.lastOrNull()
                    ).distinct()

                    adaptedPositions.forEach { pos ->
                        val wMarker = GAdvancedMarkerElement(
                            createMarkerOptions(pos.lat, pos.long)
                        ).apply {
                            setMap(map)
                        }
                        waypointMarkers.add(wMarker)
                    }
                }

                val path = positions.map { latLngLiteral(it.lat, it.long) }.toTypedArray()
                val existingPolyline = polylineRef.value
                if (existingPolyline == null) {
                    polylineRef.value =
                        GPolyline(createPolylineOptions(path.toList().toJsArray())).apply {
                            setMap(map)
                        }
                } else {
                    // Just update the path
                    existingPolyline.setPath(path.toJsArray())
                }
            }
        },
    )
}
Utils:
Copy code
package googlemaps

fun latLngLiteral(lat: Double, lng: Double): JsAny =
    js("({ lat: lat, lng: lng })")

fun createMarkerOptions(lat: Double, lng: Double): JsAny = js("""
            ({
                position: { lat: lat, lng: lng }
            })
        """)

fun createPolylineOptions(path: JsArray<JsAny>): JsAny = js("""
    ({
        path: path,
        geodesic: true,
        strokeColor: '#FF0000',
        strokeOpacity: 1.0,
        strokeWeight: 8
    })
""")

fun createGMapOptions(lat: Double, lng: Double, zoomLevel: Double, mapType: String): JsAny = js("""
    ({
        center: { lat: lat, lng: lng },
        zoom: zoomLevel,
        mapTypeId: mapType,
        mapId: "9d6d8ac542bcf9c1"
    })
""")

fun createTextMarkerOptions(lat: Double, lng: Double, text: String): JsAny = js("""
    (function() {
        var div = document.createElement('div');
        div.style.transform = 'translate(-50%, -50%)';
        div.style.padding = '8px';
        div.style.backgroundColor = 'white';
        div.style.border = '1px solid black';
        div.style.whiteSpace = 'nowrap';
        div.innerText = text;

        return {
            position: { lat: lat, lng: lng },
            content: div
        };
    })()
""")