How to Manage Markers in SymbolLayer in Mapbox v10 using Kotlin
A step-by-step guide for implementing SymbolLayer in Mapbox v10
The easiest way to add markers in a Mapbox map is to do it directly on the map layer. However, the map loads very slowly when the number of markers reaches about 180. This is a known issue, and the solution is to use a SymbolLayer.
I found a guide for Mapbox v9 but couldn’t find one for Mapbox v10. After struggling for a bit, I managed to implement a SymbolLayer in Mapbox v10. Here’s how to do it.
1. Prepare data.
Your data source must be a feature collection.
My original data is of the type ArrayList<Location>
where Location
is my own custom class. I constructed the following function in my class to convert the Location
class to a feature.
fun toFeature(): Feature {
val point = Point.fromLngLat(this.lng, this.lat)
val pointFeature = Feature.fromGeometry(point)
// Add custom properties to feature
// The following properties determine which icon to show (in this example)
pointFeature.addBooleanProperty("selected", this.selected)
pointFeature.addBooleanProperty("visited", this.visited)
pointFeature.addBooleanProperty("removed", this.removed)
return pointFeature
}
To create a feature collection from locations
of type ArrayList<Location>
:
var features: ArrayList<Feature> = arrayListOf()
for (location in locations) { features.add(location.toFeature()) }
val featureCollection: FeatureCollection = FeatureCollection.fromFeatures(features)
2. Prepare icons.
Convert a drawable resource to a bitmap so that it can be added to the Mapbox map as a marker:
// load icons
val defaultIcon = bitmapFromDrawableRes(context, R.drawable.ic_baseline_location_on_24)!!
val selectedIcon = bitmapFromDrawableRes(context, R.drawable.ic_baseline_location_on_24_selected)!!
val visitedIcon = bitmapFromDrawableRes(context, R.drawable.ic_baseline_location_on_24_visited)!!
// function to create bitmaps from drawable res
fun bitmapFromDrawableRes(context: Context, @DrawableRes resourceId: Int): Bitmap? {
val sourceDrawable = AppCompatResources.getDrawable(context, resourceId) ?: return null
if (sourceDrawable is BitmapDrawable) return sourceDrawable.bitmap
val constantState = sourceDrawable.constantState ?: return null
val drawable = constantState.newDrawable().mutate()
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
3. Add images, source, and SymbolLayer to the map style.
mapboxMap.loadStyle(
styleExtension = style(Style.OUTDOORS) {
// add images
+image("default-icon") { bitmap(defaultIcon) }
+image("selected-icon") { bitmap(selectedIcon) }
+image("visited-icon") { bitmap(visitedIcon) }
// add source
+geoJsonSource("source") { featureCollection(featureCollection) }
// add layer
+symbolLayer("layer", "source") {
// show icon image based on feature property
iconImage(switchCase {
eq { get { literal("selected") }; literal(true) }
literal("selected-icon")
eq { get { literal("visited") }; literal(true) }
literal("visited-icon")
eq { get { literal("removed") }; literal(true) }
literal("")
literal("default-icon")
})
iconAllowOverlap(true)
}
}
) {}
Now the icons should show up on the Mapbox map. To update the source data, add your newly generated feature collection to the same source (in this case, “source”
). See step 1 for instructions for generating your feature collection.
Extra: Click events
Add an onClick listener to the map, not the icon. queryRenderedFeatures
queries the map for an array of rendered features. The first result should give you the selected feature.
mapboxMap.addOnMapClickListener { point ->
mapboxMap.queryRenderedFeatures(
RenderedQueryGeometry(mapboxMap.pixelForCoordinate(point)!!),
RenderedQueryOptions(listOf("layer"), null)
) {
if (!it.isValue || it.value!!.isEmpty()) return@queryRenderedFeatures
val selectedFeature = it.value!![0].feature
// ... do things with selected feature
}
true
}
My use case
I’m using Mapbox to build an Android app that bookmarks places of interest — Narie.
I built Narie because I was looking for somewhere to store the places I’d like to visit, but:
1. Saving places with custom information in map apps is not straightforward, and
2. Google Maps doesn’t work in some countries
Narie solves these issues by offering a quick and ingenious way to save locations.
Check out the video below to see how Narie works:
Narie is handy and simple to use—as it is meant to be—and before long I have over 180 places I want to visit pinned on my map. Before using SymbolLayer, the map took ~10 seconds to load. Implementing SymbolLayer sped up the app significantly; the map now loads ~10x faster and finishes rendering within a second.
Narie was launched only recently and is completely free to use. Give Narie a try and let me know if you have any feedback!
If you enjoyed this article, please follow me on Medium to show your support. Thank you for reading!