90 votes

SQlite Obtenir les emplacements les plus proches (avec latitude et longitude)

J'ai des données avec la latitude et la longitude stockées dans ma base de données SQLite, et je veux être le plus proche des endroits les paramètres que j'ai mis dans (ex. Ma position actuelle - lat/lng, etc.).

Je sais que c'est possible dans MySQL, et j'ai fait quelques recherches que SQLite a besoin d'un custom fonction externe pour la Haversine formule (calcul de la distance sur une sphère), mais je n'ai pas trouvé tout ce qui est écrit en Java et fonctionne.

Aussi, si je veux ajouter des fonctions personnalisées, j'ai besoin de l' org.sqlite .jar (pour org.sqlite.Function), et qui alourdit inutilement la taille de l'application.

De l'autre côté de cela est, j'ai besoin de la Commande en fonction de SQL, parce que l'affichage de la distance n'est pas un problème - je l'ai déjà fait dans mon custom SimpleCursorAdapter, mais je ne peux pas trier les données, parce que je n'ai pas la distance de la colonne dans ma base de données. Que signifierait la mise à jour de la base de données à chaque fois que les modifications de l'emplacement et c'est un gaspillage de la batterie et de la performance. Donc, si quelqu'un a une idée sur le tri le curseur avec une colonne qui n'est pas dans la base de données, je lui en serais reconnaissant!

Je sais qu'il y a des tonnes de Android applications qui utilisent cette fonction, mais quelqu'un peut-il expliquer la magie.

Par ailleurs, j'ai trouvé cette alternative: Requête pour obtenir les enregistrements en fonction du Rayon de SQLite?

Il suggère de faire 4 nouvelles colonnes pour les cos et sin valeurs de la lat et de gnl, mais est-il des autres, pas tellement redondant?

115voto

breceivemail Points 8024

1) Au début de filtrer vos données SQLite avec une bonne approximation et de la diminution de la quantité de données que vous devez évaluer dans votre code java. Utilisez la procédure suivante pour cette fin:

Pour avoir un déterministe seuil et plus précis filtre sur les données, Il est préférable de calculer les 4 emplacements qui sont en radius compteur du nord, de l'ouest à l'est et au sud de votre point central dans votre code java et ensuite vérifier facilement en moins et plus que les opérateurs SQL (>, <) afin de déterminer si vos points dans la base de données sont dans un rectangle ou non.

La méthode calculateDerivedPosition(...) calcule ces points (p1, p2, p3, p4 dans l'image).

enter image description here

/**
* Calculates the end-point from a given source at a given range (meters)
* and bearing (degrees). This methods uses simple geometry equations to
* calculate the end-point.
* 
* @param point
*            Point of origin
* @param range
*            Range in meters
* @param bearing
*            Bearing in degrees
* @return End-point from the source given the desired range and bearing.
*/
public static PointF calculateDerivedPosition(PointF point,
            double range, double bearing)
    {
        double EarthRadius = 6371000; // m

        double latA = Math.toRadians(point.x);
        double lonA = Math.toRadians(point.y);
        double angularDistance = range / EarthRadius;
        double trueCourse = Math.toRadians(bearing);

        double lat = Math.asin(
                Math.sin(latA) * Math.cos(angularDistance) +
                        Math.cos(latA) * Math.sin(angularDistance)
                        * Math.cos(trueCourse));

        double dlon = Math.atan2(
                Math.sin(trueCourse) * Math.sin(angularDistance)
                        * Math.cos(latA),
                Math.cos(angularDistance) - Math.sin(latA) * Math.sin(lat));

        double lon = ((lonA + dlon + Math.PI) % (Math.PI * 2)) - Math.PI;

        lat = Math.toDegrees(lat);
        lon = Math.toDegrees(lon);

        PointF newPoint = new PointF((float) lat, (float) lon);

        return newPoint;

    }

Et maintenant créer votre requête:

PointF center = new PointF(x, y);
final double mult = 1; // mult = 1.1; is more reliable
PointF p1 = calculateDerivedPosition(center, mult * radius, 0);
PointF p2 = calculateDerivedPosition(center, mult * radius, 90);
PointF p3 = calculateDerivedPosition(center, mult * radius, 180);
PointF p4 = calculateDerivedPosition(center, mult * radius, 270);

strWhere =  " WHERE "
        + COL_X + " > " + String.valueOf(p3.x) + " AND "
        + COL_X + " < " + String.valueOf(p1.x) + " AND "
        + COL_Y + " < " + String.valueOf(p2.y) + " AND "
        + COL_Y + " > " + String.valueOf(p4.y)

Si vous avez des données qui sont à proximité de votre point central avec une bonne approximation.

2) Maintenant, vous pouvez faire une boucle sur ces données filtrées et de déterminer si elles sont vraiment à proximité de votre lieu (dans le cercle) ou non en utilisant les méthodes suivantes:

