MySQL radius search - mysql

I am making an application with radius search, here is the example I learned from
https://developers.google.com/maps/articles/phpsqlsearch_v3#findnearsql
In my case
I do
SELECT (3959 * acos(cos(radians(-37.814107)) * cos(radians(lat)) * cos(radians(lng) - radians(144.96327999999994)) + sin(radians(-37.814107)) * sin(radians(lat)))) AS distance;
when lat = -37.8141070000, lng = 144.9632800000
SELECT (3959 * acos(cos(radians(-37.814107)) * cos(radians(-37.8141070000)) * cos(radians(144.9632800000) - radians(144.96327999999994)) + sin(radians(-37.814107)) * sin(radians(-37.8141070000)))) AS distance;
MySQL output the distance as NULL, why? Did I do something wrong?
How I fix it? Sorry I am really NOT good at math.

Basic SQL query
SELECT * FROM table
You must have a MySql table in your query with lat & lng as fields.
ie
SELECT (3959 * acos(cos(radians(-37.814107))
* cos(radians(lat)) * cos(radians(lng) - radians(144.96327999999994))
+ sin(radians(-37.814107))
* sin(radians(lat)))) AS distance FROM table;

Related

Distance calculation in MySQL different from Great Circle Mapper

I can't figure out how to match the result of the Great Circle Mapper.
SELECT 3959 *
ACOS(
COS(RADIANS(41.97444444)) *
COS(RADIANS(28.42944444)) *
COS(RADIANS(-87.90666667) - RADIANS(-81.30888889)) +
SIN (RADIANS(41.97444444)) *
SIN(RADIANS(28.42944444))
) AS `distance`
Returns 1006.5099532670922
However, GCMap produces 1005 miles
Any suggestions?

How to query places sorted by distance with lat, lng coordinates in MySQL with bookshelf.js or knex?

