More on cutting polygons with polygons, in PostGIS
This is essentially the same as the other answers, however if you are dealing with larger tables and cutting lakes out of many forests you may want to try this variation.
It should take advantage of spatial indexes and only union together lakes that need to be used as a cutter.
I've done this as a select (with a CTE to provide sample data)
/* Sample data for forest */
WITH forest AS (
SELECT *
FROM (VALUES
(1,ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0))',0))
,(2,ST_GeomFromText('POLYGON((10 0,20 0,20 10,10 10,10 0))',0))
) Forest(id, geom)
),
/* Sample data for lake */
lake AS (
SELECT *
FROM (VALUES
(1,ST_GeomFromText('POLYGON((2 2,4 2,4 4,2 4,2 2))',0)) /* hole in first */
,(2,ST_GeomFromText('POLYGON((8 5,12 5,12 9,8 9,8 5))',0)) /* overlapping */
,(3,ST_GeomFromText('POLYGON((12 2,14 2,14 4,12 4,12 2))',0)) /* hole in second */
) lake(id, geom)
)
/* the actual query */
SELECT id,
ST_AsText(
ST_Difference(
f.geom,
/* Correlated subquery to fetch only the lakes intersected by the current forest */
(
SELECT ST_Union(l.geom)
FROM lake l
WHERE ST_Intersects(l.geom,f.geom)
)
)
)
FROM forest f
As mentioned by Greg Z in a comment, you first need to union your lakes so that they become one geometry (of type MULTIPOLYGON
). I would do with with a common table expression (CTE):
WITH union_lakes AS (
SELECT ST_Union(lake.geom) as geom
FROM lake
)
SELECT forest.*, ST_Difference(forest.geom, union_lakes.geom)
FROM forest
I can't test this query right now, but I believe that should work. If this leads to poor performance, it might be worthwhile taking the CTE out and storing the result as its own table (with its own spatial index).