diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 47a4969c4ee..9addd4ec419 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -143,8 +143,11 @@ jobs: - name: Build libmaplibre.so for arm-v8 run: make android-lib-arm-v8 - - name: Build documentation + - name: Build API documentation run: ./gradlew dokkaHtml + + - name: Build Examples documentation + run: make mkdocs-build - name: Copy developer config with API key for UI tests if: github.ref == 'refs/heads/main' diff --git a/.github/workflows/gh-pages-android-examples.yml b/.github/workflows/gh-pages-android-examples.yml new file mode 100644 index 00000000000..303b41776e7 --- /dev/null +++ b/.github/workflows/gh-pages-android-examples.yml @@ -0,0 +1,30 @@ +name: gh-pages-android-examples + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'platform/android/**' + +jobs: + gh-pages-android-examples: + runs-on: ubuntu-latest + defaults: + run: + working-directory: platform/android + shell: bash + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v4 + + - name: Generate documentation + run: make mkdocs-build + + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4.6.8 + with: + branch: gh-pages + folder: platform/android/site + target-folder: maplibre-native/android/examples/ diff --git a/.gitignore b/.gitignore index f417555efd5..6ba9831b1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ compile_commands.json /platform/ios/platform/ios/benchmark/assets/glyphs/Roboto Condensed Italic,Noto Sans Italic /platform/ios/platform/ios/benchmark/assets/glyphs/Noto Sans Regular /platform/android/key.json +/platform/android/site node_modules # Node binaries. /lib diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5ba07dc320..25cc6695256 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: v5.0.0 hooks: - id: check-yaml - args: [--allow-multiple-documents] + args: [--allow-multiple-documents, --unsafe] - repo: https://github.com/pre-commit/mirrors-clang-format rev: v19.1.3 hooks: diff --git a/docs/mdbook/src/android/README.md b/docs/mdbook/src/android/README.md deleted file mode 100644 index 7576536ddd6..00000000000 --- a/docs/mdbook/src/android/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# MapLibre Native for Android - -MapLibre Native for Android is a library for embedding interactive map views with scalable, customizable vector maps onto Android devices. \ No newline at end of file diff --git a/docs/mdbook/src/android/location-component-guide.md b/docs/mdbook/src/android/location-component-guide.md deleted file mode 100644 index bf1957a961e..00000000000 --- a/docs/mdbook/src/android/location-component-guide.md +++ /dev/null @@ -1,100 +0,0 @@ -# LocationComponent - -This guide will demonstrate how to utilize the [LocationComponent] to represent the user's current location. - - -1. 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 - - - - - - - -``` - -2. 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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:top}} -``` - -3. 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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:permission}} -``` - -4. 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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:onMapReady}} -``` - -5. [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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt:LocationComponentActivationOptions}} -``` - -6. 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() -``` - -7. 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] [^2]. - - ![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. -[^2]: Menu items to manage user location icon are used in the [Test App], too. - -[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 diff --git a/platform/android/DEVELOPING.md b/platform/android/DEVELOPING.md index 7047a31a8d8..9d60a6e6c15 100644 --- a/platform/android/DEVELOPING.md +++ b/platform/android/DEVELOPING.md @@ -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/ \ No newline at end of file diff --git a/platform/android/Makefile b/platform/android/Makefile index 4a3d6b93a01..9da3307c708 100644 --- a/platform/android/Makefile +++ b/platform/android/Makefile @@ -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 \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/attribution/AttributionParser.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/attribution/AttributionParser.java index d5b845f7006..f836fb37960 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/attribution/AttributionParser.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/attribution/AttributionParser.java @@ -65,6 +65,9 @@ public String createAttributionString() { */ @NonNull public String createAttributionString(boolean shortenedOutput) { + if (attributions.isEmpty()) { + return ""; + } StringBuilder stringBuilder = new StringBuilder(withCopyrightSign ? "" : "© "); int counter = 0; for (Attribution attribution : attributions) { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/snapshotter/MapSnapshotter.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/snapshotter/MapSnapshotter.kt index 8aabf4141ac..fcc836c97a0 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/snapshotter/MapSnapshotter.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/snapshotter/MapSnapshotter.kt @@ -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) +} diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/attribution/AttributionParseTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/attribution/AttributionParseTest.kt index 5a21f48a384..35197cc51fd 100644 --- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/attribution/AttributionParseTest.kt +++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/attribution/AttributionParseTest.kt @@ -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 = "© MapLibre © OpenStreetMap \n" diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt index 8485033096c..0572655617f 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt @@ -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() @@ -146,7 +146,7 @@ class JsonApiActivity : AppCompatActivity() { maplibreMap.cameraPosition = newCameraPosition } } - /* ANCHOR_END: addMarkers */ + // # --8<-- [end:addMarkers] override fun onStart() { super.onStart() diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt index 86d94ba1a94..badad0f3e57 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/GestureDetectorActivity.kt @@ -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) { diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt index e5b07a40fce..e1299c1b835 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/camera/LatLngBoundsActivity.kt @@ -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() diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt index b655c7bdd05..fd5f67d9d78 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/location/BasicLocationPulsingCircleActivity.kt @@ -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() diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt index f31739c459e..32b93771128 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterActivity.kt @@ -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 = 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 } } -} +} \ No newline at end of file diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt index cc7a4a92892..56a6ba7d5d5 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterBitMapOverlayActivity.kt @@ -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)) diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt index e07e3cce9f1..f762f854ffd 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterHeatMapActivity.kt @@ -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(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" diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt index 2c2d9ad00a9..f54dda3946c 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/snapshot/MapSnapshotterLocalStyleActivity.kt @@ -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(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(R.id.snapshot_image) as ImageView - imageView.setImageBitmap(snapshot.bitmap) + if (this::mapSnapshotter.isInitialized) { + mapSnapshotter.cancel() + } } } diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RuntimeStyleActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RuntimeStyleActivity.kt index cbd8c83f4ba..6df7788241a 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RuntimeStyleActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RuntimeStyleActivity.kt @@ -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() { diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/StyleFileActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/StyleFileActivity.kt index 20da73c4a88..209f5b87b1b 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/StyleFileActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/StyleFileActivity.kt @@ -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") "" diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/styles/TestStyles.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/styles/TestStyles.kt index 889f4ebec9a..eb8a50d9ecd 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/styles/TestStyles.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/styles/TestStyles.kt @@ -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) diff --git a/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_runtime_style.xml b/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_runtime_style.xml index edbd540d503..e82d63b5267 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_runtime_style.xml +++ b/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_runtime_style.xml @@ -43,8 +43,8 @@ android:title="@string/add_a_terrain_layer" maplibre:showAsAction="never" /> Add a dynamic GeoJSON source Remove buildings layer Add a terrain layer - Add a satellite layer + Set style with raster layer Change the water color on zoom Custom tiles Apply filtered fill diff --git a/platform/android/docs/README.md b/platform/android/docs/README.md new file mode 100644 index 00000000000..ee549ca441c --- /dev/null +++ b/platform/android/docs/README.md @@ -0,0 +1,48 @@ +# MapLibre Android Examples + +Welcome to the examples documentation of MapLibre Android. + +
+ +- :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) + +
+ + +## 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. \ No newline at end of file diff --git a/platform/android/docs/annotations/add-markers.md b/platform/android/docs/annotations/add-markers.md new file mode 100644 index 00000000000..bfa255a9301 --- /dev/null +++ b/platform/android/docs/annotations/add-markers.md @@ -0,0 +1,13 @@ +# Add Markers in Bulk + +This example demonstrates how you can add markers in bulk. + +
+ 100 images on map + + 1000 images on map +
+ +```kotlin title="BulkMarkerActivity.kt" +--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/BulkMarkerActivity.kt" +``` \ No newline at end of file diff --git a/docs/mdbook/src/android/annotation-guide.md b/platform/android/docs/annotations/marker-annotations.md similarity index 77% rename from docs/mdbook/src/android/annotation-guide.md rename to platform/android/docs/annotations/marker-annotations.md index e21eee1b280..a094e94d35f 100644 --- a/docs/mdbook/src/android/annotation-guide.md +++ b/platform/android/docs/annotations/marker-annotations.md @@ -4,6 +4,7 @@ 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] @@ -23,50 +24,44 @@ 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 `//build.gradle`), add - `okhttp` to simplify code for making HTTP requests. - - ```gradle - dependencies { - ... - implementation 'com.squareup.okhttp3:okhttp:4.10.0' - ... - } - ``` +1. In your module Gradle file (usually `//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 `MapboxMap`. +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" + ``` -```kotlin -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:top}} -``` - -4. Call `mapview.getMapSync()` in order to get a `MapboxMap` object. +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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:mapAsync}} -``` + ```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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:getEarthquakes}} -``` + ```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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/annotation/JsonApiActivity.kt:addMarkers}} -``` + ```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]. @@ -81,7 +76,7 @@ Then add markers to the map with GeoJSON: [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-guide.md +[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 diff --git a/platform/android/docs/assets/extra.css b/platform/android/docs/assets/extra.css new file mode 100644 index 00000000000..a7be243b3d9 --- /dev/null +++ b/platform/android/docs/assets/extra.css @@ -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; +} \ No newline at end of file diff --git a/platform/android/docs/assets/logo.svg b/platform/android/docs/assets/logo.svg new file mode 100644 index 00000000000..fa409227a77 --- /dev/null +++ b/platform/android/docs/assets/logo.svg @@ -0,0 +1,62 @@ + + + + diff --git a/platform/android/docs/camera/animation-types.md b/platform/android/docs/camera/animation-types.md new file mode 100644 index 00000000000..4c08acac3da --- /dev/null +++ b/platform/android/docs/camera/animation-types.md @@ -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" +``` diff --git a/platform/android/docs/camera/animator-animation.md b/platform/android/docs/camera/animator-animation.md new file mode 100644 index 00000000000..eb8029c7cfd --- /dev/null +++ b/platform/android/docs/camera/animator-animation.md @@ -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" +``` \ No newline at end of file diff --git a/platform/android/docs/camera/cameraposition.md b/platform/android/docs/camera/cameraposition.md new file mode 100644 index 00000000000..e4a22c75589 --- /dev/null +++ b/platform/android/docs/camera/cameraposition.md @@ -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" +``` \ No newline at end of file diff --git a/platform/android/docs/camera/gesture-detector.md b/platform/android/docs/camera/gesture-detector.md new file mode 100644 index 00000000000..16aff9f7886 --- /dev/null +++ b/platform/android/docs/camera/gesture-detector.md @@ -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" +``` \ No newline at end of file diff --git a/platform/android/docs/camera/lat-lng-bounds.md b/platform/android/docs/camera/lat-lng-bounds.md new file mode 100644 index 00000000000..4aaa94d62fa --- /dev/null +++ b/platform/android/docs/camera/lat-lng-bounds.md @@ -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. + +
+ +
+ +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" +``` \ No newline at end of file diff --git a/docs/mdbook/src/android/map-options-guide.md b/platform/android/docs/configuration.md similarity index 85% rename from docs/mdbook/src/android/map-options-guide.md rename to platform/android/docs/configuration.md index c3fdc5b162e..01126e8cf81 100644 --- a/docs/mdbook/src/android/map-options-guide.md +++ b/platform/android/docs/configuration.md @@ -1,4 +1,4 @@ -# MapLibre Configuration +# Configuration This guide will explain various ways to create a map. @@ -13,6 +13,7 @@ There are several ways to build a `MapView`: 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 @@ -30,7 +31,7 @@ We will explore how to achieve these configurations in XML layout and programmat To configure `MapView` within an XML layout, you need to use the right namespace and provide the necessary data in the layout file. ```xml -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/res/layout/activity_map_options_xml.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). @@ -38,7 +39,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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsXmlActivity.kt}} +--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). @@ -47,7 +48,7 @@ This can be found in [`MapOptionsXmlActivity.kt`](https://github.com/maplibre/ma Here we don't have to create MapView from XML since we want to create it programmatically. ```xml -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/res/layout/activity_map_options_xml.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). @@ -55,7 +56,7 @@ 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 -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/options/MapOptionsRuntimeActivity.kt}} +--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). @@ -77,7 +78,7 @@ If you are using MapFragment in your project, it is also easy to provide initial Let's see how this can be done in a sample activity: ```kotlin -{{#include ../../../../platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt}} +--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). diff --git a/docs/mdbook/src/android/getting-started-guide.md b/platform/android/docs/getting-started.md similarity index 83% rename from docs/mdbook/src/android/getting-started-guide.md rename to platform/android/docs/getting-started.md index 6ff8114bc81..9e05c22334b 100644 --- a/docs/mdbook/src/android/getting-started-guide.md +++ b/platform/android/docs/getting-started.md @@ -1,11 +1,5 @@ # Quickstart -
- -In versions prior to the (upcoming) MapLibre Native for Android 11.0 release, you need to use `com.mapbox.mapboxsdk.*` for imports instead of `org.maplibre.android.*`. Classes with `Mapbox` in the name are replaced with `MapLibre`. Details can be found in the [changelog](https://github.com/maplibre/maplibre-native/blob/main/platform/android/CHANGELOG.md). - -
- 1. Add bintray Maven repositories to your project-level Gradle file (usually `//build.gradle`). ```gradle @@ -17,7 +11,7 @@ In versions prior to the (upcoming) MapLibre Native for Android 11.0 release, yo } ``` -2. Add the library as a dependency into your module Gradle file (usually `//build.gradle`). Replace `` with the latest MapLibre Native version (e.g.: `org.maplibre.gl:android-sdk:10.0.2`). Visit [https://mvnrepository.com/artifact/org.maplibre.gl/android-sdk](https://mvnrepository.com/artifact/org.maplibre.gl/android-sdk) to view the version history of MapLibre Native for android. +2. Add the library as a dependency into your module Gradle file (usually `//build.gradle`). Replace `` 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 { @@ -115,6 +109,7 @@ In versions prior to the (upcoming) MapLibre Native for Android 11.0 release, yo ``` 6. Build and run the app. If you run the app successfully, a map will be displayed as seen in the screenshot below. +
Screenshot with the map in demotile style
diff --git a/platform/android/docs/location-component.md b/platform/android/docs/location-component.md new file mode 100644 index 00000000000..bedaa24404b --- /dev/null +++ b/platform/android/docs/location-component.md @@ -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 + + + + + + + +``` + +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 diff --git a/platform/android/docs/snapshotter.md b/platform/android/docs/snapshotter.md new file mode 100644 index 00000000000..89a17dbe217 --- /dev/null +++ b/platform/android/docs/snapshotter.md @@ -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. + +
+ ![Map Snapshotter with Local Style](https://github.com/user-attachments/assets/897452c6-52e3-4e58-828c-4f7366b3ba90){ width="300" } +
+ +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. + +
+ ![Map Snapshotter](https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/map_snapshotter.png){ width="300" } +
+ +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. + +
+ ![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" } +
+ + +```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. + +
+ ![Screenshot of Snapshotter with Heatmap](https://maplibre-native.s3.eu-central-1.amazonaws.com/android-documentation-resources/snapshotter_headmap_screenshot.png){ width="300" } +
+ +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. + +
+ ![Screenshot of Map Snapshot with Expression](https://github.com/user-attachments/assets/e75922ad-6115-4549-bcb7-7a40e03a81f4){ width="300" } +
+ +```kotlin title="MapSnapshotterWithinExpression.kt" +--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/turf/MapSnapshotterWithinExpression.kt" +``` \ No newline at end of file diff --git a/platform/android/mkdocs.yaml b/platform/android/mkdocs.yaml new file mode 100644 index 00000000000..0370679c8d0 --- /dev/null +++ b/platform/android/mkdocs.yaml @@ -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