Skip to content

Load GeoJSON from a URL

This tutorial shows how to load GeoJSON data from a remote URL and display it on your MapMetrics Android map — useful for loading dynamic datasets, APIs, and external data feeds.

Prerequisites

Load GeoJSON from URL

Load and display a remote GeoJSON file:

kotlin
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
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.PropertyFactory.*
import org.maplibre.android.style.sources.GeoJsonSource
import java.net.URI

class GeoJsonUrlActivity : 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 ->
                loadGeoJsonFromUrl(style)
            }
        }
    }

    private fun loadGeoJsonFromUrl(style: Style) {
        // Load GeoJSON directly from a URL
        val geoJsonUrl = "https://gateway.mapmetrics.org/assets/earthquakes.geojson"

        style.addSource(
            GeoJsonSource("remote-data", URI(geoJsonUrl))
        )

        // Visualize as colored circles
        style.addLayer(
            CircleLayer("data-layer", "remote-data")
                .withProperties(
                    circleRadius(6f),
                    circleColor(Color.parseColor("#FF6B35")),
                    circleStrokeColor(Color.WHITE),
                    circleStrokeWidth(1f)
                )
        )

        map.cameraPosition = CameraPosition.Builder()
            .target(LatLng(20.0, 0.0))
            .zoom(2.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)
    }
}

Load from an API with Coroutines

Fetch JSON from a REST API, then display:

kotlin
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.maplibre.geojson.FeatureCollection
import java.net.HttpURLConnection
import java.net.URL

private fun loadFromApi(style: Style) {
    // Add empty source first
    val source = GeoJsonSource("api-data")
    style.addSource(source)

    // Add layer
    style.addLayer(
        CircleLayer("api-layer", "api-data")
            .withProperties(
                circleRadius(5f),
                circleColor(Color.parseColor("#4285F4")),
                circleStrokeColor(Color.WHITE),
                circleStrokeWidth(1f)
            )
    )

    // Fetch data asynchronously
    lifecycleScope.launch {
        val geoJson = withContext(Dispatchers.IO) {
            fetchGeoJson("https://api.example.com/data.geojson")
        }

        geoJson?.let {
            val featureCollection = FeatureCollection.fromJson(it)
            source.setGeoJson(featureCollection)
        }
    }
}

private fun fetchGeoJson(urlString: String): String? {
    return try {
        val url = URL(urlString)
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "GET"
        connection.connectTimeout = 10000
        connection.readTimeout = 10000

        if (connection.responseCode == HttpURLConnection.HTTP_OK) {
            connection.inputStream.bufferedReader().readText()
        } else null
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

Load from Local Assets

Load a GeoJSON file bundled in assets/:

kotlin
private fun loadFromAssets(style: Style) {
    // Read from assets/data/places.geojson
    val json = assets.open("data/places.geojson")
        .bufferedReader()
        .readText()

    val featureCollection = FeatureCollection.fromJson(json)

    style.addSource(GeoJsonSource("local-data", featureCollection))

    style.addLayer(
        CircleLayer("local-layer", "local-data")
            .withProperties(
                circleRadius(6f),
                circleColor(Color.parseColor("#34A853"))
            )
    )
}

Refresh Data Periodically

Update GeoJSON data on a timer:

kotlin
import android.os.Handler
import android.os.Looper

private val refreshHandler = Handler(Looper.getMainLooper())
private val refreshInterval = 30000L // 30 seconds

private fun startAutoRefresh(style: Style) {
    val source = style.getSource("remote-data") as? GeoJsonSource ?: return

    val refreshRunnable = object : Runnable {
        override fun run() {
            lifecycleScope.launch {
                val json = withContext(Dispatchers.IO) {
                    fetchGeoJson("https://api.example.com/live-data.geojson")
                }
                json?.let {
                    source.setGeoJson(FeatureCollection.fromJson(it))
                }
            }
            refreshHandler.postDelayed(this, refreshInterval)
        }
    }

    refreshHandler.postDelayed(refreshRunnable, refreshInterval)
}

override fun onDestroy() {
    refreshHandler.removeCallbacksAndMessages(null)
    super.onDestroy()
    mapView.onDestroy()
}

GeoJSON Source Options

ConstructorDescription
GeoJsonSource(id, URI)Load from URL (auto-fetches)
GeoJsonSource(id, FeatureCollection)Load from parsed data
GeoJsonSource(id, String)Load from raw JSON string
GeoJsonSource(id, Geometry)Load single geometry
GeoJsonSource(id, URI, GeoJsonOptions)URL with clustering options

Next Steps


Tip: GeoJsonSource(id, URI) handles network fetching automatically on a background thread. For simple static URLs, this is the easiest approach. Use the coroutine method when you need custom headers, authentication, or error handling.