Calculating distance between two points (Latitude, Longitude) - sql

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.

Related

How can I add a distance in meters to the latitude value in SQL Server 2014

Given
DECLARE #p1 geography
DECLARE #p2 geography
DECLARE #distance int
SET #distance = 10000 -- meters
SET #p1 = geography::Point(51.5001524, -0.1262362, 4326)
How can I set #p2 such that its latitude is #distance meters north or south of #p1?
I have no clue how well this will work in practice, but it can be done just using inbuilt functions.
DECLARE #Distance INT = 10000
DECLARE #latitude DECIMAL(20,10) = 51.5001524
DECLARE #longitude DECIMAL(20,10) = -0.1262362
DECLARE #OriginalPoint GEOGRAPHY = GEOGRAPHY::Point(#latitude, #longitude, 4326)
--This will create a point at the same longitude but at the south pole.
DECLARE #DueSouthPoint GEOGRAPHY = GEOGRAPHY::Point(-90, #longitude, 4326)
DECLARE #SouthLineWithLengthOfDistance GEOGRAPHY =
#OriginalPoint.ShortestLineTo(#DueSouthPoint) --This is a line due south
.STIntersection( --This will return the line segment inside the circle
#OriginalPoint.STBuffer(#distance) --This will draw a circle around the original point with a radius of #distance
)
--Now we have to return the lower point on the line.
--It seems to be fairly inconsistent in which point is first in the line
--I don't want to spend the time to figure it out, so I'm just using a case to determine which point to return.
SELECT
CASE
WHEN #OriginalPoint.STDistance(#SouthLineWithLengthOfDistance.STPointN(1)) = 0
THEN #SouthLineWithLengthOfDistance.STPointN(2)
ELSE #SouthLineWithLengthOfDistance.STPointN(1)
END

SQL Server generating random spatial geography around point?

I have several thousand records in a development environment, each associated with a centroid of a particular zip code. For testing purposes, I need to randomly scatter each SQL Server geography point 0-5 miles around that centroid.
So in the example below I want to update LocationGeo so it is 0-5 miles away from its respective ZipGeo. Do I have to use a random % applied to each Lon/Lat or is there a better option?
LocationID int
LocationGeo geography
ZipCode char(5)
ZipCode char(5)
ZipGeo geography
I found an answer from this post https://gis.stackexchange.com/a/25883/37373
I adapted the response into SQL Server code.
DECLARE #geo as GEOGRAPHY,#newgeo as GEOGRAPHY
SET #geo = (SELECT ZipGeo FROM Location.ZipCodes WHERE ZipCode='90210')
DECLARE #r float,#t float, #w float, #x float, #y float, #u float, #v float;
SET #u=RAND();
SET #v=RAND();
--8046m = ~ 5 miles
SET #r= 8046/(111300*1.0);
SET #w = #r * sqrt(#u);
SET #t = 2 * PI() * #v;
SET #x = #w * cos(#t);
SET #y = #w * sin(#t);
SET #x = #x / cos(#geo.Lat);
SET #newgeo = geography::STPointFromText('POINT('+CAST(#geo.Long+#x AS VARCHAR(MAX))+' '+CAST(#geo.Lat+#y AS VARCHAR(MAX))+')',4326)
--Convert the distance back to miles to validate
SELECT #geo.STDistance(#newgeo)/1609.34

Haversine query into Oracle

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

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