diff --git a/CHANGELOG.md b/CHANGELOG.md index d54142a3f..1627f1854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt index d7f7d1fa4..ddcebbb02 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/Extentions.kt @@ -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 @@ -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 @@ -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 @@ -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>? { + featuresetId?.let { + return TypedFeaturesetDescriptor.Featureset( + featuresetId, importId + ) + } ?: layerId?.let { + return TypedFeaturesetDescriptor.Layer( + layerId + ) + } + return null +} + +@OptIn(MapboxExperimental::class) +fun Map.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] } @@ -379,6 +427,44 @@ fun CameraBoundsOptions.toCameraBoundsOptions(): com.mapbox.maps.CameraBoundsOpt .minZoom(minZoom) .build() +fun Geometry.toMap(): Map { + 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.toGeometry(): Geometry { when { this["type"] == "Point" -> { @@ -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.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()) } @@ -594,6 +709,13 @@ fun JSONObject.toMap(): Map = keys().asSequence().associateWith { } } +@OptIn(MapboxExperimental::class) +fun JSONObject.toFilteredMap(): Map { + 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 } diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapInterfaceController.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapInterfaceController.kt index be10e807b..844dc8263 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapInterfaceController.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapInterfaceController.kt @@ -5,12 +5,19 @@ import com.google.gson.Gson import com.mapbox.geojson.Feature import com.mapbox.geojson.Point import com.mapbox.maps.MapView +import com.mapbox.maps.MapboxDelicateApi +import com.mapbox.maps.MapboxExperimental import com.mapbox.maps.MapboxMap import com.mapbox.maps.TileCacheBudget import com.mapbox.maps.extension.observable.eventdata.MapLoadingErrorEventData +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.interactions.FeatureStateKey import com.mapbox.maps.mapbox_maps.pigeons.CanonicalTileID import com.mapbox.maps.mapbox_maps.pigeons.ConstrainMode import com.mapbox.maps.mapbox_maps.pigeons.FeatureExtensionValue +import com.mapbox.maps.mapbox_maps.pigeons.FeaturesetDescriptor +import com.mapbox.maps.mapbox_maps.pigeons.FeaturesetFeature +import com.mapbox.maps.mapbox_maps.pigeons.FeaturesetFeatureId import com.mapbox.maps.mapbox_maps.pigeons.MapDebugOptions import com.mapbox.maps.mapbox_maps.pigeons.MapOptions import com.mapbox.maps.mapbox_maps.pigeons.NorthOrientation @@ -27,6 +34,7 @@ import com.mapbox.maps.mapbox_maps.pigeons._MapInterface import com.mapbox.maps.mapbox_maps.pigeons._MapWidgetDebugOptions import com.mapbox.maps.mapbox_maps.pigeons._RenderedQueryGeometry import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadErrorListener +import org.json.JSONObject class MapInterfaceController( private val mapboxMap: MapboxMap, @@ -166,6 +174,30 @@ class MapInterfaceController( } } + @OptIn(MapboxExperimental::class) + override fun queryRenderedFeaturesForFeatureset( + featureset: FeaturesetDescriptor, + geometry: _RenderedQueryGeometry?, + filter: String?, + callback: (Result>) -> Unit + ) { + featureset.toTypedFeaturesetDescriptor()?.let { typedFeaturesetDescriptor -> + mapboxMap.queryRenderedFeatures( + typedFeaturesetDescriptor, + geometry?.toRenderedQueryGeometry(context), + filter?.let { Expression.fromRaw(filter) } + ) { + callback(Result.success(it.map { feature -> feature.toFLTFeaturesetFeature() }.toMutableList())) + } + } ?: { + callback( + Result.failure( + Throwable("Error querying rendered features for featureset: {featureId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId}}") + ) + ) + } + } + override fun querySourceFeatures( sourceId: String, options: SourceQueryOptions, @@ -253,6 +285,42 @@ class MapInterfaceController( } } + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun setFeatureStateForFeaturesetDescriptor( + featureset: FeaturesetDescriptor, + featureId: FeaturesetFeatureId, + state: Map, + callback: (Result) -> Unit + ) { + featureset.toTypedFeaturesetDescriptor()?.let { typedFeaturesetDescriptor -> + mapboxMap.setFeatureState( + typedFeaturesetDescriptor, + featureId.toFeaturesetFeatureId(), + state.toFeatureState() + ) { callback(Result.success(Unit)) } + } ?: { + callback(Result.failure(Throwable("Error setting feature state for feature $featureId from featureset {featuresetId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId}."))) + } + } + + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun setFeatureStateForFeaturesetFeature( + feature: FeaturesetFeature, + state: Map, + callback: (Result) -> Unit + ) { + feature.id?.let { + setFeatureStateForFeaturesetDescriptor( + feature.featureset, + it, + state, + callback + ) + } ?: { + callback(Result.failure(Throwable("Invalid feature id for the requested feature: $feature"))) + } + } + override fun getFeatureState( sourceId: String, sourceLayerId: String?, @@ -270,6 +338,40 @@ class MapInterfaceController( } } + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun getFeatureStateForFeaturesetDescriptor( + featureset: FeaturesetDescriptor, + featureId: FeaturesetFeatureId, + callback: (Result>) -> Unit + ) { + featureset.toTypedFeaturesetDescriptor()?.also { featuresetDescriptor -> + mapboxMap.getFeatureState( + featuresetDescriptor, + featureId.toFeaturesetFeatureId() + ) { + callback(Result.success(JSONObject(it.asJsonString()).toFilteredMap())) + } + } ?: { + callback(Result.failure(Throwable("Error getting feature state for feature $featureId from featureset: {featureId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId}}."))) + } + } + + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun getFeatureStateForFeaturesetFeature( + feature: FeaturesetFeature, + callback: (Result>) -> Unit + ) { + feature.id?.let { + getFeatureStateForFeaturesetDescriptor( + feature.featureset, + it, + callback + ) + } ?: { + callback(Result.failure(Throwable("Invalid feature id for the requested feature: $feature"))) + } + } + override fun removeFeatureState( sourceId: String, sourceLayerId: String?, @@ -279,13 +381,73 @@ class MapInterfaceController( ) { mapboxMap.removeFeatureState(sourceId, sourceLayerId, featureId, stateKey) { if (it.isError) { - callback(Result.failure(Throwable(it.error))) + callback(Result.failure(Throwable("Cannot remove feature state for the requested feature: $featureId"))) } else { callback(Result.success(Unit)) } } } + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun removeFeatureStateForFeaturesetDescriptor( + featureset: FeaturesetDescriptor, + featureId: FeaturesetFeatureId, + stateKey: String?, + callback: (Result) -> Unit + ) { + featureset.toTypedFeaturesetDescriptor()?.let { typedFeaturesetDescriptor -> + mapboxMap.removeFeatureState( + typedFeaturesetDescriptor, + featureId.toFeaturesetFeatureId(), + stateKey?.let { FeatureStateKey.create(it) } + ) { + if (it.isError) { + callback(Result.failure(Throwable("Cannot remove feature state for the requested featureset: {featureId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId} and featureID: $featureId."))) + } else { + callback(Result.success(Unit)) + } + } + } + } + + @OptIn(MapboxExperimental::class, MapboxDelicateApi::class) + override fun removeFeatureStateForFeaturesetFeature( + feature: FeaturesetFeature, + stateKey: String?, + callback: (Result) -> Unit + ) { + feature.id?.let { + removeFeatureStateForFeaturesetDescriptor( + feature.featureset, + it, + stateKey, + callback + ) + } ?: { + callback(Result.failure(Throwable("Invalid feature id for the requested feature: $feature"))) + } + } + + @OptIn(MapboxExperimental::class) + override fun resetFeatureStatesForFeatureset( + featureset: FeaturesetDescriptor, + callback: (Result) -> Unit + ) { + featureset.toTypedFeaturesetDescriptor()?.let { typedFeaturesetDescriptor -> + mapboxMap.resetFeatureStates( + typedFeaturesetDescriptor + ) { + if (it.isError) { + callback(Result.failure(Throwable("Error resetting feature states for the requested featureset: {featureId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId}}."))) + } else { + callback(Result.success(Unit)) + } + } + } ?: { + callback(Result.failure(Throwable("Failed to convert requested featureset: {featureId: ${featureset.featuresetId}, importId: ${featureset.importId}, layerId: ${featureset.layerId}}."))) + } + } + override fun reduceMemoryUse() { mapboxMap.reduceMemoryUse() } diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt index 06002819b..25b7e3f42 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt @@ -11,6 +11,9 @@ import androidx.lifecycle.setViewTreeLifecycleOwner import com.mapbox.bindgen.Value import com.mapbox.common.SettingsServiceFactory import com.mapbox.common.SettingsServiceStorageType +import com.mapbox.common.toValue +import com.mapbox.maps.ClickInteraction +import com.mapbox.maps.LongClickInteraction import com.mapbox.maps.MapInitOptions import com.mapbox.maps.MapView import com.mapbox.maps.MapboxMap @@ -24,6 +27,9 @@ import com.mapbox.maps.mapbox_maps.pigeons.ScaleBarSettingsInterface import com.mapbox.maps.mapbox_maps.pigeons.StyleManager import com.mapbox.maps.mapbox_maps.pigeons._AnimationManager import com.mapbox.maps.mapbox_maps.pigeons._CameraManager +import com.mapbox.maps.mapbox_maps.pigeons._InteractionPigeon +import com.mapbox.maps.mapbox_maps.pigeons._InteractionType +import com.mapbox.maps.mapbox_maps.pigeons._InteractionsListener import com.mapbox.maps.mapbox_maps.pigeons._LocationComponentSettingsInterface import com.mapbox.maps.mapbox_maps.pigeons._MapInterface import com.mapbox.maps.mapbox_maps.pigeons._ViewportMessenger @@ -235,6 +241,53 @@ class MapboxMapController( gestureController.removeListeners() result.success(null) } + "interactions#add_interaction" -> { + val listener = _InteractionsListener(messenger, channelSuffix) + val arguments: HashMap = call.arguments as? HashMap ?: return + val interactionList = arguments["interaction"] as? List ?: return + val interaction = _InteractionPigeon.fromList(interactionList) + val featuresetDescriptor = com.mapbox.maps.mapbox_maps.pigeons.FeaturesetDescriptor.fromList(interaction.featuresetDescriptor) + val interactionType = _InteractionType.valueOf(interaction.interactionType) + val stopPropagation = interaction.stopPropagation + val id = interaction.identifier.toLong() + val filter = interaction.filter.toValue() + val radius = interaction.radius + + featuresetDescriptor.featuresetId?.let { + when (interactionType) { + _InteractionType.TAP -> mapboxMap?.addInteraction( + ClickInteraction.featureset(id = it, importId = featuresetDescriptor.importId, filter = filter, radius = radius) { featuresetFeature, context -> + listener.onInteraction(context.toFLTMapContentGestureContext(), featuresetFeature.toFLTFeaturesetFeature(), id) { _ -> } + return@featureset stopPropagation + } + ) + _InteractionType.LONG_TAP -> mapboxMap?.addInteraction( + LongClickInteraction.featureset(id = it, importId = featuresetDescriptor.importId, filter = filter, radius = radius) { featuresetFeature, context -> + listener.onInteraction(context.toFLTMapContentGestureContext(), featuresetFeature.toFLTFeaturesetFeature(), id) { _ -> } + return@featureset stopPropagation + } + ) + null -> return + } + } ?: featuresetDescriptor.layerId?.let { + when (interactionType) { + _InteractionType.TAP -> mapboxMap?.addInteraction( + ClickInteraction.layer(id = it, filter = filter, radius = radius) { featuresetFeature, context -> + listener.onInteraction(context.toFLTMapContentGestureContext(), featuresetFeature.toFLTFeaturesetFeature(), id) { _ -> } + return@layer stopPropagation + } + ) + _InteractionType.LONG_TAP -> mapboxMap?.addInteraction( + LongClickInteraction.layer(id = it, filter = filter, radius = radius) { featuresetFeature, context -> + listener.onInteraction(context.toFLTMapContentGestureContext(), featuresetFeature.toFLTFeaturesetFeature(), id) { _ -> } + return@layer stopPropagation + } + ) + null -> return + } + } + result.success(null) + } "platform#releaseMethodChannels" -> { dispose() result.success(null) diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/StyleController.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/StyleController.kt index 255bf482a..7485a0167 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/StyleController.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/StyleController.kt @@ -19,6 +19,7 @@ import com.mapbox.maps.mapbox_maps.pigeons.CameraOptions import com.mapbox.maps.mapbox_maps.pigeons.CanonicalTileID import com.mapbox.maps.mapbox_maps.pigeons.CoordinateBounds import com.mapbox.maps.mapbox_maps.pigeons.DirectionalLight +import com.mapbox.maps.mapbox_maps.pigeons.FeaturesetDescriptor import com.mapbox.maps.mapbox_maps.pigeons.FlatLight import com.mapbox.maps.mapbox_maps.pigeons.ImageContent import com.mapbox.maps.mapbox_maps.pigeons.ImageStretches @@ -588,6 +589,10 @@ class StyleController(private val context: Context, private val styleManager: Ma callback(Result.success(Unit)) } + override fun getFeaturesets(): List { + return styleManager.styleManager.styleFeaturesets.map { it.toFLTFeaturesetDescriptor() } + } + override fun addStyleImage( imageId: String, scale: Double, diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/MapInterfaces.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/MapInterfaces.kt index 7e9927303..e4e43a21e 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/MapInterfaces.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/MapInterfaces.kt @@ -35,6 +35,10 @@ private fun wrapError(exception: Throwable): List { } } +private fun createConnectionError(channelName: String): FlutterError { + return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "") +} + /** * Error class for passing custom error details to Flutter via a thrown PlatformException. * @property code The error code. @@ -229,6 +233,20 @@ enum class ViewAnnotationAnchor(val raw: Int) { } } +/** The type of interaction, either tap/click or longTap/longClick */ +enum class _InteractionType(val raw: Int) { + /** A short tap or click */ + TAP(0), + /** A long tap or long click */ + LONG_TAP(1); + + companion object { + fun ofRaw(raw: Int): _InteractionType? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** Type information of the variant's content */ enum class Type(val raw: Int) { SCREEN_BOX(0), @@ -1344,6 +1362,246 @@ data class QueriedFeature( } } +/** + * Identifies a feature in a featureset. + * + * Knowing the feature identifier allows to set the feature states to a particular feature, see ``MapboxMap/setFeatureState(featureset:featureId:state:callback:)``. + * + * In a featureset a feature can come from different underlying sources. In that case their IDs are not guaranteed to be unique in the featureset. + * The ``FeaturesetFeatureId/namespace`` is used to disambiguate from which source the feature is coming. + * + * - Warning: There is no guarantee of identifier persistency. This depends on the underlying source of the features and may vary from style to style. + * If you want to store the identifiers persistently, please make sure that the style or source provides this guarantee. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class FeaturesetFeatureId( + /** A feature id coming from the feature itself.exp */ + val id: String, + /** A namespace of the feature */ + val namespace: String? = null +) { + companion object { + fun fromList(pigeonVar_list: List): FeaturesetFeatureId { + val id = pigeonVar_list[0] as String + val namespace = pigeonVar_list[1] as String? + return FeaturesetFeatureId(id, namespace) + } + } + fun toList(): List { + return listOf( + id, + namespace, + ) + } +} + +/** + * Wraps a FeatureState map + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class FeatureState( + val map: Map +) { + companion object { + fun fromList(pigeonVar_list: List): FeatureState { + val map = pigeonVar_list[0] as Map + return FeatureState(map) + } + } + fun toList(): List { + return listOf( + map, + ) + } +} + +/** + * Internal: An interaction that can be added to the map. + * + * To create an interaction use ``TapInteraction`` and ``LongClickInteraction`` implementations. + * + * See also: ``MapboxMap/addInteraction``. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class _Interaction( + /** The featureset descriptor that specifies the featureset to be included in the interaction. */ + val featuresetDescriptor: FeaturesetDescriptor, + /** The type of interaction, either tap or longTap */ + val interactionType: _InteractionType, + /** Whether to stop the propagation of the interaction to the map. Defaults to true. */ + val stopPropagation: Boolean, + /** An optional filter of features that should trigger the interaction. */ + val filter: String? = null, + /** Radius of a tappable area */ + val radius: Double? = null +) { + companion object { + fun fromList(pigeonVar_list: List): _Interaction { + val featuresetDescriptor = pigeonVar_list[0] as FeaturesetDescriptor + val interactionType = pigeonVar_list[1] as _InteractionType + val stopPropagation = pigeonVar_list[2] as Boolean + val filter = pigeonVar_list[3] as String? + val radius = pigeonVar_list[4] as Double? + return _Interaction(featuresetDescriptor, interactionType, stopPropagation, filter, radius) + } + } + fun toList(): List { + return listOf( + featuresetDescriptor, + interactionType, + stopPropagation, + filter, + radius, + ) + } +} + +/** + * Internal class to handle pigeon conversions for interactions. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class _InteractionPigeon( + /** The featureset descriptor that specifies the featureset to be included in the interaction. */ + val featuresetDescriptor: List, + /** Whether to stop the propagation of the interaction to the map */ + val stopPropagation: Boolean, + /** The type of interaction, either tap or longTap as a String */ + val interactionType: String, + /** An identifier for the interaction */ + val identifier: String, + /** An optional filter of features that should trigger the interaction. */ + val filter: String? = null, + /** Radius of a tappable area */ + val radius: Double? = null +) { + companion object { + fun fromList(pigeonVar_list: List): _InteractionPigeon { + val featuresetDescriptor = pigeonVar_list[0] as List + val stopPropagation = pigeonVar_list[1] as Boolean + val interactionType = pigeonVar_list[2] as String + val identifier = pigeonVar_list[3] as String + val filter = pigeonVar_list[4] as String? + val radius = pigeonVar_list[5] as Double? + return _InteractionPigeon(featuresetDescriptor, stopPropagation, interactionType, identifier, filter, radius) + } + } + fun toList(): List { + return listOf( + featuresetDescriptor, + stopPropagation, + interactionType, + identifier, + filter, + radius, + ) + } +} + +/** + * A featureset descriptor. + * + * The descriptor instance acts as a universal target for interactions or querying rendered features (see 'TapInteraction', 'LongTapInteraction') + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class FeaturesetDescriptor( + /** + * An optional unique identifier for the featureset within the style. + * This id is used to reference a specific featureset. + * + * * Note: If `featuresetId` is provided and valid, it takes precedence over `layerId`, + * * meaning `layerId` will not be considered even if it has a valid value. + */ + val featuresetId: String? = null, + /** + * An optional import id that is required if the featureset is defined within an imported style. + * If the featureset belongs to the current style, this field should be set to a null string. + * + * Note: `importId` is only applicable when used in conjunction with `featuresetId` + * and has no effect when used with `layerId`. + */ + val importId: String? = null, + /** + * An optional unique identifier for the layer within the current style. + * + * Note: If `featuresetId` is valid, `layerId` will be ignored even if it has a valid value. + * Additionally, `importId` does not apply when using `layerId`. + */ + val layerId: String? = null +) { + companion object { + fun fromList(pigeonVar_list: List): FeaturesetDescriptor { + val featuresetId = pigeonVar_list[0] as String? + val importId = pigeonVar_list[1] as String? + val layerId = pigeonVar_list[2] as String? + return FeaturesetDescriptor(featuresetId, importId, layerId) + } + } + fun toList(): List { + return listOf( + featuresetId, + importId, + layerId, + ) + } +} + +/** + * A basic feature of a featureset. + * + * If you use Standard Style, you can use typed alternatives like `StandardPoiFeature`, `StandardPlaceLabelsFeature`, `StandardBuildingsFeature`. + * + * The featureset feature is different to the `Turf.Feature`. The latter represents any GeoJSON feature, while the former is a high level representation of features. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class FeaturesetFeature( + /** + * An identifier of the feature. + * + * The identifier can be `nil` if the underlying source doesn't have identifiers for features. + * In this case it's impossible to set a feature state for an individual feature. + */ + val id: FeaturesetFeatureId? = null, + /** A featureset descriptor denoting the featureset this feature belongs to. */ + val featureset: FeaturesetDescriptor, + /** A feature geometry. */ + val geometry: Map, + /** Feature JSON properties. */ + val properties: Map, + /** + * A feature state. + * + * This is a **snapshot** of the state that the feature had when it was interacted with. + * To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + */ + val state: Map +) { + companion object { + fun fromList(pigeonVar_list: List): FeaturesetFeature { + val id = pigeonVar_list[0] as FeaturesetFeatureId? + val featureset = pigeonVar_list[1] as FeaturesetDescriptor + val geometry = pigeonVar_list[2] as Map + val properties = pigeonVar_list[3] as Map + val state = pigeonVar_list[4] as Map + return FeaturesetFeature(id, featureset, geometry, properties, state) + } + } + fun toList(): List { + return listOf( + id, + featureset, + geometry, + properties, + state, + ) + } +} + /** * Geometry for querying rendered features. * @@ -1853,285 +2111,330 @@ private open class MapInterfacesPigeonCodec : StandardMessageCodec() { } 137.toByte() -> { return (readValue(buffer) as Long?)?.let { - Type.ofRaw(it.toInt()) + _InteractionType.ofRaw(it.toInt()) } } 138.toByte() -> { return (readValue(buffer) as Long?)?.let { - FillExtrusionBaseAlignment.ofRaw(it.toInt()) + GestureState.ofRaw(it.toInt()) } } 139.toByte() -> { return (readValue(buffer) as Long?)?.let { - FillExtrusionHeightAlignment.ofRaw(it.toInt()) + Type.ofRaw(it.toInt()) } } 140.toByte() -> { return (readValue(buffer) as Long?)?.let { - BackgroundPitchAlignment.ofRaw(it.toInt()) + FillExtrusionBaseAlignment.ofRaw(it.toInt()) } } 141.toByte() -> { return (readValue(buffer) as Long?)?.let { - StylePackErrorType.ofRaw(it.toInt()) + FillExtrusionHeightAlignment.ofRaw(it.toInt()) } } 142.toByte() -> { return (readValue(buffer) as Long?)?.let { - ResponseErrorReason.ofRaw(it.toInt()) + BackgroundPitchAlignment.ofRaw(it.toInt()) } } 143.toByte() -> { return (readValue(buffer) as Long?)?.let { - OfflineRegionDownloadState.ofRaw(it.toInt()) + StylePackErrorType.ofRaw(it.toInt()) } } 144.toByte() -> { return (readValue(buffer) as Long?)?.let { - TileStoreUsageMode.ofRaw(it.toInt()) + ResponseErrorReason.ofRaw(it.toInt()) } } 145.toByte() -> { return (readValue(buffer) as Long?)?.let { - StylePropertyValueKind.ofRaw(it.toInt()) + OfflineRegionDownloadState.ofRaw(it.toInt()) } } 146.toByte() -> { return (readValue(buffer) as Long?)?.let { - StyleProjectionName.ofRaw(it.toInt()) + TileStoreUsageMode.ofRaw(it.toInt()) } } 147.toByte() -> { return (readValue(buffer) as Long?)?.let { - Anchor.ofRaw(it.toInt()) + StylePropertyValueKind.ofRaw(it.toInt()) } } 148.toByte() -> { return (readValue(buffer) as Long?)?.let { - HttpMethod.ofRaw(it.toInt()) + StyleProjectionName.ofRaw(it.toInt()) } } 149.toByte() -> { return (readValue(buffer) as Long?)?.let { - HttpRequestErrorType.ofRaw(it.toInt()) + Anchor.ofRaw(it.toInt()) } } 150.toByte() -> { return (readValue(buffer) as Long?)?.let { - DownloadErrorCode.ofRaw(it.toInt()) + HttpMethod.ofRaw(it.toInt()) } } 151.toByte() -> { return (readValue(buffer) as Long?)?.let { - DownloadState.ofRaw(it.toInt()) + HttpRequestErrorType.ofRaw(it.toInt()) } } 152.toByte() -> { return (readValue(buffer) as Long?)?.let { - TileRegionErrorType.ofRaw(it.toInt()) + DownloadErrorCode.ofRaw(it.toInt()) } } 153.toByte() -> { return (readValue(buffer) as Long?)?.let { - _MapEvent.ofRaw(it.toInt()) + DownloadState.ofRaw(it.toInt()) } } 154.toByte() -> { + return (readValue(buffer) as Long?)?.let { + TileRegionErrorType.ofRaw(it.toInt()) + } + } + 155.toByte() -> { + return (readValue(buffer) as Long?)?.let { + _MapEvent.ofRaw(it.toInt()) + } + } + 156.toByte() -> { return (readValue(buffer) as? List)?.let { PointDecoder.fromList(it) } } - 155.toByte() -> { + 157.toByte() -> { return (readValue(buffer) as? List)?.let { FeatureDecoder.fromList(it) } } - 156.toByte() -> { + 158.toByte() -> { return (readValue(buffer) as? List)?.let { GlyphsRasterizationOptions.fromList(it) } } - 157.toByte() -> { + 159.toByte() -> { return (readValue(buffer) as? List)?.let { TileCoverOptions.fromList(it) } } - 158.toByte() -> { + 160.toByte() -> { return (readValue(buffer) as? List)?.let { MbxEdgeInsets.fromList(it) } } - 159.toByte() -> { + 161.toByte() -> { return (readValue(buffer) as? List)?.let { CameraOptions.fromList(it) } } - 160.toByte() -> { + 162.toByte() -> { return (readValue(buffer) as? List)?.let { CameraState.fromList(it) } } - 161.toByte() -> { + 163.toByte() -> { return (readValue(buffer) as? List)?.let { CameraBoundsOptions.fromList(it) } } - 162.toByte() -> { + 164.toByte() -> { return (readValue(buffer) as? List)?.let { CameraBounds.fromList(it) } } - 163.toByte() -> { + 165.toByte() -> { return (readValue(buffer) as? List)?.let { MapAnimationOptions.fromList(it) } } - 164.toByte() -> { + 166.toByte() -> { return (readValue(buffer) as? List)?.let { CoordinateBounds.fromList(it) } } - 165.toByte() -> { + 167.toByte() -> { return (readValue(buffer) as? List)?.let { MapDebugOptions.fromList(it) } } - 166.toByte() -> { + 168.toByte() -> { return (readValue(buffer) as? List)?.let { TileCacheBudgetInMegabytes.fromList(it) } } - 167.toByte() -> { + 169.toByte() -> { return (readValue(buffer) as? List)?.let { TileCacheBudgetInTiles.fromList(it) } } - 168.toByte() -> { + 170.toByte() -> { return (readValue(buffer) as? List)?.let { MapOptions.fromList(it) } } - 169.toByte() -> { + 171.toByte() -> { return (readValue(buffer) as? List)?.let { ScreenCoordinate.fromList(it) } } - 170.toByte() -> { + 172.toByte() -> { return (readValue(buffer) as? List)?.let { ScreenBox.fromList(it) } } - 171.toByte() -> { + 173.toByte() -> { return (readValue(buffer) as? List)?.let { CoordinateBoundsZoom.fromList(it) } } - 172.toByte() -> { + 174.toByte() -> { return (readValue(buffer) as? List)?.let { Size.fromList(it) } } - 173.toByte() -> { + 175.toByte() -> { return (readValue(buffer) as? List)?.let { RenderedQueryOptions.fromList(it) } } - 174.toByte() -> { + 176.toByte() -> { return (readValue(buffer) as? List)?.let { SourceQueryOptions.fromList(it) } } - 175.toByte() -> { + 177.toByte() -> { return (readValue(buffer) as? List)?.let { FeatureExtensionValue.fromList(it) } } - 176.toByte() -> { + 178.toByte() -> { return (readValue(buffer) as? List)?.let { LayerPosition.fromList(it) } } - 177.toByte() -> { + 179.toByte() -> { return (readValue(buffer) as? List)?.let { QueriedRenderedFeature.fromList(it) } } - 178.toByte() -> { + 180.toByte() -> { return (readValue(buffer) as? List)?.let { QueriedSourceFeature.fromList(it) } } - 179.toByte() -> { + 181.toByte() -> { return (readValue(buffer) as? List)?.let { QueriedFeature.fromList(it) } } - 180.toByte() -> { + 182.toByte() -> { + return (readValue(buffer) as? List)?.let { + FeaturesetFeatureId.fromList(it) + } + } + 183.toByte() -> { + return (readValue(buffer) as? List)?.let { + FeatureState.fromList(it) + } + } + 184.toByte() -> { + return (readValue(buffer) as? List)?.let { + _Interaction.fromList(it) + } + } + 185.toByte() -> { + return (readValue(buffer) as? List)?.let { + _InteractionPigeon.fromList(it) + } + } + 186.toByte() -> { + return (readValue(buffer) as? List)?.let { + FeaturesetDescriptor.fromList(it) + } + } + 187.toByte() -> { + return (readValue(buffer) as? List)?.let { + FeaturesetFeature.fromList(it) + } + } + 188.toByte() -> { + return (readValue(buffer) as? List)?.let { + MapContentGestureContext.fromList(it) + } + } + 189.toByte() -> { return (readValue(buffer) as? List)?.let { _RenderedQueryGeometry.fromList(it) } } - 181.toByte() -> { + 190.toByte() -> { return (readValue(buffer) as? List)?.let { ProjectedMeters.fromList(it) } } - 182.toByte() -> { + 191.toByte() -> { return (readValue(buffer) as? List)?.let { MercatorCoordinate.fromList(it) } } - 183.toByte() -> { + 192.toByte() -> { return (readValue(buffer) as? List)?.let { StyleObjectInfo.fromList(it) } } - 184.toByte() -> { + 193.toByte() -> { return (readValue(buffer) as? List)?.let { StyleProjection.fromList(it) } } - 185.toByte() -> { + 194.toByte() -> { return (readValue(buffer) as? List)?.let { FlatLight.fromList(it) } } - 186.toByte() -> { + 195.toByte() -> { return (readValue(buffer) as? List)?.let { DirectionalLight.fromList(it) } } - 187.toByte() -> { + 196.toByte() -> { return (readValue(buffer) as? List)?.let { AmbientLight.fromList(it) } } - 188.toByte() -> { + 197.toByte() -> { return (readValue(buffer) as? List)?.let { MbxImage.fromList(it) } } - 189.toByte() -> { + 198.toByte() -> { return (readValue(buffer) as? List)?.let { ImageStretches.fromList(it) } } - 190.toByte() -> { + 199.toByte() -> { return (readValue(buffer) as? List)?.let { ImageContent.fromList(it) } } - 191.toByte() -> { + 200.toByte() -> { return (readValue(buffer) as? List)?.let { TransitionOptions.fromList(it) } } - 192.toByte() -> { + 201.toByte() -> { return (readValue(buffer) as? List)?.let { CanonicalTileID.fromList(it) } } - 193.toByte() -> { + 202.toByte() -> { return (readValue(buffer) as? List)?.let { StylePropertyValue.fromList(it) } @@ -2173,232 +2476,268 @@ private open class MapInterfacesPigeonCodec : StandardMessageCodec() { stream.write(136) writeValue(stream, value.raw) } - is Type -> { + is _InteractionType -> { stream.write(137) writeValue(stream, value.raw) } - is FillExtrusionBaseAlignment -> { + is GestureState -> { stream.write(138) writeValue(stream, value.raw) } - is FillExtrusionHeightAlignment -> { + is Type -> { stream.write(139) writeValue(stream, value.raw) } - is BackgroundPitchAlignment -> { + is FillExtrusionBaseAlignment -> { stream.write(140) writeValue(stream, value.raw) } - is StylePackErrorType -> { + is FillExtrusionHeightAlignment -> { stream.write(141) writeValue(stream, value.raw) } - is ResponseErrorReason -> { + is BackgroundPitchAlignment -> { stream.write(142) writeValue(stream, value.raw) } - is OfflineRegionDownloadState -> { + is StylePackErrorType -> { stream.write(143) writeValue(stream, value.raw) } - is TileStoreUsageMode -> { + is ResponseErrorReason -> { stream.write(144) writeValue(stream, value.raw) } - is StylePropertyValueKind -> { + is OfflineRegionDownloadState -> { stream.write(145) writeValue(stream, value.raw) } - is StyleProjectionName -> { + is TileStoreUsageMode -> { stream.write(146) writeValue(stream, value.raw) } - is Anchor -> { + is StylePropertyValueKind -> { stream.write(147) writeValue(stream, value.raw) } - is HttpMethod -> { + is StyleProjectionName -> { stream.write(148) writeValue(stream, value.raw) } - is HttpRequestErrorType -> { + is Anchor -> { stream.write(149) writeValue(stream, value.raw) } - is DownloadErrorCode -> { + is HttpMethod -> { stream.write(150) writeValue(stream, value.raw) } - is DownloadState -> { + is HttpRequestErrorType -> { stream.write(151) writeValue(stream, value.raw) } - is TileRegionErrorType -> { + is DownloadErrorCode -> { stream.write(152) writeValue(stream, value.raw) } - is _MapEvent -> { + is DownloadState -> { stream.write(153) writeValue(stream, value.raw) } - is Point -> { + is TileRegionErrorType -> { stream.write(154) + writeValue(stream, value.raw) + } + is _MapEvent -> { + stream.write(155) + writeValue(stream, value.raw) + } + is Point -> { + stream.write(156) writeValue(stream, value.toList()) } is Feature -> { - stream.write(155) + stream.write(157) writeValue(stream, value.toList()) } is GlyphsRasterizationOptions -> { - stream.write(156) + stream.write(158) writeValue(stream, value.toList()) } is TileCoverOptions -> { - stream.write(157) + stream.write(159) writeValue(stream, value.toList()) } is MbxEdgeInsets -> { - stream.write(158) + stream.write(160) writeValue(stream, value.toList()) } is CameraOptions -> { - stream.write(159) + stream.write(161) writeValue(stream, value.toList()) } is CameraState -> { - stream.write(160) + stream.write(162) writeValue(stream, value.toList()) } is CameraBoundsOptions -> { - stream.write(161) + stream.write(163) writeValue(stream, value.toList()) } is CameraBounds -> { - stream.write(162) + stream.write(164) writeValue(stream, value.toList()) } is MapAnimationOptions -> { - stream.write(163) + stream.write(165) writeValue(stream, value.toList()) } is CoordinateBounds -> { - stream.write(164) + stream.write(166) writeValue(stream, value.toList()) } is MapDebugOptions -> { - stream.write(165) + stream.write(167) writeValue(stream, value.toList()) } is TileCacheBudgetInMegabytes -> { - stream.write(166) + stream.write(168) writeValue(stream, value.toList()) } is TileCacheBudgetInTiles -> { - stream.write(167) + stream.write(169) writeValue(stream, value.toList()) } is MapOptions -> { - stream.write(168) + stream.write(170) writeValue(stream, value.toList()) } is ScreenCoordinate -> { - stream.write(169) + stream.write(171) writeValue(stream, value.toList()) } is ScreenBox -> { - stream.write(170) + stream.write(172) writeValue(stream, value.toList()) } is CoordinateBoundsZoom -> { - stream.write(171) + stream.write(173) writeValue(stream, value.toList()) } is Size -> { - stream.write(172) + stream.write(174) writeValue(stream, value.toList()) } is RenderedQueryOptions -> { - stream.write(173) + stream.write(175) writeValue(stream, value.toList()) } is SourceQueryOptions -> { - stream.write(174) + stream.write(176) writeValue(stream, value.toList()) } is FeatureExtensionValue -> { - stream.write(175) + stream.write(177) writeValue(stream, value.toList()) } is LayerPosition -> { - stream.write(176) + stream.write(178) writeValue(stream, value.toList()) } is QueriedRenderedFeature -> { - stream.write(177) + stream.write(179) writeValue(stream, value.toList()) } is QueriedSourceFeature -> { - stream.write(178) + stream.write(180) writeValue(stream, value.toList()) } is QueriedFeature -> { - stream.write(179) + stream.write(181) + writeValue(stream, value.toList()) + } + is FeaturesetFeatureId -> { + stream.write(182) + writeValue(stream, value.toList()) + } + is FeatureState -> { + stream.write(183) + writeValue(stream, value.toList()) + } + is _Interaction -> { + stream.write(184) + writeValue(stream, value.toList()) + } + is _InteractionPigeon -> { + stream.write(185) + writeValue(stream, value.toList()) + } + is FeaturesetDescriptor -> { + stream.write(186) + writeValue(stream, value.toList()) + } + is FeaturesetFeature -> { + stream.write(187) + writeValue(stream, value.toList()) + } + is MapContentGestureContext -> { + stream.write(188) writeValue(stream, value.toList()) } is _RenderedQueryGeometry -> { - stream.write(180) + stream.write(189) writeValue(stream, value.toList()) } is ProjectedMeters -> { - stream.write(181) + stream.write(190) writeValue(stream, value.toList()) } is MercatorCoordinate -> { - stream.write(182) + stream.write(191) writeValue(stream, value.toList()) } is StyleObjectInfo -> { - stream.write(183) + stream.write(192) writeValue(stream, value.toList()) } is StyleProjection -> { - stream.write(184) + stream.write(193) writeValue(stream, value.toList()) } is FlatLight -> { - stream.write(185) + stream.write(194) writeValue(stream, value.toList()) } is DirectionalLight -> { - stream.write(186) + stream.write(195) writeValue(stream, value.toList()) } is AmbientLight -> { - stream.write(187) + stream.write(196) writeValue(stream, value.toList()) } is MbxImage -> { - stream.write(188) + stream.write(197) writeValue(stream, value.toList()) } is ImageStretches -> { - stream.write(189) + stream.write(198) writeValue(stream, value.toList()) } is ImageContent -> { - stream.write(190) + stream.write(199) writeValue(stream, value.toList()) } is TransitionOptions -> { - stream.write(191) + stream.write(200) writeValue(stream, value.toList()) } is CanonicalTileID -> { - stream.write(192) + stream.write(201) writeValue(stream, value.toList()) } is StylePropertyValue -> { - stream.write(193) + stream.write(202) writeValue(stream, value.toList()) } else -> super.writeValue(stream, value) @@ -3075,6 +3414,31 @@ interface _CameraManager { } } } +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class _InteractionsListener(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by _InteractionsListener. */ + val codec: MessageCodec by lazy { + MapInterfacesPigeonCodec() + } + } + fun onInteraction(contextArg: MapContentGestureContext, featureArg: FeaturesetFeature, interactionIDArg: Long, callback: (Result) -> Unit) { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(contextArg, featureArg, interactionIDArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} /** * Map class provides map rendering functionality. * @@ -3173,6 +3537,20 @@ interface _MapInterface { * @return A `cancelable` object that could be used to cancel the pending query. */ fun queryRenderedFeatures(geometry: _RenderedQueryGeometry, options: RenderedQueryOptions, callback: (Result>) -> Unit) + /** + * Queries the map for rendered features with one typed featureset. + * + * The results array will contain features of the type specified by this featureset. + * + * - Important: If you need to handle basic gestures on map content, + * please prefer to use Interactions API, see `MapboxMap/addInteraction`. + * + * @param featureset A typed featureset to query with. + * @param geometry An optional screen geometry to query. Can be a `CGPoint`, `CGRect`, or an array of `CGPoint`. + * If omitted, the full viewport is queried. + * @param filter An additional filter for features. + */ + fun queryRenderedFeaturesForFeatureset(featureset: FeaturesetDescriptor, geometry: _RenderedQueryGeometry?, filter: String?, callback: (Result>) -> Unit) /** * Queries the map for source features. * @@ -3235,6 +3613,26 @@ interface _MapInterface { * @param state The `state` object with properties to update with their respective new values. */ fun setFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, state: String, callback: (Result) -> Unit) + /** + * Update the state map of a feature within a featureset. + * Update entries in the state map of a given feature within a style source. Only entries listed in the state map + * will be updated. An entry in the feature state map that is not listed in `state` will retain its previous value. + * + * @param featureset The featureset to look the feature in. + * @param featureId Identifier of the feature whose state should be updated. + * @param state Map of entries to update with their respective new values + */ + fun setFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, state: Map, callback: (Result) -> Unit) + /** + * Update the state map of an individual feature. + * + * The feature should have a non-nil ``FeaturesetFeatureType/id``. Otherwise, + * the operation will be no-op and callback will receive an error. + * + * @param feature The feature to update. + * @param state Map of entries to update with their respective new values + */ + fun setFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, state: Map, callback: (Result) -> Unit) /** * Gets the state map of a feature within a style source. * @@ -3244,9 +3642,27 @@ interface _MapInterface { * @param sourceId The style source identifier. * @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). * @param featureId The feature identifier of the feature whose state should be queried. - * @param completion The `query feature state completion` called when the query completes. + * + * @return A String representing the Feature's state map. */ fun getFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, callback: (Result) -> Unit) + /** + * Get the state map of a feature within a style source. + * + * @param featureset A featureset the feature belongs to. + * @param featureId Identifier of the feature whose state should be queried. + * + * @return The Feature's state map or an empty map if the feature could not be found. + */ + fun getFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, callback: (Result>) -> Unit) + /** + * Get the state map of a feature within a style source. + * + * @param feature An interactive feature to query the state of. + * + * @return The Feature's state map or an empty map if the feature could not be found. + */ + fun getFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, callback: (Result>) -> Unit) /** * Removes entries from a feature state object. * @@ -3254,7 +3670,7 @@ interface _MapInterface { * `stateKey`. * * Note that updates to feature state are asynchronous, so changes made by this method might not be - * immediately visible using `getStateFeature`. + * immediately visible using `getFeatureState`. * * @param sourceId The style source identifier. * @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). @@ -3262,6 +3678,32 @@ interface _MapInterface { * @param stateKey The key of the property to remove. If `null`, all feature's state object properties are removed. */ fun removeFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, stateKey: String?, callback: (Result) -> Unit) + /** + * Removes entries from a feature state object of a feature in the specified featureset. + * Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + * + * @param featureset A featureset the feature belongs to. + * @param featureId Identifier of the feature whose state should be removed. + * @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + */ + fun removeFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, stateKey: String?, callback: (Result) -> Unit) + /** + * Removes entries from a specified Feature. + * Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + * + * @param feature An interactive feature to update. + * @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + */ + fun removeFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, stateKey: String?, callback: (Result) -> Unit) + /** + * Reset all the feature states within a featureset. + * + * Note that updates to feature state are asynchronous, so changes made by this method might not be + * immediately visible using ``MapboxMap/getFeatureState()``. + * + * @param featureset A featureset descriptor + */ + fun resetFeatureStatesForFeatureset(featureset: FeaturesetDescriptor, callback: (Result) -> Unit) /** Reduces memory use. Useful to call when the application gets paused or sent to background. */ fun reduceMemoryUse() /** @@ -3669,6 +4111,28 @@ interface _MapInterface { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.queryRenderedFeaturesForFeatureset$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featuresetArg = args[0] as FeaturesetDescriptor + val geometryArg = args[1] as _RenderedQueryGeometry? + val filterArg = args[2] as String? + api.queryRenderedFeaturesForFeatureset(featuresetArg, geometryArg, filterArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.querySourceFeatures$separatedMessageChannelSuffix", codec) if (api != null) { @@ -3777,6 +4241,47 @@ interface _MapInterface { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetDescriptor$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featuresetArg = args[0] as FeaturesetDescriptor + val featureIdArg = args[1] as FeaturesetFeatureId + val stateArg = args[2] as Map + api.setFeatureStateForFeaturesetDescriptor(featuresetArg, featureIdArg, stateArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + reply.reply(wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetFeature$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featureArg = args[0] as FeaturesetFeature + val stateArg = args[1] as Map + api.setFeatureStateForFeaturesetFeature(featureArg, stateArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + reply.reply(wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureState$separatedMessageChannelSuffix", codec) if (api != null) { @@ -3799,6 +4304,47 @@ interface _MapInterface { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetDescriptor$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featuresetArg = args[0] as FeaturesetDescriptor + val featureIdArg = args[1] as FeaturesetFeatureId + api.getFeatureStateForFeaturesetDescriptor(featuresetArg, featureIdArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetFeature$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featureArg = args[0] as FeaturesetFeature + api.getFeatureStateForFeaturesetFeature(featureArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureState$separatedMessageChannelSuffix", codec) if (api != null) { @@ -3821,6 +4367,66 @@ interface _MapInterface { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetDescriptor$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featuresetArg = args[0] as FeaturesetDescriptor + val featureIdArg = args[1] as FeaturesetFeatureId + val stateKeyArg = args[2] as String? + api.removeFeatureStateForFeaturesetDescriptor(featuresetArg, featureIdArg, stateKeyArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + reply.reply(wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetFeature$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featureArg = args[0] as FeaturesetFeature + val stateKeyArg = args[1] as String? + api.removeFeatureStateForFeaturesetFeature(featureArg, stateKeyArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + reply.reply(wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.resetFeatureStatesForFeatureset$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featuresetArg = args[0] as FeaturesetDescriptor + api.resetFeatureStatesForFeatureset(featuresetArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + reply.reply(wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.reduceMemoryUse$separatedMessageChannelSuffix", codec) if (api != null) { @@ -5025,6 +5631,12 @@ interface StyleManager { * @param layerIds The ids of layers that will localize on, default is null which means will localize all the feasible layers. */ fun localizeLabels(locale: String, layerIds: List?, callback: (Result) -> Unit) + /** + * Returns the available featuresets in the currently loaded style. + * + * - Note: This function should only be called after the style is fully loaded; otherwise, the result may be unreliable. + */ + fun getFeaturesets(): List companion object { /** The codec used by StyleManager. */ @@ -6136,6 +6748,21 @@ interface StyleManager { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter.StyleManager.getFeaturesets$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getFeaturesets()) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/example/assets/featuresetsStyle.json b/example/assets/featuresetsStyle.json new file mode 100644 index 000000000..d9d37932a --- /dev/null +++ b/example/assets/featuresetsStyle.json @@ -0,0 +1,166 @@ +{ + "version": 8, + "imports": [ + { + "id": "nested", + "url": "", + "config": {}, + "data": { + "version": 8, + "featuresets": { + "poi" : { + "selectors": [ + { + "layer": "poi-label-1", + "properties": { + "type": [ "get", "type" ], + "name": [ "get", "name" ], + "class": "poi" + }, + "featureNamespace": "A" + } + ] + } + }, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "filter": "true", + "name": "nest1", + "type": "A" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0.01, 0.01 ] + }, + "id": 11 + }, + { + "type": "Feature", + "properties": { + "name": "nest2", + "type": "B" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0.01, 0.01 ] + }, + "id": 12 + }, + { + "type": "Feature", + "properties": { + "name": "nest3", + "type": "B" + }, + "geometry": { + "type": "Point", + "coordinates": [ -0.05, -0.05 ] + }, + "id": 13 + } + ] + } + } + }, + "layers": [ + { + "id": "poi-label-1", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 5, + "circle-color": "red" + } + } + ] + } + } + ], + "sources": { + "geojson": { + "type": "geojson", + "promoteId": "foo", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "foo": 1 + }, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + ] + } + }, + "geojson-2": { + "type": "geojson", + "promoteId": "bar", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "bar": 1 + }, + "geometry": { + "type": "Point", + "coordinates": [ 0.01, 0.01 ] + } + }, + { + "type": "Feature", + "properties": { + "bar": 2, + "filter": true, + "name": "qux" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0.01, 0.01 ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "background-color": "green" + }, + { + "id": "circle-1", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 5, + "circle-color": "black" + } + }, + { + "id": "circle-2", + "type": "circle", + "source": "geojson-2", + "paint": { + "circle-radius": 3, + "circle-color": "blue" + } + } + ] +} \ No newline at end of file diff --git a/example/integration_test/all_test.dart b/example/integration_test/all_test.dart index ccbe22c80..b51309988 100644 --- a/example/integration_test/all_test.dart +++ b/example/integration_test/all_test.dart @@ -48,6 +48,7 @@ import 'scale_bar_test.dart' as scale_bar_test; import 'offline_test.dart' as offline_test; import 'snapshotter/snapshotter_test.dart' as snapshotter_test; import 'viewport_test.dart' as viewport_test; +import 'interactive_features_test.dart' as interactive_features_test; void main() { animation_test.main(); @@ -74,6 +75,7 @@ void main() { // style tests style_test.main(); + interactive_features_test.main(); // layer tests background_layer_test.main(); diff --git a/example/integration_test/empty_map_widget.dart b/example/integration_test/empty_map_widget.dart index 70a2c921d..ff93d0e14 100644 --- a/example/integration_test/empty_map_widget.dart +++ b/example/integration_test/empty_map_widget.dart @@ -28,7 +28,8 @@ Future main( {double? width, double? height, CameraOptions? camera, - ViewportState? viewport}) { + ViewportState? viewport, + Alignment alignment = Alignment.topLeft}) { final completer = Completer(); MapboxOptions.setAccessToken(ACCESS_TOKEN); diff --git a/example/integration_test/interactive_features_test.dart b/example/integration_test/interactive_features_test.dart new file mode 100644 index 000000000..241d5b931 --- /dev/null +++ b/example/integration_test/interactive_features_test.dart @@ -0,0 +1,225 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; +import 'empty_map_widget.dart' as app; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('test_featureset_QRF', (WidgetTester tester) async { + // load style and position camera + final mapFuture = app.main( + width: 200, + height: 200, + camera: + CameraOptions(center: Point(coordinates: Position(0, 0)), zoom: 10), + alignment: Alignment(100, 100)); + await tester.pumpAndSettle(); + final mapboxMap = await mapFuture; + var styleJson = await rootBundle.loadString('assets/featuresetsStyle.json'); + mapboxMap.style.setStyleJSON(styleJson); + + await Future.delayed(Duration(seconds: 4)); + + // test queryRenderedFeaturesForFeatureset + var coord = await mapboxMap + .pixelForCoordinate(Point(coordinates: Position(0.01, 0.01))); + var featuresetQuery = await mapboxMap.queryRenderedFeaturesForFeatureset( + featureset: + FeaturesetDescriptor(featuresetId: "poi", importId: "nested"), + geometry: RenderedQueryGeometry.fromScreenCoordinate(coord)); + + expect(featuresetQuery.length, 2); + expect(featuresetQuery.first.properties["name"], "nest2"); + expect(featuresetQuery[1].properties["name"], "nest1"); + expect(featuresetQuery[0].properties["class"], "poi"); + + // test queryRenderedFeaturesForFeatureset with filter + var filter = '["==",["get", "type"], "A"]'; + var featuresetFilterQuery = + await mapboxMap.queryRenderedFeaturesForFeatureset( + featureset: + FeaturesetDescriptor(featuresetId: "poi", importId: "nested"), + geometry: RenderedQueryGeometry.fromScreenCoordinate(coord), + filter: filter); + + expect(featuresetFilterQuery.length, 1); + expect(featuresetFilterQuery[0].properties["name"], "nest1"); + expect(featuresetFilterQuery[0].properties["class"], "poi"); + + //test queryRenderedFeatures for full viewport + var viewportQuery = await mapboxMap.queryRenderedFeaturesForFeatureset( + featureset: + FeaturesetDescriptor(featuresetId: "poi", importId: "nested")); + + expect(viewportQuery.length, 3); + expect(viewportQuery[0].properties["name"], "nest2"); + expect(viewportQuery[1].properties["name"], "nest1"); + expect(viewportQuery[2].properties["name"], "nest3"); + expect(viewportQuery[2].properties["class"], "poi"); + }); + + testWidgets('test_featurestate_methods', (WidgetTester tester) async { + // load style and position camera + final mapFuture = app.main( + width: 200, + height: 200, + camera: + CameraOptions(center: Point(coordinates: Position(0, 0)), zoom: 10), + alignment: Alignment(100, 100)); + await tester.pumpAndSettle(); + final mapboxMap = await mapFuture; + var styleJson = await rootBundle.loadString('assets/featuresetsStyle.json'); + mapboxMap.style.setStyleJSON(styleJson); + + await app.events.onMapLoaded.future; + + var feature = FeaturesetFeature( + id: FeaturesetFeatureId(id: "11", namespace: "A"), + featureset: + FeaturesetDescriptor(featuresetId: "poi", importId: "nested"), + geometry: Point(coordinates: Position(0.01, 0.01)).toJson(), + properties: {}, + state: {}); + var state = FeatureState(map: { + "highlight": true, + }); + + // test set and get featurestate + await mapboxMap.setFeatureStateForFeaturesetFeature(feature, state); + var returnedFeatureState = + await mapboxMap.getFeatureStateForFeaturesetFeature(feature); + expect(returnedFeatureState, state.map); + + // test remove featurestate + await mapboxMap.removeFeatureStateForFeaturesetFeature( + feature: feature, stateKey: "highlight"); + var returnedFeatureState2 = + await mapboxMap.getFeatureStateForFeaturesetFeature(feature); + expect(returnedFeatureState2, {}); + + // test reset featurestate + await Future.delayed(Duration(seconds: 5)); + await mapboxMap.setFeatureStateForFeaturesetFeature(feature, state); + var returnedFeatureState3 = + await mapboxMap.getFeatureStateForFeaturesetFeature(feature); + expect(returnedFeatureState3, state.map); + + await mapboxMap.resetFeatureStatesForFeatureset( + FeaturesetDescriptor(featuresetId: "poi", importId: "nested")); + var returnedFeatureState4 = + await mapboxMap.getFeatureStateForFeaturesetFeature(feature); + expect(returnedFeatureState4, {}); + }); + + testWidgets('test_featurestate_descriptor_methods', + (WidgetTester tester) async { + // load style and position camera + final mapFuture = app.main( + width: 200, + height: 200, + camera: + CameraOptions(center: Point(coordinates: Position(0, 0)), zoom: 10), + alignment: Alignment(100, 100)); + await tester.pumpAndSettle(); + final mapboxMap = await mapFuture; + var styleJson = await rootBundle.loadString('assets/featuresetsStyle.json'); + mapboxMap.style.setStyleJSON(styleJson); + + await app.events.onMapLoaded.future; + + var featuresetDescriptor = + FeaturesetDescriptor(featuresetId: "poi", importId: "nested"); + var featuresetID = FeaturesetFeatureId(id: "11", namespace: "A"); + var state = FeatureState(map: { + "highlight": true, + }); + + await Future.delayed(Duration(seconds: 1)); + + // test set and get featurestate + await mapboxMap.setFeatureStateForFeaturesetDescriptor( + featuresetDescriptor, featuresetID, state); + var returnedFeatureState = + await mapboxMap.getFeatureStateForFeaturesetDescriptor( + featuresetDescriptor, featuresetID); + expect(returnedFeatureState, state.map); + + // test remove featurestate + await mapboxMap.removeFeatureStateForFeaturesetDescriptor( + featureset: featuresetDescriptor, + featureId: featuresetID, + stateKey: "highlight"); + var returnedFeatureState2 = + await mapboxMap.getFeatureStateForFeaturesetDescriptor( + featuresetDescriptor, featuresetID); + expect(returnedFeatureState2, {}); + }); + + testWidgets('test_state_is_queried', (WidgetTester tester) async { + // load style and position camera + final mapFuture = app.main( + width: 200, + height: 200, + camera: + CameraOptions(center: Point(coordinates: Position(0, 0)), zoom: 10), + alignment: Alignment(100, 100)); + await tester.pumpAndSettle(); + final mapboxMap = await mapFuture; + var styleJson = await rootBundle.loadString('assets/featuresetsStyle.json'); + mapboxMap.style.setStyleJSON(styleJson); + + await app.events.onMapLoaded.future; + + var featuresetID = FeaturesetFeatureId(id: "11", namespace: "A"); + var featuresetDescriptor = + FeaturesetDescriptor(featuresetId: "poi", importId: "nested"); + var state = FeatureState(map: { + "hide": true, + }); + var filter = '["==",["get", "type"], "A"]'; + Map expectedProperties = { + "name": "nest1", + "type": "A", + "class": "poi" + }; + + await mapboxMap.setFeatureStateForFeaturesetDescriptor( + featuresetDescriptor, featuresetID, state); + var queryResult = await mapboxMap.queryRenderedFeaturesForFeatureset( + featureset: featuresetDescriptor, filter: filter); + var poi = queryResult.first; + var point = Point.decode(poi.geometry); + + expect(queryResult.length, 1); + expect(poi.id?.id, featuresetID.id); + expect(poi.id?.namespace, featuresetID.namespace); + expect(poi.state, state.map); + expect(point.coordinates.lat, closeTo(0.01, 0.05)); + expect(point.coordinates.lng, closeTo(0.01, 0.05)); + expect(poi.properties, expectedProperties); + }); + + testWidgets('test_getFeaturesets', (WidgetTester tester) async { + // load style and position camera + final mapFuture = app.main( + width: 200, + height: 200, + camera: + CameraOptions(center: Point(coordinates: Position(0, 0)), zoom: 10), + alignment: Alignment(100, 100)); + await tester.pumpAndSettle(); + final mapboxMap = await mapFuture; + var styleJson = await rootBundle.loadString('assets/featuresetsStyle.json'); + mapboxMap.style.setStyleJSON(styleJson); + + await app.events.onMapLoaded.future; + + var returnedFeaturesets = await mapboxMap.style.getFeaturesets(); + + expect(returnedFeaturesets.length, 1); + expect(returnedFeaturesets.first.importId, "nested"); + }); +} diff --git a/example/lib/interactive_features_example.dart b/example/lib/interactive_features_example.dart new file mode 100644 index 000000000..a212b5c49 --- /dev/null +++ b/example/lib/interactive_features_example.dart @@ -0,0 +1,82 @@ +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; +import 'example.dart'; + +class InteractiveFeaturesExample extends StatefulWidget implements Example { + @override + final Widget leading = const Icon(Icons.touch_app); + @override + final String title = 'Interactive Features'; + @override + final String? subtitle = 'Tap a building to highlight it or a POI to hide it'; + + const InteractiveFeaturesExample({super.key}); + + @override + State createState() => InteractiveFeaturesState(); +} + +class InteractiveFeaturesState extends State { + InteractiveFeaturesState(); + MapboxMap? mapboxMap; + + _onMapCreated(MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + mapboxMap.style; + + /// Define interactions for 3D Buildings + + // Define a state to highlight the building when it is interacted with + StandardBuildingState featureState = StandardBuildingState(highlight: true); + + // Define a tap interaction targeting the Buildings featureset in the Standard style + // Set the action to occur when a building is tapped (highlight it) + var tapInteraction = TapInteraction(StandardBuildings(), (_, feature) { + mapboxMap.setFeatureStateForFeaturesetFeature(feature, featureState); + log("Building group: ${feature.group}"); + }); + + // Add the tap interaction to the map + mapboxMap.addInteraction(tapInteraction); + + // On long tap, remove the highlight state + mapboxMap + .addInteraction(LongTapInteraction(StandardBuildings(), (_, feature) { + mapboxMap + .removeFeatureStateForFeaturesetFeature(feature: feature) + .then((value) => log("Feature state removed for: ${feature.id?.id}.")) + .catchError((error) => log( + "Error removing feature state for ${feature.id?.id}, error: $error")); + })); + + /// Define interactions for Points of Interest + + // Define a tap interaction targeting the POI featureset in the Standard style, including a click radius + // Do not stop propagation of the click event to lower layers + var tapInteractionPOI = TapInteraction(StandardPOIs(), (_, feature) { + mapboxMap.setFeatureStateForFeaturesetFeature( + feature, StandardPOIsState(hide: true)); + log("POI feature name: ${feature.name}"); + }, radius: 10, stopPropagation: false); + + // Define a state to hide the POI when it is interacted with + mapboxMap.addInteraction(tapInteractionPOI); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: MapWidget( + key: ValueKey("mapWidget"), + cameraOptions: CameraOptions( + center: Point(coordinates: Position(24.9453, 60.1718)), + bearing: 49.92, + zoom: 16.35, + pitch: 40), + styleUri: MapboxStyles.STANDARD_EXPERIMENTAL, + textureView: true, + onMapCreated: _onMapCreated, + )); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 04f97880f..b64bb6b35 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -10,6 +10,7 @@ import 'package:mapbox_maps_example/ornaments_example.dart'; import 'package:mapbox_maps_example/geojson_line_example.dart'; import 'package:mapbox_maps_example/image_source_example.dart'; import 'package:mapbox_maps_example/map_interface_example.dart'; +import 'package:mapbox_maps_example/interactive_features_example.dart'; import 'package:mapbox_maps_example/polygon_annotations_example.dart'; import 'package:mapbox_maps_example/polyline_annotations_example.dart'; import 'package:mapbox_maps_example/simple_map_example.dart'; @@ -41,6 +42,7 @@ final List _allPages = [ SpinningGlobeExample(), FullMapExample(), StyleExample(), + InteractiveFeaturesExample(), CameraExample(), ProjectionExample(), MapInterfaceExample(), diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Extensions.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Extensions.swift index 407238310..284dc31d8 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Extensions.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Extensions.swift @@ -154,6 +154,95 @@ extension RenderedQueryOptions { return MapboxCoreMaps.RenderedQueryOptions(layerIds: layerIds?.compactMap { $0 }, filter: filterExp) } } + +extension String { + func toExp() throws -> Exp { + guard let data = self.data(using: .utf8) else { + throw DecodingError.valueNotFound(String.self, DecodingError.Context(codingPath: [], debugDescription: "Could not decode string to Exp")) + } + return try JSONDecoder().decode(Expression.self, from: data) + } +} + +extension _RenderedQueryGeometry: RenderedQueryGeometryConvertible { + var geometry: MapboxMaps.RenderedQueryGeometry { + switch type { + case .sCREENBOX: + guard let cgRect = convertValueToCGRect(value) else { fallthrough } + return RenderedQueryGeometry(boundingBox: cgRect) + case .sCREENCOORDINATE: + guard let cgPoint = convertValueToCGPoint(value) else { fallthrough } + return RenderedQueryGeometry(point: cgPoint) + case .lIST: + guard let cgPoints = convertValueToCGPoints(value) else { fallthrough } + return RenderedQueryGeometry(shape: cgPoints) + default: + return RenderedQueryGeometry(shape: []) + } + } +} + +func convertValueToCGRect(_ value: String) -> CGRect? { + let screenBoxArray = convertStringToDictionary(properties: value) + guard let minCoord = screenBoxArray["min"] as? [String: Double] else { return nil } + guard let maxCoord = screenBoxArray["max"] as? [String: Double] else { return nil } + guard let minX = minCoord["x"], let minY = minCoord["y"], + let maxX = maxCoord["x"], let maxY = maxCoord["y"] else { + return nil + } + let screenBox = ScreenBox(min: ScreenCoordinate(x: minX, y: minY), + max: ScreenCoordinate(x: maxX, y: maxY)) + return screenBox.toCGRect() +} + +func convertValueToCGPoint(_ value: String) -> CGPoint? { + guard let pointDict = convertStringToDictionary(properties: value) as? [String: Double], + let x = pointDict["x"], let y = pointDict["y"] else { + return nil + } + return CGPoint(x: x, y: y) +} + +func convertValueToCGPoints(_ value: String) -> [CGPoint]? { + guard let data = value.data(using: .utf8), + let rawPoints = try? JSONDecoder().decode([[String: Double]].self, from: data) else { + return nil + } + let cgPoints = rawPoints.compactMap { + guard let x = $0["x"], let y = $0["y"] else { return Optional.none } + return CGPoint(x: x, y: y) + } + return cgPoints +} + +extension FeaturesetFeatureId { + func toMapFeaturesetFeatureId() -> MapboxMaps.FeaturesetFeatureId { + return MapboxMaps.FeaturesetFeatureId(id: id, namespace: namespace) + } +} + +extension FeaturesetDescriptor { + func toMapFeaturesetDescriptor() -> MapboxMaps.FeaturesetDescriptor { + if let featuresetId { + return MapboxMaps.FeaturesetDescriptor.featureset(featuresetId, importId: importId) + } else { + return MapboxMaps.FeaturesetDescriptor.layer(layerId ?? "layer") + } + } +} + +extension FeaturesetFeature { + func toMapFeaturesetFeature() -> MapboxMaps.FeaturesetFeature { + var feature = Feature(geometry: convertDictionaryToGeometry(dict: geometry)) + feature.properties = JSONObject.init(turfRawValue: properties) + return MapboxMaps.FeaturesetFeature( + id: id?.toMapFeaturesetFeatureId(), + featureset: featureset.toMapFeaturesetDescriptor(), + geoJsonFeature: feature, + state: JSONObject.init(turfRawValue: state)!) + } +} + extension MercatorCoordinate { func toMercatorCoordinate() -> MapboxMaps.MercatorCoordinate { return MapboxMaps.MercatorCoordinate(x: x, y: y) @@ -376,7 +465,9 @@ extension MapboxMaps.QueriedSourceFeature { } extension MapboxMaps.QueriedRenderedFeature { func toFLTQueriedRenderedFeature() -> QueriedRenderedFeature { - return QueriedRenderedFeature(queriedFeature: queriedFeature.toFLTQueriedFeature(), layers: layers) + return QueriedRenderedFeature( + queriedFeature: queriedFeature.toFLTQueriedFeature(), + layers: layers) } } extension MapboxMaps.QueriedFeature { @@ -423,6 +514,34 @@ extension MapboxMaps.MapOptions { ) } } +extension MapboxMaps.FeaturesetFeatureId { + func toFLTFeaturesetFeatureId() -> FeaturesetFeatureId { + return FeaturesetFeatureId( + id: self.id, + namespace: self.namespace) + } +} + +extension MapboxMaps.FeaturesetDescriptor { + func toFLTFeaturesetDescriptor() -> FeaturesetDescriptor { + return FeaturesetDescriptor( + featuresetId: featuresetId, + importId: importId, + layerId: layerId) + } +} + +extension MapboxMaps.FeaturesetFeature { + func toFLTFeaturesetFeature() -> FeaturesetFeature { + return FeaturesetFeature( + id: id?.toFLTFeaturesetFeatureId(), + featureset: featureset.toFLTFeaturesetDescriptor(), + geometry: geometry.toMap(), + properties: properties.turfRawValue, + state: state.turfRawValue) + } +} + extension MapboxMaps.CameraBounds { func toFLTCameraBounds() -> CameraBounds { return CameraBounds( @@ -439,8 +558,7 @@ extension CGPoint { } } -extension MapboxMaps.MapContentGestureContext { - +extension MapboxMaps.InteractionContext { func toFLTMapContentGestureContext() -> MapContentGestureContext { MapContentGestureContext( touchPosition: point.toFLTScreenCoordinate(), @@ -512,7 +630,6 @@ extension MapboxMaps.StylePropertyValue { } extension MapboxMaps.Geometry { - func toMap() -> [String: Any] { switch self { case .point(let point): @@ -521,7 +638,14 @@ extension MapboxMaps.Geometry { return line.toMap() case .polygon(let polygon): return polygon.toMap() - case .multiPoint, .multiLineString, .multiPolygon, .geometryCollection: + case .multiPoint(let multiPoint): + return multiPoint.toMap() + case .multiLineString(let multiLineString): + return multiLineString.toMap() + case .multiPolygon(let multiPolygon): + return multiPolygon.toMap() + // includes GeometryCollection as it is not supported + default: return [:] } } @@ -529,19 +653,62 @@ extension MapboxMaps.Geometry { extension Point { func toMap() -> [String: Any] { - return [COORDINATES: [coordinates.longitude, coordinates.latitude]] + return [ + "type": "Point", + COORDINATES: [coordinates.longitude, coordinates.latitude] + ] } } + extension LineString { func toMap() -> [String: Any] { - return [COORDINATES: coordinates.map({[$0.longitude, $0.latitude]})] + return [ + "type": "LineString", + COORDINATES: coordinates.map({[$0.longitude, $0.latitude]}) + ] } } + extension Polygon { func toMap() -> [String: Any] { - return [COORDINATES: coordinates.map({$0.map {[$0.longitude, $0.latitude]}})] + return [ + "type": "Polygon", + COORDINATES: coordinates.map({$0.map {[$0.longitude, $0.latitude]}}) + ] + } +} + +extension MultiPoint { + func toMap() -> [String: Any] { + return [ + "type": "MultiPoint", + COORDINATES: coordinates.map({[$0.longitude, $0.latitude]}) + ] } } + +extension MultiLineString { + func toMap() -> [String: Any] { + return [ + "type": "MultiLineString", + COORDINATES: coordinates.map({$0.map {[$0.longitude, $0.latitude]}}) + ] + } +} + +extension MultiPolygon { + func toMap() -> [String: Any] { + return [ + "type": "MultiPolygon", + COORDINATES: coordinates.map({ polygon in + polygon.map({ ring in + ring.map({[$0.longitude, $0.latitude]}) + }) + }) + ] + } +} + extension CLLocationCoordinate2D { func toDict() -> [String: Any] { return [COORDINATES: [self.longitude, self.latitude]] @@ -905,6 +1072,19 @@ extension UIGestureRecognizer.State { } } +extension _InteractionType { + static func fromString(_ string: String) -> _InteractionType? { + switch string { + case "TAP": + return .tAP + case "LONG_TAP": + return .lONGTAP + default: + return nil + } + } +} + // MARK: Offline extension MapboxCoreMaps.StylePackLoadOptions { diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/MapInterfaces.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/MapInterfaces.swift index ad171d08f..ef9286e2c 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/MapInterfaces.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/MapInterfaces.swift @@ -57,6 +57,10 @@ private func wrapError(_ error: Any) -> [Any?] { ] } +private func createConnectionError(withChannelName channelName: String) -> MapInterfacesError { + return MapInterfacesError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") +} + private func isNullish(_ value: Any?) -> Bool { return value is NSNull || value == nil } @@ -186,6 +190,14 @@ enum ViewAnnotationAnchor: Int { case cENTER = 8 } +/// The type of interaction, either tap/click or longTap/longClick +enum _InteractionType: Int { + /// A short tap or click + case tAP = 0 + /// A long tap or long click + case lONGTAP = 1 +} + /// Type information of the variant's content enum Type: Int { case sCREENBOX = 0 @@ -1196,6 +1208,252 @@ struct QueriedFeature { } } +/// Identifies a feature in a featureset. +/// +/// Knowing the feature identifier allows to set the feature states to a particular feature, see ``MapboxMap/setFeatureState(featureset:featureId:state:callback:)``. +/// +/// In a featureset a feature can come from different underlying sources. In that case their IDs are not guaranteed to be unique in the featureset. +/// The ``FeaturesetFeatureId/namespace`` is used to disambiguate from which source the feature is coming. +/// +/// - Warning: There is no guarantee of identifier persistency. This depends on the underlying source of the features and may vary from style to style. +/// If you want to store the identifiers persistently, please make sure that the style or source provides this guarantee. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct FeaturesetFeatureId { + /// A feature id coming from the feature itself.exp + var id: String + /// A namespace of the feature + var namespace: String? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> FeaturesetFeatureId? { + let id = pigeonVar_list[0] as! String + let namespace: String? = nilOrValue(pigeonVar_list[1]) + + return FeaturesetFeatureId( + id: id, + namespace: namespace + ) + } + func toList() -> [Any?] { + return [ + id, + namespace, + ] + } +} + +/// Wraps a FeatureState map +/// +/// Generated class from Pigeon that represents data sent in messages. +struct FeatureState { + var map: [String: Any?] + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> FeatureState? { + let map = pigeonVar_list[0] as! [String: Any?] + + return FeatureState( + map: map + ) + } + func toList() -> [Any?] { + return [ + map + ] + } +} + +/// Internal: An interaction that can be added to the map. +/// +/// To create an interaction use ``TapInteraction`` and ``LongClickInteraction`` implementations. +/// +/// See also: ``MapboxMap/addInteraction``. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct _Interaction { + /// The featureset descriptor that specifies the featureset to be included in the interaction. + var featuresetDescriptor: FeaturesetDescriptor + /// The type of interaction, either tap or longTap + var interactionType: _InteractionType + /// Whether to stop the propagation of the interaction to the map. Defaults to true. + var stopPropagation: Bool + /// An optional filter of features that should trigger the interaction. + var filter: String? + /// Radius of a tappable area + var radius: Double? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> _Interaction? { + let featuresetDescriptor = pigeonVar_list[0] as! FeaturesetDescriptor + let interactionType = pigeonVar_list[1] as! _InteractionType + let stopPropagation = pigeonVar_list[2] as! Bool + let filter: String? = nilOrValue(pigeonVar_list[3]) + let radius: Double? = nilOrValue(pigeonVar_list[4]) + + return _Interaction( + featuresetDescriptor: featuresetDescriptor, + interactionType: interactionType, + stopPropagation: stopPropagation, + filter: filter, + radius: radius + ) + } + func toList() -> [Any?] { + return [ + featuresetDescriptor, + interactionType, + stopPropagation, + filter, + radius, + ] + } +} + +/// Internal class to handle pigeon conversions for interactions. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct _InteractionPigeon { + /// The featureset descriptor that specifies the featureset to be included in the interaction. + var featuresetDescriptor: [Any?] + /// Whether to stop the propagation of the interaction to the map + var stopPropagation: Bool + /// The type of interaction, either tap or longTap as a String + var interactionType: String + /// An identifier for the interaction + var identifier: String + /// An optional filter of features that should trigger the interaction. + var filter: String? + /// Radius of a tappable area + var radius: Double? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> _InteractionPigeon? { + let featuresetDescriptor = pigeonVar_list[0] as! [Any?] + let stopPropagation = pigeonVar_list[1] as! Bool + let interactionType = pigeonVar_list[2] as! String + let identifier = pigeonVar_list[3] as! String + let filter: String? = nilOrValue(pigeonVar_list[4]) + let radius: Double? = nilOrValue(pigeonVar_list[5]) + + return _InteractionPigeon( + featuresetDescriptor: featuresetDescriptor, + stopPropagation: stopPropagation, + interactionType: interactionType, + identifier: identifier, + filter: filter, + radius: radius + ) + } + func toList() -> [Any?] { + return [ + featuresetDescriptor, + stopPropagation, + interactionType, + identifier, + filter, + radius, + ] + } +} + +/// A featureset descriptor. +/// +/// The descriptor instance acts as a universal target for interactions or querying rendered features (see 'TapInteraction', 'LongTapInteraction') +/// +/// Generated class from Pigeon that represents data sent in messages. +struct FeaturesetDescriptor { + /// An optional unique identifier for the featureset within the style. + /// This id is used to reference a specific featureset. + /// + /// * Note: If `featuresetId` is provided and valid, it takes precedence over `layerId`, + /// * meaning `layerId` will not be considered even if it has a valid value. + var featuresetId: String? + /// An optional import id that is required if the featureset is defined within an imported style. + /// If the featureset belongs to the current style, this field should be set to a null string. + /// + /// Note: `importId` is only applicable when used in conjunction with `featuresetId` + /// and has no effect when used with `layerId`. + var importId: String? + /// An optional unique identifier for the layer within the current style. + /// + /// Note: If `featuresetId` is valid, `layerId` will be ignored even if it has a valid value. + /// Additionally, `importId` does not apply when using `layerId`. + var layerId: String? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> FeaturesetDescriptor? { + let featuresetId: String? = nilOrValue(pigeonVar_list[0]) + let importId: String? = nilOrValue(pigeonVar_list[1]) + let layerId: String? = nilOrValue(pigeonVar_list[2]) + + return FeaturesetDescriptor( + featuresetId: featuresetId, + importId: importId, + layerId: layerId + ) + } + func toList() -> [Any?] { + return [ + featuresetId, + importId, + layerId, + ] + } +} + +/// A basic feature of a featureset. +/// +/// If you use Standard Style, you can use typed alternatives like `StandardPoiFeature`, `StandardPlaceLabelsFeature`, `StandardBuildingsFeature`. +/// +/// The featureset feature is different to the `Turf.Feature`. The latter represents any GeoJSON feature, while the former is a high level representation of features. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct FeaturesetFeature { + /// An identifier of the feature. + /// + /// The identifier can be `nil` if the underlying source doesn't have identifiers for features. + /// In this case it's impossible to set a feature state for an individual feature. + var id: FeaturesetFeatureId? + /// A featureset descriptor denoting the featureset this feature belongs to. + var featureset: FeaturesetDescriptor + /// A feature geometry. + var geometry: [String?: Any?] + /// Feature JSON properties. + var properties: [String: Any?] + /// A feature state. + /// + /// This is a **snapshot** of the state that the feature had when it was interacted with. + /// To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + var state: [String: Any?] + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> FeaturesetFeature? { + let id: FeaturesetFeatureId? = nilOrValue(pigeonVar_list[0]) + let featureset = pigeonVar_list[1] as! FeaturesetDescriptor + let geometry = pigeonVar_list[2] as! [String?: Any?] + let properties = pigeonVar_list[3] as! [String: Any?] + let state = pigeonVar_list[4] as! [String: Any?] + + return FeaturesetFeature( + id: id, + featureset: featureset, + geometry: geometry, + properties: properties, + state: state + ) + } + func toList() -> [Any?] { + return [ + id, + featureset, + geometry, + properties, + state, + ] + } +} + /// Geometry for querying rendered features. /// /// Generated class from Pigeon that represents data sent in messages. @@ -1750,184 +2008,210 @@ private class MapInterfacesPigeonCodecReader: FlutterStandardReader { case 137: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return Type(rawValue: enumResultAsInt) + return _InteractionType(rawValue: enumResultAsInt) } return nil case 138: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return FillExtrusionBaseAlignment(rawValue: enumResultAsInt) + return GestureState(rawValue: enumResultAsInt) } return nil case 139: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return FillExtrusionHeightAlignment(rawValue: enumResultAsInt) + return Type(rawValue: enumResultAsInt) } return nil case 140: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return BackgroundPitchAlignment(rawValue: enumResultAsInt) + return FillExtrusionBaseAlignment(rawValue: enumResultAsInt) } return nil case 141: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return StylePackErrorType(rawValue: enumResultAsInt) + return FillExtrusionHeightAlignment(rawValue: enumResultAsInt) } return nil case 142: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return ResponseErrorReason(rawValue: enumResultAsInt) + return BackgroundPitchAlignment(rawValue: enumResultAsInt) } return nil case 143: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return OfflineRegionDownloadState(rawValue: enumResultAsInt) + return StylePackErrorType(rawValue: enumResultAsInt) } return nil case 144: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return TileStoreUsageMode(rawValue: enumResultAsInt) + return ResponseErrorReason(rawValue: enumResultAsInt) } return nil case 145: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return StylePropertyValueKind(rawValue: enumResultAsInt) + return OfflineRegionDownloadState(rawValue: enumResultAsInt) } return nil case 146: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return StyleProjectionName(rawValue: enumResultAsInt) + return TileStoreUsageMode(rawValue: enumResultAsInt) } return nil case 147: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return Anchor(rawValue: enumResultAsInt) + return StylePropertyValueKind(rawValue: enumResultAsInt) } return nil case 148: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return HttpMethod(rawValue: enumResultAsInt) + return StyleProjectionName(rawValue: enumResultAsInt) } return nil case 149: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return HttpRequestErrorType(rawValue: enumResultAsInt) + return Anchor(rawValue: enumResultAsInt) } return nil case 150: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return DownloadErrorCode(rawValue: enumResultAsInt) + return HttpMethod(rawValue: enumResultAsInt) } return nil case 151: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return DownloadState(rawValue: enumResultAsInt) + return HttpRequestErrorType(rawValue: enumResultAsInt) } return nil case 152: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return TileRegionErrorType(rawValue: enumResultAsInt) + return DownloadErrorCode(rawValue: enumResultAsInt) } return nil case 153: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return _MapEvent(rawValue: enumResultAsInt) + return DownloadState(rawValue: enumResultAsInt) } return nil case 154: - return Point.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return TileRegionErrorType(rawValue: enumResultAsInt) + } + return nil case 155: - return Feature.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return _MapEvent(rawValue: enumResultAsInt) + } + return nil case 156: - return GlyphsRasterizationOptions.fromList(self.readValue() as! [Any?]) + return Point.fromList(self.readValue() as! [Any?]) case 157: - return TileCoverOptions.fromList(self.readValue() as! [Any?]) + return Feature.fromList(self.readValue() as! [Any?]) case 158: - return MbxEdgeInsets.fromList(self.readValue() as! [Any?]) + return GlyphsRasterizationOptions.fromList(self.readValue() as! [Any?]) case 159: - return CameraOptions.fromList(self.readValue() as! [Any?]) + return TileCoverOptions.fromList(self.readValue() as! [Any?]) case 160: - return CameraState.fromList(self.readValue() as! [Any?]) + return MbxEdgeInsets.fromList(self.readValue() as! [Any?]) case 161: - return CameraBoundsOptions.fromList(self.readValue() as! [Any?]) + return CameraOptions.fromList(self.readValue() as! [Any?]) case 162: - return CameraBounds.fromList(self.readValue() as! [Any?]) + return CameraState.fromList(self.readValue() as! [Any?]) case 163: - return MapAnimationOptions.fromList(self.readValue() as! [Any?]) + return CameraBoundsOptions.fromList(self.readValue() as! [Any?]) case 164: - return CoordinateBounds.fromList(self.readValue() as! [Any?]) + return CameraBounds.fromList(self.readValue() as! [Any?]) case 165: - return MapDebugOptions.fromList(self.readValue() as! [Any?]) + return MapAnimationOptions.fromList(self.readValue() as! [Any?]) case 166: - return TileCacheBudgetInMegabytes.fromList(self.readValue() as! [Any?]) + return CoordinateBounds.fromList(self.readValue() as! [Any?]) case 167: - return TileCacheBudgetInTiles.fromList(self.readValue() as! [Any?]) + return MapDebugOptions.fromList(self.readValue() as! [Any?]) case 168: - return MapOptions.fromList(self.readValue() as! [Any?]) + return TileCacheBudgetInMegabytes.fromList(self.readValue() as! [Any?]) case 169: - return ScreenCoordinate.fromList(self.readValue() as! [Any?]) + return TileCacheBudgetInTiles.fromList(self.readValue() as! [Any?]) case 170: - return ScreenBox.fromList(self.readValue() as! [Any?]) + return MapOptions.fromList(self.readValue() as! [Any?]) case 171: - return CoordinateBoundsZoom.fromList(self.readValue() as! [Any?]) + return ScreenCoordinate.fromList(self.readValue() as! [Any?]) case 172: - return Size.fromList(self.readValue() as! [Any?]) + return ScreenBox.fromList(self.readValue() as! [Any?]) case 173: - return RenderedQueryOptions.fromList(self.readValue() as! [Any?]) + return CoordinateBoundsZoom.fromList(self.readValue() as! [Any?]) case 174: - return SourceQueryOptions.fromList(self.readValue() as! [Any?]) + return Size.fromList(self.readValue() as! [Any?]) case 175: - return FeatureExtensionValue.fromList(self.readValue() as! [Any?]) + return RenderedQueryOptions.fromList(self.readValue() as! [Any?]) case 176: - return LayerPosition.fromList(self.readValue() as! [Any?]) + return SourceQueryOptions.fromList(self.readValue() as! [Any?]) case 177: - return QueriedRenderedFeature.fromList(self.readValue() as! [Any?]) + return FeatureExtensionValue.fromList(self.readValue() as! [Any?]) case 178: - return QueriedSourceFeature.fromList(self.readValue() as! [Any?]) + return LayerPosition.fromList(self.readValue() as! [Any?]) case 179: - return QueriedFeature.fromList(self.readValue() as! [Any?]) + return QueriedRenderedFeature.fromList(self.readValue() as! [Any?]) case 180: - return _RenderedQueryGeometry.fromList(self.readValue() as! [Any?]) + return QueriedSourceFeature.fromList(self.readValue() as! [Any?]) case 181: - return ProjectedMeters.fromList(self.readValue() as! [Any?]) + return QueriedFeature.fromList(self.readValue() as! [Any?]) case 182: - return MercatorCoordinate.fromList(self.readValue() as! [Any?]) + return FeaturesetFeatureId.fromList(self.readValue() as! [Any?]) case 183: - return StyleObjectInfo.fromList(self.readValue() as! [Any?]) + return FeatureState.fromList(self.readValue() as! [Any?]) case 184: - return StyleProjection.fromList(self.readValue() as! [Any?]) + return _Interaction.fromList(self.readValue() as! [Any?]) case 185: - return FlatLight.fromList(self.readValue() as! [Any?]) + return _InteractionPigeon.fromList(self.readValue() as! [Any?]) case 186: - return DirectionalLight.fromList(self.readValue() as! [Any?]) + return FeaturesetDescriptor.fromList(self.readValue() as! [Any?]) case 187: - return AmbientLight.fromList(self.readValue() as! [Any?]) + return FeaturesetFeature.fromList(self.readValue() as! [Any?]) case 188: - return MbxImage.fromList(self.readValue() as! [Any?]) + return MapContentGestureContext.fromList(self.readValue() as! [Any?]) case 189: - return ImageStretches.fromList(self.readValue() as! [Any?]) + return _RenderedQueryGeometry.fromList(self.readValue() as! [Any?]) case 190: - return ImageContent.fromList(self.readValue() as! [Any?]) + return ProjectedMeters.fromList(self.readValue() as! [Any?]) case 191: - return TransitionOptions.fromList(self.readValue() as! [Any?]) + return MercatorCoordinate.fromList(self.readValue() as! [Any?]) case 192: - return CanonicalTileID.fromList(self.readValue() as! [Any?]) + return StyleObjectInfo.fromList(self.readValue() as! [Any?]) case 193: + return StyleProjection.fromList(self.readValue() as! [Any?]) + case 194: + return FlatLight.fromList(self.readValue() as! [Any?]) + case 195: + return DirectionalLight.fromList(self.readValue() as! [Any?]) + case 196: + return AmbientLight.fromList(self.readValue() as! [Any?]) + case 197: + return MbxImage.fromList(self.readValue() as! [Any?]) + case 198: + return ImageStretches.fromList(self.readValue() as! [Any?]) + case 199: + return ImageContent.fromList(self.readValue() as! [Any?]) + case 200: + return TransitionOptions.fromList(self.readValue() as! [Any?]) + case 201: + return CanonicalTileID.fromList(self.readValue() as! [Any?]) + case 202: return StylePropertyValue.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -1961,176 +2245,203 @@ private class MapInterfacesPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? ViewAnnotationAnchor { super.writeByte(136) super.writeValue(value.rawValue) - } else if let value = value as? Type { + } else if let value = value as? _InteractionType { super.writeByte(137) super.writeValue(value.rawValue) - } else if let value = value as? FillExtrusionBaseAlignment { + } else if let value = value as? GestureState { super.writeByte(138) super.writeValue(value.rawValue) - } else if let value = value as? FillExtrusionHeightAlignment { + } else if let value = value as? Type { super.writeByte(139) super.writeValue(value.rawValue) - } else if let value = value as? BackgroundPitchAlignment { + } else if let value = value as? FillExtrusionBaseAlignment { super.writeByte(140) super.writeValue(value.rawValue) - } else if let value = value as? StylePackErrorType { + } else if let value = value as? FillExtrusionHeightAlignment { super.writeByte(141) super.writeValue(value.rawValue) - } else if let value = value as? ResponseErrorReason { + } else if let value = value as? BackgroundPitchAlignment { super.writeByte(142) super.writeValue(value.rawValue) - } else if let value = value as? OfflineRegionDownloadState { + } else if let value = value as? StylePackErrorType { super.writeByte(143) super.writeValue(value.rawValue) - } else if let value = value as? TileStoreUsageMode { + } else if let value = value as? ResponseErrorReason { super.writeByte(144) super.writeValue(value.rawValue) - } else if let value = value as? StylePropertyValueKind { + } else if let value = value as? OfflineRegionDownloadState { super.writeByte(145) super.writeValue(value.rawValue) - } else if let value = value as? StyleProjectionName { + } else if let value = value as? TileStoreUsageMode { super.writeByte(146) super.writeValue(value.rawValue) - } else if let value = value as? Anchor { + } else if let value = value as? StylePropertyValueKind { super.writeByte(147) super.writeValue(value.rawValue) - } else if let value = value as? HttpMethod { + } else if let value = value as? StyleProjectionName { super.writeByte(148) super.writeValue(value.rawValue) - } else if let value = value as? HttpRequestErrorType { + } else if let value = value as? Anchor { super.writeByte(149) super.writeValue(value.rawValue) - } else if let value = value as? DownloadErrorCode { + } else if let value = value as? HttpMethod { super.writeByte(150) super.writeValue(value.rawValue) - } else if let value = value as? DownloadState { + } else if let value = value as? HttpRequestErrorType { super.writeByte(151) super.writeValue(value.rawValue) - } else if let value = value as? TileRegionErrorType { + } else if let value = value as? DownloadErrorCode { super.writeByte(152) super.writeValue(value.rawValue) - } else if let value = value as? _MapEvent { + } else if let value = value as? DownloadState { super.writeByte(153) super.writeValue(value.rawValue) - } else if let value = value as? Point { + } else if let value = value as? TileRegionErrorType { super.writeByte(154) + super.writeValue(value.rawValue) + } else if let value = value as? _MapEvent { + super.writeByte(155) + super.writeValue(value.rawValue) + } else if let value = value as? Point { + super.writeByte(156) super.writeValue(value.toList()) } else if let value = value as? Feature { - super.writeByte(155) + super.writeByte(157) super.writeValue(value.toList()) } else if let value = value as? GlyphsRasterizationOptions { - super.writeByte(156) + super.writeByte(158) super.writeValue(value.toList()) } else if let value = value as? TileCoverOptions { - super.writeByte(157) + super.writeByte(159) super.writeValue(value.toList()) } else if let value = value as? MbxEdgeInsets { - super.writeByte(158) + super.writeByte(160) super.writeValue(value.toList()) } else if let value = value as? CameraOptions { - super.writeByte(159) + super.writeByte(161) super.writeValue(value.toList()) } else if let value = value as? CameraState { - super.writeByte(160) + super.writeByte(162) super.writeValue(value.toList()) } else if let value = value as? CameraBoundsOptions { - super.writeByte(161) + super.writeByte(163) super.writeValue(value.toList()) } else if let value = value as? CameraBounds { - super.writeByte(162) + super.writeByte(164) super.writeValue(value.toList()) } else if let value = value as? MapAnimationOptions { - super.writeByte(163) + super.writeByte(165) super.writeValue(value.toList()) } else if let value = value as? CoordinateBounds { - super.writeByte(164) + super.writeByte(166) super.writeValue(value.toList()) } else if let value = value as? MapDebugOptions { - super.writeByte(165) + super.writeByte(167) super.writeValue(value.toList()) } else if let value = value as? TileCacheBudgetInMegabytes { - super.writeByte(166) + super.writeByte(168) super.writeValue(value.toList()) } else if let value = value as? TileCacheBudgetInTiles { - super.writeByte(167) + super.writeByte(169) super.writeValue(value.toList()) } else if let value = value as? MapOptions { - super.writeByte(168) + super.writeByte(170) super.writeValue(value.toList()) } else if let value = value as? ScreenCoordinate { - super.writeByte(169) + super.writeByte(171) super.writeValue(value.toList()) } else if let value = value as? ScreenBox { - super.writeByte(170) + super.writeByte(172) super.writeValue(value.toList()) } else if let value = value as? CoordinateBoundsZoom { - super.writeByte(171) + super.writeByte(173) super.writeValue(value.toList()) } else if let value = value as? Size { - super.writeByte(172) + super.writeByte(174) super.writeValue(value.toList()) } else if let value = value as? RenderedQueryOptions { - super.writeByte(173) + super.writeByte(175) super.writeValue(value.toList()) } else if let value = value as? SourceQueryOptions { - super.writeByte(174) + super.writeByte(176) super.writeValue(value.toList()) } else if let value = value as? FeatureExtensionValue { - super.writeByte(175) + super.writeByte(177) super.writeValue(value.toList()) } else if let value = value as? LayerPosition { - super.writeByte(176) + super.writeByte(178) super.writeValue(value.toList()) } else if let value = value as? QueriedRenderedFeature { - super.writeByte(177) + super.writeByte(179) super.writeValue(value.toList()) } else if let value = value as? QueriedSourceFeature { - super.writeByte(178) + super.writeByte(180) super.writeValue(value.toList()) } else if let value = value as? QueriedFeature { - super.writeByte(179) + super.writeByte(181) + super.writeValue(value.toList()) + } else if let value = value as? FeaturesetFeatureId { + super.writeByte(182) + super.writeValue(value.toList()) + } else if let value = value as? FeatureState { + super.writeByte(183) + super.writeValue(value.toList()) + } else if let value = value as? _Interaction { + super.writeByte(184) + super.writeValue(value.toList()) + } else if let value = value as? _InteractionPigeon { + super.writeByte(185) + super.writeValue(value.toList()) + } else if let value = value as? FeaturesetDescriptor { + super.writeByte(186) + super.writeValue(value.toList()) + } else if let value = value as? FeaturesetFeature { + super.writeByte(187) + super.writeValue(value.toList()) + } else if let value = value as? MapContentGestureContext { + super.writeByte(188) super.writeValue(value.toList()) } else if let value = value as? _RenderedQueryGeometry { - super.writeByte(180) + super.writeByte(189) super.writeValue(value.toList()) } else if let value = value as? ProjectedMeters { - super.writeByte(181) + super.writeByte(190) super.writeValue(value.toList()) } else if let value = value as? MercatorCoordinate { - super.writeByte(182) + super.writeByte(191) super.writeValue(value.toList()) } else if let value = value as? StyleObjectInfo { - super.writeByte(183) + super.writeByte(192) super.writeValue(value.toList()) } else if let value = value as? StyleProjection { - super.writeByte(184) + super.writeByte(193) super.writeValue(value.toList()) } else if let value = value as? FlatLight { - super.writeByte(185) + super.writeByte(194) super.writeValue(value.toList()) } else if let value = value as? DirectionalLight { - super.writeByte(186) + super.writeByte(195) super.writeValue(value.toList()) } else if let value = value as? AmbientLight { - super.writeByte(187) + super.writeByte(196) super.writeValue(value.toList()) } else if let value = value as? MbxImage { - super.writeByte(188) + super.writeByte(197) super.writeValue(value.toList()) } else if let value = value as? ImageStretches { - super.writeByte(189) + super.writeByte(198) super.writeValue(value.toList()) } else if let value = value as? ImageContent { - super.writeByte(190) + super.writeByte(199) super.writeValue(value.toList()) } else if let value = value as? TransitionOptions { - super.writeByte(191) + super.writeByte(200) super.writeValue(value.toList()) } else if let value = value as? CanonicalTileID { - super.writeByte(192) + super.writeByte(201) super.writeValue(value.toList()) } else if let value = value as? StylePropertyValue { - super.writeByte(193) + super.writeByte(202) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -2857,6 +3168,39 @@ class _CameraManagerSetup { } } } +/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. +protocol _InteractionsListenerProtocol { + func onInteraction(context contextArg: MapContentGestureContext, feature featureArg: FeaturesetFeature, interactionID interactionIDArg: Int64, completion: @escaping (Result) -> Void) +} +class _InteractionsListener: _InteractionsListenerProtocol { + private let binaryMessenger: FlutterBinaryMessenger + private let messageChannelSuffix: String + init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") { + self.binaryMessenger = binaryMessenger + self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + } + var codec: MapInterfacesPigeonCodec { + return MapInterfacesPigeonCodec.shared + } + func onInteraction(context contextArg: MapContentGestureContext, feature featureArg: FeaturesetFeature, interactionID interactionIDArg: Int64, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([contextArg, featureArg, interactionIDArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(MapInterfacesError(code: code, message: message, details: details))) + } else { + completion(.success(Void())) + } + } + } +} /// Map class provides map rendering functionality. /// /// @@ -2931,6 +3275,18 @@ protocol _MapInterface { /// @param completion The `query features completion` called when the query completes. /// @return A `cancelable` object that could be used to cancel the pending query. func queryRenderedFeatures(geometry: _RenderedQueryGeometry, options: RenderedQueryOptions, completion: @escaping (Result<[QueriedRenderedFeature?], Error>) -> Void) + /// Queries the map for rendered features with one typed featureset. + /// + /// The results array will contain features of the type specified by this featureset. + /// + /// - Important: If you need to handle basic gestures on map content, + /// please prefer to use Interactions API, see `MapboxMap/addInteraction`. + /// + /// @param featureset A typed featureset to query with. + /// @param geometry An optional screen geometry to query. Can be a `CGPoint`, `CGRect`, or an array of `CGPoint`. + /// If omitted, the full viewport is queried. + /// @param filter An additional filter for features. + func queryRenderedFeaturesForFeatureset(featureset: FeaturesetDescriptor, geometry: _RenderedQueryGeometry?, filter: String?, completion: @escaping (Result<[FeaturesetFeature], Error>) -> Void) /// Queries the map for source features. /// /// @param sourceId The style source identifier used to query for source features. @@ -2983,6 +3339,22 @@ protocol _MapInterface { /// @param featureId The feature identifier of the feature whose state should be updated. /// @param state The `state` object with properties to update with their respective new values. func setFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, state: String, completion: @escaping (Result) -> Void) + /// Update the state map of a feature within a featureset. + /// Update entries in the state map of a given feature within a style source. Only entries listed in the state map + /// will be updated. An entry in the feature state map that is not listed in `state` will retain its previous value. + /// + /// @param featureset The featureset to look the feature in. + /// @param featureId Identifier of the feature whose state should be updated. + /// @param state Map of entries to update with their respective new values + func setFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, state: [String: Any?], completion: @escaping (Result) -> Void) + /// Update the state map of an individual feature. + /// + /// The feature should have a non-nil ``FeaturesetFeatureType/id``. Otherwise, + /// the operation will be no-op and callback will receive an error. + /// + /// @param feature The feature to update. + /// @param state Map of entries to update with their respective new values + func setFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, state: [String: Any?], completion: @escaping (Result) -> Void) /// Gets the state map of a feature within a style source. /// /// Note that updates to feature state are asynchronous, so changes made by other methods might not be @@ -2991,21 +3363,55 @@ protocol _MapInterface { /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). /// @param featureId The feature identifier of the feature whose state should be queried. - /// @param completion The `query feature state completion` called when the query completes. + /// + /// @return A String representing the Feature's state map. func getFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, completion: @escaping (Result) -> Void) + /// Get the state map of a feature within a style source. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be queried. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + func getFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, completion: @escaping (Result<[String: Any?], Error>) -> Void) + /// Get the state map of a feature within a style source. + /// + /// @param feature An interactive feature to query the state of. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + func getFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, completion: @escaping (Result<[String: Any?], Error>) -> Void) /// Removes entries from a feature state object. /// /// Remove a specified property or all property from a feature's state object, depending on the value of /// `stateKey`. /// /// Note that updates to feature state are asynchronous, so changes made by this method might not be - /// immediately visible using `getStateFeature`. + /// immediately visible using `getFeatureState`. /// /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). /// @param featureId The feature identifier of the feature whose state should be removed. /// @param stateKey The key of the property to remove. If `null`, all feature's state object properties are removed. func removeFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, stateKey: String?, completion: @escaping (Result) -> Void) + /// Removes entries from a feature state object of a feature in the specified featureset. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be removed. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + func removeFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, stateKey: String?, completion: @escaping (Result) -> Void) + /// Removes entries from a specified Feature. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param feature An interactive feature to update. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + func removeFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, stateKey: String?, completion: @escaping (Result) -> Void) + /// Reset all the feature states within a featureset. + /// + /// Note that updates to feature state are asynchronous, so changes made by this method might not be + /// immediately visible using ``MapboxMap/getFeatureState()``. + /// + /// @param featureset A featureset descriptor + func resetFeatureStatesForFeatureset(featureset: FeaturesetDescriptor, completion: @escaping (Result) -> Void) /// Reduces memory use. Useful to call when the application gets paused or sent to background. func reduceMemoryUse() throws /// Gets elevation for the given coordinate. @@ -3398,6 +3804,36 @@ class _MapInterfaceSetup { } else { queryRenderedFeaturesChannel.setMessageHandler(nil) } + /// Queries the map for rendered features with one typed featureset. + /// + /// The results array will contain features of the type specified by this featureset. + /// + /// - Important: If you need to handle basic gestures on map content, + /// please prefer to use Interactions API, see `MapboxMap/addInteraction`. + /// + /// @param featureset A typed featureset to query with. + /// @param geometry An optional screen geometry to query. Can be a `CGPoint`, `CGRect`, or an array of `CGPoint`. + /// If omitted, the full viewport is queried. + /// @param filter An additional filter for features. + let queryRenderedFeaturesForFeaturesetChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.queryRenderedFeaturesForFeatureset\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + queryRenderedFeaturesForFeaturesetChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featuresetArg = args[0] as! FeaturesetDescriptor + let geometryArg: _RenderedQueryGeometry? = nilOrValue(args[1]) + let filterArg: String? = nilOrValue(args[2]) + api.queryRenderedFeaturesForFeatureset(featureset: featuresetArg, geometry: geometryArg, filter: filterArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + queryRenderedFeaturesForFeaturesetChannel.setMessageHandler(nil) + } /// Queries the map for source features. /// /// @param sourceId The style source identifier used to query for source features. @@ -3539,6 +3975,57 @@ class _MapInterfaceSetup { } else { setFeatureStateChannel.setMessageHandler(nil) } + /// Update the state map of a feature within a featureset. + /// Update entries in the state map of a given feature within a style source. Only entries listed in the state map + /// will be updated. An entry in the feature state map that is not listed in `state` will retain its previous value. + /// + /// @param featureset The featureset to look the feature in. + /// @param featureId Identifier of the feature whose state should be updated. + /// @param state Map of entries to update with their respective new values + let setFeatureStateForFeaturesetDescriptorChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetDescriptor\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setFeatureStateForFeaturesetDescriptorChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featuresetArg = args[0] as! FeaturesetDescriptor + let featureIdArg = args[1] as! FeaturesetFeatureId + let stateArg = args[2] as! [String: Any?] + api.setFeatureStateForFeaturesetDescriptor(featureset: featuresetArg, featureId: featureIdArg, state: stateArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setFeatureStateForFeaturesetDescriptorChannel.setMessageHandler(nil) + } + /// Update the state map of an individual feature. + /// + /// The feature should have a non-nil ``FeaturesetFeatureType/id``. Otherwise, + /// the operation will be no-op and callback will receive an error. + /// + /// @param feature The feature to update. + /// @param state Map of entries to update with their respective new values + let setFeatureStateForFeaturesetFeatureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetFeature\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setFeatureStateForFeaturesetFeatureChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featureArg = args[0] as! FeaturesetFeature + let stateArg = args[1] as! [String: Any?] + api.setFeatureStateForFeaturesetFeature(feature: featureArg, state: stateArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setFeatureStateForFeaturesetFeatureChannel.setMessageHandler(nil) + } /// Gets the state map of a feature within a style source. /// /// Note that updates to feature state are asynchronous, so changes made by other methods might not be @@ -3547,7 +4034,8 @@ class _MapInterfaceSetup { /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). /// @param featureId The feature identifier of the feature whose state should be queried. - /// @param completion The `query feature state completion` called when the query completes. + /// + /// @return A String representing the Feature's state map. let getFeatureStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureState\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getFeatureStateChannel.setMessageHandler { message, reply in @@ -3567,13 +4055,59 @@ class _MapInterfaceSetup { } else { getFeatureStateChannel.setMessageHandler(nil) } + /// Get the state map of a feature within a style source. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be queried. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + let getFeatureStateForFeaturesetDescriptorChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetDescriptor\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getFeatureStateForFeaturesetDescriptorChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featuresetArg = args[0] as! FeaturesetDescriptor + let featureIdArg = args[1] as! FeaturesetFeatureId + api.getFeatureStateForFeaturesetDescriptor(featureset: featuresetArg, featureId: featureIdArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getFeatureStateForFeaturesetDescriptorChannel.setMessageHandler(nil) + } + /// Get the state map of a feature within a style source. + /// + /// @param feature An interactive feature to query the state of. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + let getFeatureStateForFeaturesetFeatureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetFeature\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getFeatureStateForFeaturesetFeatureChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featureArg = args[0] as! FeaturesetFeature + api.getFeatureStateForFeaturesetFeature(feature: featureArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getFeatureStateForFeaturesetFeatureChannel.setMessageHandler(nil) + } /// Removes entries from a feature state object. /// /// Remove a specified property or all property from a feature's state object, depending on the value of /// `stateKey`. /// /// Note that updates to feature state are asynchronous, so changes made by this method might not be - /// immediately visible using `getStateFeature`. + /// immediately visible using `getFeatureState`. /// /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). @@ -3599,6 +4133,77 @@ class _MapInterfaceSetup { } else { removeFeatureStateChannel.setMessageHandler(nil) } + /// Removes entries from a feature state object of a feature in the specified featureset. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be removed. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + let removeFeatureStateForFeaturesetDescriptorChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetDescriptor\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + removeFeatureStateForFeaturesetDescriptorChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featuresetArg = args[0] as! FeaturesetDescriptor + let featureIdArg = args[1] as! FeaturesetFeatureId + let stateKeyArg: String? = nilOrValue(args[2]) + api.removeFeatureStateForFeaturesetDescriptor(featureset: featuresetArg, featureId: featureIdArg, stateKey: stateKeyArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + removeFeatureStateForFeaturesetDescriptorChannel.setMessageHandler(nil) + } + /// Removes entries from a specified Feature. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param feature An interactive feature to update. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + let removeFeatureStateForFeaturesetFeatureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetFeature\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + removeFeatureStateForFeaturesetFeatureChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featureArg = args[0] as! FeaturesetFeature + let stateKeyArg: String? = nilOrValue(args[1]) + api.removeFeatureStateForFeaturesetFeature(feature: featureArg, stateKey: stateKeyArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + removeFeatureStateForFeaturesetFeatureChannel.setMessageHandler(nil) + } + /// Reset all the feature states within a featureset. + /// + /// Note that updates to feature state are asynchronous, so changes made by this method might not be + /// immediately visible using ``MapboxMap/getFeatureState()``. + /// + /// @param featureset A featureset descriptor + let resetFeatureStatesForFeaturesetChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.resetFeatureStatesForFeatureset\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + resetFeatureStatesForFeaturesetChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let featuresetArg = args[0] as! FeaturesetDescriptor + api.resetFeatureStatesForFeatureset(featureset: featuresetArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + resetFeatureStatesForFeaturesetChannel.setMessageHandler(nil) + } /// Reduces memory use. Useful to call when the application gets paused or sent to background. let reduceMemoryUseChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.reduceMemoryUse\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { @@ -4660,6 +5265,10 @@ protocol StyleManager { /// @param locale The locale to apply for localization /// @param layerIds The ids of layers that will localize on, default is null which means will localize all the feasible layers. func localizeLabels(locale: String, layerIds: [String]?, completion: @escaping (Result) -> Void) + /// Returns the available featuresets in the currently loaded style. + /// + /// - Note: This function should only be called after the style is fully loaded; otherwise, the result may be unreliable. + func getFeaturesets() throws -> [FeaturesetDescriptor] } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -6017,6 +6626,22 @@ class StyleManagerSetup { } else { localizeLabelsChannel.setMessageHandler(nil) } + /// Returns the available featuresets in the currently loaded style. + /// + /// - Note: This function should only be called after the style is fully loaded; otherwise, the result may be unreliable. + let getFeaturesetsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.mapbox_maps_flutter.StyleManager.getFeaturesets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getFeaturesetsChannel.setMessageHandler { _, reply in + do { + let result = try api.getFeaturesets() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getFeaturesetsChannel.setMessageHandler(nil) + } } } /// Allows to cancel the associated asynchronous operation diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapInterfaceController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapInterfaceController.swift index e96c1cf33..cbc6ce00d 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapInterfaceController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapInterfaceController.swift @@ -153,17 +153,10 @@ final class MapInterfaceController: _MapInterface { do { switch geometry.type { case .sCREENBOX: - let screenBoxArray = convertStringToDictionary(properties: geometry.value) - guard let minCoord = screenBoxArray["min"] as? [String: Double] else { return } - guard let maxCoord = screenBoxArray["max"] as? [String: Double] else { return } - guard let minX = minCoord["x"], let minY = minCoord["y"], - let maxX = maxCoord["x"], let maxY = maxCoord["y"] else { + guard let cgRect = convertValueToCGRect(geometry.value) else { completion(.failure(FlutterError(code: MapInterfaceController.errorCode, message: "Geometry format error", details: geometry.value))) return } - let screenBox = ScreenBox(min: ScreenCoordinate(x: minX, y: minY), - max: ScreenCoordinate(x: maxX, y: maxY)) - let cgRect = screenBox.toCGRect() let queryOptions = try options.toRenderedQueryOptions() self.mapboxMap.queryRenderedFeatures(with: cgRect, options: queryOptions) { result in switch result { @@ -174,13 +167,10 @@ final class MapInterfaceController: _MapInterface { } } case .sCREENCOORDINATE: - guard let pointDict = convertStringToDictionary(properties: geometry.value) as? [String: Double], - let x = pointDict["x"], let y = pointDict["y"] else { + guard let cgPoint = convertValueToCGPoint(geometry.value) else { completion(.failure(FlutterError(code: MapInterfaceController.errorCode, message: "Geometry format error", details: geometry.value))) return } - let cgPoint = CGPoint(x: x, y: y) - try self.mapboxMap.queryRenderedFeatures(with: cgPoint, options: options.toRenderedQueryOptions()) { result in switch result { case .success(let features): @@ -190,15 +180,10 @@ final class MapInterfaceController: _MapInterface { } } case .lIST: - guard let data = geometry.value.data(using: .utf8), - let rawPoints = try? JSONDecoder().decode([[String: Double]].self, from: data) else { + guard let cgPoints = convertValueToCGPoints(geometry.value) else { completion(.failure(FlutterError(code: MapInterfaceController.errorCode, message: "Geometry format error", details: geometry.value))) return } - let cgPoints = rawPoints.compactMap { - guard let x = $0["x"], let y = $0["y"] else { return Optional.none } - return CGPoint(x: x, y: y) - } try self.mapboxMap.queryRenderedFeatures(with: cgPoints, options: options.toRenderedQueryOptions()) { result in switch result { case .success(let features): @@ -213,6 +198,27 @@ final class MapInterfaceController: _MapInterface { } } + func queryRenderedFeaturesForFeatureset(featureset: FeaturesetDescriptor, geometry: _RenderedQueryGeometry?, filter: String?, completion: @escaping (Result<[FeaturesetFeature], any Error>) -> Void) { + let filterExpression = try? filter.flatMap { try $0.toExp() } + let fltCompletion: (Result<[MapboxMaps.FeaturesetFeature], Error>) -> Void = { result in + switch result { + case .success(let features): + completion(.success(features.map({$0.toFLTFeaturesetFeature()}))) + case .failure(let error): + completion(.failure(FlutterError( + code: MapInterfaceController.errorCode, + message: "\(error)", + details: "Error querying rendered features for featureset: {featureId: \(String(describing: featureset.featuresetId)), importId: \(String(describing: featureset.importId)), layerId: \(String(describing: featureset.layerId))}." + ))) + } + } + if let geometry { + self.mapboxMap.queryRenderedFeatures(with: geometry, featureset: featureset.toMapFeaturesetDescriptor(), filter: filterExpression, completion: fltCompletion) + } else { + self.mapboxMap.queryRenderedFeatures(featureset: featureset.toMapFeaturesetDescriptor(), filter: filterExpression, completion: fltCompletion) + } + } + func querySourceFeatures(sourceId: String, options: SourceQueryOptions, completion: @escaping (Result<[QueriedSourceFeature?], Error>) -> Void) { do { try self.mapboxMap.querySourceFeatures(for: sourceId, options: options.toSourceQueryOptions()) { result in @@ -284,6 +290,50 @@ final class MapInterfaceController: _MapInterface { } } + func setFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, state: [String: Any?], completion: @escaping (Result) -> Void) { + guard let state = JSONObject.init(turfRawValue: state) else { + completion(.failure(FlutterError( + code: "setFeatureStateError", + message: "Error converting feature state.", + details: nil + ))) + return + } + _ = self.mapboxMap.setFeatureState(featureset: featureset.toMapFeaturesetDescriptor(), featureId: featureId.toMapFeaturesetFeatureId(), state: state) { error in + if let error { + completion(.failure(FlutterError( + code: "setFeatureStateError", + message: error.localizedDescription, + details: "Error setting feature state for feature \(String(describing: featureId)) from featureset {featuresetId: \(String(describing: featureset.featuresetId)), importId: \(String(describing: featureset.importId)), layerId: \(String(describing: featureset.layerId))}." + ))) + } else { + completion(.success(())) + } + } + } + + func setFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, state: [String: Any?], completion: @escaping (Result) -> Void) { + guard let state = JSONObject.init(turfRawValue: state) else { + completion(.failure(FlutterError( + code: "setFeatureStateError", + message: "Error converting feature state.", + details: nil + ))) + return + } + _ = self.mapboxMap.setFeatureState(feature.toMapFeaturesetFeature(), state: state) { error in + if let error { + completion(.failure(FlutterError( + code: "setFeatureStateError", + message: error.localizedDescription, + details: "Error setting feature state for feature: \(String(describing: feature.id))." + ))) + } else { + completion(.success(())) + } + } + } + func getFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, completion: @escaping (Result) -> Void) { self.mapboxMap.getFeatureState(sourceId: sourceId, sourceLayerId: sourceLayerId, featureId: featureId) { result in switch result { @@ -295,6 +345,36 @@ final class MapInterfaceController: _MapInterface { } } + func getFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, completion: @escaping (Result<[String: Any?], any Error>) -> Void) { + self.mapboxMap.getFeatureState(featureset: featureset.toMapFeaturesetDescriptor(), featureId: featureId.toMapFeaturesetFeatureId()) { result in + switch result { + case .success(let state): + completion(.success(state.mapValues { $0?.rawValue })) + case .failure(let error): + completion(.failure(FlutterError( + code: "getFeatureStateError", + message: "\(error)", + details: "Error getting feature state for feature \(String(describing: featureId)) from featureset {featureId: \(String(describing: featureset.featuresetId)), importId: \(String(describing: featureset.importId)), layerId: \(String(describing: featureset.layerId))}." + ))) + } + } + } + + func getFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, completion: @escaping (Result<[String: Any?], any Error>) -> Void) { + self.mapboxMap.getFeatureState(feature.toMapFeaturesetFeature()) { result in + switch result { + case .success(let state): + completion(.success(state.mapValues { $0?.rawValue })) + case .failure(let error): + completion(.failure(FlutterError( + code: "getFeatureStateError", + message: "\(error)", + details: "Error getting feature state for feature: \(String(describing: feature.id))." + ))) + } + } + } + func removeFeatureState(sourceId: String, sourceLayerId: String?, featureId: String, stateKey: String?, completion: @escaping (Result) -> Void) { self.mapboxMap.removeFeatureState(sourceId: sourceId, sourceLayerId: sourceLayerId, featureId: featureId, stateKey: stateKey) { result in switch result { @@ -306,6 +386,48 @@ final class MapInterfaceController: _MapInterface { } } + func removeFeatureStateForFeaturesetDescriptor(featureset: FeaturesetDescriptor, featureId: FeaturesetFeatureId, stateKey: String?, completion: @escaping (Result) -> Void) { + self.mapboxMap.removeFeatureState(featureset: featureset.toMapFeaturesetDescriptor(), featureId: featureId.toMapFeaturesetFeatureId(), stateKey: stateKey) { error in + if error != nil { + completion(.failure(FlutterError( + code: "removeFeatureStateError", + message: "Cannot remove feature state", + details: "Feature state was not found for the requested feature: \(String(describing: featureId))." + ))) + } else { + completion(.success(())) + } + } + } + + func removeFeatureStateForFeaturesetFeature(feature: FeaturesetFeature, stateKey: String?, completion: @escaping (Result) -> Void) { + self.mapboxMap.removeFeatureState(feature.toMapFeaturesetFeature(), stateKey: stateKey) { error in + if error != nil { + completion(.failure(FlutterError( + code: "removeFeatureStateError", + message: "Cannot remove feature state", + details: "Feature state was not found for the requested feature: \(String(describing: feature.id))" + ))) + } else { + completion(.success(())) + } + } + } + + func resetFeatureStatesForFeatureset(featureset: FeaturesetDescriptor, completion: @escaping (Result) -> Void) { + self.mapboxMap.resetFeatureStates(featureset: featureset.toMapFeaturesetDescriptor()) { error in + if error != nil { + completion(.failure(FlutterError( + code: "resetFeatureStateError", + message: "Cannot reset feature states", + details: "The requested featureset is not valid or was not found: {featureId: \(String(describing: featureset.featuresetId)), importId: \(String(describing: featureset.importId)), layerId: \(String(describing: featureset.layerId))}" + ))) + } else { + completion(.success(())) + } + } + } + func reduceMemoryUse() throws { mapboxMap.reduceMemoryUse() } diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift index 2c276ce6b..fb6974a3c 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift @@ -106,6 +106,37 @@ final class MapboxMapController: NSObject, FlutterPlatformView { case "gesture#remove_listeners": gesturesController!.removeListeners() result(nil) + case "interactions#add_interaction": + let listener = _InteractionsListener(binaryMessenger: binaryMessenger.messenger, messageChannelSuffix: binaryMessenger.suffix) + guard let arguments = methodCall.arguments as? [String: Any], + let interactionsList = arguments["interaction"] as? [Any?], + let interaction = _InteractionPigeon.fromList(interactionsList), + let featuresetDescriptor = FeaturesetDescriptor.fromList(interaction.featuresetDescriptor), + let interactionType = _InteractionType.fromString(interaction.interactionType), + let id = Int64(interaction.identifier) else { + return + } + let stopPropagation = interaction.stopPropagation + let filterExpression = try? interaction.filter.flatMap { try $0.toExp() } + let radius: CGFloat? = interaction.radius.flatMap { CGFloat($0) } + + switch interactionType { + case .tAP: + mapboxMap.addInteraction( + TapInteraction(featuresetDescriptor.toMapFeaturesetDescriptor(), filter: filterExpression, radius: radius, action: { featuresetFeature, context in + listener.onInteraction(context: context.toFLTMapContentGestureContext(), feature: featuresetFeature.toFLTFeaturesetFeature(), interactionID: id) { _ in } + return stopPropagation + }) + ) + case .lONGTAP: + mapboxMap.addInteraction( + LongPressInteraction(featuresetDescriptor.toMapFeaturesetDescriptor(), filter: filterExpression, radius: radius, action: { featuresetFeature, context in + listener.onInteraction(context: context.toFLTMapContentGestureContext(), feature: featuresetFeature.toFLTFeaturesetFeature(), interactionID: id) { _ in } + return stopPropagation + }) + ) + } + result(nil) case "platform#releaseMethodChannels": releaseMethodChannels() result(nil) diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/StyleController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/StyleController.swift index 98c4fa665..9c7b35f4c 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/StyleController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/StyleController.swift @@ -427,6 +427,12 @@ final class StyleController: StyleManager { } } + func getFeaturesets() throws -> [FeaturesetDescriptor] { + return styleManager.featuresets.map { + $0.toFLTFeaturesetDescriptor() + } + } + // MARK: Style Lights func getStyleLights() throws -> [StyleObjectInfo?] { diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/TurfAdapters.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/TurfAdapters.swift index 415146274..5abaaea56 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/TurfAdapters.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/TurfAdapters.swift @@ -70,6 +70,36 @@ extension Feature { } } +extension Turf.Geometry { + static func fromList(_ list: [Any?]) -> Turf.Geometry? { + guard let raw = list.first as? [String: Any], + let jsonData = try? JSONSerialization.data(withJSONObject: raw, options: []), + let geometry = try? JSONDecoder().decode(Turf.Geometry.self, from: jsonData) else { return nil } + + return geometry + } + + func toList() -> [Any?] { + return [ + self.toMap() + ] + } +} + +extension JSONObject { + static func fromList(_ list: [Any?]) -> JSONObject? { + guard let raw = list.first as? [String: Any] else { return nil } + + return JSONObject(turfRawValue: raw) + } + + static func fromString(_ string: String) -> JSONObject? { + guard let data = string.data(using: .utf8) else { return nil } + + return try? JSONDecoder().decode(JSONObject.self, from: data) + } +} + extension LocationCoordinate2D { fileprivate init(values: [Double]) { diff --git a/lib/mapbox_maps_flutter.dart b/lib/mapbox_maps_flutter.dart index 0078a9c6f..ad3147662 100644 --- a/lib/mapbox_maps_flutter.dart +++ b/lib/mapbox_maps_flutter.dart @@ -57,6 +57,10 @@ part 'src/style/source/rasterdem_source.dart'; part 'src/style/source/rasterarray_source.dart'; part 'src/style/source/vector_source.dart'; part 'src/style/style.dart'; +part 'src/style/interactive_features/interactive_features.dart'; +part 'src/style/interactive_features/standard_buildings.dart'; +part 'src/style/interactive_features/standard_place_labels.dart'; +part 'src/style/interactive_features/standard_poi.dart'; part 'src/location_settings.dart'; part 'src/snapshotter/snapshotter.dart'; part 'src/log_configuration.dart'; diff --git a/lib/src/callbacks.dart b/lib/src/callbacks.dart index c01a9b337..32a217b26 100644 --- a/lib/src/callbacks.dart +++ b/lib/src/callbacks.dart @@ -72,3 +72,7 @@ typedef void OnTileRegionLoadProgressListener(TileRegionLoadProgress progress); // TileRegionEstimate load progress callback. typedef void OnTileRegionEstimateProgressListenter( TileRegionEstimateProgress progress); + +// Interaction callback called when a featureset or layer is interacted with. +typedef void OnInteraction( + MapContentGestureContext context, T feature); diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 248442f3a..974d9cb14 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -443,6 +443,20 @@ class MapboxMap extends ChangeNotifier { _RenderedQueryGeometry(value: geometry.value, type: geometry.type), options); + /// Queries the map for rendered features with one typed featureset. + @experimental + Future> queryRenderedFeaturesForFeatureset( + {required FeaturesetDescriptor featureset, + RenderedQueryGeometry? geometry, + String? filter}) async { + return _mapInterface.queryRenderedFeaturesForFeatureset( + featureset, + (geometry != null) + ? _RenderedQueryGeometry(value: geometry.value, type: geometry.type) + : null, + filter); + } + /// Queries the map for source features. Future> querySourceFeatures( String sourceId, SourceQueryOptions options) => @@ -485,6 +499,26 @@ class MapboxMap extends ChangeNotifier { String featureId, String state) => _mapInterface.setFeatureState(sourceId, sourceLayerId, featureId, state); + /// Update the state map of a feature within a featureset. + /// Update entries in the state map of a given feature within a style source. Only entries listed in the state map + /// will be updated. An entry in the feature state map that is not listed in `state` will retain its previous value. + @experimental + Future setFeatureStateForFeaturesetDescriptor( + FeaturesetDescriptor featureset, + FeaturesetFeatureId featureId, + FeatureState state) => + _mapInterface.setFeatureStateForFeaturesetDescriptor( + featureset, featureId, state.map); + + /// Update the state map of an individual feature. + /// + /// The feature should have a non-nil ``FeaturesetFeatureType/id``. Otherwise, + /// the operation will be no-op and callback will receive an error. + @experimental + Future setFeatureStateForFeaturesetFeature( + FeaturesetFeature feature, FeatureState state) => + _mapInterface.setFeatureStateForFeaturesetFeature(feature, state.map); + /// Gets the state map of a feature within a style source. /// /// Note that updates to feature state are asynchronous, so changes made by other methods might not be @@ -493,6 +527,19 @@ class MapboxMap extends ChangeNotifier { String sourceId, String? sourceLayerId, String featureId) => _mapInterface.getFeatureState(sourceId, sourceLayerId, featureId); + /// Get the state map of a feature within a style source. + @experimental + Future> getFeatureStateForFeaturesetDescriptor( + FeaturesetDescriptor featureset, FeaturesetFeatureId featureId) => + _mapInterface.getFeatureStateForFeaturesetDescriptor( + featureset, featureId); + + /// Get the state map of a feature within a style source. + @experimental + Future> getFeatureStateForFeaturesetFeature( + FeaturesetFeature feature) => + _mapInterface.getFeatureStateForFeaturesetFeature(feature); + /// Removes entries from a feature state object. /// /// Remove a specified property or all property from a feature's state object, depending on the value of @@ -505,6 +552,52 @@ class MapboxMap extends ChangeNotifier { _mapInterface.removeFeatureState( sourceId, sourceLayerId, featureId, stateKey); + /// Removes entries from a feature state object of a feature in the specified featureset. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + @experimental + Future removeFeatureStateForFeaturesetDescriptor( + {required FeaturesetDescriptor featureset, + required FeaturesetFeatureId featureId, + String? stateKey}) => + _mapInterface.removeFeatureStateForFeaturesetDescriptor( + featureset, featureId, stateKey); + + /// Removes entries from a specified Feature. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + @experimental + Future removeFeatureStateForFeaturesetFeature( + {required FeaturesetFeature feature, String? stateKey}) => + _mapInterface.removeFeatureStateForFeaturesetFeature(feature, stateKey); + + /// Reset all the feature states within a featureset. + /// + /// Note that updates to feature state are asynchronous, so changes made by this method might not be + /// immediately visible using ``MapboxMap/getFeatureState(_:callback:)``. + @experimental + Future resetFeatureStatesForFeatureset( + FeaturesetDescriptor featureset) => + _mapInterface.resetFeatureStatesForFeatureset(featureset); + + /// References for all interactions added to the map. + @experimental + final _InteractionsMap _interactionsMap = + _InteractionsMap(interactions: {}); + + /// Add an interaction + @experimental + void addInteraction>( + TypedInteraction interaction) { + var id = _interactionsMap.interactions.length; + _interactionsMap.interactions[id] = _InteractionListener( + onInteractionListener: interaction.action, + interactionID: id, + ); + _InteractionsListener.setUp(_interactionsMap, + binaryMessenger: _mapboxMapsPlatform.binaryMessenger, + messageChannelSuffix: _mapboxMapsPlatform.channelSuffix.toString()); + _mapboxMapsPlatform.addInteractionsListeners(interaction, id); + } + /// Reduces memory use. Useful to call when the application gets paused or sent to background. Future reduceMemoryUse() => _mapInterface.reduceMemoryUse(); @@ -675,3 +768,57 @@ class _GestureListener extends GestureListener { onMapScrollListener?.call(context); } } + +/// Listen for a single interaction added to the map, identified by its id +class _InteractionListener + extends _InteractionsListener { + _InteractionListener({ + required this.interactionID, + required this.onInteractionListener, + }); + + int interactionID; + + final OnInteraction onInteractionListener; + + @override + void onInteraction(MapContentGestureContext context, + FeaturesetFeature feature, int interactionID) { + var featuresetID = feature.featureset.featuresetId; + T typedFeature; + + if (featuresetID == "buildings") { + typedFeature = + TypedFeaturesetFeature.fromFeaturesetFeature( + feature) as T; + } else if (featuresetID == "poi") { + typedFeature = + TypedFeaturesetFeature.fromFeaturesetFeature(feature) + as T; + } else if (featuresetID == "place-labels") { + typedFeature = + TypedFeaturesetFeature.fromFeaturesetFeature( + feature) as T; + } else { + typedFeature = feature as T; + } + + onInteractionListener.call(context, typedFeature); + } +} + +/// Listen to all interactions on the map, determine which interaction to call +class _InteractionsMap + extends _InteractionsListener { + _InteractionsMap({ + required this.interactions, + }); + + Map interactions; + + @override + void onInteraction(MapContentGestureContext context, + FeaturesetFeature feature, int interactionID) { + interactions[interactionID]?.onInteraction(context, feature, interactionID); + } +} diff --git a/lib/src/mapbox_maps_platform.dart b/lib/src/mapbox_maps_platform.dart index 8b092c325..14fc68ec2 100644 --- a/lib/src/mapbox_maps_platform.dart +++ b/lib/src/mapbox_maps_platform.dart @@ -107,7 +107,7 @@ class _MapboxMapsPlatform { case AndroidPlatformViewHostingMode.HC: return PlatformViewsService.initExpensiveAndroidView; case AndroidPlatformViewHostingMode.VD: - throw "Unexpected hostring mode(VD) when selecting an android view controller"; + throw "Unexpected hosting mode(VD) when selecting an android view controller"; } } @@ -161,6 +161,26 @@ class _MapboxMapsPlatform { } } + Future addInteractionsListeners( + _Interaction interaction, int interactionID) async { + var interactionPigeon = _InteractionPigeon( + featuresetDescriptor: + interaction.featuresetDescriptor.encode() as List, + stopPropagation: interaction.stopPropagation, + interactionType: interaction.interactionType.name, + identifier: interactionID.toString(), + radius: interaction.radius, + filter: interaction.filter); + try { + return _channel + .invokeMethod('interactions#add_interaction', { + 'interaction': interactionPigeon.encode(), + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + Future removeGestureListeners() async { try { return _channel.invokeMethod('gesture#remove_listeners'); diff --git a/lib/src/pigeons/map_interfaces.dart b/lib/src/pigeons/map_interfaces.dart index e25306cf1..1539cb573 100644 --- a/lib/src/pigeons/map_interfaces.dart +++ b/lib/src/pigeons/map_interfaces.dart @@ -168,6 +168,15 @@ enum ViewAnnotationAnchor { CENTER, } +/// The type of interaction, either tap/click or longTap/longClick +enum _InteractionType { + /// A short tap or click + TAP, + + /// A long tap or long click + LONG_TAP, +} + /// Type information of the variant's content enum Type { SCREEN_BOX, @@ -1289,6 +1298,275 @@ class QueriedFeature { } } +/// Identifies a feature in a featureset. +/// +/// Knowing the feature identifier allows to set the feature states to a particular feature, see ``MapboxMap/setFeatureState(featureset:featureId:state:callback:)``. +/// +/// In a featureset a feature can come from different underlying sources. In that case their IDs are not guaranteed to be unique in the featureset. +/// The ``FeaturesetFeatureId/namespace`` is used to disambiguate from which source the feature is coming. +/// +/// - Warning: There is no guarantee of identifier persistency. This depends on the underlying source of the features and may vary from style to style. +/// If you want to store the identifiers persistently, please make sure that the style or source provides this guarantee. +class FeaturesetFeatureId { + FeaturesetFeatureId({ + required this.id, + this.namespace, + }); + + /// A feature id coming from the feature itself.exp + String id; + + /// A namespace of the feature + String? namespace; + + Object encode() { + return [ + id, + namespace, + ]; + } + + static FeaturesetFeatureId decode(Object result) { + result as List; + return FeaturesetFeatureId( + id: result[0]! as String, + namespace: result[1] as String?, + ); + } +} + +/// Wraps a FeatureState map +class FeatureState { + FeatureState({ + required this.map, + }); + + Map map; + + Object encode() { + return [ + map, + ]; + } + + static FeatureState decode(Object result) { + result as List; + return FeatureState( + map: (result[0] as Map?)!.cast(), + ); + } +} + +/// Internal: An interaction that can be added to the map. +/// +/// To create an interaction use ``TapInteraction`` and ``LongClickInteraction`` implementations. +/// +/// See also: ``MapboxMap/addInteraction``. +class _Interaction { + _Interaction({ + required this.featuresetDescriptor, + required this.interactionType, + required this.stopPropagation, + this.filter, + this.radius, + }); + + /// The featureset descriptor that specifies the featureset to be included in the interaction. + FeaturesetDescriptor featuresetDescriptor; + + /// The type of interaction, either tap or longTap + _InteractionType interactionType; + + /// Whether to stop the propagation of the interaction to the map. Defaults to true. + bool stopPropagation; + + /// An optional filter of features that should trigger the interaction. + String? filter; + + /// Radius of a tappable area + double? radius; + + Object encode() { + return [ + featuresetDescriptor, + interactionType, + stopPropagation, + filter, + radius, + ]; + } + + static _Interaction decode(Object result) { + result as List; + return _Interaction( + featuresetDescriptor: result[0]! as FeaturesetDescriptor, + interactionType: result[1]! as _InteractionType, + stopPropagation: result[2]! as bool, + filter: result[3] as String?, + radius: result[4] as double?, + ); + } +} + +/// Internal class to handle pigeon conversions for interactions. +class _InteractionPigeon { + _InteractionPigeon({ + required this.featuresetDescriptor, + required this.stopPropagation, + required this.interactionType, + required this.identifier, + this.filter, + this.radius, + }); + + /// The featureset descriptor that specifies the featureset to be included in the interaction. + List featuresetDescriptor; + + /// Whether to stop the propagation of the interaction to the map + bool stopPropagation; + + /// The type of interaction, either tap or longTap as a String + String interactionType; + + /// An identifier for the interaction + String identifier; + + /// An optional filter of features that should trigger the interaction. + String? filter; + + /// Radius of a tappable area + double? radius; + + Object encode() { + return [ + featuresetDescriptor, + stopPropagation, + interactionType, + identifier, + filter, + radius, + ]; + } + + static _InteractionPigeon decode(Object result) { + result as List; + return _InteractionPigeon( + featuresetDescriptor: (result[0] as List?)!.cast(), + stopPropagation: result[1]! as bool, + interactionType: result[2]! as String, + identifier: result[3]! as String, + filter: result[4] as String?, + radius: result[5] as double?, + ); + } +} + +/// A featureset descriptor. +/// +/// The descriptor instance acts as a universal target for interactions or querying rendered features (see 'TapInteraction', 'LongTapInteraction') +class FeaturesetDescriptor { + FeaturesetDescriptor({ + this.featuresetId, + this.importId, + this.layerId, + }); + + /// An optional unique identifier for the featureset within the style. + /// This id is used to reference a specific featureset. + /// + /// * Note: If `featuresetId` is provided and valid, it takes precedence over `layerId`, + /// * meaning `layerId` will not be considered even if it has a valid value. + String? featuresetId; + + /// An optional import id that is required if the featureset is defined within an imported style. + /// If the featureset belongs to the current style, this field should be set to a null string. + /// + /// Note: `importId` is only applicable when used in conjunction with `featuresetId` + /// and has no effect when used with `layerId`. + String? importId; + + /// An optional unique identifier for the layer within the current style. + /// + /// Note: If `featuresetId` is valid, `layerId` will be ignored even if it has a valid value. + /// Additionally, `importId` does not apply when using `layerId`. + String? layerId; + + Object encode() { + return [ + featuresetId, + importId, + layerId, + ]; + } + + static FeaturesetDescriptor decode(Object result) { + result as List; + return FeaturesetDescriptor( + featuresetId: result[0] as String?, + importId: result[1] as String?, + layerId: result[2] as String?, + ); + } +} + +/// A basic feature of a featureset. +/// +/// If you use Standard Style, you can use typed alternatives like `StandardPoiFeature`, `StandardPlaceLabelsFeature`, `StandardBuildingsFeature`. +/// +/// The featureset feature is different to the `Turf.Feature`. The latter represents any GeoJSON feature, while the former is a high level representation of features. +class FeaturesetFeature { + FeaturesetFeature({ + this.id, + required this.featureset, + required this.geometry, + required this.properties, + required this.state, + }); + + /// An identifier of the feature. + /// + /// The identifier can be `nil` if the underlying source doesn't have identifiers for features. + /// In this case it's impossible to set a feature state for an individual feature. + FeaturesetFeatureId? id; + + /// A featureset descriptor denoting the featureset this feature belongs to. + FeaturesetDescriptor featureset; + + /// A feature geometry. + Map geometry; + + /// Feature JSON properties. + Map properties; + + /// A feature state. + /// + /// This is a **snapshot** of the state that the feature had when it was interacted with. + /// To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + Map state; + + Object encode() { + return [ + id, + featureset, + geometry, + properties, + state, + ]; + } + + static FeaturesetFeature decode(Object result) { + result as List; + return FeaturesetFeature( + id: result[0] as FeaturesetFeatureId?, + featureset: result[1]! as FeaturesetDescriptor, + geometry: (result[2] as Map?)!.cast(), + properties: + (result[3] as Map?)!.cast(), + state: (result[4] as Map?)!.cast(), + ); + } +} + /// Geometry for querying rendered features. class _RenderedQueryGeometry { _RenderedQueryGeometry({ @@ -1870,176 +2148,203 @@ class MapInterfaces_PigeonCodec extends StandardMessageCodec { } else if (value is ViewAnnotationAnchor) { buffer.putUint8(136); writeValue(buffer, value.index); - } else if (value is Type) { + } else if (value is _InteractionType) { buffer.putUint8(137); writeValue(buffer, value.index); - } else if (value is FillExtrusionBaseAlignment) { + } else if (value is GestureState) { buffer.putUint8(138); writeValue(buffer, value.index); - } else if (value is FillExtrusionHeightAlignment) { + } else if (value is Type) { buffer.putUint8(139); writeValue(buffer, value.index); - } else if (value is BackgroundPitchAlignment) { + } else if (value is FillExtrusionBaseAlignment) { buffer.putUint8(140); writeValue(buffer, value.index); - } else if (value is StylePackErrorType) { + } else if (value is FillExtrusionHeightAlignment) { buffer.putUint8(141); writeValue(buffer, value.index); - } else if (value is ResponseErrorReason) { + } else if (value is BackgroundPitchAlignment) { buffer.putUint8(142); writeValue(buffer, value.index); - } else if (value is OfflineRegionDownloadState) { + } else if (value is StylePackErrorType) { buffer.putUint8(143); writeValue(buffer, value.index); - } else if (value is TileStoreUsageMode) { + } else if (value is ResponseErrorReason) { buffer.putUint8(144); writeValue(buffer, value.index); - } else if (value is StylePropertyValueKind) { + } else if (value is OfflineRegionDownloadState) { buffer.putUint8(145); writeValue(buffer, value.index); - } else if (value is StyleProjectionName) { + } else if (value is TileStoreUsageMode) { buffer.putUint8(146); writeValue(buffer, value.index); - } else if (value is Anchor) { + } else if (value is StylePropertyValueKind) { buffer.putUint8(147); writeValue(buffer, value.index); - } else if (value is HttpMethod) { + } else if (value is StyleProjectionName) { buffer.putUint8(148); writeValue(buffer, value.index); - } else if (value is HttpRequestErrorType) { + } else if (value is Anchor) { buffer.putUint8(149); writeValue(buffer, value.index); - } else if (value is DownloadErrorCode) { + } else if (value is HttpMethod) { buffer.putUint8(150); writeValue(buffer, value.index); - } else if (value is DownloadState) { + } else if (value is HttpRequestErrorType) { buffer.putUint8(151); writeValue(buffer, value.index); - } else if (value is TileRegionErrorType) { + } else if (value is DownloadErrorCode) { buffer.putUint8(152); writeValue(buffer, value.index); - } else if (value is _MapEvent) { + } else if (value is DownloadState) { buffer.putUint8(153); writeValue(buffer, value.index); - } else if (value is Point) { + } else if (value is TileRegionErrorType) { buffer.putUint8(154); + writeValue(buffer, value.index); + } else if (value is _MapEvent) { + buffer.putUint8(155); + writeValue(buffer, value.index); + } else if (value is Point) { + buffer.putUint8(156); writeValue(buffer, value.encode()); } else if (value is Feature) { - buffer.putUint8(155); + buffer.putUint8(157); writeValue(buffer, value.encode()); } else if (value is GlyphsRasterizationOptions) { - buffer.putUint8(156); + buffer.putUint8(158); writeValue(buffer, value.encode()); } else if (value is TileCoverOptions) { - buffer.putUint8(157); + buffer.putUint8(159); writeValue(buffer, value.encode()); } else if (value is MbxEdgeInsets) { - buffer.putUint8(158); + buffer.putUint8(160); writeValue(buffer, value.encode()); } else if (value is CameraOptions) { - buffer.putUint8(159); + buffer.putUint8(161); writeValue(buffer, value.encode()); } else if (value is CameraState) { - buffer.putUint8(160); + buffer.putUint8(162); writeValue(buffer, value.encode()); } else if (value is CameraBoundsOptions) { - buffer.putUint8(161); + buffer.putUint8(163); writeValue(buffer, value.encode()); } else if (value is CameraBounds) { - buffer.putUint8(162); + buffer.putUint8(164); writeValue(buffer, value.encode()); } else if (value is MapAnimationOptions) { - buffer.putUint8(163); + buffer.putUint8(165); writeValue(buffer, value.encode()); } else if (value is CoordinateBounds) { - buffer.putUint8(164); + buffer.putUint8(166); writeValue(buffer, value.encode()); } else if (value is MapDebugOptions) { - buffer.putUint8(165); + buffer.putUint8(167); writeValue(buffer, value.encode()); } else if (value is TileCacheBudgetInMegabytes) { - buffer.putUint8(166); + buffer.putUint8(168); writeValue(buffer, value.encode()); } else if (value is TileCacheBudgetInTiles) { - buffer.putUint8(167); + buffer.putUint8(169); writeValue(buffer, value.encode()); } else if (value is MapOptions) { - buffer.putUint8(168); + buffer.putUint8(170); writeValue(buffer, value.encode()); } else if (value is ScreenCoordinate) { - buffer.putUint8(169); + buffer.putUint8(171); writeValue(buffer, value.encode()); } else if (value is ScreenBox) { - buffer.putUint8(170); + buffer.putUint8(172); writeValue(buffer, value.encode()); } else if (value is CoordinateBoundsZoom) { - buffer.putUint8(171); + buffer.putUint8(173); writeValue(buffer, value.encode()); } else if (value is Size) { - buffer.putUint8(172); + buffer.putUint8(174); writeValue(buffer, value.encode()); } else if (value is RenderedQueryOptions) { - buffer.putUint8(173); + buffer.putUint8(175); writeValue(buffer, value.encode()); } else if (value is SourceQueryOptions) { - buffer.putUint8(174); + buffer.putUint8(176); writeValue(buffer, value.encode()); } else if (value is FeatureExtensionValue) { - buffer.putUint8(175); + buffer.putUint8(177); writeValue(buffer, value.encode()); } else if (value is LayerPosition) { - buffer.putUint8(176); + buffer.putUint8(178); writeValue(buffer, value.encode()); } else if (value is QueriedRenderedFeature) { - buffer.putUint8(177); + buffer.putUint8(179); writeValue(buffer, value.encode()); } else if (value is QueriedSourceFeature) { - buffer.putUint8(178); + buffer.putUint8(180); writeValue(buffer, value.encode()); } else if (value is QueriedFeature) { - buffer.putUint8(179); + buffer.putUint8(181); + writeValue(buffer, value.encode()); + } else if (value is FeaturesetFeatureId) { + buffer.putUint8(182); + writeValue(buffer, value.encode()); + } else if (value is FeatureState) { + buffer.putUint8(183); + writeValue(buffer, value.encode()); + } else if (value is _Interaction) { + buffer.putUint8(184); + writeValue(buffer, value.encode()); + } else if (value is _InteractionPigeon) { + buffer.putUint8(185); + writeValue(buffer, value.encode()); + } else if (value is FeaturesetDescriptor) { + buffer.putUint8(186); + writeValue(buffer, value.encode()); + } else if (value is FeaturesetFeature) { + buffer.putUint8(187); + writeValue(buffer, value.encode()); + } else if (value is MapContentGestureContext) { + buffer.putUint8(188); writeValue(buffer, value.encode()); } else if (value is _RenderedQueryGeometry) { - buffer.putUint8(180); + buffer.putUint8(189); writeValue(buffer, value.encode()); } else if (value is ProjectedMeters) { - buffer.putUint8(181); + buffer.putUint8(190); writeValue(buffer, value.encode()); } else if (value is MercatorCoordinate) { - buffer.putUint8(182); + buffer.putUint8(191); writeValue(buffer, value.encode()); } else if (value is StyleObjectInfo) { - buffer.putUint8(183); + buffer.putUint8(192); writeValue(buffer, value.encode()); } else if (value is StyleProjection) { - buffer.putUint8(184); + buffer.putUint8(193); writeValue(buffer, value.encode()); } else if (value is FlatLight) { - buffer.putUint8(185); + buffer.putUint8(194); writeValue(buffer, value.encode()); } else if (value is DirectionalLight) { - buffer.putUint8(186); + buffer.putUint8(195); writeValue(buffer, value.encode()); } else if (value is AmbientLight) { - buffer.putUint8(187); + buffer.putUint8(196); writeValue(buffer, value.encode()); } else if (value is MbxImage) { - buffer.putUint8(188); + buffer.putUint8(197); writeValue(buffer, value.encode()); } else if (value is ImageStretches) { - buffer.putUint8(189); + buffer.putUint8(198); writeValue(buffer, value.encode()); } else if (value is ImageContent) { - buffer.putUint8(190); + buffer.putUint8(199); writeValue(buffer, value.encode()); } else if (value is TransitionOptions) { - buffer.putUint8(191); + buffer.putUint8(200); writeValue(buffer, value.encode()); } else if (value is CanonicalTileID) { - buffer.putUint8(192); + buffer.putUint8(201); writeValue(buffer, value.encode()); } else if (value is StylePropertyValue) { - buffer.putUint8(193); + buffer.putUint8(202); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -2075,136 +2380,156 @@ class MapInterfaces_PigeonCodec extends StandardMessageCodec { return value == null ? null : ViewAnnotationAnchor.values[value]; case 137: final int? value = readValue(buffer) as int?; - return value == null ? null : Type.values[value]; + return value == null ? null : _InteractionType.values[value]; case 138: final int? value = readValue(buffer) as int?; - return value == null ? null : FillExtrusionBaseAlignment.values[value]; + return value == null ? null : GestureState.values[value]; case 139: + final int? value = readValue(buffer) as int?; + return value == null ? null : Type.values[value]; + case 140: + final int? value = readValue(buffer) as int?; + return value == null ? null : FillExtrusionBaseAlignment.values[value]; + case 141: final int? value = readValue(buffer) as int?; return value == null ? null : FillExtrusionHeightAlignment.values[value]; - case 140: + case 142: final int? value = readValue(buffer) as int?; return value == null ? null : BackgroundPitchAlignment.values[value]; - case 141: + case 143: final int? value = readValue(buffer) as int?; return value == null ? null : StylePackErrorType.values[value]; - case 142: + case 144: final int? value = readValue(buffer) as int?; return value == null ? null : ResponseErrorReason.values[value]; - case 143: + case 145: final int? value = readValue(buffer) as int?; return value == null ? null : OfflineRegionDownloadState.values[value]; - case 144: + case 146: final int? value = readValue(buffer) as int?; return value == null ? null : TileStoreUsageMode.values[value]; - case 145: + case 147: final int? value = readValue(buffer) as int?; return value == null ? null : StylePropertyValueKind.values[value]; - case 146: + case 148: final int? value = readValue(buffer) as int?; return value == null ? null : StyleProjectionName.values[value]; - case 147: + case 149: final int? value = readValue(buffer) as int?; return value == null ? null : Anchor.values[value]; - case 148: + case 150: final int? value = readValue(buffer) as int?; return value == null ? null : HttpMethod.values[value]; - case 149: + case 151: final int? value = readValue(buffer) as int?; return value == null ? null : HttpRequestErrorType.values[value]; - case 150: + case 152: final int? value = readValue(buffer) as int?; return value == null ? null : DownloadErrorCode.values[value]; - case 151: + case 153: final int? value = readValue(buffer) as int?; return value == null ? null : DownloadState.values[value]; - case 152: + case 154: final int? value = readValue(buffer) as int?; return value == null ? null : TileRegionErrorType.values[value]; - case 153: + case 155: final int? value = readValue(buffer) as int?; return value == null ? null : _MapEvent.values[value]; - case 154: + case 156: return Point.decode(readValue(buffer)!); - case 155: + case 157: return Feature.decode(readValue(buffer)!); - case 156: + case 158: return GlyphsRasterizationOptions.decode(readValue(buffer)!); - case 157: + case 159: return TileCoverOptions.decode(readValue(buffer)!); - case 158: + case 160: return MbxEdgeInsets.decode(readValue(buffer)!); - case 159: + case 161: return CameraOptions.decode(readValue(buffer)!); - case 160: + case 162: return CameraState.decode(readValue(buffer)!); - case 161: + case 163: return CameraBoundsOptions.decode(readValue(buffer)!); - case 162: + case 164: return CameraBounds.decode(readValue(buffer)!); - case 163: + case 165: return MapAnimationOptions.decode(readValue(buffer)!); - case 164: + case 166: return CoordinateBounds.decode(readValue(buffer)!); - case 165: + case 167: return MapDebugOptions.decode(readValue(buffer)!); - case 166: + case 168: return TileCacheBudgetInMegabytes.decode(readValue(buffer)!); - case 167: + case 169: return TileCacheBudgetInTiles.decode(readValue(buffer)!); - case 168: + case 170: return MapOptions.decode(readValue(buffer)!); - case 169: + case 171: return ScreenCoordinate.decode(readValue(buffer)!); - case 170: + case 172: return ScreenBox.decode(readValue(buffer)!); - case 171: + case 173: return CoordinateBoundsZoom.decode(readValue(buffer)!); - case 172: + case 174: return Size.decode(readValue(buffer)!); - case 173: + case 175: return RenderedQueryOptions.decode(readValue(buffer)!); - case 174: + case 176: return SourceQueryOptions.decode(readValue(buffer)!); - case 175: + case 177: return FeatureExtensionValue.decode(readValue(buffer)!); - case 176: + case 178: return LayerPosition.decode(readValue(buffer)!); - case 177: + case 179: return QueriedRenderedFeature.decode(readValue(buffer)!); - case 178: + case 180: return QueriedSourceFeature.decode(readValue(buffer)!); - case 179: + case 181: return QueriedFeature.decode(readValue(buffer)!); - case 180: + case 182: + return FeaturesetFeatureId.decode(readValue(buffer)!); + case 183: + return FeatureState.decode(readValue(buffer)!); + case 184: + return _Interaction.decode(readValue(buffer)!); + case 185: + return _InteractionPigeon.decode(readValue(buffer)!); + case 186: + return FeaturesetDescriptor.decode(readValue(buffer)!); + case 187: + return FeaturesetFeature.decode(readValue(buffer)!); + case 188: + return MapContentGestureContext.decode(readValue(buffer)!); + case 189: return _RenderedQueryGeometry.decode(readValue(buffer)!); - case 181: + case 190: return ProjectedMeters.decode(readValue(buffer)!); - case 182: + case 191: return MercatorCoordinate.decode(readValue(buffer)!); - case 183: + case 192: return StyleObjectInfo.decode(readValue(buffer)!); - case 184: + case 193: return StyleProjection.decode(readValue(buffer)!); - case 185: + case 194: return FlatLight.decode(readValue(buffer)!); - case 186: + case 195: return DirectionalLight.decode(readValue(buffer)!); - case 187: + case 196: return AmbientLight.decode(readValue(buffer)!); - case 188: + case 197: return MbxImage.decode(readValue(buffer)!); - case 189: + case 198: return ImageStretches.decode(readValue(buffer)!); - case 190: + case 199: return ImageContent.decode(readValue(buffer)!); - case 191: + case 200: return TransitionOptions.decode(readValue(buffer)!); - case 192: + case 201: return CanonicalTileID.decode(readValue(buffer)!); - case 193: + case 202: return StylePropertyValue.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -3068,6 +3393,60 @@ class _CameraManager { } } +abstract class _InteractionsListener { + static const MessageCodec pigeonChannelCodec = + MapInterfaces_PigeonCodec(); + + void onInteraction(MapContentGestureContext context, + FeaturesetFeature feature, int interactionID); + + static void setUp( + _InteractionsListener? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction was null.'); + final List args = (message as List?)!; + final MapContentGestureContext? arg_context = + (args[0] as MapContentGestureContext?); + assert(arg_context != null, + 'Argument for dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction was null, expected non-null MapContentGestureContext.'); + final FeaturesetFeature? arg_feature = + (args[1] as FeaturesetFeature?); + assert(arg_feature != null, + 'Argument for dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction was null, expected non-null FeaturesetFeature.'); + final int? arg_interactionID = (args[2] as int?); + assert(arg_interactionID != null, + 'Argument for dev.flutter.pigeon.mapbox_maps_flutter._InteractionsListener.onInteraction was null, expected non-null int.'); + try { + api.onInteraction(arg_context!, arg_feature!, arg_interactionID!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } +} + /// Map class provides map rendering functionality. /// class _MapInterface { @@ -3688,6 +4067,50 @@ class _MapInterface { } } + /// Queries the map for rendered features with one typed featureset. + /// + /// The results array will contain features of the type specified by this featureset. + /// + /// - Important: If you need to handle basic gestures on map content, + /// please prefer to use Interactions API, see `MapboxMap/addInteraction`. + /// + /// @param featureset A typed featureset to query with. + /// @param geometry An optional screen geometry to query. Can be a `CGPoint`, `CGRect`, or an array of `CGPoint`. + /// If omitted, the full viewport is queried. + /// @param filter An additional filter for features. + Future> queryRenderedFeaturesForFeatureset( + FeaturesetDescriptor featureset, + _RenderedQueryGeometry? geometry, + String? filter) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.queryRenderedFeaturesForFeatureset$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([featureset, geometry, filter]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)! + .cast(); + } + } + /// Queries the map for source features. /// /// @param sourceId The style source identifier used to query for source features. @@ -3883,6 +4306,72 @@ class _MapInterface { } } + /// Update the state map of a feature within a featureset. + /// Update entries in the state map of a given feature within a style source. Only entries listed in the state map + /// will be updated. An entry in the feature state map that is not listed in `state` will retain its previous value. + /// + /// @param featureset The featureset to look the feature in. + /// @param featureId Identifier of the feature whose state should be updated. + /// @param state Map of entries to update with their respective new values + Future setFeatureStateForFeaturesetDescriptor( + FeaturesetDescriptor featureset, + FeaturesetFeatureId featureId, + Map state) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetDescriptor$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([featureset, featureId, state]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Update the state map of an individual feature. + /// + /// The feature should have a non-nil ``FeaturesetFeatureType/id``. Otherwise, + /// the operation will be no-op and callback will receive an error. + /// + /// @param feature The feature to update. + /// @param state Map of entries to update with their respective new values + Future setFeatureStateForFeaturesetFeature( + FeaturesetFeature feature, Map state) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.setFeatureStateForFeaturesetFeature$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([feature, state]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + /// Gets the state map of a feature within a style source. /// /// Note that updates to feature state are asynchronous, so changes made by other methods might not be @@ -3891,7 +4380,8 @@ class _MapInterface { /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). /// @param featureId The feature identifier of the feature whose state should be queried. - /// @param completion The `query feature state completion` called when the query completes. + /// + /// @return A String representing the Feature's state map. Future getFeatureState( String sourceId, String? sourceLayerId, String featureId) async { final String pigeonVar_channelName = @@ -3922,13 +4412,86 @@ class _MapInterface { } } + /// Get the state map of a feature within a style source. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be queried. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + Future> getFeatureStateForFeaturesetDescriptor( + FeaturesetDescriptor featureset, FeaturesetFeatureId featureId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetDescriptor$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([featureset, featureId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Map?)! + .cast(); + } + } + + /// Get the state map of a feature within a style source. + /// + /// @param feature An interactive feature to query the state of. + /// + /// @return The Feature's state map or an empty map if the feature could not be found. + Future> getFeatureStateForFeaturesetFeature( + FeaturesetFeature feature) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.getFeatureStateForFeaturesetFeature$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([feature]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Map?)! + .cast(); + } + } + /// Removes entries from a feature state object. /// /// Remove a specified property or all property from a feature's state object, depending on the value of /// `stateKey`. /// /// Note that updates to feature state are asynchronous, so changes made by this method might not be - /// immediately visible using `getStateFeature`. + /// immediately visible using `getFeatureState`. /// /// @param sourceId The style source identifier. /// @param sourceLayerId The style source layer identifier (for multi-layer sources such as vector sources). @@ -3960,6 +4523,100 @@ class _MapInterface { } } + /// Removes entries from a feature state object of a feature in the specified featureset. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param featureset A featureset the feature belongs to. + /// @param featureId Identifier of the feature whose state should be removed. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + Future removeFeatureStateForFeaturesetDescriptor( + FeaturesetDescriptor featureset, + FeaturesetFeatureId featureId, + String? stateKey) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetDescriptor$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([featureset, featureId, stateKey]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Removes entries from a specified Feature. + /// Remove a specified property or all property from a feature's state object, depending on the value of `stateKey`. + /// + /// @param feature An interactive feature to update. + /// @param stateKey The key of the property to remove. If `nil`, all feature's state object properties are removed. Defaults to `nil`. + Future removeFeatureStateForFeaturesetFeature( + FeaturesetFeature feature, String? stateKey) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.removeFeatureStateForFeaturesetFeature$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([feature, stateKey]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Reset all the feature states within a featureset. + /// + /// Note that updates to feature state are asynchronous, so changes made by this method might not be + /// immediately visible using ``MapboxMap/getFeatureState()``. + /// + /// @param featureset A featureset descriptor + Future resetFeatureStatesForFeatureset( + FeaturesetDescriptor featureset) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter._MapInterface.resetFeatureStatesForFeatureset$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([featureset]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + /// Reduces memory use. Useful to call when the application gets paused or sent to background. Future reduceMemoryUse() async { final String pigeonVar_channelName = @@ -6778,6 +7435,39 @@ class StyleManager { return; } } + + /// Returns the available featuresets in the currently loaded style. + /// + /// - Note: This function should only be called after the style is fully loaded; otherwise, the result may be unreliable. + Future> getFeaturesets() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.mapbox_maps_flutter.StyleManager.getFeaturesets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)! + .cast(); + } + } } /// Allows to cancel the associated asynchronous operation diff --git a/lib/src/style/interactive_features/interactive_features.dart b/lib/src/style/interactive_features/interactive_features.dart new file mode 100644 index 000000000..f2379b8dc --- /dev/null +++ b/lib/src/style/interactive_features/interactive_features.dart @@ -0,0 +1,61 @@ +part of '../../../mapbox_maps_flutter.dart'; + +/// A single tap interaction on a Featureset with a typed `FeaturesetDescriptor`. +final class TapInteraction + extends TypedInteraction> { + TapInteraction( + T featuresetDescriptor, OnInteraction> action, + {super.filter, super.radius, super.stopPropagation = true}) + : super( + featuresetDescriptor: featuresetDescriptor, + interactionType: "TAP", + action: action); +} + +/// A long tap interaction on a Featureset with a typed `FeaturesetDescriptor`. +final class LongTapInteraction + extends TypedInteraction> { + LongTapInteraction( + T featuresetDescriptor, OnInteraction> action, + {super.filter, super.radius, super.stopPropagation = true}) + : super( + featuresetDescriptor: featuresetDescriptor, + interactionType: "LONG_TAP", + action: action); +} + +/// An `_Interaction` with an action that has a typed `FeaturesetFeature` as input. +final class TypedInteraction + extends _Interaction { + TypedInteraction( + {required super.featuresetDescriptor, + super.filter, + super.radius, + super.stopPropagation = true, + required this.action, + required interactionType}) + : super(interactionType: _InteractionType.values.byName(interactionType)); + + OnInteraction action; +} + +/// A `FeaturesetFeature` with a typed `FeaturesetDescriptor`. This is used to provide typed access to the specific properties and state of a feature with a known type such as StandardPOIs or StandardBuildings. +class TypedFeaturesetFeature + extends FeaturesetFeature { + TypedFeaturesetFeature(T featureset, Map geometry, + Map properties, Map state, {super.id}) + : super( + featureset: featureset, + geometry: geometry, + properties: properties, + state: state); + + /// Creates a `TypedFeaturesetFeature` from a `FeaturesetFeature`. + TypedFeaturesetFeature.fromFeaturesetFeature(FeaturesetFeature feature) + : super( + id: feature.id, + featureset: feature.featureset, + geometry: feature.geometry, + properties: feature.properties, + state: feature.state); +} diff --git a/lib/src/style/interactive_features/standard_buildings.dart b/lib/src/style/interactive_features/standard_buildings.dart new file mode 100644 index 000000000..e5cc41579 --- /dev/null +++ b/lib/src/style/interactive_features/standard_buildings.dart @@ -0,0 +1,49 @@ +part of '../../../mapbox_maps_flutter.dart'; + +/// A Feature that represents a building in the Standard style. +extension StandardBuildingsFeature + on TypedFeaturesetFeature { + /// A feature state. + /// + /// This is a **snapshot** of the state that the feature had when it was interacted with. + /// To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + StandardBuildingState get stateSnapshot { + return StandardBuildingState() + ..highlight = state["highlight"] as bool? + ..select = state["select"] as bool?; + } + + /// A high-level building group like building-2d, building-3d, etc. + String? get group { + return properties["group"] as String?; + } +} + +/// A Featureset of buildings in the Standard style +class StandardBuildings extends FeaturesetDescriptor { + StandardBuildings({String importId = "basemap"}) + : super(featuresetId: "buildings", importId: importId); +} + +/// Represents available states for Buildings in the Standard style. +class StandardBuildingState extends FeatureState { + /// When `true`, the feature is highlighted. Use this state to create a temporary effect (e.g. hover). + bool? highlight; + + /// When `true`, the feature is selected. Use this state to create a permanent effect. Note: the `select` state has a higher priority than `highlight`. + bool? select; + + @override + Map get map { + return { + "highlight": highlight, + "select": select, + }; + } + + StandardBuildingState({this.highlight, this.select}) + : super(map: { + "highlight": highlight, + "select": select, + }); +} diff --git a/lib/src/style/interactive_features/standard_place_labels.dart b/lib/src/style/interactive_features/standard_place_labels.dart new file mode 100644 index 000000000..f313b1df5 --- /dev/null +++ b/lib/src/style/interactive_features/standard_place_labels.dart @@ -0,0 +1,61 @@ +part of '../../../mapbox_maps_flutter.dart'; + +/// A feature that labels places including countries, states, cities, +/// towns, and neighborhoods in the Standard style. +extension StandardPlaceLabelsFeature + on TypedFeaturesetFeature { + /// A feature state. + /// + /// This is a **snapshot** of the state that the feature had when it was interacted with. + /// To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + StandardPlaceLabelsState get stateSnapshot { + return StandardPlaceLabelsState() + ..hide = state["hide"] as bool? + ..highlight = state["highlight"] as bool? + ..select = state["select"] as bool?; + } + + /// Name of the place label. + String? get name { + return properties["name"] as String?; + } + + /// Provides a broad distinction between place types. + String? get category { + return properties["class"] as String?; + } +} + +/// A Featureset of place labels in the Standard style +class StandardPlaceLabels extends FeaturesetDescriptor { + StandardPlaceLabels({String importId = "basemap"}) + : super(featuresetId: "place-labels", importId: importId); +} + +/// Represents available states for Place Labels in the Standard style. +class StandardPlaceLabelsState extends FeatureState { + /// When `true`, hides the label. Use this state when displaying a custom annotation on top. + bool? hide; + + /// When `true`, the feature is highlighted. Use this state to create a temporary effect (e.g. hover). + bool? highlight; + + /// When `true`, the feature is selected. Use this state to create a permanent effect. Note: the `select` state has a higher priority than `highlight`. + bool? select; + + @override + Map get map { + return { + "hide": hide, + "highlight": highlight, + "select": select, + }; + } + + StandardPlaceLabelsState({this.hide, this.highlight, this.select}) + : super(map: { + "hide": hide, + "highlight": highlight, + "select": select, + }); +} diff --git a/lib/src/style/interactive_features/standard_poi.dart b/lib/src/style/interactive_features/standard_poi.dart new file mode 100644 index 000000000..148dec3da --- /dev/null +++ b/lib/src/style/interactive_features/standard_poi.dart @@ -0,0 +1,79 @@ +part of '../../../mapbox_maps_flutter.dart'; + +/// A feature that is a point of interest in the Standard style. +extension StandardPOIsFeature on TypedFeaturesetFeature { + /// A feature state. + /// + /// This is a **snapshot** of the state that the feature had when it was interacted with. + /// To update and read the original state, use ``MapboxMap/setFeatureState()`` and ``MapboxMap/getFeatureState()``. + StandardPOIsState get stateSnapshot { + return StandardPOIsState()..hide = state["hide"] as bool?; + } + + /// Name of the point of interest. + String? get name { + return properties["name"] as String?; + } + + /// A high-level point of interest category like airport, transit, etc. + String? get group { + return properties["group"] as String?; + } + + /// A broad category of point of interest. + String? get category { + return properties["class"] as String?; + } + + /// An icon identifier, designed to assign icons using the Maki icon project or other icons that follow the same naming scheme. + String? get maki { + return properties["maki"] as String?; + } + + /// Mode of transport served by a stop/station. Expected to be null for non-transit points of interest. + String? get transitMode { + return properties["transit_mode"] as String?; + } + + /// A type of transit stop. Expected to be null for non-transit points of interest. + String? get transitStopType { + return properties["transit_stop_type"] as String?; + } + + /// A rail station network identifier that is part of specific local or regional transit systems. Expected to be null for non-transit points of interest. + String? get transitNetwork { + return properties["transit_network"] as String?; + } + + /// A short identifier code of the airport. Expected to be null for non-airport points of interest + String? get airportRef { + return properties["airport_ref"] as String?; + } + + /// POI coordinate. + Point? get coordinate { + return this.geometry["coordinates"] as Point?; + } +} + +/// A Featureset of POIs in the Standard style +class StandardPOIs extends FeaturesetDescriptor { + StandardPOIs({String importId = "basemap"}) + : super(featuresetId: "poi", importId: importId); +} + +/// Represents available states for POIs in the Standard style. +class StandardPOIsState extends FeatureState { + /// When `true`, hides the icon and text. + bool? hide; + + @override + Map get map { + return {"hide": hide}; + } + + StandardPOIsState({this.hide}) + : super(map: { + "hide": hide, + }); +} diff --git a/lib/src/style/mapbox_styles.dart b/lib/src/style/mapbox_styles.dart index c599ee519..b046a9622 100644 --- a/lib/src/style/mapbox_styles.dart +++ b/lib/src/style/mapbox_styles.dart @@ -36,6 +36,12 @@ class MapboxStyles { /// improve the style. static const String SATELLITE_STREETS = "mapbox://styles/mapbox/satellite-streets-v12"; + + /// NOT FOR PRODUCTION USE. An experimental version of the Mapbox Standard style. + /// This style is used for testing new features and changes to the Mapbox Standard style. The style may change or be removed at any time. + @experimental + static const String STANDARD_EXPERIMENTAL = + "mapbox://styles/mapbox-map-design/standard-experimental-ime"; } /// A pre-specified location in the style where layer will be added to. diff --git a/lib/src/turf_adapters.dart b/lib/src/turf_adapters.dart index c3173e6b9..be76d48ba 100644 --- a/lib/src/turf_adapters.dart +++ b/lib/src/turf_adapters.dart @@ -9,8 +9,8 @@ final class Point extends turf.Point { } static Point decode(Object result) { - result as List; - return Point.fromJson((result.first as Map).cast()); + var map = (result is List) ? result.first : result; + return Point.fromJson((map as Map).cast()); } Object encode() { @@ -26,8 +26,8 @@ final class Polygon extends turf.Polygon { } static Polygon decode(Object result) { - result as List; - return Polygon.fromJson((result.first as Map).cast()); + var map = (result is List) ? result.first : result; + return Polygon.fromJson((map as Map).cast()); } factory Polygon.fromJson(Map json) { @@ -47,8 +47,8 @@ final class LineString extends turf.LineString { } static LineString decode(Object result) { - result as List; - return LineString.fromJson((result.first as Map).cast()); + var map = (result is List) ? result.first : result; + return LineString.fromJson((map as Map).cast()); } factory LineString.fromJson(Map json) { @@ -74,7 +74,12 @@ final class Feature extends turf.Feature { static Feature decode(Object result) { result as List; - return Feature.fromJson((result.first as Map).cast()); + return Feature.fromJson(jsonDecode(result.first as String)); + } + + factory Feature.fromFeature(Map feature) { + var valid = convertToValidMap(feature as Map); + return Feature.fromJson(valid); } factory Feature.fromJson(Map json) { @@ -87,3 +92,17 @@ final class Feature extends turf.Feature { fields: feature.fields); } } + +Map convertToValidMap(Map input) { + return input.map((key, value) { + if (key is! String) { + throw Exception( + "Invalid key type. Expected String but got ${key.runtimeType}"); + } + if (value is Map) { + // Recursively convert nested maps + return MapEntry(key, convertToValidMap(value)); + } + return MapEntry(key, value); + }); +}