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

Introduce Interactions API #748

Merged
merged 28 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# main

* Align tap propagation behavior on Android and iOS.
* Introduce the experimental Interactions API, a toolset that allows you to handle interactions on both layers and basemap features for styles. This API introduces a new concept called `Featureset`, which allows Evolving Basemap styles, such as Standard, to export an abstract set of features, such as POI, buildings, and place labels, regardless of which layers they are rendered on. An `Interaction` can then be targeted to these features, modifying their state when interacted with. For example, you can add a `TapInteraction` to your map which targets the `buildings` `Featureset`. When a user taps on a building, the building will be highlighted and its color will change to blue.

```dart
var tapInteraction = TapInteraction(StandardBuildings(),
(_, feature) {
mapboxMap.setFeatureStateForFeaturesetFeature(feature, StandardBuildingState(highlight: true));
log("Building group: ${feature.group}");
});
mapboxMap.addInteraction(tapInteraction);
```

Specific changes:
* Introduce the experimental `MapboxMap.addInteractions` method, which allows you to add interactions to the map.
* Introduce `TapInteraction` and `LongTapInteraction`, which allow you to add tap and longTap interactions to the map.
* Introduce `FeaturesetDescriptor` -- and convenience descriptors for `StandardBuildings`, `StandardPOIs`, and `StandardPlaceLabels` -- which allow you to describe the featureset you want `Interactions` to target.
* Introduce low-level methods for creating and manipulating interactive features: `queryRenderedFeatures`, `querySourceFeatures`, `setFeatureState`, `getFeatureState`, `removeFeatureState`, `resetFeatureState`
* For more guidance with using these new features see `interactive_features_example.dart`.

### 2.6.0-beta.1

Expand Down
122 changes: 122 additions & 0 deletions android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mapbox.maps.mapbox_maps

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import com.google.gson.Gson
Expand All @@ -9,6 +10,7 @@ import com.mapbox.bindgen.Value
import com.mapbox.common.TileRegionError
import com.mapbox.geojson.*
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.MapboxExperimental
import com.mapbox.maps.StylePackError
import com.mapbox.maps.applyDefaultParams
import com.mapbox.maps.debugoptions.MapViewDebugOptions
Expand All @@ -19,6 +21,8 @@ import com.mapbox.maps.extension.style.light.generated.directionalLight
import com.mapbox.maps.extension.style.light.generated.flatLight
import com.mapbox.maps.extension.style.projection.generated.Projection
import com.mapbox.maps.extension.style.types.StyleTransition
import com.mapbox.maps.interactions.FeatureState
import com.mapbox.maps.interactions.TypedFeaturesetDescriptor
import com.mapbox.maps.logE
import com.mapbox.maps.mapbox_maps.pigeons.*
import org.json.JSONArray
Expand Down Expand Up @@ -263,6 +267,50 @@ fun RenderedQueryOptions.toRenderedQueryOptions(): com.mapbox.maps.RenderedQuery
return com.mapbox.maps.RenderedQueryOptions(layerIds, filter?.toValue())
}

fun FeaturesetFeatureId.toFeaturesetFeatureId(): com.mapbox.maps.FeaturesetFeatureId {
return com.mapbox.maps.FeaturesetFeatureId(id, namespace)
}

@OptIn(MapboxExperimental::class)
fun FeaturesetDescriptor.toTypedFeaturesetDescriptor(): TypedFeaturesetDescriptor<FeatureState, com.mapbox.maps.interactions.FeaturesetFeature<FeatureState>>? {
featuresetId?.let {
return TypedFeaturesetDescriptor.Featureset(
featuresetId, importId
)
} ?: layerId?.let {
return TypedFeaturesetDescriptor.Layer(
layerId
)
}
return null
}

@OptIn(MapboxExperimental::class)
fun Map<String, Any?>.toFeatureState(): com.mapbox.maps.interactions.FeatureState {
val map = this
return FeatureState {
for ((key, value) in map) {
value?.let {
when (value) {
is String -> {
addStringState(key, value)
}
is Long -> {
addLongState(key, value)
}
is Double -> {
addDoubleState(key, value)
}
is Boolean -> {
addBooleanState(key, value)
}
else -> throw (RuntimeException("Unsupported (key, value): ($key, $value)"))
}
}
}
}
}

