J'ai un script de travail PHP qui obtient les valeurs de longitude et de latitude puis les introduit dans une requête MySQL. J'aimerais que ce soit uniquement MySQL. Voici mon code PHP actuel:
if ($distance != "Any" && $customer_Zip != "") { //get the great circle distance
//get the Origin Zip code info
$Zip_sql = "SELECT * FROM Zip_code WHERE Zip_code = '$customer_Zip'";
$result = mysql_query($Zip_sql);
$row = mysql_fetch_array($result);
$Origin_lat = $row['lat'];
$Origin_lon = $row['lon'];
//get the range
$lat_range = $distance/69.172;
$lon_range = abs($distance/(cos($details[0]) * 69.172));
$min_lat = number_format($Origin_lat - $lat_range, "4", ".", "");
$max_lat = number_format($Origin_lat + $lat_range, "4", ".", "");
$min_lon = number_format($Origin_lon - $lon_range, "4", ".", "");
$max_lon = number_format($Origin_lon + $lon_range, "4", ".", "");
$sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon' AND ";
}
Est-ce que quelqu'un sait comment faire cela entièrement à MySQL? J'ai un peu navigué sur Internet, mais la plupart des ouvrages sur le sujet sont assez déroutants.
De Google Code FAQ - Création d'un localisateur de magasin avec PHP, MySQL et Google Maps :
Voici l'instruction SQL qui trouvera les 20 emplacements les plus proches se trouvant dans un rayon de 25 miles de la coordonnée 37, -122. Il calcule la distance en fonction de la latitude/longitude de cette ligne et de la latitude/longitude cible, puis ne demande que les lignes dont la valeur de distance est inférieure à 25, ordonne toute la requête par distance et la limite à 20 résultats. Pour rechercher par kilomètres au lieu de miles, remplacez 3959 par 6371.
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) )
* cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin(radians(lat)) ) ) AS distance
FROM markers
HAVING distance < 25
ORDER BY distance
LIMIT 0 , 20;
$greatCircleDistance = acos( cos($latitude0) * cos($latitude1) * cos($longitude0 - $longitude1) + sin($latitude0) * sin($latitude1));
avec latitude et longitude en radian.
alors
SELECT
acos(
cos(radians( $latitude0 ))
* cos(radians( $latitude1 ))
* cos(radians( $longitude0 ) - radians( $longitude1 ))
+ sin(radians( $latitude0 ))
* sin(radians( $latitude1 ))
) AS greatCircleDistance
FROM yourTable;
est votre requête SQL
pour obtenir vos résultats en km ou en miles, multipliez le résultat par le rayon moyen de la Terre (3959
miles, 6371
Km ou 3440
milles marins)
La chose que vous calculez dans votre exemple est un cadre de sélection. Si vous placez vos données de coordonnées dans un colonne spatiale MySQL activée , vous pouvez utiliser fonctionnalité de construction de MySQL pour interroger les données.
SELECT
id
FROM spatialEnabledTable
WHERE
MBRWithin(ogc_point, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
Si vous ajoutez des champs auxiliaires à la table de coordonnées, vous pouvez améliorer le temps de réponse de la requête.
Comme ça:
CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)
Si vous utilisez TokuDB, vous obtiendrez des performances encore meilleures si vous ajoutez des index de clustering Sur l'un des prédicats, par exemple, comme ceci:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
Vous aurez besoin des valeurs de base lat et lon en degrés, ainsi que de sin (lat) en radians, cos (lat) * cos (lon) en radians et cos (lat) * sin (lon) en radians pour chaque point ..__ Ensuite, vous créez une fonction mysql, comme ceci:
CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
`cos_cos1` FLOAT, `cos_sin1` FLOAT,
`sin_lat2` FLOAT,
`cos_cos2` FLOAT, `cos_sin2` FLOAT)
RETURNS float
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
END
Cela vous donne la distance.
N'oubliez pas d'ajouter un index sur lat/lon pour que la boîte englobante puisse faciliter la recherche au lieu de la ralentir (l'index est déjà ajouté dans la requête CREATE TABLE ci-dessus).
INDEX `lat_lon_idx` (`lat`, `lon`)
Étant donné une vieille table avec seulement les coordonnées lat/lon, vous pouvez configurer un script pour le mettre à jour comme ceci: (php using meekrodb)
$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');
foreach ($users as $user)
{
$lat_rad = deg2rad($user['lat']);
$lon_rad = deg2rad($user['lon']);
DB::replace('Coordinates', array(
'object_id' => $user['id'],
'object_type' => 0,
'sin_lat' => sin($lat_rad),
'cos_cos' => cos($lat_rad)*cos($lon_rad),
'cos_sin' => cos($lat_rad)*sin($lon_rad),
'lat' => $user['lat'],
'lon' => $user['lon']
));
}
Ensuite, vous optimisez la requête réelle pour effectuer le calcul de distance uniquement lorsque cela est vraiment nécessaire, par exemple en délimitant le cercle (ainsi, ovale) de l'intérieur et de l'extérieur . Pour cela, vous devrez précalculer plusieurs métriques pour la requête elle-même. :
// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));
Compte tenu de ces préparatifs, la requête ressemble à ceci (php):
$neighbors = DB::query("SELECT id, type, lat, lon,
geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
FROM Coordinates WHERE
lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
// center radian values: sin_lat, cos_cos, cos_sin
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
// min_lat, max_lat, min_lon, max_lon for the outside box
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
// min_lat, max_lat, min_lon, max_lon for the inside box
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
// distance in radians
$distance_rad);
EXPLAIN sur la requête ci-dessus peut indiquer qu’il n’utilise pas d’index, sauf s’il ya suffisamment de résultats pour le déclencher. L'index sera utilisé lorsqu'il y aura suffisamment de données dans la table de coordonnées . Vous pouvez ajouter FORCE INDEX (lat_lon_idx) Pour utiliser l’index sans tenir compte de la taille de la table afin que vous puissiez vérifier avec EXPLAIN qu’il fonctionne correctement.
Avec les exemples de code ci-dessus, vous devriez avoir une implémentation fonctionnelle et évolutive de la recherche d'objet par distance avec une erreur minimale.
J'ai eu à travailler cela en détail, alors je vais partager mon résultat. Ceci utilise une table Zip
avec des tables latitude
et longitude
. Cela ne dépend pas de Google Maps; vous pouvez plutôt l’adapter à n’importe quelle table contenant lat/long.
SELECT Zip, primary_city,
latitude, longitude, distance_in_mi
FROM (
SELECT Zip, primary_city, latitude, longitude,r,
(3963.17 * ACOS(COS(RADIANS(latpoint))
* COS(RADIANS(latitude))
* COS(RADIANS(longpoint) - RADIANS(longitude))
+ SIN(RADIANS(latpoint))
* SIN(RADIANS(latitude)))) AS distance_in_mi
FROM Zip
JOIN (
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
) AS p
WHERE latitude
BETWEEN latpoint - (r / 69)
AND latpoint + (r / 69)
AND longitude
BETWEEN longpoint - (r / (69 * COS(RADIANS(latpoint))))
AND longpoint + (r / (69 * COS(RADIANS(latpoint))))
) d
WHERE distance_in_mi <= r
ORDER BY distance_in_mi
LIMIT 30
Regardez cette ligne au milieu de cette requête:
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
Ceci recherche les 30 entrées les plus proches de la table Zip
dans un rayon de 50 km du point lat/long 42.81/-70.81. Lorsque vous intégrez cela dans une application, vous définissez votre propre point et votre rayon de recherche.
Si vous souhaitez travailler en kilomètres plutôt qu'en miles, remplacez 69
par 111.045
et remplacez 3963.17
par 6378.10
dans la requête.
Voici un résumé détaillé. J'espère que ça aide quelqu'un. http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
SELECT *, (
6371 * acos(cos(radians(search_lat)) * cos(radians(lat) ) *
cos(radians(lng) - radians(search_lng)) + sin(radians(search_lat)) * sin(radians(lat)))
) AS distance
FROM table
WHERE lat != search_lat AND lng != search_lng AND distance < 25
ORDER BY distance
FETCH 10 ONLY
pour une distance de 25 km
J'ai écrit une procédure qui peut calculer la même chose, Mais vous devez entrer la latitude et la longitude dans la table respective.
drop procedure if exists select_lattitude_longitude;
delimiter //
create procedure select_lattitude_longitude(In CityName1 varchar(20) , In CityName2 varchar(20))
begin
declare Origin_lat float(10,2);
declare Origin_long float(10,2);
declare dest_lat float(10,2);
declare dest_long float(10,2);
if CityName1 Not In (select Name from City_lat_lon) OR CityName2 Not In (select Name from City_lat_lon) then
select 'The Name Not Exist or Not Valid Please Check the Names given by you' as Message;
else
select lattitude into Origin_lat from City_lat_lon where Name=CityName1;
select longitude into Origin_long from City_lat_lon where Name=CityName1;
select lattitude into dest_lat from City_lat_lon where Name=CityName2;
select longitude into dest_long from City_lat_lon where Name=CityName2;
select Origin_lat as CityName1_lattitude,
Origin_long as CityName1_longitude,
dest_lat as CityName2_lattitude,
dest_long as CityName2_longitude;
SELECT 3956 * 2 * ASIN(SQRT( POWER(SIN((Origin_lat - dest_lat) * pi()/180 / 2), 2) + COS(Origin_lat * pi()/180) * COS(dest_lat * pi()/180) * POWER(SIN((Origin_long-dest_long) * pi()/180 / 2), 2) )) * 1.609344 as Distance_In_Kms ;
end if;
end ;
//
delimiter ;
Je ne peux pas commenter la réponse ci-dessus, mais soyez prudent avec la réponse de @Pavel Chuchuva. Cette formule ne renverra pas de résultat si les deux coordonnées sont identiques. Dans ce cas, la distance est nulle et cette ligne ne sera pas retournée telle quelle avec cette formule.
Je ne suis pas un expert de MySQL, mais cela semble fonctionner pour moi:
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance
FROM markers HAVING distance < 25 OR distance IS NULL ORDER BY distance LIMIT 0 , 20;
Je pensais que mon implémentation de javascript serait une bonne référence à:
/*
* Check to see if the second coord is within the precision ( meters )
* of the first coord and return accordingly
*/
function checkWithinBound(coord_one, coord_two, precision) {
var distance = 3959000 * Math.acos(
Math.cos( degree_to_radian( coord_two.lat ) ) *
Math.cos( degree_to_radian( coord_one.lat ) ) *
Math.cos(
degree_to_radian( coord_one.lng ) - degree_to_radian( coord_two.lng )
) +
Math.sin( degree_to_radian( coord_two.lat ) ) *
Math.sin( degree_to_radian( coord_one.lat ) )
);
return distance <= precision;
}
/**
* Get radian from given degree
*/
function degree_to_radian(degree) {
return degree * (Math.PI / 180);
}
calculer la distance en Mysql
SELECT (6371 * acos(cos(radians(lat2)) * cos(radians(lat1) ) * cos(radians(long1) -radians(long2)) + sin(radians(lat2)) * sin(radians(lat1)))) AS distance
ainsi, la valeur de la distance sera calculée et n'importe qui peut faire la demande si nécessaire.