Postgres / postgis - ensure all polygons are closed - postgresql

I am needing to write a query which finds any unclosed polygons and closes them by copying the first point and creating an additional end point.
I am able to select the invalid rows:
SELECT delivery_zone_id, polygon from delivery_zone WHERE ST_IsClosed(polygon::geometry) = FALSE;
And I am able to dump the individual points from each of the polygons:
SELECT delivery_zone_id, ST_AsText((dp).geom) FROM
(SELECT delivery_zone_id, ST_DumpPoints(polygon::geometry) AS dp
FROM delivery_zone
WHERE ST_IsClosed(polygon::geometry) = FALSE
) AS coords;
And the result looks like this:
1 POINT(-96.80037 33.09812) ## Copy this point and add it to the set
1 POINT(-96.80427 33.0956)
1 POINT(-96.80401 33.09219)
1 POINT(-96.79603 33.09222)
1 POINT(-96.79346 33.09647)
1 POINT(-96.80037 33.09857)
4 POINT(-96.80037 33.099) ## Copy this point and add it to the set
4 POINT(-96.80427 33.0956)
4 POINT(-96.80401 33.09219)
4 POINT(-96.79603 33.09222)
4 POINT(-96.79346 33.09647)
4 POINT(-96.80037 33.09923)
This is where my sql skills are lacking. I need some help copying the first point and creating a new end point with that data. Pseudo queries are welcome - I just need to see what it might look like and I can fill in the gaps.
Update: Final solution
Thanks to the answer from JGH below, I was able to create the following update query. This will find any unclosed polygons and add a new end point by copying the first point.
Note: this will only work with simple "single" polygons. If you have complex outer and inner polygons, you will need to make some drastic changes to this query.
UPDATE delivery_zone dz
SET polygon=ST_MakePolygon(ST_AddPoint(subquery.openline, ST_PointN(subquery.openline, 1), -1))
FROM (
SELECT delivery_zone_id, ST_ExteriorRing(polygon::geometry) AS openline
FROM delivery_zone WHERE ST_IsClosed(polygon::geometry) = FALSE
) AS subquery
WHERE dz.delivery_zone_id = subquery.delivery_zone_id;

You can try using lines to add the point, then converting to polygon.
Let's note that creating a not-closed polygon is not possible... not too sure how you got one, and hopefully you would be able to convert them to a line at first.
So, the idea is to get the line, then add a point to it at the last position (-1). This point would be the same as the first point of this line (position 1). At last you can convert to a polygon
WITH src AS (
SELECT ST_GeomFromText('LINESTRING(0 0, 0 1, 1 1, 1 0)') As openline)
SELECT st_asText(openline),
st_asText(ST_MakePolygon(st_addPoint(openline,st_PointN(openline,1),-1)))
FROM src;
st_astext | st_astext
-----------------------------+--------------------------------
LINESTRING(0 0,0 1,1 1,1 0) | POLYGON((0 0,0 1,1 1,1 0,0 0))
(1 row)

Related

Postgis - ST_within didn't do what I want. How to find a point in a hollow area?

