Buena forma de obtener la ubicación del usuario en Android

211

El problema:

Obtener la ubicación actual del usuario dentro de un umbral lo antes posible y al mismo tiempo ahorrar batería.

Por qué el problema es un problema:

En primer lugar, Android tiene dos proveedores; red y GPS. Algunas veces la red es mejor y otras veces el GPS es mejor.

Por "mejor" me refiero a la velocidad frente a la relación de precisión.
Estoy dispuesto a sacrificar unos pocos metros de precisión si puedo obtener la ubicación casi al instante y sin encender el GPS.

En segundo lugar, si solicita actualizaciones para cambios de ubicación, no se envía nada si la ubicación actual es estable.

Google tiene un ejemplo de determinación de la "mejor" ubicación aquí: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Pero creo que no es tan bueno como debería /podría ser.

Estoy un poco confundido por qué Google no tiene una API normalizada para la ubicación, el desarrollador no debería tener que preocuparse de dónde es la ubicación, solo debe especificar lo que desea y el teléfono debe elegir por usted.

Lo que necesito ayuda con:

Necesito encontrar una buena manera de determinar la "mejor" ubicación, quizás a través de alguna heurística o tal vez a través de una biblioteca de terceros.

¡Esto no significa determinar el mejor proveedor!
Probablemente voy a usar todos los proveedores y elegir el mejor de ellos.

Antecedentes de la aplicación:

La aplicación recopilará la ubicación del usuario en un intervalo fijo (digamos cada 10 minutos más o menos) y la enviará a un servidor.
La aplicación debe conservar la mayor cantidad de batería posible y la ubicación debe tener una precisión de X (50-100?) Metros.

El objetivo es luego poder trazar la ruta del usuario durante el día en un mapa, por lo que necesito suficiente precisión para eso.

Misceláneo:

¿Cuáles cree que son valores razonables en las precisiones deseadas y aceptadas?
He estado usando 100m según lo aceptado y 30m según lo deseado, ¿es mucho pedir?
Me gustaría poder trazar la ruta del usuario en un mapa más adelante.
¿Son 100m para deseados y 500m para aceptados mejor?

Además, en este momento tengo el GPS encendido durante un máximo de 60 segundos por actualización de ubicación, ¿es demasiado corto para obtener una ubicación si está en interiores con una precisión de quizás 200 m?


Este es mi código actual, cualquier comentario es apreciado (aparte de la falta de verificación de errores que es TODO):

protected void runTask() {
    final LocationManager locationManager = (LocationManager) context
            .getSystemService(Context.LOCATION_SERVICE);
    updateBestLocation(locationManager
            .getLastKnownLocation(LocationManager.GPS_PROVIDER));
    updateBestLocation(locationManager
            .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
    if (getLocationQuality(bestLocation) != LocationQuality.GOOD) {
        Looper.prepare();
        setLooper(Looper.myLooper());
        // Define a listener that responds to location updates
        LocationListener locationListener = new LocationListener() {

            public void onLocationChanged(Location location) {
                updateBestLocation(location);
                if (getLocationQuality(bestLocation) != LocationQuality.GOOD)
                    return;
                // We're done
                Looper l = getLooper();
                if (l != null) l.quit();
            }

            public void onProviderEnabled(String provider) {}

            public void onProviderDisabled(String provider) {}

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
                // TODO Auto-generated method stub
                Log.i("LocationCollector", "Fail");
                Looper l = getLooper();
                if (l != null) l.quit();
            }
        };
        // Register the listener with the Location Manager to receive
        // location updates
        locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 1000, 1, locationListener,
                Looper.myLooper());
        locationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER, 1000, 1,
                locationListener, Looper.myLooper());
        Timer t = new Timer();
        t.schedule(new TimerTask() {

            @Override
            public void run() {
                Looper l = getLooper();
                if (l != null) l.quit();
                // Log.i("LocationCollector",
                // "Stopping collector due to timeout");
            }
        }, MAX_POLLING_TIME);
        Looper.loop();
        t.cancel();
        locationManager.removeUpdates(locationListener);
        setLooper(null);
    }
    if (getLocationQuality(bestLocation) != LocationQuality.BAD) 
        sendUpdate(locationToString(bestLocation));
    else Log.w("LocationCollector", "Failed to get a location");
}

private enum LocationQuality {
    BAD, ACCEPTED, GOOD;

    public String toString() {
        if (this == GOOD) return "Good";
        else if (this == ACCEPTED) return "Accepted";
        else return "Bad";
    }
}

private LocationQuality getLocationQuality(Location location) {
    if (location == null) return LocationQuality.BAD;
    if (!location.hasAccuracy()) return LocationQuality.BAD;
    long currentTime = System.currentTimeMillis();
    if (currentTime - location.getTime() < MAX_AGE
            && location.getAccuracy() <= GOOD_ACCURACY)
        return LocationQuality.GOOD;
    if (location.getAccuracy() <= ACCEPTED_ACCURACY)
        return LocationQuality.ACCEPTED;
    return LocationQuality.BAD;
}

