Haversine formula using SQL server to find closest venue - vb.net - sql

I am grabbing a postcode from a form. I can then convert this postcode to lng,lat coordinates as I have these stored in a table.
SELECT lng, lat from postcodeLngLat WHERE postcode = 'CV1'
I have another table which stores the lng,lat of a selection of venues.
SELECT v.lat, v.lng, v.name, p.lat, p.lng, p.postcode, 'HAVERSINE' AS distance FROM venuepostcodes v, postcodeLngLat p WHERE p.outcode = 'CB6' ORDER BY distance
What I am trying to do is create a datagrid which shows the distance of each venue from the postcode (CV1 in this case). I know that the Haversine formula should do what I am trying to achieve but I'm lost as to where I should start incorporating it into my query. I think the formula needs to go where I've put 'HAVERSINE' in the query above.
Any ideas?
EDIT
SELECT o.outcode AS lead_postcode, v.venue_name, 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 venuepostcodes v, outcodepostcodes o WHERE o.outcode = 'CB6' ORDER BY distance

I think you'd do best putting it in a UDF and using that in your query:
SELECT v.lat, v.lng, v.name, p.lat, p.lng, p.postcode, udf_Haversine(v.lat, v.lng, p.lat, p.lng) AS distance FROM venuepostcodes v, postcodeLngLat p WHERE p.outcode = 'CB6' ORDER BY distance
create function dbo.udf_Haversine(#lat1 float, #long1 float, #lat2 float, #long2 float) returns float begin
declare #dlon float, #dlat float, #rlat1 float, #rlat2 float, #rlong1 float, #rlong2 float, #a float, #c float, #R float, #d float, #DtoR float
select #DtoR = 0.017453293
select #R = 3937 --3976
select
#rlat1 = #lat1 * #DtoR,
#rlong1 = #long1 * #DtoR,
#rlat2 = #lat2 * #DtoR,
#rlong2 = #long2 * #DtoR
select
#dlon = #rlong1 - #rlong2,
#dlat = #rlat1 - #rlat2
select #a = power(sin(#dlat/2), 2) + cos(#rlat1) * cos(#rlat2) * power(sin(#dlon/2), 2)
select #c = 2 * atn2(sqrt(#a), sqrt(1-#a))
select #d = #R * #c
return #d
end

Alternatively uou could also use SQL Server 2008 geography datatypes. If you currently store the longitude/latitide as varchar() in the DB, you will have to store them as geograpghy datatype and then use a function like STIntersects() to get the distance.

Related

Calculation using SQL server

Formula used
This is the formula that I used to calculate
Expressed as a single equation:
eGFR = 142 x min(Scr/κ, 1)α x max(Scr/κ, 1)-1.200 x 0.9938Age x 1.012 [if female]
where:
Scr = serum creatinine in mg/dL
κ = 0.7
α = -0.329
min(Scr/κ, 1) is the minimum of Scr/κ or 1.0
max(Scr/κ, 1) is the maximum of Scr/κ or 1.0
Age (years)
Code that I tried
select
ROUND(141 * power(min(cast(40 as float) / 0.7 ) ,-0.329) *
power(max( cast(40 as float) / 0.7 * 1) ,- 1.209) * 0.993 *
cast(42 as float) * 1.018 ,2) as kidney
Correct answer should be 123.
Any clue what I am missing in the query?
Don't use MIN and MAX, those are aggregate (GROUP BY) functions. It would be good if SQL Server had GREATEST and LEAST functions, but it doesn't yet.
IIF(a < b, a, b) is LEAST(a,b).
IIF(a > b, a, b) is GREATEST(a,b).
Don't sweat the CASTs.
Make no assumptions about operator precedence ( x before +, etc). Use parentheses.
Here's a rewrite of what I believe your formula should be. But I'm not getting the right answer yet either.
DECLARE #Scr AS INT = 40;
DECLARE #k AS FLOAT = 0.7;
DECLARE #a AS FLOAT = -0.329;
DECLARE #age AS INT = 42;
DECLARE #gender AS VARCHAR(MAX) = 'female';
DECLARE #factor AS FLOAT = CASE WHEN #gender = 'male' THEN 1.0 ELSE 1.012 END;
SELECT 142
+ (IIF(1 < #Scr/#k, 1, #Scr/#k) * #a)
* (IIF(1 > #Scr/#k, 1, #Scr/#k))
- (1.2 * 0.993 * #age * #factor);
I obviously misunderstood your formula, but this should get you started. Here's a fiddle.
According to this site your equation is kinda wrong. Please be sure to add ^ to display the "to the power of".
DECLARE #Scr AS FLOAT = 40.0;
DECLARE #gender AS VARCHAR(10) = 'female';
DECLARE #age AS SMALLINT = 42;
DECLARE #k AS FLOAT = CASE WHEN #gender = 'male' THEN 0.9 ELSE 0.7 END;
DECLARE #a AS FLOAT = CASE WHEN #gender = 'male' THEN -0.302 ELSE -0.241 END;
DECLARE #factor AS FLOAT = CASE WHEN #gender = 'male' THEN 1.000 ELSE 1.012 END;
SELECT 142 * POWER(IIF(1 < (#Scr/#k), 1, #Scr/#k), #a)
* POWER(IIF(1 > (#Scr/#k), 1, #Scr/#k), -1.200)
* POWER(0.9938, #age)
* #factor;
Also this result is correct since it is calculated in mg/dl and your expected value is in umol/l
Here is another website (it may be german, but it should do the trick)

conversion from nvarchar to numeric fails

I am trying to execute this query but the following error is occurring:
Error converting data type nvarchar to numeric
this is my query :
Select Top 20 *,dbo.GetDistance(35.5,33.8, Longitude, Latitude) as Distance
From ViewBranchCoordinates
order by Distance desc
if i remove this line order by Distance desc the query run normally with no error
this is the function GetDistance
ALTER FUNCTION [dbo].[GetDistance]
(
-- Add the parameters for the function here
#Long decimal(30,24), #Lat decimal(30,24), #Long2 decimal(30,24), #Lat2 decimal(30,24)
)
--decimal(8,6), #Long)
RETURNS decimal(38,28)
AS
BEGIN
-- Declare the return variable here
DECLARE #Distance decimal(38,28)
DECLARE #valueOne decimal(30,24)
DECLARE #valueTwo decimal(30,24)
DECLARE #dLat decimal(30,24)
DECLARE #dLng decimal(30,24)
DECLARE #SectionOne decimal(30,24)
DECLARE #SectionTwo decimal(30,24)
Select #dLat = RADIANS(#Lat - #Lat2)
Select #dLng = RADIANS(#Long - #Long2)
Select #SectionOne = square(sin((#dLat)/2))
Select #SectionTwo = cos(RADIANS(#Lat)) * cos(RADIANS(#Lat2)) * square(sin(#dLng / 2))
Select #valueOne =CONVERT(decimal(30,24),#SectionOne + #SectionTwo)
Select #valueTwo = 2 * ATN2(SQRT(#valueOne), SQRT(1 - #valueOne))
Select #Distance = 6371000 * #valueTwo
RETURN #Distance
END
Any help please
I presume this will fail too?
Select Top 20 *
,dbo.GetDistance(35.5,33.8, cast (Longitude as decimal (30,24)), cast(Latitude as (30,24)) as Distance
From ViewBranchCoordinates
Your function expects data of a certain type. If your lat/long columns are nvarchar then non numeric data can be in those columns.
Search for problem data, e.g.
Select *
From ViewBranchCoordinates
Where try_cast (longitude as numeric) IS NULL
Then you need to fix the data.

SQL Server - Calculate Initial Bearing from Latitude & Longitude using GEOGRAPHY

Recently, from the help of Stackoverflow members, I got the following solution to calculate distance between two latitude and longitude points:
GEOGRAPHY::Point(DepartureAirportLatitude, DepartureAirportLongitude, 4326)
.STDistance(GEOGRAPHY::Point(ArrivalAirportLatitude, ArrivalAirportLongitude, 4326)) AS [Default],
GEOGRAPHY::Point(DepartureAirportLatitude, DepartureAirportLongitude, 4326)
.STDistance(GEOGRAPHY::Point(ArrivalAirportLatitude, ArrivalAirportLongitude, 4326)) / 1609.344 AS [Mi],
GEOGRAPHY::Point(DepartureAirportLatitude, DepartureAirportLongitude, 4326)
.STDistance(GEOGRAPHY::Point(ArrivalAirportLatitude, ArrivalAirportLongitude, 4326)) / 1000 AS [Km]
Is there a way, using the GEOGRAPHY feature, to calculate the initial bearing between the same data points, displaying it as a heading?
Thanks
Matthew
Thanks for your reply and the information.
After a few hours of playing around and extensive amounts of Google searching, I am using the below solution which is giving me the correct results:
DECLARE #Pi AS FLOAT
DECLARE #D2R AS FLOAT
SET #Pi = '3.14159265358979'
SET #D2R = #Pi / 180.0;
PICalculation AS
(
SELECT FlightNo, DepartureAirportIATA, ArrivalAirportIATA,
#D2R * DepartureAirportLatitude AS [DepartureAirportLatitude], #D2R * DepartureAirportLongitude AS [DepartureAirportLongitude],
#D2R * ArrivalAirportLatitude AS [ArrivalAirportLatitude], #D2R * ArrivalAirportLongitude AS [ArrivalAirportLongitude]
FROM RawData
),
RadiansCalculation AS
(
SELECT FlightNo, DepartureAirportIATA, ArrivalAirportIATA,
Radians(ArrivalAirportLatitude - DepartureAirportLatitude) AS [DLat],
Radians(ArrivalAirportLongitude - DepartureAirportLongitude) AS [DLon],
Radians(DepartureAirportLatitude) AS [RLat1],
Radians(ArrivalAirportLatitude) AS [RLat2]
FROM PICalculation
),
XYCalculation AS
(
SELECT FlightNo, DepartureAirportIATA, ArrivalAirportIATA,
SIN(DLon)*COS(RLat2) AS [Y],
COS(RLat1)*SIN(RLat2)-SIN(RLat1)*COS(RLat2)*COS(DLon) AS [X]
FROM RadiansCalculation
),
HeadingCalculation AS
(
SELECT FlightNo, DepartureAirportIATA, ArrivalAirportIATA,
CASE WHEN X = 0 AND Y = 0 THEN 0
ELSE CAST((DEGREES(ATN2(Y,X)) + 360) AS DECIMAL(5,1)) % 360
END AS [InitialBearing]
FROM XYCalculation
)
SELECT *
FROM DataJoin
Hopefully this solution will be useful to others who stumble across this post as well :)
Matthew
Here's a scalar function and test points that exercise it. NB: I found the source here
CREATE FUNCTION dbo.Bearing (
#point1 geography,
#point2 geography )
RETURNS float
AS
BEGIN
DECLARE #Bearing decimal(18,15)
DECLARE #Lat1 float = Radians(#point1.Lat)
DECLARE #Lat2 float = Radians(#point2.Lat)
DECLARE #dLon float = Radians(#point2.Long - #point1.Long)
IF (#point1.STEquals(#point2) = 1)
SET #Bearing = NULL
ELSE
SET #Bearing = ATN2(
sin(#dLon)*cos(#Lat2),
(cos(#Lat1)*sin(#Lat2)) - (sin(#Lat1)*cos(#Lat2)*cos(#dLon))
)
SET #Bearing = (Degrees(#Bearing) + 360) % 360
RETURN #Bearing
END
GO
DECLARE #g GEOGRAPHY = GEOGRAPHY::Point(43, 43, 4326);
DECLARE #g1 GEOGRAPHY = GEOGRAPHY::Point(42, 43, 4326);
DECLARE #g2 GEOGRAPHY = GEOGRAPHY::Point(44, 43, 4326);
DECLARE #g3 GEOGRAPHY = GEOGRAPHY::Point(43, 42, 4326);
DECLARE #g4 GEOGRAPHY = GEOGRAPHY::Point(43, 44, 4326);
SELECT dbo.[Bearing](#g, #g1),
[dbo].[Bearing](#g, #g2),
[dbo].[Bearing](#g, #g3),
[dbo].[Bearing](#g, #g4);
If doing this in T-SQL ends up being too slow, there are a couple of CLR implementations at that link as well.

How to Return Resultset with a Given Radius

I have the following SQL (SQL Server) and it works for the most part. The problem is I am really creating a square and not a true circle. My goal is to pass in a city and state which has a lat and long, then find all cities within a 100 mile radius of that lat long. The latitude and longitude are stored in the DB so all my values are there. I just need a more precise way of doing it. Here is my code thus far:
ALTER PROCEDURE [dbo].[sp_StoresByZipArea] (#zip nvarchar(5), #Radius float) AS
DECLARE #LatRange float
DECLARE #LongRange float
DECLARE #LowLatitude float
DECLARE #HighLatitude float
DECLARE #LowLongitude float
DECLARE #HighLongitude float
DECLARE #istartlat float
DECLARE #istartlong float
SELECT #iStartlat=Latitude, #iStartLong=Longitude from zipcodes where zipcode=#ZIP
SELECT #LatRange = #Radius / ((6076 / 5280) * 60)
SELECT #LongRange = #Radius / (((cos((#iStartLat * 3.141592653589 / 180)) * 6076.) / 5280.) * 60)
SELECT #LowLatitude = #istartlat - #LatRange
SELECT #HighLatitude = #istartlat + #LatRange
SELECT #LowLongitude = #istartlong - #LongRange
SELECT #HighLongitude = #istartlong + #LongRange
/** Now you can create a SQL statement which limits the recordset of cities in this manner: **/
SELECT * FROM ZipCodes
WHERE (Latitude <= #HighLatitude) AND (Latitude >= #LowLatitude) AND (Longitude >= #LowLongitude) AND (Longitude <= #HighLongitude)
I've used the great circle distance to do this in the past. The implementation below tells you the distance between two different points, which could be used to do what you are talking about:
create function dbo.GreatCircleDistance
(
#Latitude1 float,
#Longitude1 float,
#Latitude2 float,
#Longitude2 float
)
returns float
as
/*
FUNCTION: dbo.GreatCircleDistance
Computes the Great Circle distance in kilometers
between two points on the Earth using the
Haversine formula distance calculation.
Input Parameters:
#Longitude1 - Longitude in degrees of point 1
#Latitude1 - Latitude in degrees of point 1
#Longitude2 - Longitude in degrees of point 2
#Latitude2 - Latitude in degrees of point 2
*/
begin
declare #radius float
declare #lon1 float
declare #lon2 float
declare #lat1 float
declare #lat2 float
declare #a float
declare #distance float
-- Sets average radius of Earth in Kilometers
set #radius = 6371.0E
-- Convert degrees to radians
set #lon1 = radians( #Longitude1 )
set #lon2 = radians( #Longitude2 )
set #lat1 = radians( #Latitude1 )
set #lat2 = radians( #Latitude2 )
set #a = sqrt(square(sin((#lat2-#lat1)/2.0E)) +
(cos(#lat1) * cos(#lat2) * square(sin((#lon2-#lon1)/2.0E))) )
set #distance =
#radius * ( 2.0E *asin(case when 1.0E < #a then 1.0E else #a end ))
return #distance
end
Not sure if this helps, but I think there is an error here:
SELECT #LatRange = #Radius / ((6076 / 5280) * 60)
The (6076 / 5280) part will always evaluate to 1.
This functionality is available in-box for SQL Server 2012 and above. See Query Spatial Data for Nearest Neighbor:
DECLARE #g geography;
DECLARE #h geography;
-- SRID 4326 specifies the use of WGS 84 coordinate system (same as GPS)
SET #g = geography::STGeomFromText('POINT(-122.360 47.656)', 4326);
SET #h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);
-- Returns 995 meters
SELECT #g.STDistance(#h);

how to truncate a number in sybase ASE?

http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc38151.1540/doc/html/san1278453173757.html
The functions TRUNCATE and TRUNCNUM are not supported in Adaptive Server Enterprise.
Does anyone know another way of doing this in ASE?
Thanks
I know these ways:
select Number = floor ( 455.443 )
select Number = cast ( 455.443 as int )
select Number = convert ( int, 455.443 )
select Number = 455.443 - ( 455.443 % 1 )
How about using the Floor function? It essentially does the same thing, and is supported in ASE.
This is an old question, but I did this a few days ago to mimic the "truncnum" function described in the link above.
create function custom_truncnum(#numberToTruncate float, #decimalPlaces int)
returns float
AS
declare #tenToTheXPower float;
declare #leftSideOfDecimal float;
declare #returnVal float;
set #tenToTheXPower = power(10, ABS(#decimalPlaces);
set #leftSideOfDecimal = FLOOR(#numberToTruncate);
if (#decimalPlaces <= 0)
set #returnVal = FLOOR(#numberToTruncate / #tenToTheXPower) * #tenToTheXPower;
else
set #returnVal = #leftSideOfDecimal + (FLOOR(#numberToTruncate - #leftSideOfDecimal) * #tenToTheXPower) / #tenToTheXPower);
return #returnVal;
GO
Now you should be able to do this in Sybase ASE:
SELECT dbo.custom_truncnum(345.567, 2)
345.56
SELECT dbo.custom_truncnum(345.562, 2)
345.56
SELECT dbo.custom_truncnum(345.567, -1)
340
SELECT dbo.custom_truncnum(345.567, -2)
300
SELECT dbo.custom_truncnum(345.567, 0) --This is the same as FLOOR(345.567)
345