Skip to content

Explore deduplicating multiple painters of the same resource  #696

@mtpdog

Description

@mtpdog

What happened?

Icons are not reused and are added to Maplibre style again for each new layer instance.
Here is an example.

When only one layer is added (MovingSymbolWithSelection("layer_1")) Maplibre shows 11 painters added (which is expected for 10 markers + 1 fallback icon):

 I  Adding painter __MAPLIBRE_COMPOSE_painter_0
 I  Adding painter __MAPLIBRE_COMPOSE_painter_1
 I  Adding painter __MAPLIBRE_COMPOSE_painter_2
 I  Adding painter __MAPLIBRE_COMPOSE_painter_3
 I  Adding painter __MAPLIBRE_COMPOSE_painter_4
 I  Adding painter __MAPLIBRE_COMPOSE_painter_5
 I  Adding painter __MAPLIBRE_COMPOSE_painter_6
 I  Adding painter __MAPLIBRE_COMPOSE_painter_7
 I  Adding painter __MAPLIBRE_COMPOSE_painter_8
 I  Adding painter __MAPLIBRE_COMPOSE_painter_9
 I  Adding painter __MAPLIBRE_COMPOSE_painter_10

However, when more instances of the same layer are added Maplibre adds painters for each layer as if they were new drawables:

fun MovingSymbols() {
    MovingSymbolWithSelection("layer_1")
    MovingSymbolWithSelection("layer_2")
    MovingSymbolWithSelection("layer_3")
}

Log:

 I  Adding painter __MAPLIBRE_COMPOSE_painter_0
 I  Adding painter __MAPLIBRE_COMPOSE_painter_1
 I  Adding painter __MAPLIBRE_COMPOSE_painter_2
 I  Adding painter __MAPLIBRE_COMPOSE_painter_3
 I  Adding painter __MAPLIBRE_COMPOSE_painter_4
 I  Adding painter __MAPLIBRE_COMPOSE_painter_5
 I  Adding painter __MAPLIBRE_COMPOSE_painter_6
 I  Adding painter __MAPLIBRE_COMPOSE_painter_7
 I  Adding painter __MAPLIBRE_COMPOSE_painter_8
 I  Adding painter __MAPLIBRE_COMPOSE_painter_9
 I  Adding painter __MAPLIBRE_COMPOSE_painter_10
 I  Adding painter __MAPLIBRE_COMPOSE_painter_11
 I  Adding painter __MAPLIBRE_COMPOSE_painter_12
 I  Adding painter __MAPLIBRE_COMPOSE_painter_13
 I  Adding painter __MAPLIBRE_COMPOSE_painter_14
 I  Adding painter __MAPLIBRE_COMPOSE_painter_15
 I  Adding painter __MAPLIBRE_COMPOSE_painter_16
 I  Adding painter __MAPLIBRE_COMPOSE_painter_17
 I  Adding painter __MAPLIBRE_COMPOSE_painter_18
 I  Adding painter __MAPLIBRE_COMPOSE_painter_19
 I  Adding painter __MAPLIBRE_COMPOSE_painter_20
 I  Adding painter __MAPLIBRE_COMPOSE_painter_21
 I  Adding painter __MAPLIBRE_COMPOSE_painter_22
 I  Adding painter __MAPLIBRE_COMPOSE_painter_23
 I  Adding painter __MAPLIBRE_COMPOSE_painter_24
 I  Adding painter __MAPLIBRE_COMPOSE_painter_25
 I  Adding painter __MAPLIBRE_COMPOSE_painter_26
 I  Adding painter __MAPLIBRE_COMPOSE_painter_27
 I  Adding painter __MAPLIBRE_COMPOSE_painter_28
 I  Adding painter __MAPLIBRE_COMPOSE_painter_29
 I  Adding painter __MAPLIBRE_COMPOSE_painter_30
 I  Adding painter __MAPLIBRE_COMPOSE_painter_31
 I  Adding painter __MAPLIBRE_COMPOSE_painter_32

In case of hundreds of different markers and several layers Maplibre quickly reaches its limit of around 7k-8k painters and then crashes.

Suggestion: Can we add all resources once and reuse them by hashCode() link like "Res.drawable.marker.hashCode()"? This worked well in native.

Affected Platforms

  • Android
  • iOS
  • Desktop (JVM)
  • Browser (JS)
  • Browser (Wasm)

Platform Version

Android 15

Library Version

0.11.1

Sample Code

@Composable
fun MovingSymbols() {
    MovingSymbolWithSelection("layer_1")
    //MovingSymbolWithSelection("layer_2")
    //MovingSymbolWithSelection("layer_3")
}

@Composable
fun MovingSymbolWithSelection(layerName: String) {
    var pointPosition by remember { mutableStateOf(Position(latitude = 48.8, longitude = 9.2))}
    var markerIcon by remember { mutableStateOf("marker")}
    val source by derivedStateOf {
        FeatureCollection( Feature(
            geometry = Point(pointPosition),
            properties = JsonObject(getIconGeoJson(markerIcon).toMap().mapValues { JsonPrimitive(it.value) }
            )
        ))
    }

    val markersList = listOf("marker1", "marker2", "marker3", "marker4", "marker5", "marker6", "marker7", "marker8","marker9", "marker10" )

    LaunchedEffect(Unit){
        while (true){
            pointPosition = Position(
                latitude = pointPosition.latitude + Random.nextDouble(-0.02, 0.02),
                longitude = pointPosition.longitude + Random.nextDouble(-0.02, 0.02),
            )
            markerIcon = markersList.random()
            delay(50)
        }
    }

    SymbolLayer(
        id = layerName,
        iconImage = switch(
            feature["iconId"].asString(),
            case(label = "marker1", output = image(painterResource(Res.drawable.marker1))),
            case(label = "marker2", output = image(painterResource(Res.drawable.marker2))),
            case(label = "marker3", output = image(painterResource(Res.drawable.marker3))),
            case(label = "marker4", output = image(painterResource(Res.drawable.marker4))),
            case(label = "marker5", output = image(painterResource(Res.drawable.marker5))),
            case(label = "marker6", output = image(painterResource(Res.drawable.marker6))),
            case(label = "marker7", output = image(painterResource(Res.drawable.marker7))),
            case(label = "marker8", output = image(painterResource(Res.drawable.marker8))),
            case(label = "marker9", output = image(painterResource(Res.drawable.marker9))),
            case(label = "marker10", output = image(painterResource(Res.drawable.marker10))),
            fallback = image(painterResource(Res.drawable.error_24px)),
        ),
        source = rememberGeoJsonSource(data = GeoJsonData.Features(source)),
    )
}

@Serializable
data class GeoJsonProperties(
    val iconId: String? = null
)

fun getIconGeoJson(iconId: String?): GeoJsonProperties {
    return GeoJsonProperties(
        iconId = iconId
    )
}

fun GeoJsonProperties.toMap(): Map<String, String?> = mapOf(
    "iconId" to iconId,
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    api ergonomicsImproves API usability without net-new functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions