Haversine formula delivers incorrect result in Netlogo - gis

I am using the gis extension of Netlogo and am trying to calculate distances between agents in km.
With gis:envelope-of I can get agent's positions in degree (!) latitude and longitude, so I want to use the Haversine formula to calculate distances. The projection of my underlying GIS-shapefile is WGS 84.
What I've written for two exemplary points is the following:
to haversine
let lata 31.8930795682288
let longa 36.0898369172224
let latb 31.9964498913126
let longb 36.0318329821497
let rad (pi / 180)
let result acos (cos (rad * lata) * cos(rad * (latb )) + sin ( rad * (lata) )* sin(rad * (latb)) * cos(rad * (longa - longb))) * 6371
print result
end
It yields a distance of 11.5 km, while the same formula applied in excel (using the RADIANS-formula instead of "rad") leaves me with 12.73 km, a number that this website http://www.movable-type.co.uk/scripts/latlong.html confirms.
Any idea what I'm doing wrong here? Thanks in advance.

The formula that computes result is not correct. It looks like you reversed sin and cos. Use
let result acos(sin(rad * lata) * sin(rad * latb) + cos(rad * lata) * cos(rad * latb) * cos(rad * (longa - longb))) * 6371
Also, according to the docs that I've read (e.g. cos and acos), the netlogo trig functions use degrees, so the calculation should be
let result rad * acos(sin(lata) * sin(latb) + cos(lata) * cos(latb) * cos(longa - longb)) * 6371

Related

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]

Explanation of SQL query

I am having some issues with this SQL query. This actually comes from a PROCEDURE that is dealing with spatial calculations. The procedure finds locations within a certain radius. Really I am having a hard time understanding what is going on. I just need to rewrite it for my purposes if someone could help explain a couple of things. I will be forever grateful to whoever attempts to help. Thanks so much in advance!
Link To Source: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Full Procedure - I Just Need Some Explanation Help On The Actual SELECT Statement - Is Here Just For Some Clarification So Whoever Is Reading Can Get A Better Understanding
CREATE PROCEDURE geodist (IN userid int, IN dist int)BEGINdeclare mylon double; declare mylat double;declare lon1 float; declare lon2 float;declare lat1 float; declare lat2 float;
-- get the original lon and lat for the userid
select longitude, latitude into mylon, mylat from users5where id=userid limit 1;
-- calculate lon and lat for the rectangle:
set lon1 = mylon-dist/abs(cos(radians(mylat))*69);set lon2 = mylon+dist/abs(cos(radians(mylat))*69);set lat1 = mylat-(dist/69); set lat2 = mylat+(dist/69);
-- run the query:
SELECT destination.*,3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) asdistance FROM users destination, users originWHERE origin.id=userid
and destination.longitude between lon1 and lon2 and destination.latitude between lat1 and lat2
having distance < dist ORDER BY Distance limit 10;END $$
Full Query The Part I Need a Little Clarification On:
SELECT destination.*,3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) as distance FROM users destination, users origin WHERE origin.id=userid
and locations.longitude between lon1 and lon2 and locations.latitude between lat1 and lat2
having distance < dist ORDER BY Distance;
First Part:
SELECT destination.*
Question: What is with the period and * after the word destination? Sure we are selecting destination but is this concatenating? Like prefixing the word destination to every column in the table?
Next Part:
3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) as distance
Question: Is this just performing the calculation and storing it as the keyword destination? The keyword "as" is throwing me off here. Is it searching the database for a column destination?
Third Part:
FROM users destination, users origin WHERE origin.id=userid
Question: a little confused on where the query is headed. users destination? users origin? What is this grabbing?
Last Part:
having distance < dist ORDER BY Distance;
Question: Confused about the "having" keyword and where having distance comes into play. Is it trying to grab distance from the table because this is something that is obviously calculated on the fly.
First Part
* selects all columns. You can specify all columns for a particular table using the dot notation as you would use to specify an individual column for a table. For example:
SELECT t1.* FROM t1 NATURAL JOIN t2
Will only SELECT columns from t1 even though t2 is used in the JOIN.
Next Part
This just does a mathematical calculation on some columns and aliases the result in distance (not "destination"). You can reference the result as distance in the result set. The AS keyword sets the column alias, but it is optional.
Third Part
destination and origin are aliases. They are leaving off the optional AS keyword. It would be the same to write:
FROM users AS destination, users AS origin
The users table is being renamed for the duration of the query so it can be referenced by the alias but also to avoid the collision of two of the same table name in the query which would be invalid.
Last Part
distance is an alias for the mathematical calculation above. HAVING is like WHERE, but it has to be used for aggregation (e.g. GROUP BY). I could be wrong, but I don't think it's necessary in this query.
I feel like I'm missing something here... Why doesn't the code use the 'mylon' and 'mylat' in the math part of the final query?
So, this:
SELECT destination.*,
3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -
destination.lat) * pi()/180 / 2), 2) +
COS(orig.lat * pi()/180) * COS(destination.lat * pi()/180) *
POWER(SIN((orig.lon -destination.lon) * pi()/180 / 2), 2) ))
as distance
FROM users AS destination, users AS orig
WHERE origin.id=userid
AND destination.longitude BETWEEN lon1 AND lon2
AND destination.latitude BETWEEN lat1 AND lat2
HAVING distance < dist ORDER BY Distance limit 10;
becomes this:
SELECT destination.*,
3956 * 2 * ASIN(SQRT( POWER(SIN((mylat -
dest.lat) * pi()/180 / 2), 2) +
COS(mylat * pi()/180) * COS(dest.lat * pi()/180) *
POWER(SIN((mylon -dest.lon) * pi()/180 / 2), 2) ))
as distance
FROM users AS destination
WHERE
destination.longitude BETWEEN lon1 AND lon2
AND destination.latitude BETWEEN lat1 AND lat2
HAVING distance < dist ORDER BY Distance limit 10;
edit: my best guess is that "origin.id=userid" should read "origin.id=destination.id". Am I correct in thinking that this join is done to filter out all users who are outside of the "distance box" -- in order to avoid calculating the actual geo distance?

