STIntersects in SQL Server Spatial, probably a bug - sql

I show you the conclusion after many headaches and tests.
If we execute the following script in any SSMS session under SQL Server 2012 (also tested in 2017):
DECLARE #g geography;
DECLARE #h geography;
SET #g = geography::STPolyFromText('POLYGON ((-2.2141931466053393 36.848142880725426, -2.1632066297350296 36.864255247830073, 3.0526676842932088 39.266689645726004, 3.168352172454167 39.329935703712941, 3.2286305251469463 39.370418565526464, 3.2322979289615716 39.374091534163213, 3.2372882795963895 39.379457236292687, 3.2583498367577581 39.409984643625563, 3.3506438583660594 39.556107032723332, 3.4300534340816529 39.699235599046659, 3.2674327158297669 42.289964571287427, 3.1599698848294775 42.435144600606137, 0.79329389164208441 42.99441296630598, -7.6685442882152826 43.77780739075277, -8.0476876236197672 43.711457921649725, -9.2115225666636089 43.160194190763086, -9.2984566034556444 43.054102468725411, -9.2715498704469024 42.881894543711283, -7.5169998811285126 37.556527784974641, -7.4389506340710883 37.345028476444256, -7.4300533647696216 37.33933723597093, -7.426814647077407 37.337972629773219, -7.4171018386796526 37.336039770123939, -7.36341431379385 37.33029040340196, -4.5559445740609537 37.01597060730704, -2.2141931466053393 36.848142880725426))', 4326);
SET #h = geography::STPointFromText('POINT (-5.7805724666961673 43.604738856455796)', 4326);
select #g.STIntersects(#h)
We obtain the result 1. Which means that the geometries intersect. Which is not true and you can see clearly if we represent the geometries in GIS tools, such as ArcMap, QGIS or visualize in sites like https://clydedacruz.github.io/openstreetmap-wkt-playground/.
This does not happen only with that point, they can test with others such as the following:
POINT (-5.7808907869201684 43.607612302768608)
POINT (-5.7867532730156022 43.607109291914668)
POINT (-5.7910420343533673 43.607409757130171)
POINT (-5.7962209295114038 43.605527381457819)
POINT (-5.8379991303640395 43.609944466702757)
POINT (-5.8372379698022909 43.613519832305009)
POINT (-5.8339925740272829 43.616976768767834)
POINT (-5.832657139630153 43.620206197447274)
POINT (-5.827899502105284 43.624465756821465)
POINT (-5.8230287455979495 43.6276474699738)
Clarify that it is not a problem of orientation of the ring. Other interior points appear as intersecting.
I have not found an explanation, just a possibility of a bug in SQL server. This makes me very distrustful of the STIntersects () function that I use in countless places in my code.
I would appreciate any response.

After a lot of thinking, my conclusion is that the problem is the projection
used in geography for the operations and graphic representations. If we make the problem with geometry the result is different. You can see here:
DECLARE #g geometry;
DECLARE #h geometry;
SET #g = geometry::STPolyFromText('POLYGON ((-2.2141931466053393 36.848142880725426, -2.1632066297350296 36.864255247830073, 3.0526676842932088 39.266689645726004, 3.168352172454167 39.329935703712941, 3.2286305251469463 39.370418565526464, 3.2322979289615716 39.374091534163213, 3.2372882795963895 39.379457236292687, 3.2583498367577581 39.409984643625563, 3.3506438583660594 39.556107032723332, 3.4300534340816529 39.699235599046659, 3.2674327158297669 42.289964571287427, 3.1599698848294775 42.435144600606137, 0.79329389164208441 42.99441296630598, -7.6685442882152826 43.77780739075277, -8.0476876236197672 43.711457921649725, -9.2115225666636089 43.160194190763086, -9.2984566034556444 43.054102468725411, -9.2715498704469024 42.881894543711283, -7.5169998811285126 37.556527784974641, -7.4389506340710883 37.345028476444256, -7.4300533647696216 37.33933723597093, -7.426814647077407 37.337972629773219, -7.4171018386796526 37.336039770123939, -7.36341431379385 37.33029040340196, -4.5559445740609537 37.01597060730704, -2.2141931466053393 36.848142880725426))', 4326);
SET #h = geometry::STPointFromText('POINT (-5.7805724666961673 43.604738856455796)', 4326);
select #g.STIntersects(#h)
select #g union all select #h
I hope that it is useful for someone.

