Raster ST_Clip fails when a geometry barely intersects - postgis

I am trying to perform spatial statistics using postgis. Once in a while I have ST_Clip crushes and halt the query. I figure that this occurs when polygon barely intersects with raster. Please see the sample below.
SELECT ST_Summary(
ST_Clip(
ST_AddBand(
ST_MakeEmptyRaster(16, 16, 0, 0, 1, 1, 0, 0),
ARRAY[
ROW(1, '8BUI'::text, 0, 255),
ROW(2, '8BUI'::text, 0, 255),
ROW(3, '8BUI'::text, 0, 255)
]::addbandarg[]
)
-- this works
--, ST_GeomFromText('POLYGON((15.999999 15.999999, 15.999999 17, 17 17, 17 15.999999, 15.999999 15.999999))')
-- this fails
, ST_GeomFromText('POLYGON((15.9999999 15.9999999, 15.9999999 17, 17 17, 17 15.9999999, 15.9999999 15.9999999))')
)
);
With the above query I am getting following error.
psql:demo_clip_fail_barelyintersects.sql:16: ERROR: RASTER_clip: Could not get band from working raster
CONTEXT: PL/pgSQL function st_clip(raster,integer[],geometry,double precision[],boolean) line 8 at RETURN
I am hoping to getting no record returned instead, or some kind of empty raster. In my production code, the geometry/raster pair was found by ST_Intersects(r.rast, p.geom) between table of polygons and raster. One way I thought about making bounding box for raster which is slightly smaller than the extent of raster, but this is pretty ugly...
My version of postgres and postgis are
PostgreSQL 9.6.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.9.1,
64-bit
POSTGIS="2.3.1 r15264" GEOS="3.6.0-CAPI-1.10.0 r0" PROJ="Rel.
4.9.3, 15 August 2016" GDAL="GDAL 2.1.2, released 20 16/10/24" LIBXML="2.9.4" LIBJSON="0.12.1" RASTER
Thanks!

My tentative solution, wrap with begin/exception/end block and let the exception part to return empty raster. Performance suffers (~ two times). It will create false negative, but not sure what to look for...
-- function to work around bug in st_clip (fails when polygon barely intersects with raster)
-- not sure how much damage this has on performance
create or replace function st_clip_fuzzy(
rast raster, nband integer[],
geom geometry,
nodataval double precision[] DEFAULT NULL, crop boolean DEFAULT TRUE
)
returns raster
as $$
declare
rec record;
g geometry;
begin
return st_clip($1, $2, $3, $4, $5);
exception
when others then
select st_intersection(st_envelope(rast), geom) into g;
raise warning 'st_clip_fuzzy: intersection %', st_astext(g);
raise warning 'st_clip_fuzzy: area intersection %', st_area(g);
raise warning 'st_clip_fuzzy: area pixel %', abs(ST_ScaleX(rast) * ST_ScaleY(rast));
raise warning 'st_clip_fuzzy: area ratio %', st_area(g) / abs(ST_ScaleX(rast) * ST_ScaleY(rast));
return ST_MakeEmptyRaster(0, 0, ST_UpperLeftX(rast), ST_UpperLeftY(rast), ST_ScaleX(rast), ST_ScaleY(rast), ST_SkewX(rast), ST_SkewY(rast), ST_SRID(rast));
end;
$$ language 'plpgsql' immutable;
CREATE OR REPLACE FUNCTION st_clip_fuzzy(
rast raster, nband integer,
geom geometry,
nodataval double precision, crop boolean DEFAULT TRUE
)
-- four more interfaces with different set of arguments

Related

PostGIS Raster compute min, max altitude value and slope from a DEM

