Расчет расстояния большого круга с помощью SQLite - PullRequest
7 голосов
/ 18 января 2010

Вот моя проблема, у меня есть таблица SQLite с местоположениями и широтой / долготой. В основном мне нужно:

SELECT location, HAVERSINE(lat, lon) AS distance FROM location ORDER BY distance ASC;

HAVERSINE() - это PHP-функция , которая должна возвращать Расстояние по большому кругу (в милях или км) с учетом пары значений широты и долготы , Одна из этих пар должна быть предоставлена ​​PHP, а другая пара должна предоставляться каждой строкой широты / долготы , доступной в таблице locations.

Поскольку SQLite не имеет расширения Geo Spatial ( AFAIK SpatiaLite существует, но все же ... ) Я предполагаю, что лучшим подходом будет использовать пользовательскую функцию с любым из методов PDO:

Я думаю, что для этого случая PDO::sqliteCreateFunction() будет достаточно, однако мой ограниченный опыт использования этой функции может быть сведен к случаям использования, аналогичным приведенному в руководстве по PHP:

$db = new PDO('sqlite:geo.db');

function md5_and_reverse($string) { return strrev(md5($string)); }

$db->sqliteCreateFunction('md5rev', 'md5_and_reverse', 1);
$rows = $db->query('SELECT md5rev(filename) FROM files')->fetchAll();

У меня возникли проблемы с выяснением, как я могу получить пользовательскую функцию SQLite для одновременной обработки данных из PHP и данных таблиц , и я был бы признателен, если бы кто-то мог помочь мне решить эту проблему в то же время немного лучше понимая UDF-функции SQLite (большой выигрыш в SQLite IMO).

Заранее спасибо!

Ответы [ 3 ]

9 голосов
/ 18 января 2010

До сих пор я мог думать только об этом решении:

$db = new PDO('sqlite:geo.db');

$db->sqliteCreateFunction('ACOS', 'acos', 1);
$db->sqliteCreateFunction('COS', 'cos', 1);
$db->sqliteCreateFunction('RADIANS', 'deg2rad', 1);
$db->sqliteCreateFunction('SIN', 'sin', 1);

И затем выполните следующий длинный запрос:

SELECT "location",
       (6371 * ACOS(COS(RADIANS($latitude)) * COS(RADIANS("latitude")) * COS(RADIANS("longitude") - RADIANS($longitude)) + SIN(RADIANS($latitude)) * SIN(RADIANS("latitude")))) AS "distance"
FROM "locations"
HAVING "distance" < $distance
ORDER BY "distance" ASC
LIMIT 10;

Если кто-нибудь может придумать лучшее решение, пожалуйста, дайте мне знать.


Я просто нашел эту интересную ссылку , завтра попробую.

3 голосов
/ 16 апреля 2013

Из вашей "интересной ссылки".

function sqlite3_distance_func($lat1,$lon1,$lat2,$lon2) {
    // convert lat1 and lat2 into radians now, to avoid doing it twice below
    $lat1rad = deg2rad($lat1);
    $lat2rad = deg2rad($lat2);
    // apply the spherical law of cosines to our latitudes and longitudes, and set the result appropriately
    // 6378.1 is the approximate radius of the earth in kilometres
    return acos( sin($lat1rad) * sin($lat2rad) + cos($lat1rad) * cos($lat2rad) * cos( deg2rad($lon2) - deg2rad($lon1) ) ) * 6378.1;
}

$db->sqliteCreateFunction('DISTANCE', 'sqlite3_distance_func', 4);

Затем выполните запрос с:

"SELECT * FROM location ORDER BY distance(latitude,longitude,{$lat},{$lon}) LIMIT 1"

РЕДАКТИРОВАТЬ (по QOP): Мне наконец-то понадобилось это снова, и это решение отлично сработало, я просто закончил тем, что немного изменил код, чтобы он стал немного более подробным и изящно обрабатывал нечисловые значения вот оно:

$db->sqliteCreateFunction('distance', function () {
    if (count($geo = array_map('deg2rad', array_filter(func_get_args(), 'is_numeric'))) == 4) {
        return round(acos(sin($geo[0]) * sin($geo[2]) + cos($geo[0]) * cos($geo[2]) * cos($geo[1] - $geo[3])) * 6378.14, 3);
    }

    return null;
}, 4);
0 голосов
/ 18 января 2010

Построение ответа Аликс ...

$db->sqliteCreateFunction('HAVERSINE', 'haversine', 2);

Я бы предположил, что это позволит работать запросу, который вы указали в своем вопросе.

...