fun MapDebugOptions.toMapDebugOptions(): com.mapbox.maps.MapDebugOptions {
return com.mapbox.maps.MapDebugOptions.values()[data.ordinal]
}
Expand Down Expand Up @@ -379,6 +427,44 @@ fun CameraBoundsOptions.toCameraBoundsOptions(): com.mapbox.maps.CameraBoundsOpt
.minZoom(minZoom)
.build()

fun Geometry.toMap(): Map<String?, Any?> {
return when (this) {
is Point -> mapOf(
"type" to "Point",
"coordinates" to listOf(this.latitude(), this.longitude())
)
is LineString -> mapOf(
"type" to "LineString",
"coordinates" to this.coordinates().map { listOf(it.latitude(), it.longitude()) }
)
is Polygon -> mapOf(
"type" to "Polygon",
"coordinates" to this.coordinates().map { ring ->
ring.map { listOf(it.latitude(), it.longitude()) }
}
)
is MultiPoint -> mapOf(
"type" to "MultiPoint",
"coordinates" to this.coordinates().map { listOf(it.latitude(), it.longitude()) }
)
is MultiLineString -> mapOf(
"type" to "MultiLineString",
"coordinates" to this.coordinates().map { line ->
line.map { listOf(it.latitude(), it.longitude()) }
}
)
is MultiPolygon -> mapOf(
"type" to "MultiPolygon",
"coordinates" to this.coordinates().map { polygon ->
polygon.map { ring ->
ring.map { listOf(it.latitude(), it.longitude()) }
}
}
)
else -> throw IllegalArgumentException("Unsupported geometry type")
}
}

fun Map<String?, Any?>.toGeometry(): Geometry {
when {
this["type"] == "Point" -> {
Expand Down Expand Up @@ -504,6 +590,35 @@ fun com.mapbox.maps.QueriedRenderedFeature.toFLTQueriedRenderedFeature(): Querie
return QueriedRenderedFeature(queriedFeature.toFLTQueriedFeature(), layers)
}

fun com.mapbox.maps.FeaturesetFeatureId.toFLTFeaturesetFeatureId(): FeaturesetFeatureId {
return FeaturesetFeatureId(featureId, featureNamespace)
}

fun com.mapbox.maps.FeaturesetDescriptor.toFLTFeaturesetDescriptor(): FeaturesetDescriptor {
return FeaturesetDescriptor(featuresetId, importId, layerId)
}

@SuppressLint("RestrictedApi")
@OptIn(MapboxExperimental::class)
fun com.mapbox.maps.interactions.FeaturesetFeature<FeatureState>.toFLTFeaturesetFeature(): FeaturesetFeature {
return FeaturesetFeature(
id?.toFLTFeaturesetFeatureId(),
descriptor.toFeaturesetDescriptor().toFLTFeaturesetDescriptor(),
geometry.toMap(),
properties.toFilteredMap(),
JSONObject(state.asJsonString()).toFilteredMap()
)
}

@SuppressLint("RestrictedApi")
fun com.mapbox.maps.InteractionContext.toFLTMapContentGestureContext(): MapContentGestureContext {
return MapContentGestureContext(
ScreenCoordinate(screenCoordinate.x, screenCoordinate.y),
coordinateInfo.coordinate,
GestureState.ENDED
)
}

fun com.mapbox.maps.QueriedSourceFeature.toFLTQueriedSourceFeature(): QueriedSourceFeature {
return QueriedSourceFeature(queriedFeature.toFLTQueriedFeature())
}
Expand Down Expand Up @@ -594,6 +709,13 @@ fun JSONObject.toMap(): Map<String?, Any?> = keys().asSequence().associateWith {
}
}

@OptIn(MapboxExperimental::class)
fun JSONObject.toFilteredMap(): Map<String, Any?> {
return this.toMap()
.filterKeys { it != null } // Filter out null keys
.mapKeys { it.key!! }
}

fun Number.toLogicalPixels(context: Context): Double {
return this.toDouble() / context.resources.displayMetrics.density
}
Expand Down
Loading