2

The Issue

I am currently storing the latitude and longitude values for a location using the MySQL POINT field type in the format:

POINT(51.507351 -0.127758)

I have never before used this kind of field type, and therefore do not have any experience with the queries, and how to actually, efficiently, use the data stored.

My Research

I have found many links that demonstrate various methods to search for items within a specified radius. However, most of these are using independent latitude and longitude fields instead of using the MySQL spatial fields.

Please see the following:

My Question

I am trying to search for any records within a given radius (in metres). Based on the structure of my table, what is the best, and most efficient, method of searching my records and returning any items within the specified radius (circle not rectangle)?

This is what I have so far:

SELECT
    *,
    (
        6373 * acos (
            cos ( radians( PASSED_IN_LATITUDE ) )
            * cos( radians( X(location) ) )
            * cos( radians( Y(location) ) - radians( PASSED_IN_LONGITUDE ) )
            + sin ( radians( PASSED_IN_LATITUDE ) )
            * sin( radians( X(location) ) 
        )
    ) AS distance
FROM locations
HAVING distance < PASSED_IN_RADIUS

I took the above code from another answer but given this answer was posted 2 years ago, I have assumed it it out of date and therefore this may not be the most efficient method anymore...

Community
  • 1
  • 1
Ben Carey
  • 16,540
  • 19
  • 87
  • 169

1 Answers1

2

Assuming you have a spatial key on location, you can do something like this:

select * from locations where 
contains(geomfromtext('polygon($bounding_rect_coords)'),location) 
and earth_distance(location,point($lat,$lon)) < $radius

The bounding rectangle coordinates should be computed using the following formulas:

$deg_to_rad = $PI/180.0
$rad_to_deg = 1.0/$deg_to_rad
$delta_y = $rad_to_deg *($radius / ($earth_radius * cos($lat*$deg_to_rad))) // the length of the parallel = EARTH_R * cos(lat)
$delta_x = $rad_to_deg * ($radius/$earth_radius)
$x1 = $lat - $delta_x
$x2 = $lat + $delta_x
$y1 = $lon - $delta_y
$y2 = $lon + $delta_y

Then the rectangle is obtained with

geomfromtext('polygon(($x1 $y1,$x2 $y1,$x2 $y2, $x1 $y2, $x1 $y1))')

This is best done in the application to offload the database server.

This rectangle is actually a spherical rectangle, thus the use of the PI constant in its computation. The idea is simple. For the given parallel, convert the search radius into degrees of longitude. That is how many degrees east and west we need to go from the target to cover our candidate points. Then compute the same for degrees of latitude - unlike longitude, this will not be coordinate-dependent as all meridians have the same length. That is how many degrees we need to go north and south.

The above computation assumes that the search radius is smaller than the length of the parallel, which will be the case in most of the United States for a reasonable search radius, but might not hold in some parts of Alaska, for example. So it would be a good idea to check for that (if delta_y > 90) and clip it accordingly. You should also check to see if you are right on the North or South pole, things break there completely. But hopefully your data does not have too many polar records.

For earth_distance() you have several options:

Your computation itself is OK even though it is two years old - nothing revolutionary has been discovered in the last two years as far as measuring the distance between two points on the earth is concerned as far as I know.

Your original method would work as well except it will be inefficient. The addition of contains clause allows us to reduce our search to a (hopefully) relatively small set that is guaranteed to be within the search radius very quickly. Then we take each candidate and filter out the ones that did not make the cut on earth_distance().

I must add a standard disclaimer that I inserted variables into SQL that potentially might not have been sanitized. Make sure to validate the resulting SQL queries for SQL injection attacks when writing the actual production code.

Sasha Pachev
  • 5,162
  • 3
  • 20
  • 20
  • Thank you very much for this! This is exactly what I am looking for, I think. Just to confirm, `earth_distance` is a plugin to MySQL? I am using a RackSpace Cloud cluster so have pretty much as much control as I could possibly need... My instances are running CentOS, is this still okay to isntall UDF's (I have never looked into UDF)? You mention a rectangle in your answer, is it actually a rectangle or is it a polygon/circle? I only ask because in your calculation, you have used PI... – Ben Carey May 26 '16 at 09:34
  • `earth_distance()` is a UDF (user-defined function), which is technically different from a MySQL plugin (plugins can do more), but it is the same idea. You compile the C file, produce a shared library, then register it with MySQL and it loads it when needed. Will work fine in your setup as long as you have the root shell on the server and the ability to install a C compiler (gcc). The rectangle strictly speaking is a spherical rectangle, thus PI pops up, but from the point of view of a spatial index it is a regular rectangle. – Sasha Pachev May 26 '16 at 17:56
  • Brilliant, thank you Sasha! And just to clarify, the `$earth_radius` is either 6373 or 3959 depending on whether I want to use kilometres/miles? I tried to install the UDF on my mac as I am currently testing locally but I wasn't able to do so... Do you have any clear instructions on how I can do this? If not, don't worry as I can just test it on my cluster instead... – Ben Carey May 26 '16 at 18:01
  • Ben - the instructions are in README.md - you may need to adapt them some for OS X. The basic idea is that you need to have a C compiler, MySQL header files, and CMake. Once those are installed the same commands should work, although the installation paths will likely be different. I noticed a mistake in my earlier answer and corrected it - I had delta_x and delta_y backwards. I also added a clarify explanation of how we build the candidate spherical rectangle. – Sasha Pachev May 26 '16 at 18:08
  • And yes, $earth_radius should be in the correct units according to which system you are using - metric or English. – Sasha Pachev May 26 '16 at 18:10
  • Hi Sasha. Thank you for all of your help thus far! I have one final question for you if that is okay? I have not been able to install your UDF packages as I have had complications with installing the dev tools etc. Instead of spending time doing this, as I am short on time, I am going to have to implement your second suggestion. Would you mind expanding a little more on how to implement this solution? Creating the function is easy, its what the query looks like that calls it that I need assistance with... – Ben Carey May 28 '16 at 13:36
  • The query will be exactly the same assuming you name the function `earth_distance()` and it takes geometry points as arguments. You can extract lat and lon using `x()` and `y()` SQL functions inside the function. Or alternatively, if the function takes lat/lon, you can do the extraction in the query using the same functions. Also, the dev-tools complications should be easy to resolve. Post a separate SO question, if nobody answers, I'll be happy to take a look. – Sasha Pachev May 31 '16 at 18:56