diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index ef6e801f4..d881ab342 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; @@ -134,6 +135,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; @@ -391,6 +393,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); @@ -964,7 +984,7 @@ public void dispose() { if (fillManager != null) { fillManager.onDestroy(); } - + stopListeningForLocationUpdates(); mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); } @@ -991,6 +1011,9 @@ public void onActivityResumed(Activity activity) { return; } mapView.onResume(); + if(myLocationEnabled){ + startListeningForLocationUpdates(); + } } @Override @@ -999,6 +1022,7 @@ public void onActivityPaused(Activity activity) { return; } mapView.onPause(); + stopListeningForLocationUpdates(); } @Override @@ -1155,13 +1179,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 25f98f1d4..b27ceb944 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -319,7 +319,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/ios/Classes/Extensions.swift b/ios/Classes/Extensions.swift index 800e32dcb..461b0ffe2 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 170921e7b..2ffce4b4a 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -657,6 +657,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 d5d34db94..28fdc9528 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -9,6 +9,8 @@ typedef void OnMapLongClickCallback(Point point, LatLng coordinates); typedef void OnStyleLoadedCallback(); +typedef void OnUserLocationUpdated(UserLocation location); + typedef void OnCameraTrackingDismissedCallback(); typedef void OnCameraTrackingChangedCallback(MyLocationTrackingMode mode); @@ -38,8 +40,9 @@ class MapboxMapController extends ChangeNotifier { this.onMapLongClick, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, - this.onCameraIdle, - this.onMapIdle}) + this.onMapIdle, + this.onUserLocationUpdated, + this.onCameraIdle}) : assert(_id != null) { _cameraPosition = initialCameraPosition; @@ -139,12 +142,16 @@ class MapboxMapController extends ChangeNotifier { onMapIdle(); } }); + MapboxGlPlatform.getInstance(_id).onUserLocationUpdatedPlatform.add((location) { + onUserLocationUpdated?.call(location); + }); } static Future init( int id, CameraPosition initialCameraPosition, {OnStyleLoadedCallback onStyleLoadedCallback, OnMapClickCallback onMapClick, + OnUserLocationUpdated onUserLocationUpdated, OnMapLongClickCallback onMapLongClick, OnCameraTrackingDismissedCallback onCameraTrackingDismissed, OnCameraTrackingChangedCallback onCameraTrackingChanged, @@ -155,6 +162,7 @@ class MapboxMapController extends ChangeNotifier { return MapboxMapController._(id, initialCameraPosition, onStyleLoadedCallback: onStyleLoadedCallback, onMapClick: onMapClick, + onUserLocationUpdated: onUserLocationUpdated, onMapLongClick: onMapLongClick, onCameraTrackingDismissed: onCameraTrackingDismissed, onCameraTrackingChanged: onCameraTrackingChanged, @@ -167,6 +175,8 @@ class MapboxMapController extends ChangeNotifier { final OnMapClickCallback onMapClick; final OnMapLongClickCallback onMapLongClick; + final OnUserLocationUpdated onUserLocationUpdated; + final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; final OnCameraTrackingChangedCallback onCameraTrackingChanged; @@ -338,17 +348,17 @@ class MapboxMapController extends ChangeNotifier { /// been notified. Future addSymbol(SymbolOptions options, [Map data]) async { List result = await addSymbols([options], [data]); - + return result.first; } + Future> addSymbols(List options, + [List data]) async { + final List effectiveOptions = + options.map((o) => SymbolOptions.defaultOptions.copyWith(o)).toList(); - Future> addSymbols(List options, [List data]) async { - final List effectiveOptions = options.map( - (o) => SymbolOptions.defaultOptions.copyWith(o) - ).toList(); - - final symbols = await MapboxGlPlatform.getInstance(_id).addSymbols(effectiveOptions, data); + final symbols = await MapboxGlPlatform.getInstance(_id) + .addSymbols(effectiveOptions, data); symbols.forEach((s) => _symbols[s.id] = s); notifyListeners(); return symbols; @@ -401,7 +411,7 @@ class MapboxMapController extends ChangeNotifier { symbols.forEach((s) { assert(_symbols[s.id] == s); }); - + await _removeSymbols(symbols.map((s) => s.id)); notifyListeners(); } diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index fc2c8f9e7..90b842b3f 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -30,6 +30,7 @@ class MapboxMap extends StatefulWidget { this.compassViewMargins, this.attributionButtonMargins, this.onMapClick, + this.onUserLocationUpdated, this.onMapLongClick, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, @@ -147,6 +148,10 @@ class MapboxMap extends StatefulWidget { final OnMapClickCallback onMapClick; final OnMapClickCallback onMapLongClick; + /// 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 map's camera no longer follows the physical device location, e.g. because the user moved the map final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; @@ -216,6 +221,7 @@ class _MapboxMapState extends State { id, widget.initialCameraPosition, onStyleLoadedCallback: widget.onStyleLoadedCallback, onMapClick: widget.onMapClick, + onUserLocationUpdated: widget.onUserLocationUpdated, onMapLongClick: widget.onMapLongClick, onCameraTrackingDismissed: widget.onCameraTrackingDismissed, onCameraTrackingChanged: widget.onCameraTrackingChanged, diff --git a/mapbox_gl_platform_interface/lib/src/location.dart b/mapbox_gl_platform_interface/lib/src/location.dart index 6372e4ca9..419ad6614 100644 --- a/mapbox_gl_platform_interface/lib/src/location.dart +++ b/mapbox_gl_platform_interface/lib/src/location.dart @@ -104,3 +104,35 @@ class LatLngBounds { 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}); +} \ No newline at end of file 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 16bd88da9..2a9c53bc5 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 @@ -63,6 +63,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.'); 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 e6c0032cc..986fc182e 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 @@ -76,6 +76,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 668c88fb1..4a5cc1765 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);