Trying to find distance between two sets of Lat-longs - sql-server

I have a dataset that has a 100,000+ addresses, and each of the addresses have two sets of Latitude and Longitude (basically x_lat, x_lon, y_lat, y_lon) on MSSQL. The lat-longs are for the same address but from two different sources, and I am trying to find the difference in distance between the two. I've done some research, and am trying to use the code below (from AakashM on Stackoverflow) but I'm getting an error saying that the subquery returned more than 1 value, which is not permitted.
DECLARE #orig_lat DECIMAL(12, 9)
DECLARE #orig_lng DECIMAL(12, 9)
SET #orig_lat=(SELECT x_lat FROM #Distance) SET #orig_lng= (SELECT x_lon FROM #Distance)
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
SELECT *,
#orig.STDistance(geography::Point(y_Lat, y_Lon, 4326))
AS distance
--INTO #includeDistances
FROM #Distance
The error makes sense because the #orig_lat and #orig_lon need to be set to a specific lat-long, but is there a way I can set it to the column instead so I can get the distance for each of the addresses, without having to manually input the lat-longs? I've included an image of the first two rows of the #Distance dataset below:
enter image description here

If I understand you correctly, you're try to calculate the distinct between x and y lat/lng's.
Example
SELECT *
,Distance = geography::Point(x_Lat, x_Lon, 4326).STDistance(geography::Point(y_Lat, y_Lon, 4326))
From YourTable
Where x_lat<>y_lat
or x_lon<>y_lon
There WHERE is optional.

Related

Expand a point by a specific number of miles

