Skip to content

Commit

Permalink
Listen to OnUserLocationUpdated to provide user location to app (#237)
Browse files Browse the repository at this point in the history
* 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

* add android, web; fix conflicts

Co-authored-by: m0nac0 <58807793+m0nac0@users.noreply.github.com>
Co-authored-by: Tobrun <tobrun.van.nuland@gmail.com>
  • Loading branch information
3 people authored Oct 24, 2020
1 parent 95b7d7b commit 6ed4cfe
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 13 deletions.
57 changes: 55 additions & 2 deletions android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -134,6 +135,7 @@ final class MapboxMapController
private final String styleStringInitial;
private LocationComponent locationComponent = null;
private LocationEngine locationEngine = null;
private LocationEngineCallback<LocationEngineResult> locationEngineCallback = null;
private LocalizationPlugin localizationPlugin;
private Style style;

Expand Down Expand Up @@ -391,6 +393,24 @@ private void enableLocationComponent(@NonNull Style style) {
}
}

private void onUserLocationUpdate(Location location){
if(location==null){
return;
}

final Map<String, Object> 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<String, Object> 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);
Expand Down Expand Up @@ -964,7 +984,7 @@ public void dispose() {
if (fillManager != null) {
fillManager.onDestroy();
}

stopListeningForLocationUpdates();
mapView.onDestroy();
registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this);
}
Expand All @@ -991,6 +1011,9 @@ public void onActivityResumed(Activity activity) {
return;
}
mapView.onResume();
if(myLocationEnabled){
startListeningForLocationUpdates();
}
}

@Override
Expand All @@ -999,6 +1022,7 @@ public void onActivityPaused(Activity activity) {
return;
}
mapView.onPause();
stopListeningForLocationUpdates();
}

@Override
Expand Down Expand Up @@ -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<LocationEngineResult>() {
@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]);
Expand Down
5 changes: 4 additions & 1 deletion example/lib/map_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,10 @@ class MapUiBodyState extends State<MapUiBody> {
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<Widget> columnChildren = <Widget>[
Expand Down
13 changes: 13 additions & 0 deletions ios/Classes/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 8 additions & 0 deletions ios/Classes/MapboxMapController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
30 changes: 20 additions & 10 deletions lib/src/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ typedef void OnMapLongClickCallback(Point<double> point, LatLng coordinates);

typedef void OnStyleLoadedCallback();

typedef void OnUserLocationUpdated(UserLocation location);

typedef void OnCameraTrackingDismissedCallback();
typedef void OnCameraTrackingChangedCallback(MyLocationTrackingMode mode);

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -139,12 +142,16 @@ class MapboxMapController extends ChangeNotifier {
onMapIdle();
}
});
MapboxGlPlatform.getInstance(_id).onUserLocationUpdatedPlatform.add((location) {
onUserLocationUpdated?.call(location);
});
}

static Future<MapboxMapController> init(
int id, CameraPosition initialCameraPosition,
{OnStyleLoadedCallback onStyleLoadedCallback,
OnMapClickCallback onMapClick,
OnUserLocationUpdated onUserLocationUpdated,
OnMapLongClickCallback onMapLongClick,
OnCameraTrackingDismissedCallback onCameraTrackingDismissed,
OnCameraTrackingChangedCallback onCameraTrackingChanged,
Expand All @@ -155,6 +162,7 @@ class MapboxMapController extends ChangeNotifier {
return MapboxMapController._(id, initialCameraPosition,
onStyleLoadedCallback: onStyleLoadedCallback,
onMapClick: onMapClick,
onUserLocationUpdated: onUserLocationUpdated,
onMapLongClick: onMapLongClick,
onCameraTrackingDismissed: onCameraTrackingDismissed,
onCameraTrackingChanged: onCameraTrackingChanged,
Expand All @@ -167,6 +175,8 @@ class MapboxMapController extends ChangeNotifier {
final OnMapClickCallback onMapClick;
final OnMapLongClickCallback onMapLongClick;

final OnUserLocationUpdated onUserLocationUpdated;

final OnCameraTrackingDismissedCallback onCameraTrackingDismissed;
final OnCameraTrackingChangedCallback onCameraTrackingChanged;

Expand Down Expand Up @@ -338,17 +348,17 @@ class MapboxMapController extends ChangeNotifier {
/// been notified.
Future<Symbol> addSymbol(SymbolOptions options, [Map data]) async {
List<Symbol> result = await addSymbols([options], [data]);

return result.first;
}

Future<List<Symbol>> addSymbols(List<SymbolOptions> options,
[List<Map> data]) async {
final List<SymbolOptions> effectiveOptions =
options.map((o) => SymbolOptions.defaultOptions.copyWith(o)).toList();

Future<List<Symbol>> addSymbols(List<SymbolOptions> options, [List<Map> data]) async {
final List<SymbolOptions> 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;
Expand Down Expand Up @@ -401,7 +411,7 @@ class MapboxMapController extends ChangeNotifier {
symbols.forEach((s) {
assert(_symbols[s.id] == s);
});

await _removeSymbols(symbols.map((s) => s.id));
notifyListeners();
}
Expand Down
6 changes: 6 additions & 0 deletions lib/src/mapbox_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class MapboxMap extends StatefulWidget {
this.compassViewMargins,
this.attributionButtonMargins,
this.onMapClick,
this.onUserLocationUpdated,
this.onMapLongClick,
this.onCameraTrackingDismissed,
this.onCameraTrackingChanged,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -216,6 +221,7 @@ class _MapboxMapState extends State<MapboxMap> {
id, widget.initialCameraPosition,
onStyleLoadedCallback: widget.onStyleLoadedCallback,
onMapClick: widget.onMapClick,
onUserLocationUpdated: widget.onUserLocationUpdated,
onMapLongClick: widget.onMapLongClick,
onCameraTrackingDismissed: widget.onCameraTrackingDismissed,
onCameraTrackingChanged: widget.onCameraTrackingChanged,
Expand Down
32 changes: 32 additions & 0 deletions mapbox_gl_platform_interface/lib/src/location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ abstract class MapboxGlPlatform {
ArgumentCallbacks<void>();

final ArgumentCallbacks<void> onMapIdlePlatform = ArgumentCallbacks<void>();

final ArgumentCallbacks<UserLocation> onUserLocationUpdatedPlatform = ArgumentCallbacks<UserLocation>();

Future<void> initPlatform(int id) async {
throw UnimplementedError('initPlatform() has not been implemented.');
Expand Down
14 changes: 14 additions & 0 deletions mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions mapbox_gl_web/lib/src/mapbox_map_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 6ed4cfe

Please sign in to comment.