From 864c544a3c79af88c4cb80056a0448e586047ccf Mon Sep 17 00:00:00 2001 From: Gergely Morva Date: Tue, 12 Apr 2022 10:06:27 +0200 Subject: [PATCH] Implement layer filtering --- .../mapbox/mapboxgl/MapboxMapController.java | 43 +++++++++++++++++++ example/lib/layer.dart | 25 +++++++---- ios/Classes/MapboxMapController.swift | 34 +++++++++++++++ lib/src/controller.dart | 4 ++ .../lib/src/mapbox_gl_platform_interface.dart | 2 + .../lib/src/method_channel_mapbox_gl.dart | 10 +++++ .../lib/src/mapbox_web_gl_platform.dart | 5 +++ 7 files changed, 115 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index fa1b851a6..a022c9739 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -27,6 +27,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.mapbox.android.core.location.LocationEngine; import com.mapbox.android.core.location.LocationEngineCallback; import com.mapbox.android.core.location.LocationEngineProvider; @@ -59,7 +60,9 @@ import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin; import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.CircleLayer; +import com.mapbox.mapboxsdk.style.layers.FillExtrusionLayer; import com.mapbox.mapboxsdk.style.layers.FillLayer; +import com.mapbox.mapboxsdk.style.layers.HeatmapLayer; import com.mapbox.mapboxsdk.style.layers.HillshadeLayer; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.LineLayer; @@ -1107,6 +1110,46 @@ public void onFailure(@NonNull Exception exception) { style.removeLayer(layerId); interactiveFeatureLayerIds.remove(layerId); + result.success(null); + break; + } + case "style#setFilter": + { + if (style == null) { + result.error( + "STYLE IS NULL", + "The style is null. Has onStyleLoaded() already been invoked?", + null); + } + String layerId = call.argument("layerId"); + String filter = call.argument("filter"); + + Layer layer = style.getLayer(layerId); + + JsonParser parser = new JsonParser(); + JsonElement jsonElement = parser.parse(filter); + Expression expression = Expression.Converter.convert(jsonElement); + + if (layer instanceof CircleLayer) { + ((CircleLayer) layer).setFilter(expression); + } else if (layer instanceof FillExtrusionLayer) { + ((FillExtrusionLayer) layer).setFilter(expression); + } else if (layer instanceof FillLayer) { + ((FillLayer) layer).setFilter(expression); + } else if (layer instanceof HeatmapLayer) { + ((HeatmapLayer) layer).setFilter(expression); + } else if (layer instanceof LineLayer) { + ((LineLayer) layer).setFilter(expression); + } else if (layer instanceof SymbolLayer) { + ((SymbolLayer) layer).setFilter(expression); + } else { + result.error( + "INVALID LAYER TYPE", + String.format("Layer '%s' does not support filtering.", layerId), + null); + break; + } + result.success(null); break; } diff --git a/example/lib/layer.dart b/example/lib/layer.dart index b6afc3cbb..2734720b9 100644 --- a/example/lib/layer.dart +++ b/example/lib/layer.dart @@ -22,7 +22,9 @@ class LayerState extends State { static final LatLng center = const LatLng(-33.86711, 151.1947171); late MapboxMapController controller; - Timer? timer; + Timer? bikeTimer; + Timer? filterTimer; + int filteredId = 0; @override Widget build(BuildContext context) { @@ -136,15 +138,22 @@ class LayerState extends State { ), minzoom: 11, ); - timer = Timer.periodic( - Duration(milliseconds: 10), - (t) => controller.setGeoJsonSource( - "moving", _movingFeature(t.tick / 2000))); + + bikeTimer = Timer.periodic(Duration(milliseconds: 10), (t) { + controller.setGeoJsonSource("moving", _movingFeature(t.tick / 2000)); + }); + + controller.setFilter('fills', ['==', 'id', filteredId]); + filterTimer = Timer.periodic(Duration(seconds: 3), (t) { + filteredId = filteredId == 0 ? 1 : 0; + controller.setFilter('fills', ['==', 'id', filteredId]); + }); } @override void dispose() { - timer?.cancel(); + bikeTimer?.cancel(); + filterTimer?.cancel(); super.dispose(); } } @@ -186,7 +195,7 @@ final _fills = { { "type": "Feature", "id": 0, // web currently only supports number ids - "properties": {}, + "properties": {'id': 0}, "geometry": { "type": "Polygon", "coordinates": [ @@ -211,7 +220,7 @@ final _fills = { { "type": "Feature", "id": 1, - "properties": {}, + "properties": {'id': 1}, "geometry": { "type": "Polygon", "coordinates": [ diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 1ac3fa4a5..0157bf7e0 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -720,6 +720,40 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma mapView.style?.removeLayer(layer) result(nil) + case "style#setFilter": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let layerId = arguments["layerId"] as? String else { return } + guard let filter = arguments["filter"] as? String else { return } + guard let layer = mapView.style?.layer(withIdentifier: layerId) else { + result(nil) + return + } + + do { + let filter = try JSONSerialization.jsonObject( + with: filter.data(using: .utf8)!, + options: .fragmentsAllowed + ) + let predicate = NSPredicate(mglJSONObject: filter) + if let layer = layer as? MGLVectorStyleLayer { + layer.predicate = predicate + } else { + result(FlutterError( + code: "invalidLayerType", + message: "Invalid layer type", + details: "Layer '\(layerId)' does not support filtering." + )) + return + } + result(nil) + } catch { + result(FlutterError( + code: "invalidExpression", + message: "Invalid filter expression", + details: "Could not parse filter expression." + )) + } + case "source#addGeoJson": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } diff --git a/lib/src/controller.dart b/lib/src/controller.dart index b11505ddc..5dd9816e1 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -1056,6 +1056,10 @@ class MapboxMapController extends ChangeNotifier { return _mapboxGlPlatform.removeLayer(layerId); } + Future setFilter(String layerId, dynamic filter) { + return _mapboxGlPlatform.setFilter(layerId, filter); + } + /// Returns the point on the screen that corresponds to a geographical coordinate ([latLng]). The screen location is in screen pixels (not display pixels) relative to the top left of the map (not of the whole screen) /// /// Note: The resulting x and y coordinates are rounded to [int] on web, on other platforms they may differ very slightly (in the range of about 10^-10) from the actual nearest screen coordinate. diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index 48b319af4..9fc41911e 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -86,6 +86,8 @@ abstract class MapboxGlPlatform { Future removeLayer(String imageLayerId); + Future setFilter(String layerId, dynamic filter); + Future toScreenLocation(LatLng latLng); Future> toScreenLocationBatch(Iterable latLngs); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index dc3e01c32..b003e166f 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -472,6 +472,16 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { } } + @override + Future setFilter(String layerId, dynamic filter) async { + try { + return await _channel.invokeMethod('style#setFilter', + {'layerId': layerId, 'filter': jsonEncode(filter)}); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + @override Future toLatLng(Point screenLocation) async { try { diff --git a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart index c08136cba..d64967c55 100644 --- a/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart +++ b/mapbox_gl_web/lib/src/mapbox_web_gl_platform.dart @@ -629,6 +629,11 @@ class MapboxWebGlPlatform extends MapboxGlPlatform _map.removeLayer(layerId); } + @override + Future setFilter(String layerId, dynamic filter) async { + _map.setFilter(layerId, filter); + } + @override Future addGeoJsonSource(String sourceId, Map geojson, {String? promoteId}) async {