SQlite Obteniendo las ubicaciones más cercanas (con latitud y longitud)

Tengo datos con latitud y longitud almacenados en mi base de datos SQLite, y quiero obtener las ubicaciones más cercanas a los parámetros que pongo (por ejemplo, mi ubicación actual – lat / lng, etc).

Sé que esto es posible en MySQL, y he hecho bastante investigación que SQLite necesita una función externa personalizada para la fórmula de Haversine (calcular la distancia en una esfera), pero no he encontrado nada que está escrito en Java y funciona .

Además, si quiero agregar funciones personalizadas, necesito la org.sqlite .jar (para org.sqlite.Function ), y eso agrega un tamaño innecesario a la aplicación.

El otro lado de esto es, necesito el Orden por la función de SQL, porque mostrar la distancia por sí sola no es tanto un problema – Ya lo hice en mi SimpleCursorAdapter personalizado, pero no puedo ordenar los datos, porque yo No tienen la columna de distancia en mi base de datos. Eso significaría actualizar la base de datos cada vez que cambie la ubicación y eso es un desperdicio de batería y rendimiento. Así que si alguien tiene alguna idea en la clasificación del cursor con una columna que no está en la base de datos, yo también estaría agradecido!

Sé que hay toneladas de aplicaciones de Android por ahí que utilizan esta función, pero alguien puede explicar la magia.

Por cierto, encontré esta alternativa: Consulta para obtener registros basados ​​en Radius en SQLite?

Está sugiriendo hacer 4 columnas nuevas para cos y valores de pecado de lat y lng, pero ¿hay alguna otra manera, no tan redundante?

1) En primer lugar filtrar sus datos SQLite con una buena aproximación y disminuir la cantidad de datos que usted necesita para evaluar en su código java. Utilice el siguiente procedimiento para este propósito:

Para tener un umbral determinista y un filtro más preciso en los datos, es mejor calcular 4 ubicaciones que están en el radius del norte, oeste, este y sur de su punto central en su código java y luego comprobar fácilmente por menos de y más Que los operadores de SQL (>, <) para determinar si sus puntos en la base de datos están en ese rectángulo o no.

El método calculateDerivedPosition(...) calcula esos puntos para usted (p1, p2, p3, p4 en la imagen).

Introduzca aquí la descripción de la imagen

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

Y ahora crea tu consulta:

 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) 

COL_X es el nombre de la columna de la base de datos que almacena los valores de latitud y COL_Y es la longitud.

Así que tiene algunos datos que están cerca de su punto central con una buena aproximación.

2) Ahora puedes realizar un bucle en estos datos filtrados y determinar si están realmente cerca de tu punto (en el círculo) o no usando los siguientes métodos:

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

¡Disfrutar!

Utilicé y personalizé esta referencia y la completé.

Sé que esto ha sido contestado y aceptado pero pensé que añadiría mis experiencias y solución.

Mientras que era feliz hacer una función del haversine en el dispositivo para calcular la distancia exacta entre la posición actual del usuario y cualquier localización de blanco particular allí era una necesidad de clasificar y de limitar los resultados de la pregunta en orden de la distancia.

La solución menos que satisfactoria es devolver el lote y ordenar y filtrar después de que el hecho, pero esto daría lugar a un segundo cursor y muchos resultados innecesarios que se devuelven y descartan.

Mi solución preferida era pasar en un orden de clasificación de los valores del cuadrado delta del largo y lats:

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

No hay necesidad de hacer la haversine completa sólo para un orden de clasificación y no hay necesidad de raíz cuadrada los resultados por lo tanto SQLite puede manejar el cálculo.

EDITAR:

Esta respuesta sigue recibiendo amor. Funciona muy bien en la mayoría de los casos, pero si necesita un poco más de precisión, por favor echa un vistazo a la respuesta @Teasel por debajo de lo que agrega un factor "fudge" que corrige las imprecisiones que aumentan a medida que la latitud se acerca a 90.