I have a DEM dataset and some polygons that represents parcels. For each parcels I would like to compute the maximum/minimum altitude and the average slope. Based on the PostGIS documentation and several example on Internet, two functions could be used to compute this data. The first one is ST_SummaryStatsAgg and the other is ST_DumpAsPolygons.
So, I've created a trigger that computes, before a new parcel is inserted, some statistics, but I am confused about the results. Here is my code:
--First cut the raster based on the parcel shape
SELECT INTO __rasterClip
ST_Union(ST_Clip(foncier.reunion_mnt.rast, NEW.geom, -9999, TRUE))
FROM foncier.reunion_mnt
WHERE NEW.geom && foncier.reunion_mnt.rast;
--Compute slope with ST_DumpAsPolygons OR ST_SummaryStatsAgg
SELECT INTO __slope1 (ST_DumpAsPolygons(ST_Slope(__rasterClip, 1, '32BF', 'DEGREES', 1.0))).val;
SELECT INTO __slope2 (ST_SummaryStatsAgg(ST_Slope(__rasterClip, 1, '32BF', 'DEGREES', 1.0), 1, TRUE, 1)).max;
RAISE NOTICE 'Slope1 %', MAX(__slope1 );
RAISE NOTICE 'Slope2 %', __slope2;
--Compute min/max altitude
SELECT INTO __rasterStats (ST_SummaryStatsAgg(__rasterClip, 1, TRUE, 1)).*;
SELECT INTO __polyDump (ST_DumpAsPolygons(__rasterClip, 1, TRUE)).*;
RAISE NOTICE 'Stat % - %', __rasterStats.min, __rasterStats.max;
RAISE NOTICE 'Poly % - %', Min( __polyDump.val ), Max( __polyDump.val );
The results of the RAISE NOTICE:
NOTICE: Slope1 5.14276456832886
NOTICE: Slope2 51.9147148132324
NOTICE: Stat 222.76 - 251.22
NOTICE: Poly 225.929992675781 - 225.929992675781
There is clearly something wrong. The slope between the two functions is not the same and the min and max altitude for the ST_DumpAsPolygons is the same.
So could you please help me and tell me:
What is the most effective way to compute the min/max altitude and the average slope for a parcel based on a raster DEM?
For my general knowledge is it best to use ST_SummaryStatsAgg or ST_DumpAsPolygons. In which case is it best to use on or the other?
In a trigger how to declare the variable type of these two functions (ST_SummaryStatsAgg, ST_DumpAsPolygons). My first attempt was to declare them using their return type (summarystats and geomval). But I was getting errors so I switch to Record. Is it correct?
Thanks for your help!

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

TSQL - get closest coordinate on linestring to a point

