How to expand a polygon to reach a nearby line - postgis

I would like to expand a polygon so that it fills an empty space between itself and a nearby (and touching in two points) line, as in the image posted here. As you can see the blue linestring makes an empty space on top of the pink polygon and I want to fill it with the polygon. Is there a postgis solution to this ? I havent' found any "easy" way.
Thanks !

The solution is similar to the one I presented here. Only in this case you need to buff up the linestring a bit.
WITH p AS (
SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(0 0,1 0,1 1, 0 1, 0 0)')) as geo
),
l AS (
SELECT ST_BUFFER(ST_GeomFromText('LINESTRING(0.0 0.0,0.5 0, 0.7 -1, 1 0)'),0.000000000000001) as geo
),
bigpoly AS(
SELECT ST_UNION(geo) as geom
FROM(
SELECT geo FROM p
UNION ALL
SELECT geo FROM l) as q
)
SELECT ST_BUFFER(ST_BuildArea(ST_InteriorRingN(geom,i)),0.000000000000001) as geo
FROM bigpoly
CROSS JOIN generate_series(1,(SELECT ST_NumInteriorRings(geom) FROM bigpoly)) as i
This will give you the missing piece, now you just need to ST_UNION it with the rest, you might also want to check if it's really a correct one if your original polygon contains holes.

Related

SQL Server Polygon format

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

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

linestring created from coordinates POSTGIS

Can someone help ?
Here is the part of my code (sql) which doesn't work :
SELECT ST_LENGTH(geom) into distance FROM
SELECT ST_GeographyFromText('srid=4326;linestring(lon_bus lat_bus, lon_stop lat_stop)') AS geom)
AS dis;
lon_bus, lat_bus, lon_stop and lat_stop are coordinates I get from my database. When I try this, I have an error of parsing. But when I replace these variable by numeric, it works. Can someone help me on it? I would like to keep these variables in my code.
It doesn't work because the WKT with variables is invalid. Remember, WKT is just regular text, so don't confuse WKT with SQL.
You can make a LineString from two point geometries, then cast it to ::geography.
SELECT ST_MakeLine(ST_MakePoint(lon_bus, lat_bus),
ST_MakePoint(lon_stop, lat_stop))::geography AS geog
FROM (
SELECT 1 AS lon_bus, 2 AS lat_bus, 3 AS lon_stop, 4 AS lat_stop
) AS data;
To get the geodesic length, use ST_Length on the geography.
Based on the usage, the question isn't about how to make a linestring, but how to calculated the distance between two geographic positions. There are several ways to do this:
SELECT
ST_Distance(bus, stop) AS cartesian_distance,
ST_Distance_Sphere(bus, stop) AS sphere_distance,
ST_Distance(bus::geography, stop::geography) AS geography_distance,
ST_Length(ST_MakeLine(bus, stop)::geography) AS geography_length
FROM (
SELECT ST_MakePoint(lon_bus, lat_bus) AS bus, ST_MakePoint(lon_stop, lat_stop) AS stop
FROM (SELECT 1 AS lon_bus, 2 AS lat_bus, 3 AS lon_stop, 4 AS lat_stop) AS data
) AS data;
-[ RECORD 1 ]------+-----------------
cartesian_distance | 2.82842712474619
sphere_distance | 314283.687770102
geography_distance | 313588.397192902
geography_length | 313588.397192902
The last two get the same result. If you don't need the linestring (e.g. to draw on a map), then the simplest method is used for geography_distance.

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.

Selecting Nodes Separating Line Segments

I want to select nodes separating line segments in a layer. I want to select nodes only where they are intersected by two lines, NOT when they meet with more than two line (e.g. a T intersection or four way intersection, etc.).
Here's the best picture I can give (I dont have the reputation to post pictures). The --- line on the left is the first segment and the --x--x--x line on the right the second. The O is the node in the middle I want to select.
--------------------------------------0--x---x--x---x---x---x--x--x--x--x--x--x--x
I do NOT want to select nodes where more than two lines touch the node.
So far I have tried this query
CREATE TABLE contacts_st_touching_faults as
SELECT ST_Intersection(a.the_geom, b.the_geom), Count(Distinct a.gid) = 2
FROM final_layer as a, final_layer as b
WHERE ST_Touches(a.the_geom, b.the_geom)
AND a.gid != b.gid
GROUP BY ST_Intersection(a.the_geom, b.the_geom)
When I run this query it gives me intersections with more than two lines intersecting (T intersections and 4 way intersections).
I have also tried subing ST_intersects in and that didn't seem to work as well as ST_touches, but if you know how to make them work or any other method, it would be much appreciated!
Thanks for the help!
This should work:
WITH contacts AS(
SELECT a.gid AS gid1,b.gid AS gid2, ST_Intersection(a.the_geom, b.the_geom) AS intersection
FROM final_layer as a, final_layer as b
WHERE ST_Touches(a.the_geom, b.the_geom)
AND a.gid<b.gid
)
SELECT *
FROM contacts c1
LEFT JOIN contacts c2
ON ((c1.gid1=c2.gid1 AND c1.gid2<>c2.gid2) OR (c1.gid1=c2.gid2 AND c1.gid1<>c1.gid2))
AND c1.intersection=c2.intersection
WHERE c2.gid1 IS NULL;
It will perform better if ST_Intersection is moved to the final query but I wanted to make it simple.
This will list nodes with tow lines intersecting.
SELECT array_agg(gid) AS gids, count(gid) AS count, geom
FROM
-- lists all vertices (points) from lines
(SELECT gid, (ST_DumpPoints(geom)).geom AS geom
FROM lines_layer) AS p
GROUP BY p.geom
HAVING count(gid) = 2
ORDER BY count(gid);
For all nodes, replace '= 2' with '> 1'

Resources