distance between two longitude and latitude - sql-server-2008

How should I write query for this?
Consider P1(a, b) and P2(c, d) to be two points on a 2D plane.
a happens to equal the minimum value in Northern Latitude (LAT_N in
STATION).
b happens to equal the maximum value in Northern Latitude
(LAT_N in STATION)
c happens to equal the minimum value in Western
Longitude (LONG_W in STATION)
d happens to equal the maximum value
in Western Longitude (LONG_W in STATION)
Query the Manhattan Distance between points P1 and P2 and round it to a scale of 4 decimal places.
Table STATION(ID number, CITY varchar2(21), STATE varchar2(2), LAT_N number, LONG_W number)
this is my try but returns NULL
select
2 * 3961 * asin(power(sqrt((sin(radians(MAX(LAT_N) - MIN(LAT_N)) / 2) )) , 2 ) + cos(radians(MAX(LAT_N))) * cos(radians(MIN(LAT_N))) * power(sqrt((sin(radians(MAX(LONG_W) - MIN(LONG_W)) / 2) )) , 2 )) as distance from station where city like 'manhattan';
any idea will be appreciated

For MySQL,
SELECT ROUND(ABS(MIN(LAT_N) - MAX(LAT_N)) + ABS(MAX(LONG_W) - MIN(LONG_W)),4) FROM STATION

For SQL server you can use the following query:
SELECT convert(decimal(10,4),ABS(MIN(LAT_N)-MAX(LAT_N)) + ABS(MIN(LONG_W)-MAX(LONG_W))) FROM STATION

