2010-06-27 2 views
26

У меня есть этот запрос, который делает работу отлично в MySQLЗапрос на получение записей на основе Radius в SQLite?

SELECT ((ACOS(SIN(12.345 * PI()/180) * SIN(lat * PI()/180) + 
     COS(12.345 * PI()/180) * COS(lat * PI()/180) * COS((67.89 - lon) * 
     PI()/180)) * 180/PI()) * 60 * 1.1515 * 1.609344) AS distance, poi.* 
FROM poi 
WHERE lang='eng' 
HAVING distance<='30' 

расстояние в километрах, вход lat=12.345 и lon=67.89

SQLite 3, и я не могу запустить пользовательские функции с ним, как это на Android. У меня также нет acos() и т. Д., Поскольку это не является частью стандартного SQLite.

Как будет выглядеть запрос SQLite?

+1

У вас есть два варианта расчета, за пределами базы данных или проектируйте координаты на эллипсоиде, которые доставят вам счетчики и позволят использовать формулу правильного расстояния в качестве приближения. –

+0

Я открыт для этого решения, можете ли вы подробно рассказать об этом в ответ. – Pentium10

+2

Для начинающих, таких как я, вся формула называется «Сферический закон косинусов» и описывается здесь: http://www.movable-type.co.uk/scripts/latlong.html –

ответ

14

Вы можете создать 4 новых столбца, являющихся sin и cos lat и lon. Начиная с cos(a+b) = cos a cos b - sin a sin b и других появлений sin и cos, таких как SIN(12.345 * PI()/180), можно вычислить в программе перед запуском запроса, большое выражение «расстояние» сводится к чему-то вроде формы P * SIN_LAT + Q * COS_LAT + ..., которую может обрабатывать SQLite3.

BTW, см. Также Sqlite on Android: How to create a sqlite dist db function - to be used in the app for distance calculation using lat, long.

+0

На самом деле, я довольно скептически отношусь к подходу, указанному в ответе на контент, на который вы ссылаетесь. 'org.sqlite' не находится в Android, и неясно, существует ли реализация' org.sqlite', которая будет работать совместно с средой Android SQLite. – Pentium10

+0

Ваш метод для добавления некоторых столбцов звучит. – Pentium10

+0

Итак, как вы обрабатываете 'ACOS()'? создание пользовательской функции SQL с помощью 'sqlite3_create_function()'? Как вы это делаете в Android? –

28

Вот реализация на Java для создания запроса на основе местоположения на устройстве Android. Идея исходит от KennyTM (см. Принятый ответ) и подразумевает добавление 4 столбцов в таблице для хранения значений синуса и косинуса широты и долготы.

Вот код подготовки данных для таблицы «Магазин» во время вставки:

public static void injectLocationValues(ContentValues values, double latitude, double longitude) { 
    values.put(LocationColumns.LATITUDE, latitude); 
    values.put(LocationColumns.LONGITUDE, longitude); 
    values.put(LocationColumns.COSLAT, Math.cos(MathUtil.deg2rad(latitude))); 
    values.put(LocationColumns.SINLAT, Math.sin(MathUtil.deg2rad(latitude))); 
    values.put(LocationColumns.COSLNG, Math.cos(MathUtil.deg2rad(longitude))); 
    values.put(LocationColumns.SINLNG, Math.sin(MathUtil.deg2rad(longitude))); 
} 

public static double deg2rad(double deg) { 
    return (deg * Math.PI/180.0); 
} 

Вы можете создать свой прогноз с помощью следующей функции:

