Skip to content

Combine Multiple Data Sources

This tutorial shows how to load and display data from multiple GeoJSON sources simultaneously on your MapMetrics Android map — useful for layering different datasets like POIs, routes, and zones.

Prerequisites

Multiple Sources with Different Layer Types

Add points, lines, and polygons from separate sources:

kotlin
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.JsonObject
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.layers.CircleLayer
import org.maplibre.android.style.layers.FillLayer
import org.maplibre.android.style.layers.LineLayer
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.geojson.*

class MultiSourceActivity : AppCompatActivity() {

    private lateinit var mapView: MapView
    private lateinit var map: MapMetricsMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_map)

        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync { mapMetricsMap ->
            map = mapMetricsMap

            map.setStyle(
                Style.Builder().fromUri(
                    "https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
                )
            ) { style ->
                addZones(style)        // Polygons
                addRoutes(style)       // Lines
                addPointsOfInterest(style)  // Points
                setCamera()
            }
        }
    }

    private fun addZones(style: Style) {
        val zone1 = Feature.fromGeometry(
            Polygon.fromLngLats(listOf(listOf(
                Point.fromLngLat(2.32, 48.87),
                Point.fromLngLat(2.35, 48.87),
                Point.fromLngLat(2.35, 48.855),
                Point.fromLngLat(2.32, 48.855),
                Point.fromLngLat(2.32, 48.87),
            ))),
            JsonObject().apply { addProperty("name", "Zone A") }
        )

        val zone2 = Feature.fromGeometry(
            Polygon.fromLngLats(listOf(listOf(
                Point.fromLngLat(2.35, 48.855),
                Point.fromLngLat(2.38, 48.855),
                Point.fromLngLat(2.38, 48.84),
                Point.fromLngLat(2.35, 48.84),
                Point.fromLngLat(2.35, 48.855),
            ))),
            JsonObject().apply { addProperty("name", "Zone B") }
        )

        style.addSource(
            GeoJsonSource("zones-source",
                FeatureCollection.fromFeatures(listOf(zone1, zone2)))
        )

        // Fill layer for zones
        style.addLayer(
            FillLayer("zones-fill", "zones-source")
                .withProperties(
                    fillColor(Color.parseColor("#4285F4")),
                    fillOpacity(0.2f)
                )
        )

        // Outline layer for zones
        style.addLayer(
            LineLayer("zones-outline", "zones-source")
                .withProperties(
                    lineColor(Color.parseColor("#4285F4")),
                    lineWidth(2f)
                )
        )
    }

    private fun addRoutes(style: Style) {
        val route = Feature.fromGeometry(
            LineString.fromLngLats(listOf(
                Point.fromLngLat(2.3200, 48.8600),
                Point.fromLngLat(2.3350, 48.8580),
                Point.fromLngLat(2.3500, 48.8560),
                Point.fromLngLat(2.3600, 48.8500),
                Point.fromLngLat(2.3700, 48.8450),
            ))
        )

        style.addSource(
            GeoJsonSource("routes-source", FeatureCollection.fromFeature(route))
        )

        style.addLayer(
            LineLayer("routes-layer", "routes-source")
                .withProperties(
                    lineColor(Color.parseColor("#FF6B35")),
                    lineWidth(4f),
                    lineOpacity(0.8f)
                )
        )
    }

    private fun addPointsOfInterest(style: Style) {
        val pois = listOf(
            Triple(2.3376, 48.8606, "Louvre Museum"),
            Triple(2.3266, 48.8600, "Musée d'Orsay"),
            Triple(2.3499, 48.8530, "Notre-Dame"),
            Triple(2.3464, 48.8462, "Luxembourg Gardens"),
            Triple(2.3532, 48.8619, "Centre Pompidou"),
        )

        val features = pois.map { (lng, lat, name) ->
            Feature.fromGeometry(
                Point.fromLngLat(lng, lat),
                JsonObject().apply { addProperty("name", name) }
            )
        }

        style.addSource(
            GeoJsonSource("pois-source", FeatureCollection.fromFeatures(features))
        )

        style.addLayer(
            CircleLayer("pois-layer", "pois-source")
                .withProperties(
                    circleRadius(8f),
                    circleColor(Color.parseColor("#34A853")),
                    circleStrokeColor(Color.WHITE),
                    circleStrokeWidth(2f)
                )
        )
    }

    private fun setCamera() {
        map.cameraPosition = CameraPosition.Builder()
            .target(LatLng(48.855, 2.350))
            .zoom(13.0)
            .build()
    }

    override fun onStart() { super.onStart(); mapView.onStart() }
    override fun onResume() { super.onResume(); mapView.onResume() }
    override fun onPause() { super.onPause(); mapView.onPause() }
    override fun onStop() { super.onStop(); mapView.onStop() }
    override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mapView.onSaveInstanceState(outState)
    }
}

Toggle Layer Visibility

Let users show/hide each data layer:

kotlin
import android.widget.ToggleButton
import org.maplibre.android.style.layers.Property

private fun setupLayerToggles(style: Style) {
    val layerToggles = mapOf(
        R.id.btnZones to listOf("zones-fill", "zones-outline"),
        R.id.btnRoutes to listOf("routes-layer"),
        R.id.btnPois to listOf("pois-layer"),
    )

    for ((btnId, layerIds) in layerToggles) {
        findViewById<ToggleButton>(btnId).setOnCheckedChangeListener { _, checked ->
            for (layerId in layerIds) {
                val layer = style.getLayer(layerId)
                layer?.setProperties(
                    visibility(
                        if (checked) Property.VISIBLE else Property.NONE
                    )
                )
            }
        }
    }
}

Mix Local and Remote Sources

Combine bundled data with API data:

kotlin
private fun addMixedSources(style: Style) {
    // Remote: live data from API
    style.addSource(
        GeoJsonSource(
            "live-data",
            java.net.URI("https://api.example.com/live-events.geojson")
        )
    )

    style.addLayer(
        CircleLayer("live-layer", "live-data")
            .withProperties(
                circleRadius(6f),
                circleColor(Color.RED)
            )
    )

    // Local: static boundary from assets
    val boundaryJson = assets.open("data/city-boundary.geojson")
        .bufferedReader().readText()

    style.addSource(
        GeoJsonSource("boundary", FeatureCollection.fromJson(boundaryJson))
    )

    style.addLayer(
        LineLayer("boundary-layer", "boundary")
            .withProperties(
                lineColor(Color.parseColor("#333333")),
                lineWidth(2f),
                lineDasharray(arrayOf(3f, 2f))
            )
    )
}

Layer Ordering

Control which layers appear on top:

kotlin
// Add below a specific layer
style.addLayerBelow(fillLayer, "road-label")

// Add above a specific layer
style.addLayerAbove(circleLayer, "zones-fill")

// Typical order (bottom to top):
// 1. Fill layers (zones, polygons)
// 2. Line layers (routes, boundaries)
// 3. Circle/Symbol layers (POIs, markers)

Next Steps


Tip: Add layers in the correct visual order — fills first, then lines, then points. Use addLayerBelow or addLayerAbove to insert layers at specific positions in the rendering stack. Points should always be on top so they remain clickable.