У меня есть данные с широтой и долготой, хранящиеся в моей базе данных SQLite, и я хочу получить ближайшие местоположения по параметрам, которые я ввел (например, мое текущее местоположение - широта / долгота и т. Д.).
Я знаю, что это возможно в MySQL, и я провел довольно много исследований о том, что SQLite нужна специальная внешняя функция для формулы Хаверсина (вычисление расстояния на сфере), но я не нашел ничего, что написано на Java и работает .
Кроме того, если я хочу добавить пользовательские функции, мне нужен org.sqlite
файл .jar (for org.sqlite.Function
), и это добавляет приложению ненужный размер.
С другой стороны, мне нужна функция Order by из SQL, потому что отображение расстояния - не такая уж большая проблема - я уже сделал это в моем пользовательском SimpleCursorAdapter, но я не могу отсортировать данные, потому что я в моей базе данных нет столбца расстояния. Это будет означать обновление базы данных каждый раз при изменении местоположения, а это потеря заряда аккумулятора и производительности. Так что если у кого-то есть идея отсортировать курсор по столбцу, которого нет в базе данных, я тоже был бы благодарен!
Я знаю, что существует множество приложений для Android, которые используют эту функцию, но кто-нибудь может объяснить волшебство.
Кстати, я нашел такую альтернативу: запрос на получение записей на основе радиуса в SQLite?
Предлагается создать 4 новых столбца для значений cos и sin для lat и lng, но есть ли другой, не такой избыточный способ?
Ответы:
1) Сначала отфильтруйте данные SQLite с хорошим приближением и уменьшите объем данных, которые вам нужно оценить в своем java-коде. Для этого используйте следующую процедуру:
Чтобы иметь детерминированный порог и более точный фильтр данных, лучше рассчитать 4 местоположения, которые находятся в
radius
метрах от севера, запада, востока и юга от вашей центральной точки в вашем Java-коде, а затем легко проверить меньше или больше чем Операторы SQL (>, <), чтобы определить, находятся ли ваши точки в базе данных в этом прямоугольнике или нет.Этот метод
calculateDerivedPosition(...)
рассчитывает эти баллы за вас (p1, p2, p3, p4 на картинке)./** * 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; }
А теперь создайте свой запрос:
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
- это имя столбца в базе данных, в котором хранятся значения широты иCOL_Y
долготы.Итак, у вас есть данные, которые находятся рядом с вашей центральной точкой с хорошим приближением.
2) Теперь вы можете перебирать эти отфильтрованные данные и определять, действительно ли они находятся рядом с вашей точкой (в круге) или нет, используя следующие методы:
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; }
Наслаждайтесь!
Я использовал и настроил эту ссылку и завершил ее.
источник
Ответ Криса действительно полезен (спасибо!), Но будет работать, только если вы используете прямолинейные координаты (например, ссылки на сетку UTM или ОС). Если для широты / долготы используются градусы (например, WGS84), то вышеуказанное работает только на экваторе. На других широтах вам нужно уменьшить влияние долготы на порядок сортировки. (Представьте, что вы находитесь близко к северному полюсу ... градус широты остается таким же, как и везде, но градус долготы может составлять всего несколько футов. Это будет означать, что порядок сортировки неверен).
Если вы не на экваторе, предварительно вычислите коэффициент выдумки, исходя из вашей текущей широты:
<fudge> = Math.pow(Math.cos(Math.toRadians(<lat>)),2);
Тогда заказывайте по:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
Это все еще только приближение, но намного лучше, чем первое, поэтому неточности порядка сортировки будут намного реже.
источник
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
быть меньшеdistance
илиdistance^2
?Я знаю, что на это ответили и приняли, но я подумал, что добавлю свой опыт и решение.
Хотя я был счастлив использовать функцию гаверсинуса на устройстве для расчета точного расстояния между текущим положением пользователя и любым конкретным целевым местоположением, возникла необходимость в сортировке и ограничении результатов запроса в порядке расстояния.
Менее чем удовлетворительное решение - вернуть партию, отсортировать и отфильтровать постфактум, но это приведет к тому, что второй курсор и многие ненужные результаты будут возвращены и отброшены.
Мое предпочтительное решение заключалось в передаче в порядке сортировки квадратов значений дельты long и lats:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))
Нет необходимости выполнять полный хаверсинус только для порядка сортировки, и нет необходимости извлекать квадратный корень из результатов, поэтому SQLite может обрабатывать вычисления.
РЕДАКТИРОВАТЬ:
Этот ответ до сих пор вызывает любовь. В большинстве случаев он работает нормально, но если вам нужно немного больше точности, ознакомьтесь с ответом @Teasel ниже, в котором добавлен коэффициент «выдумки», исправляющий неточности, которые увеличиваются по мере приближения широты к 90.
источник
cos(latitude)
чтобы широта и долгота были примерно равны. См en.wikipedia.org/wiki/...Чтобы максимально повысить производительность, я предлагаю улучшить идею @Chris Simpson следующим
ORDER BY
предложением:ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM)
В этом случае вы должны передать из кода следующие значения:
<L> = center_lat^2 + center_lon^2 <A> = 2 * center_lat <B> = 2 * center_lon
И вы также должны сохранить
LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2
как дополнительный столбец в базе данных. Заполните его, вставив свои объекты в базу данных. Это немного повышает производительность при извлечении большого количества данных.источник
Попробуйте что-то вроде этого:
//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)); } }
После этого id будет содержать элемент, который вы хотите получить из базы данных, чтобы вы могли его получить:
//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));
Надеюсь, это поможет!
источник