diff --git a/example/lib/layer.dart b/example/lib/layer.dart index 7a03fb47..d197d249 100644 --- a/example/lib/layer.dart +++ b/example/lib/layer.dart @@ -145,10 +145,11 @@ class LayerState extends State { controller.onFeatureTapped.add(onFeatureTap); } - void onFeatureTap(dynamic featureId, Point point, LatLng latLng) { + void onFeatureTap( + dynamic featureId, Point point, LatLng latLng, String layerId) { final snackBar = SnackBar( content: Text( - 'Tapped feature with id $featureId', + 'Tapped feature with id $featureId on payer $layerId', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), backgroundColor: Theme.of(context).primaryColor, diff --git a/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapController.java b/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapController.java index 724c39c1..18819522 100644 --- a/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapController.java +++ b/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/MapLibreMapController.java @@ -22,6 +22,7 @@ import android.view.TextureView; import android.view.View; import android.widget.FrameLayout; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; @@ -652,7 +653,7 @@ private void addHeatmapLayer( } } - private Feature firstFeatureOnLayers(RectF in) { + private Pair firstFeatureOnLayers(RectF in) { if (style != null) { final List layers = style.getLayers(); final List layersInOrder = new ArrayList(); @@ -665,7 +666,7 @@ private Feature firstFeatureOnLayers(RectF in) { for (String id : layersInOrder) { List features = mapLibreMap.queryRenderedFeatures(in, id); if (!features.isEmpty()) { - return features.get(0); + return new Pair(features.get(0), id); } } } @@ -1659,14 +1660,15 @@ public void onDidBecomeIdle() { public boolean onMapClick(@NonNull LatLng point) { PointF pointf = mapLibreMap.getProjection().toScreenLocation(point); RectF rectF = new RectF(pointf.x - 10, pointf.y - 10, pointf.x + 10, pointf.y + 10); - Feature feature = firstFeatureOnLayers(rectF); + Pair featureLayerPair = firstFeatureOnLayers(rectF); final Map arguments = new HashMap<>(); arguments.put("x", pointf.x); arguments.put("y", pointf.y); arguments.put("lng", point.getLongitude()); arguments.put("lat", point.getLatitude()); - if (feature != null) { - arguments.put("id", feature.id()); + if (featureLayerPair != null && featureLayerPair.first != null) { + arguments.put("layerId", featureLayerPair.second); + arguments.put("id", featureLayerPair.first.id()); methodChannel.invokeMethod("feature#onTap", arguments); } else { methodChannel.invokeMethod("map#onMapClick", arguments); @@ -2137,8 +2139,8 @@ boolean onMoveBegin(MoveGestureDetector detector) { PointF pointf = detector.getFocalPoint(); LatLng origin = mapLibreMap.getProjection().fromScreenLocation(pointf); RectF rectF = new RectF(pointf.x - 10, pointf.y - 10, pointf.x + 10, pointf.y + 10); - Feature feature = firstFeatureOnLayers(rectF); - if (feature != null && startDragging(feature, origin)) { + Pair featureLayerPair = firstFeatureOnLayers(rectF); + if (featureLayerPair != null && featureLayerPair.first != null && startDragging(featureLayerPair.first, origin)) { invokeFeatureDrag(pointf, "start"); return true; } diff --git a/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift b/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift index f525566e..344b3fde 100644 --- a/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift +++ b/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift @@ -56,9 +56,9 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, target: self, action: #selector(handleMapTap(sender:)) ) - for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer { + /*for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer { singleTap.require(toFail: recognizer) - } + }*/ mapView.addGestureRecognizer(singleTap) let longPress = UILongPressGestureRecognizer( @@ -71,9 +71,9 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, longPress.require(toFail: recognizer) } var longPressRecognizerAdded = false - + if let args = args as? [String: Any] { - + Convert.interpretMapLibreMapOptions(options: args["options"], delegate: self) if let initialCameraPosition = args["initialCameraPosition"] as? [String: Any], let camera = MLNMapCamera.fromDict(initialCameraPosition, mapView: mapView), @@ -170,7 +170,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, if let langStr = Locale.current.languageCode { setMapLanguage(language: langStr) } - + result(nil) case "map#updateContentInsets": guard let arguments = methodCall.arguments as? [String: Any] else { return } @@ -316,7 +316,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, case "camera#move": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let cameraUpdate = arguments["cameraUpdate"] as? [Any] else { return } - + if let camera = Convert.parseCameraUpdate(cameraUpdate: cameraUpdate, mapView: mapView) { mapView.setCamera(camera, animated: false) } @@ -325,12 +325,12 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let cameraUpdate = arguments["cameraUpdate"] as? [Any] else { return } guard let camera = Convert.parseCameraUpdate(cameraUpdate: cameraUpdate, mapView: mapView) else { return } - - + + let completion = { result(nil) } - + if let duration = arguments["duration"] as? TimeInterval { if let padding = Convert.parseLatLngBoundsPadding(cameraUpdate) { mapView.fly(to: camera, edgePadding: padding, withDuration: duration / 1000, completionHandler: completion) @@ -537,7 +537,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, properties: properties ) result(nil) - + case "heatmapLayer#add": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } @@ -840,11 +840,11 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, } layer.isVisible = visible result(nil) - + case "map#querySourceFeatures": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let sourceId = arguments["sourceId"] as? String else { return } - + var sourceLayerId = Set() if let layerId = arguments["sourceLayerId"] as? String { sourceLayerId.insert(layerId) @@ -853,10 +853,10 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, if let filter = arguments["filter"] as? [Any] { filterExpression = NSPredicate(mglJSONObject: filter) } - + var reply = [String: NSObject]() var features: [MLNFeature] = [] - + guard let style = mapView.style else { return } if let source = style.source(withIdentifier: sourceId) { if let vectorSource = source as? MLNVectorTileSource { @@ -865,7 +865,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, features = shapeSource.features(matching: filterExpression) } } - + var featuresJson = [String]() for feature in features { let dictionary = feature.geoJSONDictionary() @@ -883,11 +883,11 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, case "style#getLayerIds": var layerIds = [String]() - + guard let style = mapView.style else { return } - + style.layers.forEach { layer in layerIds.append(layer.identifier) } - + var reply = [String: NSObject]() reply["layers"] = layerIds as NSObject result(reply) @@ -902,18 +902,18 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, var reply = [String: NSObject]() reply["sources"] = sourceIds as NSObject result(reply) - + case "style#getFilter": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let layerId = arguments["layerId"] as? String else { return } - + guard let style = mapView.style else { return } guard let layer = style.layer(withIdentifier: layerId) else { return } - + var currentLayerFilter : String = "" if let vectorLayer = layer as? MLNVectorStyleLayer { if let layerFilter = vectorLayer.predicate { - + let jsonExpression = layerFilter.mgl_jsonExpressionObject if let data = try? JSONSerialization.data(withJSONObject: jsonExpression, options: []) { currentLayerFilter = String(data: data, encoding: String.Encoding.utf8) ?? "" @@ -925,11 +925,11 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, ).flutterError) return; } - + var reply = [String: NSObject]() reply["filter"] = currentLayerFilter as NSObject result(reply) - + default: result(FlutterMethodNotImplemented) } @@ -967,7 +967,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, private func getCamera() -> MLNMapCamera? { return trackCameraPosition ? mapView.camera : nil } - + private func setMapLanguage(language: String) { self.mapView.setMapLanguage(language) } @@ -975,8 +975,8 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, /* * Scan layers from top to bottom and return the first matching feature */ - private func firstFeatureOnLayers(at: CGPoint) -> MLNFeature? { - guard let style = mapView.style else { return nil } + private func firstFeatureOnLayers(at: CGPoint) -> (feature: MLNFeature?, layerId: String?) { + guard let style = mapView.style else { return (nil, nil) } // get layers in order (interactiveFeatureLayerIds is unordered) let clickableLayers = style.layers.filter { layer in @@ -989,10 +989,10 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, styleLayerIdentifiers: [layer.identifier] ) if let feature = features.first { - return feature + return (feature, layer.identifier) } } - return nil + return (nil, nil) } /* @@ -1004,13 +1004,15 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, let point = sender.location(in: mapView) let coordinate = mapView.convert(point, toCoordinateFrom: mapView) - if let feature = firstFeatureOnLayers(at: point) { + let result = firstFeatureOnLayers(at: point) + if let feature = result.feature { channel?.invokeMethod("feature#onTap", arguments: [ "id": feature.identifier, "x": point.x, "y": point.y, "lng": coordinate.longitude, "lat": coordinate.latitude, + "layerId": result.layerId, ]) } else { channel?.invokeMethod("map#onMapClick", arguments: [ @@ -1053,22 +1055,23 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, let point = sender.location(in: mapView) let coordinate = mapView.convert(point, toCoordinateFrom: mapView) - if dragFeature == nil, began, sender.numberOfTouches == 1, - let feature = firstFeatureOnLayers(at: point), - let draggable = feature.attribute(forKey: "draggable") as? Bool, - draggable - { - sender.state = UIGestureRecognizer.State.began - dragFeature = feature - originDragCoordinate = coordinate - previousDragCoordinate = coordinate - mapView.allowsScrolling = false - let eventType = "start" - invokeFeatureDrag(point, coordinate, eventType) - for gestureRecognizer in mapView.gestureRecognizers! { - if let _ = gestureRecognizer as? UIPanGestureRecognizer { - gestureRecognizer.addTarget(self, action: #selector(handleMapPan)) - break + if dragFeature == nil, began, sender.numberOfTouches == 1 { + let result = firstFeatureOnLayers(at: point) + if let feature = result.feature, + let draggable = feature.attribute(forKey: "draggable") as? Bool, + draggable { + sender.state = UIGestureRecognizer.State.began + dragFeature = feature + originDragCoordinate = coordinate + previousDragCoordinate = coordinate + mapView.allowsScrolling = false + let eventType = "start" + invokeFeatureDrag(point, coordinate, eventType) + for gestureRecognizer in mapView.gestureRecognizers! { + if let _ = gestureRecognizer as? UIPanGestureRecognizer { + gestureRecognizer.addTarget(self, action: #selector(handleMapPan)) + break + } } } } diff --git a/maplibre_gl/lib/src/annotation_manager.dart b/maplibre_gl/lib/src/annotation_manager.dart index 4c8ac4d9..f4c980e4 100644 --- a/maplibre_gl/lib/src/annotation_manager.dart +++ b/maplibre_gl/lib/src/annotation_manager.dart @@ -55,7 +55,8 @@ abstract class AnnotationManager { } } - _onFeatureTapped(dynamic id, Point point, LatLng coordinates) { + _onFeatureTapped( + dynamic id, Point point, LatLng coordinates, String layerId) { final annotation = _idToAnnotation[id]; if (annotation != null) { onTap!(annotation); diff --git a/maplibre_gl/lib/src/controller.dart b/maplibre_gl/lib/src/controller.dart index 71a5e827..7d247fab 100644 --- a/maplibre_gl/lib/src/controller.dart +++ b/maplibre_gl/lib/src/controller.dart @@ -8,7 +8,7 @@ typedef OnMapClickCallback = void Function( Point point, LatLng coordinates); typedef OnFeatureInteractionCallback = void Function( - dynamic id, Point point, LatLng coordinates); + dynamic id, Point point, LatLng coordinates, String layerId); typedef OnFeatureDragnCallback = void Function(dynamic id, {required Point point, @@ -93,7 +93,8 @@ class MapLibreMapController extends ChangeNotifier { _maplibrePlatform.onFeatureTappedPlatform.add((payload) { for (final fun in List.from(onFeatureTapped)) { - fun(payload["id"], payload["point"], payload["latLng"]); + fun(payload["id"], payload["point"], payload["latLng"], + payload["layerId"]); } }); diff --git a/maplibre_gl_platform_interface/lib/src/method_channel_maplibre_gl.dart b/maplibre_gl_platform_interface/lib/src/method_channel_maplibre_gl.dart index d2b4cccc..40ca381f 100644 --- a/maplibre_gl_platform_interface/lib/src/method_channel_maplibre_gl.dart +++ b/maplibre_gl_platform_interface/lib/src/method_channel_maplibre_gl.dart @@ -18,10 +18,12 @@ class MapLibreMethodChannel extends MapLibrePlatform { final double y = call.arguments['y']; final double lng = call.arguments['lng']; final double lat = call.arguments['lat']; + final String layerId = call.arguments['layerId']; onFeatureTappedPlatform({ 'id': id, 'point': Point(x, y), - 'latLng': LatLng(lat, lng) + 'latLng': LatLng(lat, lng), + 'layerId': layerId }); case 'feature#onDrag': final id = call.arguments['id'];