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

Low location Precision due to removal of GoogleLocationEngine? #446

Open
RobertSasak opened this issue Sep 17, 2024 · 9 comments
Open

Low location Precision due to removal of GoogleLocationEngine? #446

RobertSasak opened this issue Sep 17, 2024 · 9 comments

Comments

@RobertSasak
Copy link
Contributor

Motivation

After upgrading maplibre-react-native to version 10.x I notice that on Android phone the location precision decrease. It is bit hard to debug but blue location circle is bigger.

Question

Can this be related to removing of Google Location Services com.google.android.gms ?
https://github.com/maplibre/maplibre-native/releases/tag/android-v10.0.0

If so is there a way of again using Google Location Services?

@szk5
Copy link

szk5 commented Sep 25, 2024

Has there been a solution to the problem since then?

@RobertSasak
Copy link
Contributor Author

Based on the release notes https://github.com/maplibre/maplibre-native/releases/tag/android-v10.0.0 I created following patch which again include GMS location services.

I think that solves the issue. Closing for now.

diff --git a/node_modules/@maplibre/maplibre-react-native/android/rctmln/build.gradle b/node_modules/@maplibre/maplibre-react-native/android/rctmln/build.gradle
index 7ea3e1d..e8dae6c 100644
--- a/node_modules/@maplibre/maplibre-react-native/android/rctmln/build.gradle
+++ b/node_modules/@maplibre/maplibre-react-native/android/rctmln/build.gradle
@@ -37,6 +37,7 @@ dependencies {
     implementation "org.maplibre.gl:android-sdk-turf:6.0.1"
 
     // Dependencies
+    implementation "com.google.android.gms:play-services-location:21.0.1"
     implementation "androidx.vectordrawable:vectordrawable:1.1.0"
     implementation "androidx.annotation:annotation:1.7.0"
     implementation "androidx.appcompat:appcompat:1.6.1"
diff --git a/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/LocationManager.java b/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/LocationManager.java
index bc5c444..738373e 100644
--- a/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/LocationManager.java
+++ b/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/LocationManager.java
@@ -12,6 +12,11 @@ import org.maplibre.android.location.engine.LocationEngineCallback;
 import com.mapbox.android.core.location.LocationEngineListener;
 import com.mapbox.android.core.location.LocationEnginePriority;
 */
+import org.maplibre.android.location.engine.LocationEngineProxy;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.maplibre.rctmln.location.engine.GoogleLocationEngineImpl;
 
 import org.maplibre.android.location.engine.LocationEngineDefault;
 import org.maplibre.android.location.engine.LocationEngineRequest;
@@ -63,7 +68,13 @@ public class LocationManager implements LocationEngineCallback<LocationEngineRes
 
     }
     private void buildEngineRequest() {
-        locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(this.context.getApplicationContext());
+        if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
+            locationEngine = new LocationEngineProxy<>(new GoogleLocationEngineImpl(context.getApplicationContext()));
+            Log.d(LOG_TAG, "Google Location Engine created successfully.");
+        } else {
+            locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(this.context.getApplicationContext());
+            Log.d(LOG_TAG, "Default Location Engine created successfully.");
+        }
         locationEngineRequest = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS)
                 .setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS)
                 .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