Determine longitudes and latitudes within a range

I have locations in my database. A location has the attributes latitude and longitude (taken from google maps, example: 48.809591).
Is there any query that could help me retrieve the locations within a range of another location?
Example:
I have the location A with latitude = 48.809591, and longitude = 2.124009 and want to retrieve all location objects in my database that are within 5 miles of location A
My first thought was to retrieve the locations in a square where location.latitude < A.latitude + 5 miles and location.latitude > A.latitude - 5 miles and location.longitude < A.longitude + 5 miles and location.longitude > A.longitude - 5 miles, and then remove the irrelevant locations from the returned array with the help of something like http://www.movable-type.co.uk/scripts/latlong.html
Any ideas?
Just in case you're using MySQL as your DBMS1, you may be interested in checking out the following presentation:
Geo/Spatial Search with MySQL2 by Alexander Rubin
The author describes how you can use the Haversine Formula in MySQL to order spatial data by proximity and limit the results to a defined radius. More importantly, he also describes how to avoid a full table scan for such queries, using traditional indexes on the latitude and longitude columns.
1 Even if you aren't, this is still interesting and applicable.
2 There is also a pdf version of the presentation.
The calculation you want, i think, is called the great circle distance:
http://en.wikipedia.org/wiki/Great-circle_distance
You would need a distance function.
For SQL Server it would look something like this (note that distance is in kilometers),
CREATE FUNCTION distance
(
#startLatitude float,
#startLongitude float,
#endLatitude float,
#endLongitude float
)
RETURNS float
AS
BEGIN
DECLARE #distance float;
set #distance =
6371 * 2 * atn2(sqrt(power(sin(pi() / 180 * (#endLatitude - #startLatitude) / 2), 2) +
power(cos(#startLatitude * pi() / 180), 2) *
power(sin(pi() / 180 * (#endLongitude - #startLongitude) / 2), 2)),
sqrt(1 - power(sin(pi() / 180 * (#endLatitude - #startLatitude) / 2), 2) +
power(cos(#startLatitude * pi() / 180), 2) *
power(sin(pi() / 180 * (#endLongitude - #startLongitude) / 2), 2)));
RETURN #distance
END

how to get accurate or (near accurate) distance between two places? Mysql/PHP

SELECT postcode, lat, lng,
truncate(
(degrees(acos
(sin(radians(lat))
*
sin( radians('.$latitude.'))
+
cos(radians(lat))
*
cos( radians('.$latitude.'))
*
cos( radians(lng - ('.$longitude.')))
)
)
* 69.172), 2)
as distance
FROM myData
This query calculates distance (in miles). But when I check distance for same lat and longitude at google maps my result doesnt match. If the distance is around 10 miles then my result is a bit accurate, but over that it gets wrong (for example, my result showed 13 miles and google showed 22 miles for same post code values)
I got this query from http://forums.mysql.com/read.php?23,3868,3868#msg-3868
How can I get it accurate. Any ideas?
Thanks for help.
UPDATE
I tried #Blixt code in PHP. Picked up 2 sample postcodes and their lats longs
//B28 9ET
$lat1 = 52.418819;
$long1 = -1.8481053;
//CV5 8BX
$lat2 = 52.4125573;
$long2 = -1.5407743;
$dtr = M_PI / 180;
$latA = $lat1 * $dtr;
$lonA = $long1 * $dtr;
$latB = $lat2 * $dtr;
$lonB = $long2 * $dtr;
$EarthRadius = 3958.76; //miles
echo $distance = $EarthRadius * acos(cos($latA) * cos($latB) * cos($lonB - $lonA) + sin($latA) * sin($latB));
Results:
My app - 12.95 miles
Google - 17.8 miles
Any ideas how to get it right?
Have a look at this source code. When I tested it against various other measurement services it seemed to get the same results. It's C# but the math should be easy enough to convert.
Here are the relevant pieces:
public const double EarthRadius = 6371.0072; // Kilometers
//public const double EarthRadius = 3958.76; // Miles
/* ... */
const double dtr = Math.PI / 180;
double latA = this.Latitude * dtr;
double lonA = this.Longitude * dtr;
double latB = other.Latitude * dtr;
double lonB = other.Longitude * dtr;
return GpsLocation.EarthRadius * Math.Acos(Math.Cos(latA) * Math.Cos(latB) * Math.Cos(lonB - lonA) + Math.Sin(latA) * Math.Sin(latB));
Note: The Earth is not perfectly spherical, so the constants used may differ. There is no easy way to make the measurements truly exact. See Wikipedia.

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;