Consider the overly simplistic example: POINT(0 0) and LINESTRING (1 -10, 1 10)
The closest point on the line to the POINT would be 1, 0.
How would one determine this in TSQL? My simple, not entirely accurate, approach was to make a linestring (POINT POINT) and extend out the X coord of one coords until the two linestrings intersected.
So:
linestring (0 0, 0.25 0) (no intersect)
linestring (0 0, 0.5 0) (no intersect)
linestring (0 0, 0.75 0) (no intersect)
linestring (0 0, 1 0) (intersection - so 1 0 is the point closest to POINT
This quasi worked, but doesn't seem to the most bestest/more performant way of accomplishing this.
For example, one inefficiency is that I move it one direction (positive increments), and if there was no match (after x attempts), then I would start over, but with negative increments.
To optimize, I tried moving in larger steps, then when intersected (probably went past the point), I backed off 1 increment and started from there with a smaller increment. I did this a couple of times - instead of going in tiny tiny increments so as not to overshoot by too much.
One acceptable assumption based on my processing that the POINT will be next to (left/right) of the LINESTRING.
Another acceptable assumption is that the LINESTRING will be fairly "perpendicular" to the POINT.
I think you can do this mathematically rather than with a brute-force iterative algorithm.
There is a post to get closest point to a line that describes the method.
I converted this method to SQL which returns the correct value (1,0). Your 'trivial' example is actually a bit of an edge case (vertical line with infinite slope) so it seems robust.
I also tested the source code with this example: https://www.desmos.com/calculator/iz07az84f5 and using the input for the line of (-1,2) (3,0) and a point at (2,2) got the correct answer (1.4, 0.8).
SQL code (also in SQL Fiddle at http://sqlfiddle.com/#!6/d87aa/15)
DECLARE #x int, #y int, #x1 int, #y1 int, #x2 int, #y2 int
DECLARE #atb2 float, #atp_dot_atb float
DECLARE #t float
--SELECT #x=0, #y=0
--SELECT #x1=1, #y1=10, #x2=1, #y2=-10
SELECT #x=2, #y=2
SELECT #x1=-1, #y1=2, #x2=3, #y2=0
SELECT #atb2 = SQUARE(#x2-#x1) + SQUARE(#y2-#y1) -- Basically finding the squared magnitude of a_to_b
SELECT #atp_dot_atb = (#x-#x1)*(#x2-#x1) + (#y-#y1)*(#y2-#y1) -- The dot product of a_to_p and a_to_b
SELECT #t = #atp_dot_atb / #atb2 -- The normalized "distance" from a to your closest point
SELECT #x1 + (#x2-#x1)*#t, #y1 + (#y2-#y1)*#t --Add the distance to A, moving towards B

Find the points using Oracle spatial directly in front of a polgon

I have many polygons and many points and want to find the point such that when a line is drawn between that point and the polygon it does not intersect with any other polygon. So basically I need a point that is very close to the polygon and no other polygon is between them.
I tried the following query and it gives me all the points whether or not they are being intersected by a polygon or not.
SELECT P.POINTLOC from pointTable P WHERE NOT MDSYS.SDO_OVERLAPBDYINTERSECT(P.POINTLOC," +
"MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1)," +
"MDSYS.SDO_ORDINATE_ARRAY(4, 226, 150, 254, 164, 240, 191, 212, 176,4,226))) = 'TRUE'";
Then I tried this query and it gives some correct points but miss a few correct points:
SELECT P.POINTLOC from pointTable P WHERE MDSYS.SDO_WITHIN_DISTANCE(P.POINTLOC," +
"MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1)," +
"MDSYS.SDO_ORDINATE_ARRAY(4, 226, 150, 254, 164, 240, 191, 212, 176,4,226)),'distance = 40') = 'TRUE'";
Can some one point out which Oracle spatial operator would be best suited for this situation?
It sounds like you're looking for a Nearest Neighbour implementation - Oracle provides the Spatial operator SDO_NN.
You can find more details here:
http://docs.oracle.com/database/121/SPATL/sdo_operat.htm#SPATL1032
This will only get you so far, in that it'll find the nearest point to a polygon, but it won't guarantee that there are no polygons between your point and your target polygon. I suspect if you want to ensure this, you'll have have to get creative.
My approach would be:
use SDO_NN to get the closest point or points
use SDO_GEOM.SDO_CENTROID to find the polygon centre of gravity
create an in-query/in-memory SDO_GEOMETRY line that joins the two points
use this as the basis of a NOT EXISTS clause to exclude points where a polygon intersects that line
Something like the following untested / not-quite finished example, perhaps:
SELECT *
FROM points pnt
WHERE sdo_nn(pnt.point, sdo_geometry(your polygon here)) = 'TRUE' -- fill this with your polygon
AND NOT EXISTS (
SELECT 1
FROM polygons plg
WHERE sdo_geom.sdo_intersection(
plg.polygon
, sdo_geometry(
2002
, NULL -- oracle srid
, NULL -- point
, sdo_elem_info_array(1, 2, 1) -- line
, sdo_ordinate_array(
, sdo_geom.sdo_centroid(
sdo_geometry(your polygon here) -- fill this with your polygon
, 0.05
).x
, sdo_geom.sdo_centroid(
sdo_geometry(your polygon here) -- fill this with your polygon
, 0.05 -- tolerance
).t
, pnt.point.sdo_point.x
, pnt.point.sdo_point.y
) -- line between point and polygon centroid
)
, 0.05 -- tolerance
) = 'TRUE'
)
Depending on your dataset/performance, you might want to do some of this in PL/SQL using collections or loops.
The example above is a bit rough and ready, but I hope you get the gist.

STintersects() to find the Intersection point

I have two sql server geometry-ies which I am using to check if they intersect. If they do I need the intersection point.
Currently I can get only Boolean output where if it intersects = 1 and if it does not intersect it will give =0 . Is there any way I can find the intersection of two shapes in geometry?
Update this question led to my next question concerning how can one check if a point (lat/long) exists in a region which has 4000 points (lat/long). Can one use stcontains or stintersects on geography?
sql - STContains on Geography column
DECLARE #line1 GEOMETRY = geometry::STGeomFromText('LINESTRING(0 0, 1 1)', 0)
DECLARE #line2 GEOMETRY = geometry::STGeomFromText('LINESTRING(1 0, 0 1)', 0)
SELECT #line1.STIntersection(#line2).ToString()

Resources