private synchronized void updateBestLocation(Location location) {
    bestLocation = getBestLocation(location, bestLocation);
}

// Pretty much an unmodified version of googles example
protected Location getBestLocation(Location location,
        Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return location;
    }
    if (location == null) return currentBestLocation;
    // Check whether the new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;
    // If it's been more than two minutes since the current location, use
    // the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return location;
        // If the new location is more than two minutes older, it must be
        // worse
    } else if (isSignificantlyOlder) {
        return currentBestLocation;
    }
    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
            .getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    // Check if the old and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());
    // Determine location quality using a combination of timeliness and
    // accuracy
    if (isMoreAccurate) {
        return location;
    } else if (isNewer && !isLessAccurate) {
        return location;
    } else if (isNewer && !isSignificantlyLessAccurate
            && isFromSameProvider) {
        return location;
    }
    return bestLocation;
}

/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
        return provider2 == null;
    }
    return provider1.equals(provider2);
}
Nicklas A.
fuente
77
Entrando muy tarde, pero el "Proveedor de ubicación fusionada" que se anunció recientemente en IO 2013 parece que satisface muchas de sus necesidades - developer.android.com/google/play-services/location.html
Matt
no debería ser la última línea de getBestLocation (): return currentBestLocation; en lugar de devolver bestLocation ;?
Gavriel

Respuestas:

164

Parece que estamos codificando la misma aplicación ;-)
Aquí está mi implementación actual. Todavía estoy en la fase de prueba beta de mi aplicación de carga de GPS, por lo que podría haber muchas mejoras posibles. pero parece funcionar bastante bien hasta ahora.

/**
 * try to get the 'best' location selected from all providers
 */
private Location getBestLocation() {
    Location gpslocation = getLocationByProvider(LocationManager.GPS_PROVIDER);
    Location networkLocation =
            getLocationByProvider(LocationManager.NETWORK_PROVIDER);
    // if we have only one location available, the choice is easy
    if (gpslocation == null) {
        Log.d(TAG, "No GPS Location available.");
        return networkLocation;
    }
    if (networkLocation == null) {
        Log.d(TAG, "No Network Location available");
        return gpslocation;
    }
    // a locationupdate is considered 'old' if its older than the configured
    // update interval. this means, we didn't get a
    // update from this provider since the last check
    long old = System.currentTimeMillis() - getGPSCheckMilliSecsFromPrefs();
    boolean gpsIsOld = (gpslocation.getTime() < old);
    boolean networkIsOld = (networkLocation.getTime() < old);
    // gps is current and available, gps is better than network
    if (!gpsIsOld) {
        Log.d(TAG, "Returning current GPS Location");
        return gpslocation;
    }
    // gps is old, we can't trust it. use network location
    if (!networkIsOld) {
        Log.d(TAG, "GPS is old, Network is current, returning network");
        return networkLocation;
    }
    // both are old return the newer of those two
    if (gpslocation.getTime() > networkLocation.getTime()) {
        Log.d(TAG, "Both are old, returning gps(newer)");
        return gpslocation;
    } else {
        Log.d(TAG, "Both are old, returning network(newer)");
        return networkLocation;
    }
}

/**
 * get the last known location from a specific provider (network/gps)
 */
private Location getLocationByProvider(String provider) {
    Location location = null;
    if (!isProviderSupported(provider)) {
        return null;
    }
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    try {
        if (locationManager.isProviderEnabled(provider)) {
            location = locationManager.getLastKnownLocation(provider);
        }
    } catch (IllegalArgumentException e) {
        Log.d(TAG, "Cannot acces Provider " + provider);
    }
    return location;
}

Editar: aquí está la parte que solicita las actualizaciones periódicas de los proveedores de ubicación:

public void startRecording() {
    gpsTimer.cancel();
    gpsTimer = new Timer();
    long checkInterval = getGPSCheckMilliSecsFromPrefs();
    long minDistance = getMinDistanceFromPrefs();
    // receive updates
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    for (String s : locationManager.getAllProviders()) {
        locationManager.requestLocationUpdates(s, checkInterval,
                minDistance, new LocationListener() {

                    @Override
                    public void onStatusChanged(String provider,
                            int status, Bundle extras) {}

                    @Override
                    public void onProviderEnabled(String provider) {}

                    @Override
                    public void onProviderDisabled(String provider) {}

                    @Override
                    public void onLocationChanged(Location location) {
                        // if this is a gps location, we can use it
                        if (location.getProvider().equals(
                                LocationManager.GPS_PROVIDER)) {
                            doLocationUpdate(location, true);
                        }
                    }
                });
        // //Toast.makeText(this, "GPS Service STARTED",
        // Toast.LENGTH_LONG).show();
        gps_recorder_running = true;
    }
    // start the gps receiver thread
    gpsTimer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            Location location = getBestLocation();
            doLocationUpdate(location, false);
        }
    }, 0, checkInterval);
}

