214 votes

latitude/longitude trouver la latitude/longitude la plus proche - sql complexe ou calcul complexe !

J'ai une latitude et une longitude et je veux extraire l'enregistrement de la base de données, qui a la latitude et la longitude la plus proche par la distance, si cette distance est plus longue que celle spécifiée, alors ne le récupère pas.

Table structure:
id
latitude
longitude
place name
city
country
state
zip
sealevel

1 votes

Il s'agit d'une sorte de duplication de la Recherche de proximité question.

1 votes

Il y a une série de diapositives par Alexander Rubin sur Recherche géo (de proximité) avec MySQL (lien PDF)

0 votes

Méfiez-vous des réponses ici. La plupart d'entre elles ne peuvent pas utiliser d'index et sont donc peu performantes pour les grands ensembles de données. Certaines sont limitées au calcul de la distance non sphérique, et ne sont donc pas utiles dans de nombreuses applications globales.

263voto

Kaletha Points 744
SELECT latitude, longitude, SQRT(
    POW(69.1 * (latitude - [startlat]), 2) +
    POW(69.1 * ([startlng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM TableName HAVING distance < 25 ORDER BY distance;

[starlat] et [démarrage] est la position où l'on commence à mesurer la distance.

58 votes

Pour des raisons de performance, il est préférable de ne pas élever au carré la variable de distance mais plutôt la valeur de test '25'... puis d'élever au carré les résultats qui ont passé si vous avez besoin de montrer la distance.

11 votes

Quelle serait la même requête si la distance était en mètres ? (qui est actuellement en miles, non ?)

1 votes

La lattitude se mesure en degrés, et non en miles ou en mètres. fr.wikipedia.org/wiki/Latitude

128voto

Igor Zevaka Points 32586

Il faut traduire la distance en degrés de longitude et de latitude, filtrer sur la base de ces degrés pour délimiter les entrées qui se trouvent à peu près dans la boîte englobante, puis effectuer un filtrage plus précis de la distance. Voici un excellent document qui explique comment faire tout cela :

http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

33voto

circuitry Points 316

Voici ma solution complète mise en œuvre en PHP.

Cette solution utilise la formule de Haversine telle que présentée dans http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Il convient de noter que la formule Haversine présente des faiblesses autour des pôles. Cette réponse montre comment mettre en œuvre le formule de distance du Grand Cercle de vincenty pour contourner ce problème, mais j'ai choisi de n'utiliser que la Haversine car elle est suffisamment bonne pour mes besoins.

Je stocke la latitude en tant que DECIMAL(10,8) et la longitude en tant que DECIMAL(11,8). J'espère que cela vous aidera !

showClosest.php

<?PHP
/**
 * Use the Haversine Formula to display the 100 closest matches to $origLat, $origLon
 * Only search the MySQL table $tableName for matches within a 10 mile ($dist) radius.
 */
include("./assets/db/db.php"); // Include database connection function
$db = new database(); // Initiate a new MySQL connection
$tableName = "db.table";
$origLat = 42.1365;
$origLon = -71.7559;
$dist = 10; // This is the maximum distance (in miles) away from $origLat, $origLon in which to search
$query = "SELECT name, latitude, longitude, 3956 * 2 * 
          ASIN(SQRT( POWER(SIN(($origLat - abs(latitude))*pi()/180/2),2)
          +COS($origLat*pi()/180 )*COS(abs(latitude)*pi()/180)
          *POWER(SIN(($origLon-longitude)*pi()/180/2),2))) 
          as distance FROM $tableName WHERE 
          longitude between ($origLon-$dist/abs(cos(radians($origLat))*69)) 
          and ($origLon+$dist/abs(cos(radians($origLat))*69)) 
          and latitude between ($origLat-($dist/69)) 
          and ($origLat+($dist/69)) 
          having distance < $dist ORDER BY distance limit 100;"; 
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)) {
    echo $row['name']." > ".$row['distance']."<BR>";
}
mysql_close($db);
?>

./assets/db/db.php

<?PHP
/**
 * Class to initiate a new MySQL connection based on $dbInfo settings found in dbSettings.php
 *
 * @example $db = new database(); // Initiate a new database connection
 * @example mysql_close($db); // close the connection
 */
class database{
    protected $databaseLink;
    function __construct(){
        include "dbSettings.php";
        $this->database = $dbInfo['host'];
        $this->mysql_user = $dbInfo['user'];
        $this->mysql_pass = $dbInfo['pass'];
        $this->openConnection();
        return $this->get_link();
    }
    function openConnection(){
    $this->databaseLink = mysql_connect($this->database, $this->mysql_user, $this->mysql_pass);
    }

    function get_link(){
    return $this->databaseLink;
    }
}
?>

./assets/db/dbSettings.php

<?php
$dbInfo = array(
    'host'      => "localhost",
    'user'      => "root",
    'pass'      => "password"
);
?>

Il est possible d'améliorer les performances en utilisant une procédure stockée MySQL, comme le suggère l'article "Geo-Distance-Search-with-MySQL" publié ci-dessus.

J'ai une base de données de ~17 000 places et le temps d'exécution des requêtes est de 0,054 seconde.

0 votes

Comment puis-je obtenir la distance en Km ou en Mètres ? Salutations !

2 votes

AVERTISSEMENT. Excellente solution, mais il y a un bug. Tous les abs doivent être supprimés. Il n'est pas nécessaire de prendre la valeur abs lors de la conversion des degrés en radians, et, même si vous le faisiez, vous ne le faites que pour une seule des latitudes. Veuillez l'éditer afin de corriger le bug.

1 votes

Et pour tous ceux qui veulent ce compteur : Convertir 3956 miles en kilomètres : Le rayon de la Terre ; Convertissez 69 miles en kilomètres : la longueur approximative d'un degré de latitude en km ; Et entrez la distance en kilomètres.

26voto

Evan Points 1045

Juste au cas où vous seriez paresseux comme moi, voici une solution amalgamée à partir de cette réponse et d'autres réponses sur SO.

set @orig_lat=37.46; 
set @orig_long=-122.25; 
set @bounding_distance=1;

SELECT
*
,((ACOS(SIN(@orig_lat * PI() / 180) * SIN(`lat` * PI() / 180) + COS(@orig_lat * PI() / 180) * COS(`lat` * PI() / 180) * COS((@orig_long - `long`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance` 
FROM `cities` 
WHERE
(
  `lat` BETWEEN (@orig_lat - @bounding_distance) AND (@orig_lat + @bounding_distance)
  AND `long` BETWEEN (@orig_long - @bounding_distance) AND (@orig_long + @bounding_distance)
)
ORDER BY `distance` ASC
limit 25;

1 votes

Que fait exactement bounding_distance Cette valeur limite-t-elle les résultats à une distance d'un kilomètre ? Dans ce cas, les résultats seront renvoyés dans un rayon d'un kilomètre.

1 votes

@ bounding_distance est ici en degrés, et est utilisé pour accélérer les calculs en limitant la région de recherche effective. Par exemple, si vous savez que votre utilisateur se trouve dans une certaine ville, et que vous savez que vous avez quelques points dans cette ville, vous pouvez sans risque définir votre distance limite à quelques degrés.

1 votes

Quelle formule de distance géographique utilise-t-on ?

13voto

Nicholas Points 35

C'est facile ;)

SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;

Il suffit de remplacer les coordonnées par celles que vous souhaitez. Les valeurs doivent être stockées sous forme de double. Ceci est un exemple fonctionnel de MySQL 5.x.

Cheers

2 votes

Aucune idée de pourquoi upvote, OP veut limiter et commander par une certaine distance, pas limiter par 30 et commander par dx+dy

3 votes

Ça l'a fait pour moi. Ce n'est pas ce que l'OP voulait, mais c'est ce que je voulais, alors merci d'avoir répondu ! :)

0 votes

ABS() le plus externe est suffisant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X