Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guide to using GeoJsonDataSource #2977

Merged
merged 25 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5a48b53
added geojson md file
jDilshodbek Sep 4, 2024
1325a81
Merge branch 'maplibre:main' into feature/documentation-geojson
jDilshodbek Oct 12, 2024
c15a2d6
Merge branch 'maplibre:main' into feature/documentation-geojson
jDilshodbek Oct 26, 2024
8c51236
filled geojson documentation
jDilshodbek Oct 27, 2024
3136421
Merge branch 'maplibre:main' into feature/documentation-geojson
jDilshodbek Oct 28, 2024
695f230
corrected capitalizations
jDilshodbek Oct 29, 2024
1dd6acd
Merge remote-tracking branch 'origin/feature/documentation-geojson' i…
jDilshodbek Oct 29, 2024
8c56720
Merge branch 'main' into feature/documentation-geojson
jDilshodbek Oct 29, 2024
d5697dd
refined and reconstructed with chatgpt
jDilshodbek Oct 29, 2024
1d4f681
Merge branch 'main' into feature/documentation-geojson
jDilshodbek Nov 13, 2024
1f3bc7f
corrected headline spacing
jDilshodbek Nov 13, 2024
4e9d445
capitalised first letters of sentences
jDilshodbek Nov 13, 2024
d2a2cbe
added correct dots
jDilshodbek Nov 13, 2024
5b2bcac
removed json part
jDilshodbek Nov 13, 2024
5700ae3
referenced geojsonsource sample
jDilshodbek Nov 13, 2024
5ccce01
referenced maplibre setup sample
jDilshodbek Nov 13, 2024
7b89d7b
added remote service reference
jDilshodbek Nov 13, 2024
8cf7ba3
fromjson feature referenced
jDilshodbek Nov 13, 2024
ff7d6ec
referenced jsonobject
jDilshodbek Nov 13, 2024
22aa9a7
referenced update runtime feature
jDilshodbek Nov 13, 2024
03040e0
geojson example updated
jDilshodbek Nov 16, 2024
64a197c
Merge remote-tracking branch 'origin/main' into feature/documentation…
jDilshodbek Nov 16, 2024
49089d7
renamed title
jDilshodbek Nov 27, 2024
21d9573
Merge branch 'main' into feature/documentation-geojson
louwers Nov 28, 2024
a23b6ec
Fix some problems, use new snippet syntax
louwers Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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? ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -65,6 +66,7 @@ class CollectionUpdateOnStyleChange : AppCompatActivity(), OnMapReadyCallback, S

style.addSource(source)
style.addLayer(lineLayer)
// # --8<-- [end:setupLayer]
}

private fun setupStyleChangeView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand All @@ -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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -39,6 +39,7 @@ class NoStyleActivity : AppCompatActivity() {
.withLayer(SymbolLayer(layerId, sourceId).withProperties(iconImage(imageId)))
)
}
// # --8<-- [end:setup]
}

override fun onStart() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
}
}

// # --8<-- [start:updateSource]
private fun updateSource(style: Style?) {
val featureCollection = createFeatureCollection()
if (source != null) {
Expand All @@ -70,6 +71,7 @@ class ZoomFunctionSymbolLayerActivity : AppCompatActivity() {
style!!.addSource(source!!)
}
}
// # --8<-- [end:updateSource]

private fun toggleSymbolLayerVisibility() {
layer!!.setProperties(
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"""
{
Expand Down Expand Up @@ -250,6 +250,7 @@ class MapSnapshotterWithinExpression : AppCompatActivity() {
}
""".trimIndent()
).features()!![0].geometry() as Polygon
// # --8<-- [end:fromJson]
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -23,6 +24,7 @@ object ResourceUtils {
}
return json
}
// # --8<-- [end:readRawResource]

fun convertDpToPx(context: Context, dp: Float): Float {
return TypedValue.applyDimension(
Expand Down
134 changes: 134 additions & 0 deletions platform/android/docs/geojson-guide.md
Original file line number Diff line number Diff line change
@@ -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.
Loading