Guide to using GeoJsonDataSource (#2977)

Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
Dilshodbek Jumabaev
2024-11-28 22:32:17 +05:00
committed by GitHub
parent 4878005ffe
commit 8b32c9a783
9 changed files with 155 additions and 4 deletions

View File

@@ -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? ->

View File

@@ -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() {

View File

@@ -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]
}

View File

@@ -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() {

View File

@@ -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(

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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(

View 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.