public void doLocationUpdate(Location l, boolean force) {
    long minDistance = getMinDistanceFromPrefs();
    Log.d(TAG, "update received:" + l);
    if (l == null) {
        Log.d(TAG, "Empty location");
        if (force)
            Toast.makeText(this, "Current location not available",
                    Toast.LENGTH_SHORT).show();
        return;
    }
    if (lastLocation != null) {
        float distance = l.distanceTo(lastLocation);
        Log.d(TAG, "Distance to last: " + distance);
        if (l.distanceTo(lastLocation) < minDistance && !force) {
            Log.d(TAG, "Position didn't change");
            return;
        }
        if (l.getAccuracy() >= lastLocation.getAccuracy()
                && l.distanceTo(lastLocation) < l.getAccuracy() && !force) {
            Log.d(TAG,
                    "Accuracy got worse and we are still "
                      + "within the accuracy range.. Not updating");
            return;
        }
        if (l.getTime() <= lastprovidertimestamp && !force) {
            Log.d(TAG, "Timestamp not never than last");
            return;
        }
    }
    // upload/store your location here
}

Cosas para considerar:

  • no solicite actualizaciones de GPS con demasiada frecuencia, agota la batería. Actualmente uso 30 minutos como predeterminado para mi aplicación.

  • agregue una marca de "distancia mínima a la última ubicación conocida". sin esto, sus puntos "saltarán" cuando el GPS no esté disponible y la ubicación se triangule desde las torres celulares. o puede verificar si la nueva ubicación está fuera del valor de precisión de la última ubicación conocida.

Grifo
fuente
2
En realidad, nunca obtienes una ubicación nueva, solo usas ubicaciones que están allí desde actualizaciones anteriores. Creo que este código se beneficiaría enormemente al agregar un oyente que actualiza la ubicación al encender el GPS de vez en cuando.
Nicklas A.
2
lo siento, pensé que solo estaba interesado en la parte que selecciona lo mejor de todas las ubicaciones disponibles. Agregué el código anterior que también los solicita. Si se recibe una nueva ubicación GPS, se almacena / carga de inmediato. si recibo una actualización de ubicación de red, la almaceno como referencia y 'espero' que también reciba una actualización de GPS hasta que ocurra la próxima verificación de ubicación.
Gryphius
2
También tuve un método stopRecording () que canceló el temporizador. Eventualmente cambié de un temporizador a un ScheduledThreadPoolExecutor, así que stopRecording ahora básicamente llama a ejecutor.shutdown () y anula el registro de todos los oyentes de actualizaciones de ubicación
Gryphius
1
de acuerdo con mi scm, stopRecording solo se llama gpsTimer.cancel () y establece gps_recorder_running = false, por lo que, como en su caso, no hay limpieza de los oyentes en ese momento. el código actual realiza un seguimiento de todos los oyentes activos en un vector, no tenía esto cuando escribí esta respuesta hace 1,5 años.
Gryphius
1
ya está en github , pero no estoy seguro de que esta sea la mejor manera de hacer cosas con GPS hoy en día. Afaik han realizado muchas mejoras en la API de ubicación desde que escribí este código.
Gryphius
33

Para seleccionar el proveedor de ubicación adecuado para su aplicación, puede usar los objetos Criteria :

Criteria myCriteria = new Criteria();
myCriteria.setAccuracy(Criteria.ACCURACY_HIGH);
myCriteria.setPowerRequirement(Criteria.POWER_LOW);
// let Android select the right location provider for you
String myProvider = locationManager.getBestProvider(myCriteria, true); 

// finally require updates at -at least- the desired rate
long minTimeMillis = 600000; // 600,000 milliseconds make 10 minutes
locationManager.requestLocationUpdates(myProvider,minTimeMillis,0,locationListener); 

Lea la documentación de requestLocationUpdates para obtener más detalles sobre cómo se tienen en cuenta los argumentos:

La frecuencia de notificación puede controlarse utilizando los parámetros minTime y minDistance. Si minTime es mayor que 0, el LocationManager podría potencialmente descansar durante milisegundos minTime entre actualizaciones de ubicación para ahorrar energía. Si minDistance es mayor que 0, una ubicación solo se transmitirá si el dispositivo se mueve por medidores minDistance. Para obtener notificaciones con la mayor frecuencia posible, establezca ambos parámetros en 0.

Más pensamientos

  • Puede controlar la precisión de los objetos de ubicación con Location.getAccuracy () , que devuelve la precisión estimada de la posición en metros.
  • El Criteria.ACCURACY_HIGHcriterio debería proporcionarle errores por debajo de los 100 m, lo que no es tan bueno como el GPS, pero satisface sus necesidades.
  • También debe controlar el estado de su proveedor de ubicación y cambiar a otro proveedor si el usuario no lo encuentra o lo deshabilita.
  • El proveedor pasivo también puede ser una buena combinación para este tipo de aplicación: la idea es usar actualizaciones de ubicación siempre que sean solicitadas por otra aplicación y transmitidas en todo el sistema.
