Fastest Way to Find Distance Between Two Lat/Long Points

后端 未结 15 945
时光说笑
时光说笑 2020-11-21 10:12

I currently have just under a million locations in a mysql database all with longitude and latitude information.

I am trying to find the distance between one point a

相关标签:
15条回答
  • 2020-11-21 10:52

    Have a read of Geo Distance Search with MySQL, a solution based on implementation of Haversine Formula to MySQL. This is a complete solution description with theory, implementation and further performance optimization. Although the spatial optimization part didn't work correctly in my case.

    I noticed two mistakes in this:

    1. the use of abs in the select statement on p8. I just omitted abs and it worked.

    2. the spatial search distance function on p27 does not convert to radians or multiply longitude by cos(latitude), unless his spatial data is loaded with this in consideration (cannot tell from context of article), but his example on p26 indicates that his spatial data POINT is not loaded with radians or degrees.

    0 讨论(0)
  • 2020-11-21 10:53

    Not a MySql specific answer, but it'll improve the performance of your sql statement.

    What you're effectively doing is calculating the distance to every point in the table, to see if it's within 10 units of a given point.

    What you can do before you run this sql, is create four points that draw a box 20 units on a side, with your point in the center i.e.. (x1,y1 ) . . . (x4, y4), where (x1,y1) is (givenlong + 10 units, givenLat + 10units) . . . (givenLong - 10units, givenLat -10 units). Actually, you only need two points, top left and bottom right call them (X1, Y1) and (X2, Y2)

    Now your SQL statement use these points to exclude rows that definitely are more than 10u from your given point, it can use indexes on the latitudes & longitudes, so will be orders of magnitude faster than what you currently have.

    e.g.

    select . . . 
    where locations.lat between X1 and X2 
    and   locations.Long between y1 and y2;
    

    The box approach can return false positives (you can pick up points in the corners of the box that are > 10u from the given point), so you still need to calculate the distance of each point. However this again will be much faster because you have drastically limited the number of points to test to the points within the box.

    I call this technique "Thinking inside the box" :)

    EDIT: Can this be put into one SQL statement?

    I have no idea what mySql or Php is capable of, sorry. I don't know where the best place is to build the four points, or how they could be passed to a mySql query in Php. However, once you have the four points, there's nothing stopping you combining your own SQL statement with mine.

    select name, 
           ( 3959 * acos( cos( radians(42.290763) ) 
                  * cos( radians( locations.lat ) ) 
                  * cos( radians( locations.lng ) - radians(-71.35368) ) 
                  + sin( radians(42.290763) ) 
                  * sin( radians( locations.lat ) ) ) ) AS distance 
    from locations 
    where active = 1 
    and locations.lat between X1 and X2 
    and locations.Long between y1 and y2
    having distance < 10 ORDER BY distance;
    

    I know with MS SQL I can build a SQL statement that declares four floats (X1, Y1, X2, Y2) and calculates them before the "main" select statement, like I said, I've no idea if this can be done with MySql. However I'd still be inclined to build the four points in C# and pass them as parameters to the SQL query.

    Sorry I can't be more help, if anyone can answer the MySQL & Php specific portions of this, feel free to edit this answer to do so.

    0 讨论(0)
  • 2020-11-21 10:54
    • Create your points using Point values of Geometry data types in MyISAM table. As of Mysql 5.7.5, InnoDB tables now also support SPATIAL indices.

    • Create a SPATIAL index on these points

    • Use MBRContains() to find the values:

      SELECT  *
      FROM    table
      WHERE   MBRContains(LineFromText(CONCAT(
              '('
              , @lon + 10 / ( 111.1 / cos(RADIANS(@lon)))
              , ' '
              , @lat + 10 / 111.1
              , ','
              , @lon - 10 / ( 111.1 / cos(RADIANS(@lat)))
              , ' '
              , @lat - 10 / 111.1 
              , ')' )
              ,mypoint)
      

    , or, in MySQL 5.1 and above:

        SELECT  *
        FROM    table
        WHERE   MBRContains
                        (
                        LineString
                                (
                                Point (
                                        @lon + 10 / ( 111.1 / COS(RADIANS(@lat))),
                                        @lat + 10 / 111.1
                                      ),
                                Point (
                                        @lon - 10 / ( 111.1 / COS(RADIANS(@lat))),
                                        @lat - 10 / 111.1
                                      ) 
                                ),
                        mypoint
                        )
    

    This will select all points approximately within the box (@lat +/- 10 km, @lon +/- 10km).

    This actually is not a box, but a spherical rectangle: latitude and longitude bound segment of the sphere. This may differ from a plain rectangle on the Franz Joseph Land, but quite close to it on most inhabited places.

    • Apply additional filtering to select everything inside the circle (not the square)

    • Possibly apply additional fine filtering to account for the big circle distance (for large distances)

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