The problem is not with the STIntersects() method but with the precision that SQL Server uses to store geospatial data, per this blog
SQL Server stores geography and geometry coordinates as binary data, adhering to the IEEE-754 standard for binary floating-point arithmetic. Based on this standard, each coordinate is stored as a double-precision floating-point number that is 64 bits (8 bytes) long.
According to Microsoft Docs
The error tolerance for the geography methods can be as large as 1.0e-7 * extents. The extents refer to the approximate maximal distance between points of the geographyobject.
Using the below code you can see the precision changes.
DECLARE #p1 geography;
DECLARE #p2 geography;
DECLARE #h geography;
SET #p1 = geography::STPointFromText('POINT (-7.6685442882152826 43.77780739075277)', 4326);
SET #p2 = geography::STPointFromText('POINT (0.79329389164208441 42.99441296630598)', 4326);
SET #h = geography::STPointFromText('POINT (-5.7805724666961673 43.604738856455796)', 4326);
select #p1.Lat, #p1.Long
select #p2.Lat, #p2.Long
select #h.Lat, #h.Long

Try running this in SSMS:
SELECT #g UNION ALL SELECT #h
As you can see in the "Spatial results" tab, the point is inside the polygon (regardless of the projection used).

Related

SQL Server Geography Data Type - why do two separate Polygons Intersect?

I have two geometry variables and I want to find out if they intersect.
My first variable is called #orderBounds2.
DECLARE #orderBounds2 geography
I set it to a value
set #orderBounds2 = 0xE6100000010405000000B81E85EB51F837C07CF2B0506B3A40C0B81E85EB51F837C01283C0CAA1955C4097900F7A363343401283C0CAA1955C4097900F7A363343407CF2B0506B3A40C0B81E85EB51F837C07CF2B0506B3A40C001000000020000000001000000FFFFFFFF0000000003;
I look to see what it is in text.
select #orderBounds2.STAsText() --POLYGON ((-32.4564 -23.97, 114.338 -23.97, 114.338 38.4001, -32.4564 38.4001, -32.4564 -23.97))
My 2nd variable is #geo
declare #geo geography
I set it to a value.
select #geo = 0xE6100000010405000000A228D027F2C6514033F9669B1B034B40CDCCCCCCCC4C514033F9669B1B034B40CDCCCCCCCC4C51400000000000404F40A228D027F2C651400000000000404F40A228D027F2C6514033F9669B1B034B4001000000020000000001000000FFFFFFFF0000000003;
I look to see what it is in text.
select #geo.STAsText() --POLYGON ((54.02428 71.10853, 54.02428 69.2, 62.5 69.2, 62.5 71.10853, 54.02428 71.10853))
Now I want to find out if the polygons that my two variables represent intersect?
select #geo.STIntersects(#orderBounds2) --= 1
The answer is 1, so yes the two areas do intersect.
However, I don't understand why . The polygons in text show (longitude.latitude) points and I cannot see how they intersect.
The 2nd Polygon is between latitude 69.2 and 71.1
The 1st Polygon is between latitude -23.4 and 38.4
The edges of a geography polygon follow great circle arcs, so the segment from 114.338 38.4001 to -32.4564 38.4001 goes very far north.
DECLARE #orderBounds2 geography = cast(0xE6100000010405000000B81E85EB51F837C07CF2B0506B3A40C0B81E85EB51F837C01283C0CAA1955C4097900F7A363343401283C0CAA1955C4097900F7A363343407CF2B0506B3A40C0B81E85EB51F837C07CF2B0506B3A40C001000000020000000001000000FFFFFFFF0000000003 as geography)
declare #geo geography = cast( 0xE6100000010405000000A228D027F2C6514033F9669B1B034B40CDCCCCCCCC4C514033F9669B1B034B40CDCCCCCCCC4C51400000000000404F40A228D027F2C651400000000000404F40A228D027F2C6514033F9669B1B034B4001000000020000000001000000FFFFFFFF0000000003 as geography)
select #orderBounds2
union all
select #geo
outputs

How do I calculate distance between two latitude, longitude points in miles using standard SQL without trigonometry?

How do I calculate distance between two latitude, longitude points in miles using standard SQL without trigonometry?
Without trig you get incorrect results. Find an explanation with answer, formula and example here:
https://jonisalonen.com/2014/computing-distance-between-coordinates-can-be-simple-and-fast
Approximation without precision and trig
DECLARE #SqDegreeLatInMiles AS REAL = 4774.81
DECLARE #SqDegreeLongInMiles AS REAL = 2809
-- VA, Zip=23452
DECLARE #Point_A_Lat AS REAL = 36.8366
DECLARE #Point_A_Long AS REAL = 76.0952
-- TX, Zip=75225
DECLARE #Point_B_Lat AS REAL = 32.8644
DECLARE #Point_B_Long AS REAL = 96.7946
DECLARE #DistanceInMiles AS REAL
SET #DistanceInMiles = SQRT (#SqDegreeLatInMiles * POWER(#Point_A_Lat - #Point_B_Lat,2) + #SqDegreeLongInMiles * POWER(#Point_A_Long - #Point_B_Long,2))
PRINT #DistanceInMiles

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

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.