Stéphane
fuente
Lo he investigado, Criteriapero ¿qué pasa si la última ubicación de red es increíble? No puedo creer que Google haya dificultado esto a los desarrolladores.
Nicklas A.
Además de usar los Criterios, puede, en cada actualización de ubicación enviada por el proveedor que seleccionó, verificar lastKnowLocation para el proveedor de GPS y compararlo (precisión y fecha) con su ubicación actual. Pero esto me parece agradable en lugar de un requisito de sus especificaciones; Si a veces se logra una precisión algo mejor, ¿será realmente útil para sus usuarios?
Stéphane
Eso es lo que estoy haciendo ahora, el problema es que me cuesta mucho averiguar si el último conocimiento es lo suficientemente bueno. También puedo agregar que no tengo que limitarme a un solo proveedor, cuanto más uso, más rápido puedo obtener un bloqueo.
Nicklas A.
Tenga en cuenta que PASSIVE_PROVIDER requiere API Nivel 8 o superior.
Eduardo
@ Stéphane lo siento por la edición. No te preocupes por eso. Tu publicación es correcta. Hice esa edición por error. Lo siento. Saludos.
Gaucho
10

Respondiendo los dos primeros puntos :

  • El GPS siempre le dará una ubicación más precisa, si está habilitado y si no hay paredes gruesas alrededor .

  • Si la ubicación no cambió, puede llamar a getLastKnownLocation (String) y recuperar la ubicación de inmediato.

Usando un enfoque alternativo :

Puede intentar obtener la identificación de la celda en uso o todas las celdas vecinas

TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
GsmCellLocation loc = (GsmCellLocation) mTelephonyManager.getCellLocation(); 
Log.d ("CID", Integer.toString(loc.getCid()));
Log.d ("LAC", Integer.toString(loc.getLac()));
// or 
List<NeighboringCellInfo> list = mTelephonyManager.getNeighboringCellInfo ();
for (NeighboringCellInfo cell : list) {
    Log.d ("CID", Integer.toString(cell.getCid()));
    Log.d ("LAC", Integer.toString(cell.getLac()));
}

Puede referirse a la ubicación de la celda a través de varias bases de datos abiertas (por ejemplo, http://www.location-api.com/ o http://opencellid.org/ )


La estrategia sería leer la lista de ID de torre al leer la ubicación. Luego, en la siguiente consulta (10 minutos en su aplicación), léalos nuevamente. Si al menos algunas torres son iguales, entonces es seguro de usar getLastKnownLocation(String). Si no lo están, espere onLocationChanged(). Esto evita la necesidad de una base de datos de terceros para la ubicación. También puedes probar este enfoque .

Aleadam
fuente
Sí, pero el problema surge si lastKnownLocation es realmente malo. Necesito una buena forma de decidir la mejor de las dos ubicaciones.
Nicklas A.
Puede almacenar la información de las torres y verificar si esas torres cambiaron. Si lo hicieron, espere una nueva ubicación, si no (o si solo algunos cambiaron), luego reutilícela. De esta forma, evita comparar las ubicaciones de las torres con una base de datos.
Aleadam
Usar torres me parece una gran exageración, aunque es una buena idea.
Nicklas A.
@Nicklas, el código no se vuelve más complicado que eso. Sin embargo, necesitará android.Manifest.permission # ACCESS_COARSE_UPDATES.
Aleadam
Sí, pero todavía necesito usar un servicio de terceros y también necesito una forma de decidir cuándo usar la información de la torre sobre los datos de ubicación, esto solo agrega una capa adicional de complejidad.
Nicklas A.
9

Esta es mi solución que funciona bastante bien:

private Location bestLocation = null;
private Looper looper;
private boolean networkEnabled = false, gpsEnabled = false;

private synchronized void setLooper(Looper looper) {
    this.looper = looper;
}

private synchronized void stopLooper() {
    if (looper == null) return;
    looper.quit();
}

@Override
protected void runTask() {
    final LocationManager locationManager = (LocationManager) service
            .getSystemService(Context.LOCATION_SERVICE);
    final SharedPreferences prefs = getPreferences();
    final int maxPollingTime = Integer.parseInt(prefs.getString(
            POLLING_KEY, "0"));
    final int desiredAccuracy = Integer.parseInt(prefs.getString(
            DESIRED_KEY, "0"));
    final int acceptedAccuracy = Integer.parseInt(prefs.getString(
            ACCEPTED_KEY, "0"));
    final int maxAge = Integer.parseInt(prefs.getString(AGE_KEY, "0"));
    final String whichProvider = prefs.getString(PROVIDER_KEY, "any");
    final boolean canUseGps = whichProvider.equals("gps")
            || whichProvider.equals("any");
    final boolean canUseNetwork = whichProvider.equals("network")
            || whichProvider.equals("any");
    if (canUseNetwork)
        networkEnabled = locationManager
                .isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    if (canUseGps)
        gpsEnabled = locationManager
                .isProviderEnabled(LocationManager.GPS_PROVIDER);
    // If any provider is enabled now and we displayed a notification clear it.
    if (gpsEnabled || networkEnabled) removeErrorNotification();
    if (gpsEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.GPS_PROVIDER));
    if (networkEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
    if (desiredAccuracy == 0
            || getLocationQuality(desiredAccuracy, acceptedAccuracy,
                    maxAge, bestLocation) != LocationQuality.GOOD) {
        // Define a listener that responds to location updates
        LocationListener locationListener = new LocationListener() {

            public void onLocationChanged(Location location) {
                updateBestLocation(location);
                if (desiredAccuracy != 0
                        && getLocationQuality(desiredAccuracy,
                                acceptedAccuracy, maxAge, bestLocation)
                                == LocationQuality.GOOD)
                    stopLooper();
            }

            public void onProviderEnabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled =true;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = true;
                // The user has enabled a location, remove any error
                // notification
                if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }

            public void onProviderDisabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled=false;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = false;
                if (!gpsEnabled && !networkEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
                Log.i(LOG_TAG, "Provider " + provider + " statusChanged");
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER)) networkEnabled = 
                        status == LocationProvider.AVAILABLE
                        || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER))
                    gpsEnabled = status == LocationProvider.AVAILABLE
                      || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                // None of them are available, stop listening
                if (!networkEnabled && !gpsEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
                // The user has enabled a location, remove any error
                // notification
                else if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }
        };
        if (networkEnabled || gpsEnabled) {
            Looper.prepare();
            setLooper(Looper.myLooper());
            // Register the listener with the Location Manager to receive
            // location updates
            if (canUseGps)
                locationManager.requestLocationUpdates(
                        LocationManager.GPS_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            if (canUseNetwork)
                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            Timer t = new Timer();
            t.schedule(new TimerTask() {

                @Override
                public void run() {
                    stopLooper();
                }
            }, maxPollingTime * 1000);
            Looper.loop();
            t.cancel();
            setLooper(null);
            locationManager.removeUpdates(locationListener);
        } else // No provider is enabled, show a notification
        showErrorNotification();
    }
    if (getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
            bestLocation) != LocationQuality.BAD) {
        sendUpdate(new Event(EVENT_TYPE, locationToString(desiredAccuracy,
                acceptedAccuracy, maxAge, bestLocation)));
    } else Log.w(LOG_TAG, "LocationCollector failed to get a location");
}

