From 7413f35fd63fec3fa1b9eff03e72ea3d28a70719 Mon Sep 17 00:00:00 2001 From: Gnative Date: Wed, 5 Jul 2023 19:43:57 +0200 Subject: [PATCH] feat: add setFeatureState / getFeatureState / removeFeatureState --- .../components/mapview/RCTMGLMapView.kt | 39 +++++ .../mapview/RCTMGLMapViewManager.kt | 24 +++ docs/MapView.md | 56 +++++++ docs/docs.json | 150 +++++++++++++++++ example/src/examples/V10/SetFeatureState.js | 154 ++++++++++++++++++ example/src/scenes/GroupAndItem.tsx | 2 + ios/RCTMGL-v10/RCTMGLMapViewManager.m | 23 +++ ios/RCTMGL-v10/RCTMGLMapViewManager.swift | 49 ++++++ src/components/MapView.tsx | 78 +++++++++ 9 files changed, 575 insertions(+) create mode 100644 example/src/examples/V10/SetFeatureState.js diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt index b5ef1bb4fc..38f459f6d1 100644 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt @@ -1088,6 +1088,45 @@ open class RCTMGLMapView(private val mContext: Context, var mManager: RCTMGLMapV } } + fun setFeatureState( + sourceId: String, + sourceLayerId: String?, + featureId: String, + state: Map + ) { + if (mMap == null) { + Logger.e("MapView", "setFeatureState, map is null") + return + } + mMap.setFeatureState(sourceId, sourceLayerId, featureId, state) + } + + fun getFeatureState( + callbackID: String?, + sourceId: String, + sourceLayerId: String?, + featureId: String + ) { + val result = mMap?.getFeatureState(sourceId, sourceLayerId, featureId) { result -> + sendResponse(callbackID) { response -> + if (result != null) { + response.putString("data", "test") + } else { + response.putNull("data") + } + } + } + } + + fun removeFeatureState( + sourceId: String, + sourceLayerId: String?, + featureId: String, + stateKey: String? + ) { + mMap?.removeFeatureState(sourceId, sourceLayerId, featureId, stateKey) + } + // endregion companion object { diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt index 7af3d01f35..180b89f145 100644 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt @@ -292,6 +292,30 @@ open class RCTMGLMapViewManager(context: ReactApplicationContext) : args!!.getString(3) ); } + "setFeatureState" -> { + mapView!!.setFeatureState( + args!!.getString(1), + args!!.getString(2), + args!!.getString(3), + args!!.getString(4), + ); + } + "getFeatureState" -> { + mapView!!.getFeatureState( + args!!.getString(0), + args!!.getString(1), + args!!.getString(2), + args!!.getString(3), + ); + } + "removeFeatureState" -> { + mapView!!.removeFeatureState( + args!!.getString(1), + args!!.getString(2), + args!!.getString(3), + args!!.getString(4) + ); + } "queryRenderedFeaturesAtPoint" -> { mapView.queryRenderedFeaturesAtPoint( args!!.getString(0), diff --git a/docs/MapView.md b/docs/MapView.md index f80602801b..f1dc0b72dc 100644 --- a/docs/MapView.md +++ b/docs/MapView.md @@ -666,6 +666,62 @@ await this._map.setSourceVisibility(false, 'composite', 'building') ``` +### setFeatureState(sourceId[, sourceLayerId], featureId, state) + +Update the state map of a feature within a style source. 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. + +#### arguments +| Name | Type | Required | Description | +| ---- | :--: | :------: | :----------: | +| `sourceId` | `string` | `Yes` | Identifier of the target source (e.g. 'composite') | +| `sourceLayerId` | `String` | `No` | Identifier of the target source-layer (e.g. 'building') | +| `featureId` | `string` | `Yes` | Identifier of the feature whose state should be updated. | +| `state` | `{[key:string]:any}` | `Yes` | undefined | + + + +```javascript +await this._map.setFeatureState('composite', 'building', 'id-01, { active: true }) +``` + + +### getFeatureState(sourceId[, sourceLayerId], featureId) + +Get the state map of a feature within a style source. + +#### arguments +| Name | Type | Required | Description | +| ---- | :--: | :------: | :----------: | +| `sourceId` | `string` | `Yes` | Identifier of the target source (e.g. 'composite') | +| `sourceLayerId` | `String` | `No` | Identifier of the target source-layer (e.g. 'building') | +| `featureId` | `string` | `Yes` | Identifier of the feature whose state should be retrieved. | + + + +```javascript +await this._map.getFeatureState('composite', 'building', 'id') +``` + + +### removeFeatureState(sourceId[, sourceLayerId], featureId[, stateKey]) + +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. + +#### arguments +| Name | Type | Required | Description | +| ---- | :--: | :------: | :----------: | +| `sourceId` | `string` | `Yes` | The style source identifier | +| `sourceLayerId` | `String` | `No` | Identifier of the target source-layer (e.g. 'building') | +| `featureId` | `string` | `Yes` | Identifier of the feature whose state should be updated. | +| `stateKey` | `String` | `No` | The key of the property to remove. If null, all feature’s state object properties are removed. Defaults to null. | + + + +```javascript +this._map.removeFeatureState('composite', 'building', 'id', 'active') +``` + + ### showAttribution() Show the attribution and telemetry action sheet.
If you implement a custom attribution button, you should add this action to the button. diff --git a/docs/docs.json b/docs/docs.json index 1bd4918acd..f8ccee734b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -3097,6 +3097,156 @@ "\nawait this._map.setSourceVisibility(false, 'composite', 'building')\n\n" ] }, + { + "name": "setFeatureState", + "docblock": "Update the state map of a feature within a style source. Update entries in the state map of a given feature within a style source.\nOnly 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.\n\n@example\nawait this._map.setFeatureState('composite', 'building', 'id-01, { active: true })\n\n@param {String} sourceId - Identifier of the target source (e.g. 'composite')\n@param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building')\n@param {String} featureId - Identifier of the feature whose state should be updated.\n@param", + "modifiers": [], + "params": [ + { + "name": "sourceId", + "description": "Identifier of the target source (e.g. 'composite')", + "type": { + "name": "string" + }, + "optional": false + }, + { + "name": "sourceLayerId", + "description": "Identifier of the target source-layer (e.g. 'building')", + "type": { + "name": "String" + }, + "optional": true + }, + { + "name": "featureId", + "description": "Identifier of the feature whose state should be updated.", + "type": { + "name": "string" + }, + "optional": false + }, + { + "name": "state", + "optional": false, + "type": { + "name": "{[key:string]:any}" + } + } + ], + "returns": null, + "description": "Update the state map of a feature within a style source. Update entries in the state map of a given feature within a style source.\nOnly 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.", + "examples": [ + "\nawait this._map.setFeatureState('composite', 'building', 'id-01, { active: true })\n\n" + ] + }, + { + "name": "getFeatureState", + "docblock": "Get the state map of a feature within a style source.\n\n@example\nawait this._map.getFeatureState('composite', 'building', 'id')\n\n@param {String} sourceId - Identifier of the target source (e.g. 'composite')\n@param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building')\n@param {String} featureId - Identifier of the feature whose state should be retrieved.\n@returns {Promise<{ [key: string]: any }>} Promise that resolves to the feature state object.", + "modifiers": [ + "async" + ], + "params": [ + { + "name": "sourceId", + "description": "Identifier of the target source (e.g. 'composite')", + "type": { + "name": "string" + }, + "optional": false + }, + { + "name": "sourceLayerId", + "description": "Identifier of the target source-layer (e.g. 'building')", + "type": { + "name": "String" + }, + "optional": true + }, + { + "name": "featureId", + "description": "Identifier of the feature whose state should be retrieved.", + "type": { + "name": "string" + }, + "optional": false + } + ], + "returns": { + "type": { + "name": "Promise", + "elements": [ + { + "name": "signature", + "type": "object", + "raw": "{ [key: string]: any }", + "signature": { + "properties": [ + { + "key": { + "name": "string" + }, + "value": { + "name": "any", + "required": true + } + } + ] + } + } + ], + "raw": "Promise<{ [key: string]: any }>" + } + }, + "description": "Get the state map of a feature within a style source.", + "examples": [ + "\nawait this._map.getFeatureState('composite', 'building', 'id')\n\n" + ] + }, + { + "name": "removeFeatureState", + "docblock": "Removes entries from a feature state object.\n\nRemove a specified property or all property from a feature’s state object, depending on the value of stateKey.\n\n@example\nthis._map.removeFeatureState('composite', 'building', 'id', 'active')\n\n@param {String} sourceId - The style source identifier\n@param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building')\n@param {String} featureId - Identifier of the feature whose state should be updated.\n@param {String} stateKey - The key of the property to remove. If null, all feature’s state object properties are removed. Defaults to null.", + "modifiers": [], + "params": [ + { + "name": "sourceId", + "description": "The style source identifier", + "type": { + "name": "string" + }, + "optional": false + }, + { + "name": "sourceLayerId", + "description": "Identifier of the target source-layer (e.g. 'building')", + "type": { + "name": "String" + }, + "optional": true + }, + { + "name": "featureId", + "description": "Identifier of the feature whose state should be updated.", + "type": { + "name": "string" + }, + "optional": false + }, + { + "name": "stateKey", + "description": "The key of the property to remove. If null, all feature’s state object properties are removed. Defaults to null.", + "type": { + "name": "String" + }, + "optional": true + } + ], + "returns": null, + "description": "Removes entries from a feature state object.\n\nRemove a specified property or all property from a feature’s state object, depending on the value of stateKey.", + "examples": [ + "\nthis._map.removeFeatureState('composite', 'building', 'id', 'active')\n\n" + ] + }, { "name": "showAttribution", "docblock": "Show the attribution and telemetry action sheet.\nIf you implement a custom attribution button, you should add this action to the button.", diff --git a/example/src/examples/V10/SetFeatureState.js b/example/src/examples/V10/SetFeatureState.js new file mode 100644 index 0000000000..2db17002d3 --- /dev/null +++ b/example/src/examples/V10/SetFeatureState.js @@ -0,0 +1,154 @@ +import React from 'react'; +import {Alert, View, Text} from 'react-native'; +import MapboxGL from '@rnmapbox/maps'; + +import BaseExamplePropTypes from '../common/BaseExamplePropTypes'; +import Page from '../common/Page'; +import Bubble from '../common/Bubble'; + +const GeoJSON = { + "type": "FeatureCollection", + "features": [ + { + "id": "square", + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [ + [ + [ + 8.307645244914198, + 48.49063431500133 + ], + [ + 8.307645244914198, + 45.57348217729648 + ], + [ + 12.538446560172616, + 45.57348217729648 + ], + [ + 12.538446560172616, + 48.49063431500133 + ], + [ + 8.307645244914198, + 48.49063431500133 + ] + ] + ], + "type": "Polygon" + } + } + ] +} + +const styles = { + mapView: { flex: 1 }, + bubbles:{ + position: 'absolute', + left:0, + right:0, + bottom: 50, + padding:10, + }, + bubble :{ + position: 'relative', + padding:0, + top:0, + left:0, + right:0, + bottom:0, + margin:0, + marginBottom: 10 + } +}; + +class SerFeatureState extends React.Component { + static propTypes = { + ...BaseExamplePropTypes, + }; + + state = { + active: null, + }; + + onPress = () => { + this.setState( + { + active: !this.state.active, + }, + () => { + this._map.setFeatureState('customShapeSource', 'customSourceLayer', 'square', { active: this.state.active } ); + }, + ); + }; + + onGetFeatureStatePress = async () => { + const featureState = await this._map.getFeatureState('customShapeSource', 'customSourceLayer', 'square', 'active') + console.log(featureState); + Alert.alert("Feature state", JSON.stringify(featureState)); + } + + onRemoveFeatureStatePress = () => { + this._map.removeFeatureState('customShapeSource', 'customSourceLayer', 'square') + this.setState({ + active: null + }) + } + + render() { + return ( + + (this._map = c)} + style={styles.mapView} + > + + { + console.log(`ShapeSource onPress: ${e.features}`, e.features); + }} + > + + + + + + Toggle Feature State + + + Remove Feature State + + + Get Feature State + + + + ); + } +} + +export default SerFeatureState; diff --git a/example/src/scenes/GroupAndItem.tsx b/example/src/scenes/GroupAndItem.tsx index 643405de12..98b0b002a3 100644 --- a/example/src/scenes/GroupAndItem.tsx +++ b/example/src/scenes/GroupAndItem.tsx @@ -86,6 +86,7 @@ import MapHandlers from '../examples/V10/MapHandlers'; import Markers from '../examples/V10/Markers'; import QueryTerrainElevation from '../examples/V10/QueryTerrainElevation'; import TerrainSkyAtmosphere from '../examples/V10/TerrainSkyAtmosphere'; +import SetFeatureState from '../examples/V10/SetFeatureState' const MostRecentExampleKey = '@recent_example'; @@ -265,6 +266,7 @@ const Examples = new ExampleGroup('React Native Mapbox', [ new ExampleItem('Query Terrain Elevation', QueryTerrainElevation), new ExampleItem('Camera Animation', CameraAnimation), new ExampleItem('Map Handlers', MapHandlers), + new ExampleItem('Set Feature State', SetFeatureState), ]), new ExampleGroup('Map', [ new ExampleItem('Show Map', ShowMap), diff --git a/ios/RCTMGL-v10/RCTMGLMapViewManager.m b/ios/RCTMGL-v10/RCTMGLMapViewManager.m index 48ed57ccb5..e43d05d012 100644 --- a/ios/RCTMGL-v10/RCTMGLMapViewManager.m +++ b/ios/RCTMGL-v10/RCTMGLMapViewManager.m @@ -51,6 +51,29 @@ @interface RCT_EXTERN_REMAP_MODULE(RCTMGLMapView, RCTMGLMapViewManager, RCTViewM resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(setFeatureState:(nonnull NSNumber *)reactTag + sourceId:(nonnull NSString*)sourceId + sourceLayerId:(nullable NSString*)sourceLayerId + featureId:(nonnull NSString*)featureId + state:(nonnull NSDictionary *)state + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getFeatureState:(nonnull NSNumber *)reactTag + sourceId:(nonnull NSString*)sourceId + sourceLayerId:(nullable NSString*)sourceLayerId + featureId:(nonnull NSString*)featureId + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(removeFeatureState:(nonnull NSNumber *)reactTag + sourceId:(nonnull NSString*)sourceId + sourceLayerId:(nullable NSString*)sourceLayerId + featureId:(nonnull NSString*)featureId + stateKey:(nullable NSString*)stateKey + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(getCenter:(nonnull NSNumber*)reactTag resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) diff --git a/ios/RCTMGL-v10/RCTMGLMapViewManager.swift b/ios/RCTMGL-v10/RCTMGLMapViewManager.swift index 9b65798116..05bd773ac8 100644 --- a/ios/RCTMGL-v10/RCTMGLMapViewManager.swift +++ b/ios/RCTMGL-v10/RCTMGLMapViewManager.swift @@ -101,6 +101,55 @@ extension RCTMGLMapViewManager { } } + @objc + func setFeatureState(_ reactTag: NSNumber, + sourceId: String, + sourceLayerId: String?, + featureId: String, + state: [String: Any], + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) -> Void { + withMapboxMap(reactTag, name:"setFeatureState", rejecter: rejecter) { mapboxMap in + mapboxMap.setFeatureState(sourceId: sourceId, sourceLayerId: sourceLayerId, featureId: featureId, state:state) + resolver(nil) + } + } + + @objc + func getFeatureState(_ reactTag: NSNumber, + sourceId: String, + sourceLayerId: String?, + featureId: String, + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) -> Void { + withMapboxMap(reactTag, name:"getFeatureState", rejecter: rejecter) { mapboxMap in + mapboxMap.getFeatureState(sourceId: sourceId, sourceLayerId: sourceLayerId, featureId: featureId) { result in + switch result { + case .success(let featureState): + // Handle success case, e.g., invoke resolver with the feature state + resolver(featureState) + case .failure(let error): + // Handle failure case, e.g., invoke rejecter with the error + rejecter("Error retrieving feature state", error.localizedDescription, error) + } + } + } + } + + @objc + func removeFeatureState(_ reactTag: NSNumber, + sourceId: String, + sourceLayerId: String?, + featureId: String, + stateKey: String?, + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) -> Void { + withMapboxMap(reactTag, name:"getFeatureState", rejecter: rejecter) { mapboxMap in + mapboxMap.removeFeatureState(sourceId: sourceId, sourceLayerId: sourceLayerId, featureId: featureId, stateKey:stateKey) + resolver(nil) + } + } + @objc func getCenter(_ reactTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx index cee1c091a1..767ab5689e 100644 --- a/src/components/MapView.tsx +++ b/src/components/MapView.tsx @@ -811,6 +811,84 @@ class MapView extends NativeBridgeComponent( ]); } + /** + * Update the state map of a feature within a style source. 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. + * + * @example + * await this._map.setFeatureState('composite', 'building', 'id-01, { active: true }) + * + * @param {String} sourceId - Identifier of the target source (e.g. 'composite') + * @param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building') + * @param {String} featureId - Identifier of the feature whose state should be updated. + * @param + */ + setFeatureState( + sourceId: string, + sourceLayerId: string | null = null, + featureId: string, + state: { [key: string]: any }, + ) { + this._runNative('setFeatureState', [ + sourceId, + sourceLayerId, + featureId, + state + ]); + } + + + /** + * Get the state map of a feature within a style source. + * + * @example + * await this._map.getFeatureState('composite', 'building', 'id') + * + * @param {String} sourceId - Identifier of the target source (e.g. 'composite') + * @param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building') + * @param {String} featureId - Identifier of the feature whose state should be retrieved. + * @returns {Promise<{ [key: string]: any }>} Promise that resolves to the feature state object. + */ + async getFeatureState( + sourceId: string, + sourceLayerId: string | null = null, + featureId: string, + ): Promise<{ [key: string]: any }> { + return await this._runNative<{ [key: string]: any }>('getFeatureState', [ + sourceId, + sourceLayerId, + featureId, + ]); + } + + /** + * 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. + * + * @example + * this._map.removeFeatureState('composite', 'building', 'id', 'active') + * + * @param {String} sourceId - The style source identifier + * @param {String=} sourceLayerId - Identifier of the target source-layer (e.g. 'building') + * @param {String} featureId - Identifier of the feature whose state should be updated. + * @param {String} stateKey - The key of the property to remove. If null, all feature’s state object properties are removed. Defaults to null. + */ + removeFeatureState( + sourceId: string, + sourceLayerId: string | null = null, + featureId: string, + stateKey: string | null = null, + ) { + this._runNative('removeFeatureState', [ + sourceId, + sourceLayerId, + featureId, + stateKey + ]); + } + + /** * Show the attribution and telemetry action sheet. * If you implement a custom attribution button, you should add this action to the button.