Instead of reinventing the wheel, you can make use of the SQL Server geography data types. These data types are present as of version 2008, and there are functions is there to do in exactly what you're trying to do without all of the math involved.
Take a look at this example (there's far too much to include here) or look up more information on MSDN.

Manhattan Distance (Taxicab Geometry)
Manhattan Distance between points P1(a,b) and P2(c,d)= |a-c|+|b-d|
--a= min(LAT_N)
--b= max(LAT_N)
--c= min(LONG_W)
--d= max(LONG_w)
SELECT ROUND(ABS(MIN(LAT_N) - MIN(LONG_W)),4) + ROUND(ABS(MAX(LAT_N) - MAX(LONG_W)),4) FROM STATION;

You can use this maths formula to get the straight distance between two points:
Distance = squarerootof((x2−x1)^2+(y2−y1)^2)

select round(abs(min(lat_n)- max(lat_n)) + abs(min(long_w)- max(long_w)),4) from station;

Declare #a float, #b float, #c float, #d float;
SET #a =(select LAT_N from station where LAT_N = (select min(LAT_N) from station) )
SET #b =(select LONG_W from station where LONG_W =(Select min(LONG_W) from station) )
SET #c= (select LAT_N from station where LAT_N = (select max(LAT_N) from station) )
SET #d= (select LONG_W from station where LONG_W = (select max(LONG_W) from station) )
select cast(ROUND((abs(#a -#c)+ abs(#b - #d)),4) as decimal(12,4))

Easiest way of doing this problem is
Using mathematics function [ Dis. = sqrt((x2−x1)^2+(y2−y1)^2) ], try:
for sql server:
select format(abs(min(LAT_N)-max(LAT_N))+abs(min(long_w)-max(long_w)),'.####') from station;
or
select format(sqrt(square(min(LAT_N)-max(LAT_N))+square(min(long_w)-max(long_w))),'.####') from station;
for SQL:
SELECT ROUND(ABS(MIN(LAT_N)-MAX(LAT_N)) + ABS(MIN(LONG_W)-MAX(LONG_W)), 4) FROM STATION;
for simplicity to understand:
select
round(
abs(
min(lat_n)- max(lat_n)
) + abs(
min(long_w)- max(long_w)
), 4
)
from
station;

Here: a=MIN(LAT_N) c=MAX(LAT_N) b=MIN(LONG_W) d=MAX(LONG_W)
#Query for obtaining Manhattan distance is:
SELECT ROUND(ABS(MIN(LAT_N)-MAX(LAT_N))+ABS(MIN(LONG_W)-MAX(LONG_W)),4) FROM STATION;

You can use the code below using Oracle.
SELECT
ROUND(ABS(MIN(LAT_N) - MAX(LAT_N)) + ABS(MIN(LONG_W) - MAX(LONG_W)) , 4)
FROM STATION;

According to the question, they are asking to find Manhattan Distance but you are trying to find Euclidean distance between two points. Both of them are different
Manhattan Distance:
|X1 - X2| + |Y1 - Y2|
Euclidean Distance:
sqrt((X2−X1)^2+(Y2 −Y1)^2)
select Format(ABS(MIN(LAT_N)-MAX(LAT_N)) + ABS(MIN(LONG_W)-MAX(LONG_W)),'.####') from STATION;

Euclidean distance between two points. Both of them are different.
Manhattan Distance:
|X1 - X2| + |Y1 - Y2|
select round((max(lat_n)-min(lat_n))+(max(long_w)-min(long_w)),4)
from station

For whatever reason, the compiler seems to ignore the ROUND() function and still output all the decimal places. I used the SUBSTR() function to work around this bug. So the query would look like:
select substr((ABS(MAX(LAT_N) - MIN(LAT_N)) + ABS(MAX(LONG_W) - MIN(LONG_W))), 1, 8) FROM STATION;

I tried this one for MYSQL:
CREATE TABLE points (
a DECIMAL(10,4),
b DECIMAL(10,4),
c DECIMAL(10,4),
d DECIMAL(10,4));
INSERT INTO points VALUE ((SELECT MIN(lat_n) FROM station),
(SELECT MIN(long_w) FROM station),
(SELECT MAX(lat_n) FROM station),
(SELECT MAX(long_w) FROM station));
SELECT ROUND((c - a) + (d - b),4) FROM points;

select round(abs(max(lat_n )-min(lat_n)) + abs(max(long_w)-min(long_w)),4)
from station

I tried this one,
SELECT ROUND(MAX(LAT_N) - MIN(LAT_N) + MAX(LONG_W) - MIN(LONG_W), 4) FROM STATION

select cast((round((max(lat_n)-min(lat_n)),4)) as decimal (10,4))+ cast(round((max(long_w)-min(long_w)),4) as decimal(10,4)) from station;

select round( ABS(max(LAT_N)-min(LAT_N)) +
ABS(max(LONG_W)-min(LONG_W)),4 ) FROM STATION

-- a = min(lat_n); b = min(long_w) ; c= max(lat_n) ; d= max(long_w)
-- Manhattan Distance between points P1(a,b) and P2(c,d)= |c-a|+|d-b|
select round(abs(max(lat_n )-min(lat_n)) + abs(max(long_w)-min(long_w)),4)
from station

select cast(round(abs(Min(LAT_N) - Max(LAT_N)),4) + round(abs(Min(LONG_W) - Max(LONG_W)),4) as decimal(10,4)) from station;
try this query ,it worked for me

For MySQL:
select round((max(LAT_N) - min(LAT_N)) + (max(LONG_W) - min(LONG_W)),4) from station

Manhattan distance
Definition: The distance between two points measured along axes at right angles. In a plane with p1 at (x1, y1) and p2 at (x2, y2), it is |x1 - x2| + |y1 - y2|.
In the problem x1 is min(lat_n) and x2 is max(lat_n), similarly y1 will be min(long_w) and y2 will be max(long_w). Hence the query to perform this in MySQL is;
SELECT ROUND(ABS(MIN(LAT_N)-MAX(LAT_N)) + ABS(MIN(LONG_W)-MAX(LONG_W)),4) FROM STATION;

For MS SQL server logic
To find Manhattan distance between 2 points: P1(A,B) AND P2(C,D)
Formula : (C-A) + (D-B)
SELECT CAST(MAX(LAT_N) - MIN(LAT_N) + MAX(LONG_W) - MIN(LONG_W) AS DECIMAL(10,4))
FROM STATION
If you use the Round function, it will show you the wrong answer because the question asks for 4 decimal places only.

Related

How to apply multiple Order by in Rails

In my Rails application I have a table called HealthcareCenter.
I want to list HealthcareCenter based on below order(I have pagination, per page should list 50 records).
Number of recommentations (If user logged in)
Ratings (high rating first)
Distance (first set distance should be < 25km, second distance between 25 and 100, third any distance but ascending order)
I will explain by below image
At image we can see 5 clinics, as like shown in image sql should order the clicnics
I have written
HealthcareCenter.where("healthcare_centers.name LIKE ?", "%#{query}%")
.where(type_id: type_id)
.order("healthcare_centers.overall_rating DESC")
.calculate_distance2a(lat, lon)
.order("distance")
.order("healthcare_centers.name")
.offset(offset)
.limit(limit)
def self.calculate_distance2a(orig_lat, orig_lon, dist = nil)
lat1, lon1, lat2, lon2 = the_square(orig_lat, orig_lon, dist)
self.select(" healthcare_centers.id,
ROUND(
6371 * 2 * ASIN ( SQRT (
POWER( SIN((#{orig_lat} - healthcare_centers.latitude)*pi()/180 / 2),2)
+ COS(#{orig_lat} * pi()/180)
* COS(healthcare_centers.latitude *pi()/180)
* POWER(SIN((#{orig_lon} - healthcare_centers.longitude) *pi()/180 / 2), 2)
) ),2) as distance"
)
.where("healthcare_centers.longitude between #{lon1} and #{lon2}")
.where("healthcare_centers.latitude between #{lat1} and #{lat2}")
.order("distance")
end
This query give me the list of clinics with high rating (because currenly I am checking user not logged in) but gives me greater than 25KM as first result
Because on my DB more than 25KM have high rating
Can you please guide me on how to include those distance condition
1) <= 25
2) > 25 && <= 100
3) > 100 (should be in asc order)
Consider If less than 25KM we have 2 Clicnics.Clinic A 4.3(rating), 2Km distance, Clinic B 3(rating), 1.5km based on rating Clinic A should list on top.
You should calculate distance as a column alias an then
HealthcareCenter.where("healthcare_centers.name LIKE ?", "%#{query}%")
.where(type_id: type_id)
.order("healthcare_centers.overall_rating DESC,
distance,
healthcare_centers.name")

Distance by zip code formula

I found this formula and it works, however what i trying to do is to give ability to filter by distance from his ZIP code.
I found formula that calculates distance between two latitude and longitude coordinates.
(3956 * ACOS(COS(RADIANS(start_lat)) * COS(RADIANS(end_lat)) * COS(RADIANS(end_lon) - RADIANS(start_lon)) + SIN(RADIANS(start_lat)) * SIN(RADIANS(end_lat))))
I have filter on page that sends following info
$_POST["start_latitude"] = 34.023179;
$_POST["start_longitude"] = -118.303965;
$_POST["max_distance"] = 50;
If i do
SELECT (3956 * ACOS(COS(RADIANS({$_POST["start_latitude"]})) * COS(RADIANS(34.018626))
* COS(RADIANS(-118.249978) - RADIANS({$_POST["start_longitude"]}))
+ SIN(RADIANS({$_POST["start_latitude"]})) * SIN(RADIANS(34.018626))))
Will output distance as number of miles 4 miles in this case.
How can i convert this formula for my goal to find places no longer than say 50 miles from coordinates entered? I know all need to be done is change of places in formula, but i am not good with school math.
SELECT place_name FROM places
WHERE place_latitude = ? AND place_longitude = ?
EDIT:
I have places table where i got 1000 records in format
id place_name latitude longitude
1 xxx 432423 -43432
2 yyy 523452 -54353
3 zzz 553453 -53422
etc.
So the formula has to do something like
SELECT place_name FROM places
(CALCULATE each place distance from
$_POST["start_latitude"] and $_POST["start_longitude"]
and select only ones that) < 50
Put the distance formula into the WHERE clause:
SELECT place_name
FROM places
WHERE (3956 * ACOS(COS(RADIANS(:start_latitude)) * COS(RADIANS(latitude)) * COS(RADIANS(-longitude) - RADIANS(:start_longitude)) + SIN(RADIANS(:start_latitude)) * SIN(RADIANS(latitude))))
< :max_distance
This resouce and web service usefull, check it:
http://www.codebump.com/services/PlaceLookup.asmx
function calc_distance($point1, $point2)
{
$radius = 3958; // Earth's radius (miles)
$deg_per_rad = 57.29578; // Number of degrees/radian (for conversion)
$distance = ($radius * pi() * sqrt(
($point1['lat'] - $point2['lat'])
* ($point1['lat'] - $point2['lat'])
+ cos($point1['lat'] / $deg_per_rad) // Convert these to
* cos($point2['lat'] / $deg_per_rad) // radians for cos()
* ($point1['long'] - $point2['long'])
* ($point1['long'] - $point2['long'])
) / 180);
return $distance; // Returned using the units used for $radius.
}
EDIT
Check the page :
http://www.mssqltips.com/sqlservertip/2690/calculate-the-geographical-distance-between-two-cities-in-sql-server/
--
DECLARE #98001 GEOGRAPHY;
DECLARE #Patzip GEOGRAPHY;
SELECT #98001 = Coordinates FROM ZipCodeLKUP INNER JOIN
Facility ON ZipCodeLKUP.ZipCode = Facility.ZipCode
Where Facility.ZipCode=98001
SELECT #Patzip = Coordinates FROM ZipCodeLKUP INNER JOIN
HIDIMV_year ON ZipCodeLKUP.ZipCode = HIDIMV_year .Patzip
where PATZIP in ('98001', '98466','97202')
SELECT #98001.STDistance(#Patzip)/1000 AS [Distance in KM]

Given longitude and latitude of two areas, how to find the distance between them in meters. How to query in SQL..?

I basically have two table with ID, Area.Longitude and latitude column. I need to find the difference in distances that are more than 20 meters apart for each area.
Table 1
##[ID] [Area] [Latitude] [Longitude]##
ID1 Area1 51.51141557 -0.138341652
ID2 Area2 51.50950278 -0.156438192
ID3 Area3 51.5071583 -0.153418937
Table 2
##[ID] [Area] [Latitude] [Longitude]##
ID1 Area1 51.50819747 -0.141020749
ID2 Area2 51.50781548 -0.14294574
ID3 Area3 51.51286329 -0.14765827
I want to compare table 1( Area 1) and Table 2 ( Area 2) find the difference between them (given in longitude and latitude) and display the results in meters (their distance between them)
Result##
##[ID] [Area] [Distance apart in Meter]##
ID1 Area1 5 meter
ID2 Area2 10 meter
ID3 Area3 20 meter
How do I write an sql query for to achieve this. Please help
This is the oracle function We use in our projects to calculate distance. You please modify syntax accordingly to suit mysql / sql-server
create or replace
FUNCTION CALC_DISTANCE (Lat1 IN NUMBER,
Lon1 IN NUMBER,
Lat2 IN NUMBER,
Lon2 IN NUMBER) RETURN NUMBER IS
-- Convert degrees to radians
DEGTORAD NUMBER := 57.29577951;
--Radius NUMBER := 6387.7; -- For km
Radius NUMBER := 6387700 -- For metres
BEGIN
RETURN(NVL(Radius,0) * ACOS((sin(NVL(Lat1,0) / DegToRad) * SIN(NVL(Lat2,0) / DegToRad)) +
(COS(NVL(Lat1,0) / DegToRad) * COS(NVL(Lat2,0) / DegToRad) *
Cos(Nvl(Lon2,0) / Degtorad - Nvl(Lon1,0)/ Degtorad))));
END;
To use this function to get in meters, you can probably use,
SELECT
CALC_DISTANCE ('51.51141557' ,'-0.138341652',
'51.50819747', '-0.141020749') AS DISTANCE
FROM DUAL;

SQL Server - single Inline Query - (decimal remainder of x/y (rounded to 6 characters) ) / z

Can I ask for help on a SQL Statement please, I have to do the calculation inline and cannot declare variables for it
Calculation:
-91000000 / 2700000 = -33.7037037037
I need the remainder (7037037037 - but only up to 6 characters ) to be multiplied by 15000
703703 / 15000 = Final Answer of 49.913533
I thought I could do this:
select cast(ParseName(abs(cast(-91000000 as decimal)/ 2700000 ) %1,1) as numeric(8,8)) / 15000
WITH cte AS
(
SELECT -91000000 AS x, 2700000 AS y
)
SELECT ABS(ROUND((CAST(x AS decimal) / CAST(y AS decimal)) - (x/y), 6)) * 1000000 / 15000 FROM CTE

Fastest Way to Find Distance Between Two Lat/Long Points

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 and many other points via a query. It's not as fast as I want it to be especially with 100+ hits a second.
Is there a faster query or possibly a faster system other than mysql for this? I'm using this query:
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
HAVING distance < 10
ORDER BY distance;
Note: The provided distance is in Miles. If you need Kilometers, use 6371 instead of 3959.
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(#lat)))
, ' '
, #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)
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.
I needed to solve similar problem (filtering rows by distance from single point) and by combining original question with answers and comments, I came up with solution which perfectly works for me on both MySQL 5.6 and 5.7.
SELECT
*,
(6371 * ACOS(COS(RADIANS(56.946285)) * COS(RADIANS(Y(coordinates)))
* COS(RADIANS(X(coordinates)) - RADIANS(24.105078)) + SIN(RADIANS(56.946285))
* SIN(RADIANS(Y(coordinates))))) AS distance
FROM places
WHERE MBRContains
(
LineString
(
Point (
24.105078 + 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 + 15 / 111.133
),
Point (
24.105078 - 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 - 15 / 111.133
)
),
coordinates
)
HAVING distance < 15
ORDER By distance
coordinates is field with type POINT and has SPATIAL index
6371 is for calculating distance in kilometres
56.946285 is latitude for central point
24.105078 is longitude for central point
15 is maximum distance in kilometers
In my tests, MySQL uses SPATIAL index on coordinates field to quickly select all rows which are within rectangle and then calculates actual distance for all filtered places to exclude places from rectangles corners and leave only places inside circle.
This is visualisation of my result:
Gray stars visualise all points on map, yellow stars are ones returned by MySQL query. Gray stars inside corners of rectangle (but outside circle) were selected by MBRContains() and then deselected by HAVING clause.
The following MySQL function was posted on this blog post. I haven't tested it much, but from what I gathered from the post, if your latitude and longitude fields are indexed, this may work well for you:
DELIMITER $$
DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $$
CREATE FUNCTION get_distance_in_miles_between_geo_locations(
geo1_latitude decimal(10,6), geo1_longitude decimal(10,6),
geo2_latitude decimal(10,6), geo2_longitude decimal(10,6))
returns decimal(10,3) DETERMINISTIC
BEGIN
return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180)
+ COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180)
* COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI())
* 60 * 1.1515);
END $$
DELIMITER ;
Sample usage:
Assuming a table called places with fields latitude & longitude:
SELECT get_distance_in_miles_between_geo_locations(-34.017330, 22.809500,
latitude, longitude) AS distance_from_input FROM places;
if you are using MySQL 5.7.*, then you can use st_distance_sphere(POINT, POINT).
Select st_distance_sphere(POINT(-2.997065, 53.404146 ), POINT(58.615349, 23.56676 ))/1000 as distcance
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) *
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) *
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)*
pi()/180))))*180/pi())*60*1.1515 ) as distance
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X
ORDER BY ID DESC
This is the distance calculation query between to points in MySQL, I have used it in a long database, it it working perfect! Note: do the changes (database name, table name, column etc) as per your requirements.
set #latitude=53.754842;
set #longitude=-2.708077;
set #radius=20;
set #lng_min = #longitude - #radius/abs(cos(radians(#latitude))*69);
set #lng_max = #longitude + #radius/abs(cos(radians(#latitude))*69);
set #lat_min = #latitude - (#radius/69);
set #lat_max = #latitude + (#radius/69);
SELECT * FROM postcode
WHERE (longitude BETWEEN #lng_min AND #lng_max)
AND (latitude BETWEEN #lat_min and #lat_max);
source
select
(((acos(sin(('$latitude'*pi()/180)) * sin((`lat`*pi()/180))+cos(('$latitude'*pi()/180))
* cos((`lat`*pi()/180)) * cos((('$longitude'- `lng`)*pi()/180))))*180/pi())*60*1.1515)
AS distance
from table having distance<22;
A MySQL function which returns the number of metres between the two coordinates:
CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000
To return the value in a different format, replace the 6371000 in the function with the radius of Earth in your choice of unit. For example, kilometres would be 6371 and miles would be 3959.
To use the function, just call it as you would any other function in MySQL. For example, if you had a table city, you could find the distance between every city to every other city:
SELECT
`city1`.`name`,
`city2`.`name`,
ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
`city` AS `city1`
JOIN
`city` AS `city2`
The full code with details about how to install as MySQL plugin are here: https://github.com/lucasepe/lib_mysqludf_haversine
I posted this last year as comment. Since kindly #TylerCollier suggested me to post as answer, here it is.
Another way is to write a custom UDF function that returns the haversine distance from two points. This function can take in input:
lat1 (real), lng1 (real), lat2 (real), lng2 (real), type (string - optinal - 'km', 'ft', 'mi')
So we can write something like this:
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2) < 40;
to fetch all records with a distance less then 40 kilometers. Or:
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2, 'ft') < 25;
to fetch all records with a distance less then 25 feet.
The core function is:
double
haversine_distance( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ) {
double result = *(double*) initid->ptr;
/*Earth Radius in Kilometers.*/
double R = 6372.797560856;
double DEG_TO_RAD = M_PI/180.0;
double RAD_TO_DEG = 180.0/M_PI;
double lat1 = *(double*) args->args[0];
double lon1 = *(double*) args->args[1];
double lat2 = *(double*) args->args[2];
double lon2 = *(double*) args->args[3];
double dlon = (lon2 - lon1) * DEG_TO_RAD;
double dlat = (lat2 - lat1) * DEG_TO_RAD;
double a = pow(sin(dlat * 0.5),2) +
cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD) * pow(sin(dlon * 0.5),2);
double c = 2.0 * atan2(sqrt(a), sqrt(1-a));
result = ( R * c );
/*
* If we have a 5th distance type argument...
*/
if (args->arg_count == 5) {
str_to_lowercase(args->args[4]);
if (strcmp(args->args[4], "ft") == 0) result *= 3280.8399;
if (strcmp(args->args[4], "mi") == 0) result *= 0.621371192;
}
return result;
}
A fast, simple and accurate (for smaller distances) approximation can be done with a spherical projection. At least in my routing algorithm I get a 20% boost compared to the correct calculation. In Java code it looks like:
public double approxDistKm(double fromLat, double fromLon, double toLat, double toLon) {
double dLat = Math.toRadians(toLat - fromLat);
double dLon = Math.toRadians(toLon - fromLon);
double tmp = Math.cos(Math.toRadians((fromLat + toLat) / 2)) * dLon;
double d = dLat * dLat + tmp * tmp;
return R * Math.sqrt(d);
}
Not sure about MySQL (sorry!).
Be sure you know about the limitation (the third param of assertEquals means the accuracy in kilometers):
float lat = 24.235f;
float lon = 47.234f;
CalcDistance dist = new CalcDistance();
double res = 15.051;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
res = 150.748;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 1, lon + 1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 1, lon + 1), 1e-2);
res = 1527.919;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 10, lon + 10), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 10, lon + 10), 10);
Here is a very detailed description of Geo Distance Search with MySQL a solution based on implementation of Haversine Formula to mysql. The complete solution description with theory, implementation and further performance optimization. Although the spatial optimization part didn't work correct in my case.
http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
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:
the use of abs in the select statement on p8. I just omitted abs and it worked.
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.
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515 as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";
Using mysql
SET #orig_lon = 1.027125;
SET #dest_lon = 1.027125;
SET #orig_lat = 2.398441;
SET #dest_lat = 2.398441;
SET #kmormiles = 6371;-- for distance in miles set to : 3956
SELECT #kmormiles * ACOS(LEAST(COS(RADIANS(#orig_lat)) *
COS(RADIANS(#dest_lat)) * COS(RADIANS(#orig_lon - #dest_lon)) +
SIN(RADIANS(#orig_lat)) * SIN(RADIANS(#dest_lat)),1.0)) as distance;
See: https://andrew.hedges.name/experiments/haversine/
See: https://stackoverflow.com/a/24372831/5155484
See: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
NOTE: LEAST is used to avoid null values as a comment suggested on https://stackoverflow.com/a/24372831/5155484
I really liked #Māris Kiseļovs solution, but I like many others may have the Lat and lng's POINTS reversed from his example. In generalising it I though I would share it. In my case I need to find all the start_points that are within a certain radius of an end_point.
I hope this helps someone.
SELECT #LAT := ST_X(end_point), #LNG := ST_Y(end_point) FROM routes WHERE route_ID = 280;
SELECT
*,
(6371e3 * ACOS(COS(RADIANS(#LAT)) * COS(RADIANS(ST_X(start_point)))
* COS(RADIANS(ST_Y(start_point)) - RADIANS(#LNG)) + SIN(RADIANS(#LAT))
* SIN(RADIANS(ST_X(start_point))))) AS distance
FROM routes
WHERE MBRContains
(
LineString
(
Point (
#LNG + 15 / (111.320 * COS(RADIANS(#LAT))),
#LAT + 15 / 111.133
),
Point (
#LNG - 15 / (111.320 * COS(RADIANS(#LAT))),
#LAT - 15 / 111.133
)
),
POINT(ST_Y(end_point),ST_X(end_point))
)
HAVING distance < 100
ORDER By distance;