Is it possible to search nearest places in mysql by using Bookshelf.js, or Knex? And, if yes, how to do it correct?
UPD:
I'm not strong with raw mysql queries, and can't find how to search with lat, lng in Bookshelf and knex.
For example, i have places table with:
title
address
lat
lng.
What query i should use to get all places in 10 miles radius from Point(lat, lng)?
thanks.
After some research i found working solution:
const {lat, lng} = req.params;
const X1 = +lat - 10,
Y1 = +lng - 10,
X2 = +lat + 10,
Y2 = +lng + 10;
const distanceInMilesSql = `( 3959 * acos( cos( radians(${+lat}) )
* cos( radians( place.lat ) )
* cos( radians( place.lng ) - radians(${+lng}) )
+ sin( radians(${+lat}) )
* sin( radians( place.lat ) ) ) ) AS distance
`;
const places = await new Place().query(qb => {
qb.whereBetween('lat', [X1, X2]);
qb.whereBetween('lng', [Y1, Y2]);
qb.column(['*', Bookshelf.knex.raw(distanceInMilesSql)]);
qb.having('distance', '<', 10);
}).fetchAll({withRelated: [
'place_specific_fields',
'state',
'media'
]});
Demo
http://rextester.com/NDOR4479
SQL
SELECT *,
ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
* COS(RADIANS(lng - :lng))) * 3959 AS distance
FROM places
WHERE ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
* COS(RADIANS(lng - :lng))) * 3959 <= 10
ORDER BY distance;
Notes
The above uses the Spherical Law of Cosines where :lat and :lng are the latitude and longitude values from Point and 3959 is the Earth radius in miles.
If it helps anyone, here's some code I use (Strapi + MySQL):
Query for events
Join locations into events, where id matches
Gets anything within 5km (5000 meters) of the user
On a side note: As a Strapi user, a raw query like this negates the high-level functionality that Strapi auto-builds for the events model. ie Strapi url filtering like &Cost_gte=20 won't work. I'm still working on how to get around this and avoid having to build an entire custom controller from scratch (all just to have distance filtering?).
Code:
const knex = strapi.connections.default
const result = await knex('events')
.join('locations', 'events.Location', 'locations.id')
.where(knex.raw(
`round(st_distance_sphere(
st_geomfromtext(CONCAT('POINT(',locations.Longitude, ' ',locations.Latitude,')')),
st_geomfromtext(CONCAT('POINT(` + ctx.query.Longitude + ` ` + ctx.query.Latitude + `)'))
)) <= 5000`
))
return result
You don't make it clear in your question, but I believe that the Bookshelf.js library works as a layer above PostgreSQL, which means that you have access to geometric data types. These can make use of these special operations. The search could be entirely SQL based, so without further context I can simply offer the following:
Combine the latitude and longitude into a single data piece of type point (using the decimal form of the latitude and longitude). Here I will refer to that as coord.
With the proper data in order, you can do a query of:
SELECT p.*, (point'(#origin_long,$origin_lat)' <-> `coord`) distance
FROM `places` p
WHERE circle '((#origin_long, #origin_lat),#radius)' #> `coord`
ORDER BY distance;
If the data structures are set right, this should return a list, ordered by distance, of all pairs of coordinates within a given radius. You can set the coordinates at the top of this function, or maybe even wrap it in a stored procedure (if you don't mind the overhead).
For further reading, I know that PostgreSQL has an "Earth Distance" function as well, so if you are looking to include the calculation of true distance traveled between points, it might be useful!

Combine two MySQL SELECT queries from single table

I have an SQL statement like this:
SELECT
(111.045 * DEGREES(ACOS(COS(RADIANS({$lat}))
* COS(RADIANS(lat))
* COS(RADIANS({$lon}) - RADIANS(lon)) + SIN(RADIANS({$lat}))
* SIN(RADIANS(lat))))) AS distance,
(`f1` + `f2` + `f5` + `f6`) AS sum
FROM `shops`
WHERE distance <= 25
where first part returns shops within certain distance and second part returns sum of given fields in the same row of the same table.
Both parts work separately but do not return results when combined.
Please tell me what i do wrong?
Don't use alias in where
SELECT
(111.045 * DEGREES(ACOS(COS(RADIANS({$lat}))
* COS(RADIANS(lat))
* COS(RADIANS({$lon}) - RADIANS(lon)) + SIN(RADIANS({$lat}))
* SIN(RADIANS(lat))))) AS distance,
(f1 + f2 + f5 + f6) AS sum
FROM shops
WHERE (111.045 * DEGREES(ACOS(COS(RADIANS({$lat}))
* COS(RADIANS(lat))
* COS(RADIANS({$lon}) - RADIANS(lon)) + SIN(RADIANS({$lat}))
* SIN(RADIANS(lat))))) <= 25

Large MySQL DB (21MM records) with location data - each location has lat and long - need to run 'nearby' query

We have a large location DB - with lat long specified for each row. The DB is hosted in MySQL.
We need to run two type of queries:
places nearby (sort by distance)
places nearby by category (where category is a column)
With the number of records growing, this query seems to slow down drastically.
SELECT *, ( 3959 * acos( cos( radians(40.759105) ) * cos( radians( Latitude ) ) * cos( radians( longitude) - radians(-73.984654) ) + sin( radians(40.759105) ) * sin( radians( Latitude ) ) ) ) as distance FROM mcw_in WHERE Latitude <> '' ORDER BY distance LIMIT 0,20
How can I create an index in MySQL to address the slowness? Is there any other solution - like using any geospatial data types?
MySQL Manual :: Introduction to MySQL Spatial Support
MySQL Manual :: Creating Spatial Indexes
But really this won't work in MySQL since they haven't really implemented the functions.
If you are open to it, I would reccomend using PostGIS or Spatialiate (running on Postgresql and SQLLite respectively) or even mongodb or geocouch. These have a much larger suite of implemented spatial functions. If you look at the MySQL documentation it mostly says "not implemented" for the spatial functions.
It is better to use range queries by defining a bounding box surrounding the center. The following query searches the nearest 20 locations within distance $dist from the center ($lat0, $lng0), with the result sorted by the distance. You need two indexes, one on 'lat' and one on 'lng'. Some explanations can be found here.
SELECT *,
    ( 6371 * acos(
    cos(radians($lat0)) * cos(radians(lat)) * cos(radians(lng) - radians($lng0)) +
    sin(radians($lat0)) * sin(radians(lat))
    ) ) AS distance
FROM `locations`
WHERE lat < degrees( asin( sin(radians($lat0)) * cos($dist / 6371) +
        cos(radians($lat0)) * sin($dist / 6371) * cos(radians(0)) ))
  AND lat > degrees( asin( sin(radians($lat0)) * cos($dist / 6371) +
        cos(radians($lat0)) * sin($dist / 6371) * cos(radians(180)) ))
  AND lng < $lng0 - degrees( atan2(sin(radians(90)) * sin(radians($dist / 6371)) * cos(radians($lat0)),
        cos(radians($dist / 6371)) - sin(radians($lat0)) * sin(radians($lat0))) )
  AND lng > $lng0 + degrees( atan2(sin(radians(90)) * sin(radians($dist / 6371)) * cos(radians($lat0)),
        cos(radians($dist / 6371)) - sin(radians($lat0)) * sin(radians($lat0))) )
ORDER BY distance LIMIT 20;

OpenStreetMap Proximity search using mySQL

I'm just playing around with a dataset of my region generated by JOSM. I moved it into a mySQL DB with the 0.6 API scheme using Osmosis and now I'm desperately trying the following:
I want to get all streets of a city.
AFAIK there is no tag/relation in the OSM data to determine this so I tried it using a proximity search to get all nodes in a radius around a node representing the city center.
Most of the time I looked at the approaches here
What I got is the following SQL code that should get me the closest 100 nodes around the node with id 36187002 and within a radius of 10km.
set #nodeid = 36187002;
set #dist = 10;
select longitude, latitude into #mylon, #mylat from nodes where id=#nodeid limit 1;
SELECT id, ( 6371 * acos( cos( radians(#mylon) ) * cos( radians( latitude ) ) *
cos( radians( longitude ) - radians(#mylat) ) + sin( radians(#mylon) ) * sin( radians( latitude ) ) ) )
AS distance
FROM nodes HAVING distance < #dist ORDER BY distance LIMIT 0 , 100;
Well.. it doesn't work. :( I guess the main problem is that OSM lats/lons are multiplied by 10.000.000 and I don't know how I can correct this function to make it work.
Any ideas about this? All solutions/alternatives are very welcome!
It may be faster to add extra columns to your table for the latitude and longitude expressed as the double data type (so the trigonometric functions stand a chance) - you may want to go further and precalculate the xaxis, yaxis and zaxis as columns (again, stored as double)
So, your new columns are loosely (you may need to add data type conversions as required):
XAxis = cos(radians(Latitude / 10000000)) * cos(radians(Longitude / 10000000))
YAxis = cos(radians(Latitude / 10000000)) * sin(radians(Longitude / 10000000))
ZAxis = sin(radians(Latitude / 10000000))
Then, your proximity search becomes:
set #nodeid = 36187002;
set #dist = 10;
SELECT XAxis, YAxis, ZAxis
INTO #CntXAxis, #CntYAxis, #CntZAxis
FROM nodes
WHERE id=#nodeid limit 1;
SELECT id, ( 6371 * acos(
CASE
WHEN nodes.XAxis * #CntXAxis
+ nodes.YAxis * #CntYAxis
+ nodes.ZAxis * #CntZAxis > 1.0 THEN 1.0
ELSE nodes.XAxis * #CntXAxis
+ nodes.YAxis * #CntYAxis
+ nodes.ZAxis * #CntZAxis
END
) AS Distance
FROM nodes
HAVING Distance < #dist
ORDER BY distance LIMIT 0 , 100;
I modified the query a little and it works.
Here my code:
set #nodeid = 122317;
set #dist = 10;
select lon, lat into #mylon, #mylat from nodes where id=#nodeid limit 1;
SELECT id, ( 6371 * acos(
sin(radians(#mylat)) * sin(radians(lat)) +
cos(radians(#mylat)) * cos( radians(lat)) *
cos(radians(lon) - radians(#mylon))
))
AS distance
FROM nodes having distance <#dist
i´ve got the formula from the german wikipedia and it works fine. I've had in in some ruby code fist, but its also work as an sql-query.
To select some special nodes i added this
(select nodes.id,lat,lon,k,v from nodes join node_tags on nodes.id=node_tags.id where k='public_transport') as stations
as the FROM condition to specify the tags of the nodes. (Of course it changes the lat/log access to stations.lat/stations.log in the code above.