Guide to using GeoJsonDataSource (#2977)
Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
committed by
GitHub
parent
4878005ffe
commit
8b32c9a783
@@ -43,6 +43,7 @@ class QuerySourceFeaturesActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun initStyle(style: Style) {
|
||||
// # --8<-- [start:JsonObject]
|
||||
val properties = JsonObject()
|
||||
properties.addProperty("key1", "value1")
|
||||
val source = GeoJsonSource(
|
||||
@@ -62,6 +63,7 @@ class QuerySourceFeaturesActivity : AppCompatActivity() {
|
||||
val layer = CircleLayer("test-layer", source.id)
|
||||
.withFilter(visible)
|
||||
style.addLayer(layer)
|
||||
// # --8<-- [end:JsonObject]
|
||||
|
||||
// Add a click listener
|
||||
maplibreMap.addOnMapClickListener { point: LatLng? ->
|
||||
|
||||
@@ -56,6 +56,7 @@ class CollectionUpdateOnStyleChange : AppCompatActivity(), OnMapReadyCallback, S
|
||||
}
|
||||
|
||||
private fun setupLayer(style: Style) {
|
||||
// # --8<-- [start:setupLayer]
|
||||
val source = GeoJsonSource("source", featureCollection)
|
||||
val lineLayer = LineLayer("layer", "source")
|
||||
.withProperties(
|
||||
@@ -65,6 +66,7 @@ class CollectionUpdateOnStyleChange : AppCompatActivity(), OnMapReadyCallback, S
|
||||
|
||||
style.addSource(source)
|
||||
style.addLayer(lineLayer)
|
||||
// # --8<-- [end:setupLayer]
|
||||
}
|
||||
|
||||
private fun setupStyleChangeView() {
|
||||
|
||||
@@ -45,10 +45,11 @@ class HeatmapLayerActivity : AppCompatActivity() {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// # --8<-- [start:createEarthquakeSource]
|
||||
private fun createEarthquakeSource(): GeoJsonSource {
|
||||
return GeoJsonSource(EARTHQUAKE_SOURCE_ID, URI(EARTHQUAKE_SOURCE_URL))
|
||||
}
|
||||
// # --8<-- [end:createEarthquakeSource]
|
||||
|
||||
private fun createHeatmapLayer(): HeatmapLayer {
|
||||
val layer = HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID)
|
||||
@@ -188,6 +189,7 @@ class HeatmapLayerActivity : AppCompatActivity() {
|
||||
mapView.onDestroy()
|
||||
}
|
||||
|
||||
// # --8<-- [start:constants]
|
||||
companion object {
|
||||
private const val EARTHQUAKE_SOURCE_URL =
|
||||
"https://maplibre.org/maplibre-gl-js-docs/assets/earthquakes.geojson"
|
||||
@@ -196,4 +198,5 @@ class HeatmapLayerActivity : AppCompatActivity() {
|
||||
private const val HEATMAP_LAYER_SOURCE = "earthquakes"
|
||||
private const val CIRCLE_LAYER_ID = "earthquakes-circle"
|
||||
}
|
||||
// # --8<-- [end:constants]
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class NoStyleActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMapSimpleBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// # --8<-- [start:setup]
|
||||
binding.mapView.getMapAsync { map ->
|
||||
map.moveCamera(CameraUpdateFactory.newLatLngZoom(cameraTarget, cameraZoom))
|
||||
map.setStyle(
|
||||
@@ -39,6 +39,7 @@ class NoStyleActivity : AppCompatActivity() {
|
||||
.withLayer(SymbolLayer(layerId, sourceId).withProperties(iconImage(imageId)))
|
||||
)
|
||||
}
|
||||
// # --8<-- [end:setup]
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
||||
@@ -329,6 +329,7 @@ class RuntimeStyleActivity : AppCompatActivity() {
|
||||
|
||||
private fun addParksLayer() {
|
||||
// Add a source
|
||||
// # --8<-- [start:source]
|
||||
val source: Source = try {
|
||||
GeoJsonSource("amsterdam-spots", ResourceUtils.readRawResource(this, R.raw.amsterdam))
|
||||
} catch (ioException: IOException) {
|
||||
@@ -347,6 +348,7 @@ class RuntimeStyleActivity : AppCompatActivity() {
|
||||
PropertyFactory.fillOpacity(0.3f),
|
||||
PropertyFactory.fillAntialias(true)
|
||||
)
|
||||
// # --8<-- [end:source]
|
||||
|
||||
// Only show me parks (except westerpark with stroke-width == 3)
|
||||
layer.setFilter(
|
||||
|
||||
@@ -61,6 +61,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// # --8<-- [start:updateSource]
|
||||
private fun updateSource(style: Style?) {
|
||||
val featureCollection = createFeatureCollection()
|
||||
if (source != null) {
|
||||
@@ -70,6 +71,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
|
||||
style!!.addSource(source!!)
|
||||
}
|
||||
}
|
||||
// # --8<-- [end:updateSource]
|
||||
|
||||
private fun toggleSymbolLayerVisibility() {
|
||||
layer!!.setProperties(
|
||||
@@ -78,6 +80,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
|
||||
isShowingSymbolLayer = !isShowingSymbolLayer
|
||||
}
|
||||
|
||||
// # --8<-- [start:createFeatureCollection]
|
||||
private fun createFeatureCollection(): FeatureCollection {
|
||||
val point = if (isInitialPosition) {
|
||||
Point.fromLngLat(-74.01618140, 40.701745)
|
||||
@@ -89,6 +92,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
|
||||
val feature = Feature.fromGeometry(point, properties)
|
||||
return FeatureCollection.fromFeatures(arrayOf(feature))
|
||||
}
|
||||
// # --8<-- [end:createFeatureCollection]
|
||||
|
||||
private fun addLayer(style: Style) {
|
||||
layer = SymbolLayer(LAYER_ID, SOURCE_ID)
|
||||
|
||||
@@ -187,9 +187,9 @@ class MapSnapshotterWithinExpression : AppCompatActivity() {
|
||||
super.onSaveInstanceState(outState, outPersistentState)
|
||||
binding.mapView.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun bufferLineStringGeometry(): Polygon {
|
||||
private fun bufferLineStringGeometry(): Polygon {
|
||||
// TODO replace static data by Turf#Buffer: mapbox-java/issues/987
|
||||
// # --8<-- [start:fromJson]
|
||||
return FeatureCollection.fromJson(
|
||||
"""
|
||||
{
|
||||
@@ -250,6 +250,7 @@ class MapSnapshotterWithinExpression : AppCompatActivity() {
|
||||
}
|
||||
""".trimIndent()
|
||||
).features()!![0].geometry() as Polygon
|
||||
// # --8<-- [end:fromJson]
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.io.*
|
||||
|
||||
object ResourceUtils {
|
||||
@JvmStatic
|
||||
// # --8<-- [start:readRawResource]
|
||||
fun readRawResource(context: Context?, @RawRes rawResource: Int): String {
|
||||
var json = ""
|
||||
if (context != null) {
|
||||
@@ -23,6 +24,7 @@ object ResourceUtils {
|
||||
}
|
||||
return json
|
||||
}
|
||||
// # --8<-- [end:readRawResource]
|
||||
|
||||
fun convertDpToPx(context: Context, dp: Float): Float {
|
||||
return TypedValue.applyDimension(
|
||||
|
||||
134
platform/android/docs/geojson-guide.md
Normal file
134
platform/android/docs/geojson-guide.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Using a GeoJSON Source
|
||||
|
||||
This guide will teach you how to use [`GeoJsonSource`](https://maplibre.org/maplibre-native/android/api/-map-libre%20-native%20-android/org.maplibre.android.style.sources/-geo-json-source/index.html) by deep diving into [GeoJSON](https://geojson.org/) file format.
|
||||
|
||||
## Goals
|
||||
|
||||
After finishing this documentation you should be able to:
|
||||
|
||||
1. Understand how `Style`, `Layer`, and `Source` interact with each other.
|
||||
2. Explore building blocks of GeoJSON data.
|
||||
3. Use GeoJSON files in constructing `GeoJsonSource`s.
|
||||
4. Update data at runtime.
|
||||
|
||||
## 1. Styles, Layers, and Data source
|
||||
|
||||
- A style defines the visual representation of the map such as colors and appearance.
|
||||
- Layers control how data should be presented to the user.
|
||||
- Data sources hold actual data and provides layers with it.
|
||||
|
||||
Styles consist of collections of layers and a data source. Layers reference data sources. Hence, they require a unique source ID when you construct them.
|
||||
It would be meaningless if we don't have any data to show, so we need know how to supply data through a data source.
|
||||
|
||||
Firstly, we need to understand how to store data and pass it into a data source; therefore, we will discuss GeoJSON in the next session.
|
||||
|
||||
## 2. GeoJSON
|
||||
|
||||
[GeoJSON](https://geojson.org/) is a JSON file for encoding various geographical data structures.
|
||||
It defines several JSON objects to represent geospatial information. Typicalle the`.geojson` extension is used for GeoJSON files.
|
||||
We define the most fundamental objects:
|
||||
|
||||
- `Geometry` refers to a single geometric shape that contains one or more coordinates. These shapes are visual objects displayed on a map. A geometry can be one of the following six types:
|
||||
- Point
|
||||
- MultiPoint
|
||||
- LineString
|
||||
- MultilineString
|
||||
- Polygon
|
||||
- MultiPolygon
|
||||
- `Feautue` is a compound object that combines a single geometry with user-defined attributes, such as name, color.
|
||||
- `FeatureCollection` is set of features stored in an array. It is a root object that introduces all other features.
|
||||
|
||||
A typical GeoJSON structure might look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [125.6, 10.1]
|
||||
},
|
||||
"properties": {
|
||||
"name": "Dinagat Islands"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
So far we learned describing geospatial data in GeoJSON files. We will start applying this knowledge into our map applications.
|
||||
|
||||
## 3. GeoJsonSource
|
||||
|
||||
As we discussed before, map requires some sort data to be rendered. We use different sources such as Vector, Raster and GeoJSON.
|
||||
We will focus exclusively on `GeoJsonSource` and will not address other sources.
|
||||
|
||||
`GeoJsonSource` is a type of source that has a unique `String` ID and GeoJSON data.
|
||||
|
||||
There are several ways to construct a `GeoJsonSource`:
|
||||
|
||||
- Locally stored files such as assets and raw folders
|
||||
- Remote services
|
||||
- Raw string parsed into FeatureCollections objects
|
||||
- Geometry, Feature, and FeatureCollection objects that map to GeoJSON Base builders
|
||||
|
||||
A sample `GeoJsonSource`:
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/CollectionUpdateOnStyleChange.kt:setupLayer"
|
||||
```
|
||||
|
||||
Note that you can not simply show data on a map. Layers must reference them. Therefore, you create a layer that gives visual appearance to it.
|
||||
|
||||
### Creating GeoJSON sources
|
||||
|
||||
There are various ways you can create a `GeoJSONSource`. Some of the options are shown below.
|
||||
|
||||
```kotlin title="Loading from local files with assets folder file"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/NoStyleActivity.kt:setup"
|
||||
```
|
||||
|
||||
```kotlin title="Loading with raw folder file"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/RuntimeStyleActivity.kt:source"
|
||||
```
|
||||
|
||||
```kotlin title="Parsing inline JSON"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/utils/ResourceUtils.kt:readRawResource"
|
||||
```
|
||||
|
||||
```kotlin title="Loading from remote services"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/HeatmapLayerActivity.kt:createEarthquakeSource"
|
||||
```
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/HeatmapLayerActivity.kt:constants"
|
||||
```
|
||||
|
||||
```kotlin title="Parsing string with the fromJson method of FeatureCollection"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/turf/MapSnapshotterWithinExpression.kt:fromJson"
|
||||
```
|
||||
|
||||
```kotlin title="Creating Geometry, Feature, and FeatureCollections from scratch"
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/feature/QuerySourceFeaturesActivity.kt:JsonObject"
|
||||
```
|
||||
|
||||
Note that the GeoJSON objects we discussed earlier have classes defined in the MapLibre SDK.
|
||||
Therefore, we can either map JSON objects to regular Java/Kotlin objects or build them directly.
|
||||
|
||||
## 4. Updating data at runtime
|
||||
|
||||
The key feature of `GeoJsonSource`s is that once we add one, we can set another set of data.
|
||||
We achieve this using `setGeoJson()` method. For instance, we create a source variable and check if we have not assigned it, then we create a new source object and add it to style; otherwise, we set a different data source:
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/ZoomFunctionSymbolLayerActivity.kt:createFeatureCollection"
|
||||
```
|
||||
|
||||
```kotlin
|
||||
--8<-- "MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/style/ZoomFunctionSymbolLayerActivity.kt:updateSource"
|
||||
```
|
||||
|
||||
See [this guide](styling/animated-symbol-layer.md) for an advanced example that showcases random cars and a passenger on a map updating their positions with smooth animation.
|
||||
|
||||
## Summary
|
||||
|
||||
GeoJsonSources have their pros and cons. They are most effective when you want to add additional data to your style or provide features like animating objects on your map.
|
||||
|
||||
However, working with large datasets can be challenging if you need to manipulate and store data within the app; in such cases, it’s better to use a remote data source.
|
||||
Reference in New Issue
Block a user