private synchronized void showErrorNotification() {
    if (notifId != 0) return;
    ServiceHandler handler = service.getHandler();
    NotificationInfo ni = NotificationInfo.createSingleNotification(
            R.string.locationcollector_notif_ticker,
            R.string.locationcollector_notif_title,
            R.string.locationcollector_notif_text,
            android.R.drawable.stat_notify_error);
    Intent intent = new Intent(
            android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    ni.pendingIntent = PendingIntent.getActivity(service, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    Message msg = handler.obtainMessage(ServiceHandler.SHOW_NOTIFICATION);
    msg.obj = ni;
    handler.sendMessage(msg);
    notifId = ni.id;
}

private void removeErrorNotification() {
    if (notifId == 0) return;
    ServiceHandler handler = service.getHandler();
    if (handler != null) {
        Message msg = handler.obtainMessage(
                ServiceHandler.CLEAR_NOTIFICATION, notifId, 0);
        handler.sendMessage(msg);
        notifId = 0;
    }
}

@Override
public void interrupt() {
    stopLooper();
    super.interrupt();
}

private String locationToString(int desiredAccuracy, int acceptedAccuracy,
        int maxAge, Location location) {
    StringBuilder sb = new StringBuilder();
    sb.append(String.format(
            "qual=%s time=%d prov=%s acc=%.1f lat=%f long=%f",
            getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
                    location), location.getTime() / 1000, // Millis to
                                                            // seconds
            location.getProvider(), location.getAccuracy(), location
                    .getLatitude(), location.getLongitude()));
    if (location.hasAltitude())
        sb.append(String.format(" alt=%.1f", location.getAltitude()));
    if (location.hasBearing())
        sb.append(String.format(" bearing=%.2f", location.getBearing()));
    return sb.toString();
}

private enum LocationQuality {
    BAD, ACCEPTED, GOOD;

    public String toString() {
        if (this == GOOD) return "Good";
        else if (this == ACCEPTED) return "Accepted";
        else return "Bad";
    }
}

private LocationQuality getLocationQuality(int desiredAccuracy,
        int acceptedAccuracy, int maxAge, Location location) {
    if (location == null) return LocationQuality.BAD;
    if (!location.hasAccuracy()) return LocationQuality.BAD;
    long currentTime = System.currentTimeMillis();
    if (currentTime - location.getTime() < maxAge * 1000
            && location.getAccuracy() <= desiredAccuracy)
        return LocationQuality.GOOD;
    if (acceptedAccuracy == -1
            || location.getAccuracy() <= acceptedAccuracy)
        return LocationQuality.ACCEPTED;
    return LocationQuality.BAD;
}

private synchronized void updateBestLocation(Location location) {
    bestLocation = getBestLocation(location, bestLocation);
}

protected Location getBestLocation(Location location,
        Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return location;
    }
    if (location == null) return currentBestLocation;
    // Check whether the new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;
    // If it's been more than two minutes since the current location, use
    // the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return location;
        // If the new location is more than two minutes older, it must be
        // worse
    } else if (isSignificantlyOlder) {
        return currentBestLocation;
    }
    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
            .getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    // Check if the old and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());
    // Determine location quality using a combination of timeliness and
    // accuracy
    if (isMoreAccurate) {
        return location;
    } else if (isNewer && !isLessAccurate) {
        return location;
    } else if (isNewer && !isSignificantlyLessAccurate
            && isFromSameProvider) {
        return location;
    }
    return bestLocation;
}