See the screen print.
I ran a spatial query in Postgis to return the electoral constituency (area) that a point on the map lies in. The query uses a ST_within function where the point is within a polygon.
As you can see from the print, the point is not actually 'in' the polygon area of York Outer although technically you might say it's 'within' it, or at least Postgis thinks so. The point would actually lie in York Central.
I'm sure Postgis actually returns both but since I only fetch the first record from the cursor, this is what I see.
A point can only be in one electoral constituency at a time and this query has returned the wrong one or rather I asked the wrong question of the database.
Which function should I be using to ensure I always return the correct area for a point where it's possible the area may have a hollow interior or be a strange shape?
Thanks
Phil
This should work as you described it. Maybe something is wrong with the data? Could you provide a small repro, with polygon / point data?
Also, a somewhat common reason for such problems is not valid GIS data. You can check the polygon shape with PostGIS's ST_IsValid function. If the data is not valid, different tools might interpret it in different ways, and how GIS data is drawn might not match what PostGIS thinks this data represents, causing more confusion.
Here is a simple repro showing it works as you expect it to work, with point inside the outer polygon's hole only st_within the inner polygon, not the outer one:
select st_astext(point), name
from
(select
'outer' as name,
st_geomfromtext('polygon((0 0, 30 0, 30 30, 0 30, 0 0), (10 10, 20 10, 20 20, 10 20, 10 10))') g
union all
select
'inner' as name,
st_geomfromtext('polygon((10 10, 20 10, 20 20, 10 20, 10 10))') g
) shapes
cross join
(select st_geomfromtext('point(15 15)') point
union all
select st_geomfromtext('point(5 5)') point
) points
where st_within(point, g)
My results are
1 POINT(5 5) outer
2 POINT(15 15) inner
Considering your polygons and query are the way you described, it should work without any problems. Consider the following geometries ..
.. you see that the point lies only inside the inner polygon. If you perform a query with ST_Within giving the coordinates of the point, you should get only the inner polygon:
WITH j (geom) AS (VALUES
('POLYGON((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))'),
('POLYGON((26.88 31.08,30.57 31.08,30.57 28.49,26.88 28.49,26.88 31.08))'))
SELECT * FROM j
WHERE ST_Within('POINT(28.46 28.64)',j.geom)
However, if your query is somehow using the BBOX of the polygons instead of their area, you will indeed get the outer polygons as well, e.g.:
WITH j (geom) AS (VALUES
('POLYGON((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))'),
('POLYGON((26.88 31.08,30.57 31.08,30.57 28.49,26.88 28.49,26.88 31.08))'))
SELECT * FROM j
WHERE ST_Within('POINT(28.46 28.64)',j.geom::GEOMETRY::BOX2D)
Consider adding a data sample and the query to your question. Hopefully it helps you debug your code.

Orientdb - Traverse based on a specific edge field

In the figure below each vertex is connected to others with edges of class E_FIELD. Each edge has a field called propName. Starting from a vertex I need to traverse through edges with propName set a specific value. In the figure below, I need to start from vertex 1 and traverse through the edges which have 'place' as their propName fields (going from 1 to 2 and then to 3). It should exclude vertex 4 as its propName is not set to place. How can I do this?
The following query should help you in your quest:
SELECT FROM (TRAVERSE * FROM <your_vertex_rid>) WHERE #class = "E_FIELD" AND propName = "place"

How to change background color for each two rows in SSRS in a group

How can I write the expression in order to change background color for each two rows in SSRS?
I need something like that:
I tried expression
=IIF(Fields!Type.Value="2016 Submitted" , "LightBlue",
IIF(Fields!Type.Value="2015 Submitted" , "LightBlue",
Nothing))
But because some months dont have values it looks like this:
If I try this expression I get below:
=IIF(RunningValue(Fields!Count.Value, CountDistinct, Nothing) MOD 2 = 1, "White", "PaleTurquoise")
Dance-Henry I tried your code
=IIF(RowNumber(Nothing) Mod 4 = 1 or RowNumber(Nothing) Mod 4 = 2, "Aqua","White")
and this is what i got:
You can select the row in design pane and press F4 to setup property BackgroundColor as =IIF(RowNumber(Nothing) Mod 4 = 1 or RowNumber(Nothing) Mod 4 = 2, "Aqua","White")
Captures are attached. Do it accordingly.
RESULT is something like this
After going thru the link https://blogs.msdn.microsoft.com/chrishays/2004/08/30/green-bar-matrix/
Tested and it works well for the Green Bar Effect for Matrix. I will show it step by step here as for later reference.
Step 1: Create the Matrix and add one more column under the innermost row grouping in your matrix. (ColorNameTextbox here)
Step 2: Select the textbox of ColorNameTextbox and press F4 to setup BackgroundColor property as =Value shown below.
Step 3: Select the textbox of Matrix cell and press F4 to setup BackgroundColor property as =ReportItems!ColorNameTextbox.Value shown below.
Step 4: Drag the inner grouping header (ColorNameTextbox) to be as narrow as possible.
Step 5: Preview Pane to check the result.
If you could add a hidden color_group column and populate it with a new number at each point you want to change the color (in your case 1,1,2,2,3,3,4,4) then you could use something like the following (which works for varying size groups):
IIF(RunningValue(Fields!color_group.Value, CountDistinct, Nothing) MOD 2 = 1, "White", "PaleTurquoise")
I had a similar problem where I could not come up with alternating rows because my data has Column Groups that were the RowNumber(Nothing) method to fail. Learning from all the other posts here this is how I resolved it in steps.
I added the following code into the report properties, that provides a function to get the row number, each time it is called. The function increments the row count each time it is called. >>Right click space around the report >> Properties >> Code. Alternatively go to Code Properties Window, when the report is selected.
Add the following lines:
Public Row_Sum As Decimal = 0
Public Function Lookup_Sum( ) As integer
Row_Sum = Row_Sum + 1
Return Row_Sum
End Function
I added a new column at the beginning of the rows called No. that would calculate and show the row number.Right click the first column >>Insert Column>>Inside Group-Left.
On the expression for the new report add this line of code. Also take a note of the name of the TextBox that will have the No. Value. It will be needed in the next step. In my case the TextBox is called TextBox6 (Properties Window). >>Right Click Cell>>Expression.
Add the code:
=Code.Lookup_Sum()
I highlighted the entire row and went to the Background property and added the following expression to compute the row number.
Add the code(TextBox6 is the name Textbox name noted above):
=IIF(VAL(ReportItems!Textbox6.Value) MOD 2, "LIGHTBLUE", "WHITE")

