I am trying to return matching zip codes as a table, so I can use it with 'Where zipCode IN(...) statement.
create function dbo.zipSearch(#zip varchar(12), #mile int)
returns table
as
begin
declare #ns float = #mile * 0.00569;
declare #ew float = #mile * 0.01629;
declare #ltt float, #lng float;
Select #ltt = latitude, #lng = longitude From ZipCode Where ZIP = #zip;
Select ZIP From zipcode Where latitude >= #ltt - #ns and latitude <= #ltt + #ns and longitude >= #lng - #ew and longitude <= #lng + #ew;
return
end
What would be the alternative if this is not possible?
Here is how you could turn this into an inline table valued function. The performance benefits might surprise you.
create function dbo.zipSearch(#zip varchar(12), #mile int)returns table as return
Select ZIP
From zipcode
cross apply
(
select latitude
, longitude
from ZipCode
where Zip = #zip
) LatLong
Where latitude >= LatLong.latitude - (#mile * 0.00569)
and latitude <= LatLong.latitude + (#mile * 0.00569)
and longitude >= LatLong.longitude - (#mile * 0.01629)
and longitude <= LatLong.longitude + (#mile * 0.01629);
Here are just a few articles about the differences.
Multi-statement Table Valued Function vs Inline Table Valued Function
http://www.sqlservercentral.com/blogs/discussionofsqlserver/2012/02/15/comparing-inline-and-multistatement-table-valued-functions/
http://sqlmag.com/t-sql/inline-vs-multistatement-table-valued-udfs
Posting the final version of the function in case someone else hits the same wall as I do. Thanks to #Tab Alleman for the tip. It was a syntax issue.
create function dbo.zipSearch(#zip varchar(12), #mile int)
returns #tmp TABLE (zipCode varchar(12))
as
begin
declare #ns float = #mile * 0.00569;
declare #ew float = #mile * 0.01629;
declare #ltt float, #lng float;
Select #ltt = latitude, #lng = longitude From ZipCode Where ZIP = #zip;
INSERT #tmp
Select ZIP From zipcode Where latitude >= #ltt - #ns and latitude <= #ltt + #ns and longitude >= #lng - #ew and longitude <= #lng + #ew;
return
end
Related
I'm new to SQL Server's capabilities regarding the Geography data type and how it can be used to calculate distances between two points. However, with the help of a good YouTube video, I was able to quickly write a query to find all the rows in a Locations table that are within a defined radius of specific lat/long point.
DECLARE #Lat DECIMAL(12,9)
DECLARE #Long DECIMAL(12,9)
DECLARE #Miles INT
DECLARE #Meters FLOAT
-- Coordinates set to a reference point
SET #Lat = 29.761209
SET #Long = -95.383513
SET #Miles = 15
SET #Meters = #Miles * 1609.34
DECLARE #Orig GEOGRAPHY = GEOGRAPHY::Point(#Lat, #Long, 4326)
SELECT
#Orig.STDistance(GEOGRAPHY::Point(latitude, longitude, 4326)) / 1609.34 As MilesDistance,
*
FROM Locations
WHERE #Orig.STDistance(GEOGRAPHY::Point(latitude, longitude, 4326)) <= #Meters
AND latitude IS NOT NULL AND longitude IS NOT NULL
ORDER BY 1
But I need to take this a step further now. Rather than comparing the Locations to a single reference point, I need to compare them to a set of multiple points. This will basically be a cartesian join returning distances for all combinations of my Location records and lat/long values that I pull from another table, which will presumably take the place of my #Orig variable that was created from a single point.
OK, so I was able to come up with an effective solution. I first created a SQL function:
ALTER FUNCTION [dbo].[MilesBetweenTwoPoints]
(
#LatA DECIMAL(10,6),
#LongA DECIMAL(10,6),
#LatB DECIMAL(10,6),
#LongB DECIMAL(10,6)
)
RETURNS DECIMAL(10,6) AS
BEGIN
DECLARE #Miles DECIMAL(10,6)
DECLARE #PointA GEOGRAPHY = GEOGRAPHY::Point(#LatA, #LongA, 4326)
DECLARE #PointB GEOGRAPHY = GEOGRAPHY::Point(#LatB, #LongB, 4326)
SELECT #Miles = #PointA.STDistance(#PointB) / 1609.34
RETURN #Miles
END
And then I modified my query to look something like this:
SELECT L1.latitude, L1.longitude, L2.latitude, L2.longitude,
dbo.MilesBetweenTwoPoints(L1.latitude, L1.longitude, L2.latitude, L2.longitude) As DistanceMiles
FROM Locations L1
INNER JOIN OtherLocations L2 ON 1=1
WHERE dbo.MilesBetweenTwoPoints(L1.latitude, L1.longitude, L2.latitude, L2.longitude) < #Miles
i have following query how to check latitude and longitude are in circle or not but its show wrong result please help
declare #latitudeCar float = 25.681137335685306;
declare #longitudeCar float = 66.697998046875;
declare #latitudePlace float = 25.918526162075153;
declare #longitudePlace float = 66.170654296875;
declare #source geography = 'POINT(' + cast(#latitudeCar as nvarchar) + ' ' + cast(#longitudeCar as nvarchar) + ')'
declare #target geography = 'POINT(' + cast(#latitudePlace as nvarchar) + ' ' + cast(#longitudePlace as nvarchar) + ')'
declare #radius float = 164096.68447201277
declare #check float
declare #isPastCircle bit
set #check = (select #source.STDistance(#target)/1609.344)
--select #source.STDistance(#target)/1000
set #isPastCircle = (select case when #check > #radius then 1 else 0 end);
select #isPastCircle [isPastCircle], #check [carDistance], #radius [acceptableRadius]
Always result is Wrong :(
It looks like you're trying to do your calculations in some fixed radius specified in miles but you're tripping the conversions. Below is a cleaned up version of the code:
declare #latitudeCar float = 25.681137335685306,
#longitudeCar float = 66.697998046875,
#latitudePlace float = 25.918526162075153,
#longitudePlace float = 66.170654296875;
declare #source geography = geography::Point(#latitudeCar, #longitudeCar, 4326),
#target geography = geography::Point(#latitudePlace, #longitudePlace, 4326);
declare #radius_in_miles int = 100;
declare #radius_in_meters float = #radius_in_miles * 1609.344;
select #target.STBuffer(#radius_in_meters).STContains(#source) AS [isWithinCircle],
#target.STDistance(#source) AS [distance_in_meters],
#target.STBuffer(#radius_in_meters).STDisjoint(#source) AS [isPastCircle];
TL;DR - Try, as often as possible, to abstract your conversions away. In this case, I'm specifying my desired radius in the units I'm used to (i.e. miles) and then using a different variable to hold the desired radius that the spatial reference uses (if you're ever in doubt, just query sys.spatial_reference_systems). Also note that I simplified the within/outside of specified radius calculations to use STContains() and STDisjoint() (respectively). I'm assuming that you don't actually care about the distance between the two points, so using those alone should speed things up a bit.
Your query seems correct other than the "<" in the select case. It should be:
set #isPastCircle = (select case when #check > #radius then 1 else 0 end);
I'm trying to implement a very efficient check to see whether two points are within a mile of each other.
I only care whether they are within a mile - nothing else about the distance matters to me.
Because of that narrow focus, I am not looking for a general purpose "how far apart are these points" function.
My current approach is to compute the Haversine distance, and then check to see if it's less than a mile.
Efficiency matters in this case because I have to compute this yes/no flag for large record sets.
So, what is the most efficient way to tell whether two lat/long points are within a mile of each other?
I'm doing this check in T-SQL, not that it matters much.
My current haversine computation is below.
CREATE FUNCTION dbo.USR_UFN_HAVERSINE_DISTANCE
(
#LAT1 FLOAT(18)
,#LONG1 FLOAT(18)
,#LAT2 FLOAT(18)
,#LONG2 FLOAT(18)
,#UnitOfMeasure NVARCHAR(10) = 'KILOMETERS'
)
RETURNS FLOAT(18)
AS
BEGIN
DECLARE
#R FLOAT(8)
,#DLAT FLOAT(18)
,#DLON FLOAT(18)
,#A FLOAT(18)
,#C FLOAT(18)
,#D FLOAT(18)
;
SET #R =
CASE #UnitOfMeasure
WHEN 'MILES' THEN 3956.55
WHEN 'KILOMETERS' THEN 6367.45
WHEN 'FEET' THEN 20890584
WHEN 'METERS' THEN 6367450
ELSE 6367.45 --km
END
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;
Since you specify that you need to run this over large data sets, I'd suggest a table-valued function. Better if you can pre-compute the geography points, but this does it all inline.
create function dbo.fn_areWithinOneMile(#long1 float, #lat1 float, #long2 float, #lat2 float)
returns table
as
return
select cast(
case when
geography::Point(#lat1, #long1, 4236).STDistance(geography::Point(#lat2, #long2, 4236)) > 1609.34 then 0
else 1
end as bit) as [withinOneMile?]
go
with cte as (select * from (values
(42, 42),
(43, 43),
(44, 44)
) as x(lat, long)
), j as (
select long, lat, lag(long, 1) over (order by lat) as long2, lag(lat, 1) over (order by lat) as lat2
from cte
)
select *
from j
cross apply dbo.fn_areWithinOneMile(long, lat, long2, lat2) as o
where long2 is not null;
DECLARE
#pt1 geography,
#pt2 geography;
SET #pt1 = geography::Point(45.65100, -120.34900, 4326);
SET #pt2 = geography::Point(44.65100, -120.37654, 4326);
SELECT #pt1.STDistance(#pt2);
-The return value is in meters though you can specify the return by changing the SRID.
-The list of SRID's are available here
Select * from sys.spatial_reference_systems
I have encountered a problem that is driving me crazy.
A few years ago i developed a browser app that calculate the distance from one given point (latitude and longitude coords) to anothers given points.
Everything has worked fine until a few days ago when a client from Panama started working with us. The same SQL procedure that works for years is giving us wrong measurements.
This is the SQL formula:
(Acos(Sin((Ofd.Latitud * PI()) / 180) * Sin((#Longitud * PI()) / 180) + Cos((Ofd.Latitud * PI()) / 180) * Cos((#Longitud * PI()) / 180) * Cos((Ofd.Logitud * PI() / 180) - (#Latitud * PI()) / 180)) * 6371 * 1000) AS Distance
I tried to calculate the distance using the new method since SQL 2008
DECLARE #Latitude float = 8.9749377
DECLARE #Longitude float = -79.5060562
DECLARE #TLatitude float = 8.9868425
DECLARE #TLongitude float = -79.5012872
DECLARE #Source geography
DECLARE #Target geography
SET #Source = geography::STPointFromText('POINT(' + CAST(#Latitude as varchar(20)) + ' ' + CAST(#Longitude as varchar(20)) + ')',4326)
SET #Target = geography::STPointFromText('POINT(' + CAST(#TLatitude as varchar(20)) + ' ' + CAST(#TLongitude as varchar(20)) + ')',4326)
SELECT #source.STDistance(#Target)
The diference between the two methods is negligible, a few meters. The distance that returns the method is ~500m
So, the problem is that the real distance is almost 1500 meters, I've seen and measured the distance in google maps and 1.500 meters is the real distance. The funny side is that, this problem, only happens in Panama. With the clients in Spain we have no problem calculating the distance.
Have I found the Bermuda's triangle?
You have the Latitude and Longitude reversed. WKT POINT coordinates are ordered X,Y (Longitude, Latitude).
DECLARE #Latitude float = 8.9749377
DECLARE #Longitude float = -79.5060562
DECLARE #TLatitude float = 8.9868425
DECLARE #TLongitude float = -79.5012872
DECLARE #Source geography
DECLARE #Target geography
SET #Source = geography::STPointFromText('POINT(' + CAST(#Longitude as varchar(20)) + ' ' + CAST(#Latitude as varchar(20)) + ')',4326)
SET #Target = geography::STPointFromText('POINT(' + CAST(#TLongitude as varchar(20)) + ' ' + CAST(#TLatitude as varchar(20)) + ')',4326)
SELECT #source.STDistance(#Target)
Say I have the latitude and longitude of a city and I need to find out all the airport that are within 100 miles of this location. How would I accomplish this? My data resides in SQL Server. 1 table has all the city info with lat and long and the other has the airport info with lat and long.
First ... convert city's data point
DECLARE #point geography;
SELECT geography::STPointFromText('POINT(' + CAST(#lat AS VARCHAR(20)) + ' ' +
CAST(#lon AS VARCHAR(20)) + ')', 4326)
where #lat and #lon are the latitude and longitude of the city in question.
Then you can query the table ...
SELECT [column1],[column2],[etc]
FROM [table]
WHERE #point.STBuffer(160934.4).STIntersects(geography::STPointFromText(
'POINT(' + CAST([lat] AS VARCHAR(20)) + ' ' +
CAST([lon] AS VARCHAR(20)) + ')', 4326) );
where 160934.4 is the number of meters in 100 miles.
This will be slow, though. If you wanted to do even more spatial work, you could add a persisted computed column (because lat and lon points aren't really going to change) and then use a spatial index.
ALTER TABLE [table]
ADD geo_point AS geography::STPointFromText('POINT(' + CAST([lat] AS VARCHAR(20))
+ ' ' + CAST([lon] AS VARCHAR(20)) + ')', 4326) PERSISTED;
CREATE SPATIAL INDEX spix_table_geopt
ON table(geo_point)
WITH ( BOUNDING_BOX = ( 0, 0, 500, 200 ) ); --you'd have to know your data
I used/wrote this several years ago, and it was close enough for what I needed. Part of the formula takes into account the curvature of the earth if I remember correctly, but it has been a while. I used zip codes, but you could easily adapt for cities instead - same logic.
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)