/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) return provider2 == null;
    return provider1.equals(provider2);
}
Nicklas A.
fuente
Hola tengo Nicklas misma manera equirement podía i comunicar a usted por cualquier medio .. yo estaría gracias completo a usted si pudiera nos ayuda ..
School Boy
¿Podría publicar todo el código? Gracias, realmente apreciado
rodi
Ese es todo el código. Ya no tengo acceso al proyecto.
Nicklas A.
1
Parece que has tomado el código de este proyecto "android-protips-location" y todavía está vivo. La gente puede ver cómo funciona aquí code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77
7

La precisión de la ubicación depende principalmente del proveedor de ubicación utilizado:

  1. GPS: obtendrá varios metros de precisión (suponiendo que tenga recepción GPS)
  2. Wifi: obtendrá unos cientos de metros de precisión
  3. Red celular: obtendrá resultados muy inexactos (he visto una desviación de hasta 4 km ...)

Si lo que busca es precisión, entonces el GPS es su única opción.

He leído un artículo muy informativo al respecto aquí .

En cuanto al tiempo de espera del GPS, 60 segundos deberían ser suficientes y, en la mayoría de los casos, incluso demasiado. Creo que 30 segundos está bien y, a veces, incluso menos de 5 segundos ...

si solo necesita una única ubicación, le sugiero que, en su onLocationChangedmétodo, una vez que reciba una actualización anule el registro del oyente y evite el uso innecesario del GPS.

Muzikant
fuente
Realmente no me importa de dónde obtengo mi ubicación, no quiero limitarme a un solo proveedor
Nicklas A.
Puede registrar todos los proveedores de ubicación disponibles en el dispositivo (puede obtener la lista de todos los proveedores en LocationManager.getProviders ()), pero si está buscando una solución precisa, en la mayoría de los casos, el proveedor de la red no será útil para usted.
Muzikant
Sí, pero no se trata de elegir entre proveedores, se trata de obtener la mejor ubicación en general (incluso cuando hay varios proveedores involucrados)
Nicklas A.
4

Actualmente estoy usando ya que es confiable para obtener la ubicación y calcular la distancia para mi aplicación ... estoy usando esto para mi aplicación de taxi.

use la API de fusión que los desarrolladores de Google han desarrollado con la fusión del sensor GPS, el magnetómetro, el acelerómetro y también el uso de Wifi o la ubicación de la celda para calcular o estimar la ubicación. También es capaz de proporcionar actualizaciones de ubicación también dentro del edificio con precisión. para obtener más información, acceda al enlace https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi

import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class MainActivity extends Activity implements LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private static final long ONE_MIN = 500;
    private static final long TWO_MIN = 500;
    private static final long FIVE_MIN = 500;
    private static final long POLLING_FREQ = 1000 * 20;
    private static final long FASTEST_UPDATE_FREQ = 1000 * 5;
    private static final float MIN_ACCURACY = 1.0f;
    private static final float MIN_LAST_READ_ACCURACY = 1;

    private LocationRequest mLocationRequest;
    private Location mBestReading;
TextView tv;
    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!servicesAvailable()) {
            finish();
        }

        setContentView(R.layout.activity_main);