public static boolean pointIsInCircle(PointF pointForCheck, PointF center,
            double radius) {
        if (getDistanceBetweenTwoPoints(pointForCheck, center) <= radius)
            return true;
        else
            return false;
    }

public static double getDistanceBetweenTwoPoints(PointF p1, PointF p2) {
        double R = 6371000; // m
        double dLat = Math.toRadians(p2.x - p1.x);
        double dLon = Math.toRadians(p2.y - p1.y);
        double lat1 = Math.toRadians(p1.x);
        double lat2 = Math.toRadians(p2.x);

        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2)
                * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double d = R * c;

        return d;
    }

Profitez-en!

J'ai utilisé et personnalisé cette référence et l'a achevé.

72voto

Teasel Points 526

Chris réponse est vraiment utile (merci!), mais ne fonctionnera que si vous êtes à l'aide de coordonnées rectilignes (par exemple UTM ou OS de la grille de références). Si à l'aide de degrés de lat/lng (par exemple WGS84) puis le ci-dessus ne fonctionne qu'à l'équateur. À d'autres latitudes, vous avez besoin de diminuer l'impact de longitude sur l'ordre de tri. (Imaginez que vous êtes à proximité du pôle nord... un degré de latitude est toujours la même c'est n'importe où, mais d'un degré de longitude est peut-être seulement un peu les pieds. Cela signifie que l'ordre de tri est incorrect).

Si vous n'êtes pas à l'équateur, pré-calculer le fudge factor, en fonction de votre latitude:

<fudge> = Math.pow(Math.cos(Math.toRadians(<lat>)),2);

Ensuite, la commande par:

((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)

C'est encore qu'une approximation, mais beaucoup mieux que la première, donc de l'ordre de tri des inexactitudes sera beaucoup plus rares.

71voto

Chris Simpson Points 3894

Je sais que cela a été répondu et accepté, mais j'ai pensé ajouter mon expérience et de leur solution.

Alors que j'ai été heureux de faire un haversine de la fonction sur l'appareil pour calculer la distance exacte entre l'utilisateur de la position actuelle et les objectifs endroit, il y avait un besoin de trier et de limiter les résultats de la requête dans l'ordre de la distance.

Le moins que satisfaisante solution est de retourner le beaucoup et, de tri et de filtre après le fait, mais il en résulterait un deuxième curseur et bien des résultats retournés et jetés.

Mon préféré la solution a été de passer dans un ordre de tri des carrés des valeurs delta de la longue et lats letton:

((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) +
 (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))

Il n'y a pas besoin de faire le plein haversine juste pour un ordre de tri et il n'y a pas besoin de racine carrée par conséquent, les résultats SQLite peut gérer le calcul.

2voto

Morrison Chang Points 4047

Avez-vous envisagé d' utiliser un tag / index Geohash pour vos entrées afin de réduire la taille de votre jeu de résultats, puis d'appliquer la fonction appropriée?

Une autre question de stackoverflow dans un domaine similaire: trouver le point le plus proche d'un point donné

-2voto

NickG Points 1259

Regardez ce post:

Fonction de distance pour sqlite

Cela semble vous permettre d'ajouter une fonction Distance () personnalisée à SQLite, ce qui vous évitera peut-être de passer à travers toutes les étapes des autres réponses.

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