Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Returns layer Id on Feature Tap #475

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
5 changes: 3 additions & 2 deletions example/lib/layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ class LayerState extends State {
controller.onFeatureTapped.add(onFeatureTap);
}

void onFeatureTap(dynamic featureId, Point<double> point, LatLng latLng) {
void onFeatureTap(
dynamic featureId, Point<double> point, LatLng latLng, String layerId) {
final snackBar = SnackBar(
content: Text(
'Tapped feature with id $featureId',
'Tapped feature with id $featureId on payer $layerId',
LouisRaverdy marked this conversation as resolved.
Show resolved Hide resolved
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
backgroundColor: Theme.of(context).primaryColor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -652,7 +653,7 @@ private void addHeatmapLayer(
}
}

private Feature firstFeatureOnLayers(RectF in) {
private Pair<Feature, String> firstFeatureOnLayers(RectF in) {
if (style != null) {
final List<Layer> layers = style.getLayers();
final List<String> layersInOrder = new ArrayList<String>();
Expand All @@ -665,7 +666,7 @@ private Feature firstFeatureOnLayers(RectF in) {
for (String id : layersInOrder) {
List<Feature> features = mapLibreMap.queryRenderedFeatures(in, id);
if (!features.isEmpty()) {
return features.get(0);
return new Pair<Feature, String>(features.get(0), id);
}
}
}
Expand Down Expand Up @@ -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<Feature, String> featureLayerPair = firstFeatureOnLayers(rectF);
final Map<String, Object> 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);
Expand Down Expand Up @@ -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<Feature, String> featureLayerPair = firstFeatureOnLayers(rectF);
if (featureLayerPair != null && featureLayerPair.first != null && startDragging(featureLayerPair.first, origin)) {
invokeFeatureDrag(pointf, "start");
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you comment it out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, this was to enhanced the speed of the callback, with this commented the interaction callback switch from 1s to 0.1s. I was unable to remove all the slow gestureRecognizers

singleTap.require(toFail: recognizer)
}
}*/
mapView.addGestureRecognizer(singleTap)

let longPress = UILongPressGestureRecognizer(
Expand All @@ -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),
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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<String>()
if let layerId = arguments["sourceLayerId"] as? String {
sourceLayerId.insert(layerId)
Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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) ?? ""
Expand All @@ -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)
}
Expand Down Expand Up @@ -967,16 +967,16 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate,
private func getCamera() -> MLNMapCamera? {
return trackCameraPosition ? mapView.camera : nil
}

private func setMapLanguage(language: String) {
self.mapView.setMapLanguage(language)
}

/*
* 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
Expand All @@ -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)
}

/*
Expand All @@ -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: [
Expand Down Expand Up @@ -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
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion maplibre_gl/lib/src/annotation_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ abstract class AnnotationManager<T extends Annotation> {
}
}

_onFeatureTapped(dynamic id, Point<double> point, LatLng coordinates) {
_onFeatureTapped(
dynamic id, Point<double> point, LatLng coordinates, String layerId) {
final annotation = _idToAnnotation[id];
if (annotation != null) {
onTap!(annotation);
Expand Down
5 changes: 3 additions & 2 deletions maplibre_gl/lib/src/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ typedef OnMapClickCallback = void Function(
Point<double> point, LatLng coordinates);

typedef OnFeatureInteractionCallback = void Function(
dynamic id, Point<double> point, LatLng coordinates);
dynamic id, Point<double> point, LatLng coordinates, String layerId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My issue with this is that it changes the callback function signature. Fine since the library is not stable yet. But maybe it's type to make a InteractionEvent class to package all the attributes inside?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not, but I don't think it's necessary, given that functionality is vital.


typedef OnFeatureDragnCallback = void Function(dynamic id,
{required Point<double> point,
Expand Down Expand Up @@ -93,7 +93,8 @@ class MapLibreMapController extends ChangeNotifier {
_maplibrePlatform.onFeatureTappedPlatform.add((payload) {
for (final fun
in List<OnFeatureInteractionCallback>.from(onFeatureTapped)) {
fun(payload["id"], payload["point"], payload["latLng"]);
fun(payload["id"], payload["point"], payload["latLng"],
payload["layerId"]);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>(x, y),
'latLng': LatLng(lat, lng)
'latLng': LatLng(lat, lng),
'layerId': layerId
});
case 'feature#onDrag':
final id = call.arguments['id'];
Expand Down