SQL Server Polygon format - sql-server

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).

Related

Trying to find distance between two sets of Lat-longs

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.

How to get the distance between points in my geom and entry point longitude and latitude

everyone, I have table name 'geom' and I would like to calculate the distance between points that exist in my table like 0101000020730800001DAB949EE95D4040E124CD1FD3F04340
and entry points longitude and latitude and I tried this
SELECT *
FROM postgis.cafeecoor
WHERE ST_Distance_Sphere(geom, ST_MakePoint(32.733792,39.865589)) <= 1 * 1609.34
You can use the geography datatype that returns distances in meters.
SELECT *, st_distance(geom::geography, ST_MakePoint(32.733792,39.865589)::geography)
FROM postgis.cafeecoor
For your example, it returns 1760.32533367 meters.
Depending how your geometry is saved (as a true geometry or as text, with or without a set projection), you might have to add a few extra steps, like creating the geometry and setting its coordinate system
SELECT *, st_distance(
st_setsrid(geom::geometry,4326)::geography,
ST_MakePoint(32.733792,39.865589)::geography)
FROM postgis.cafeecoor;

SQL WKT draws well with geometry, but not with geography data type

I have a following piece of code
DECLARE #g geometry;
DECLARE #borders geography;
SET #g = geometry::STGeomFromText('SOME WKT', 0);
SET #borders = GEOGRAPHY::STGeomFromText(#g.MakeValid().STAsText(),4326)
SELECT #g;
SELECT #borders;
Since it's too long to paste it here, WKT Can be fond at this link: https://justpaste.it/6qw0a
Can someone please explain to me, why it displays well when I draw it as geometry, but when I try to draw it as a geography, it displays entire world instead of a small group of islands.
Here's the screenshot:
You have a ring orientation problem. For geography shapes, the order in which you specify the points on the border matters. That is, imagine a square with corners A, B, C, and D. (A, B, C, D, A) is not the same as (A, D, C, B, A). One of them specifies what you think it would while the other defines the rest of the world with a square shaped hole in it! But don't feel bad, this is a very common "gotcha!" in geographic data.
Your data is inconsistent in how it defines its points. That is, some specify the island border as clockwise while others are specified counterclockwise. All is not lost though. Using your WKT, I believe I was able to recover the desired shape(s).
DECLARE #wkt varchar(max) = '«your wkt here»';
DECLARE #borders geography;
SET #borders = GEOGRAPHY::STGeomFromText(#wkt, 4326);
select geography::UnionAggregate(b2.g)
from Util.dbo.Numbers as n
cross apply (
select #borders.STGeometryN(n.n) as g1,
#borders.STGeometryN(n.n).ReorientObject() as g2
) as b
cross apply (
select case when g1.EnvelopeAngle() > 90 then g2 else g1 end as g
) as b2
where n.n <= #borders.STNumGeometries();
By way of explanation, I'm picking out each individual geometry from the geometry collection by index, specifying both it, and it's re-oriented version via a cross apply, and then using the heuristic of "if the envelope angle of the geography is greater than 90°, it's probably mis-oriented" to choose the (likely to be) correct one. From there, I throw it all into a UnionAggregate to jam them all back into one geography instance.
Lastly, in case it's not obvious, Numbers is just a table of integers that I have lying around for occasions such as these.
DECLARE #geom GEOMETRY = 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))';
DECLARE #geog GEOGRAPHY = #geom.MakeValid().STUnion(#geom.STStartPoint()).STAsText()
This topic had the answer to my question

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