tv= (TextView) findViewById(R.id.tv1);
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(POLLING_FREQ);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_FREQ);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();


        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onPause() {d
        super.onPause();

        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }


        tv.setText(location + "");
        // Determine whether new location is better than current best
        // estimate
        if (null == mBestReading || location.getAccuracy() < mBestReading.getAccuracy()) {
            mBestReading = location;


            if (mBestReading.getAccuracy() < MIN_ACCURACY) {
                LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            }
        }
    }

    @Override
    public void onConnected(Bundle dataBundle) {
        // Get first reading. Get additional location updates if necessary
        if (servicesAvailable()) {

            // Get best last location measurement meeting criteria
            mBestReading = bestLastKnownLocation(MIN_LAST_READ_ACCURACY, FIVE_MIN);

            if (null == mBestReading
                    || mBestReading.getAccuracy() > MIN_LAST_READ_ACCURACY
                    || mBestReading.getTime() < System.currentTimeMillis() - TWO_MIN) {

                LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);

               //Schedule a runnable to unregister location listeners

                    @Override
                    public void run() {
                        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, MainActivity.this);

                    }

                }, ONE_MIN, TimeUnit.MILLISECONDS);

            }

        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }


    private Location bestLastKnownLocation(float minAccuracy, long minTime) {
        Location bestResult = null;
        float bestAccuracy = Float.MAX_VALUE;
        long bestTime = Long.MIN_VALUE;

        // Get the best most recent location currently available
        Location mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        //tv.setText(mCurrentLocation+"");
        if (mCurrentLocation != null) {
            float accuracy = mCurrentLocation.getAccuracy();
            long time = mCurrentLocation.getTime();

            if (accuracy < bestAccuracy) {
                bestResult = mCurrentLocation;
                bestAccuracy = accuracy;
                bestTime = time;
            }
        }

        // Return best reading or null
        if (bestAccuracy > minAccuracy || bestTime < minTime) {
            return null;
        }
        else {
            return bestResult;
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

    private boolean servicesAvailable() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        if (ConnectionResult.SUCCESS == resultCode) {
            return true;
        }
        else {
            GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
            return false;
        }
    }
}
AshisParajuli
fuente
2

Busqué en Internet una respuesta actualizada (el año pasado) usando los últimos métodos de extracción de ubicación sugeridos por Google (para usar FusedLocationProviderClient). Finalmente llegué a esto:

https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates

Creé un nuevo proyecto y copié la mayor parte de este código. Auge. Funciona. Y creo que sin ninguna línea en desuso.

Además, el simulador no parece tener una ubicación GPS, que yo sepa. Llegó al extremo de informar esto en el registro: "Todas las configuraciones de ubicación están satisfechas".

Y finalmente, en caso de que quisieras saber (lo hice), NO necesitas una clave de API de Google Maps de la consola de desarrolladores de Google, si todo lo que quieres es la ubicación del GPS.

También es útil su tutorial. Pero quería un tutorial completo / ejemplo de código de una página, y eso. Su tutorial se acumula pero es confuso cuando eres nuevo en esto porque no sabes qué piezas necesitas de páginas anteriores.

https://developer.android.com/training/location/index.html

Y finalmente, recuerda cosas como esta:

No solo tuve que modificar mainActivity.Java. También tuve que modificar Strings.xml, androidmanifest.xml, y el build.gradle correcto. Y también tu activity_Main.xml (pero esa parte fue fácil para mí).

Necesitaba agregar dependencias como esta: implementación 'com.google.android.gms: play-services-location: 11.8.0' y actualizar la configuración de mi SDK de Android Studio para incluir los servicios de Google Play. (configuración del archivo configuración del sistema de apariencia Android SDK SDK Tools verifique los servicios de Google Play).

actualización: el simulador de Android parecía tener una ubicación y eventos de cambio de ubicación (cuando cambié el valor en la configuración del sim). Pero mis mejores y primeros resultados fueron en un dispositivo real. Por lo tanto, probablemente sea más fácil probarlo en dispositivos reales.

Neo42
fuente
1

Recientemente refactorizado para obtener la ubicación del código, aprender algunas buenas ideas y finalmente lograr una biblioteca y demostración relativamente perfecta.

La respuesta de @Gryphius es buena

    //request all valid provider(network/gps)
private boolean requestAllProviderUpdates() {
    checkRuntimeEnvironment();
    checkPermission();

    if (isRequesting) {
        EasyLog.d("Request location update is busy");
        return false;
    }


    long minTime = getCheckTimeInterval();
    float minDistance = getCheckMinDistance();

    if (mMapLocationListeners == null) {
        mMapLocationListeners = new HashMap<>();
    }

    mValidProviders = getValidProviders();
    if (mValidProviders == null || mValidProviders.isEmpty()) {
        throw new IllegalArgumentException("Not available provider.");
    }

    for (String provider : mValidProviders) {
        LocationListener locationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                if (location == null) {
                    EasyLog.e("LocationListener callback location is null.");
                    return;
                }
                printf(location);
                mLastProviderTimestamp = location.getTime();

                if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
                    finishResult(location);
                } else {
                    doLocationResult(location);
                }

                removeProvider(location.getProvider());
                if (isEmptyValidProviders()) {
                    requestTimeoutMsgInit();
                    removeUpdates();
                }
            }

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
            }

            @Override
            public void onProviderEnabled(String provider) {
            }

            @Override
            public void onProviderDisabled(String provider) {
            }
        };
        getLocationManager().requestLocationUpdates(provider, minTime, minDistance, locationListener);
        mMapLocationListeners.put(provider, locationListener);
        EasyLog.d("Location request %s provider update.", provider);
    }
    isRequesting = true;
    return true;
}

