212 votes

Bon moyen d’obtenir l’utilisateur ' emplacement dans Android

Le problème:

L'obtention de l'utilisateur de l'emplacement actuel à l'intérieur d'un seuil le plus tôt possible et en même temps économiser la batterie.

Pourquoi le problème est un problème:

Tout d'abord, android a deux fournisseurs de réseau et GPS. Parfois, le réseau est le meilleur et parfois le GPS est mieux.

Par "mieux", je veux dire de la vitesse par rapport à l'exactitude ratio.
Je suis prêt à sacrifier quelques mètres en précision si je peux obtenir l'emplacement de presque instantané et sans avoir à allumer le GPS.

Deuxièmement, si vous demander les mises à jour pour les modifications de l'emplacement, rien n'est envoyé si la position actuelle est stable.

Google est un exemple de déterminer le "meilleur" emplacement ici: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Mais je pense que c'est pas loin d'être aussi bon qu'il devrait ou pourrait être.

Je suis un peu confus pourquoi google n'a pas normalisée de l'API pour l'emplacement, le développeur ne devrait pas avoir à faire de l'endroit où l'emplacement est à partir, vous devez simplement spécifier ce que vous voulez et le téléphone devrait choisir pour vous.

Ce que j'ai besoin d'aide:

J'ai besoin de trouver une bonne façon de déterminer le "meilleur" emplacement, peut-être bien que certains heuristique ou peut-être grâce à quelques la 3e partie de la bibliothèque.

Cela ne signifie pas déterminer le meilleur fournisseur!
Je suis probablement va utiliser tous les fournisseurs et choisir le meilleur d'entre eux.

Arrière-plan de l'application:

L'application permettra de recueillir la position de l'utilisateur à un intervalle fixe (disons toutes les 10 minutes) et de l'envoyer à un serveur.
L'application doit conserver autant de batterie que possible et l'emplacement doit avoir X (50-100?) mètres de précision.

L'objectif est pour plus tard être en mesure de tracer le chemin d'accès de l'utilisateur au cours de la journée, sur une carte, donc j'ai besoin d'une précision suffisante pour que.

Divers:

Que pensez-vous des valeurs raisonnables souhaité et accepté des précisions?
J'ai été en utilisant 100m comme acceptée et 30m en tant désiré, est-ce trop demander?
J'aimerais être capable de tracer le chemin d'accès de l'utilisateur sur une carte plus tard.
Est à 100m de désiré et 500 m pour le mieux accepté?

Aussi, en ce moment j'ai le GPS sur pour un maximum de 60 secondes par emplacement de mise à jour, est-ce trop courte pour obtenir un emplacement, si vous êtes à l'intérieur avec une précision de peut-être 200 m?


C'est mon code actuel, tout commentaire est apprécié (à l'exception de l'absence de vérification d'erreur qui est à faire):

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);
}

167voto

Gryphius Points 13111

On dirait que nous sommes codage de la même application ;-)
Voici mon actuel de mise en œuvre. Je suis encore dans la phase de beta-test de mon GPS uploader application, donc il pourrait y avoir beaucoup d'améliorations possibles. mais il semble fonctionner assez bien pour l'instant.

/**
 * 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;
}

Edit: voici la partie qui demande les mises à jour périodiques de l'emplacement des fournisseurs:

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
}

Les choses à considérer:

  • ne demande pas de GPS mises à jour trop souvent, elle draine l'énergie de la batterie. J'ai actuellement l'utilisation de 30 minutes par défaut pour mon application.

  • ajoutez une distance minimale par rapport à la dernière position connue'. sans cela, vos points va "sauter partout" lorsque le GPS n'est pas disponible et l'emplacement est triangulé de la tours de téléphonie cellulaire. ou vous pouvez vérifier si le nouvel emplacement est à l'extérieur de l'exactitude valeur à partir de la dernière position connue.

33voto

Stéphane Points 3629

Choisir le bon fournisseur d'emplacement pour votre application, vous pouvez utiliser les Critères des objets:

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); 

Lisez la documentation pour requestLocationUpdates pour plus de détails sur la façon dont les arguments sont pris en compte:

La fréquence de notification peut être contrôlé à l'aide de la minTime et minDistance paramètres. Si minTime est supérieure à 0, la LocationManager pourrait potentiellement reste pour minTime millisecondes entre l'emplacement des mises à jour pour économiser l'énergie. Si minDistance est supérieur à 0, un emplacement ne être diffusé que si l'appareil se déplace par minDistance mètres. Pour obtenir les notifications aussi fréquemment que possible, de définir les paramètres à 0.

Plus de pensées

  • Vous pouvez contrôler l'exactitude de l'Emplacement des objets avec de l'Emplacement.getAccuracy(), qui retourne la précision estimée de la position en mètres.
  • l' Criteria.ACCURACY_HIGH critère devrait vous donner des erreurs dessous de 100m, ce qui n'est pas aussi bon que le GPS peut être, mais qui correspond à vos besoins.
  • Vous devez également surveiller l'état de votre fournisseur d'emplacement, et de passer à un autre fournisseur si elle devient indisponible ou désactivée par l'utilisateur.
  • Le passif fournisseur peut également être un bon match pour ce type d'application: l'idée est d'utiliser l'emplacement des mises à jour à chaque fois qu'ils sont demandés par une autre application et à la diffusion à l'échelle du système.

10voto

Aleadam Points 25433

Répondre à la première de deux points:

  • GPS sera toujours vous donner un endroit plus précis, s'il est activé et si il n'y a pas l'épaisseur des murs autour.

  • Si l'emplacement n'a pas changé, vous pouvez appeler getLastKnownLocation(Chaîne de caractères) et de récupérer l'emplacement immédiatement.

En utilisant une approche alternative:

Vous pouvez essayer d'obtenir l' id de cellule ou de l'utilisation de toutes les cellules voisines

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()));
}

Vous pouvez vous référer ensuite à l'emplacement des cellules par le biais de plusieurs bases de données ouvertes (p. ex., http://www.location-api.com/ ou http://opencellid.org/ )


La stratégie serait de lire la liste de tour Id lors de la lecture de l'emplacement. Puis, dans la prochaine requête (10 minutes dans votre application), les lire à nouveau. Si au moins certains tours sont les mêmes, alors il est sûr à utiliser, getLastKnownLocation(String). Si ils ne le sont pas, alors attendez onLocationChanged(). Cela évite la nécessité d'une base de données de tiers pour l'emplacement. Vous pouvez également essayer cette approche.

9voto

Nicklas A. Points 2979

Il s’agit de ma solution qui fonctionne assez bien :

7voto

Muzikant Points 2073

Précision de la localisation dépend principalement de l'emplacement fournisseur de:

  1. GPS, vous obtiendrez plusieurs mètres de précision (en supposant que vous avez une réception GPS)
  2. Wifi vous obtenez à quelques centaines de mètres de la précision
  3. Réseau de cellules - vous obtiendrez très des résultats inexacts (j'ai vu jusqu'à 4 km d'écart...)

Si c'est la précision que vous recherchez, puis le GPS est votre seule option.

J'ai lu un article très instructif à ce sujet ici.

Comme pour le GPS délai de 60 secondes devrait suffire, et dans la plupart des cas, même trop. Je pense que 30 secondes est OK et parfois même en moins de 5 sec...

si vous avez seulement besoin d'un seul emplacement, je proposerais que dans votre onLocationChanged méthode, une fois que vous recevez une mise à jour, vous pourrez annuler l'inscription de l'auditeur, et d'éviter l'utilisation du GPS.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X