Visualize data as a heatmap
This example demonstrates adding earthquake frequency data from a GeoJSON file to a map and visualizing it as a heatmap in an Android application. The implementation involves creating a GeoJsonSource
with earthquake data, defining a HeatmapLayer
to display the data with a color ramp based on density, and adjusting attributes like weight, intensity, radius, and opacity at different zoom levels. A CircleLayer
is used to represent earthquake data with varying circle sizes and colors depending on the magnitude of the event. Various interpolation techniques are employed to achieve smooth transitions and visual effects on the map.
This example code is part of the Maps SDK for Android Examples App, a working Android project available on GitHub. Android developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.
See our Run the Maps SDK for Android Examples App tutorial for step-by-step instructions.
package com.mapbox.maps.testapp.examples.globe
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.expressions.dsl.generated.interpolate
import com.mapbox.maps.extension.style.layers.addLayerAbove
import com.mapbox.maps.extension.style.layers.addLayerBelow
import com.mapbox.maps.extension.style.layers.generated.CircleLayer
import com.mapbox.maps.extension.style.layers.generated.HeatmapLayer
import com.mapbox.maps.extension.style.layers.generated.circleLayer
import com.mapbox.maps.extension.style.layers.generated.heatmapLayer
import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName
import com.mapbox.maps.extension.style.projection.generated.projection
import com.mapbox.maps.extension.style.sources.addSource
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
import com.mapbox.maps.extension.style.style
import com.mapbox.maps.testapp.databinding.ActivityHeatmapLayerBinding
/**
* Add earthquake frequency data to a style from a GeoJSON file and render
* it on a map in globe projection using a HeatmapLayer.
*/
class HeatmapLayerGlobeActivity : AppCompatActivity() {
private lateinit var mapboxMap: MapboxMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityHeatmapLayerBinding.inflate(layoutInflater)
setContentView(binding.root)
mapboxMap = binding.mapView.mapboxMap.apply {
loadStyle(
style(Style.DARK) {
+projection(ProjectionName.GLOBE)
}
) { style ->
addRuntimeLayers(style)
}
}
}
private fun addRuntimeLayers(style: Style) {
style.addSource(createEarthquakeSource())
style.addLayerAbove(createHeatmapLayer(), "waterway-label")
style.addLayerBelow(createCircleLayer(), HEATMAP_LAYER_ID)
}
private fun createEarthquakeSource(): GeoJsonSource {
return geoJsonSource(EARTHQUAKE_SOURCE_ID) {
data(EARTHQUAKE_SOURCE_URL)
}
}
private fun createHeatmapLayer(): HeatmapLayer {
return heatmapLayer(
HEATMAP_LAYER_ID,
EARTHQUAKE_SOURCE_ID
) {
maxZoom(9.0)
sourceLayer(HEATMAP_LAYER_SOURCE)
// Begin color ramp at 0-stop with a 0-transparancy color
// to create a blur-like effect.
heatmapColor(
interpolate {
linear()
heatmapDensity()
stop {
literal(0)
rgba(33.0, 102.0, 172.0, 0.0)
}
stop {
literal(0.2)
rgb(103.0, 169.0, 207.0)
}
stop {
literal(0.4)
rgb(209.0, 229.0, 240.0)
}
stop {
literal(0.6)
rgb(253.0, 219.0, 240.0)
}
stop {
literal(0.8)
rgb(239.0, 138.0, 98.0)
}
stop {
literal(1)
rgb(178.0, 24.0, 43.0)
}
}
)
// Increase the heatmap weight based on frequency and property magnitude
heatmapWeight(
interpolate {
linear()
get { literal("mag") }
stop {
literal(0)
literal(0)
}
stop {
literal(6)
literal(1)
}
}
)
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
heatmapIntensity(
interpolate {
linear()
zoom()
stop {
literal(0)
literal(1)
}
stop {
literal(9)
literal(3)
}
}
)
// Adjust the heatmap radius by zoom level
heatmapRadius(
interpolate {
linear()
zoom()
stop {
literal(0)
literal(2)
}
stop {
literal(9)
literal(20)
}
}
)
// Transition from heatmap to circle layer by zoom level
heatmapOpacity(
interpolate {
linear()
zoom()
stop {
literal(7)
literal(1)
}
stop {
literal(9)
literal(0)
}
}
)
}
}
private fun createCircleLayer(): CircleLayer {
return circleLayer(
CIRCLE_LAYER_ID,
EARTHQUAKE_SOURCE_ID
) {
circleRadius(
interpolate {
linear()
zoom()
stop {
literal(7)
interpolate {
linear()
get { literal("mag") }
stop {
literal(1)
literal(1)
}
stop {
literal(6)
literal(4)
}
}
}
stop {
literal(16)
interpolate {
linear()
get { literal("mag") }
stop {
literal(1)
literal(5)
}
stop {
literal(6)
literal(50)
}
}
}
}
)
circleColor(
interpolate {
linear()
get { literal("mag") }
stop {
literal(1)
rgba(33.0, 102.0, 172.0, 0.0)
}
stop {
literal(2)
rgb(102.0, 169.0, 207.0)
}
stop {
literal(3)
rgb(209.0, 229.0, 240.0)
}
stop {
literal(4)
rgb(253.0, 219.0, 199.0)
}
stop {
literal(5)
rgb(239.0, 138.0, 98.0)
}
stop {
literal(6)
rgb(178.0, 24.0, 43.0)
}
}
)
circleOpacity(
interpolate {
linear()
zoom()
stop {
literal(7)
literal(0)
}
stop {
literal(8)
literal(1)
}
}
)
circleStrokeColor("white")
circleStrokeWidth(0.1)
}
}
companion object {
private const val EARTHQUAKE_SOURCE_URL =
"https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
private const val EARTHQUAKE_SOURCE_ID = "earthquakes"
private const val HEATMAP_LAYER_ID = "earthquakes-heat"
private const val HEATMAP_LAYER_SOURCE = "earthquakes"
private const val CIRCLE_LAYER_ID = "earthquakes-circle"
}
}