//remove request update
public void removeUpdates() {
    checkRuntimeEnvironment();

    LocationManager locationManager = getLocationManager();
    if (mMapLocationListeners != null) {
        Set<String> keys = mMapLocationListeners.keySet();
        for (String key : keys) {
            LocationListener locationListener = mMapLocationListeners.get(key);
            if (locationListener != null) {
                locationManager.removeUpdates(locationListener);
                EasyLog.d("Remove location update, provider is " + key);
            }
        }
        mMapLocationListeners.clear();
        isRequesting = false;
    }
}

//Compared with the last successful position, to determine whether you need to filter
private boolean isNeedFilter(Location location) {
    checkLocation(location);

    if (mLastLocation != null) {
        float distance = location.distanceTo(mLastLocation);
        if (distance < getCheckMinDistance()) {
            return true;
        }
        if (location.getAccuracy() >= mLastLocation.getAccuracy()
                && distance < location.getAccuracy()) {
            return true;
        }
        if (location.getTime() <= mLastProviderTimestamp) {
            return true;
        }
    }
    return false;
}

private void doLocationResult(Location location) {
    checkLocation(location);

    if (isNeedFilter(location)) {
        EasyLog.d("location need to filtered out, timestamp is " + location.getTime());
        finishResult(mLastLocation);
    } else {
        finishResult(location);
    }
}

//Return to the finished position
private void finishResult(Location location) {
    checkLocation(location);

    double latitude = location.getLatitude();
    double longitude = location.getLongitude();
    float accuracy = location.getAccuracy();
    long time = location.getTime();
    String provider = location.getProvider();

    if (mLocationResultListeners != null && !mLocationResultListeners.isEmpty()) {
        String format = "Location result:<%f, %f> Accuracy:%f Time:%d Provider:%s";
        EasyLog.i(String.format(format, latitude, longitude, accuracy, time, provider));

        mLastLocation = location;
        synchronized (this) {
            Iterator<LocationResultListener> iterator =  mLocationResultListeners.iterator();
            while (iterator.hasNext()) {
                LocationResultListener listener = iterator.next();
                if (listener != null) {
                    listener.onResult(location);
                }
                iterator.remove();
            }
        }
    }
}

Implementación completa: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

1.Gracias a las ideas de solución @Gryphius, también comparto el código completo.

2.Cada solicitud para completar la ubicación, es mejor eliminar las actualizaciones, de lo contrario, la barra de estado del teléfono siempre mostrará el ícono de posicionamiento

Bingerz
fuente
0

En mi experiencia, he encontrado que es mejor ir con la corrección de GPS a menos que no esté disponible. No sé mucho sobre otros proveedores de ubicación, pero sé que para GPS hay algunos trucos que se pueden usar para dar un poco de medida de precisión de gueto. La altitud es a menudo una señal, por lo que puede verificar valores ridículos. Existe la medida de precisión en las correcciones de ubicación de Android. Además, si puede ver la cantidad de satélites utilizados, esto también puede indicar la precisión.

Una forma interesante de tener una mejor idea de la precisión podría ser solicitar un conjunto de soluciones muy rápidamente, como ~ 1 / seg durante 10 segundos y luego dormir durante un minuto o dos. Una charla en la que he estado ha llevado a creer que algunos dispositivos Android lo harán de todos modos. Luego eliminaría los valores atípicos (he escuchado que se menciona el filtro de Kalman aquí) y usaría algún tipo de estrategia de centrado para obtener una solución única.

Obviamente, la profundidad a la que llega depende de cuán difíciles sean sus requisitos. Si tiene un requisito particularmente estricto para obtener LA MEJOR ubicación posible, creo que encontrará que el GPS y la ubicación de la red son tan similares como las manzanas y las naranjas. Además, el GPS puede ser muy diferente de un dispositivo a otro.

remolcadores
fuente
Bueno, no es importante que sea lo mejor, solo que sea lo suficientemente bueno para trazar en un mapa y que no agote la batería, ya que esta es una tarea de fondo.
Nicklas A.
-3

Skyhook (http://www.skyhookwireless.com/) tiene un proveedor de ubicación que es mucho más rápido que el estándar que ofrece Google. Puede ser lo que estás buscando. No estoy afiliado a ellos.

Ed Burnette
fuente
Interesante, de hecho, solo parecen usar WiFi, lo cual es muy agradable, pero todavía necesito que funcione cuando no hay wifi o conexión 3G / 2G, por lo que esto agregaría otra capa de abstracción. Buena captura sin embargo.
Nicklas A.
1
Skyhook parece usar una combinación de WiFi, GPS y torres celulares. Visite skyhookwireless.com/howitworks para obtener detalles técnicos. Han obtenido varias victorias de diseño últimamente, por ejemplo, Mapquest, Twydroid, ShopSavvy y Sony NGP. Tenga en cuenta que descargar y probar su SDK parece ser gratuito, pero debe comunicarse con ellos para obtener una licencia para distribuirlo en su aplicación. Lamentablemente no enumeran el precio en su sitio web.
Ed Burnette
Oh ya veo. Bueno, si no es gratis para usar comercialmente, me temo que no puedo usarlo.
Nicklas A.