How to efficiently find the closest locations nearby a given location

前端 未结 4 1264
死守一世寂寞
死守一世寂寞 2020-11-27 12:07

I\'m making a script where a load of business are loaded into a mySQL database with a latitude and longitude. Then I am supplying that script with a latitude an longitude (o

相关标签:
4条回答
  • 2020-11-27 12:47

    Option 1: Do the calculation on the database by switching to a database that supports GeoIP.

    Option 2: Do the calculation on the database: you're using MySQL, so the following stored procedure should help

    CREATE FUNCTION distance (latA double, lonA double, latB double, LonB double)
        RETURNS double DETERMINISTIC
    BEGIN
        SET @RlatA = radians(latA);
        SET @RlonA = radians(lonA);
        SET @RlatB = radians(latB);
        SET @RlonB = radians(LonB);
        SET @deltaLat = @RlatA - @RlatB;
        SET @deltaLon = @RlonA - @RlonB;
        SET @d = SIN(@deltaLat/2) * SIN(@deltaLat/2) +
        COS(@RlatA) * COS(@RlatB) * SIN(@deltaLon/2)*SIN(@deltaLon/2);
        RETURN 2 * ASIN(SQRT(@d)) * 6371.01;
    END//
    

    EDIT

    If you have an index on latitude and longitude in your database, you can reduce the number of calculations that need to be calculated by working out an initial bounding box in PHP ($minLat, $maxLat, $minLong and $maxLong), and limiting the rows to a subset of your entries based on that (WHERE latitude BETWEEN $minLat AND $maxLat AND longitude BETWEEN $minLong AND $maxLong). Then MySQL only needs to execute the distance calculation for that subset of rows.

    FURTHER EDIT (as an explanation for the previous edit)

    If you're simply using the SQL statement provided by Jonathon (or a stored procedure to calculate the distance) then SQL still has to look through every record in your database, and to calculate the distance for every record in your database before it can decide whether to return that row or discard it.

    Because the calculation is relatively slow to execute, it would be better if you could reduce the set of rows that need to be calculated, eliminating rows that will clearly fall outside of the required distance, so that we're only executing the expensive calculation for a smaller number of rows.

    If you consider that what you're doing is basically drawing a circle on a map, centred on your initial point, and with a radius of distance; then the formula simply identifies which rows fall within that circle... but it still has to checking every single row.

    Using a bounding box is like drawing a square on the map first with the left, right, top and bottom edges at the appropriate distance from our centre point. Our circle will then be drawn within that box, with the Northmost, Eastmost, Southmost and Westmost points on the circle touching the borders of the box. Some rows will fall outside that box, so SQL doesn't even bother trying to calculate the distance for those rows. It only calculates the distance for those rows that fall within the bounding box to see if they fall within the circle as well.

    Within PHP, we can use a very simple calculation that works out the minimum and maximum latitude and longitude based on our distance, then set those values in the WHERE clause of your SQL statement. This is effectively our box, and anything that falls outside of that is automatically discarded without any need to actually calculate its distance.

    There's a good explanation of this (with PHP code) on the Movable Type website that should be essential reading for anybody planning to do any GeoPositioning work in PHP.

    0 讨论(0)
  • 2020-11-27 12:51

    There is much easier way to work this one out.

    1. We know that 0.1 difference in latitude at exact same longitude equal to distance of 11.12 km. (1.0 in lat will make that distance 111.2 km)

    2. Also with 0.1 difference in longitude and same latitude distance is 3.51 km (1.0 in lon will make that distance 85.18 km) (to convert into miles we multiply that by 1.60934)

    NOTE. Be aware that longitude goes from -180 to 180, so difference between -180 to 179.9 is 0.1 which is 3.51 km.

    All we need to know now is list of all zipcodes with lon and lat (you already have that)

    So now to narrow your search by 90% you only need to cut out all results that will definitely not be within 100 kilometers, for example. our coordinates $lat1 and $lon2 for 100 kilomiters difference of 2 in both lat and lon will be more than enough.

    $lon=...;
    $lat=...;
    $dif=2;
    
    SELECT zipcode from zipcode_table WHERE latitude>($lan-$dif) AND latitude<($lan+$dif) AND longitude>($lon-$dif) AND longitude<($lon+$dif)
    

    Something like that. Of course if you need to cover smaller or larger area you will need to change $dif accordingly.

    This way Mysql will only look into very limited saving comp resources.

    0 讨论(0)
  • 2020-11-27 12:53

    If you have a lot of points, queries with distance formulas in them will be very slow because it's not using an index for the search. For efficiency you'd either have to use a rectangular bounding box to make it faster, or you can use a database with GIS features built in. PostGIS is free and here's an article on doing nearest neighbor search:

    http://www.bostongis.com/PrinterFriendly.aspx?content_name=postgis_nearest_neighbor_generic

    0 讨论(0)
  • 2020-11-27 13:02

    I think what you're trying to achieve could be done better using the Haversine formula in your SQL. Google has a tutorial on how to get the nearest locations in a MySQL database but the general idea is this SQL:

    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;
    

    Then all the work you need to do is done on the database, so you don't have to pull all the businesses into your PHP script before you even check the distance.

    0 讨论(0)
提交回复
热议问题