/** 
* Build query based on distance using spherical law of cosinus 
* 
* d = acos(sin(lat1).sin(lat2)+cos(lat1).cos(lat2).cos(long2−long1)).R 
* where R=6371 and latitudes and longitudes expressed in radians 
* 
* In Sqlite we do not have access to acos() sin() and lat() functions. 
* Knowing that cos(A-B) = cos(A).cos(B) + sin(A).sin(B) 
* We can determine a distance stub as: 
* d = sin(lat1).sin(lat2)+cos(lat1).cos(lat2).(cos(long2).cos(long1)+sin(long2).sin(long1)) 
* 
* First comparison point being fixed, sin(lat1) cos(lat1) sin(long1) and cos(long1) 
* can be replaced by constants. 
* 
* Location aware table must therefore have the following columns to build the equation: 
* sinlat => sin(radians(lat)) 
* coslat => cos(radians(lat)) 
* coslng => cos(radians(lng)) 
* sinlng => sin(radians(lng)) 
* 
* Function will return a real between -1 and 1 which can be used to order the query. 
* Distance in km is after expressed from R.acos(result) 
* 
* @param latitude, latitude of search 
* @param longitude, longitude of search 
* @return selection query to compute the distance 
*/ 
public static String buildDistanceQuery(double latitude, double longitude) { 
    final double coslat = Math.cos(MathUtil.deg2rad(latitude)); 
    final double sinlat = Math.sin(MathUtil.deg2rad(latitude)); 
    final double coslng = Math.cos(MathUtil.deg2rad(longitude)); 
    final double sinlng = Math.sin(MathUtil.deg2rad(longitude)); 
    //@formatter:off 
    return "(" + coslat + "*" + LocationColumns.COSLAT 
      + "*(" + LocationColumns.COSLNG + "*" + coslng 
      + "+" + LocationColumns.SINLNG + "*" + sinlng 
      + ")+" + sinlat + "*" + LocationColumns.SINLAT 
      + ")"; 
    //@formatter:on 
} 

Это впрыснуть ответную колонку с расстоянием, на котором вам нужно применить следующую формулу для преобразования в километрах:

public static double convertPartialDistanceToKm(double result) { 
    return Math.acos(result) * 6371; 
} 

Если вы хотите заказать свой запрос, используя частичное расстояние, вам необходимо заказать DESC, а не ASC.

+0

Обратите внимание, что этот подход считывает все местоположения с диска и выполняет расчет для каждого (даже если вы ограничиваете радиус!). Он также несовместим с индексами (всегда выполняет полное сканирование таблицы). Запрос может быть значительно ускорен путем ограничения границ, используя метод, описанный в [этот вопрос] (http://stackoverflow.com/q/3695224/1643723), и фильтрацию/упорядочение результатов с помощью подзапроса с использованием вышеописанного метода. И вы можете использовать индекс с этим первым запросом, в отличие от этого! – user1643723

9

Если бы тот же вопрос, работая на sqlite3 для ИОС, поиграв с формулой здесь является способ сделать это без использования функции из SQL стороне (псевдо-код):

  1. Pre Высчитайте эти значения для каждого элемента хранится в базе данных (и хранить их):

    cos_lat = cos(lat * PI/180) 
    sin_lat = sin(lat * PI/180) 
    cos_lng = cos(lng * PI/180) 
    sin_lng = sin(lng * PI/180) 
    
  2. Предварительно рассчитать эти значения во время поиска (для данной позиции cur_lat, cur_lng)

    CUR_cos_lat = cos(cur_lat * PI/180) 
    CUR_sin_lat = sin(cur_lat * PI/180) 
    CUR_cos_lng = cos(cur_lng * PI/180) 
    CUR_sin_lng = sin(cur_lng * PI/180) 
    cos_allowed_distance = cos(2.0/6371) # This is 2km 
    
  3. Ваш SQL-запрос будет выглядеть следующим образом (заменить CUR_ * значениями вы только вычисленных)

    SELECT * FROM положение WHERE CUR_sin_lat * sin_lat + CUR_cos_lat * cos_lat * (cos_lng * CUR_cos_lng + sin_lng * CUR_sin_lng)> cos_allowed_distance;

+1

Мне нравится этот подход, но правильное уравнение? Большинство уравнений, которые я видел, имеют acos(). Твой нет. Я что-то упускаю? – Sam

+1

Я предполагаю, что вместо '' acos (левая часть) по сравнению с 2'' они используют '' левую часть, сравнимую с cos (2) '', которая должна быть эквивалентной. – Slawa

Смежные вопросы