Orthogonal Distance between polygons in PostGIS - postgresql

I have a point on line with two polygons on both sides. The scenario is shown in the following:
Now, I would like to compute the perpendicular distance between two polygons (for example, yellow line) using a PostGIS query. I was wondering if someone could suggest me how to do that?
EDIT1:
Above given scenario was an example. There can be complications in scenarios having streets, points and polygons. For example, the side parallel to street may not always be there.
Scenario_2:
Scenario_3:
EDIT2
Scenario_4
I want to calculate the perpendicular distance only where there is a point on line. I understand there can be exceptions in this, as point by #JGH.

Assuming your data is projected and that the distance between the points and the two nearest polygon is the one you are looking for, you can compute the distance from each point to the two polygons and make the sum.
1) compute the distance. Restrict the search to a reasonable distance, maybe twice the expected largest distance. Make sure the geometries are indexed!!
SELECT pt.gid point_gid, plg.gid polygon_gid, ST_Distance(pt.geom, plg.geom) distance
FROM pointlayer pt, polygonlayer plg
WHERE ST_Distance(pt.geom, plg.geom) < 50
ORDER BY pt.gid, ST_Distance(pt.geom, plg.geom);
2) For each point, get the two closest polygons using the partition function
SELECT
point_gid, polygon_gid, distance
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY point_gid ORDER BY distance asc) AS rank,
t.*
FROM
[distanceTable] t) top_n
WHERE
top_n.rank <= 2;
3) agregate the result and keep track of which polygons were used
select point_gid,
sum(distance) streetwidth,
string_agg(polygon_gid || ' - ' || distance,';') polyid_dist_info
from [top_2_dist dst]
group by dst.point_gid;
All together:
SELECT
point_gid,
sum(distance) streetwidth,
string_agg(polygon_gid || ' - ' || distance,';') polyid_dist_info
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY point_gid ORDER BY distance asc) AS rank,
t.*
FROM
( SELECT pt.gid point_gid,
plg.gid polygon_gid,
ST_Distance(pt.geom, plg.geom) distance
FROM pointlayer pt, polygonlayer plg
WHERE ST_Distance(pt.geom, plg.geom) < 50
) t
) top_n
WHERE
top_n.rank <= 2
GROUP BY point_gid;

Related

Summing road lengths by type and gridcell

I am pretty new to PostGIS, PostgreSQL, and SQL, so please be patient.
I have a table 'highways' that looks like this:
Then I would like to sum the road lengths by type and grid cell to produce a table like this:
I have been able to sum the road lengths in one cell (or bounding box) with:
SELECT geom
round(SUM(
ST_Length(
ST_Intersection(geom, (SELECT geom FROM boundaries WHERE area_id=-1377803))::geography
))) AS "length (meters)", type
FROM highways
WHERE ST_Intersects(geom, (SELECT geom FROM boundaries WHERE area_id=-1377803))
GROUP BY type
ORDER BY "length (meters)" DESC
LIMIT 10;
And to create the grid with:
WITH grid AS (
SELECT (ST_SquareGrid(1, ST_Transform(geom,4326))).*
FROM boundaries
)
SELECT ST_AsText(geom)
FROM grid LIMIT 10;
I found of example for counting points here, and I could replicate it for counting the number of roads by cell like this:
SELECT COUNT(*), grid.geom
FROM
highways AS hwy
INNER JOIN
ST_SquareGrid(
1,
ST_SetSRID(ST_EstimatedExtent('highways', 'geom'), 4326)
) AS grid
ON ST_Intersects(hwy.geom, grid.geom)
GROUP BY grid.geom, hwy.type LIMIT 10;
But I couldn't extrapolate it for what I intend to do. Any help with this is highly appreciated.

how to count how many buffers intersects in PostGIS

How can I count how many buffers intersects and select only those which have between 2 AND 6 intersections with other buffers? I can create a buffer with following query, but I don't have an idea on how to count intersections
SELECT ST_Buffer(geom::geography, 400)
FROM mytable;
I appreciate any help. Thanks.
It is wrong to use a buffer in such case, as the buffer is only an approximation. Instead, use the index compatible st_dwithin() function.
The idea is to select all points (polygons or else) that are within twice the distance, to group the result and keep the ones having at least 6 nearby features.
The example below use 2 tables, but you can use the same table twice.
SELECT myTable.ID, count(*), array_agg(myOtherTable.ID) as nearby_ids
FROM mytable
JOIN myOtherTable ON st_Dwithin(mytable.geom::geography, myOtherTable.geom::geography, 800)
GROUP BY myTable.ID
HAVING count(*) >= 6;
To use the same table twice, you can alias them:
SELECT a.ID, count(*), array_agg(b.ID) as nearby_ids
FROM mytable a
JOIN mytable b ON st_Dwithin(a.geom::geography, b.geom::geography, 800)
GROUP BY a.ID
HAVING count(*) >= 6;