How to find LINESTRINGs that touch in a begin/ending node

In PostGIS you can intersect two geometries using:
geometry ST_Intersection (geometry geomA, geometry geomB);
In my case both geomA and geomB are LINESTRING so ST_Intersection() returns a POINT geometry.
I want to know if the intersection occurs in a begin/end node (the geometries touch) or in the middle (the geometries intersect).
I can compare the (Point.X, Point.Y) with each ending node:
geomA.nodes(0) - geomA.nodes(len-1)
geomB.nodes(0) - geomB.nodes(len-1)
But is very complex. And I would like a simple solution.
There are 3 intersect cases.
Example 1: Two lines in a "L" shape intersect in an end node on both lines on the bottom left.
Example 2: Two lines in a "T" shape where the vertical line intersects in the middle of the horizontal line. In this case the vertical line end node touches a non-end node of the horizontal line.
Example 3: Two lines in a "X" shape. Intersection point isn't an end node for either line.
For my problem I'm only interested in finding the touching scenario like Example 2.
NOTE
This is the pseudo code I use now.
geomM, geomN Linestring
a, b, c, d, z Points.
(a,b) begin/end node for geomM ST_StartPoint(geom) and ST_EndPoint(geom)
(c,d) begin/end node for geomN
z = ST_Intersect(geomM, geomN)
SELECT geomM, geomN, z
FROM Table
WHERE
(A and not ( B or C or D))
OR (B and not ( A or C or D))
OR (C and not ( A or B or D))
OR (D and not ( A or B or C))
A, B, C, D replace ( a=z ) ( b=z ) ( c=z ) ( d=z )
This mean one node {a,b,c,d} is equal to intersection z. But only one
This return all "T" shape intersections.
You need the PostGIS function ST_Touches() here. The function returns true if the geometries touch on their boundaries, but false if they intersect. In terms of your examples, Example 1 and 2 return true, Example 3 returns false.
Relaxed solution
To select the IDs of all pairs of touching geometry(LINESTRING, xxx) records from a single table use this:
SELECT x.id AS idA, y.id AS idB
FROM my_table x
JOIN my_table y ON ST_Touches(y.the_geom, x.the_geom)
WHERE x.id < y.id;
(The WHERE clause avoids duplicate results like (132, 254) and (254, 132).)
Note that the linestrings can also touch on any of their non-node vertices. If you want to strictly follow Example 2 then you have to compare every point on every linestring against every point on all other linestrings, which is obviously going to be a very intensive operation. Example 2 is basically only feasible when you know that the linestrings are very short, preferably just straight lines.
Strict solution, straight lines only
If all LINESTRINGs are straight, i.e. composed of a starting and an ending node only, then this is your solution:
SELECT h.id AS touched, v.id AS touching, ST_Intersection(h.the_geom, v.the_geom) AS touch_point
FROM my_table h -- "horizontal" T bar, being touched
JOIN my_table v ON -- "vertical" T bar, touching
(
-- The "vertical" start node touches, but not on either of the "horizonal" nodes
ST_Equals(ST_Intersection(h.the_geom, v.the_geom), ST_StartPoint(v.the_geom))
AND NOT ST_Equals(ST_StartPoint(h.the_geom), ST_StartPoint(v.the_geom))
AND NOT ST_Equals(ST_EndPoint(h.the_geom), ST_StartPoint(v.the_geom))
) OR (
-- The "vertical" end node touches, but not on either of the "horizonal" nodes
ST_Equals(ST_Intersection(h.the_geom, v.the_geom), ST_EndPoint(v.the_geom))
AND NOT ST_Equals(ST_StartPoint(h.the_geom), ST_EndPoint(v.the_geom))
AND NOT ST_Equals(ST_EndPoint(h.the_geom), ST_EndPoint(v.the_geom))
);
All the requirements are checked in the JOIN ON clause. This will also return the location where the "vertical" bar of the T touches the "horizontal" bar. Note that the conditions are short-circuited when being evaluated and repeated calls to a function with the same input data are optimized to a single call.

