I've uploaded a shapefile to SQL Server using ogr2ogr. This shapefile represents St. Tammany Parish.
Now I'm trying to check if a given coordinate falls within the parish and if not, then check if it is n meters away from the parish. To do this, I'm using geography::STContains for the first requirement and geography::STDistance for the second.
The issue is that STContains is always returning false. What is particularly frustrating is that if I select the geography field and the point, then look at the spatial results in SSMS, it appears to me that STContains should be returning true.
This is the query that I'm running:
DECLARE #0 INT; SET #0 = 30.480252;
DECLARE #1 INT; SET #1 = -90.112577;
SELECT TOP 1
Jurisdiction.GeographicArea,
geography::Point(#0, #1, 4326) AS Coordinate,
(geography::Point(#0, #1, 4326)).STContains(Jurisdiction.GeographicArea.MakeValid()) AS PointContainsShape -- returns 0,
Jurisdiction.GeographicArea.MakeValid().STContains(geography::Point(#0, #1, 4326)) AS ShapeContainsPoint -- returns 0,
Jurisdiction.GeographicArea.MakeValid().STDistance(geography::Point(#0, #1, 4326)) AS DistanceFromPointToShape -- returns 16580.3696843463
FROM
Jurisdiction
And these are images of the spatial results; notice that 30.480252, -90.112577 falls almost in the middle of the GeographicArea:
GeographicArea
Coordinate
At this point I'm a bit lost. Why would the geography methods return a false value when I can clearly see that the point is in the geography?
Related
I have a string in SQL with the following structure:
#number <logical> #number <logical> ....
ex:
#SQL='((#1 or #2) and (#10 or #21))'
I would like to update the string to be:
#SQL='((con_1=1 or con_2=1) and (con_10=1 or con_21=1))'
meaning remove the '#' and replace it with 'con_', leave the number (1 digit or more) as is and add '=1' after the digit.
Any idea how to do it?
Please avoid composing function or procedure for that.
you may use patindex, stuff or any other built in function for that.
First split the string into records on the '#' char that starts the value needing processed.
STUFF can be used to insert the '=1' after the last digit of the value in each record. I think searching for the last number is harder than searching for the first. The reverse allows searching for the first instead of the last. Because the string is reversed, insert the reverse '1=' at the start of the reverse number. If there is no number, return the revered value as is.
Concatenate it all back with 'con_' instead of '#'.
declare #SQL varchar(200) = ' ((#1 or #2) and (#10 or #21)) #33 #55 ';
with split as (
select value, REVERSE(value) as [rev], PATINDEX('%[0-9]%', REVERSE(value)) as [pat]
FROM STRING_SPLIT(#SQL, '#')
), fixed as (
select value, rev, pat, CASE WHEN pat= 0 THEN REVERSE(rev) ELSE REVERSE(STUFF(rev, pat, 0, '1=')) END as [fix]
FROM split
) select STRING_AGG(fix, 'con_') from fixed
Result: ((con_1=1 or con_2=1) and (con_10=1 or con_21=1)) con_33=1 con_55=1
It would be nice if Regex support was included in SQL server. I imagine to do so would require a careful implementation to avoid using all the server resources. A regex search on a millions of records or varchar(max) columns....
Found this solution which uses SQL# (SQLsharp).
SQL# is a .NET / CLR library that resides in a SQL Server 2005 (or newer) database and provides a suite of User-Defined Functions, Stored Procedures, User-Defined Aggregates, and User-Defined Types.
My solution:
declare #SQL varchar(200) = '#1 OR(#2 AND #3 AND (#4 or #5 or #6) AND (#7 or #8))'
SELECT SQL#.RegEx_Replace4k(#SQL, N'(#)+(\d*)', N'CON_$2=1', -1, 1, N'')
I am trying to check point in polygon, but STIntersect producing wrong result, below is given both point and polygon.
DECLARE #point GEOGRAPHY = GEOGRAPHY::Point(25.631119, 54.666181, 4326)
DECLARE #polygon GEOGRAPHY = GEOGRAPHY::STGeomFromText('POLYGON
((55.368827104612151 25.270499845366924, 55.368226289792808
25.268578811810126, 55.367410898252288 25.269529630198974,
55.368054628415855 25.270364015710307, 55.368827104612151
25.270499845366924))', 4326)
SELECT #polygon.STIntersects(#point), #point.STIntersects(#polygon)
Result is returning 1, however it should return 0, because i pick the point out side of fence,,
Am I making anything wrong with polygon coordinates? Please advice me for any stupid mistake of mine as I am new in spatial data types.
In order to "fix" this, we need to make some form of assumption. If the assumption cannot be "instruct the users to enter polygons that obey the left-hand rule" then we have to use something else.
Here, I'm assuming that the correct "sense" for the polygon is whichever way around means that it's enclosing the smallest area:
DECLARE #point GEOGRAPHY = GEOGRAPHY::Point(25.631119, 54.666181, 4326)
DECLARE #polygon GEOGRAPHY = GEOGRAPHY::STGeomFromText('POLYGON
((55.368827104612151 25.270499845366924,
55.368226289792808 25.268578811810126,
55.367410898252288 25.269529630198974,
55.368054628415855 25.270364015710307,
55.368827104612151 25.270499845366924))', 4326)
SET #polygon = CASE
WHEN #polygon.STArea() > #polygon.ReorientObject().STArea()
THEN #polygon.ReorientObject()
ELSE #polygon
END
SELECT #polygon
(You may also wish to do a plain SELECT #polygon before the CASE to see what the original looked like)
However, take careful note of what the above assumption means - if your users may wish to provide "the whole planet minus this small area around 55 degrees East/25 degrees North", there's now no way for you to accept such a value.
References - STArea ReorientObject
I've got a table full of WKT (well known text) in a varchar(max) column. This is formatted exactly as geometry. So, for example, one of the values is:
POLYGON ((174.893529167059 -37.0260462162965,174.89351593407 -37.0260221329151,174.893508034056 -37.0260077002766,174.893444415879 -37.0258916500588,174.893416916056 -37.0258414997842,174.893481733297 -37.0258186834198,174.893492016755 -37.0258150663863,174.89349653254 -37.025823316032,174.893512415978 -37.0258522827285,174.893556883897 -37.0259333832477,174.893591032956 -37.0259956661343,174.893604265986 -37.0260197504078,174.893575149738 -37.0260300006258,174.893529167059 -37.0260462162965))
However, I need to convert this varchar(max) field into a geometry field. Unfortunately, SQL Server needs to have some other information in order to do this, so I can't just change the type of the field from varchar(max) to geometry.
I've created a blank geometry column in the same table (MyGeometry) but I'm failing in trying to convert it. Here is my code (where 2193 is the CRS I'm dealing with). WKT is my varchar(max) field and MyGeometry is my new geometry field.
UPDATE MY_TABLE
SET MyGeometry = geometry::STPolyFromText('' + WKT + '', 2193)
EDIT - currently coming back saying the WKT is not valid (System.FormatException 24111 - the input isn't valid).
Which is strange as it matches up perfectly with some other inputs I have in how it's styled.
This will do :
UPDATE MY_TABLE
SET MyGeometry = geometry::STGeomFromText('POLYGON ((174.893529167059 -37.0260462162965,174.89351593407 -37.0260221329151,174.893508034056 -37.0260077002766,174.893444415879 -37.0258916500588,174.893416916056 -37.0258414997842,174.893481733297 -37.0258186834198,174.893492016755 -37.0258150663863,174.89349653254 -37.025823316032,174.893512415978 -37.0258522827285,174.893556883897 -37.0259333832477,174.893591032956 -37.0259956661343,174.893604265986 -37.0260197504078,174.893575149738 -37.0260300006258,174.893529167059 -37.0260462162965))', 0)
You can change that 'POLYGON....' to your VARCHAR(MAX) field.
0 at the end, which is SRID only relevant if you're using Geography.
You can also try :-
UPDATE MY_TABLE SET MyGeometry = GEOMETRY::STGeomFromText('POLYGON ((174.893529167059 -37.0260462162965,174.89351593407 -37.0260221329151,174.893508034056 -37.0260077002766,174.893444415879 -37.0258916500588,174.893416916056 -37.0258414997842,174.893481733297 -37.0258186834198,174.893492016755 -37.0258150663863,174.89349653254 -37.025823316032,174.893512415978 -37.0258522827285,174.893556883897 -37.0259333832477,174.893591032956 -37.0259956661343,174.893604265986 -37.0260197504078,174.893575149738 -37.0260300006258,174.893529167059 -37.0260462162965))',4326);
The value of SRID passed should be 4326 and not 0
You can check the URL below. I found this information in another similar question (older post) so pasting it without any changes
I found solution, SQL Server Spatial Tools
http://sqlspatialtools.codeplex.com/
Followings are the methods solved my problem.
IsValidGeographyFromText(string inputWKT, int srid)
Check if an input WKT can represent a valid geography. This function requires that the WTK coordinate values are longitude/latitude values, in that order and that a valid geography SRID value is supplied. This function will not throw an exception even in edge conditions (i.e. longitude/latitude coordinates are reversed to latitude/longitude).
SqlGeography MakeValidGeographyFromText(string inputWKT, int srid)
Convert an input WKT to a valid geography instance. This function requires that the WKT coordinate values are longitude/latitude values, in that order and that a valid geography SRID value is supplied.
The following is being attempted in the console for a postgis enabled rails4.2 application.
#target = Target.last
#meter_radius = 1000
#valid_points = Target.where("ST_DWithin(#{#target.lat}, #{#target.lon}, #{#meter_radius}))
lat and lon are defined as decimal values. This translates into the following query
SELECT "targets".* FROM "targets" WHERE (ST_DWithin(38.656679, 15.984094, 1000))
with the error:
PG::UndefinedFunction: ERROR: function st_dwithin(numeric, numeric, integer) does not exist
I believe I need to declare the data type (geometric or geographic) for these values but am not sure how. I am also wondering whether the ST_DWithin function can work off of the 3857 data type, even though the documentation does not state so.
note the #target object also has a lonlat attributes defined as a spatial value in postgresql with :srid=>3857, :type=>"point" defined.
Update
#valid_points = Target.where("ST_DWithin(lonlat, ST_PointFromText('#{#target.lonlat}', #{#meter_radius}))
returns a result and thus appears syntactically valid.
SELECT "targets".* FROM "targets" WHERE (ST_DWithin(lonlat, ST_PointFromText('POINT (15.984094 38.656679)', 3857), 1000))
However the result is incorrect. It essentially finds all the points of the table. Being of SRID type, this needed to be expressed in degrees, not meters.
One answer, leveraging the lonlat spatial point stored:
#target = Target.last
#degree_radius = 0.2249
#valid_points = Target.where("ST_DWithin(lonlat, ST_PointFromText('#{#target.lonlat}', #{#degree_radius}))
which returns a result and thus appears syntactically valid.
SELECT "targets".* FROM "targets" WHERE (ST_DWithin(lonlat, ST_PointFromText('POINT (15.984094 38.656679)', 3857), 0.2249))
I'm having an issue with SQL Server 2008 and the geometry functions. I'm trying to create non-overlapping shapes using the STDifference function... but it does not seem to be working 100% of the time.
Here is all the sample code to demonstrate the issue. Anybody have any idea to make these non-overlapping polygons? (except "don't make your polygons so complex")
-- create our Geometry polygons.
DECLARE #Geo1 Geometry, #Geo2 Geometry;
SET #Geo1 = geometry::STGeomFromText('POLYGON ((-79725887.5234375 42951274.765625, -79699548.921875 42991827.84375, -79695546.375697568 42997990.498925969, -79695559.967852488 42998690.733179785, -79696152.0625 43029192.375, -79715247.75 43051708.75, -79802181.8984375 43020797.71875, -79806253.6640625 43018621.6953125, -79810436.03125 43014767.046875, -79825837.5 43000572.375, -79830640.625 42973672.03125, -79832936.7265625 42960812.4296875, -79725887.5234375 42951274.765625))',0);
SET #Geo2 = geometry::STGeomFromText('POLYGON ((-79863430.6875 42937209.4375, -79850399.0625 42940068.75, -79850394.9375 42940069.625, -79845320.6875 42946082.625, -79843216.25 42948576.375, -79832949.125 42960743, -79814909.939453125 43061773.556640625, -79817218.30078125 43060262.947265625, -79823496.6875 43056154.3125, -79867800.5 43027161.5625, -79871834.50390625 43024521.70703125, -79875660.228515625 43022018.123046875, -79875666.8125 43022013.8125, -79875674.536313191 43022008.760254942, -79875676.408203125 43022007.53515625, -79875677.236328125 43022006.994140625, -79875718.458984375 43021980.017578125, -79875728.506891936 43021973.442301653, -79881723.017578125 43018050.58984375, -79882437.0625 43017583.3125, -79882585.375 43017486.25, -79884466.9375 43016254.9375, -79884550.875 43016200, -79886514.3125 43014915.125, -79887785.5 43014083.25, -79887790.4375 43014080, -79887793.125 43014078.26953125, -79887807.171875 43014069.076171875, -79887825.568359375 43014057.03515625, -79887831.322335 43014053.269705132, -79888029.5 43013923.5625, -79890094.5625 43012572.1875, -79934236.875 42983685.125, -79863430.6875 42937209.4375))',0);
-- A quick test so you can see them
SELECT #Geo1, 'Geo1' as MyName UNION ALL SELECT #Geo2, 'Geo2' as MyName;
-- how do they compare initially?
SELECT
#Geo1.STTouches(#Geo2) as 'Touches'
,#Geo1.STOverlaps(#Geo2) as 'Overlaps'
,#Geo1.STIntersects(#Geo2) as 'Intersects'
-- try to make them NOT overlap or intersect
SET #Geo1 = #Geo1.STDifference(#Geo2);
SET #Geo2 = #Geo2.STDifference(#Geo1);
-- Now, how do they compare?
SELECT
#Geo1.STTouches(#Geo2) as 'Touches'
,#Geo1.STOverlaps(#Geo2) as 'Overlaps'
,#Geo1.STIntersects(#Geo2) as 'Intersects'
-- what does the intersection look like?
SELECT #Geo1.STIntersection(#Geo2).STAsText();
This is the final select result:
MULTIPOLYGON (((-79831832.015625 42966999.5078125, -79830502.34765625 42974446.45703125, -79830181.430394545 42976243.79133676, -79830220.96484375 42976022.375, -79831832.015625 42966999.5078125)), ((-79832481.0538819 42963364.484146826, -79832328.75390625 42964217.45703125, -79831832.015625 42966999.5078125, -79832481.0538819 42963364.484146826)))
Not an answer, just an observation... I get different results if I introduce two new variables for the two differences.
-- create our Geometry polygons.
DECLARE #Geo1 Geometry, #Geo2 Geometry, #Geo3 Geometry, #Geo4 Geometry;
SET #Geo1 = geometry::STGeomFromText('POLYGON ((-79725887.5234375 42951274.765625, -79699548.921875 42991827.84375, -79695546.375697568 42997990.498925969, -79695559.967852488 42998690.733179785, -79696152.0625 43029192.375, -79715247.75 43051708.75, -79802181.8984375 43020797.71875, -79806253.6640625 43018621.6953125, -79810436.03125 43014767.046875, -79825837.5 43000572.375, -79830640.625 42973672.03125, -79832936.7265625 42960812.4296875, -79725887.5234375 42951274.765625))',0);
SET #Geo2 = geometry::STGeomFromText('POLYGON ((-79863430.6875 42937209.4375, -79850399.0625 42940068.75, -79850394.9375 42940069.625, -79845320.6875 42946082.625, -79843216.25 42948576.375, -79832949.125 42960743, -79814909.939453125 43061773.556640625, -79817218.30078125 43060262.947265625, -79823496.6875 43056154.3125, -79867800.5 43027161.5625, -79871834.50390625 43024521.70703125, -79875660.228515625 43022018.123046875, -79875666.8125 43022013.8125, -79875674.536313191 43022008.760254942, -79875676.408203125 43022007.53515625, -79875677.236328125 43022006.994140625, -79875718.458984375 43021980.017578125, -79875728.506891936 43021973.442301653, -79881723.017578125 43018050.58984375, -79882437.0625 43017583.3125, -79882585.375 43017486.25, -79884466.9375 43016254.9375, -79884550.875 43016200, -79886514.3125 43014915.125, -79887785.5 43014083.25, -79887790.4375 43014080, -79887793.125 43014078.26953125, -79887807.171875 43014069.076171875, -79887825.568359375 43014057.03515625, -79887831.322335 43014053.269705132, -79888029.5 43013923.5625, -79890094.5625 43012572.1875, -79934236.875 42983685.125, -79863430.6875 42937209.4375))',0);
-- A quick test so you can see them
SELECT #Geo1, 'Geo1' as MyName UNION ALL SELECT #Geo2, 'Geo2' as MyName;
-- how do they compare initially?
SELECT
#Geo1.STTouches(#Geo2) as 'Touches'
,#Geo1.STOverlaps(#Geo2) as 'Overlaps'
,#Geo1.STIntersects(#Geo2) as 'Intersects'
-- try to make them NOT overlap or intersect
SET #Geo3 = #Geo1.STDifference(#Geo2);
SET #Geo4 = #Geo2.STDifference(#Geo1);
-- Now, how do they compare?
SELECT
#Geo3.STTouches(#Geo4) as 'Touches'
,#Geo3.STOverlaps(#Geo4) as 'Overlaps'
,#Geo3.STIntersects(#Geo4) as 'Intersects'
-- what does the intersection look like?
SELECT #Geo3.STIntersection(#Geo4).STAsText();
Compare:
Touches Overlaps Intersects
1 0 1
Final Select:
MULTIPOINT ((-79830220.96484375 42976022.375), (-79831832.015625 42966999.5078125))
I suspect this is due to the method/precision with which SQL Server performs spatial calculations. For operations like STIntersection() etc, SQL Server rounds the supplied float coordinate values to a 27 bit integer grid. This can introduce slight distortions to the coordinates returned as the result of spatial operations.
In your case, your coordinate values are large, which means that the fixed size integer grid must be made quite coarse to accommodate the full range of data. The relative difference between the intersecting coordinates that you're trying to remove, however, is very small. When snapped to this coarse grid, SQL Server is unable to correctly identify the area of intersection.
See here for a explanation, and also a suggestion that this situation has been improved in SQL Server Denali:
https://connect.microsoft.com/SQLServer/feedback/details/580254/spatial-operations-are-done-with-a-low-precision-causing-troubles-in-the-returned-data
As pointed out by Joe Stefanelli, your polygons don't technically overlap but they do intersect. The reason is that if you cut a hole out of one polygon using a smaller polygon, the smaller polygon will not overlap the new polygon but it will intersect it along the edge of the cut. It isn't like a table saw that removes some material along the edge of the cut. I could not include the code because SO is having a problem with certain SQL statements and blocks the post.
SEE REFERENCE A in image
If you would like it not to overlap OR intersect, you can create your own sawblade effect. Apply a very small buffer around the smaller of the geometries when using it in the STDifference() function.
SEE REFERENCE B in image
I know this post is old but it does seem pertinent still for those of us who will still be on 2008 for a while.