# Combine or cut shapes

Model / geo

A variant of [Work with geography](https://meridian.online/operations/work-with-geography).

Take the intersection, difference or union of geometries, or clip one layer by another.

Page: <https://meridian.online/operations/work-with-geography/combine-or-cut-shapes>

## Status

Runs as a SQL step.

arcform carries it as a `sql:` step today, with the `spatial` extension.

## The DuckDB step

take the intersection, difference and union of shapes, and clip one layer by another

```sql
INSTALL spatial; LOAD spatial; SET geometry_always_xy = true;

CREATE TEMP TABLE a AS SELECT ST_GeomFromText('POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))') AS geom;
CREATE TEMP TABLE b AS SELECT ST_GeomFromText('POLYGON ((2 2, 6 2, 6 6, 2 6, 2 2))') AS geom;
CREATE TEMP TABLE overlay AS
SELECT ST_Intersection(a.geom, b.geom) AS both_shapes, ST_Difference(a.geom, b.geom) AS only_a, ST_Union(a.geom, b.geom) AS either FROM a, b;
-- clip a layer of parcels by a window: keep the part of each parcel inside it, and drop the parcels that miss it
CREATE TEMP TABLE parcels AS SELECT * FROM (VALUES (1, ST_GeomFromText('POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))')), (2, ST_GeomFromText('POLYGON ((3 0, 6 0, 6 2, 3 2, 3 0))')), (3, ST_GeomFromText('POLYGON ((8 8, 9 8, 9 9, 8 9, 8 8))'))) t(id, geom);
CREATE TEMP TABLE clipped AS
SELECT p.id, ST_Intersection(p.geom, w.geom) AS geom FROM parcels p, (SELECT ST_MakeEnvelope(1, 0, 4, 5) AS geom) w WHERE ST_Intersects(p.geom, w.geom);
-- erase is the same layer cut the other way: what of each parcel lies outside the window
CREATE TEMP TABLE erased AS
SELECT p.id, ST_Difference(p.geom, w.geom) AS geom FROM parcels p, (SELECT ST_MakeEnvelope(1, 0, 4, 5) AS geom) w;
SELECT ST_Area(both_shapes) AS both_area, ST_Area(only_a) AS only_a_area, ST_Area(either) AS either_area FROM overlay;
SELECT id, ST_Area(geom) AS clipped_area FROM clipped ORDER BY id;
SELECT CASE WHEN ((SELECT ST_Area(both_shapes) = 4 AND ST_Equals(both_shapes, ST_GeomFromText('POLYGON ((2 2, 4 2, 4 4, 2 4, 2 2))')) AND ST_Area(only_a) = 12 AND ST_Area(either) = 28 AND ST_GeometryType(either) = 'POLYGON' FROM overlay)
  AND (SELECT count(*) = 2 AND list(id ORDER BY id) = [1, 2] AND list(ST_Area(geom) ORDER BY id) = [2.0, 2.0] FROM clipped)
  AND (SELECT list(ST_Area(geom) ORDER BY id) = [2.0, 4.0, 1.0] FROM erased)) THEN 'as expected' ELSE error('the squares should overlap in an area of 4, differ by 12 and join to 28, and the window should clip two parcels to an area of 2 each and drop the third') END AS verdict;
```

The statement builds its own data.

It needs the `spatial` extension.

## What other tools call it

Meridian's classification; an independent blind rating agreed on 95.7% of 324 rows (variant, version 3).

- Alteryx Designer: [`Spatial Process`](https://help.alteryx.com/current/en/designer/tools/spatial/spatial-process-tool.html) (read 2026-09-27)
- Duckle: [`Clip Geometry`](https://github.com/slothflowlabs/duckle/blob/f85f8960edfb6efbf4cb89d58a7b32e16df0e867/frontend/src/workflow-ui/palette-data.ts) (read 2026-09-25)
- Duckle: [`Erase Geometry`](https://github.com/slothflowlabs/duckle/blob/f85f8960edfb6efbf4cb89d58a7b32e16df0e867/frontend/src/workflow-ui/palette-data.ts) (read 2026-09-25)
- Ibis: [`GeoDifference`](https://github.com/ibis-project/ibis/blob/05d2b293344e86cb41c1a426071734f7e270335a/ibis/expr/operations/geospatial.py) (read 2026-09-27)
- Ibis: [`GeoIntersection`](https://github.com/ibis-project/ibis/blob/05d2b293344e86cb41c1a426071734f7e270335a/ibis/expr/operations/geospatial.py) (read 2026-09-27)
- Ibis: [`GeoUnaryUnion`](https://github.com/ibis-project/ibis/blob/05d2b293344e86cb41c1a426071734f7e270335a/ibis/expr/operations/geospatial.py) (read 2026-09-27)
- Ibis: [`GeoUnion`](https://github.com/ibis-project/ibis/blob/05d2b293344e86cb41c1a426071734f7e270335a/ibis/expr/operations/geospatial.py) (read 2026-09-27)
