From 564556a0d488edd86a5993ccfb5c56804c14da3d Mon Sep 17 00:00:00 2001 From: Nathan Hamblen Date: Sat, 29 Feb 2020 17:09:48 -0500 Subject: [PATCH 1/2] Listen to OnUserLocationUpdated to provide user location to app While the `myLocationEnabled` property is set to `true`, this method is called whenever a new location update is received by the map view. iOS only, needs Android. I did check that the location properties carried here are also provided in Android's [Location][1] object. [1]: https://developer.android.com/reference/android/location/Location --- ios/Classes/Extensions.swift | 13 +++++++++++ ios/Classes/MapboxMapController.swift | 8 +++++++ lib/src/controller.dart | 23 ++++++++++++++++++- lib/src/location.dart | 33 +++++++++++++++++++++++++++ lib/src/mapbox_map.dart | 6 +++++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ios/Classes/Extensions.swift b/ios/Classes/Extensions.swift index 1b5039d99..e2e203626 100644 --- a/ios/Classes/Extensions.swift +++ b/ios/Classes/Extensions.swift @@ -19,6 +19,19 @@ extension MGLMapCamera { } } +extension CLLocation { + func toDict() -> [String: Any]? { + return ["position": self.coordinate.toArray(), + "altitude": self.altitude, + "bearing": self.course, + "speed": self.speed, + "horizontalAccuracy": self.horizontalAccuracy, + "verticalAccuracy": self.verticalAccuracy, + "timestamp": Int(self.timestamp.timeIntervalSince1970 * 1000) + ] + } +} + extension CLLocationCoordinate2D { func toArray() -> [Double] { return [self.latitude, self.longitude] diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index f663cb5fc..479260b38 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -435,6 +435,14 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool { return true } + + func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) { + if let channel = channel, let userLocation = userLocation, let location = userLocation.location { + channel.invokeMethod("map#onUserLocationUpdated", arguments: [ + "userLocation": location.toDict() + ]); + } + } func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) { if let channel = channel { diff --git a/lib/src/controller.dart b/lib/src/controller.dart index ccca05c46..182e1a07c 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -8,6 +8,8 @@ typedef void OnMapClickCallback(Point point, LatLng coordinates); typedef void OnStyleLoadedCallback(); +typedef void OnUserLocationUpdated(UserLocation location); + typedef void OnCameraTrackingDismissedCallback(); typedef void OnCameraTrackingChangedCallback(MyLocationTrackingMode mode); @@ -35,7 +37,8 @@ class MapboxMapController extends ChangeNotifier { this.onMapClick, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, - this.onMapIdle}) + this.onMapIdle, + this.onUserLocationUpdated}) : assert(_id != null), assert(channel != null), _channel = channel { @@ -47,6 +50,7 @@ class MapboxMapController extends ChangeNotifier { int id, CameraPosition initialCameraPosition, {OnStyleLoadedCallback onStyleLoadedCallback, OnMapClickCallback onMapClick, + OnUserLocationUpdated onUserLocationUpdated, OnCameraTrackingDismissedCallback onCameraTrackingDismissed, OnCameraTrackingChangedCallback onCameraTrackingChanged, OnMapIdleCallback onMapIdle}) async { @@ -57,6 +61,7 @@ class MapboxMapController extends ChangeNotifier { return MapboxMapController._(id, channel, initialCameraPosition, onStyleLoadedCallback: onStyleLoadedCallback, onMapClick: onMapClick, + onUserLocationUpdated: onUserLocationUpdated, onCameraTrackingDismissed: onCameraTrackingDismissed, onCameraTrackingChanged: onCameraTrackingChanged, onMapIdle: onMapIdle); @@ -68,6 +73,8 @@ class MapboxMapController extends ChangeNotifier { final OnMapClickCallback onMapClick; + final OnUserLocationUpdated onUserLocationUpdated; + final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; final OnCameraTrackingChangedCallback onCameraTrackingChanged; @@ -177,6 +184,20 @@ class MapboxMapController extends ChangeNotifier { onCameraTrackingChanged(MyLocationTrackingMode.values[mode]); } break; + case 'map#onUserLocationUpdated': + final dynamic userLocation = call.arguments['userLocation']; + if (onUserLocationUpdated != null) { + onUserLocationUpdated(UserLocation( + position: LatLng(userLocation['position'][0], userLocation['position'][1]), + altitude: userLocation['altitude'], + bearing: userLocation['bearing'], + speed: userLocation['speed'], + horizontalAccuracy: userLocation['horizontalAccuracy'], + verticalAccuracy: userLocation['verticalAccuracy'], + timestamp: DateTime.fromMillisecondsSinceEpoch(userLocation['timestamp']) + )); + } + break; case 'map#onCameraTrackingDismissed': if (onCameraTrackingDismissed != null) { onCameraTrackingDismissed(); diff --git a/lib/src/location.dart b/lib/src/location.dart index 4f33b7b49..7fea9625c 100644 --- a/lib/src/location.dart +++ b/lib/src/location.dart @@ -103,3 +103,36 @@ class LatLngBounds { @override int get hashCode => hashValues(southwest, northeast); } + +/// User's observed location +class UserLocation { + /// User's position in latitude and longitude + final LatLng position; + + /// User's altitude in meters + final double altitude; + + /// Direction user is traveling, measured in degrees + final double bearing; + + /// User's speed in meters per second + final double speed; + + /// The radius of uncertainty for the location, measured in meters + final double horizontalAccuracy; + + /// Accuracy of the altitude measurement, in meters + final double verticalAccuracy; + + /// Time the user's location was observed + final DateTime timestamp; + + const UserLocation( + {@required this.position, + @required this.altitude, + @required this.bearing, + @required this.speed, + @required this.horizontalAccuracy, + @required this.verticalAccuracy, + @required this.timestamp}); +} diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 6cf587667..7f217487d 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -29,6 +29,7 @@ class MapboxMap extends StatefulWidget { this.compassViewMargins, this.attributionButtonMargins, this.onMapClick, + this.onUserLocationUpdated, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, this.onMapIdle, @@ -127,6 +128,10 @@ class MapboxMap extends StatefulWidget { final OnMapClickCallback onMapClick; + /// While the `myLocationEnabled` property is set to `true`, this method is + /// called whenever a new location update is received by the map view. + final OnUserLocationUpdated onUserLocationUpdated; + /// Called when the location tracking mode changes, such as when the user moves the map final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; final OnCameraTrackingChangedCallback onCameraTrackingChanged; @@ -206,6 +211,7 @@ class _MapboxMapState extends State { id, widget.initialCameraPosition, onStyleLoadedCallback: widget.onStyleLoadedCallback, onMapClick: widget.onMapClick, + onUserLocationUpdated: widget.onUserLocationUpdated, onCameraTrackingDismissed: widget.onCameraTrackingDismissed, onCameraTrackingChanged: widget.onCameraTrackingChanged, onMapIdle: widget.onMapIdle); From 9c95ee57060986d5650f88179e323689fdf2cb87 Mon Sep 17 00:00:00 2001 From: m0nac0 <58807793+m0nac0@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:13:04 +0200 Subject: [PATCH 2/2] add android, web; fix conflicts --- .../mapbox/mapboxgl/MapboxMapController.java | 56 ++++++++++++++++++- example/lib/map_ui.dart | 5 +- lib/src/controller.dart | 3 + .../lib/src/mapbox_gl_platform_interface.dart | 4 ++ .../lib/src/method_channel_mapbox_gl.dart | 14 +++++ .../lib/src/mapbox_map_controller.dart | 1 + 6 files changed, 81 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 5944b0a73..9e13b0d90 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -17,6 +17,7 @@ import android.graphics.PointF; import android.graphics.RectF; import android.location.Location; +import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import androidx.annotation.NonNull; @@ -125,6 +126,7 @@ final class MapboxMapController private final String styleStringInitial; private LocationComponent locationComponent = null; private LocationEngine locationEngine = null; + private LocationEngineCallback locationEngineCallback = null; private LocalizationPlugin localizationPlugin; private Style style; @@ -361,6 +363,24 @@ private void enableLocationComponent(@NonNull Style style) { } } + private void onUserLocationUpdate(Location location){ + if(location==null){ + return; + } + + final Map userLocation = new HashMap<>(6); + userLocation.put("position", new double[]{location.getLatitude(), location.getLongitude()}); + userLocation.put("altitude", location.getAltitude()); + userLocation.put("bearing", location.getBearing()); + userLocation.put("horizontalAccuracy", location.getAccuracy()); + userLocation.put("verticalAccuracy", (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? location.getVerticalAccuracyMeters() : null); + userLocation.put("timestamp", location.getTime()); + + final Map arguments = new HashMap<>(1); + arguments.put("userLocation", userLocation); + methodChannel.invokeMethod("map#onUserLocationUpdated", arguments); + } + private void enableSymbolManager(@NonNull Style style) { if (symbolManager == null) { symbolManager = new SymbolManager(mapView, mapboxMap, style); @@ -862,6 +882,7 @@ public void dispose() { if (circleManager != null) { circleManager.onDestroy(); } + stopListeningForLocationUpdates(); mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); @@ -889,6 +910,9 @@ public void onActivityResumed(Activity activity) { return; } mapView.onResume(); + if(myLocationEnabled){ + startListeningForLocationUpdates(); + } } @Override @@ -897,6 +921,7 @@ public void onActivityPaused(Activity activity) { return; } mapView.onPause(); + stopListeningForLocationUpdates(); } @Override @@ -1053,13 +1078,42 @@ public void setAttributionButtonMargins(int x, int y) { } private void updateMyLocationEnabled() { - if(this.locationComponent == null && myLocationEnabled == true){ + if(this.locationComponent == null && myLocationEnabled){ enableLocationComponent(mapboxMap.getStyle()); } + if(myLocationEnabled){ + startListeningForLocationUpdates(); + }else { + stopListeningForLocationUpdates(); + } + locationComponent.setLocationComponentEnabled(myLocationEnabled); } + private void startListeningForLocationUpdates(){ + if(locationEngineCallback == null && locationComponent!=null && locationComponent.getLocationEngine()!=null){ + locationEngineCallback = new LocationEngineCallback() { + @Override + public void onSuccess(LocationEngineResult result) { + onUserLocationUpdate(result.getLastLocation()); + } + + @Override + public void onFailure(@NonNull Exception exception) { + } + }; + locationComponent.getLocationEngine().requestLocationUpdates(locationComponent.getLocationEngineRequest(), locationEngineCallback , null); + } + } + + private void stopListeningForLocationUpdates(){ + if(locationEngineCallback != null && locationComponent!=null && locationComponent.getLocationEngine()!=null){ + locationComponent.getLocationEngine().removeLocationUpdates(locationEngineCallback); + locationEngineCallback = null; + } + } + private void updateMyLocationTrackingMode() { int[] mapboxTrackingModes = new int[] {CameraMode.NONE, CameraMode.TRACKING, CameraMode.TRACKING_COMPASS, CameraMode.TRACKING_GPS}; locationComponent.setCameraMode(mapboxTrackingModes[this.myLocationTrackingMode]); diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index 18ebff438..1ea203ea3 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -258,7 +258,10 @@ class MapUiBodyState extends State { this.setState(() { _myLocationTrackingMode = MyLocationTrackingMode.None; }); - } + }, + onUserLocationUpdated:(location){ + print("new location: ${location.position}, alt.: ${location.altitude}, bearing: ${location.bearing}, speed: ${location.speed}, horiz. accuracy: ${location.horizontalAccuracy}, vert. accuracy: ${location.verticalAccuracy}"); + }, ); final List columnChildren = [ diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 5cdb97ee6..0cf5f9fe2 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -135,6 +135,9 @@ class MapboxMapController extends ChangeNotifier { onMapIdle(); } }); + MapboxGlPlatform.getInstance(_id).onUserLocationUpdatedPlatform.add((location) { + onUserLocationUpdated?.call(location); + }); } static Future init( 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 b55d11870..7677574df 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 @@ -60,6 +60,8 @@ abstract class MapboxGlPlatform { ArgumentCallbacks(); final ArgumentCallbacks onMapIdlePlatform = ArgumentCallbacks(); + + final ArgumentCallbacks onUserLocationUpdatedPlatform = ArgumentCallbacks(); Future initPlatform(int id) async { throw UnimplementedError('initPlatform() has not been implemented.'); @@ -210,4 +212,6 @@ abstract class MapboxGlPlatform { throw UnimplementedError( 'setSymbolTextIgnorePlacement() has not been implemented.'); } + + } 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 71698c199..2c13bb100 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 @@ -70,6 +70,20 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { case 'map#onIdle': onMapIdlePlatform(null); break; + case 'map#onUserLocationUpdated': + final dynamic userLocation = call.arguments['userLocation']; + if (onUserLocationUpdatedPlatform != null) { + onUserLocationUpdatedPlatform(UserLocation( + position: LatLng(userLocation['position'][0], userLocation['position'][1]), + altitude: userLocation['altitude'], + bearing: userLocation['bearing'], + speed: userLocation['speed'], + horizontalAccuracy: userLocation['horizontalAccuracy'], + verticalAccuracy: userLocation['verticalAccuracy'], + timestamp: DateTime.fromMillisecondsSinceEpoch(userLocation['timestamp']) + )); + } + break; default: throw MissingPluginException(); } diff --git a/mapbox_gl_web/lib/src/mapbox_map_controller.dart b/mapbox_gl_web/lib/src/mapbox_map_controller.dart index ed436ea6b..384ad8a3a 100644 --- a/mapbox_gl_web/lib/src/mapbox_map_controller.dart +++ b/mapbox_gl_web/lib/src/mapbox_map_controller.dart @@ -400,6 +400,7 @@ class MapboxMapController extends MapboxGlPlatform ); _geolocateControl.on('geolocate', (e) { _myLastLocation = LatLng(e.coords.latitude, e.coords.longitude); + onUserLocationUpdatedPlatform(UserLocation(position: LatLng(e.coords.latitude, e.coords.longitude), altitude: e.coords.altitude, bearing: e.coords.heading, speed: e.coords.speed, horizontalAccuracy: e.coords.accuracy, verticalAccuracy: e.coords.altitudeAccuracy, timestamp: DateTime.fromMillisecondsSinceEpoch(e.timestamp))); }); _geolocateControl.on('trackuserlocationstart', (_) { _onCameraTrackingChanged(true);