Add more examples to MapLibre Andoid Example Documentation (#3038)

This commit is contained in:
Bart Louwers
2024-11-22 21:10:40 +01:00
committed by GitHub
parent 06fb5c4733
commit 34c104b8e2
36 changed files with 833 additions and 315 deletions

View File

@@ -94,14 +94,8 @@ To run the benchmarks (for Android) include the following line on a PR comment:
[maplibre-native/docs/mdbook](https://maplibre.org/maplibre-native/docs/book/) describes how Tracy can be used for profiling.
## Examples Documentation
## Documentation
To build the Examples Documentation you need to have Docker installed.
We use Dokka for the API documentation.
From `platform/android`, run:
```
make mkdocs
```
Next, visit http://localhost:8000/maplibre-native/android/examples/
The documentation site with examples uses MkDocs along with Material for MkDocs. For more information on how to work on the examples, see [`docs/README.md`](./docs/REAME.md`).

View File

@@ -316,8 +316,10 @@ clean:
.PHONY: mkdocs
mkdocs:
docker build -t squidfunk/mkdocs-material docs
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
.PHONY: mkdocs-build
mkdocs-build:
docker build -t squidfunk/mkdocs-material docs
docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build --strict

View File

@@ -24,7 +24,7 @@ import org.maplibre.android.utils.BitmapUtils
* with an ImageSource
*
*
* GL-native equivalent of https://maplibre.org/maplibre-gl-js-docs/example/animate-images/
* MapLibre Native equivalent of https://maplibre.org/maplibre-gl-js/docs/examples/animate-images/
*
*/
class AnimatedImageSourceActivity : AppCompatActivity(), OnMapReadyCallback {
@@ -40,6 +40,7 @@ class AnimatedImageSourceActivity : AppCompatActivity(), OnMapReadyCallback {
}
override fun onMapReady(map: MapLibreMap) {
// --8<-- [start:onMapReady]
val quad = LatLngQuad(
LatLng(46.437, -80.425),
LatLng(46.437, -71.516),
@@ -50,7 +51,7 @@ class AnimatedImageSourceActivity : AppCompatActivity(), OnMapReadyCallback {
val layer = RasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE)
map.setStyle(
Style.Builder()
.fromUri(TestStyles.getPredefinedStyleWithFallback("Streets"))
.fromUri(TestStyles.AMERICANA)
.withSource(imageSource)
.withLayer(layer)
) { style: Style? ->
@@ -59,6 +60,7 @@ class AnimatedImageSourceActivity : AppCompatActivity(), OnMapReadyCallback {
handler.postDelayed(it, 100)
}
}
// --8<-- [end:onMapReady]
}
override fun onStart() {
@@ -110,11 +112,13 @@ class AnimatedImageSourceActivity : AppCompatActivity(), OnMapReadyCallback {
}
override fun run() {
// --8<-- [start:setImage]
imageSource.setImage(drawables[drawableIndex++]!!)
if (drawableIndex > 3) {
drawableIndex = 0
}
handler.postDelayed(this, 1000)
// --8<-- [end:setImage]
}
init {

View File

@@ -35,7 +35,6 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
private val random = Random()
private lateinit var mapView: MapView
private lateinit var maplibreMap: MapLibreMap
private var style: Style? = null
private val randomCars: MutableList<Car> = ArrayList()
private var randomCarSource: GeoJsonSource? = null
private var taxi: Car? = null
@@ -49,22 +48,21 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
mapView.onCreate(savedInstanceState)
mapView.getMapAsync {
maplibreMap = it
it.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets")) { style: Style? ->
this.style = style
setupCars()
animateRandomRoutes()
animateTaxi()
it.setStyle(TestStyles.OPENFREEMAP_LIBERY) { style ->
setupCars(style)
animateRandomRoutes(style)
animateTaxi(style)
}
}
}
private fun setupCars() {
addRandomCars()
addPassenger()
addMainCar()
private fun setupCars(style: Style) {
addRandomCars(style)
addPassenger(style)
addTaxi(style)
}
private fun animateRandomRoutes() {
private fun animateRandomRoutes(style: Style) {
val longestDrive = longestDrive
val random = Random()
for (car in randomCars) {
@@ -85,7 +83,7 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
updateRandomDestinations()
animateRandomRoutes()
animateRandomRoutes(style)
}
})
}
@@ -105,7 +103,8 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
}
}
private fun animateTaxi() {
// --8<-- [start:animateTaxi]
private fun animateTaxi(style: Style) {
val valueAnimator = ValueAnimator.ofObject(LatLngEvaluator(), taxi!!.current, taxi!!.next)
valueAnimator.addUpdateListener(object : AnimatorUpdateListener {
private var latLng: LatLng? = null
@@ -118,8 +117,8 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
valueAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
updatePassenger()
animateTaxi()
updatePassenger(style)
animateTaxi(style)
}
})
valueAnimator.addListener(object : AnimatorListenerAdapter() {
@@ -134,15 +133,16 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
valueAnimator.start()
animators.add(valueAnimator)
}
// --8<-- [end:animateTaxi]
private fun updatePassenger() {
private fun updatePassenger(style: Style) {
passenger = latLngInBounds
updatePassengerSource()
updatePassengerSource(style)
taxi!!.setNext(passenger)
}
private fun updatePassengerSource() {
val source = style!!.getSourceAs<GeoJsonSource>(PASSENGER_SOURCE)
private fun updatePassengerSource(style: Style) {
val source = style.getSourceAs<GeoJsonSource>(PASSENGER_SOURCE)
val featureCollection = FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
@@ -198,7 +198,7 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
private val duration: Long
get() = (random.nextInt(DURATION_RANDOM_MAX) + DURATION_BASE).toLong()
private fun addRandomCars() {
private fun addRandomCars(style: Style) {
var latLng: LatLng
var next: LatLng
for (i in 0..9) {
@@ -216,12 +216,13 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
randomCars.add(
Car(feature, next, duration)
)
}
randomCarSource = GeoJsonSource(RANDOM_CAR_SOURCE, featuresFromRoutes())
style!!.addSource(randomCarSource!!)
style!!.addImage(
style.addSource(randomCarSource!!)
style.addImage(
RANDOM_CAR_IMAGE_ID,
(ResourcesCompat.getDrawable(resources, R.drawable.ic_car_top, theme) as BitmapDrawable).bitmap
ResourcesCompat.getDrawable(resources, R.drawable.ic_car_top, theme)!!
)
val symbolLayer = SymbolLayer(RANDOM_CAR_LAYER, RANDOM_CAR_SOURCE)
symbolLayer.withProperties(
@@ -230,10 +231,12 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
PropertyFactory.iconRotate(Expression.get(PROPERTY_BEARING)),
PropertyFactory.iconIgnorePlacement(true)
)
style!!.addLayerBelow(symbolLayer, WATERWAY_LAYER_ID)
style.addLayerBelow(symbolLayer, "label_country_1")
}
private fun addPassenger() {
// --8<-- [start:addPassenger]
private fun addPassenger(style: Style) {
passenger = latLngInBounds
val featureCollection = FeatureCollection.fromFeatures(
arrayOf(
@@ -245,22 +248,24 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
)
)
)
style!!.addImage(
style.addImage(
PASSENGER,
(ResourcesCompat.getDrawable(resources, R.drawable.icon_burned, theme) as BitmapDrawable).bitmap
ResourcesCompat.getDrawable(resources, R.drawable.icon_burned, theme)!!
)
val geoJsonSource = GeoJsonSource(PASSENGER_SOURCE, featureCollection)
style!!.addSource(geoJsonSource)
style.addSource(geoJsonSource)
val symbolLayer = SymbolLayer(PASSENGER_LAYER, PASSENGER_SOURCE)
symbolLayer.withProperties(
PropertyFactory.iconImage(PASSENGER),
PropertyFactory.iconIgnorePlacement(true),
PropertyFactory.iconAllowOverlap(true)
)
style!!.addLayerBelow(symbolLayer, RANDOM_CAR_LAYER)
style.addLayerBelow(symbolLayer, RANDOM_CAR_LAYER)
}
// --8<-- [end:addPassenger]
private fun addMainCar() {
// --8<-- [start:addTaxi]
private fun addTaxi(style: Style) {
val latLng = latLngInBounds
val properties = JsonObject()
properties.addProperty(PROPERTY_BEARING, Car.getBearing(latLng, passenger))
@@ -273,12 +278,12 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
)
val featureCollection = FeatureCollection.fromFeatures(arrayOf(feature))
taxi = Car(feature, passenger, duration)
style!!.addImage(
style.addImage(
TAXI,
(ResourcesCompat.getDrawable(resources, R.drawable.ic_taxi_top, theme) as BitmapDrawable).bitmap
)
taxiSource = GeoJsonSource(TAXI_SOURCE, featureCollection)
style!!.addSource(taxiSource!!)
style.addSource(taxiSource!!)
val symbolLayer = SymbolLayer(TAXI_LAYER, TAXI_SOURCE)
symbolLayer.withProperties(
PropertyFactory.iconImage(TAXI),
@@ -286,17 +291,23 @@ class AnimatedSymbolLayerActivity : AppCompatActivity() {
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconIgnorePlacement(true)
)
style!!.addLayer(symbolLayer)
style.addLayer(symbolLayer)
}
// --8<-- [end:addTaxi]
// --8<-- [start:latLngInBounds]
private val latLngInBounds: LatLng
get() {
val bounds = maplibreMap.projection.visibleRegion.latLngBounds
val generator = Random()
val randomLat = bounds.latitudeSouth + generator.nextDouble() * bounds.latitudeNorth - bounds.latitudeSouth
val randomLon = bounds.longitudeWest + generator.nextDouble() * bounds.longitudeEast - bounds.longitudeWest
val randomLat = bounds.latitudeSouth + generator.nextDouble() * (bounds.latitudeNorth - bounds.latitudeSouth)
val randomLon = bounds.longitudeWest + generator.nextDouble() * (bounds.longitudeEast - bounds.longitudeWest)
return LatLng(randomLat, randomLon)
}
// --8<-- [end:latLngInBounds]
override fun onStart() {
super.onStart()

View File

@@ -41,7 +41,7 @@ class BuildingFillExtrusionActivity : AppCompatActivity() {
if (map != null) {
maplibreMap = map
}
maplibreMap.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets")) { style: Style ->
maplibreMap.setStyle(TestStyles.OPENFREEMAP_BRIGHT) { style: Style ->
setupBuildings(style)
setupLight()
}
@@ -50,37 +50,43 @@ class BuildingFillExtrusionActivity : AppCompatActivity() {
}
private fun setupBuildings(style: Style) {
val fillExtrusionLayer = FillExtrusionLayer("3d-buildings", "composite")
// --8<-- [start:setupBuildings]
val fillExtrusionLayer = FillExtrusionLayer("building-3d", "openmaptiles")
fillExtrusionLayer.sourceLayer = "building"
fillExtrusionLayer.setFilter(
Expression.eq(
Expression.get("extrude"),
Expression.literal("true")
Expression.all(
Expression.has("render_height"),
Expression.has("render_min_height")
)
)
fillExtrusionLayer.minZoom = 15f
fillExtrusionLayer.setProperties(
PropertyFactory.fillExtrusionColor(Color.LTGRAY),
PropertyFactory.fillExtrusionHeight(Expression.get("height")),
PropertyFactory.fillExtrusionBase(Expression.get("min_height")),
PropertyFactory.fillExtrusionHeight(Expression.get("render_height")),
PropertyFactory.fillExtrusionBase(Expression.get("render_min_height")),
PropertyFactory.fillExtrusionOpacity(0.9f)
)
style.addLayer(fillExtrusionLayer)
// --8<-- [end:setupBuildings]
}
private fun setupLight() {
light = maplibreMap.style!!.light
findViewById<View>(R.id.fabLightPosition).setOnClickListener { v: View? ->
// --8<-- [start:lightPosition]
isInitPosition = !isInitPosition
if (isInitPosition) {
light!!.position = Position(1.5f, 90f, 80f)
} else {
light!!.position = Position(1.15f, 210f, 30f)
}
// --8<-- [end:lightPosition]
}
findViewById<View>(R.id.fabLightColor).setOnClickListener { v: View? ->
// --8<-- [start:lightColor]
isRedColor = !isRedColor
light!!.setColor(ColorUtils.colorToRgbaString(if (isRedColor) Color.RED else Color.BLUE))
// --8<-- [end:lightColor]
}
}

View File

@@ -4,13 +4,12 @@ import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapView.OnDidFinishLoadingStyleListener
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.OnMapReadyCallback
import org.maplibre.android.maps.Style
import org.maplibre.android.style.expressions.Expression
import org.maplibre.android.style.layers.CircleLayer
@@ -42,52 +41,59 @@ class CircleLayerActivity : AppCompatActivity(), View.OnClickListener {
private var isLoadingStyle = true
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_circle_layer)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(
OnMapReadyCallback { map: MapLibreMap? ->
if (map != null) {
maplibreMap = map
}
maplibreMap.setStyle(TestStyles.getPredefinedStyleWithFallback("Satellite Hybrid"))
mapView.addOnDidFinishLoadingStyleListener(
OnDidFinishLoadingStyleListener {
val style = maplibreMap.style
addBusStopSource(style)
addBusStopCircleLayer(style)
initFloatingActionButtons()
isLoadingStyle = false
}
)
mapView.getMapAsync { map: MapLibreMap? ->
if (map != null) {
maplibreMap = map
}
)
maplibreMap.setStyle(TestStyles.PROTOMAPS_WHITE) {
maplibreMap.getStyle { style ->
addBusStopSource(style)
addBusStopCircleLayer(style)
initFloatingActionButtons()
isLoadingStyle = false
style.addImage(
"bus-icon",
AppCompatResources.getDrawable(this, R.drawable.ic_directions_bus_black)!!
)
}
}
}
}
private fun addBusStopSource(style: Style?) {
private fun addBusStopSource(style: Style) {
// --8<-- [start:addBusStopSource]
try {
source = GeoJsonSource(SOURCE_ID, URI(URL_BUS_ROUTES))
} catch (exception: URISyntaxException) {
Timber.e(exception, "That's not an url... ")
}
style!!.addSource(source!!)
style.addSource(source!!)
// --8<-- [end:addBusStopSource]
}
private fun addBusStopCircleLayer(style: Style?) {
private fun addBusStopCircleLayer(style: Style) {
// --8<-- [start:addBusStopCircleLayer]
layer = CircleLayer(LAYER_ID, SOURCE_ID)
layer!!.setProperties(
PropertyFactory.circleColor(Color.parseColor("#FF9800")),
PropertyFactory.circleRadius(2.0f)
)
style!!.addLayerBelow(layer!!, "water_intermittent")
style.addLayer(layer!!)
// --8<-- [end:addBusStopCircleLayer]
}
private fun initFloatingActionButtons() {
routeFab = findViewById(R.id.fab_route)
routeFab.setColorFilter(ContextCompat.getColor(this@CircleLayerActivity, R.color.primary))
routeFab.setOnClickListener(this@CircleLayerActivity)
routeFab.setColorFilter(ContextCompat.getColor(this, R.color.primary))
routeFab.setOnClickListener(this)
styleFab = findViewById(R.id.fab_style)
styleFab.setOnClickListener(this@CircleLayerActivity)
styleFab.setOnClickListener(this)
}
override fun onClick(view: View) {
@@ -113,102 +119,107 @@ class CircleLayerActivity : AppCompatActivity(), View.OnClickListener {
}
private fun addClusteredSource() {
try {
maplibreMap.style!!.addSource(
GeoJsonSource(
SOURCE_ID_CLUSTER,
URI(URL_BUS_ROUTES),
GeoJsonOptions()
.withCluster(true)
.withClusterMaxZoom(14)
.withClusterRadius(50)
maplibreMap.getStyle { style ->
try {
// --8<-- [start:addClusteredSource]
style.addSource(
GeoJsonSource(
SOURCE_ID_CLUSTER,
URI(URL_BUS_ROUTES),
GeoJsonOptions()
.withCluster(true)
.withClusterMaxZoom(14)
.withClusterRadius(50)
)
)
)
} catch (malformedUrlException: URISyntaxException) {
Timber.e(malformedUrlException, "That's not an url... ")
}
// --8<-- [end:addClusteredSource]
} catch (malformedUrlException: URISyntaxException) {
Timber.e(malformedUrlException, "That's not an url... ")
}
// Add unclustered layer
val layers = arrayOf(
intArrayOf(
150,
ResourcesCompat.getColor(
// --8<-- [start:unclusteredLayer]
val unclustered = SymbolLayer("unclustered-points", SOURCE_ID_CLUSTER)
unclustered.setProperties(
PropertyFactory.iconImage("bus-icon"),
)
unclustered.setFilter(
Expression.neq(Expression.get("cluster"), true)
)
style.addLayer(unclustered)
// --8<-- [end:unclusteredLayer]
// --8<-- [start:clusteredCircleLayers]
val layers = arrayOf(
150 to ResourcesCompat.getColor(
resources,
R.color.redAccent,
theme
)
),
intArrayOf(20, ResourcesCompat.getColor(resources, R.color.greenAccent, theme)),
intArrayOf(
0,
ResourcesCompat.getColor(
),
20 to ResourcesCompat.getColor(resources, R.color.greenAccent, theme),
0 to ResourcesCompat.getColor(
resources,
R.color.blueAccent,
theme
)
)
)
val unclustered = SymbolLayer("unclustered-points", SOURCE_ID_CLUSTER)
unclustered.setProperties(
PropertyFactory.iconImage("bus-15")
)
maplibreMap.style!!.addLayer(unclustered)
for (i in layers.indices) {
// Add some nice circles
val circles = CircleLayer("cluster-$i", SOURCE_ID_CLUSTER)
circles.setProperties(
PropertyFactory.circleColor(layers[i][1]),
PropertyFactory.circleRadius(18f)
)
val pointCount = Expression.toNumber(Expression.get("point_count"))
circles.setFilter(
if (i == 0) {
Expression.all(
Expression.has("point_count"),
Expression.gte(
pointCount,
Expression.literal(
layers[i][0]
)
)
// --8<-- [end:clusteredCircleLayers]
)
} else {
Expression.all(
Expression.has("point_count"),
Expression.gt(
pointCount,
Expression.literal(
layers[i][0]
)
),
Expression.lt(
pointCount,
Expression.literal(
layers[i - 1][0]
// --8<-- [start:clusteredCircleLayersLoop]
for (i in layers.indices) {
// Add some nice circles
val circles = CircleLayer("cluster-$i", SOURCE_ID_CLUSTER)
circles.setProperties(
PropertyFactory.circleColor(layers[i].second),
PropertyFactory.circleRadius(18f)
)
val pointCount = Expression.toNumber(Expression.get("point_count"))
circles.setFilter(
if (i == 0) {
Expression.all(
Expression.has("point_count"),
Expression.gte(
pointCount,
Expression.literal(layers[i].first)
)
)
)
}
} else {
Expression.all(
Expression.has("point_count"),
Expression.gt(
pointCount,
Expression.literal(layers[i].first)
),
Expression.lt(
pointCount,
Expression.literal(layers[i - 1].first)
)
)
}
)
style.addLayer(circles)
}
// --8<-- [end:clusteredCircleLayersLoop]
// --8<-- [start:countLabels]
val count = SymbolLayer("count", SOURCE_ID_CLUSTER)
count.setProperties(
PropertyFactory.textField(Expression.toString(Expression.get("point_count"))),
PropertyFactory.textFont(arrayOf("Noto Sans Medium")),
PropertyFactory.textSize(12f),
PropertyFactory.textColor(Color.WHITE),
PropertyFactory.textIgnorePlacement(true),
PropertyFactory.textAllowOverlap(true)
)
maplibreMap.style!!.addLayer(circles)
style.addLayer(count)
// --8<-- [end:countLabels]
}
// Add the count labels
val count = SymbolLayer("count", SOURCE_ID_CLUSTER)
count.setProperties(
PropertyFactory.textField(Expression.toString(Expression.get("point_count"))),
PropertyFactory.textSize(12f),
PropertyFactory.textColor(Color.WHITE),
PropertyFactory.textIgnorePlacement(true),
PropertyFactory.textAllowOverlap(true)
)
maplibreMap.style!!.addLayer(count)
}
private fun removeFabs() {
routeFab!!.visibility = View.GONE
styleFab!!.visibility = View.GONE
routeFab.visibility = View.GONE
styleFab.visibility = View.GONE
}
private fun changeMapStyle() {
@@ -226,13 +237,8 @@ class CircleLayerActivity : AppCompatActivity(), View.OnClickListener {
maplibreMap.setStyle(Style.Builder().fromUri(nextStyle))
}
private fun addBusStop() {
maplibreMap.style!!.addLayer(layer!!)
maplibreMap.style!!.addSource(source!!)
}
private val nextStyle: String
private get() {
get() {
currentStyleIndex++
if (currentStyleIndex == Data.STYLES.size) {
currentStyleIndex = 0
@@ -277,12 +283,8 @@ class CircleLayerActivity : AppCompatActivity(), View.OnClickListener {
private object Data {
val STYLES = arrayOf(
TestStyles.getPredefinedStyleWithFallback("Streets"),
TestStyles.getPredefinedStyleWithFallback("Outdoor"),
TestStyles.getPredefinedStyleWithFallback("Bright"),
TestStyles.getPredefinedStyleWithFallback("Pastel"),
TestStyles.getPredefinedStyleWithFallback("Satellite Hybrid"),
TestStyles.getPredefinedStyleWithFallback("Satellite Hybrid")
TestStyles.PROTOMAPS_WHITE,
TestStyles.PROTOMAPS_LIGHT
)
}
@@ -290,7 +292,7 @@ class CircleLayerActivity : AppCompatActivity(), View.OnClickListener {
const val SOURCE_ID = "bus_stop"
const val SOURCE_ID_CLUSTER = "bus_stop_cluster"
const val URL_BUS_ROUTES =
"https://raw.githubusercontent.com/cheeaun/busrouter-sg/master/data/2/bus-stops.geojson"
"https://s3.eu-central-1.amazonaws.com/maplibre-native/android-documentation-resources/bus-stops.geojson"
const val LAYER_ID = "stops_layer"
}
}

View File

@@ -37,7 +37,7 @@ class CustomSpriteActivity : AppCompatActivity() {
mapView.onCreate(savedInstanceState)
mapView.getMapAsync {
maplibreMap = it
it.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets")) { style: Style ->
it.setStyle(TestStyles.OPENFREEMAP_LIBERY) { style: Style ->
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setColorFilter(
ContextCompat.getColor(
@@ -50,6 +50,7 @@ class CustomSpriteActivity : AppCompatActivity() {
override fun onClick(view: View) {
if (!this::point.isInitialized) {
Timber.i("First click -> Car")
// --8<-- [start:addImage]
// Add an icon to reference later
style.addImage(
CUSTOM_ICON,
@@ -76,8 +77,9 @@ class CustomSpriteActivity : AppCompatActivity() {
)
// lets add a circle below labels!
maplibreMap.style!!.addLayerBelow(layer, "water_intermittent")
maplibreMap.style!!.addLayerBelow(layer, "water-intermittent")
fab.setImageResource(R.drawable.ic_directions_car_black)
// --8<-- [end:addImage]
} else {
// Update point
point = Point.fromLngLat(

View File

@@ -42,7 +42,7 @@ class DataDrivenStyleActivity : AppCompatActivity() {
mapView.getMapAsync {
// Store for later
maplibreMap = it
it.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets")) { style: Style? ->
it.setStyle(TestStyles.VERSATILES) { style: Style? ->
// Add a parks layer
addParksLayer()
@@ -155,42 +155,56 @@ class DataDrivenStyleActivity : AppCompatActivity() {
}
}
private fun addExponentialZoomFunction() {
Timber.i("Add exponential zoom function")
val layer = maplibreMap.style!!.getLayerAs<FillLayer>("water")!!
layer.setProperties(
PropertyFactory.fillColor(
Expression.interpolate(
Expression.exponential(0.5f),
Expression.zoom(),
Expression.stop(1, Expression.color(Color.RED)),
Expression.stop(5, Expression.color(Color.BLUE)),
Expression.stop(10, Expression.color(Color.GREEN))
)
)
)
Timber.i("Fill color: %s", layer.fillColor)
maplibreMap.getStyle { style ->
style.layers.filter { it.id.startsWith("water") }.filterIsInstance<FillLayer>()
.forEach { layer ->
// --8<-- [start:addExponentialZoomFunction]
layer.setProperties(
PropertyFactory.fillColor(
Expression.interpolate(
Expression.exponential(0.5f),
Expression.zoom(),
Expression.stop(1, Expression.color(Color.RED)),
Expression.stop(5, Expression.color(Color.BLUE)),
Expression.stop(10, Expression.color(Color.GREEN))
)
)
)
// --8<-- [end:addExponentialZoomFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
}
}
private fun addIntervalZoomFunction() {
Timber.i("Add interval zoom function")
val layer = maplibreMap.style!!.getLayerAs<FillLayer>("water")!!
layer.setProperties(
PropertyFactory.fillColor(
Expression.step(
Expression.zoom(),
Expression.rgba(0.0f, 255.0f, 255.0f, 1.0f),
Expression.stop(1, Expression.rgba(255.0f, 0.0f, 0.0f, 1.0f)),
Expression.stop(5, Expression.rgba(0.0f, 0.0f, 255.0f, 1.0f)),
Expression.stop(10, Expression.rgba(0.0f, 255.0f, 0.0f, 1.0f))
maplibreMap.getStyle { style ->
style.layers.filter { it.id.startsWith("water") }.filterIsInstance<FillLayer>().forEach { layer ->
// --8<-- [start:addIntervalZoomFunction]
layer.setProperties(
PropertyFactory.fillColor(
Expression.step(
Expression.zoom(),
Expression.rgba(0.0f, 255.0f, 255.0f, 1.0f),
Expression.stop(1, Expression.rgba(255.0f, 0.0f, 0.0f, 1.0f)),
Expression.stop(5, Expression.rgba(0.0f, 0.0f, 255.0f, 1.0f)),
Expression.stop(10, Expression.rgba(0.0f, 255.0f, 0.0f, 1.0f))
)
)
)
)
)
Timber.i("Fill color: %s", layer.fillColor)
// --8<-- [end:addIntervalZoomFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
}
}
private fun addExponentialSourceFunction() {
Timber.i("Add exponential source function")
// --8<-- [start:addExponentialSourceFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -203,11 +217,13 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addExponentialSourceFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
private fun addCategoricalSourceFunction() {
Timber.i("Add categorical source function")
// --8<-- [start:addCategoricalSourceFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -223,22 +239,27 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addCategoricalSourceFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
private fun addIdentitySourceFunction() {
Timber.i("Add identity source function")
// --8<-- [start:addIdentitySourceFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillOpacity(
Expression.get("fill-opacity")
)
)
// --8<-- [end:addIdentitySourceFunction]
Timber.i("Fill opacity: %s", layer.fillOpacity)
}
private fun addIntervalSourceFunction() {
Timber.i("Add interval source function")
// --8<-- [start:addIntervalSourceFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -251,11 +272,13 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addIntervalSourceFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
private fun addCompositeExponentialFunction() {
Timber.i("Add composite exponential function")
// --8<-- [start:addCompositeExponentialFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -295,11 +318,13 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addCompositeExponentialFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
private fun addCompositeIntervalFunction() {
Timber.i("Add composite interval function")
// --8<-- [start:addCompositeIntervalFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -339,11 +364,13 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addCompositeIntervalFunction]
Timber.i("Fill color: %s", layer.fillColor)
}
private fun addCompositeCategoricalFunction() {
Timber.i("Add composite categorical function")
// --8<-- [start:addCompositeCategoricalFunction]
val layer = maplibreMap.style!!.getLayerAs<FillLayer>(AMSTERDAM_PARKS_LAYER)!!
layer.setProperties(
PropertyFactory.fillColor(
@@ -503,6 +530,7 @@ class DataDrivenStyleActivity : AppCompatActivity() {
)
)
)
// --8<-- [end:addCompositeCategoricalFunction]
Timber.i("Fill color: %s", layer.fillColor)
}

View File

@@ -51,13 +51,12 @@ class DistanceExpressionActivity : AppCompatActivity() {
}
private fun setupStyle() {
// --8<-- [start:FillLayer]
val center = Point.fromLngLat(lon, lat)
val circle = TurfTransformation.circle(center, 150.0, TurfConstants.UNIT_METRES)
// Setup style with additional layers,
// using Streets as a base style
maplibreMap.setStyle(
Style.Builder()
.fromUri(TestStyles.getPredefinedStyleWithFallback("Streets"))
.fromUri(TestStyles.OPENFREEMAP_BRIGHT)
.withSources(
GeoJsonSource(
POINT_ID,
@@ -71,24 +70,26 @@ class DistanceExpressionActivity : AppCompatActivity() {
fillOpacity(0.5f),
fillColor(Color.parseColor("#3bb2d0"))
),
"poi-label"
"poi"
)
// --8<-- [end:FillLayer]
) { style ->
// Show only POI labels inside circle radius using distance expression
val symbolLayer = style.getLayer("poi_z16") as SymbolLayer
symbolLayer.setFilter(
lt(
distance(
Point.fromLngLat(lon, lat)
),
150
)
)
// Hide other types of labels to highlight POI labels
(style.getLayer("road_label") as SymbolLayer?)?.setProperties(visibility(NONE))
(style.getLayer("airport-label-major") as SymbolLayer?)?.setProperties(visibility(NONE))
(style.getLayer("poi_transit") as SymbolLayer?)?.setProperties(visibility(NONE))
// --8<-- [start:distanceExpression]
for (layer in style.layers) {
if (layer is SymbolLayer) {
if (layer.id.startsWith("poi")) {
layer.setFilter(lt(
distance(
Point.fromLngLat(lon, lat)
),
150
))
} else {
layer.setProperties(visibility(NONE))
}
}
}
// --8<-- [end:distanceExpression]
}
}
@@ -124,7 +125,7 @@ class DistanceExpressionActivity : AppCompatActivity() {
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
outState?.let {
outState.let {
binding.mapView.onSaveInstanceState(it)
}
}

View File

@@ -72,7 +72,7 @@ class DraggableMarkerActivity : AppCompatActivity() {
maplibreMap.setStyle(
Style.Builder()
.fromUri(TestStyles.getPredefinedStyleWithFallback("Streets"))
.fromUri(TestStyles.PROTOMAPS_LIGHT)
.withImage(markerImageId, IconFactory.getInstance(this).defaultMarker().bitmap)
.withSource(source)
.withLayer(layer)
@@ -91,6 +91,7 @@ class DraggableMarkerActivity : AppCompatActivity() {
)
)
// --8<-- [start:addOnMapClickListener]
maplibreMap.addOnMapClickListener {
// Adding a marker on map click
val features = maplibreMap.queryRenderedSymbols(it, layerId)
@@ -108,7 +109,9 @@ class DraggableMarkerActivity : AppCompatActivity() {
false
}
// --8<-- [end:addOnMapClickListener]
// --8<-- [start:draggableSymbolsManager]
draggableSymbolsManager = DraggableSymbolsManager(
mapView,
maplibreMap,
@@ -148,22 +151,25 @@ class DraggableMarkerActivity : AppCompatActivity() {
.show()
}
})
// --8<-- [end:draggableSymbolsManager]
}
}
// --8<-- [start:addMarker]
private fun addMarker(latLng: LatLng) {
featureCollection.features()?.add(
Feature.fromGeometry(Point.fromLngLat(latLng.longitude, latLng.latitude), null, generateMarkerId())
)
source.setGeoJson(featureCollection)
}
// --8<-- [end:addMarker]
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// Dispatching parent's touch events to the manager
draggableSymbolsManager?.onParentTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
// --8<-- [start:DraggableSymbolsManager]
/**
* A manager, that allows dragging symbols after they are long clicked.
* Since this manager lives outside of the Maps SDK, we need to intercept parent's motion events
@@ -201,7 +207,7 @@ class DraggableMarkerActivity : AppCompatActivity() {
private val androidGesturesManager: AndroidGesturesManager = AndroidGesturesManager(mapView.context, false)
private var draggedSymbolId: String? = null
private val onSymbolDragListeners: MutableList<OnSymbolDragListener> = mutableListOf<OnSymbolDragListener>()
private val onSymbolDragListeners: MutableList<OnSymbolDragListener> = mutableListOf()
init {
maplibreMap.addOnMapLongClickListener {
@@ -303,6 +309,7 @@ class DraggableMarkerActivity : AppCompatActivity() {
fun onSymbolDragFinished(id: String)
}
}
// --8<-- [end:DraggableSymbolsManager]
override fun onStart() {
super.onStart()

View File

@@ -4,15 +4,32 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.*
import org.maplibre.android.style.layers.*
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
import org.maplibre.geojson.Feature
import org.maplibre.geojson.Point
import timber.log.Timber
import java.net.URI
import java.net.URISyntaxException
import kotlin.math.atan2
fun calculateRotationAngle(from: Point, to: Point): Float {
val longitudeDiff = to.longitude() - from.longitude()
val latitudeDiff = to.latitude() - from.latitude()
val angleRadians = atan2(longitudeDiff, latitudeDiff)
val angleDegrees = Math.toDegrees(angleRadians)
return ((angleDegrees + 360) % 360).toFloat() // Normalize to 0-360 degrees
}
/**
* Test activity showcasing using realtime GeoJSON to move a symbol on your map
@@ -26,8 +43,16 @@ class RealTimeGeoJsonActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var maplibreMap: MapLibreMap
private var handler: Handler? = null
private var runnable: Runnable? = null
private var lastLocation: Point? = null
private val netherlands = LatLng(52.646396, 5.723877)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
supportActionBar?.hide() // Hides the action bar if present
setContentView(R.layout.activity_default)
mapView = findViewById<View>(R.id.mapView) as MapView
mapView.onCreate(savedInstanceState)
@@ -36,21 +61,31 @@ class RealTimeGeoJsonActivity : AppCompatActivity(), OnMapReadyCallback {
override fun onMapReady(map: MapLibreMap) {
maplibreMap = map
maplibreMap.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets")) { style -> // add source
maplibreMap.cameraPosition = CameraPosition.Builder().target(netherlands).zoom(6.0).build()
maplibreMap.setStyle(TestStyles.PROTOMAPS_WHITE) { style -> // add source
ResourcesCompat.getDrawable(resources, R.drawable.ic_airplanemode_active_black, theme)
?.let { style.addImage("plane", it) }
// --8<-- [start:addSource]
try {
style.addSource(GeoJsonSource(ID_GEOJSON_SOURCE, URI(URL_GEOJSON_SOURCE)))
} catch (malformedUriException: URISyntaxException) {
Timber.e(malformedUriException, "Invalid URL")
}
// --8<-- [end:addSource]
// add layer
// --8<-- [start:addLayer]
val layer = SymbolLayer(ID_GEOJSON_LAYER, ID_GEOJSON_SOURCE)
layer.setProperties(PropertyFactory.iconImage("rocket_15"))
layer.setProperties(
PropertyFactory.iconImage("plane"),
PropertyFactory.iconAllowOverlap(true)
)
style.addLayer(layer)
// --8<-- [end:addLayer]
// loop refresh geojson
handler = Handler(Looper.getMainLooper())
runnable = RefreshGeoJsonRunnable(maplibreMap!!, handler!!)
runnable = RefreshGeoJsonRunnable(maplibreMap, handler!!)
runnable?.let {
handler!!.postDelayed(it, 2000)
}
@@ -90,16 +125,46 @@ class RealTimeGeoJsonActivity : AppCompatActivity(), OnMapReadyCallback {
mapView.onSaveInstanceState(outState)
}
private class RefreshGeoJsonRunnable(
private fun setIconRotation(features: List<Feature>) {
// --8<-- [start:setIconRotation]
if (features.size != 1) {
Timber.e("Expected only one feature")
return
}
val feature = features[0]
val geometry = feature.geometry()
if (geometry !is Point) {
Timber.e("Expected geometry to be a point")
return
}
if (lastLocation == null) {
lastLocation = geometry
return
}
maplibreMap.style!!.getLayer(ID_GEOJSON_LAYER)!!.setProperties(
PropertyFactory.iconRotate(calculateRotationAngle(lastLocation!!, geometry)),
)
// --8<-- [end:setIconRotation]
lastLocation = geometry
}
// --8<-- [start:Runnable]
private inner class RefreshGeoJsonRunnable(
private val maplibreMap: MapLibreMap,
private val handler: Handler
) : Runnable {
override fun run() {
val geoJsonSource = maplibreMap.style!!.getSource(ID_GEOJSON_SOURCE) as GeoJsonSource
geoJsonSource.setUri(URL_GEOJSON_SOURCE)
val features = geoJsonSource.querySourceFeatures(null)
setIconRotation(features)
handler.postDelayed(this, 2000)
}
}
// --8<-- [end:Runnable]
companion object {
private const val ID_GEOJSON_LAYER = "border"

View File

@@ -11,6 +11,8 @@ object TestStyles {
const val OPENFREEMAP_LIBERY = "https://tiles.openfreemap.org/styles/liberty"
const val OPENFREEMAP_BRIGHT = "https://tiles.openfreemap.org/styles/bright"
const val AWS_OPEN_DATA_STANDARD_LIGHT = "https://maps.geo.us-east-2.amazonaws.com/maps/v0/maps/OpenDataStyle/style-descriptor?key=v1.public.eyJqdGkiOiI1NjY5ZTU4My0yNWQwLTQ5MjctODhkMS03OGUxOTY4Y2RhMzgifR_7GLT66TNRXhZJ4KyJ-GK1TPYD9DaWuc5o6YyVmlikVwMaLvEs_iqkCIydspe_vjmgUVsIQstkGoInXV_nd5CcmqRMMa-_wb66SxDdbeRDvmmkpy2Ow_LX9GJDgL2bbiCws0wupJPFDwWCWFLwpK9ICmzGvNcrPbX5uczOQL0N8V9iUvziA52a1WWkZucIf6MUViFRf3XoFkyAT15Ll0NDypAzY63Bnj8_zS8bOaCvJaQqcXM9lrbTusy8Ftq8cEbbK5aMFapXRjug7qcrzUiQ5sr0g23qdMvnKJQFfo7JuQn8vwAksxrQm6A0ByceEXSfyaBoVpFcTzEclxUomhY.NjAyMWJkZWUtMGMyOS00NmRkLThjZTMtODEyOTkzZTUyMTBi"
private fun protomaps(style: String): String {
@@ -23,7 +25,9 @@ object TestStyles {
val PROTOMAPS_GRAYSCALE = protomaps("grayscale")
val PROTOMAPS_WHITE = protomaps("white")
val PROTOMAPS_BLACK = protomaps("black")
fun getPredefinedStyleWithFallback(name: String): String {
try {
val style = Style.getPredefinedStyle(name)

View File

@@ -0,0 +1,3 @@
FROM squidfunk/mkdocs-material
RUN pip install mkdocs-macros-plugin

View File

@@ -1,48 +1,39 @@
# MapLibre Android Examples
Welcome to the examples documentation of MapLibre Android.
To build the Examples Documentation you need to have Docker installed.
<div class="grid cards" markdown>
From `platform/android`, run:
- :material-clock-fast:{ .lg .middle } __Quickstart__
```
make mkdocs
```
---
Next, visit http://localhost:8000/maplibre-native/android/examples/
Learn how to include MapLibre Android in your project
## Snippets
[:octicons-arrow-right-24: Getting started](getting-started.md)
We use [a Markdown extension for snippets](https://facelessuser.github.io/pymdown-extensions/extensions/snippets/#snippet-sections). This way code can be referenced instead of copy pasted into the documentation. This avoids code examples from becoming out of date or failing to compile. The syntax is as follows:
- :fontawesome-brands-slack:{ .lg .middle } __Find us on Slack__
````kotlin
// --8<-- [start:fun]
fun double(x: Int): Int {
return 2 * x
}
// --8<-- [end:fun]
````
---
Next, you'll be able to reference that piece of code in Markdown like so:
Discuss the project and ask questions in the `#maplibre-android` channel
```
--8<-- "example.kt:fun"
```
[:octicons-arrow-right-24: Slack](https://osmus.slack.com/archives/C01G3D28DAB)
Where `example.kt` is the path to the file.
- :fontawesome-solid-code:{ .lg .middle } __Contribute to these Docs__
## Static Assets
---
Static assets are ideally uploaded to the [MapLibre Native S3 Bucket](https://maplibre-native.s3.eu-central-1.amazonaws.com/index.html#android-documentation-resources/).
Share your own examples with the community!
Please open an issue with the ARN of your AWS account to get upload privileges.
[:octicons-arrow-right-24: Documentation on GitHub](https://github.com/maplibre/maplibre-native/tree/main/platform/android/docs)
</div>
## Open-Source Apps Using MapLibre Android
You can learn how to use the API from MapLibre Android by stuying the source code of existing apps that intergrate MapLibre Android. Here are some open-source apps that use MapLibre Android:
- [Streetcomplete](https://github.com/streetcomplete/StreetComplete) ([source code](https://github.com/search?q=repo%3Astreetcomplete%2FStreetComplete%20maplibre&type=code))
- [The official Wikipedia app for Android](https://github.com/wikimedia/apps-android-wikipedia) ([source code](https://github.com/search?q=repo%3Awikimedia%2Fapps-android-wikipedia%20maplibre&type=code)).
- [MapLibreAndroidTestApp](https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp). This app is part of the MapLibre Native repository and is used for (automated) testing. Many of the examples in this documentation site come directly from this app.
## See Also
- [MapLibre Android API Documentation](https://maplibre.org/maplibre-native/android/api/)
- [Source code on GitHub](https://github.com/maplibre/maplibre-native/tree/main/platform/android)
- [Latest releases](https://github.com/maplibre/maplibre-native/releases?q=android-v11&expanded=true)
- [GitHub Discussions](https://github.com/maplibre/maplibre-native/discussions/categories/q-a?discussions_q=is%3Aopen+category%3AQ%26A+label%3Aandroid)
- [MapLibre on Slack](https://slack.openstreetmap.us). Join the `#maplibre-native` and `#maplibre-android` channels.
Use the CDN URL `https://dwxvn1oqw6mkc.cloudfront.net/` instead of linking to the S3 bucket directly.

View File

@@ -20,4 +20,8 @@
.md-copyright__highlight {
color: inherit;
}
.inline-attribution {
font-size: smaller;
}

View File

@@ -1,8 +1,6 @@
# Animation Types
!!! note
You can find the full source code of this example in [`CameraAnimationTypeActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/activity/camera/CameraAnimationTypeActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("CameraAnimationTypeActivity.kt") }}
This example showcases the different animation types.
@@ -19,8 +17,8 @@ The `MapLibreMap.moveCamera` method jumps to the camera position provided.
```
<figure markdown="span">
<video controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/move_animation_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/move_animation.mp4" />
<video controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/move_animation_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/move_animation.mp4" />
</video>
</figure>
@@ -33,8 +31,8 @@ The `MapLibreMap.moveCamera` eases to the camera position provided (with constan
```
<figure markdown="span">
<video preload="none" controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/ease_animation_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/ease_animation.mp4" />
<video preload="none" controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/ease_animation_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/ease_animation.mp4" />
</video>
</figure>
@@ -50,8 +48,8 @@ The `MapLibreMap.animateCamera` uses a powered flight animation move to the came
```
<figure markdown="span">
<video preload="none" controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/animate_animation_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/animate_animation.mp4" />
<video preload="none" controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/animate_animation_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/animate_animation.mp4" />
</video>
</figure>

View File

@@ -1,14 +1,12 @@
# CameraPosition Capabilities
!!! note
You can find the full source code of this example in [`CameraPositionActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/CameraPositionActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("CameraPositionActivity.kt") }}
This example showcases how to listen to camera change events.
<figure markdown="span">
<video controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/cameraposition_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/cameraposition.mp4" />
<video controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/cameraposition_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/cameraposition.mp4" />
</video>
</figure>

View File

@@ -1,15 +1,12 @@
# LatLngBounds API
!!! note
You can find the full source code of this example in [`LatLngBoundsActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("LatLngBoundsActivity.kt") }}
This example demonstrates setting the camera to some bounds defined by some features. It sets these bounds when the map is initialized and when the [bottom sheet](https://m2.material.io/components/sheets-bottom) is opened or closed.
<figure markdown="span">
<video controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/lat_lng_bounds_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/lat_lng_bounds.mp4" />
<video controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/lat_lng_bounds_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/lat_lng_bounds.mp4" />
</video>
</figure>

View File

@@ -1,8 +1,6 @@
# Min/Max Zoom
# Max/Min Zoom
!!! note
You can find the full source code of this example in [`MaxMinZoomActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/MaxMinZoomActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MaxMinZoomActivity.kt") }}
This example shows how to configure a maximum and a minimum zoom level.
@@ -21,8 +19,9 @@ As a bonus, this example also shows how you can define a click listener to the m
You can remove a click listener again with `MapLibreMap.removeOnMapClickListener`. To use this API you need to assign the click listener to a variable, since you need to pass the listener to that method.
<figure markdown="span">
<video controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/max_min_zoom_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/max_min_zoom.mp4" />
<video controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/max_min_zoom_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/max_min_zoom.mp4" />
</video>
{{ openmaptiles_caption }}
</figure>

View File

@@ -1,8 +1,6 @@
# Scroll by Method
!!! note
You can find the full source code of this example in [`ScrollByActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/ScrollByActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("ScrollByActivity.kt") }}
This example shows how you can move the map by x/y pixels.

View File

@@ -1,14 +1,12 @@
# Zoom Methods
!!! note
You can find the full source code of this example in [`ManualZoomActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/ManualZoomActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("ManualZoomActivity.kt") }}
This example shows different methods of zooming in.
<figure markdown="span">
<video controls width="250" poster="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/zoom_methods_thumbnail.jpg">
<source src="https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/zoom_methods.mp4" />
<video controls width="250" poster="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/zoom_methods_thumbnail.jpg">
<source src="https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/zoom_methods.mp4" />
</video>
</figure>

View File

@@ -38,7 +38,7 @@ This can be found in [`activity_map_options_xml.xml`](https://github.com/maplibr
You can assign any other existing values to the `maplibre...` tags. Then, you only need to create `MapView` and `MapLibreMap` objects with a simple setup in the Activity.
```kotlin
```kotlin title="MapOptionsXmlActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsXmlActivity.kt"
```
@@ -55,8 +55,8 @@ This can be found in [`activity_map_options_runtime.xml`](https://github.com/map
A `MapLibreMapOptions` object must be created and passed to the MapView constructor. All setup is done in the Activity code:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsRuntimeActivity.ktl"
```kotlin title="MapOptionsRuntimeActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsRuntimeActivity.kt"
```
This can be found in [`MapOptionsRuntimeActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsRuntimeActivity.kt).

View File

@@ -0,0 +1,48 @@
# MapLibre Android Examples
Welcome to the examples documentation of MapLibre Android.
<div class="grid cards" markdown>
- :material-clock-fast:{ .lg .middle } __Quickstart__
---
Learn how to include MapLibre Android in your project
[:octicons-arrow-right-24: Getting started](getting-started.md)
- :fontawesome-brands-slack:{ .lg .middle } __Find us on Slack__
---
Discuss the project and ask questions in the `#maplibre-android` channel
[:octicons-arrow-right-24: Slack](https://osmus.slack.com/archives/C01G3D28DAB)
- :fontawesome-solid-code:{ .lg .middle } __Contribute to these Docs__
---
Share your own examples with the community!
[:octicons-arrow-right-24: Documentation on GitHub](https://github.com/maplibre/maplibre-native/tree/main/platform/android/docs)
</div>
## Open-Source Apps Using MapLibre Android
You can learn how to use the API from MapLibre Android by stuying the source code of existing apps that intergrate MapLibre Android. Here are some open-source apps that use MapLibre Android:
- [Streetcomplete](https://github.com/streetcomplete/StreetComplete) ([source code](https://github.com/search?q=repo%3Astreetcomplete%2FStreetComplete%20maplibre&type=code))
- [The official Wikipedia app for Android](https://github.com/wikimedia/apps-android-wikipedia) ([source code](https://github.com/search?q=repo%3Awikimedia%2Fapps-android-wikipedia%20maplibre&type=code)).
- [MapLibreAndroidTestApp](https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp). This app is part of the MapLibre Native repository and is used for (automated) testing. Many of the examples in this documentation site come directly from this app.
## See Also
- [MapLibre Android API Documentation](https://maplibre.org/maplibre-native/android/api/)
- [Source code on GitHub](https://github.com/maplibre/maplibre-native/tree/main/platform/android)
- [Latest releases](https://github.com/maplibre/maplibre-native/releases?q=android-v11&expanded=true)
- [GitHub Discussions](https://github.com/maplibre/maplibre-native/discussions/categories/q-a?discussions_q=is%3Aopen+category%3AQ%26A+label%3Aandroid)
- [MapLibre on Slack](https://slack.openstreetmap.us). Join the `#maplibre-native` and `#maplibre-android` channels.

View File

@@ -76,7 +76,10 @@ val locationComponentOptions =
Here is the final results with different color configurations. For the complete content of this demo, please refer to the source code of the [Test App].
![result](https://github.com/maplibre/maplibre-native/assets/19887090/03dfc87b-111b-4dd0-b4a3-d89e30ed6b63)
<figure markdown="span">
![result](https://github.com/maplibre/maplibre-native/assets/19887090/03dfc87b-111b-4dd0-b4a3-d89e30ed6b63)
{{ openmaptiles_caption() }}
</figure>
[^1]: A variety of [camera modes] determine how the camera will track the user location.

View File

@@ -0,0 +1,37 @@
import os
from pathlib import Path
def find_file(start_directory, target_filename):
"""Recursively search for a file by name starting from start_directory."""
for root, dirs, files in os.walk(start_directory):
if target_filename in files:
path = Path(os.path.join(root, target_filename))
return path.relative_to("/docs")
raise FileNotFoundError(f"File '{target_filename}' could not be found in {start_directory}")
def define_env(env):
"""
This is the hook for the variables, macros and filters.
"""
@env.macro
def activity_source_note(filename):
file_path = find_file(f"{os.getcwd()}/MapLibreAndroidTestApp", filename)
return f"""
!!! note
You can find the full source code of this example in [`{filename}`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/{file_path}) of the MapLibreAndroidTestApp.
"""
@env.macro
def s3_url(filename):
return f"https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/{filename}"
@env.macro
def openmaptiles_caption():
return f"""
<caption>
<p class="inline-attribution">Map data [OpenStreetMap](https://www.openstreetmap.org/copyright). © [OpenMapTiles](https://openmaptiles.org/).</p>
</caption>
"""

View File

@@ -4,9 +4,7 @@ This guide will help you walk through how to use [MapSnapshotter](https://maplib
## Map Snapshot with Local Style
!!! note
You can find the full source code of this example in [`MapSnapshotterLocalStyleActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MapSnapshotterLocalStyleActivity.kt") }}
To get started we will show how to use the map snapshotter with a local style.
@@ -36,14 +34,12 @@ Lastly we use the `.start()` method to create the snapshot, and pass callbacks f
## Show a Grid of Snapshots
!!! note
You can find the full source code of this example in [`MapSnapshotterActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MapSnapshotterActivity.kt") }}
In this example, we demonstrate how to use the `MapSnapshotter` to create multiple map snapshots with different styles and camera positions, displaying them in a grid layout.
<figure markdown="span">
![Map Snapshotter](https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/map_snapshotter.png){ width="300" }
![Map Snapshotter](https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/map_snapshotter.png){ width="300" }
</figure>
First we create a [`GridLayout`](https://developer.android.com/reference/kotlin/android/widget/GridLayout) and a list of `MapSnapshotter` instances. We create a `Style.Builder` with a different style for each cell in the grid.
@@ -78,14 +74,12 @@ In the last column of the first row we add two bitmaps. See the next example for
## Map Snapshot with Bitmap Overlay
!!! note
You can also find this code in [`MapSnapshotterBitMapOverlayActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MapSnapshotterBitMapOverlayActivity.kt") }}
This example adds a bitmap on top of the snapshot. It also demonstrates how you can add a click listener to a snapshot.
<figure markdown="span">
![Screenshot of Map Snapshot with Bitmap Overlay](https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/map_snapshot_with_bitmap_overlay.png){ width="300" }
![Screenshot of Map Snapshot with Bitmap Overlay](https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/map_snapshot_with_bitmap_overlay.png){ width="300" }
</figure>
@@ -95,14 +89,12 @@ This example adds a bitmap on top of the snapshot. It also demonstrates how you
## Map Snapshotter with Heatmap Layer
!!! note
You can find the full source code of this example in [`MapSnapshotterHeatMapActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MapSnapshotterHeatMapActivity.kt") }}
In this example, we demonstrate how to use the `MapSnapshotter` to create a snapshot of a map that includes a heatmap layer. The heatmap represents earthquake data loaded from a GeoJSON source.
<figure markdown="span">
![Screenshot of Snapshotter with Heatmap](https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/snapshotter_headmap_screenshot.png){ width="300" }
![Screenshot of Snapshotter with Heatmap](https://dwxvn1oqw6mkc.cloudfront.net/android-documentation-resources/snapshotter_headmap_screenshot.png){ width="300" }
</figure>
First, we create the `MapSnapshotterHeatMapActivity` class, which extends `AppCompatActivity` and implements `MapSnapshotter.SnapshotReadyCallback` to receive the snapshot once it's ready.
@@ -148,9 +140,7 @@ Finally, we ensure to cancel the snapshotter in the `onStop` method to free up r
## Map Snapshotter with Expression
!!! note
You can find the full source code of this example in [`MapSnapshotterWithinExpression.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/turf/MapSnapshotterWithinExpression.kt) of the MapLibreAndroidTestApp.
{{ activity_source_note("MapSnapshotterWithinExpression.kt") }}
In this example the map on top is a live while the map on the bottom is a snapshot that is updated as you pan the map. We style of the snapshot is modified: using a [within](https://maplibre.org/maplibre-style-spec/expressions/#within) expression only POIs within a certain distance to a line is shown. A highlight for this area is added to the map as are various points.

View File

@@ -0,0 +1,22 @@
# Animated Image Source
{{ activity_source_note("AnimatedImageSourceActivity.kt") }}
In this example we will see how we can animate an image source. This is the MapLibre Native equivalent of [this MapLibre GL JS example](https://maplibre.org/maplibre-gl-js/docs/examples/animate-images/).
<figure markdown="span">
<video controls width="400" poster="{{ s3_url("animated_image_source_thumbnail.jpg") }}" >
<source src="{{ s3_url("animated_image_source.mp4") }}" />
</video>
{{ openmaptiles_caption() }}
</figure>
We set up an [image source](https://maplibre.org/maplibre-style-spec/sources/#image) in a particular quad. Then we kick of a runnable that periodically updates the image source.
```kotlin title="Creating the image source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedImageSourceActivity.kt:onMapReady"
```
```kotlin title="Updating the image source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedImageSourceActivity.kt:setImage"
```

View File

@@ -0,0 +1,35 @@
# Animated SymbolLayer
{{ activity_source_note("AnimatedSymbolLayerActivity.kt") }}
<figure markdown="span">
<video controls width="250" poster="{{ s3_url("animated_symbol_layer_thumbnail.jpg") }}" >
<source src="{{ s3_url("animated_symbol_layer.mp4") }}" />
</video>
{{ openmaptiles_caption() }}
</figure>
Notice that there are (red) cars randomly moving around, and a (yellow) taxi that is always heading to the passenger (indicated by the M symbol), which upon arrival hops to a different location again. We will focus on the passanger and the taxi, because the cars randomly moving around follow a similar pattern.
In a real application you would of course retrieve the locations from some sort of external API, but for the purposes of this example a random latitude longtitude pair within bounds of the currently visible screen will do.
```kotlin title="Getter method to get a random location on the screen"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedSymbolLayerActivity.kt:latLngInBounds"
```
```kotlin title="Adding a passenger at a random location (on screen)"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedSymbolLayerActivity.kt:addPassenger"
```
Adding the taxi on screen is done very similarly.
```kotlin title="Adding the taxi with bearing"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedSymbolLayerActivity.kt:addTaxi"
```
For animating the taxi we use a [`ValueAnimator`](https://developer.android.com/reference/android/animation/ValueAnimator).
```kotlin title="Animate the taxi driving towards the passenger"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/AnimatedSymbolLayerActivity.kt:animateTaxi"
```

View File

@@ -0,0 +1,25 @@
# Building Layer
{{ activity_source_note("BuildingFillExtrusionActivity.kt") }}
In this example will show how to add a [Fill Extrusion](https://maplibre.org/maplibre-style-spec/layers/#fill-extrusion) layer to a style.
<figure markdown="span">
<video controls width="400" poster="{{ s3_url("building_layer_thumbnail.jpg") }}" >
<source src="{{ s3_url("building_layer.mp4") }}" />
</video>
{{ openmaptiles_caption() }}
</figure>
We use the [OpenFreeMap Bright](https://openfreemap.org/quick_start/) style which, unlike OpenFreeMap Libery, does not have a fill extrusion layer by default. However, if you inspect this style with [Maputnik](https://maplibre.org/maputnik) you will find that the multipolygons in the `building` layer (of the `openfreemap` source) each have `render_min_height` and `render_height` properties.
```kotlin title="Setting up the fill extrusion layer"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/BuildingFillExtrusionActivity.kt:setupBuildings"
```
```kotlin title="Changing the light direction"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/BuildingFillExtrusionActivity.kt:lightPosition"
```
```kotlin title="Changing the light color"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/BuildingFillExtrusionActivity.kt:lightColor"
```

View File

@@ -0,0 +1,49 @@
# Circle Layer (with Clustering)
{{ activity_source_note("CircleLayerActivity.kt") }}
In this example we will add a circle layer for a GeoJSON source. We also show how you can use the [cluster](https://maplibre.org/maplibre-style-spec/sources/#cluster) property of a GeoJSON source.
<figure markdown="span">
<video controls width="400" poster="{{ s3_url("circle_layer_cluster_thumbnail.jpg") }}" >
<source src="{{ s3_url("circle_layer_cluster.mp4") }}" />
</video>
</figure>
Create a `GeoJsonSource` instance, pass a unique identifier for the source and the URL where the GeoJSON is available. Next add the source to the style.
```kotlin title="Setting up the GeoJSON source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:addBusStopSource"
```
Now you can create a `CircleLayer`, pass it a unique identifier for the layer and the source identifier of the GeoJSON source just created. You can use a `PropertyFactory` to pass [circle layer properties](https://maplibre.org/maplibre-style-spec/layers/#circle). Lastly add the layer to your style.
```kotlin title="Create circle layer a small orange circle for each bus stop"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:addBusStopCircleLayer"
```
## Clustering
Next we will show you how you can use clustering. Create a `GeoJsonSource` as before, but with some additional options to enable clustering.
```kotlin title="Setting up the clustered GeoJSON source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:addClusteredSource"
```
When enabling clustering some [special attributes](https://maplibre.org/maplibre-style-spec/sources/#cluster) will be available to the points in the newly created layer. One is `cluster`, which is true if the point indicates a cluster. We want to show a bus stop for points that are **not** clustered.
```kotlin title="Add a symbol layers for points that are not clustered"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:unclusteredLayer"
```
Next we define which point amounts correspond to which colors. More than 150 points will get a red circle, clusters with 21-150 points will be green and clusters with 20 or less points will be green.
```kotlin title="Define different colors for different point amounts"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:clusteredCircleLayers"
```
Lastly we iterate over the array of `Pair`s to create a `CircleLayer` for each element.
```kotlin title="Add different circle layers for clusters of different point amounts"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CircleLayerActivity.kt:clusteredCircleLayersLoop"
```

View File

@@ -0,0 +1,9 @@
# Add Custom Sprite
{{ activity_source_note("CustomSpriteActivity.kt") }}
This example showcases adding a sprite image and using it in a Symbol Layer.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CustomSpriteActivity.kt:addImage"
```

View File

@@ -0,0 +1,88 @@
# Data Driven Style
{{ activity_source_note("DataDrivenStyleActivity.kt") }}
In this example we will look at various types of data-driven styling.
The examples with 'Source' in the title apply data-driven styling the [parks of Amsterdam](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/res/raw/amsterdam.geojson). Those examples often are based on the somewhat arbitrary `stroke-width` property part of the GeoJSON features. These examples are therefore most interesting to learn about the Kotlin API that can be used for data-driven styling.
!!! tip
Refer to the [MapLibre Style Spec](https://maplibre.org/maplibre-style-spec/) for more information about [expressions](https://maplibre.org/maplibre-style-spec/expressions/) such as [`interpolate`](https://maplibre.org/maplibre-style-spec/expressions/#interpolate) and [`step`](https://maplibre.org/maplibre-style-spec/expressions/#step).
## Exponential Zoom Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addExponentialZoomFunction"
```
<figure markdown="span">
<video controls width="250" poster="{{ s3_url("exponential_zoom_function_thumbnail.jpg") }}" >
<source src="{{ s3_url("exponential_zoom_function.mp4") }}" />
</video>
</figure>
## Interval Zoom Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addIntervalZoomFunction"
```
<figure markdown="span">
<video controls width="250" poster="{{ s3_url("interval_zoom_function_thumbnail.jpg") }}" >
<source src="{{ s3_url("interval_zoom_function.mp4") }}" />
</video>
</figure>
```json title="Equivalent JSON"
["step",["zoom"],["rgba",0.0,255.0,255.0,1.0],1.0,["rgba",255.0,0.0,0.0,1.0],5.0,["rgba",0.0,0.0,255.0,1.0],10.0,["rgba",0.0,255.0,0.0,1.0]]
```
## Exponential Source Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addExponentialSourceFunction"
```
## Categorical Source Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addCategoricalSourceFunction"
```
## Identity Source Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addIdentitySourceFunction"
```
## Interval Source Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addIntervalSourceFunction"
```
## Composite Exponential Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addCompositeExponentialFunction"
```
## Identity Source Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addIdentitySourceFunction"
```
## Composite Interval Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addCompositeIntervalFunction"
```
## Composite Categorical Function
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DataDrivenStyleActivity.kt:addCompositeCategoricalFunction"
```

View File

@@ -0,0 +1,22 @@
# Distance Expression
{{ activity_source_note("DistanceExpressionActivity.kt") }}
This example shows how you can modify a style to only show certain features within a certain distance to a point. For this the [distance expression](https://maplibre.org/maplibre-style-spec/expressions/#within) is used.
<figure markdown="span">
![Screenshot of map where only labels inside some circular area are shown]({{ s3_url("distance_expression_activity.png") }}){ width="400" }
{{ openmaptiles_caption() }}
</figure>
First we add a [fill layer](https://maplibre.org/maplibre-style-spec/layers/#fill) and a GeoJSON source.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DistanceExpressionActivity.kt:FillLayer"
```
Next, we only show features from symbol layers that are less than a certain distance from the point. All symbol layers whose identifier does not start with `poi` are completely hidden.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DistanceExpressionActivity.kt:distanceExpression"
```

View File

@@ -0,0 +1,36 @@
# Draggable Marker
{{ activity_source_note("DraggableMarkerActivity.kt") }}
<figure markdown="span">
<video controls width="400" poster="{{ s3_url("draggable_marker_thumbnail.jpg") }}" >
<source src="{{ s3_url("draggable_marker.mp4") }}" />
</video>
</figure>
## Adding a marker on tap
```kotlin title="Adding a tap listener to the map to add a marker on tap"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DraggableMarkerActivity.kt:addOnMapClickListener"
```
## Allowing markers to be dragged
This is slightly more involved, as we implement it by implementing a `DraggableSymbolsManager` helper class.
This class is initialized and we pass a few callbacks when when markers are start or end being dragged.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DraggableMarkerActivity.kt:draggableSymbolsManager"
```
The implementation of `DraggableSymbolsManager` follows. In its initializer we define a handler for when a user long taps on a marker. This then starts dragging that marker. It does this by temporarily suspending all other gestures.
We create a custom implementation of `MoveGestureDetector.OnMoveGestureListener` and pass this to an instance of `AndroidGesturesManager` linked to the map view.
!!! tip
See [maplibre-gestures-android](https://github.com/maplibre/maplibre-gestures-android) for the implementation details of the gestures library used by MapLibre Android.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/DraggableMarkerActivity.kt:DraggableSymbolsManager"
```

View File

@@ -0,0 +1,37 @@
# Add live realtime data
{{ activity_source_note("RealTimeGeoJsonActivity.kt") }}
In this example you will learn how to add a live GeoJSON source. We have set up a [lambda function](https://m6rgfvqjp34nnwqcdm4cmmy3cm0dtupu.lambda-url.us-east-1.on.aws/) that returns a new GeoJSON point every time it is called.
<figure markdown="span">
<video controls width="250" poster="{{ s3_url("live_realtime_data_thumbnail.jpg") }}" >
<source src="{{ s3_url("live_realtime_data.mp4") }}" />
</video>
</figure>
First we will create a `GeoJSONSource`.
```kotlin title="Adding GeoJSON source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RealTimeGeoJsonActivity.kt:addSource"
```
Next we will create a `SymbolLayer` that uses the source.
```kotlin title="Adding a SymbolLayer source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RealTimeGeoJsonActivity.kt:addLayer"
```
We use define a `Runnable` and use `android.os.Handler` with a `android.os.Looper` to update the GeoJSON source every 2 seconds.
```kotlin title="Defining a Runnable for updating the GeoJSON source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RealTimeGeoJsonActivity.kt:Runnable"
```
## Bonus: set icon rotation
You can set the icon rotation of the icon when ever the point is updated based on the last two points.
```kotlin title="Defining a Runnable for updating the GeoJSON source"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RealTimeGeoJsonActivity.kt:setIconRotation"
```

View File

@@ -4,6 +4,7 @@ repo_url: https://github.com/maplibre/maplibre-native
site_description: MapLibre Android Examples
edit_uri: edit/main/platform/android/docs
copyright: Map data from <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>
exclude_docs: README.md
extra_css:
- assets/extra.css
theme:
@@ -40,6 +41,7 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets:
dedent_subsections: true
check_paths: true
- pymdownx.superfences
- pymdownx.escapeall:
hardbreak: True
@@ -66,6 +68,9 @@ extra:
link: https://github.com/maplibre
plugins:
- search
- macros:
module_name: docs/main
on_undefined: strict
- social:
cards_layout_options:
background_color: '#295DAA'