Haversine query into Oracle - sql

I'm currently having to go through my queries and transfer them over to using Oracle rather than SQLSERVER and i'm a bit stuck with this query which i'm using from here
SELECT TOP 1 * FROM ( SELECT o.outcode AS lead_postcode, v.location,
v.location_name, v.outcode AS venue_postcode, 6371.0E *
( 2.0E *asin(case when 1.0E < (sqrt(square(sin(((RADIANS(CAST(o.lat AS FLOAT)))-
(RADIANS(CAST(v.lat AS FLOAT))))/2.0E)) + (cos(RADIANS(CAST(v.lat AS FLOAT)))
* cos(RADIANS(CAST(o.lat AS FLOAT))) * square(sin(((RADIANS(CAST(o.lng AS FLOAT)))-
(RADIANS(CAST(v.lng AS FLOAT))))/2.0E))))) then 1.0E else
(sqrt(square(sin(((RADIANS(CAST(o.lat AS FLOAT)))-(RADIANS(CAST(v.lat AS FLOAT))))
/2.0E)) + (cos(RADIANS(CAST(v.lat AS FLOAT))) * cos(RADIANS(CAST(o.lat AS FLOAT)))
* square(sin(((RADIANS(CAST(o.lng AS FLOAT)))-(RADIANS(CAST(v.lng AS FLOAT))))
/2.0E))))) end )) AS distance FROM venue_postcodes v, uk_postcodes o
WHERE o.outcode = #nrpostcode ) i WHERE distance<100 ORDER BY distance
Now I know this is a horrible query to look at but Oracle seems to be having a lot of problems with it.
Firstly it doesn't like the E in 6371E and all the subsequent E's
Secondly it doesn't like the square function so I decided to use the power function but this still gave me errors.
Thirdly it doesn't like the radians function
Fourthly it doesn't like the TOP 1 part so I had changed this to use ROWNUM in the WHERE clause
I'm completely lost as to what to do here.
Any ideas as to what I can do to make it work?
Thanks in advance

I'd recommend you take a slightly different approach.
Check out this site: http://psoug.org/reference/functions.html
Look for the part referring to "calc distance"

I know how to do it in SQL Server, which should be easy enough to port over to Oracle:
Here's a UDF I created to get the approximate crows flight distance between two zip codes using the Haversine formula:
ALTER FUNCTION [dbo].[fn_GetZipDistanceMiles](
#ZipFrom VARCHAR(20),
#ZipTo VARCHAR(20)
)
RETURNS FLOAT
AS
BEGIN
DECLARE #Latitude1 FLOAT
DECLARE #Longitude1 FLOAT
DECLARE #Latitude2 FLOAT
DECLARE #Longitude2 FLOAT
SELECT #Latitude1 = Latitude,
#Longitude1 = Longitude
FROM ZipCode
WHERE ZipCode = #ZipFrom
SELECT #Latitude2 = Latitude,
#Longitude2 = Longitude
FROM ZipCode
WHERE ZipCode = #ZipTo
-- CONSTANTS
DECLARE #EarthRadiusInMiles FLOAT
SET #EarthRadiusInMiles = 3963.1
-- RADIANS conversion
DECLARE #Lat1Radians FLOAT
DECLARE #Long1Radians FLOAT
DECLARE #Lat2Radians FLOAT
DECLARE #Long2Radians FLOAT
SET #Lat1Radians = #Latitude1 * PI() / 180
SET #Long1Radians = #Longitude1 * PI() / 180
SET #Lat2Radians = #Latitude2 * PI() / 180
SET #Long2Radians = #Longitude2 * PI() / 180
RETURN ACOS(COS(#Lat1Radians) * COS(#Long1Radians) * COS(#Lat2Radians) * COS(#Long2Radians) + COS(#Lat1Radians) * SIN(#Long1Radians) * COS(#Lat2Radians) * SIN(#Long2Radians) + SIN(#Lat1Radians) * SIN(#Lat2Radians)) * #EarthRadiusInMiles
END

Related

Inverse Distance Weighting algorithm in SQL [duplicate]

I've got a working PHP script that gets Longitude and Latitude values and then inputs them into a MySQL query. I'd like to make it solely MySQL. Here's my current PHP Code:
if ($distance != "Any" && $customer_zip != "") { //get the great circle distance
//get the origin zip code info
$zip_sql = "SELECT * FROM zip_code WHERE zip_code = '$customer_zip'";
$result = mysql_query($zip_sql);
$row = mysql_fetch_array($result);
$origin_lat = $row['lat'];
$origin_lon = $row['lon'];
//get the range
$lat_range = $distance/69.172;
$lon_range = abs($distance/(cos($details[0]) * 69.172));
$min_lat = number_format($origin_lat - $lat_range, "4", ".", "");
$max_lat = number_format($origin_lat + $lat_range, "4", ".", "");
$min_lon = number_format($origin_lon - $lon_range, "4", ".", "");
$max_lon = number_format($origin_lon + $lon_range, "4", ".", "");
$sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon' AND ";
}
Does anyone know how to make this entirely MySQL? I've browsed the Internet a bit but most of the literature on it is pretty confusing.
From Google Code FAQ - Creating a Store Locator with PHP, MySQL & Google Maps:
Here's the SQL statement that will find the closest 20 locations that are within a radius of 25 miles to the 37, -122 coordinate. It calculates the distance based on the latitude/longitude of that row and the target latitude/longitude, and then asks for only rows where the distance value is less than 25, orders the whole query by distance, and limits it to 20 results. To search by kilometers instead of miles, replace 3959 with 6371.
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) )
* cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin(radians(lat)) ) ) AS distance
FROM markers
HAVING distance < 25
ORDER BY distance
LIMIT 0 , 20;
$greatCircleDistance = acos( cos($latitude0) * cos($latitude1) * cos($longitude0 - $longitude1) + sin($latitude0) * sin($latitude1));
with latitude and longitude in radian.
so
SELECT
acos(
cos(radians( $latitude0 ))
* cos(radians( $latitude1 ))
* cos(radians( $longitude0 ) - radians( $longitude1 ))
+ sin(radians( $latitude0 ))
* sin(radians( $latitude1 ))
) AS greatCircleDistance
FROM yourTable;
is your SQL query
to get your results in Km or miles, multiply the result with the mean radius of Earth (3959 miles,6371 Km or 3440 nautical miles)
The thing you are calculating in your example is a bounding box.
If you put your coordinate data in a spatial enabled MySQL column, you can use MySQL's build in functionality to query the data.
SELECT
id
FROM spatialEnabledTable
WHERE
MBRWithin(ogc_point, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
If you add helper fields to the coordinates table, you can improve response time of the query.
Like this:
CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)
If you're using TokuDB, you'll get even better performance if you add clustering
indexes on either of the predicates, for example, like this:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
You'll need the basic lat and lon in degrees as well as sin(lat) in radians, cos(lat)*cos(lon) in radians and cos(lat)*sin(lon) in radians for each point.
Then you create a mysql function, smth like this:
CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
`cos_cos1` FLOAT, `cos_sin1` FLOAT,
`sin_lat2` FLOAT,
`cos_cos2` FLOAT, `cos_sin2` FLOAT)
RETURNS float
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
END
This gives you the distance.
Don't forget to add an index on lat/lon so the bounding boxing can help the search instead of slowing it down (the index is already added in the CREATE TABLE query above).
INDEX `lat_lon_idx` (`lat`, `lon`)
Given an old table with only lat/lon coordinates, you can set up a script to update it like this: (php using meekrodb)
$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');
foreach ($users as $user)
{
$lat_rad = deg2rad($user['lat']);
$lon_rad = deg2rad($user['lon']);
DB::replace('Coordinates', array(
'object_id' => $user['id'],
'object_type' => 0,
'sin_lat' => sin($lat_rad),
'cos_cos' => cos($lat_rad)*cos($lon_rad),
'cos_sin' => cos($lat_rad)*sin($lon_rad),
'lat' => $user['lat'],
'lon' => $user['lon']
));
}
Then you optimize the actual query to only do the distance calculation when really needed, for example by bounding the circle (well, oval) from inside and outside.
For that, you'll need to precalculate several metrics for the query itself:
// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));
Given those preparations, the query goes something like this (php):
$neighbors = DB::query("SELECT id, type, lat, lon,
geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
FROM Coordinates WHERE
lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
// center radian values: sin_lat, cos_cos, cos_sin
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
// min_lat, max_lat, min_lon, max_lon for the outside box
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
// min_lat, max_lat, min_lon, max_lon for the inside box
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
// distance in radians
$distance_rad);
EXPLAIN on the above query might say that it's not using index unless there's enough results to trigger such. The index will be used when there's enough data in the coordinates table.
You can add
FORCE INDEX (lat_lon_idx)
to the SELECT to make it use the index with no regards to the table size, so you can verify with EXPLAIN that it is working correctly.
With the above code samples you should have a working and scalable implementation of object search by distance with minimal error.
I have had to work this out in some detail, so I'll share my result. This uses a zip table with latitude and longitude tables. It doesn't depend on Google Maps; rather you can adapt it to any table containing lat/long.
SELECT zip, primary_city,
latitude, longitude, distance_in_mi
FROM (
SELECT zip, primary_city, latitude, longitude,r,
(3963.17 * ACOS(COS(RADIANS(latpoint))
* COS(RADIANS(latitude))
* COS(RADIANS(longpoint) - RADIANS(longitude))
+ SIN(RADIANS(latpoint))
* SIN(RADIANS(latitude)))) AS distance_in_mi
FROM zip
JOIN (
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
) AS p
WHERE latitude
BETWEEN latpoint - (r / 69)
AND latpoint + (r / 69)
AND longitude
BETWEEN longpoint - (r / (69 * COS(RADIANS(latpoint))))
AND longpoint + (r / (69 * COS(RADIANS(latpoint))))
) d
WHERE distance_in_mi <= r
ORDER BY distance_in_mi
LIMIT 30
Look at this line in the middle of that query:
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
This searches for the 30 nearest entries in the zip table within 50.0 miles of the lat/long point 42.81/-70.81 . When you build this into an app, that's where you put your own point and search radius.
If you want to work in kilometers rather than miles, change 69 to 111.045 and change 3963.17 to 6378.10 in the query.
Here's a detailed writeup. I hope it helps somebody. http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
SELECT *, (
6371 * acos(cos(radians(search_lat)) * cos(radians(lat) ) *
cos(radians(lng) - radians(search_lng)) + sin(radians(search_lat)) * sin(radians(lat)))
) AS distance
FROM table
WHERE lat != search_lat AND lng != search_lng AND distance < 25
ORDER BY distance
FETCH 10 ONLY
for distance of 25 km
I have written a procedure that can calculate the same,
but you have to enter the latitude and longitude in the respective table.
drop procedure if exists select_lattitude_longitude;
delimiter //
create procedure select_lattitude_longitude(In CityName1 varchar(20) , In CityName2 varchar(20))
begin
declare origin_lat float(10,2);
declare origin_long float(10,2);
declare dest_lat float(10,2);
declare dest_long float(10,2);
if CityName1 Not In (select Name from City_lat_lon) OR CityName2 Not In (select Name from City_lat_lon) then
select 'The Name Not Exist or Not Valid Please Check the Names given by you' as Message;
else
select lattitude into origin_lat from City_lat_lon where Name=CityName1;
select longitude into origin_long from City_lat_lon where Name=CityName1;
select lattitude into dest_lat from City_lat_lon where Name=CityName2;
select longitude into dest_long from City_lat_lon where Name=CityName2;
select origin_lat as CityName1_lattitude,
origin_long as CityName1_longitude,
dest_lat as CityName2_lattitude,
dest_long as CityName2_longitude;
SELECT 3956 * 2 * ASIN(SQRT( POWER(SIN((origin_lat - dest_lat) * pi()/180 / 2), 2) + COS(origin_lat * pi()/180) * COS(dest_lat * pi()/180) * POWER(SIN((origin_long-dest_long) * pi()/180 / 2), 2) )) * 1.609344 as Distance_In_Kms ;
end if;
end ;
//
delimiter ;
I can't comment on the above answer, but be careful with #Pavel Chuchuva's answer. That formula will not return a result if both coordinates are the same. In that case, distance is null, and so that row won't be returned with that formula as is.
I'm not a MySQL expert, but this seems to be working for me:
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance
FROM markers HAVING distance < 25 OR distance IS NULL ORDER BY distance LIMIT 0 , 20;
I thought my javascript implementation would be a good reference to:
/*
* Check to see if the second coord is within the precision ( meters )
* of the first coord and return accordingly
*/
function checkWithinBound(coord_one, coord_two, precision) {
var distance = 3959000 * Math.acos(
Math.cos( degree_to_radian( coord_two.lat ) ) *
Math.cos( degree_to_radian( coord_one.lat ) ) *
Math.cos(
degree_to_radian( coord_one.lng ) - degree_to_radian( coord_two.lng )
) +
Math.sin( degree_to_radian( coord_two.lat ) ) *
Math.sin( degree_to_radian( coord_one.lat ) )
);
return distance <= precision;
}
/**
* Get radian from given degree
*/
function degree_to_radian(degree) {
return degree * (Math.PI / 180);
}
calculate distance in Mysql
SELECT (6371 * acos(cos(radians(lat2)) * cos(radians(lat1) ) * cos(radians(long1) -radians(long2)) + sin(radians(lat2)) * sin(radians(lat1)))) AS distance
thus distance value will be calculated and anyone can apply as required.

