Split out Android examples to separate documentation site (#2978)

This commit is contained in:
Bart Louwers
2024-11-13 18:19:42 +01:00
committed by GitHub
parent 0bac36d8f6
commit b4b779dd29
41 changed files with 2923 additions and 1085 deletions

View File

@@ -92,3 +92,16 @@ To run the benchmarks (for Android) include the following line on a PR comment:
## Profiling
[maplibre-native/docs/mdbook](https://maplibre.org/maplibre-native/docs/book/) describes how Tracy can be used for profiling.
## Examples Documentation
To build the Examples Documentation you need to have Docker installed.
From `platform/android`, run:
```
make mkdocs
```
Next, visit http://localhost:8000/maplibre-native/android/examples/

View File

@@ -311,3 +311,13 @@ clean:
./MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/activity/gen \
./MapLibreAndroid/src/main/assets \
./MapLibreAndroidTestApp/src/main/assets/integration
### MkDocs Documentation ########################################################
.PHONY: mkdocs
mkdocs:
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
.PHONY: mkdocs-build
mkdocs-build:
docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build --strict

View File

@@ -65,6 +65,9 @@ public class AttributionParser {
*/
@NonNull
public String createAttributionString(boolean shortenedOutput) {
if (attributions.isEmpty()) {
return "";
}
StringBuilder stringBuilder = new StringBuilder(withCopyrightSign ? "" : "© ");
int counter = 0;
for (Attribution attribution : attributions) {

View File

@@ -2,6 +2,7 @@ package org.maplibre.android.snapshotter
import android.content.Context
import android.graphics.*
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.Html
@@ -515,15 +516,7 @@ open class MapSnapshotter(context: Context, options: Options) {
drawAttribution(canvas, measure, anchorPoint)
} else {
val snapshot = mapSnapshot.bitmap
Logger.e(
TAG,
String.format(
"Could not generate attribution for snapshot size: %s x %s." + " You are required to provide your own attribution for the used sources: %s",
snapshot.width,
snapshot.height,
mapSnapshot.attributions
)
)
Logger.e(TAG, "Could not generate attribution for snapshot size: ${snapshot.width} x ${snapshot.height}. You are required to provide your own attribution for the used sources: ${mapSnapshot.attributions.joinToString()}")
}
}
@@ -544,7 +537,17 @@ open class MapSnapshotter(context: Context, options: Options) {
textView.textSize = 10 * scale
textView.setTextColor(textColor)
textView.setBackgroundResource(R.drawable.maplibre_rounded_corner)
textView.text = Html.fromHtml(createAttributionString(mapSnapshot, shortText))
val attributionString = createAttributionString(mapSnapshot, shortText)
if (attributionString == "") {
Logger.w(
TAG,
String.format(
"Attribution string is empty. Make sure you provide your own attribution for the used sources if needed.",
)
)
return TextView(context)
}
textView.text = fromHTML(attributionString)
textView.measure(widthMeasureSpec, heightMeasureSpec)
textView.layout(0, 0, textView.measuredWidth, textView.measuredHeight)
return textView
@@ -770,3 +773,9 @@ open class MapSnapshotter(context: Context, options: Options) {
private const val LOGO_MARGIN_DP = 4
}
}
fun fromHTML(source: String) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY)
} else {
Html.fromHtml(source)
}

View File

@@ -362,6 +362,21 @@ class AttributionParseTest {
)
}
@Test
@Throws(Exception::class)
fun testOutputNoAttribution() {
val attributionParser = AttributionParser.Options(RuntimeEnvironment.application)
.withAttributionData("")
.withCopyrightSign(false)
.withImproveMap(false)
.build()
Assert.assertEquals(
"Attribution string should match",
"",
attributionParser.createAttributionString()
)
}
companion object {
private const val STREETS_ATTRIBUTION =
"<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">&copy; MapLibre</a> <a href=\"http://www.openstreetmap.org/about/\" target=\"_blank\">&copy; OpenStreetMap</a> \n"

View File

@@ -27,7 +27,7 @@ import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
/* ANCHOR: top */
// # --8<-- [start:top]
class JsonApiActivity : AppCompatActivity() {
// Declare a variable for MapView
@@ -35,7 +35,7 @@ class JsonApiActivity : AppCompatActivity() {
// Declare a variable for MapLibreMap
private lateinit var maplibreMap: MapLibreMap
/* ANCHOR_END: top */
// # --8<-- [end:top]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -48,7 +48,7 @@ class JsonApiActivity : AppCompatActivity() {
// Init the MapView
mapView = findViewById(R.id.mapView)
/* ANCHOR: mapAsync */
// # --8<-- [start:mapAsync]
mapView.getMapAsync { map ->
maplibreMap = map
@@ -57,10 +57,10 @@ class JsonApiActivity : AppCompatActivity() {
// Fetch data from USGS
getEarthQuakeDataFromUSGS()
}
/* ANCHOR_END: mapAsync */
// # --8<-- [end:mapAsync]
}
/* ANCHOR: getEarthquakes */
// # --8<-- [start:getEarthquakes]
// Get Earthquake data from usgs.gov, read API doc at:
// https://earthquake.usgs.gov/fdsnws/event/1/
private fun getEarthQuakeDataFromUSGS() {
@@ -91,9 +91,9 @@ class JsonApiActivity : AppCompatActivity() {
}
})
}
/* ANCHOR_END: getEarthquakes */
// # --8<-- [end:getEarthquakes]
/* ANCHOR: addMarkers */
// # --8<-- [start:addMarkers]
private fun addMarkersToMap(data: FeatureCollection) {
val bounds = mutableListOf<LatLng>()
@@ -146,7 +146,7 @@ class JsonApiActivity : AppCompatActivity() {
maplibreMap.cameraPosition = newCameraPosition
}
}
/* ANCHOR_END: addMarkers */
// # --8<-- [end:addMarkers]
override fun onStart() {
super.onStart()

View File

@@ -110,6 +110,7 @@ class GestureDetectorActivity : AppCompatActivity() {
}
fun attachListeners() {
// # --8<-- [start:addOnMoveListener]
maplibreMap.addOnMoveListener(
object : OnMoveListener {
override fun onMoveBegin(detector: MoveGestureDetector) {
@@ -132,6 +133,7 @@ class GestureDetectorActivity : AppCompatActivity() {
}
}
)
// # --8<-- [end:addOnMoveListener]
maplibreMap.addOnRotateListener(
object : OnRotateListener {
override fun onRotateBegin(detector: RotateGestureDetector) {

View File

@@ -52,6 +52,7 @@ class LatLngBoundsActivity : AppCompatActivity() {
binding.mapView.getMapAsync { map ->
maplibreMap = map
// # --8<-- [start:featureCollection]
val featureCollection: FeatureCollection =
fromJson(GeoParseUtil.loadStringFromAssets(this, "points-sf.geojson"))
bounds = createBounds(featureCollection)
@@ -59,6 +60,7 @@ class LatLngBoundsActivity : AppCompatActivity() {
map.getCameraForLatLngBounds(bounds, createPadding(peekHeight))?.let {
map.cameraPosition = it
}
// # --8<-- [end:featureCollection]
try {
loadStyle(featureCollection)
@@ -71,7 +73,7 @@ class LatLngBoundsActivity : AppCompatActivity() {
private fun loadStyle(featureCollection: FeatureCollection) {
maplibreMap.setStyle(
Style.Builder()
.fromUri(TestStyles.getPredefinedStyleWithFallback("Streets"))
.fromUri(TestStyles.VERSATILES)
.withLayer(
SymbolLayer("symbol", "symbol")
.withProperties(
@@ -129,6 +131,7 @@ class LatLngBoundsActivity : AppCompatActivity() {
return intArrayOf(additionalPadding, additionalPadding, additionalPadding, bottomPadding)
}
// # --8<-- [start:createBounds]
private fun createBounds(featureCollection: FeatureCollection): LatLngBounds {
val boundsBuilder = LatLngBounds.Builder()
featureCollection.features()?.let {
@@ -139,6 +142,7 @@ class LatLngBoundsActivity : AppCompatActivity() {
}
return boundsBuilder.build()
}
// # --8<-- [end:createBounds]
override fun onStart() {
super.onStart()

View File

@@ -21,7 +21,7 @@ import org.maplibre.android.maps.Style
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
/* ANCHOR: top */
// # --8<-- [start:top]
/**
* This activity shows a basic usage of the LocationComponent's pulsing circle. There's no
* customization of the pulsing circle's color, radius, speed, etc.
@@ -43,9 +43,9 @@ class BasicLocationPulsingCircleActivity : AppCompatActivity(), OnMapReadyCallba
mapView.onCreate(savedInstanceState)
checkPermissions()
}
/* ANCHOR_END: top */
// # --8<-- [end:top]
/* ANCHOR: onMapReady */
// # --8<-- [start:onMapReady]
@SuppressLint("MissingPermission")
override fun onMapReady(maplibreMap: MapLibreMap) {
this.maplibreMap = maplibreMap
@@ -63,9 +63,9 @@ class BasicLocationPulsingCircleActivity : AppCompatActivity(), OnMapReadyCallba
locationComponent!!.forceLocationUpdate(lastLocation)
}
}
/* ANCHOR_END: onMapReady */
// # --8<-- [end:onMapReady]
/* ANCHOR: LocationComponentActivationOptions */
// # --8<-- [start:LocationComponentActivationOptions]
private fun buildLocationComponentActivationOptions(
style: Style,
locationComponentOptions: LocationComponentOptions
@@ -82,7 +82,7 @@ class BasicLocationPulsingCircleActivity : AppCompatActivity(), OnMapReadyCallba
)
.build()
}
/* ANCHOR_END: LocationComponentActivationOptions */
// # --8<-- [end:LocationComponentActivationOptions]
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_pulsing_location_mode, menu)
@@ -136,7 +136,7 @@ class BasicLocationPulsingCircleActivity : AppCompatActivity(), OnMapReadyCallba
maplibreMap.setStyle(Style.Builder().fromUri(Utils.nextStyle()))
}
/* ANCHOR: permission */
// # --8<-- [start:permission]
private fun checkPermissions() {
if (PermissionsManager.areLocationPermissionsGranted(this)) {
mapView.getMapAsync(this)
@@ -170,7 +170,7 @@ class BasicLocationPulsingCircleActivity : AppCompatActivity(), OnMapReadyCallba
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionsManager!!.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
/* ANCHOR_END: permission */
// # --8<-- [end:permission]
override fun onStart() {
super.onStart()

View File

@@ -22,10 +22,8 @@ import org.maplibre.android.snapshotter.MapSnapshotter
import org.maplibre.android.style.expressions.Expression
import org.maplibre.android.style.layers.Property
import org.maplibre.android.style.layers.PropertyFactory
import org.maplibre.android.style.layers.RasterLayer
import org.maplibre.android.style.layers.SymbolLayer
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.android.style.sources.RasterSource
import org.maplibre.android.style.sources.Source
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
@@ -35,11 +33,13 @@ import java.util.Objects
import java.util.Random
/**
* Test activity showing how to use a the [com.mapbox.mapboxsdk.snapshotter.MapSnapshotter]
* Test activity showing how to use the [MapSnapshotter]
*/
class MapSnapshotterActivity : AppCompatActivity() {
lateinit var grid: GridLayout
private val snapshotters: MutableList<MapSnapshotter> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map_snapshotter)
@@ -47,81 +47,81 @@ class MapSnapshotterActivity : AppCompatActivity() {
// Find the grid view and start snapshotting as soon
// as the view is measured
grid = findViewById(R.id.snapshot_grid)
grid.getViewTreeObserver()
.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
grid.getViewTreeObserver().removeOnGlobalLayoutListener(this)
addSnapshots()
}
})
grid.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
grid.viewTreeObserver.removeOnGlobalLayoutListener(this)
addSnapshots()
}
})
}
private fun addSnapshots() {
Timber.i("Creating snapshotters")
for (row in 0 until grid.rowCount) {
for (column in 0 until grid.columnCount) {
startSnapShot(row, column)
startSnapshot(row, column)
}
}
}
private fun startSnapShot(row: Int, column: Int) {
// Optionally the style
val builder = Style.Builder()
.fromUri(
TestStyles.getPredefinedStyleWithFallback(
if ((column + row) % 2 == 0) {
"Streets"
} else {
"Pastel"
}
)
)
// # --8<-- [start:startSnapshot]
private fun startSnapshot(row: Int, column: Int) {
// # --8<-- [start:styleBuilder]
val styles = arrayOf(
TestStyles.DEMOTILES,
TestStyles.AMERICANA,
TestStyles.OPENFREEMAP_LIBERY,
TestStyles.AWS_OPEN_DATA_STANDARD_LIGHT,
TestStyles.PROTOMAPS_LIGHT,
TestStyles.PROTOMAPS_DARK,
TestStyles.PROTOMAPS_WHITE,
TestStyles.PROTOMAPS_GRAYSCALE,
TestStyles.VERSATILES
)
val builder = Style.Builder().fromUri(
styles[(row * grid.rowCount + column) % styles.size]
)
// # --8<-- [end:styleBuilder]
// Define the dimensions
// # --8<-- [start:mapSnapShotterOptions]
val options = MapSnapshotter.Options(
grid.measuredWidth / grid.columnCount,
grid.measuredHeight / grid.rowCount
) // Optionally the pixel ratio
)
.withPixelRatio(1f)
.withLocalIdeographFontFamily(MapLibreConstants.DEFAULT_FONT)
// # --8<-- [end:mapSnapShotterOptions]
// Optionally the visible region
// # --8<-- [start:setRegion]
if (row % 2 == 0) {
options.withRegion(
LatLngBounds.Builder()
.include(
LatLng(
randomInRange(-80f, 80f).toDouble(),
randomInRange(-160f, 160f)
.toDouble()
randomInRange(-160f, 160f).toDouble()
)
)
.include(
LatLng(
randomInRange(-80f, 80f).toDouble(),
randomInRange(-160f, 160f)
.toDouble()
randomInRange(-160f, 160f).toDouble()
)
)
.build()
)
}
// # --8<-- [end:setRegion]
// Optionally the camera options
// # --8<-- [start:setCameraPosition]
if (column % 2 == 0) {
options.withCameraPosition(
CameraPosition.Builder()
.target(
if (options.region != null) {
options.region!!.center
} else {
LatLng(
randomInRange(-80f, 80f).toDouble(),
randomInRange(-160f, 160f)
.toDouble()
)
}
options.region?.center ?: LatLng(
randomInRange(-80f, 80f).toDouble(),
randomInRange(-160f, 160f).toDouble()
)
)
.bearing(randomInRange(0f, 360f).toDouble())
.tilt(randomInRange(0f, 60f).toDouble())
@@ -130,21 +130,15 @@ class MapSnapshotterActivity : AppCompatActivity() {
.build()
)
}
if (row == 0 && column == 0) {
// Add a source
val source: Source =
RasterSource("my-raster-source", "maptiler://sources/satellite", 512)
builder.withLayerAbove(
RasterLayer("satellite-layer", "my-raster-source"),
"country_1"
)
builder.withSource(source)
} else if (row == 0 && column == 2) {
// # --8<-- [end:setCameraPosition]
// # --8<-- [start:addMarkerLayer]
if (row == 0 && column == 2) {
val carBitmap = BitmapUtils.getBitmapFromDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_directions_car_black, theme)
)
// marker source
// Marker source
val markerCollection = FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
@@ -159,7 +153,7 @@ class MapSnapshotterActivity : AppCompatActivity() {
)
val markerSource: Source = GeoJsonSource(MARKER_SOURCE, markerCollection)
// marker layer
// Marker layer
val markerSymbolLayer = SymbolLayer(MARKER_LAYER, MARKER_SOURCE)
.withProperties(
PropertyFactory.iconImage(Expression.get(TITLE_FEATURE_PROPERTY)),
@@ -167,11 +161,7 @@ class MapSnapshotterActivity : AppCompatActivity() {
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconSize(
Expression.switchCase(
Expression.toBool(
Expression.get(
SELECTED_FEATURE_PROPERTY
)
),
Expression.toBool(Expression.get(SELECTED_FEATURE_PROPERTY)),
Expression.literal(1.5f),
Expression.literal(1.0f)
)
@@ -187,16 +177,16 @@ class MapSnapshotterActivity : AppCompatActivity() {
.withCameraPosition(
CameraPosition.Builder()
.target(
LatLng(
5.537109374999999,
52.07950600379697
)
LatLng(5.537109374999999, 52.07950600379697)
)
.zoom(1.0)
.padding(1.0, 1.0, 1.0, 1.0)
.build()
)
}
// # --8<-- [end:addMarkerLayer]
// # --8<-- [start:startSnapshotter]
options.withStyleBuilder(builder)
val snapshotter = MapSnapshotter(this@MapSnapshotterActivity, options)
@@ -206,8 +196,9 @@ class MapSnapshotterActivity : AppCompatActivity() {
}
override fun onStyleImageMissing(imageName: String) {
val androidIcon =
BitmapUtils.getBitmapFromDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_android_2, theme))
val androidIcon = BitmapUtils.getBitmapFromDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_android_2, theme)
)
snapshotter.addImage(imageName, androidIcon!!, false)
}
})
@@ -225,11 +216,10 @@ class MapSnapshotterActivity : AppCompatActivity() {
}
}
)
snapshotters.add(snapshotter)
}
public override fun onPause() {
override fun onPause() {
super.onPause()
// Make sure to stop the snapshotters on pause
@@ -240,11 +230,11 @@ class MapSnapshotterActivity : AppCompatActivity() {
}
private fun featureProperties(id: String, title: String): JsonObject {
val `object` = JsonObject()
`object`.add(ID_FEATURE_PROPERTY, JsonPrimitive(id))
`object`.add(TITLE_FEATURE_PROPERTY, JsonPrimitive(title))
`object`.add(SELECTED_FEATURE_PROPERTY, JsonPrimitive(false))
return `object`
val jsonObject = JsonObject()
jsonObject.add(ID_FEATURE_PROPERTY, JsonPrimitive(id))
jsonObject.add(TITLE_FEATURE_PROPERTY, JsonPrimitive(title))
jsonObject.add(SELECTED_FEATURE_PROPERTY, JsonPrimitive(false))
return jsonObject
}
companion object {
@@ -252,7 +242,7 @@ class MapSnapshotterActivity : AppCompatActivity() {
private const val SELECTED_FEATURE_PROPERTY = "selected"
private const val TITLE_FEATURE_PROPERTY = "title"
// layer & source constants
// Layer & source constants
private const val MARKER_SOURCE = "marker-source"
private const val MARKER_LAYER = "marker-layer"
private val random = Random()
@@ -260,4 +250,4 @@ class MapSnapshotterActivity : AppCompatActivity() {
return random.nextFloat() * (max - min) + min
}
}
}
}

View File

@@ -50,7 +50,7 @@ class MapSnapshotterBitMapOverlayActivity :
Math.min(container.measuredHeight, 1024)
)
.withStyleBuilder(
Style.Builder().fromUri(TestStyles.getPredefinedStyleWithFallback("Outdoor"))
Style.Builder().fromUri(TestStyles.AMERICANA)
)
.withCameraPosition(
CameraPosition.Builder().target(LatLng(52.090737, 5.121420))

View File

@@ -23,10 +23,15 @@ import java.net.URI
import java.net.URISyntaxException
/**
* Test activity showing how to use a the [MapSnapshotter] and heatmap layer on it.
* Test activity showing how to use the [MapSnapshotter] and heatmap layer on it.
*/
// # --8<-- [start:class_declaration]
class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.SnapshotReadyCallback {
// # --8<-- [end:class_declaration]
private var mapSnapshotter: MapSnapshotter? = null
// # --8<-- [start:onCreate]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map_snapshotter_marker)
@@ -36,9 +41,9 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
override fun onGlobalLayout() {
container.viewTreeObserver.removeOnGlobalLayoutListener(this)
Timber.i("Starting snapshot")
val builder = Style.Builder().fromUri(TestStyles.getPredefinedStyleWithFallback("Outdoor"))
val builder = Style.Builder().fromUri(TestStyles.AMERICANA)
.withSource(earthquakeSource!!)
.withLayerAbove(heatmapLayer, "water_intermittent")
.withLayerAbove(heatmapLayer, "water")
mapSnapshotter = MapSnapshotter(
applicationContext,
MapSnapshotter.Options(container.measuredWidth, container.measuredHeight)
@@ -55,23 +60,15 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
}
})
}
// # --8<-- [end:onCreate]
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparency color
// to create a blur-like effect.
// Increase the heatmap weight based on frequency and property magnitude
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
// Adjust the heatmap radius by zoom level
// Transition from heatmap to circle layer by zoom level
// # --8<-- [start:heatmapLayer]
private val heatmapLayer: HeatmapLayer
private get() {
get() {
val layer = HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID)
layer.maxZoom = 9f
layer.sourceLayer = HEATMAP_LAYER_SOURCE
layer.setProperties( // Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparency color
// to create a blur-like effect.
layer.setProperties(
PropertyFactory.heatmapColor(
Expression.interpolate(
Expression.linear(), Expression.heatmapDensity(),
@@ -82,7 +79,7 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
Expression.literal(0.8), Expression.rgb(239, 138, 98),
Expression.literal(1), Expression.rgb(178, 24, 43)
)
), // Increase the heatmap weight based on frequency and property magnitude
),
PropertyFactory.heatmapWeight(
Expression.interpolate(
Expression.linear(),
@@ -90,8 +87,7 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
Expression.stop(0, 0),
Expression.stop(6, 1)
)
), // Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
),
PropertyFactory.heatmapIntensity(
Expression.interpolate(
Expression.linear(),
@@ -99,7 +95,7 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
Expression.stop(0, 1),
Expression.stop(9, 3)
)
), // Adjust the heatmap radius by zoom level
),
PropertyFactory.heatmapRadius(
Expression.interpolate(
Expression.linear(),
@@ -107,7 +103,7 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
Expression.stop(0, 2),
Expression.stop(9, 20)
)
), // Transition from heatmap to circle layer by zoom level
),
PropertyFactory.heatmapOpacity(
Expression.interpolate(
Expression.linear(),
@@ -119,33 +115,40 @@ class MapSnapshotterHeatMapActivity : AppCompatActivity(), MapSnapshotter.Snapsh
)
return layer
}
// # --8<-- [end:heatmapLayer]
override fun onStop() {
super.onStop()
mapSnapshotter!!.cancel()
}
// # --8<-- [start:onSnapshotReady]
@SuppressLint("ClickableViewAccessibility")
override fun onSnapshotReady(snapshot: MapSnapshot) {
Timber.i("Snapshot ready")
val imageView = findViewById<ImageView>(R.id.snapshot_image)
imageView.setImageBitmap(snapshot.bitmap)
}
// # --8<-- [end:onSnapshotReady]
// # --8<-- [start:onStop]
override fun onStop() {
super.onStop()
mapSnapshotter?.cancel()
}
// # --8<-- [end:onStop]
// # --8<-- [start:earthquakeSource]
private val earthquakeSource: Source?
private get() {
get() {
var source: Source? = null
try {
source = GeoJsonSource(EARTHQUAKE_SOURCE_ID, URI(EARTHQUAKE_SOURCE_URL))
} catch (uriSyntaxException: URISyntaxException) {
Timber.e(uriSyntaxException, "That's not an url... ")
Timber.e(uriSyntaxException, "That's not a valid URL.")
}
return source
}
// # --8<-- [end:earthquakeSource]
companion object {
private const val EARTHQUAKE_SOURCE_URL =
"https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
"https://maplibre.org/maplibre-gl-js/docs/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"

View File

@@ -5,6 +5,7 @@ import android.view.View
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import okio.ByteString.Companion.readByteString
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.Style
@@ -19,8 +20,15 @@ import java.lang.RuntimeException
/**
* Test activity showing how to use a the MapSnapshotter with a local style
*/
class MapSnapshotterLocalStyleActivity : AppCompatActivity(), MapSnapshotter.SnapshotReadyCallback {
private var mapSnapshotter: MapSnapshotter? = null
class MapSnapshotterLocalStyleActivity : AppCompatActivity() {
private lateinit var mapSnapshotter: MapSnapshotter
companion object {
private const val LATITUDE = 52.090737
private const val LONGITUDE = 5.121420
private const val ZOOM = 2.0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map_snapshotter_marker)
@@ -29,48 +37,39 @@ class MapSnapshotterLocalStyleActivity : AppCompatActivity(), MapSnapshotter.Sna
.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
container.viewTreeObserver.removeOnGlobalLayoutListener(this)
val styleJson: String
styleJson = try {
ResourceUtils.readRawResource(
this@MapSnapshotterLocalStyleActivity,
R.raw.sat_style
)
} catch (exception: IOException) {
throw RuntimeException(exception)
}
// # --8<-- [start:readStyleJson]
val styleJson = resources.openRawResource(R.raw.demotiles).reader().readText()
// # --8<-- [end:readStyleJson]
Timber.i("Starting snapshot")
// # --8<-- [start:createMapSnapshotter]
mapSnapshotter = MapSnapshotter(
applicationContext,
MapSnapshotter.Options(
Math.min(container.measuredWidth, 1024),
Math.min(container.measuredHeight, 1024)
container.measuredWidth.coerceAtMost(1024),
container.measuredHeight.coerceAtMost(1024)
)
.withStyleBuilder(Style.Builder().fromJson(styleJson))
.withCameraPosition(
CameraPosition.Builder().target(LatLng(52.090737, 5.121420))
.zoom(18.0).build()
CameraPosition.Builder().target(LatLng(LATITUDE, LONGITUDE))
.zoom(ZOOM).build()
)
)
mapSnapshotter!!.start(
this@MapSnapshotterLocalStyleActivity,
object : MapSnapshotter.ErrorHandler {
override fun onError(error: String) {
Timber.e(error)
}
}
)
// # --8<-- [end:createMapSnapshotter]
// # --8<-- [start:createSnapshot]
mapSnapshotter.start({ snapshot ->
Timber.i("Snapshot ready")
val imageView = findViewById<View>(R.id.snapshot_image) as ImageView
imageView.setImageBitmap(snapshot.bitmap)
}) { error -> Timber.e(error )}
// # --8<-- [end:createSnapshot]
}
})
}
override fun onStop() {
super.onStop()
mapSnapshotter!!.cancel()
}
override fun onSnapshotReady(snapshot: MapSnapshot) {
Timber.i("Snapshot ready")
val imageView = findViewById<View>(R.id.snapshot_image) as ImageView
imageView.setImageBitmap(snapshot.bitmap)
if (this::mapSnapshotter.isInitialized) {
mapSnapshotter.cancel()
}
}
}

View File

@@ -202,8 +202,8 @@ class RuntimeStyleActivity : AppCompatActivity() {
true
}
R.id.action_add_satellite_layer -> {
addSatelliteLayer()
R.id.action_add_raster_source_layer -> {
addRasterSourceLayer()
true
}
@@ -501,13 +501,14 @@ class RuntimeStyleActivity : AppCompatActivity() {
).show()
}
private fun addSatelliteLayer() {
// Add a source
val source: Source = RasterSource("my-raster-source", "maptiler://sources/satellite", 512)
maplibreMap.style!!.addSource(source)
private fun addRasterSourceLayer() {
// Take note of Tile Usage Policy https://operations.osmfoundation.org/policies/tiles/
val osmTileSet = TileSet("osm", "https://tile.openstreetmap.org/{z}/{x}/{y}.png")
osmTileSet.attribution = "OSM Contributors"
val source = RasterSource("osm", osmTileSet, 128)
val layer = RasterLayer("osm-layer", "osm")
// Add a layer
maplibreMap.style!!.addLayer(RasterLayer("satellite-layer", "my-raster-source"))
maplibreMap.setStyle(Style.Builder().withSource(source).withLayer(layer))
}
private fun updateWaterColorOnZoom() {

View File

@@ -75,7 +75,7 @@ class StyleFileActivity : AppCompatActivity() {
*/
private fun loadStyleFileTask(context: Context): String =
try {
ResourceUtils.readRawResource(context, R.raw.sat_style)
ResourceUtils.readRawResource(context, R.raw.demotiles)
} catch (exception: Exception) {
Timber.e(exception, "Can't load local file style")
""

View File

@@ -3,10 +3,27 @@ package org.maplibre.android.testapp.styles
import org.maplibre.android.maps.Style
object TestStyles {
val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json"
const val DEMOTILES = "https://demotiles.maplibre.org/style.json"
val AMERICANA = "https://americanamap.org/style.json"
const val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json"
const val AMERICANA = "https://americanamap.org/style.json"
const val OPENFREEMAP_LIBERY = "https://tiles.openfreemap.org/styles/liberty"
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 {
return "https://api.protomaps.com/styles/v2/${style}.json?key=e761cc7daedf832a"
}
val PROTOMAPS_LIGHT = protomaps("light")
val PROTOMAPS_DARK = protomaps("dark")
val PROTOMAPS_GRAYSCALE = protomaps("grayscale")
val PROTOMAPS_WHITE = protomaps("white")
fun getPredefinedStyleWithFallback(name: String): String {
try {
val style = Style.getPredefinedStyle(name)

View File

@@ -43,8 +43,8 @@
android:title="@string/add_a_terrain_layer"
maplibre:showAsAction="never" />
<item
android:id="@+id/action_add_satellite_layer"
android:title="@string/add_a_satellite_layer"
android:id="@+id/action_add_raster_source_layer"
android:title="@string/set_raster_style"
maplibre:showAsAction="never" />
<item
android:id="@+id/action_update_water_color_on_zoom"

File diff suppressed because it is too large Load Diff

View File

@@ -1,766 +0,0 @@
{
"id": "hybrid",
"name": "Satellite Hybrid",
"zoom": 1,
"pitch": 0,
"center": [
0,
0
],
"glyphs": "maptiler://fonts/{fontstack}/{range}.pbf",
"layers": [
{
"id": "satellite",
"type": "raster",
"paint": {
"raster-opacity": 1
},
"filter": [
"all"
],
"layout": {
"visibility": "visible"
},
"source": "satellite",
"minzoom": 0
},
{
"id": "tunnel",
"type": "line",
"paint": {
"line-color": "rgba(255, 255, 255, 0.2)",
"line-width": [
"interpolate",
[
"exponential",
1.5
],
[
"zoom"
],
6,
0.5,
20,
30
],
"line-dasharray": [
0.28,
0.14
]
},
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"brunnel",
"tunnel"
],
[
"in",
"class",
"motorway",
"primary",
"secondary",
"tertiary",
"trunk"
]
],
"layout": {
"line-cap": "butt",
"line-join": "miter"
},
"source": "openmaptiles",
"source-layer": "transportation"
},
{
"id": "path",
"type": "line",
"paint": {
"line-color": "rgba(247, 247, 247, 0.33)",
"line-width": [
"interpolate",
[
"exponential",
1.5
],
[
"zoom"
],
14,
0.5,
20,
4
],
"line-dasharray": [
1,
1
]
},
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"in",
"class",
"path",
"track"
]
],
"layout": {
"line-cap": "square",
"line-join": "bevel"
},
"source": "openmaptiles",
"source-layer": "transportation"
},
{
"id": "road",
"type": "line",
"paint": {
"line-color": [
"interpolate",
[
"linear"
],
[
"zoom"
],
8,
"rgba(255, 255, 255, 0.2)",
14,
"rgba(255, 255, 255, 0.3)"
],
"line-width": [
"interpolate",
[
"linear"
],
[
"zoom"
],
5,
[
"match",
[
"get",
"class"
],
[
"motorway",
"motorway_link"
],
1,
0
],
7,
[
"match",
[
"get",
"class"
],
[
"motorway",
"motorway_link"
],
1.4,
0
],
8,
[
"match",
[
"get",
"class"
],
[
"motorway",
"motorway_link",
"primary",
"trunk"
],
0.75,
0
],
9,
[
"match",
[
"get",
"class"
],
[
"secondary",
"tertiary"
],
0.7,
1
],
10,
[
"match",
[
"get",
"class"
],
[
"motorway",
"motorway_link"
],
1.3,
1.3
],
14,
[
"match",
[
"get",
"class"
],
[
"minor",
"service"
],
0.5,
2.4
]
]
},
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"!in",
"class",
"rail",
"ferry",
"path",
"track"
],
[
"!=",
"brunnel",
"tunnel"
]
],
"layout": {
"line-cap": "butt",
"line-join": "round"
},
"source": "openmaptiles",
"minzoom": 6,
"source-layer": "transportation"
},
{
"id": "railway",
"type": "line",
"paint": {
"line-color": "rgba(179, 170, 158, 0.2)",
"line-opacity": [
"interpolate",
[
"linear"
],
[
"zoom"
],
11,
0.5,
16,
1.3
]
},
"filter": [
"==",
"class",
"rail"
],
"layout": {
"visibility": "visible"
},
"source": "openmaptiles",
"minzoom": 11,
"source-layer": "transportation"
},
{
"id": "admin_sub",
"type": "line",
"paint": {
"line-color": "rgba(194, 194, 194, 0.5)",
"line-dasharray": [
2,
1
]
},
"filter": [
"in",
"admin_level",
4,
6,
8
],
"layout": {
"visibility": "visible"
},
"source": "openmaptiles",
"minzoom": 3,
"source-layer": "boundary"
},
{
"id": "admin_country-dark",
"type": "line",
"paint": {
"line-color": "rgba(0, 0, 0, 0.51)",
"line-width": [
"interpolate",
[
"exponential",
1.5
],
[
"zoom"
],
3,
0.5,
21,
32
],
"line-offset": 1
},
"filter": [
"all",
[
"<=",
"admin_level",
2
],
[
"==",
"$type",
"LineString"
]
],
"layout": {
"line-cap": "butt",
"line-join": "round",
"visibility": "visible"
},
"source": "openmaptiles",
"source-layer": "boundary"
},
{
"id": "admin_country",
"type": "line",
"paint": {
"line-color": "rgba(226, 226, 226, 1)",
"line-width": [
"interpolate",
[
"exponential",
1.5
],
[
"zoom"
],
3,
0.5,
21,
32
]
},
"filter": [
"all",
[
"<=",
"admin_level",
2
],
[
"==",
"$type",
"LineString"
]
],
"layout": {
"line-cap": "round",
"line-join": "round",
"visibility": "visible"
},
"source": "openmaptiles",
"source-layer": "boundary"
},
{
"id": "road_label",
"type": "symbol",
"paint": {
"text-color": "rgba(255, 255, 255, 1)",
"text-halo-color": "rgba(43, 43, 43, 1)",
"text-halo-width": 1
},
"filter": [
"==",
"$type",
"LineString"
],
"layout": {
"text-font": [
"Noto Sans Regular"
],
"text-size": [
"interpolate",
[
"cubic-bezier",
0.75,
1,
0.75,
1
],
[
"zoom"
],
10,
8,
15,
9
],
"text-field": "{name:latin} {name:nonlatin}",
"text-transform": "none",
"symbol-placement": "line",
"text-letter-spacing": 0.1,
"text-rotation-alignment": "map"
},
"source": "openmaptiles",
"source-layer": "transportation_name"
},
{
"id": "place_label",
"type": "symbol",
"paint": {
"text-color": "rgba(255, 255, 255, 1)",
"text-halo-blur": 0.5,
"text-halo-color": [
"match",
[
"get",
"class"
],
[
"city"
],
"rgba(0, 0, 0, 0.75)",
"rgba(43, 43, 43, 1)"
],
"text-halo-width": 1
},
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"!in",
"class",
"country",
"state"
]
],
"layout": {
"text-font": [
"Noto Sans Regular"
],
"text-size": [
"interpolate",
[
"cubic-bezier",
0.5,
1,
0.5,
1
],
[
"zoom"
],
3,
[
"match",
[
"get",
"class"
],
"city",
11,
10
],
6,
[
"match",
[
"get",
"class"
],
"city",
14.5,
11
],
8,
[
"match",
[
"get",
"class"
],
"city",
16,
12
],
10,
[
"match",
[
"get",
"class"
],
"city",
20,
13
]
],
"text-field": "{name:latin}\n{name:nonlatin}",
"text-max-width": 10
},
"source": "openmaptiles",
"maxzoom": 16,
"minzoom": 3,
"source-layer": "place"
},
{
"id": "state_label",
"type": "symbol",
"paint": {
"text-color": "rgba(255, 255, 255, 1)",
"text-halo-blur": 1,
"text-halo-color": "rgba(0, 0, 0, 1)",
"text-halo-width": 1
},
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"in",
"class",
"state"
]
],
"layout": {
"text-font": [
"Noto Sans Italic"
],
"text-size": [
"interpolate",
[
"cubic-bezier",
0.75,
1,
0.75,
1
],
[
"zoom"
],
1,
[
"step",
[
"get",
"rank"
],
13,
1,
12,
2,
12
],
4,
[
"step",
[
"get",
"rank"
],
15,
1,
14,
2,
14
],
6,
[
"step",
[
"get",
"rank"
],
23,
1,
18,
2,
18
],
9,
[
"step",
[
"get",
"rank"
],
27,
1,
22,
2,
22
]
],
"text-field": "{name:latin}",
"text-max-width": 10
},
"source": "openmaptiles",
"maxzoom": 12,
"minzoom": 3,
"source-layer": "place"
},
{
"id": "country_label",
"type": "symbol",
"paint": {
"text-color": "rgba(255, 255, 255, 1)",
"text-halo-blur": 1,
"text-halo-color": "rgba(0, 0, 0, 1)",
"text-halo-width": 1
},
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"in",
"class",
"country"
]
],
"layout": {
"text-font": [
"Noto Sans Bold"
],
"text-size": [
"interpolate",
[
"cubic-bezier",
0.75,
1,
0.75,
1
],
[
"zoom"
],
1,
[
"step",
[
"get",
"rank"
],
13,
1,
12,
2,
12
],
4,
[
"step",
[
"get",
"rank"
],
15,
1,
14,
2,
14
],
6,
[
"step",
[
"get",
"rank"
],
23,
1,
18,
2,
18
],
9,
[
"step",
[
"get",
"rank"
],
27,
1,
22,
2,
22
]
],
"text-field": "{name:latin}",
"text-max-width": 10
},
"source": "openmaptiles",
"maxzoom": 12,
"source-layer": "place"
}
],
"bearing": 0,
"sources": {
"satellite": {
"url": "maptiler://sources/satellite",
"type": "raster",
"tileSize": 256
},
"openmaptiles": {
"url": "maptiler://sources/v3",
"type": "vector"
}
},
"version": 8,
"metadata": {
"maputnik:renderer": "mbgljs",
"openmaptiles:version": "3.x"
}
}

View File

@@ -73,7 +73,7 @@
<string name="add_a_dynamic_geojson_source">Add a dynamic GeoJSON source</string>
<string name="remove_buildings_layer">Remove buildings layer</string>
<string name="add_a_terrain_layer">Add a terrain layer</string>
<string name="add_a_satellite_layer">Add a satellite layer</string>
<string name="set_raster_style">Set style with raster layer</string>
<string name="change_the_water_color_on_zoom">Change the water color on zoom</string>
<string name="custom_tiles">Custom tiles</string>
<string name="apply_filtered_fill">Apply filtered fill</string>

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

@@ -0,0 +1,13 @@
# Add Markers in Bulk
This example demonstrates how you can add markers in bulk.
<div style="display: flex; width: 100%; justify-content: center; gap: 1em;">
<img src="https://github.com/user-attachments/assets/69f3f7fd-1778-4e2a-afa9-df3b01ab80c8" alt="100 images on map" width="300px"/>
<img src="https://github.com/user-attachments/assets/ea75a2ee-bb86-4ec0-ba54-7e328699d806" alt="1000 images on map" width="300px">
</div>
```kotlin title="BulkMarkerActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/BulkMarkerActivity.kt"
```

View File

@@ -0,0 +1,83 @@
# Annotation: Marker
This guide will show you how to add Markers in the map.
`Annotation` is an overlay on top of a Map. In package
`org.maplibre.android.annotations`, it has the following subclasses:
1. [Marker]
2. [Polyline]
3. [Polygon]
A Marker shows an icon image at a geographical location. By default, marker uses
a [provided image] as its icon.
![marker image]
Or, the icon can be customized using [IconFactory] to generate an
[Icon] using a provided image.
For more customization, please read the documentation about [MarkerOptions].
In this showcase, we continue the code from the [Quickstart],
rename Activity into `JsonApiActivity`,
and pull the GeoJSON data from a free and public API.
Then add markers to the map with GeoJSON:
1. In your module Gradle file (usually `<project>/<app-module>/build.gradle`), add `okhttp` to simplify code for making HTTP requests.
```gradle
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
...
}
```
2. Sync your Android project the with Gradle files.
3. In `JsonApiActivity` we add a new variable for `MapLibreMap`.
It is used to add annotations to the map instance.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:top"
```
4. Call `mapview.getMapSync()` in order to get a `MapLibreMap` object.
After `maplibreMap` is assigned, call the `getEarthQuakeDataFromUSGS()` method
to make a HTTP request and transform data into the map annotations.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:mapAsync"
```
5. Define a function `getEarthQuakeDataFromUSGS()` to fetch GeoJSON data from a public API.
If we successfully get the response, call `addMarkersToMap()` on the UI thread.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:getEarthquakes"
```
6. Now it is time to add markers into the map.
- In the `addMarkersToMap()` method, we define two types of bitmap for the marker icon.
- For each feature in the GeoJSON, add a marker with a snippet about earthquake details.
- If the magnitude of an earthquake is bigger than 6.0, we use the red icon. Otherwise, we use the blue one.
- Finally, move the camera to the bounds of the newly added markers
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:addMarkers"
```
7. Here is the final result. For the full contents of `JsonApiActivity`, please visit source code of our [Test App].
<div style="align: center">
<img src="https://github.com/maplibre/maplibre-native/assets/19887090/00446249-9b19-4a48-8a46-00d4c5a2f981" alt="Screenshot with the map in demotile style">
</div>
[Marker]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-marker/index.html
[provided image]: https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroid/src/main/res/drawable-xxxhdpi/maplibre_marker_icon_default.png
[Polyline]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-polyline/index.html
[Polygon]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-polygon/index.html
[marker image]: https://raw.githubusercontent.com/maplibre/maplibre-native/main/test/fixtures/sprites/default_marker.png
[IconFactory]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-icon-factory/index.html
[Icon]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-icon/index.html
[Quickstart]: ../getting-started.md
[mvn]: https://mvnrepository.com/artifact/org.maplibre.gl/android-plugin-annotation-v9
[Android Developer Documentation]: https://developer.android.com/topic/libraries/architecture/coroutines
[MarkerOptions]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.annotations/-marker-options/index.html
[Test App]: https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt

View File

@@ -0,0 +1,18 @@
[data-md-color-scheme="default"] {
--md-primary-fg-color: #295DAA;
--md-accent-fg-color: #568ad6;
}
[data-md-color-scheme="slate"] {
--md-primary-fg-color: #295DAA;
--md-accent-fg-color: #568ad6;
}
.md-nav__title {
display: none;
}
.md-grid {
max-width: 1800px;
}

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="110.14971mm"
height="110.14971mm"
viewBox="0 0 110.14971 110.14971"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:export-filename="maplibreappicon.png"
inkscape:export-xdpi="3.6895239"
inkscape:export-ydpi="3.6895239"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
sodipodi:docname="maplibreappicon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.65575523"
inkscape:cx="16.774552"
inkscape:cy="359.1279"
inkscape:window-width="1392"
inkscape:window-height="1212"
inkscape:window-x="169"
inkscape:window-y="348"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
showgrid="false" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-49.925144,-93.425148)"><rect
style="fill:#295daa;fill-opacity:1;stroke-width:0.264583"
id="rect2"
width="110.14971"
height="110.14971"
x="49.925144"
y="93.425148"
ry="23.5" /><path
fill="#82b4fe"
fill-rule="evenodd"
d="m 105.19551,109.31159 c -13.050838,-0.0847 -23.732067,9.79276 -23.811706,22.0173 -0.07038,10.82464 5.282141,16.10916 11.078368,21.83157 4.040982,3.98939 8.297338,8.1915 11.082068,14.63199 0.18786,0.15266 0.31354,0.25453 0.36751,0.30268 a 1.3663083,1.3663083 0 0 0 0.89906,0.34025 1.368425,1.368425 0 0 0 0.90381,-0.32834 c 0.0145,-0.013 0.0413,-0.0244 0.0741,-0.0384 0.09,-0.0386 0.22675,-0.0974 0.29739,-0.26008 2.77548,-6.37911 7.09242,-10.5246 11.21516,-14.4833 5.87084,-5.63747 11.34745,-10.8966 11.41757,-21.68842 0.0796,-12.22481 -10.47221,-22.24008 -23.52331,-22.32528 z m -0.22754,35.01734 c 6.46218,0.0421 11.73586,-5.24139 11.77845,-11.80094 0.0426,-6.55955 -5.16175,-11.91128 -11.6242,-11.95335 -6.462448,-0.0421 -11.735858,5.2414 -11.778721,11.80095 -0.0426,6.55955 5.161757,11.91127 11.624201,11.95334 z"
clip-rule="evenodd"
id="path3"
style="stroke-width:0.264583" /><path
fill="#ffffff"
fill-rule="evenodd"
d="m 96.509239,172.21893 c -1.078177,-0.007 -1.960033,1.18533 -1.969558,2.66303 l -0.04868,7.4795 c -0.0098,1.4777 0.856457,2.68129 1.934898,2.68843 l 16.506031,0.10743 c 1.07818,0.007 1.96003,-1.18534 1.96956,-2.66304 l 0.0487,-7.4795 c 0.01,-1.47796 -0.85646,-2.68155 -1.9349,-2.68843 z"
clip-rule="evenodd"
id="path4"
style="stroke-width:0.264583" /></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,13 @@
# Animation Types
This example showcases the different animation types.
- **Move**: available via the `MapLibreMap.moveCamera` method.
- **Ease**: available via the `MapLibreMap.easeCamera` method.
- **Animate**: available via the `MapLibreMap.animateCamera` method.
It also shows how to pass callbacks that are called when an animation is cancelled.
```kotlin title="CameraAnimationTypeActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/CameraAnimationTypeActivity.kt"
```

