2010-01-17 26 views
6

Voici mon problème, j'ai une table SQLite avec les emplacements et les latitudes/longitudes. Fondamentalement, je dois:Calcul de la distance du grand cercle avec SQLite

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

HAVERSINE() est une fonction PHP qui doit retourner le Great-Circle Distance (en miles ou km), étant donné une paire de valeurs de latitude et de longitude. L'une de ces paires doit être fournie par PHP et l'autre paire doit être fournie par chaque ligne de latitude/longitude disponible dans le tableau locations.

Depuis SQLite ne possède aucune extension spatiale Géo ( AFAIKSpatiaLite existe mais quand même ...) Je devine que la meilleure approche serait d'utiliser une fonction personnalisée avec l'une des méthodes AOP:

Je pense que pour ce cas PDO::sqliteCreateFunction() serait suffisant, mais mon expérience limitée avec cette fonction peut être réduite à des cas d'utilisation similaire à celui fourni dans le Manuel 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(); 

J'ai du mal à comprendre comment puis-je obtenir un utilisateur SQLite fonction définie à traiter les données de PHP et les données de table en même temps et j'apprécierais si quelqu'un pourrait m'aider à résoudre ce problème tout en comprenant SQLite UDFs (une grande victoire de SQLite IMO) un peu mieux.

Merci d'avance!

+1

Avez-vous avoir un moyen qui fonctionnera sans définir les fonctions personnalisées? Voir http://stackoverflow.com/questions/3126830/query-to-get-records-based-on-radius-in-sqlite – Pentium10

+0

@ Pentium10: Non, pas un rapide au moins. Vérifiez mon autre question: http://stackoverflow.com/questions/2096385/formulas-to-calculate-geo-proximity. –

Répondre

2

De votre "lien intéressant".

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

Effectuez ensuite une requête avec:

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

EDIT (par QOP): j'ai finalement besoin de ce nouveau et cette solution est très bien passé, je viens fini par modifier le code un peu à elle est un peu moins verbeux et gère les valeurs non numériques avec élégance, ici il est:

$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

Est-ce que cela fonctionne autour de l'équateur ou Greenwich? –

+0

Est-ce que cette fonction retourne en miles ou en kilomètres? –

9

Jusqu'à présent, je ne pouvais penser à cette solution:

$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); 

Et puis exécutez la requête suivante longue:

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; 

Si quelqu'un peut penser à une meilleure solution s'il vous plaît laissez-moi savoir.


Je viens found this interesting link, je vais essayer demain.

0

bâtiment au large de la réponse d'Alix ...

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

Je suppose que cela permettrait à la requête que vous avez spécifié dans votre question au travail.

+0

Je pourrais utiliser 'HAVERSINE (" latitude "," longitude ")' mais je n'ai aucune idée de la façon de travailler avec les variables PHP '$ latitude' et '$ longitude'. –