La respuesta de Chris es realmente útil (gracias!), Pero sólo funcionará si usas coordenadas rectilíneas (por ejemplo, referencias de cuadrícula UTM u OS). Si usa grados para lat / lng (por ejemplo, WGS84) entonces lo anterior sólo funciona en el ecuador. En otras latitudes, es necesario disminuir el impacto de la longitud en el orden de clasificación. (Imagínese que está cerca del polo norte … un grado de latitud sigue siendo el mismo que está en cualquier lugar, pero un grado de longitud puede ser sólo unos pocos pies. Esto significa que el orden es incorrecto).

Si no está en el ecuador, pre-calcule el factor de fudge, basado en su latitud actual:

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

Luego ordene por:

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

Todavía es sólo una aproximación, pero mucho mejor que la primera, por lo que las imprecisiones de orden serán mucho más raras.

¿Ha considerado una etiqueta / índice Geohash para sus entradas para reducir el tamaño de su conjunto de resultados y, a continuación, aplicar la función adecuada.

Otra cuestión de stackoverflow en un área similar: find-the-closer-point-to-a-given-point

Con el fin de aumentar el rendimiento tanto como sea posible, sugiero mejorar la idea de @Chris Simpson con la siguiente cláusula ORDER BY :

 ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM) 

En este caso, debe pasar los siguientes valores del código:

 <L> = center_lat^2 + center_lon^2 <A> = 2 * center_lat <B> = 2 * center_lon 

Y también debe almacenar LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2 como columna adicional en la base de datos. Llénelo insertando sus entidades en la base de datos. Esto mejora ligeramente el rendimiento al extraer gran cantidad de datos.

Echa un vistazo a este post:

Función de distancia para sqlite

Parece que le permite agregar una función de distancia personalizada () a SQLite que podría permitirle evitar saltar a través de todos los aros en las otras respuestas.

Intente algo como esto:

  //locations to calculate difference with Location me = new Location(""); Location dest = new Location(""); //set lat and long of comparison obj me.setLatitude(_mLat); me.setLongitude(_mLong); //init to circumference of the Earth float smallest = 40008000.0f; //m //var to hold id of db element we want Integer id = 0; //step through results while(_myCursor.moveToNext()){ //set lat and long of destination obj dest.setLatitude(_myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LATITUDE))); dest.setLongitude(_myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LONGITUDE))); //grab distance between me and the destination float dist = me.distanceTo(dest); //if this is the smallest dist so far if(dist < smallest){ //store it smallest = dist; //grab it's id id = _myCursor.getInt(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_ID)); } } 

Después de esto, id contiene el elemento que desea de la base de datos para que pueda buscarlo:

  //now we have traversed all the data, fetch the id of the closest event to us _myCursor = _myDBHelper.fetchID(id); _myCursor.moveToFirst(); //get lat and long of nearest location to user, used to push out to map view _mLatNearest = _myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LATITUDE)); _mLongNearest = _myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LONGITUDE)); 

¡Espero que ayude!

  • Tablas de muestra Android sqlite
  • Cómo comparar representaciones de cadena de dobles si termina en .00
  • Problema de orden de consultas SQLite de Android
  • No puedo crear una base de datos SQLite en mi aplicación android
  • `RenamingDelegatingContext` está obsoleto. ¿Cómo probamos SQLite db ahora?
  • SimpleCursorAdapter de Android con consultas utilizando DISTINCT
  • Android.database.sqlite.SQLiteException: cerca de "SELECT": error de sintaxis (código 1):, al compilar: SELECT * FROM Table_Name
  • Leer 3 elementos seleccionados al mismo tiempo
  • Conexión de SQLite se filtró aunque todo se cerró
  • Android / SQLite - Operación de bits en la cláusula WHERE
  • BroadcastReceiver para SMS de varias partes
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.