问题
So I have this function to calculate nearest cities based on latitude, longitude and radius parameters.
DELIMITER $$
DROP PROCEDURE IF EXISTS `world_db`.`geolocate_close_cities`$$
CREATE PROCEDURE `geolocate_close_cities`(IN p_latitude DECIMAL(8,2), p_longitude DECIMAL(8,2), IN p_radius INTEGER(5))
BEGIN
SELECT id, country_id, longitude, latitude, city,
truncate((degrees(acos( sin(radians(latitude))
* sin(radians(p_latitude))
+ cos(radians(latitude))
* cos(radians(p_latitude))
* cos(radians(p_longitude - longitude) ) ) )
* 69.09*1.6),1) as distance
FROM cities
HAVING distance < p_radius
ORDER BY distance desc;
END$$
DELIMITER ;
Here's the structure of my cities table:
> +------------+-------------+------+-----+---------+----------------+ |
> Field | Type | Null | Key | Default | Extra |
> +------------+-------------+------+-----+---------+----------------+ |
> id | int(11) | NO | PRI | NULL | auto_increment | |
> country_id | smallint(6) | NO | | NULL | | |
> region_id | smallint(6) | NO | | NULL | | |
> city | varchar(45) | NO | | NULL | | |
> latitude | float | NO | | NULL | | |
> longitude | float | NO | | NULL | | |
> timezone | varchar(10) | NO | | NULL | | |
> dma_id | smallint(6) | YES | | NULL | | |
> code | varchar(4) | YES | | NULL | |
> +------------+-------------+------+-----+---------+----------------+
It works very well.
What i'd lke to do (pseudcode) is something like:
SELECT * FROM cities WHERE DISTANCE(SELECT id FROM cities WHERE id={cityId}, {km))
and it'll return me the closest cities.
Any ideas of how I can do this?
At the moment, I just call the function, and then iterate through the ids into an array and then perform a WHEREIN in the city table which obviously isn't very efficient.
Any help is MUCH appreciated. Thanks.
回答1:
If you can limit the maximum distance between your cities and your local position, take advantage of the fact that one minute of latitude (north - south) is one nautical mile.
Put an index on your latitude table.
Make yourself a haversine(lat1, lat2, long1, long2, unit) stored function from the haversine formula shown in your question. See below
Then do this, given mylatitude, mylongitude, and mykm.
SELECT *
from cities a
where :mylatitude >= a.latitude - :mykm/111.12
and :mylatitude <= a.latitude + :mykm/111.12
and haversine(:mylatitude,a.latitude,:mylongitude,a.longitude, 'KM') <= :mykm
order by haversine(:mylatitude,a.latitude,:mylongitude,a.longitude, 'KM')
This will use a latitude bounding box to crudely rule out cities that are too far away from your point. Your DBMS will use an index range scan on your latitude index to quickly pick out the rows in your cities table that are worth considering. Then it will run your haversine function, the one with all the sine and cosine maths, only on those rows.
I suggest latitude because the on-the-ground distance of longitude varies with latitude.
Note this is crude. It's fine for a store-finder, but don't use it if you're a civil engineer -- the earth has an elliptical shape and the this assumes it's circular.
(Sorry about the 111.12 magic number. That's the number of km in a degree of latitude, that is in sixty nautical miles.)
See here for a workable distance function.
Why does this MySQL stored function give different results than to doing the calculation in the query?
来源:https://stackoverflow.com/questions/7505936/geolocation-distance-sql-from-a-cities-table