View File

@@ -0,0 +1,7 @@
# Animator Animation
This example showcases how to use the Animator API to schedule a sequence of map animations.
```kotlin title="CameraAnimatorActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/CameraAnimatorActivity.kt"
```

View File

@@ -0,0 +1,7 @@
# CameraPosition Capabilities
This example showcases how to listen to camera change events.
```kotlin title="CameraPositionActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/CameraPositionActivity.kt"
```

View File

@@ -0,0 +1,28 @@
# Gesture Detector
The gesture detector of MapLibre Android is encapsulated in the [`maplibre-gestures-android`](https://github.com/maplibre/maplibre-gestures-android) package.
#### Gesture Listeners
You can add listeners for move, rotate, scale and shove gestures. For example, adding a move gesture listener with `MapLibreMap.addOnRotateListener`:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt:addOnMoveListener"
```
Refer to the full example below for examples of listeners for the other gesture types.
#### Settings
You can access an `UISettings` object via `MapLibreMap.uiSettings`. Available settings include:
- **Toggle Quick Zoom**. You can double tap on the map to use quick zoom. You can toggle this behavior on and off (`UiSettings.isQuickZoomGesturesEnabled`).
- **Toggle Velocity Animations**. By default flicking causes the map to continue panning (while decelerating). You can turn this off with `UiSettings.isScaleVelocityAnimationEnabled`.
- **Toggle Rotate Enabled**. Use `uiSettings.isRotateGesturesEnabled`.
- **Toggle Zoom Enabled**. Use `uiSettings.isZoomGesturesEnabled`.
## Full Example Activity
```kotlin title="GestureDetectorActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt"
```

View File

@@ -0,0 +1,26 @@
# 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/testapp/activity/camera/LatLngBoundsActivity.kt) of the MapLibreAndroidTestApp.
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">
<source src="https://github.com/user-attachments/assets/46a89dfb-3abc-448a-be53-8abe39b2919b" />
</video>
</figure>
Here you can see how the feature collection is loaded and how `MapLibreMap.getCameraForLatLngBounds` is used to set the bounds during map initialization:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt:featureCollection"
```
The `createBounds` function uses the `LatLngBounds` API to include all points within the bounds:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt:createBounds"
```

View File

@@ -0,0 +1,86 @@
# Configuration
This guide will explain various ways to create a map.
When working with maps, you likely want to configure the `MapView`.
There are several ways to build a `MapView`:
1. Using existing XML namespace tags for`MapView` in the layout.
2. Creating `MapLibreMapOptions` and passing builder function values into the `MapView`.
3. Creating a `SupportMapFragment` with the help of `MapLibreMapOptions`.
Before diving into `MapView` configurations, let's understand the capabilities of both XML namespaces and `MapLibreMapOptions`.
Here are some common configurations you can set:
- Map base URI
- Camera settings
- Zoom level
- Pitch
- Gestures
- Compass
- Logo
- Attribution
- Placement of the above elements on the map and more
We will explore how to achieve these configurations in XML layout and programmatically in Activity code, step by step.
### `MapView` Configuration with an XML layout
To configure `MapView` within an XML layout, you need to use the right namespace and provide the necessary data in the layout file.
```xml
--8<-- "MapLibreAndroidTestApp/src/main/res/layout/activity_map_options_xml.xml"
```
This can be found in [`activity_map_options_xml.xml`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/res/layout/activity_map_fragment.xml).
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
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsXmlActivity.kt"
```
This can be found in [`MapOptionsXmlActivity.kt`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsXmlActivity.kt).
### `MapView` configuration with `MapLibreMapOptions`
Here we don't have to create MapView from XML since we want to create it programmatically.
```xml
--8<-- "MapLibreAndroidTestApp/src/main/res/layout/activity_map_options_runtime.xml"
```
This can be found in [`activity_map_options_runtime.xml`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapLibreAndroidTestApp/src/main/res/layout/activity_map_options_runtime.xml).
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"
```
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).
Finally you will see a result similar to this:
<div style="text-align: center">
<img src="https://github.com/user-attachments/assets/dd85f496-3e6f-4788-933e-4ec3d5999935" alt="Screenshot with the MapLibreMapOptions">
</div>
For the full contents of `MapOptionsRuntimeActivity` and `MapOptionsXmlActivity`, please take a look at the source code of [MapLibreAndroidTestApp](https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options).
You can read more about `MapLibreMapOptions` in the [Android API documentation](https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.maps/-map-libre-map-options/index.html?query=open%20class%20MapLibreMapOptions%20:%20Parcelable).
### `SupportMapFragment` with the help of `MapLibreMapOptions`.
If you are using MapFragment in your project, it is also easy to provide initial values to the `newInstance()` static method of `SupportMapFragment`, which requires a `MapLibreMapOptions` parameter.
Let's see how this can be done in a sample activity:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt"
```
You can also find the full contents of `SupportMapFragmentActivity` in the [MapLibreAndroidTestApp](https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt).
To learn more about `SupportMapFragment`, please visit the [Android API documentation](https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.maps/-support-map-fragment/index.html?query=open%20class%20SupportMapFragment%20:%20Fragment,%20OnMapReadyCallback).

View File

@@ -0,0 +1,115 @@
# Quickstart
1. Add bintray Maven repositories to your project-level Gradle file (usually `<project>/<app-module>/build.gradle`).
```gradle
allprojects {
repositories {
...
mavenCentral()
}
}
```
2. Add the library as a dependency into your module Gradle file (usually `<project>/<app-module>/build.gradle`). Replace `<version>` with the [latest MapLibre Android version](https://github.com/maplibre/maplibre-native/releases?q=android-v11&expanded=true) (e.g.: `org.maplibre.gl:android-sdk:11.5.2`):
```gradle
dependencies {
...
implementation 'org.maplibre.gl:android-sdk:<version>'
...
}
```
3. Sync your Android project with Gradle files.
4. Add a `MapView` to your layout XML file (usually `<project>/<app-module>/src/main/res/layout/activity_main.xml`).
```xml
...
<org.maplibre.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
...
```
5. Initialize the `MapView` in your `MainActivity` file by following the example below:
```kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import org.maplibre.android.Maplibre
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.testapp.R
class MainActivity : AppCompatActivity() {
// Declare a variable for MapView
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Init MapLibre
MapLibre.getInstance(this)
// Init layout view
val inflater = LayoutInflater.from(this)
val rootView = inflater.inflate(R.layout.activity_main, null)
setContentView(rootView)
// Init the MapView
mapView = rootView.findViewById(R.id.mapView)
mapView.getMapAsync { map ->
map.setStyle("https://demotiles.maplibre.org/style.json")
map.cameraPosition = CameraPosition.Builder().target(LatLng(0.0,0.0)).zoom(1.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 onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}
```
6. Build and run the app. If you run the app successfully, a map will be displayed as seen in the screenshot below.
<div style="text-align: center;">
<img src="https://user-images.githubusercontent.com/32692818/228113379-475e86f5-e3fa-4a36-8b4b-1fcba0f1eb3b.png" alt="Screenshot with the map in demotile style" width="50%" height="50%">
</div>

View File

@@ -0,0 +1,95 @@
# LocationComponent
This guide will demonstrate how to utilize the [LocationComponent] to represent the user's current location.
When implementing the [LocationComponent], the application should request location permissions. Declare the need for foreground location in the `AndroidManifest.xml` file. For more information, please refer to the [Android Developer Documentation].
```xml
<manifest ... >
<!-- Always include this permission -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Include only if your app benefits from precise location access. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
```
Create a new activity named `BasicLocationPulsingCircleActivity`:
- This Activity should implement the `OnMapReadyCallback` interface. The `onMapReady()` method is triggered when the map is ready to be used.
- Add a variable `permissionsManager` to manage permissions.
- Add a variable `locationComponent` to manage user location.
- At the end of the `onCreate()` method, call `checkPermissions()` to ensure that the application can access the user's location.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:top"
```
In the `checkPermissions()` method, the [PermissionManager] is used to request location permissions at runtime and handle the callbacks for permission granting or rejection.Additionally, you should pass the results of `Activity.onRequestPermissionResult()` to it. If the permissions are granted, call `mapView.getMapAsync(this)` to register the activity as a listener for onMapReady event.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:permission"
```
In the `onMapReady()` method, first set the style and then handle the user's location using the [LocationComponent].
To configure the [LocationComponent], developers should use [LocationComponentOptions].
In this demonstration, we create an instance of this class.
In this method:
- Use the annotation `@SuppressLint("MissingPermission")` to suppress warnings related to missing location access permissions.
- In `setStyle(),` you can utilize other public and token-free styles like [demotiles] instead of the [predefined styles].
- For the builder of [LocationComponentOptions], use `pulseEnabled(true)` to enable the pulse animation, which enhances awareness of the user's location.
- Use method `buildLocationComponentActivationOptions()` to set [LocationComponentActivationOptions], then activate `locatinoComponent` with it.
- To apply options, make sure you call `activateLocationComponent()` of `locationComponent`. You can also set `locationComponent`'s various properties like `isLocationComponentEnabled` , `cameraMode` , etc...
- `CameraMode.TRACKING`[^1] means that when the user's location is updated, the camera will reposition accordingly.
- `locationComponent!!.forceLocationUpdate(lastLocation)` updates the the user's last known location.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:onMapReady"
```
[LocationComponentActivationOptions] is used to hold the style, [LocationComponentOptions] and other locating behaviors.
- It can also be used to configure how to obtain the current location, such as [LocationEngine] and intervals.
- In this demonstration, it sets 750ms as the fastest interval for location updates, providing high accuracy location results (but with higher power consumption).
- For more information, please visit the [documentation page][LocationComponentActivationOptions].
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:LocationComponentActivationOptions"
```
For further customization, you can also utilize the `foregroundTintColor()` and `pulseColor()` methods on the [LocationComponentOptions] builder:
```kotlin
val locationComponentOptions =
LocationComponentOptions.builder(this@BasicLocationPulsingCircleActivity)
.pulseEnabled(true)
.pulseColor(Color.RED) // Set color of pulse
.foregroundTintColor(Color.BLACK) // Set color of user location
.build()
```
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)
[^1]: A variety of [camera modes] determine how the camera will track the user location.
They provide the right context to your users at the correct time.
[LocationComponent]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location/-location-component/index.html
[Android Developer Documentation]: https://developer.android.com/training/location/permissions
[onMapReadyCallback]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.maps/-on-map-ready-callback/index.html
[PermissionManager]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location.permissions/-permissions-manager/index.html
[LocationComponentOptions]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location/-location-component-options/index.html
[demotiles]: https://demotiles.maplibre.org/style.json
[predefined styles]: https://github.com/maplibre/maplibre-native/tree/main/src/mbgl/util/tile_server_options.cpp
[LocationComponentActivationOptions]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location/-location-component-activation-options/index.html
[LocationEngine]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location.engine/-location-engine/index.html
[Test APP]: https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt
[camera modes]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.location.modes/-camera-mode/index.html

View File

@@ -0,0 +1,163 @@
# Using the Snapshotter
This guide will help you walk through how to use [MapSnapshotter](https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.snapshotter/-map-snapshotter/index.html).
## 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.
To get started we will show how to use the map snapshotter with a local style.
<figure markdown="span">
![Map Snapshotter with Local Style](https://github.com/user-attachments/assets/897452c6-52e3-4e58-828c-4f7366b3ba90){ width="300" }
</figure>
Add the [source code of the Demotiles style](https://github.com/maplibre/demotiles/blob/gh-pages/style.json) as `demotiles.json` to the `res/raw` directory of our app[^1]. First we will read this style:
[^1]: See [App resources overview](https://developer.android.com/guide/topics/resources/providing-resources) for this and other ways you can provide resources to your app.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt:readStyleJson"
```
Next, we configure the MapSnapshotter, passing height and width, the style we just read and the camera position:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt:createMapSnapshotter"
```
Lastly we use the `.start()` method to create the snapshot, and pass callbacks for when the snapshot is ready or for when an error occurs.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt:createSnapshot"
```
## 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.
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" }
</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.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt:styleBuilder"
```
Next we create a `MapSnapshotter.Options` object to customize the settings of each snapshot(ter).
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt:mapSnapShotterOptions"
```
For some rows we randomize the visible region of the snapshot:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt:setRegion"
```
For some columns we randomize the camera position:
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt:setCameraPosition"
```
In the last column of the first row we add two bitmaps. See the next example for more details.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt:addMarkerLayer"
```
## 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.
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" }
</figure>
```kotlin title="MapSnapshotterBitMapOverlayActivity.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt"
```
## 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.
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" }
</figure>
First, we create the `MapSnapshotterHeatMapActivity` class, which extends `AppCompatActivity` and implements `MapSnapshotter.SnapshotReadyCallback` to receive the snapshot once it's ready.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:class_declaration"
```
In the `onCreate` method, we set up the layout and initialize the `MapSnapshotter` once the layout is ready.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:onCreate"
```
Here, we wait for the layout to be laid out using an `OnGlobalLayoutListener` before initializing the `MapSnapshotter`. We create a `Style.Builder` with a base style (`TestStyles.AMERICANA`), add the earthquake data source, and add the heatmap layer above the "water" layer.
The `heatmapLayer` property defines the `HeatmapLayer` used to visualize the earthquake data.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:heatmapLayer"
```
This code sets up the heatmap layer's properties, such as color ramp, weight, intensity, radius, and opacity, using expressions that interpolate based on data properties and zoom level.
We also define the `earthquakeSource`, which loads data from a GeoJSON file containing earthquake information.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:earthquakeSource"
```
When the snapshot is ready, the `onSnapshotReady` callback is invoked, where we set the snapshot bitmap to an `ImageView` to display it.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:onSnapshotReady"
```
Finally, we ensure to cancel the snapshotter in the `onStop` method to free up resources.
```kotlin
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt:onStop"
```
## 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.
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.
<figure markdown="span">
![Screenshot of Map Snapshot with Expression](https://github.com/user-attachments/assets/e75922ad-6115-4549-bcb7-7a40e03a81f4){ width="300" }
</figure>
```kotlin title="MapSnapshotterWithinExpression.kt"
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/turf/MapSnapshotterWithinExpression.kt"
```

View File

@@ -0,0 +1,74 @@
site_name: MapLibre Android Examples
site_url: https://www.maplibre.org/maplibre-native/android/examples
repo_url: https://github.com/louwers/maplibre-native
site_description: MapLibre Android Examples
edit_uri: edit/main/platform/android/docs
extra_css:
- assets/extra.css
theme:
name: 'material'
favicon: https://maplibre.org/favicon.ico
logo: assets/logo.svg
features:
- content.code.copy
- search.suggest
- navigation.instant
- navigation.sections
- content.action.edit
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to system preference
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets:
dedent_subsections: true
- pymdownx.superfences
- pymdownx.escapeall:
hardbreak: True
nbsp: True
- admonition
- pymdownx.details
- footnotes
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
extra:
social:
- icon: fontawesome/brands/mastodon
link: https://mastodon.social/@maplibre
- icon: fontawesome/brands/x-twitter
link: https://twitter.com/maplibre
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/company/maplibre
- icon: fontawesome/brands/slack
link: https://osmus.slack.com/archives/C01G3D28DAB
- icon: fontawesome/brands/github
link: https://github.com/maplibre
plugins:
- search
- social:
cards_layout_options:
background_color: '#295DAA'
validation:
omitted_files: warn
absolute_links: warn
unrecognized_links: warn
anchors: warn