Intersecting Layers Returns Empty Collection Despite Visible Intersection

When I perform an intersection of a polygon I draw in OpenLayers and a postgis database layer, it seems that I am getting incorrect results.
Intersection works correctly on some layers. For instance, if I intersect a triangle with a layer of polygons that represent crop fields, I get the following:
The query my app generates to produce the above result is:
SELECT ST_AsText(ST_Intersection(%(geometries_0)s::geometry, %(geometry)s::geometry))
where geometries_0 is my triangle:
POLYGON((-104.84928345939991 40.518951354186285,-104.82319093011056 40.51953858115158,-104.83700967095314 40.50707521626648,-104.84928345939991 40.518951354186285))
and geometry is my layer of crop fields, as well-known text:
MULTIPOLYGON(((-104.841309611298 40.5075331998226,-104.84173356681 40.5069932245841,-104.842041204329 40.50640946683,-104.842224948796 40.5057962996657,-104.842280275816 40.5051688207073,-104.842205823049 40.5045424803865,-104.842003423773 40.5039327015263,-104.841678061729 40.5033544995574,-104.841237748411 40.502822112724,-104.840693325791 40.5023486513933,-104.840058199365 40.5019457751149,-104.839348008051 40.5016234053897,-104.838580239118 40.5013894812384,-104.837773797582 40.5012497635973,-104.836948540713 40.501207693373,-104.836124789073 40.5012643066572,-104.83532282616 40.5014182091969,-104.834562398965 40.5016656107496,-104.833862231727 40.5020004184754,-104.833239564888 40.5024143870601,-104.832709730574 40.5028973218633,-104.83228577506 40.5034373300773,-104.831978137541 40.5040211136997,-104.831794393074 40.5046342970926,-104.831739066055 40.5052617810515,-104.831813518821 40.5058881146554,-104.832015918097 40.5064978757385,-104.832341280141 40.5070760506105,-104.83278159346 40.5076084036796,-104.833326016079 40.5080818278834,-104.833961142505 40.5084846673069,-104.834671333819 40.5088070040565,-104.835439102753 40.5090409023397,-104.836245544289 40.5091806037522,-104.837070801158 40.5092226689759,-104.837894552799 40.5091660624086,-104.83869651571 40.509012177646,-104.839456942906 40.5087648031902,-104.840157110143 40.5084300292289,-104.840779776982 40.5080160977709,-104.841309611298 40.5075331998226)))
However, if I perform the same query with a different layer ("soils"), I get an empty result:
The query is the same:
SELECT ST_AsText(ST_Intersection(%(geometries_0)s::geometry, %(geometry)s::geometry))
with a polygon geometries_0 that should overlap:
POLYGON((-104.84627938530097 40.54511058649626,-104.83460641167578 40.545175808723876,-104.84070039055733 40.537283458057615,-104.84627938530097 40.54511058649626))
and a geometry layer representing soils, similar to the crop fields in the above query:
MULTIPOLYGON(((-104.939716 40.258166,-104.939775 40.258174,-104.939963 40.258159,-104.940159 40.258065,-104.940039 40.257671,-104.939917 40.25749,-104.939928 40.257419,-104.94003 40.257404,-104.940265 40.257641,-104.940632 40.257902,-104.940826 40.258061,-104.941051 40.258188,-104.941123 40.258235,-104.941205 40.258283,-104.941246 40.258275,-104.941287 40.258212,-104.941186 40.258094,-104.941186 40.258007,-104.941167 40.257921,-104.941105 40.257858,-104.941044 40.257786,-104.941045 40.257716,-104.941127 40.257676,-104.94122 40.257653,-104.94141 40.257731,-104.941559 40.257671,-104.941255 40.257181,-104.940857 40.256794,-104.940644 40.256478,-104.940319 40.255997,-104.940003 40.255728,-104.939676 40.255561,-104.939419 40.255544,-104.938895 40.255529,-104.938287 40.255512,-104.938046 40.255528,-104.937733 40.255549,-104.937322 40.255533,-104.937012 40.255577,-104.936947 40.255593,-104.936623 40.255774,-104.936581 40.255924,-104.93658 40.256042,-104.936661 40.256223,-104.93671 40.256436,-104.936842 40.256618,-104.937262 40.256753,-104.937662 40.256818,-104.937897 40.257,-104.938181 40.25745,-104.938374 40.257742,-104.938465 40.257931,-104.938782 40.258051,-104.939121 40.258092,-104.939439 40.258133,-104.939716 40.258166)))
I use the postgis ST_AsText function to convert database layers to well-known text, and I checked to make sure that all layers have the EPSG:4326 projection (using the Find_SRID function).
Why would one layer (crop fields) intersect correctly, and another (soils) not? I've tried the same query using geographies instead of geometries, with the same result.
It returns an empty collection because they don't intersect. In fact, they are 32 km away from each other.
SELECT ST_Intersects(A, B), ST_Distance(A, B)/1000 AS dist_km
FROM (
SELECT
'POLYGON((-104.84627938530097 40.54511058649626,-104.83460641167578 40.545175808723876,-104.84070039055733 40.537283458057615,-104.84627938530097 40.54511058649626))'::geography AS A,
'MULTIPOLYGON(((-104.939716 40.258166,-104.939775 40.258174,-104.939963 40.258159,-104.940159 40.258065,-104.940039 40.257671,-104.939917 40.25749,-104.939928 40.257419,-104.94003 40.257404,-104.940265 40.257641,-104.940632 40.257902,-104.940826 40.258061,-104.941051 40.258188,-104.941123 40.258235,-104.941205 40.258283,-104.941246 40.258275,-104.941287 40.258212,-104.941186 40.258094,-104.941186 40.258007,-104.941167 40.257921,-104.941105 40.257858,-104.941044 40.257786,-104.941045 40.257716,-104.941127 40.257676,-104.94122 40.257653,-104.94141 40.257731,-104.941559 40.257671,-104.941255 40.257181,-104.940857 40.256794,-104.940644 40.256478,-104.940319 40.255997,-104.940003 40.255728,-104.939676 40.255561,-104.939419 40.255544,-104.938895 40.255529,-104.938287 40.255512,-104.938046 40.255528,-104.937733 40.255549,-104.937322 40.255533,-104.937012 40.255577,-104.936947 40.255593,-104.936623 40.255774,-104.936581 40.255924,-104.93658 40.256042,-104.936661 40.256223,-104.93671 40.256436,-104.936842 40.256618,-104.937262 40.256753,-104.937662 40.256818,-104.937897 40.257,-104.938181 40.25745,-104.938374 40.257742,-104.938465 40.257931,-104.938782 40.258051,-104.939121 40.258092,-104.939439 40.258133,-104.939716 40.258166)))'::geography AS B
) AS data;
st_intersects | dist_km
---------------+------------------
f | 32.1052124928391
Something with your map is incorrect.
After some debugging (in addition to some helpful troubleshooting steps) I realized that I was building my WKT blobs incorrectly using the ST_AsText function, specifically for my soils layer. As a result, my intersection was not being applied to the entire set of geometries contained within my soils layer.
Currently my soils layer contains a subset of SSURGO soil map units, some of which do not actually contain a geometry. In order to correctly build a text string representing all non-null geometries, I needed to explicitly join the geometries before converting the result to WKT:
SELECT ST_AsText(ST_Union(the_geom)) FROM schema.layer
did the trick.