So I have a Geography type spatial column in SQL Server. This represents lat/long coordinates as a single point
Values look like this (as a string)
POINT (-96.63 32.97)
What I want to do is expand it into a circle/polygon by a specific number of miles.
I think STBuffer is the command I want, but I have no idea how to use it expand the radius by X number of miles.
My goal is to then later do STContains on it to see if some other geography object is contained inside of it. Something like this:
WHERE L.SPATIAL_OBJ.STContains(IBL.SPATIAL_OBJ) = 1
I figured it out. It looks like this.
I would do it as a function.
CREATE FUNCTION [dbo].[udf_mile_ring]
(
#lat FLOAT,
#lon FLOAT,
#miles int
)
RETURNS GEOGRAPHY
AS
BEGIN
DECLARE #meters FLOAT = #miles / 0.000621371;
RETURN GEOGRAPHY::Point(#lat, #lon, 4326).STBuffer(#meters);
END
GO

SQL Server Polygon format

SELECT TOP 1 KMLLocation FROM Polygon
Result: 0xE61000000104210000004ACE893DB4BF19403EB2B96A9EFF534082919735B1C0194039807EDFBFFF534052F17F4754C81940976F7D586F0054405AD76839D0D31940AED3484BE50054409E961FB8CAD3194087BF266BD40054403F90BC7328D3194071C806D2C5005440844A5CC7B8D21940E4D70FB1C100544014CE6E2D93D1194062BF27D6A90054404FB2D5E594D01940E27668588C005440E910381268D01940E7E26F7B820054409AEAC9FCA3CF1940A54E4013610054409432A9A10DD01940CD902A8A5700544015E126A3CAD01940FE0E45813E0054407FBDC282FBD11940C30DF8FC30005440EB1B98DC28D2194038312427130054403065E08096CE19404A7CEE04FBFF534082035ABA82CD1940C2F7FE06EDFF5340462234828DCB19409373620FEDFF534031B2648EE5CD1940E338F06AB9FF5340CFDC43C2F7CE1940321EA5129EFF5340B98C9B1A68CE194055A3570394FF5340A8E15B5837CE1940639CBF0985FF53403868AF3E1ECA1940001FBC7669FF534029CB10C7BAC81940ED9C668176FF5340C6DB4AAFCDC61940AD18AE0E80FF534047CCECF318C51940F390291F82FF5340EDB776A224C41940FD851E317AFF5340E4F90CA837C31940C440D7BE80FF5340DF1AD82AC1C21940462234828DFF53409C69C2F693C1194007D2C5A695FF5340C5387F130AC119406AFB57569AFF5340D1E7A38CB8C01940574277499CFF53404ACE893DB4BF19403EB2B96A9EFF534001000000020000000001000000FFFFFFFF0000000003
SELECT TOP 1 KMLLocation .ToString() FROM Polygon
Result:
POLYGON ((79.994044 6.437211, 79.996086 6.438176, 80.006796 6.445634, 80.013995 6.456849...
Expected Result:
POLYGON ((6.437211 79.994044, 6.438176 79.996086, 6.445634 80.006796, 6.456849, 80.013995...
How do I swap Lat and Long from geography polygon?
Here's what I came up with:
DECLARE #g GEOGRAPHY = /* your hex representation from above */
#new VARCHAR(MAX);
WITH points AS (
SELECT #g.STPointN(n.n) AS p, n
FROM tempdb.dbo.Numbers AS n
WHERE n <= #g.STNumPoints()
)
SELECT #new = CONCAT('POLYGON((', (
SELECT STRING_AGG(CONCAT(p.Lat, ' ', p.Long), ',') WITHIN GROUP (ORDER BY p.n)
FROM points AS p
), '))')
SELECT geography::STGeomFromText(#new, #g.STSrid).ReorientObject();
It's pretty straightforward. I'm creating a common table expression (CTE) to decompose the existing polygon into its constituent corners using a numbers (or tally) table. All that is is a table with one column with integers from 1 to some large number. It's helpful for cases like this. From there, I'm swapping the latitude and longitude of the points and then re-assembling them into the WKT for the polygon. Lastly, I'm creating an actual geography instance from the WKT. Note - I'm calling ReorientObject() as the new polygon appears to suffer from a ring orientation problem insofar as it defined the whole globe with a small hole in it (the shape of your polygon).

Fastest way to calculate distances between two coordinates?

We currently use the Geography type to calculate distance between a current location and the coordinates in our tsql table. Our code is based on this sqlauthority.com example.
Is there a faster way to retrieve the distance between two points? These calls will be done by a mobile phone app, so they should ideally be very fast.
After testing it with a distance I know, looping 100 times per batch and running the batch 15 times to make sure the 10 runs the client statistics stores in SSMS are cycled past initial query plan generation so it doesn't skew the results. Here are the averages of the remaining. The calculation method seems to be twice as fast as the geography option.
With a difference in distance returned of 0.0000000020044.
Calculation script used (returned miles: 41.9013152732833)
set nocount on;
declare
#lat1 float = 45.489614
,#lon1 float = -122.650021
,#lat2 float = 44.94404
,#lon2 float = -123.025739
select 3959.1825574 * acos(sin(#lat1/57.295779513082323) * sin(#lat2/57.295779513082323) + cos(#lat1/57.295779513082323) * cos(#lat2/57.295779513082323) * cos((#lon2-#lon1)/57.295779513082323)) distance_in_miles
GO 100
Geography script used (returned miles: 41.9013152752877)
set nocount on;
declare
#g geography = geography::Point(45.489614, -122.650021, 4326)
,#h geography = geography::Point(44.94404, -123.025739, 4326)
select #h.STDistance(#g) / 1609.344 distance_in_miles -- 1609.344 is meters in mile. STDistance = meters.
GO 100
Fair warning, doing it in a non-system function will still have unpredictable performance. I would recommend doing it inline for calculation.
Here's a raw calculation example.
Working example of inline syntax for miles. It is the easiest, most accurate and shortest syntax I could find.
adjusted for accuracy
if object_id('tempdb..#LatLongInfo','U') is not null
begin
drop table #LatLongInfo;
end;
create table #LatLongInfo (
lat1 float,
lon1 float,
lat2 float,
lon2 float
);
insert into #LatLongInfo
values (21, -76, 23, -72);
select
3959.1825574 * acos(sin(lat1/57.295779513082323) * sin(lat2/57.295779513082323) + cos(lat1/57.295779513082323) * cos(lat2/57.295779513082323) * cos((lon2-lon1)/57.295779513082323)) distance_in_miles
from #LatLongInfo;
Hope this helps. I used something like this to find the doctors within a given range for patients back when sql2000 was released, it's been a while. Google was a newborn, no maps, nothing but a search box and one button. You have me all nostalgic now...I remember reading this when I coded that the first time.

Can't get a simple Entity Framework spatial query to work

This query on my sql server returns lots of rows:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
but the equivalent in EF returns none:
DbGeography referencepoint = DbGeography.PointFromText("POINT(48.208173 16.373813)", 4326);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint ) <= 20000
select r).ToList();
Looking at the query generated via profiler I see this:
declare #p3 sys.geography
set #p3=convert(sys.geography,0xE6100000010CD4D17135B25F30408274B169A51A4840)
SELECT *
FROM [myTable]
WHERE ([Location].STDistance(#p3)) <= 20000
Does EF have an issue here, or do I?
OP has the issue here :) Both SQL and EF are working as expected. OP's statement was incorrect.
SQL Point Syntax:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
is actually equivalent to .Net:
DbGeography referencepoint = DbGeography.PointFromText("POINT(16.373813 48.208173)", 4326);
// Note the parameters are reversed from OP's statement
In SQL and EF (.Net) the Geography data type uses a standard WellKnownText notation to define points and polygons and other structures internally.
In WellKnownText format a Point is specified as POINT(X Y) on a Cartesian plane.
- Note the lack of a comma, the values are only delimited by a space
When we want to express the location on the earth as a point on a Cartesian plane, the X axis is the equator, the Y axis is then a Meridian line running between the North and South Poles.
Longitute, by definition is the east-west position on the surface of the Earth (so parallel with the equator, the X ordinate)
Latitude, by definition is the north-south position on the surface of the Earth (perpendicular to the equator, the Y ordinate)
Therefore to express a Point on the earth in WellKnownText format as if it were a point on a Cartesian plane we must use this syntax:
POINT(Longitude Latitude)
What confuses the issue is that in most verbal and written forms we refer to Latitude and Longitude in that order, so in SQL we have a helper function that takes the parameters in that order, because this was supposed to make it less confusing. And in a way it is, because the parameters are named appropriately. To further explain the point I have expanded out OP's statements with the correction
SQL
DECLARE #latitude float = 48.208173
DECLARE #longitude float = 16.373813
DECLARE #srid int = 4326
DECLARE #referencepoint Geography = Geography::Point(#latitude, #longitude, #srid);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
.Net
double latitude = 48.208173;
double longitude = 16.373813;
int srid = 4326;
DbGeography referencepoint = DbGeography.PointFromText($"POINT({longitude} {latitude})", srid);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint) <= 20000
select r).ToList();
I can't even find a good reference explaining why we generally refer to Latitude and Longitude (in that order) I suspect it's based on the fact that LatLon rolls off the tongue better or because latitude was discovered/measured first?

SQL Server Spatial Query: where condition behaving «oddly»

I've realized this «silly» spatial query to find all the points that lie 5Km far form a center.
Source table holds +150K rows.
Here the query:
DECLARE #position geography = geography::Parse('POINT(9.123 45.123)')
DECLARE #circle geography = #position.STBuffer(5000) -- A circle of 5Km of radius
SELECT
g.Coordinate.STDistance(#position), g.Coordinate.Filter(#circle)
FROM
[DB_NAME].[SCHEMA].[TABLE] AS g WITH (nolock)
WHERE
g.Coordinate.Filter(#circle) = 1
I oddly observe that the WHERE condition doesn't work: in fact I retrieve even +600 points where the condition returns 0.
Any suggestions?
For the sake of clarity table schema was
[DB_NAME].[SCHEMA].[TABLE](Coordinate geography NOT NULL)
Official documentation states: «Returns 1 if a geography instance potentially intersects another geography instance. This method may produce a false-positive return, and the exact result may be plan-dependent. Returns an accurate 0 value (true negative return) if there is no intersection of geography instances found.»
So I mean that 0 is always ok, while 1 could be approximated (IMHO this behaviour is absolutely reasonable)
By the way #Damien observation lead me to simply work around:
DECLARE #position geography = geography::Parse('POINT(9.123 45.123)')
DECLARE #circle geography = #position.STBuffer(5000) -- A circle of 5Km of radius
SELECT * FROM
(SELECT
g.Coordinate.Filter(#circle) filter, g.Coordinate Coord
FROM [DB_NAME].[SCHEMA].[TABLE] AS g WITH (nolock)
WHERE
g.Coordinate.Filter(#circle) = 1
) t
WHERE t.filter = 1
that recalls me the «Double Check Pattern» esoterism… but in that case It's clear the motivation.
One point that could be more investigated is about the return value conversion… Many years ago I stumbled upon on a similar issue where in a server farm an implicit conversion of a boolean tre to int led to -1 (0xFFFFFFFF) instead of 1 (0x00000001)… COM ages…

Resources