How to handle 800k points as MVT

I have a table with about 800k points with information that includes bathimetry stored on PostGIS and served using ST_AsMVTGeom this is connected to a Web App develop using Leaflet but a certain levels (0-5) a single Vector-tile it's contaning all the data which makes the Query very slow (it takes minutes). I wonder if there exists (whitin PostGIS) a way to simplify the rows output depending on zoom level.
I'm using the following Query to create the tiles:
SELECT ST_AsMVT(q.*, 'bathymetry_layer', 4096, 'geom')
FROM (
SELECT
c.gid AS id,
c.x,
c.y,
c.z,
c.name,
c.inst,
ST_AsMVTGeom(
geom,
ST_MakeEnvelope(-86.8798828125, 20.632784250388028, -86.8359375, 20.673905264672843, 4326),
4096,
256,
false
) geom
FROM bathymetry_table WHERE geom && ST_MakeEnvelope(-86.8798828125, 20.632784250388028, -86.8359375, 20.673905264672843, 4326) AND ST_Intersects(geom, ST_MakeEnvelope(-86.8798828125, 20.632784250388028, -86.8359375, 20.673905264672843, 4326))
) q
Result of EXPLAIN ANALYZE:
Index Scan using geodat_batimetria_catoche_xcalak_geom_idx on geodat_batimetria_catoche_xcalak c (cost=0.29..8.56 rows=1 width=101) (actual time=0.117..11539.038 rows=434271 loops=1)
Index Cond: ((geom && '0103000020E610000001000000050000002B8716D9CECB55C0516B9A779CA234402B8716D9CECB55C0D34D621058493540492EFF21FD9E55C0D34D621058493540492EFF21FD9E55C0516B9A779CA234402B8716D9CECB55C0516B9A779CA23440'::geometry) AND (geom && '0103000020E610000001000000050000002B8716D9CECB55C0516B9A779CA234402B8716D9CECB55C0D34D621058493540492EFF21FD9E55C0D34D621058493540492EFF21FD9E55C0516B9A779CA234402B8716D9CECB55C0516B9A779CA23440'::geometry))
Filter: _st_intersects(geom, '0103000020E610000001000000050000002B8716D9CECB55C0516B9A779CA234402B8716D9CECB55C0D34D621058493540492EFF21FD9E55C0D34D621058493540492EFF21FD9E55C0516B9A779CA234402B8716D9CECB55C0516B9A779CA23440'::geometry)
Rows Removed by Filter: 5
Planning time: 7.222 ms
Execution time: 11579.044 ms
This function would generate vector tiles for all zooms, clustering points together for low zooms (0-10), and keeping them as is for zooms 11+.
CREATE OR REPLACE FUNCTION get_tile(z integer, x integer, y integer) RETURNS bytea AS
$$
-- MVTs can be combined by concatenating them together.
SELECT STRING_AGG(mvt, '') AS mvt
FROM (
-- Each `mvt` must be non-NULL, otherwise UNION ALL will skip them all
-- First generate the tile for the zoomed-out layer, clustering points together
SELECT COALESCE(ST_AsMVT(tile, 'bathymetry_layer', 4096, 'geom'), '') AS mvt
FROM (
-- Create a cluster of points for each tile
SELECT ST_AsMVTGeom(ST_Transform(center, 3857),
ST_TileEnvelope(z, x, y),
extent => 4096)
AS geom
-- need to add all the other columns here,
-- but they either have to be aggregated for each cluster,
-- or you need to pick one point to represent the whole cluster
FROM (
-- Cluster points into groups using DBSCAN algorithm
SELECT ST_Centroid(ST_Collect(geom)) AS center
FROM (SELECT *,
ST_ClusterDBSCAN(ST_TRANSFORM(geom, 3857),
-- Decide how many clusters you want per tile.
-- at zoom 0, earth circumference equals 1 tile. At each subsequent zoom level,
-- there are twice as many tiles for the same circumference.
-- Additionally, break each tile into 256 clusters
(40075016.6855785 / (256 * 2 ^ z)),
1) OVER () AS cluster_id
FROM bathymetry_table
-- This assumes your data should be clustered before zoom 11, and shouldn't after.
-- Keep this value in sync with the one below
WHERE z < 11
-- This assumes your data is in 4326. The envelope is always generated in 3857.
AND geom && ST_Transform(ST_TileEnvelope(z, x, y), 4326))
AS cluster
group by cluster_id) AS clusters) as tile
UNION ALL
SELECT COALESCE(ST_AsMVT(tile, 'bathymetry_layer', 4096, 'geom', 'id'), '') AS mvt
FROM (
-- Once zoomed in, there is no longer a need to cluster points
-- Note that the list of columns is different from the one above - you many want to adjust that.
SELECT ST_AsMVTGeom(ST_Transform(coord, 3857),
ST_TileEnvelope(z, x, y),
extent => 4096)
AS geom
, c.gid AS id
, c.x
, c.y
, c.z
, c.name
, c.inst
FROM bathymetry_table c
WHERE z >= 11
AND c.geom && ST_Transform(ST_TileEnvelope(z, x, y), 4326)) as tile) AS mvt
;
$$ LANGUAGE SQL IMMUTABLE
STRICT
PARALLEL SAFE;

