diff --git a/docs/mdbook/src/SUMMARY.md b/docs/mdbook/src/SUMMARY.md index 16e6b3922..a2ce8b9a0 100644 --- a/docs/mdbook/src/SUMMARY.md +++ b/docs/mdbook/src/SUMMARY.md @@ -12,4 +12,5 @@ - [MapLibre Native for Android](./android/README.md) - [Quickstart](./android/getting-started-guide.md) + - [Annotation: Marker](./android/annotation-guide.md) - [Location Component](./android/location-component-guide.md) diff --git a/docs/mdbook/src/android/annotation-guide.md b/docs/mdbook/src/android/annotation-guide.md new file mode 100644 index 000000000..a42bc285a --- /dev/null +++ b/docs/mdbook/src/android/annotation-guide.md @@ -0,0 +1,89 @@ +# 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 +`com.mapbox.mapboxsdk.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 `//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`. + It is used to add annotations to the map instance. + +```kotlin +{{#include ../../../../platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/JsonApiActivity.kt:top}} +``` + +4. Call `mapview.getMapSync()` in order to get a `MapboxMap` object. + After `mapboxMap` is assigned, call the `getEarthQuakeDataFromUSGS()` method + to make a HTTP request and transform data into the map annotations. + +```kotlin +{{#include ../../../../platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/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/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/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/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/JsonApiActivity.kt:addMarkers}} +``` + +7. Here is the final result. For the full contents of `JsonApiActivity`, please visit source code of [Test APP] + +
+ Screenshot with the map in demotile style +
+ +[Marker]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20for%20-android/com.mapbox.mapboxsdk.annotations/-marker/index.html +[provided image]: https://github.com/maplibre/maplibre-native/blob/main/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/maplibre_marker_icon_default.png +[Polyline]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20for%20-android/com.mapbox.mapboxsdk.annotations/-polyline/index.html +[Polygon]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20for%20-android/com.mapbox.mapboxsdk.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%20for%20-android/com.mapbox.mapboxsdk.annotations/-icon-factory/index.html +[Icon]: https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20for%20-android/com.mapbox.mapboxsdk.annotations/-icon/index.html +[Quickstart]: ./getting-started-guide.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%20for%20-android/com.mapbox.mapboxsdk.annotations/-marker-options/index.html +[Test App]: https://github.com/maplibre/maplibre-native/tree/main/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/JsonApiActivity.kt diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index d6bcc56ba..d94d21718 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -111,6 +111,19 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity" /> + + + + + maplibreMap = map + + maplibreMap.setStyle("https://demotiles.maplibre.org/style.json") + + // Fetch data from USGS + getEarthQuakeDataFromUSGS() + } + /* ANCHOR_END: mapAsync */ + } + + /* ANCHOR: getEarthquakes */ + // Get Earthquake data from usgs.gov, read API doc at: + // https://earthquake.usgs.gov/fdsnws/event/1/ + private fun getEarthQuakeDataFromUSGS() { + val url = "https://earthquake.usgs.gov/fdsnws/event/1/query".toHttpUrl().newBuilder() + .addQueryParameter("format", "geojson") + .addQueryParameter("starttime", "2022-01-01") + .addQueryParameter("endtime", "2022-12-31") + .addQueryParameter("minmagnitude", "5.8") + .addQueryParameter("latitude", "24") + .addQueryParameter("longitude", "121") + .addQueryParameter("maxradius", "1.5") + .build() + val request: Request = Request.Builder().url(url).build() + + OkHttpClient().newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + Toast.makeText(this@JsonApiActivity, "Fail to fetch data", Toast.LENGTH_SHORT) + .show() + } + + override fun onResponse(call: Call, response: Response) { + val featureCollection = response.body?.string() + ?.let(FeatureCollection::fromJson) + ?: return + // If FeatureCollection in response is not null + // Then add markers to map + runOnUiThread { addMarkersToMap(featureCollection) } + } + }) + } + /* ANCHOR_END: getEarthquakes */ + + /* ANCHOR: addMarkers */ + private fun addMarkersToMap(data: FeatureCollection) { + val bounds = mutableListOf() + + // Get bitmaps for marker icon + val infoIconDrawable = ResourcesCompat.getDrawable( + this.resources, + // Intentionally specify package name + // This makes copy from another project easier + org.maplibre.android.R.drawable.maplibre_info_icon_default, + null + )!! + val bitmapBlue = infoIconDrawable.toBitmap() + val bitmapRed = infoIconDrawable + .mutate() + .apply { setTint(Color.RED) } + .toBitmap() + + // Add symbol for each point feature + data.features()?.forEach { feature -> + val geometry = feature.geometry()?.toJson() ?: return@forEach + val point = Point.fromJson(geometry) ?: return@forEach + val latLng = LatLng(point.latitude(), point.longitude()) + bounds.add(latLng) + + // Contents in InfoWindow of each marker + val title = feature.getStringProperty("title") + val epochTime = feature.getNumberProperty("time") + val dateString = SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.TAIWAN).format(epochTime) + + // If magnitude > 6.0, show marker with red icon. If not, show blue icon instead + val mag = feature.getNumberProperty("mag") + val icon = IconFactory.getInstance(this) + .fromBitmap(if (mag.toFloat() > 6.0) bitmapRed else bitmapBlue) + + // Use MarkerOptions and addMarker() to add a new marker in map + val markerOptions = MarkerOptions() + .position(latLng) + .title(dateString) + .snippet(title) + .icon(icon) + maplibreMap.addMarker(markerOptions) + } + + // Move camera to newly added annotations + maplibreMap.getCameraForLatLngBounds(LatLngBounds.fromLatLngs(bounds))?.let { + val newCameraPosition = CameraPosition.Builder() + .target(it.target) + .zoom(it.zoom - 0.5) + .build() + maplibreMap.cameraPosition = newCameraPosition + } + } + /* ANCHOR_END: addMarkers */ + + 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) + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_json_api.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_json_api.xml new file mode 100644 index 000000000..5815eda54 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_json_api.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 1d858873b..6ac420127 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -8,6 +8,7 @@ Showcase using a Map Fragment with a fragment backstack Activity with multiple maps on screen Add marker to map on long press + Use JSON API to add markers Different types of zoom methods Configure a max and min zoomlevel Learn how to handle the InfoWindow diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 28d0becd9..dc4d597ae 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -11,6 +11,7 @@ Polygon Press Map For Marker Zoom function with SymbolLayer + Add Markers From JSON API Standard InfoWindow Custom InfoWindow Custom Dynamic InfoWindow