Context
I have a massive MultiPolygon (~90'000 vertices, super complex, spreading over 60km², with lots of holes and parts) resulting of some Python computation. This MultiPolygon is coming as a WKT string. When I copy paste the query with that MultiPolygon into pgAdmin4, it makes the browser freeze for ~1minute. And if I dump that query into a file, it takes some time, and the file is ~20MB.
On the other hand, I have some other polygons (~86'000 features). They are already stored in a PostGIS database. They correspond to some specific regions of interest over a country. They are randomly spread out all over the country. They do not form a continuous surface. And they are quite small (to some square meters to max 1-2km²).
Needs
I need to get all features which intersect the large MultiPolygon. I am especially interested in the centroids of the polygons, after being clipped by the fat MultiPolygon (the ones outside being naturally no more needed).
Issue and SQL Code
Here's the query, it's embedded as a sub-sub SELECT
statement (I found it was faster to "split" the code like this). I identified the part shown here as the bottleneck. It's taking ~1h on a mainstream 2016 laptop.
SELECT fid, id, name, stuff, pseudo_d3d
FROM (
SELECT
*,
round(ST_3ddistance(
ST_SetSRID(st_makepoint(2600000.0,1200000.0,500.0), 2056),
ST_SetSRID(
ST_MakePoint(ST_X(centroid),ST_Y(centroid),ST_Z(intersect)/0.9989
), 2056)
)
) AS pseudo_d3d
FROM (
SELECT
ST_Intersection( -- compute the real intersection
landcoverpolygons.geom,
SELECT ST_Transform(
ST_GeomFromText('MULTIPOLYGON (( <90'000 WGS84 points coordinates> ))
), 2056)
) AS intersect
FROM landcoverpolygons
-- polygons_categories is holding some values list
INNER JOIN polygons_categories
ON (landcoverpolygons.landcovertype = polygons_categories.landcovertype)
WHERE ST_Intersects( -- compute the intersect to filter out prior to the intersection
ST_Transform(
landcoverpolygons.geom, 2056
),
SELECT ST_Transform(
ST_GeomFromText('MULTIPOLYGON (( <90'000 WGS84 points coordinates> ))
), 2056)
)
AND landcoverpolygons.landcovertype IN (
SELECT landcovertype FROM polygons_categories WHERE forest=True
)
AND landcoverpolygons.name_type <> 'foreign'
) AS foo LATERAL ST_SetSRID(ST_Centroid(foo.intersect), 2056) AS centroid
) AS bar;
What I've tried so far
A first idea was to test for the ST_Intersects
prior to the computation of ST_Intersection
. This improved things consequently (between ~50 and 400% depending on the input MultiPolygon).
In addition, as the following part repeats two times...:
SELECT ST_Transform(
ST_GeomFromText('MULTIPOLYGON (( <90'000 WGS84 points coordinates> ))
), 2056)
...I would wrote this as follow using a WITH
statement if that query wasn't enclosed in two other SELECT
statements:
WITH data (foo) AS (
SELECT ST_Transform(
ST_GeomFromText('MULTIPOLYGON (( <90'000 WGS84 points coordinates> ))
), 2056)
)
SELECT
ST_Intersection( -- compute the real intersection
landcoverpolygons.geom,
data.foo
) AS intersect
FROM landcoverpolygons
INNER JOIN polygons_categories
ON (landcoverpolygons.landcovertype = polygons_categories.landcovertype)
WHERE ST_Intersects( -- compute the intersect to filter out prior to the intersection
ST_Transform(
landcoverpolygons.geom, 2056
),
data.foo
)
AND landcoverpolygons.landcovertype IN (
SELECT landcovertype FROM polygons_categories WHERE forest=True
)
AND landcoverpolygons.name_type <> 'foreign';
This seems to go "a little" faster (~<5%).
Please, also notice that I already have indexes on landcoverpolygons.geom
, landcoverpolygons.landcovertype
and polygons_categories.landcovertype
. I also have indexes on the transformation shown here.
So, indexes on landcoverpolygons
are as follow:
CREATE INDEX landcoverpolygons_wgs84_gix
ON public.landcoverpolygons
USING gist (geom);
CREATE INDEX landcoverpolygons_2056_gix
ON public.landcoverpolygons
USING gist (ST_Transform(geom, 2056));
But it's not at all improving the computation time. It's even the opposite and made it rise between +50% and +100%! Surprisingly. Or not.... Maybe because there are features all over the massive MultiPolygon, so I would intuitively say that the index is of absolutely no help here (which seems confirmed here: https://stackoverflow.com/a/5203827/6630397).
Question
How could I lower the computation time of ST_Intersect()
which seems to be the most time consuming operation here (see edits below)?
EDIT:
- As suggested by geozelot, using
ST_SubDivide()
improved the computation time between 0-6% max.: 56 minutes instead of 1h in the best run I've executed. In addition, I faced this error when usingST_SubDivide()
in theWHERE
statement, so I was only able to use it when first using the MultiPolygon (in the first version of the code, without the CTE, which is the one I use when actually running the full query):
set-returning functions are not allowed in WHERE
In addition, the problem with ST_SubDivide() is that if a landcover feature is covered by X parts of the subdivided MultiPolygon, it will results in X different records for that exact same landcover feature, but I naturally only need a single one (Fig.1).
Fig.1 As a result, a single landcover features is splitted by the st_subdivded MultiPolygon.
- As suggested by ziggy, I also tried to replace
data.foo
in the ST_Intersect block (but not the ST_Intersection of course!) byST_Envelope(data.foo)
or evenST_ConvexHull(data.foo)
but this also doesn't improve the computation time.
From these observations, I think I can pretty much say that it's the ST_Intersections()
itself which costs the most time.
EDIT 2021-09-26: I also discovered the 'EXPLAIN ANALYZE' button on pgAdmin4 (but I don't understand well how to interpret the results, this is quite obscure to me).
I think this, from the 'Analysis' tab in the bottom pane showing the results may be relevant (this is another feature I'm actually working on, which only takes some minutes instead of a full hour):
Fig.2. Analysis tab in the results pane of pgAdmin4 after EXPLAIN ANALYZE was run with all options checked.
along with this table, from the 'Statistics' pane:
Fig.3 "Statistics per Node Type" from the 'Statistics tab in the pgAdmin4 results pane.
But this surprises me as the shown condition is not mentioning the intersection. And I have no idea what is this _1
suffix doing (second row of Fig.2).
Versioning:
PostgreSQL: "PostgreSQL 13.3 (Debian 13.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit"
PostGIS: "3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1"
ST_SubDivide
your MultiPolygon (e.g. in a CTE)