Select Intersect Polygon From Single Feature Layer

I have hundreds of polygon (circles) where some of the polygon intersected with each others. This polygon is come from single feature layer. What I am trying to do is to delete the intersected circles.
It is similar to this question: link, but those were using two different layer. In my case the intersection is from single feature layers.
If I understood your question right, you just need to either create a CTE or simple subquery.
This might give you a good idea of how to solve your issue:
CREATE TABLE t (id INTEGER, geom GEOMETRY);
INSERT INTO t VALUES
(1,'POLYGON((-4.54 54.30,-4.46 54.30,-4.46 54.29,-4.54 54.29,-4.54 54.30))'),
(2,'POLYGON((-4.66 54.16,-4.56 54.16,-4.56 54.14,-4.66 54.14,-4.66 54.16))'),
(3,'POLYGON((-4.60 54.19,-4.57 54.19,-4.57 54.15,-4.60 54.15,-4.60 54.19))'),
(4,'POLYGON((-4.40 54.40,-4.36 54.40,-4.36 54.38,-4.40 54.38,-4.40 54.40))');
This data set contains 4 polygons in total and two of them overlap, as seen in the following picture:
Applying a CTE with a subquery might give you what you want, which is the non-overlapping polygons from the same table:
SELECT id, ST_AsText(geom) FROM t
WHERE id NOT IN (
WITH j AS (SELECT * FROM t)
SELECT j.id
FROM j
JOIN t ON t.id <> j.id
WHERE ST_Intersects(j.geom,t.geom)
);
id | st_astext
----+---------------------------------------------------------------------
1 | POLYGON((-4.54 54.3,-4.46 54.3,-4.46 54.29,-4.54 54.29,-4.54 54.3))
4 | POLYGON((-4.4 54.4,-4.36 54.4,-4.36 54.38,-4.4 54.38,-4.4 54.4))
(2 rows)
You can write quite clear delete statement using EXISTS clause. You literally want to delete the rows, for which there exists other rows which geometry intersects:
DELETE
FROM myTable t1
WHERE EXISTS (SELECT 1 FROM myTable t2 WHERE t2.id <> t1.id AND ST_Intersects(t1.geom, t2.geom))

postgis: ST_Distance between two geography. Syntax error

i am trying to running this on my table
select ST_Distance (
select location from utenti where user_id=464,
select location from utenti where user_id=474604
);
having a location column of this type location geography(POINT, 4326)
i am getting a syntax error and i'm not understanding why.
how can i achieve my goal?
for example if i select that column in two queries for each users i get a data like this
"0101000020E61000001435F98F528125402AE5B512BAA34540"
and running:
select ST_Distance(%s, %s);
it works but the distance doesn't seem to be true. hm
As pointed out in the comments above you can rewrite the query as:
SELECT ST_Distance(a.geom, b.geom) FROM utenti a, utenti b WHERE a.user_id=464 AND b.user_id=474604;
BUT this will give you the distance in degrees (as that is what your points are stored as). So you will want to change your function to be:
SELECT ST_Distance_Sphere(a.geom, b.geom) FROM utenti a, utenti b WHERE a.user_id=464 AND b.user_id=474604;
ST_Distance_sphere will take some of the curvature of the Earth in to account and will return a distance in metres. If you require absolute accuracy and are not worried about speed you can account for all of the Earth's curvature by using st_distance_spheroid.
İf you polygon to point distance
select st_distance(
st_geomfromtext('POINT (0 0)'),
st_geomfromtext('POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))')
)
same base spatial geom column for
select st_distance(
st_geomfromtext(c.geom),
st_geomfromtext(b.geom)
) from city c , build b where b.c_ID=c.ID