diff --git a/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/Convert.java b/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/Convert.java index c15fc69be..ed9430f37 100644 --- a/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/Convert.java +++ b/maplibre_gl/android/src/main/java/org/maplibre/maplibregl/Convert.java @@ -256,6 +256,12 @@ static void interpretMapLibreMapOptions(Object o, MapLibreMapOptionsSink sink, C if (myLocationTrackingMode != null) { sink.setMyLocationTrackingMode(toInt(myLocationTrackingMode)); } + final Object userLocationAnchor = data.get("userLocationAnchor"); + if (userLocationAnchor != null) { + final List userLocationAnchorData = toList(userLocationAnchor); + final Point point = toPoint(userLocationAnchorData, metrics.density); + sink.setUserLocationAnchor(point.x, point.y); + } final Object myLocationRenderMode = data.get("myLocationRenderMode"); if (myLocationRenderMode != null) { sink.setMyLocationRenderMode(toInt(myLocationRenderMode)); 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 c856ed80c..31769a60e 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 @@ -126,6 +126,7 @@ final class MapLibreMapController private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private int myLocationTrackingMode = 0; + private PointF userLocationAnchorOffset = null; private int myLocationRenderMode = 0; private boolean disposed = false; private boolean dragEnabled = true; @@ -690,6 +691,14 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { result.success(Convert.toJson(getCameraPosition())); break; } + case "map#setUserLocationAnchor": + { + float x = call.argument("x"); + float y = call.argument("y"); + setUserLocationAnchor(x, y); + result.success(null); + break; + } case "map#updateMyLocationTrackingMode": { int myLocationTrackingMode = call.argument("mode"); @@ -1936,6 +1945,13 @@ public void setMyLocationRenderMode(int myLocationRenderMode) { } } + @Override + public void setUserLocationAnchor(float x, float y) { + userLocationAnchorOffset = new PointF(x, y); + Log.d(TAG, "setUserLocationAnchor not supported on Android"); + + } + public void setLogoViewMargins(int x, int y) { mapLibreMap.getUiSettings().setLogoMargins(x, 0, 0, y); } 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 6bfcf1fc1..90b74706c 100644 --- a/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift +++ b/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapController.swift @@ -19,6 +19,7 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, private var initialTilt: CGFloat? private var cameraTargetBounds: MLNCoordinateBounds? + private var userLocationAnchorOffset: CGPoint? private var trackCameraPosition = false private var myLocationEnabled = false private var scrollingEnabled = true @@ -125,6 +126,13 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, return true } + func mapViewUserLocationAnchorPoint(_ mapView: MLNMapView) -> CGPoint { + if let offset = userLocationAnchorOffset { + return offset + } + return CGPoint(x: mapView.bounds.midX, y: mapView.bounds.midY) + } + func onMethodCall(methodCall: FlutterMethodCall, result: @escaping FlutterResult) { switch methodCall.method { case "map#waitForMap": @@ -167,6 +175,12 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, result(nil) } } + case "map#setUserLocationAnchor": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let x = arguments["x"] as? Double else { return } + guard let y = arguments["y"] as? Double else { return } + setUserLocationAnchor(x: x, y: y) + result(nil) case "map#updateMyLocationTrackingMode": guard let arguments = methodCall.arguments as? [String: Any] else { return } if let myLocationTrackingMode = arguments["mode"] as? UInt, @@ -1753,6 +1767,10 @@ class MapLibreMapController: NSObject, FlutterPlatformView, MLNMapViewDelegate, } } + func setUserLocationAnchor(x: Double, y: Double) { + userLocationAnchorOffset = CGPoint(x: x, y: y) + } + func setLogoViewMargins(x: Double, y: Double) { mapView.logoViewMargins = CGPoint(x: x, y: y) } diff --git a/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapOptionsSink.swift b/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapOptionsSink.swift index c0468c06d..659d299ca 100644 --- a/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapOptionsSink.swift +++ b/maplibre_gl/ios/maplibre_gl/Sources/maplibre_gl/MapLibreMapOptionsSink.swift @@ -13,6 +13,7 @@ protocol MapLibreMapOptionsSink { func setMyLocationEnabled(myLocationEnabled: Bool) func setMyLocationTrackingMode(myLocationTrackingMode: MLNUserTrackingMode) func setMyLocationRenderMode(myLocationRenderMode: MyLocationRenderMode) + func setUserLocationAnchor(anchorPoint: CGPoint) func setLogoViewMargins(x: Double, y: Double) func setCompassViewPosition(position: MLNOrnamentPosition) func setCompassViewMargins(x: Double, y: Double) diff --git a/maplibre_gl/lib/src/controller.dart b/maplibre_gl/lib/src/controller.dart index 192a8cff9..e539d76ee 100644 --- a/maplibre_gl/lib/src/controller.dart +++ b/maplibre_gl/lib/src/controller.dart @@ -260,6 +260,17 @@ class MapLibreMapController extends ChangeNotifier { notifyListeners(); } + /// Set the user location anchor point + /// + /// [anchor] is the position of the user location icon relative to + /// the map view. + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + Future setUserLocationAnchor(Point anchor) async { + await _maplibrePlatform.setUserLocationAnchor(anchor); + } + /// Triggers a resize event for the map on web (ignored on Android or iOS). /// /// Checks first if a resize is required or if it looks like it is already correctly resized. diff --git a/maplibre_gl_platform_interface/lib/src/maplibre_gl_platform_interface.dart b/maplibre_gl_platform_interface/lib/src/maplibre_gl_platform_interface.dart index d748807c1..b5f67320b 100644 --- a/maplibre_gl_platform_interface/lib/src/maplibre_gl_platform_interface.dart +++ b/maplibre_gl_platform_interface/lib/src/maplibre_gl_platform_interface.dart @@ -52,6 +52,7 @@ abstract class MapLibrePlatform { Future updateMapOptions(Map optionsUpdate); Future animateCamera(CameraUpdate cameraUpdate, {Duration? duration}); Future moveCamera(CameraUpdate cameraUpdate); + Future setUserLocationAnchor(Point anchor); Future updateMyLocationTrackingMode( MyLocationTrackingMode myLocationTrackingMode); 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 3272a2501..386460b4c 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 @@ -209,6 +209,15 @@ class MapLibreMethodChannel extends MapLibrePlatform { }); } + @override + Future setUserLocationAnchor(Point anchor) async { + await _channel + .invokeMethod('map#setUserLocationAnchor', { + 'x': anchor.x, + 'y': anchor.y, + }); + } + @override Future updateMyLocationTrackingMode( MyLocationTrackingMode myLocationTrackingMode) async { diff --git a/maplibre_gl_web/lib/src/convert.dart b/maplibre_gl_web/lib/src/convert.dart index 1f8e2245c..61a2aa8ac 100644 --- a/maplibre_gl_web/lib/src/convert.dart +++ b/maplibre_gl_web/lib/src/convert.dart @@ -62,6 +62,10 @@ class Convert { CompassViewPosition.values[options['compassViewPosition']]; sink.setCompassAlignment(position); } + if (options.containsKey('userLocationAnchor')) { + sink.setUserLocationAnchor( + options['userLocationAnchor'][0], options['userLocationAnchor'][1]); + } if (options.containsKey('compassViewMargins')) { sink.setCompassViewMargins( options['compassViewMargins'][0], options['compassViewMargins'][1]); diff --git a/maplibre_gl_web/lib/src/maplibre_web_gl_platform.dart b/maplibre_gl_web/lib/src/maplibre_web_gl_platform.dart index a6ad2b7ec..3c6757aa2 100644 --- a/maplibre_gl_web/lib/src/maplibre_web_gl_platform.dart +++ b/maplibre_gl_web/lib/src/maplibre_web_gl_platform.dart @@ -690,6 +690,12 @@ class MapLibreMapController extends MapLibrePlatform } } + @override + Future setUserLocationAnchor(Point anchor) async { + print('setUserLocationAnchor not available in web'); + return; + } + @override void setStyleString(String? styleString) { //remove old mouseenter callbacks to avoid multicalling diff --git a/maplibre_gl_web/lib/src/options_sink.dart b/maplibre_gl_web/lib/src/options_sink.dart index 10fdfadec..74e708707 100644 --- a/maplibre_gl_web/lib/src/options_sink.dart +++ b/maplibre_gl_web/lib/src/options_sink.dart @@ -27,6 +27,8 @@ abstract class MapLibreMapOptionsSink { void setMyLocationRenderMode(int myLocationRenderMode); + void setUserLocationAnchor(double x, double y); + void setLogoViewMargins(int x, int y); void setCompassAlignment(CompassViewPosition position);