diff --git a/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/engine/GoogleLocationEngineImpl.java b/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/engine/GoogleLocationEngineImpl.java
new file mode 100644
index 0000000..55ba244
--- /dev/null
+++ b/node_modules/@maplibre/maplibre-react-native/android/rctmln/src/main/java/com/maplibre/rctmln/location/engine/GoogleLocationEngineImpl.java
@@ -0,0 +1,151 @@
+package com.maplibre.rctmln.location.engine;
+
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.location.Location;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.location.Priority;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
+import org.maplibre.android.location.engine.LocationEngineCallback;
+import org.maplibre.android.location.engine.LocationEngineImpl;
+import org.maplibre.android.location.engine.LocationEngineRequest;
+import org.maplibre.android.location.engine.LocationEngineResult;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wraps implementation of Fused Location Provider
+ */
+public class GoogleLocationEngineImpl implements LocationEngineImpl<LocationCallback> {
+    private final FusedLocationProviderClient fusedLocationProviderClient;
+
+    @VisibleForTesting
+    GoogleLocationEngineImpl(FusedLocationProviderClient fusedLocationProviderClient) {
+        this.fusedLocationProviderClient = fusedLocationProviderClient;
+    }
+
+    public GoogleLocationEngineImpl(@NonNull Context context) {
+        this.fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
+    }
+
+    @NonNull
+    @Override
+    public LocationCallback createListener(LocationEngineCallback<LocationEngineResult> callback) {
+        return new GoogleLocationEngineCallbackTransport(callback);
+    }
+
+    @SuppressLint("MissingPermission")
+    @Override
+    public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
+            throws SecurityException {
+        GoogleLastLocationEngineCallbackTransport transport =
+                new GoogleLastLocationEngineCallbackTransport(callback);
+        fusedLocationProviderClient.getLastLocation().addOnSuccessListener(transport).addOnFailureListener(transport);
+    }
+
+    @SuppressLint("MissingPermission")
+    @Override
+    public void requestLocationUpdates(@NonNull LocationEngineRequest request,
+                                       @NonNull LocationCallback listener,
+                                       @Nullable Looper looper) throws SecurityException {
+        fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), listener, looper);
+    }
+
+    @SuppressLint("MissingPermission")
+    @Override
+    public void requestLocationUpdates(@NonNull LocationEngineRequest request,
+                                       @NonNull PendingIntent pendingIntent) throws SecurityException {
+        fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), pendingIntent);
+    }
+
+    @Override
+    public void removeLocationUpdates(@NonNull LocationCallback listener) {
+        if (listener != null) {
+            fusedLocationProviderClient.removeLocationUpdates(listener);
+        }
+    }
+
+    @Override
+    public void removeLocationUpdates(PendingIntent pendingIntent) {
+        if (pendingIntent != null) {
+            fusedLocationProviderClient.removeLocationUpdates(pendingIntent);
+        }
+    }
+
+    private static LocationRequest toGMSLocationRequest(LocationEngineRequest request) {
+        LocationRequest.Builder builder = new LocationRequest.Builder(request.getInterval());
+        builder.setMinUpdateIntervalMillis(request.getFastestInterval());
+        builder.setMinUpdateDistanceMeters(request.getDisplacement());
+        builder.setMaxUpdateDelayMillis(request.getMaxWaitTime());
+        builder.setPriority(toGMSLocationPriority(request.getPriority()));
+        return builder.build();
+    }
+
+    private static int toGMSLocationPriority(int enginePriority) {
+        switch (enginePriority) {
+            case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
+                return Priority.PRIORITY_HIGH_ACCURACY;
+            case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
+                return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
+            case LocationEngineRequest.PRIORITY_LOW_POWER:
+                return Priority.PRIORITY_LOW_POWER;
+            case LocationEngineRequest.PRIORITY_NO_POWER:
+            default:
+                return Priority.PRIORITY_PASSIVE;
+        }
+    }
+
+    private static final class GoogleLocationEngineCallbackTransport extends LocationCallback {
+        private final LocationEngineCallback<LocationEngineResult> callback;
+
+        GoogleLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
+            this.callback = callback;
+        }
+
+        @Override
+        public void onLocationResult(LocationResult locationResult) {
+            super.onLocationResult(locationResult);
+            List<Location> locations = locationResult.getLocations();
+            if (!locations.isEmpty()) {
+                callback.onSuccess(LocationEngineResult.create(locations));
+            } else {
+                callback.onFailure(new Exception("Unavailable location"));
+            }
+        }
+    }
+
+    @VisibleForTesting
+    static final class GoogleLastLocationEngineCallbackTransport
+            implements OnSuccessListener<Location>, OnFailureListener {
+        private final LocationEngineCallback<LocationEngineResult> callback;
+
+        GoogleLastLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
+            this.callback = callback;
+        }
+
+        @Override
+        public void onSuccess(Location location) {
+            callback.onSuccess(location != null ? LocationEngineResult.create(location) :
+                    LocationEngineResult.create(Collections.<Location>emptyList()));
+        }
+
+        @Override
+        public void onFailure(@NonNull Exception e) {
+            callback.onFailure(e);
+        }
+    }
+}
\ No newline at end of file

@tyrauber
Copy link
Collaborator

@RobertSasak, Can you explain this in more detail?

Was this related to this:

Breaking: the LocationEngine implemented with Google Location Services has been removed to make MapLibre GL Native for Android fully FLOSS (maplibre/maplibre-native#379).

Was com.google.android.gms not fully open source, so it was removed from the Android SDK, but as a result the GPS location is less precise? Is that correct?

@RobertSasak
Copy link
Contributor Author

Correct. I have come to the same assumption. Without Google Location Service a device use only a GPS which takes longer time, requires direct vision to sky and use more battery.

Proprietary Google Location Service combines also Wifi, mobile network and GPS.

However I could not reliably verify it as I think it is depending on device and GPS conditions.

@KiwiKilian
Copy link
Collaborator

KiwiKilian commented Nov 26, 2024

@RobertSasak did the low precision ever occur again with your fix?

I've a report on Android 13, where the location jumps, while it smoothly moves on Android 14.

@KiwiKilian KiwiKilian reopened this Nov 26, 2024
@RobertSasak
Copy link
Contributor Author

After a fix I provided there were no more reports from users.

@KiwiKilian
Copy link
Collaborator

Did the initial report state, which Android version was used? Would be super helpful to know.

If this fixes the problem for us too, I'm thinking we should allow a clean way to choose the location provider.

@RobertSasak
Copy link
Contributor Author

Most of the user complains were from Android 34 which is Android 14. Few were also from 31 and 33. That would confirm also your findings. I agree it would be nice to be able to provide custom Location Provider.

@KiwiKilian KiwiKilian changed the title Lower location precision Low location Precision due to removal of GoogleLocationEngine? Nov 26, 2024
@KiwiKilian KiwiKilian changed the title Low location Precision due to removal of GoogleLocationEngine? Low location Precision due to removal of GoogleLocationEngine? Nov 26, 2024
@KiwiKilian
Copy link
Collaborator

It's reported to us, that in Flight Mode under Android 13 there is no new location at all in our app. Meanwhile it works on Google Maps on the same device and in our app under Android 14. Of course all of this can also be device specific.

Will try patch package and report the result. Thanks for your swift responses!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants