Postgis - How to detect an instersection of the bigger area instersected - postgresql

I have two maps loaded into the database, one map has the geometry of the states, and the other one has the geometry of the urban areas.
I want to make and intersection to make a relation between the urban areas and the states to know which urban area belong to each state.
The problem is that some urban areas occupy two states, the urban area belongs to the state in which it has more area within the state geometry.
i could use the command ST_Intersects, but it would add it both states in which the urban are instersect with the states.
What sql command i have to use? i have read the documentation of
ST_CoveredBy and ST_Within but i'm not really sure if they work for what i need to do.

First create the instersection object between state and urban region and calculate area size, using the ST_intersect on the JOIN will use index to avoid overhead.
Then assign a row_number to each urban_id order by area size.
With rn = 1 mean only return the largest area for each urban_id.
.
WITH cte as (
SELECT S.state_id,
U.urban_id,
ST_Area(ST_Intersection( S.geom, U.geom )) a_geom
-- This create the intersect geom and calculate area
FROM states S
JOIN urban U
ON ST_Intersects( S.geom, U.geom ) -- This is a boolean function
),
area as (
SELECT state_id,
urban_id,
row_number() over (partition by urban_id order by a_geom desc) as rn
FROM cte
)
SELECT state_id,
urban_id
FROM area
WHERE rn = 1

You can use ST_Area on ST_Intersection to sort and LATERAL JOIN.
WITH states(id, geom) AS(
VALUES (1, ST_MakePolygon(ST_GeomFromText('LINESTRING(0 0, 1 0, 1 1, 0 1, 0 0)')))
,(2, ST_MakePolygon(ST_GeomFromText('LINESTRING(1 0, 2 0, 2 1, 1 1, 1 0)')))
),cities(id, geom) AS(
VALUES (1,ST_Buffer(ST_GeomFromText('POINT(0.5 0.5)'), 0.3))
,(2,ST_Buffer(ST_GeomFromText('POINT(1.5 0.5)'), 0.3))
,(3,ST_Buffer(ST_GeomFromText('POINT(1.1 0.5)'), 0.3))
)
SELECT c.id AS city, s.id AS state
FROM cities AS c
CROSS JOIN LATERAL (SELECT s.id, s.geom
FROM states AS s
WHERE ST_Intersects(s.geom, c.geom)
ORDER BY ST_AREA(ST_Intersection(s.geom,c.geom)) DESC
LIMIT 1) AS s

Related

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 select same column with different conditions

I have a table table1 with these columns:
region (varchar)
surface (float8)
decision (bool)
I want to select AVG(surface) for the cases where decision is True and where it is False and group the result by region.
I finally want 3 columns :
region
the average surface of surfaces where decision is true
the average surface of surfaces where decision is false
I tried :
SELECT
region,
(SELECT AVG(surface_m2) FROM table1 WHERE avis_final_bri),
(SELECT AVG(surface_m2) FROM table1 WHERE avis_final_bri)
FROM
table1
GROUP BY
region
but the query does not work.
I also tried to define another table the WITH statement but it did not work. I tried with the JOIN but it failed as well.
You can use the FILTER clause:
demo:db<>fiddle
SELECT
region,
AVG(surface) FILTER (WHERE decision = true),
AVG(surface) FILTER (WHERE decision = false)
FROM
table1
GROUP BY region
Alternatively to use more common SQL, you can use the CASE clause:
SELECT
region,
AVG(CASE WHEN decision = true THEN surface END),
AVG(CASE WHEN decision = false THEN surface END)
FROM
table1
GROUP BY region

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

How to get the MAX(SUM of values) to find the category with the biggest total? PostgreSQL

I have two tables. One is Transactions and the other is Tickets. In Tickets I have the Ticket_Number,the name of the Category(Theater,Cinema,Concert), the Price of the Ticket. In Transactions I also have the Ticket_Number. What i want to do is to Get a SUM of money for each Category, and then with that data I want to Select the Category with the most money.
I already managed to get the SUM for each category but I am stuck here
SELECT category, SUM (Tickets.Price) AS Price
FROM Tickets,Transactions
WHERE Tickets.ticket_num=Transactions.ticket_num
GROUP BY Category
ORDER BY Price DESC;
I know i can add LIMIT 1 but I know it's not correct because 2 or more values can be the same
Using ROW_NUMBER to generate a sequence based on the sum of the price. Then, restrict to only the matching aggregated row with the highest total price.
WITH cte AS (
SELECT category, SUM(t1.Price) AS Price,
ROW_NUMBER() OVER (ORDER BY SUM(t1.Price) DESC) rn
FROM Tickets t1
INNER JOIN Transactions t2
ON t1.ticket_num = t2.ticket_num
GROUP BY Category
)
SELECT category, Price
FROM cte
WHERE rn = 1
ORDER BY Price DESC;
Note that if you want to capture all categories tied for the highest price, should a tie occur, then replace ROW_NUMBER in the above CTE with RANK, keeping everything else the same.
What you are looking for is a window function DENSE_RANK() which will handle ties properly.
RANK() will also work for your case, but if you would like to extend it to get TOP N places with ties (where N > 1), dense rank is the way to go.
SELECT Category, Price
FROM (
SELECT
Category,
SUM(ti.Price) AS Price,
DENSE_RANK() OVER (ORDER BY SUM(ti.Price) DESC) AS rnk
FROM Tickets ti
INNER JOIN Transactions tr ON
ti.ticket_num = tr.ticket_num
GROUP BY Category
) t
WHERE rnk = 1
I've also replaced the old style and not recommended joining of tables as comma separated list in FROM clause to a proper INNER JOIN clause and assigned aliases to tables.
You can use rank() to rank the sums of the prices, more expensive first.
SELECT category,
price
FROM (SELECT category,
sum(tickets.price) price,
rank() OVER (ORDER BY sum(tickets.price) DESC) r
FROM tickets
INNER JOIN transactions
ON transactions.ticket_num = tickets.ticket_num
GROUP BY category) x
WHERE r = 1;
I also took the liberty to rewrite your join from the ancient comma style to a modern, clearer version.

postgis advanced (?) selection query

The problem: I need to select, for each building in my table that has say at least 2 pharmacies and 2 education centers within a radius of 1km, all POIs (pharmacies, comercial centres, medical centers, education centers, police stations, fire stations) which are within 1km of the respective building. table structure->
building (id serial, name varchar )
poi_category(id serial, cname varchar) --cname being the category name of course
poi(id serial, name varchar, c_id integer)-- c_id is the FK referencing poi_category(id)
all coordinate columns are of type geometry not geography (let's call them geom)
here's the way i thought it should be done but i'm not sure it's even correct let alone the optimal solution to this problem
SELECT r.id_b, r.id_p
FROM (
SELECT b.id AS id_b, p.id AS id_p, pc.id AS id_pc,pc.cname
FROM building AS b, poi AS p, poi_category AS pc
WHERE ST_DWithin(b.geom,p.geom, 1000) AND p.c_id=pc.id
) AS r,
(
SELECT * FROM r GROUP BY id_b
) AS r1
HAVING count (
SELECT *
FROM r, r1
WHERE r1.id_b=r.id_b AND r.id_pc='pharmacy'
)>1
AND
count (
SELECT *
FROM r, r1
WHERE r1.id_b=r.id_b AND r.id_pc='ed. centre'
)>1
Is this the way to go for what i need ? What solution would be better from a performance point of view? What about the most elegant solution?
I've also posted here :http://gis.stackexchange.com/questions/11445/postgis-advanced-selection-query
This is a solution I elaborated. It's the fastest one I could find but it's still slow. Given the nature of the task I doubt it can be made faster...
WITH
building AS (
SELECT way, osm_id
FROM osm_polygon
WHERE tags #> hstore('building','yes')
--ORDER BY 1
LIMIT 1000
),
pharmacy AS (
SELECT way
FROM osm_poi
WHERE tags #> hstore('amenity','pharmacy')
),
school AS (
SELECT way
FROM osm_poi
WHERE tags #> hstore('amenity','school')
)
SELECT ST_AsText(building.way) AS geom, building.osm_id AS label
FROM building
WHERE
(SELECT count(*) > 1
FROM pharmacy
WHERE ST_DWithin(building.way,pharmacy.way,1000))
AND
(SELECT count(*) > 1
FROM school
WHERE ST_DWithin(building.way,school.way,1000))
Yours. S.