Add a Heatmap Layer
This tutorial shows how to create heatmap visualizations on your MapMetrics Android map — ideal for showing data density like earthquake activity, population, or events.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Heatmap
Load earthquake data and display it as a heatmap:
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.expressions.Expression
import org.maplibre.android.style.expressions.Expression.*
import org.maplibre.android.style.layers.HeatmapLayer
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.sources.GeoJsonSource
import java.net.URI
class HeatmapActivity : 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 ->
addHeatmap(style)
}
}
}
private fun addHeatmap(style: Style) {
// Add GeoJSON source with earthquake data
style.addSource(
GeoJsonSource(
"earthquake-source",
URI("https://gateway.mapmetrics.org/assets/earthquakes.geojson")
)
)
// Add heatmap layer
style.addLayer(
HeatmapLayer("heatmap-layer", "earthquake-source").withProperties(
// Weight based on magnitude
heatmapWeight(
interpolate(
linear(), get("mag"),
stop(0, 0),
stop(6, 1)
)
),
// Increase intensity with zoom
heatmapIntensity(
interpolate(
linear(), zoom(),
stop(0, 1),
stop(9, 3)
)
),
// Color ramp from transparent to red
heatmapColor(
interpolate(
linear(), heatmapDensity(),
stop(0, rgba(33f, 102f, 172f, 0f)),
stop(0.2, rgb(103f, 169f, 207f)),
stop(0.4, rgb(209f, 229f, 240f)),
stop(0.6, rgb(253f, 219f, 199f)),
stop(0.8, rgb(239f, 138f, 98f)),
stop(1.0, rgb(178f, 24f, 43f))
)
),
// Increase radius with zoom
heatmapRadius(
interpolate(
linear(), zoom(),
stop(0, 2),
stop(9, 20)
)
),
// Fade out at high zoom
heatmapOpacity(
interpolate(
linear(), zoom(),
stop(7, 1),
stop(9, 0)
)
)
)
)
// Set initial view
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)
}
}Heatmap with Point Layer Transition
Show heatmap at low zoom, individual points at high zoom:
kotlin
import org.maplibre.android.style.layers.CircleLayer
private fun addHeatmapWithPoints(style: Style) {
style.addSource(
GeoJsonSource(
"earthquake-source",
URI("https://gateway.mapmetrics.org/assets/earthquakes.geojson")
)
)
// Heatmap layer — visible at low zoom
val heatmapLayer = HeatmapLayer("heatmap-layer", "earthquake-source")
.withProperties(
heatmapWeight(
interpolate(linear(), get("mag"), stop(0, 0), stop(6, 1))
),
heatmapColor(
interpolate(
linear(), heatmapDensity(),
stop(0, rgba(33f, 102f, 172f, 0f)),
stop(0.2, rgb(103f, 169f, 207f)),
stop(0.4, rgb(209f, 229f, 240f)),
stop(0.6, rgb(253f, 219f, 199f)),
stop(0.8, rgb(239f, 138f, 98f)),
stop(1.0, rgb(178f, 24f, 43f))
)
),
heatmapRadius(
interpolate(linear(), zoom(), stop(0, 2), stop(9, 20))
),
// Fade heatmap between zoom 7-9
heatmapOpacity(
interpolate(linear(), zoom(), stop(7, 1), stop(9, 0))
)
)
heatmapLayer.maxZoom = 9f
style.addLayer(heatmapLayer)
// Point layer — visible at high zoom
val circleLayer = CircleLayer("point-layer", "earthquake-source")
.withProperties(
// Color by magnitude
circleColor(
interpolate(
linear(), get("mag"),
stop(1, color(Color.parseColor("#2DC4B2"))),
stop(2, color(Color.parseColor("#3BB3C3"))),
stop(3, color(Color.parseColor("#669EC4"))),
stop(4, color(Color.parseColor("#8B88B6"))),
stop(5, color(Color.parseColor("#A2719B"))),
stop(6, color(Color.parseColor("#AA5E79")))
)
),
// Size by magnitude
circleRadius(
interpolate(linear(), get("mag"), stop(1, 4), stop(6, 16))
),
circleOpacity(
interpolate(linear(), zoom(), stop(7, 0), stop(8, 1))
),
circleStrokeColor(Color.WHITE),
circleStrokeWidth(1f)
)
circleLayer.minZoom = 7f
style.addLayer(circleLayer)
}Heatmap Properties Reference
| Property | Description |
|---|---|
heatmapColor | Color ramp based on heatmapDensity() |
heatmapWeight | Weight of each data point (often based on a property) |
heatmapIntensity | Global multiplier for density (use with zoom) |
heatmapRadius | Radius of influence per point in pixels |
heatmapOpacity | Layer transparency (0.0 - 1.0) |
Next Steps
- Circle Layer — Styled point markers
- Data-Driven Styling — Color by data properties
- Add Clusters — Clustered point markers
Tip: Always pair a heatmap with a point layer transition as shown above. Heatmaps are useful for overview zoom levels (1-8), but at street level users need to see individual data points.