find all users id and distance into database within 100 meter range by my current location(latitude, longitude)?

i have the following data:
Current location
a. Latitude
b. Longitude
userid
in my database i have latitude and longitude of all users
i want to find out all users within range of 100 meter from my current location.
i have used the some of the example but they are showing distance of 9 meter for the same latitude and longitude up to 6 digit decimal place in latitude and longitude.
i ahve used the following code
SELECT * , ((((acos( sin( ($lat * pi() /180 )) * sin((latitude* pi() /180) ) + cos( ( $lat * pi() /180 )) * cos((latitude* pi() /180)) * cos((( $lon -longitude) * pi() /180 )))) *180 / pi()) *60 * 1.1515)*1.60934) ASdistanceFROM tablename where radar_status<>'0' and having distance<'0.100' ORDER BY distance ASC limit 1,10
Thankyou
Please check the result or check the link
http://forums.asp.net/t/1325947.aspx/1
http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#PolesAnd180thMeridian
if you pass the current user's latitude, longitude and the distance you wants to find, then the following query will do the stuff.
SELECT * FROM(SELECT *,(((acos(sin((#latitude*pi()/180)) * sin((Latitude*pi()/180))+cos((#latitude*pi()/180)) * cos((Latitude*pi()/180)) * cos(((#longitude - Longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) as distance FROM Distances ) t WHERE distance <= #distance
please check this also
$distance = 10;
$latitude = 37.295092;
$longitude = -121.896490;
$sql = "SELECT loc.*, (((acos(sin(($latitude*pi()/180)) * sin((`latitude`*pi()/180))+cos(($latitude*pi()/180)) * cos((`latitude`*pi()/180)) * cos((($longitude - `longitude`)*pi()/180))))*180/pi())*60*1.1515) AS `distance` FROM table_with_lonlat_ref loc HAVING distance < $distance"
there are few formula to do this and looks like you are using the spherical law of cosines. You seem to be converting to radians which is good and looks like you are converting from Earth radius to km fine too - but check out this below:
1) Your SQL setup is in radians by default I assume?
sin(pi/2)=1 etc.
2) I think you are over converting to radians with an extra pi/180 in your query. Try this one as the meat of your query.
try this:
ACOS( SIN( $lat1*pi()/180 ) * SIN ( latitude*pi()/180 ) + COS( $lat*pi()/180 ) * COS ( latitude*pi()/180 ) * COS ( ($lon-longitude)*pi()/180 ) )*63.71
Which can get tricky to figure out the brackets but is
ACOS( SIN(lat1)*sin(lat2) + COS(lat1)*cos(lat2)*cos(lon1-lon2) ) * RadiusEarth
and all angles in radians. Radius Earth in same units as your query filter, i.e. km as you filter on <0.100
Your *60*1.51 with another 1.60 elsewhere could be giving you a spurious result - are these scale factors giving you something other than the Earth Radius?
...Nick

Calculating distance between two points (Latitude, Longitude)

I am trying to calculate the distance between two positions on a map.
I have stored in my data: Longitude, Latitude, X POS, Y POS.
I have been previously using the below snippet.
DECLARE #orig_lat DECIMAL
DECLARE #orig_lng DECIMAL
SET #orig_lat=53.381538 set #orig_lng=-1.463526
SELECT *,
3956 * 2 * ASIN(
SQRT( POWER(SIN((#orig_lat - abs(dest.Latitude)) * pi()/180 / 2), 2)
+ COS(#orig_lng * pi()/180 ) * COS(abs(dest.Latitude) * pi()/180)
* POWER(SIN((#orig_lng - dest.Longitude) * pi()/180 / 2), 2) ))
AS distance
--INTO #includeDistances
FROM #orig dest
I don't however trust the data coming out of this, it seems to be giving slightly inaccurate results.
Some sample data in case you need it
Latitude Longitude Distance
53.429108 -2.500953 85.2981833133896
Could anybody help me out with my code, I don't mind if you want to fix what I already have if you have a new way of achieving this that would be great.
Please state what unit of measurement your results are in.
Since you're using SQL Server 2008, you have the geography data type available, which is designed for exactly this kind of data:
DECLARE #source geography = 'POINT(0 51.5)'
DECLARE #target geography = 'POINT(-3 56)'
SELECT #source.STDistance(#target)
Gives
----------------------
538404.100197555
(1 row(s) affected)
Telling us it is about 538 km from (near) London to (near) Edinburgh.
Naturally there will be an amount of learning to do first, but once you know it it's far far easier than implementing your own Haversine calculation; plus you get a LOT of functionality.
If you want to retain your existing data structure, you can still use STDistance, by constructing suitable geography instances using the Point method:
DECLARE #orig_lat DECIMAL(12, 9)
DECLARE #orig_lng DECIMAL(12, 9)
SET #orig_lat=53.381538 set #orig_lng=-1.463526
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
SELECT *,
#orig.STDistance(geography::Point(dest.Latitude, dest.Longitude, 4326))
AS distance
--INTO #includeDistances
FROM #orig dest
The below function gives distance between two geocoordinates in miles
create function [dbo].[fnCalcDistanceMiles] (#Lat1 decimal(8,4), #Long1 decimal(8,4), #Lat2 decimal(8,4), #Long2 decimal(8,4))
returns decimal (8,4) as
begin
declare #d decimal(28,10)
-- Convert to radians
set #Lat1 = #Lat1 / 57.2958
set #Long1 = #Long1 / 57.2958
set #Lat2 = #Lat2 / 57.2958
set #Long2 = #Long2 / 57.2958
-- Calc distance
set #d = (Sin(#Lat1) * Sin(#Lat2)) + (Cos(#Lat1) * Cos(#Lat2) * Cos(#Long2 - #Long1))
-- Convert to miles
if #d <> 0
begin
set #d = 3958.75 * Atan(Sqrt(1 - power(#d, 2)) / #d);
end
return #d
end
The below function gives distance between two geocoordinates in kilometres
CREATE FUNCTION dbo.fnCalcDistanceKM(#lat1 FLOAT, #lat2 FLOAT, #lon1 FLOAT, #lon2 FLOAT)
RETURNS FLOAT
AS
BEGIN
RETURN ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0))*6371
END
The below function gives distance between two geocoordinates in kilometres
using Geography data type which was introduced in sql server 2008
DECLARE #g geography;
DECLARE #h geography;
SET #g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);
SET #h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);
SELECT #g.STDistance(#h);
Usage:
select [dbo].[fnCalcDistanceKM](13.077085,80.262675,13.065701,80.258916)
Reference: Ref1,Ref2
It looks like Microsoft invaded brains of all other respondents and made them write as complicated solutions as possible.
Here is the simplest way without any additional functions/declare statements:
SELECT geography::Point(LATITUDE_1, LONGITUDE_1, 4326).STDistance(geography::Point(LATITUDE_2, LONGITUDE_2, 4326))
Simply substitute your data instead of LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2 e.g.:
SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c
Create Function [dbo].[DistanceKM]
(
#Lat1 Float(18),
#Lat2 Float(18),
#Long1 Float(18),
#Long2 Float(18)
)
Returns Float(18)
AS
Begin
Declare #R Float(8);
Declare #dLat Float(18);
Declare #dLon Float(18);
Declare #a Float(18);
Declare #c Float(18);
Declare #d Float(18);
Set #R = 6367.45
--Miles 3956.55
--Kilometers 6367.45
--Feet 20890584
--Meters 6367450
Set #dLat = Radians(#lat2 - #lat1);
Set #dLon = Radians(#long2 - #long1);
Set #a = Sin(#dLat / 2)
* Sin(#dLat / 2)
+ Cos(Radians(#lat1))
* Cos(Radians(#lat2))
* Sin(#dLon / 2)
* Sin(#dLon / 2);
Set #c = 2 * Asin(Min(Sqrt(#a)));
Set #d = #R * #c;
Return #d;
End
GO
Usage:
select dbo.DistanceKM(37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875)
Outputs:
0,02849639
You can change #R parameter with commented floats.
As you're using SQL 2008 or later, I'd recommend checking out the GEOGRAPHY data type. SQL has built in support for geospatial queries.
e.g. you'd have a column in your table of type GEOGRAPHY which would be populated with a geospatial representation of the coordinates (check out the MSDN reference linked above for examples). This datatype then exposes methods allowing you to perform a whole host of geospatial queries (e.g. finding the distance between 2 points)
In addition to the previous answers, here is a way to calculate the distance inside a SELECT:
CREATE FUNCTION Get_Distance
(
#La1 float , #Lo1 float , #La2 float, #Lo2 float
)
RETURNS TABLE
AS
RETURN
-- Distance in Meters
SELECT GEOGRAPHY::Point(#La1, #Lo1, 4326).STDistance(GEOGRAPHY::Point(#La2, #Lo2, 4326))
AS Distance
GO
Usage:
select Distance
from Place P1,
Place P2,
outer apply dbo.Get_Distance(P1.latitude, P1.longitude, P2.latitude, P2.longitude)
Scalar functions also work but they are very inefficient when computing large amount of data.
I hope this might help someone.

SQL Select: toilets within 50KM of a certain position?

I need to find toilets around me, say within 50KM, and I have my position in term of latitude and longitude, toilets in database looks like:
Toilet ID (primary)
Address
Latitude
Longitude
my location: my_lat, my_lon
is it possible to construct a statement that returns all toilets within 50KM of (my_lat, my_lon)? Something like
select * from ToiletTable where
SQRT((Latitude - my_lat)^2 + (Longitude - my_lon)^2) < 50
Thanks!
You are looking for the Haversine formula
Here are two full implementations, one in SQL: Haversine Implementation
EDIT:
Here's a Haversine implementation of a UDF in SQLite. Unfortunately it's against the iPhone, but at least you have the exact implementation you need. Now you just need to determine how to plug it in.
SQL inlined below
CREATE FUNCTION [dbo].[GetDistance]
(
#lat1 Float(8),
#long1 Float(8),
#lat2 Float(8),
#long2 Float(8)
)
RETURNS Float(8)
AS
BEGIN
DECLARE #R Float(8);
DECLARE #dLat Float(8);
DECLARE #dLon Float(8);
DECLARE #a Float(8);
DECLARE #c Float(8);
DECLARE #d Float(8);
SET #R = 6371; --This value is 6371 for kilometers, 3960 for miles.
SET #dLat = RADIANS(#lat2 - #lat1);
SET #dLon = RADIANS(#long2 - #long1);
SET #a = SIN(#dLat / 2) * SIN(#dLat / 2) + COS(RADIANS(#lat1))
* COS(RADIANS(#lat2)) * SIN(#dLon / 2) * SIN(#dLon / 2);
SET #c = 2 * ASIN(MIN(SQRT(#a)));
SET #d = #R * #c;
RETURN #d;
END
GO
Assuming you're not in the polar or pacific regions, i'd use:
where pow(2*(latitude - ?), 2) + pow(longitude - ?, 2) < distance

cities and distance by latitude-longitude

I have a table with:
city
latitude
longitude
And I need a sql query to know all cities are 100 miles from new york.
Here's ours. You may need to modify it for your table structure. Ours looks up retail locations (and amenities), not cities, but the hard part is the "closest by distance" which works in this statement.
CREATE PROCEDURE [dbo].[GetNearbyLocations] #CenterLatitude FLOAT, #CenterLongitude FLOAT
AS
DECLARE #CntXAxis FLOAT
DECLARE #CntYAxis FLOAT
DECLARE #CntZAxis FLOAT
SET #CntXAxis = COS(RADIANS(#CenterLatitude)) * COS(RADIANS(#CenterLongitude))
SET #CntYAxis = COS(RADIANS(#CenterLatitude)) * SIN(RADIANS(#CenterLongitude))
SET #CntZAxis = SIN(RADIANS(#CenterLatitude))
SELECT LocationId, LocationName, Address, City, State, Zip, Phone, Latitude, Longitude,
hasATM, hasCarWash, hasDiesel, hasE85, is24hr, hasTrendar, hasWiFi, isTravelCenter, isMiniTravelCenter, isTruckerFriendly, hasScale, hasHotFood,
ProxDistance = 3961 * ACOS( dbo.XAxis(latitude, longitude)*#CntXAxis + dbo.YAxis(latitude, longitude)*#CntYAxis + dbo.ZAxis(latitude)*#CntZAxis)
FROM Locations
WHERE latitude IS NOT NULL
ORDER BY ProxDistance ASC
GO
Edit - added (sorry I missed these originally)
-- USER-DEFINED FUNCTIONS
-- XAxis #########################################
CREATE FUNCTION [dbo].[XAxis] (#lat float, #lon float)
RETURNS float
AS
BEGIN
RETURN COS(4 * (4 * atn2(1, 5) - atn2(1, 239)) / 180 * #lat) * COS(4 * (4 * atn2(1, 5) - atn2(1, 239)) / 180 * #lon)
END
CREATE FUNCTION [dbo].[YAxis] (#lat float, #lon float)
RETURNS float AS
BEGIN
RETURN COS(4 * (4 * atn2(1,5) - atn2(1,239)) / 180 * #lat) * SIN(4 * (4 * atn2(1,5) - atn2(1,239)) / 180 * #lon)
END
CREATE FUNCTION [dbo].[ZAxis] (#lat float)
RETURNS float AS
BEGIN
RETURN SIN(4 * (4 * atn2(1,5) - atn2(1,239)) / 180 * #lat)
END
Maybe this helps you: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
It is a nice introduction. Or just google for mysql distance queries, you will find some tutorials.
If you have the possibility and want to have it easier, switch to PostgreSQL which supports distance queries out of the box.
You need the lat long of NYC, or whichever city you want to analyze against. then simply measure miles between the two set of coordinates using math. In Excel the formula is
getDistance(latitude1, longitude1, latitude2, longitude2)
https://gis.stackexchange.com/questions/88484/excel-distance-calculation