Steps by example¶
This page runs every step type on the Pondsworth sample data and shows what comes
out. Each example is a pipeline in pipelines/pondsworth.yaml (from duck-soup tutorial) with the same name as its
section, using the shared source list and
working_crs: EPSG:32631. The YAML below shows only the parts that differ.
How to read the diagrams:
base features (the rows flowing through) the other source the step uses a feature with no match input shown for comparison
The tables are the actual output, with NULL for empty values. In the editor, every step here is a card in the add step gallery; see Steps in the editor.
Joins: add columns from another source¶
spatial_join¶
Copies columns from the feature that a base feature touches, contains or lies within. Use it to answer "which district is each place in?".
base: places
steps:
- type: spatial_join
source: districts
predicate: intersects
fields: {district: district_name}
After: each place now carries the name of the district it's in. The Old Lighthouse, outside every district, gets none.
name |
district |
|---|---|
| Mallard Museum | Old Puddleton |
| Puddle Lane Café | Old Puddleton |
| Quackington Library | Old Puddleton |
| Soggy Crust Café | Mallard Quay |
| Duckling Swim School | Mallard Quay |
| Breadcrumb Hill | Featherhill |
| Featherhill Library | Featherhill |
| Museum of Rubber Ducks | Reedmoor |
| Reedmoor Pond Café | Reedmoor |
| Old Duck House | Featherhill |
| Old Lighthouse | NULL |
fieldsmaps new column name → column in the joined source: here, the district'sdistrict_namearrives asdistrict.- Every base feature is kept. The Old Lighthouse is outside every district, so its
districtis NULL (the red ring). Add afilterafterwards if you only want matches. predicateis read from the base feature's side:intersects(touches or overlaps),within(base feature inside the source feature),contains(base feature contains the source feature). For points in polygons,intersectsandwithingive the same result.
When a feature matches several: on_multiple¶
Central Puddle Park overlaps all four districts. By default (match: first) a base feature
still gets exactly one match, and on_multiple decides which:
base: parks
steps:
- type: spatial_join
source: districts
fields: {district_first: district_name}
- type: spatial_join
source: districts
on_multiple: largest_overlap
fields: {district_largest: district_name}
After: the district each option picks for Central Puddle Park. first takes Old Puddleton (dashed), largest_overlap takes Mallard Quay (filled).
name |
district_first |
district_largest |
|---|---|---|
| Central Puddle Park | Old Puddleton | Mallard Quay |
| Willow Wallow | Featherhill | Featherhill |
| Kingfisher Meadow | Mallard Quay | Mallard Quay |
| Bulrush Fields | Reedmoor | Reedmoor |
on_multiple: first(the default) takes the matching district that comes first in the source file: Old Puddleton. It's deterministic, but not meaningful.on_multiple: largest_overlaptakes the district with the biggest shared area: most of Central Puddle Park is in Mallard Quay.- In the editor, this is the spatial join's when several match field.
Keeping every match: match: all¶
base: parks
steps:
- type: spatial_join
source: districts
match: all
fields: {district: district_name}
name |
district |
|---|---|
| Bulrush Fields | Reedmoor |
| Central Puddle Park | Featherhill |
| Central Puddle Park | Mallard Quay |
| Central Puddle Park | Old Puddleton |
| Central Puddle Park | Reedmoor |
| Kingfisher Meadow | Mallard Quay |
| Willow Wallow | Featherhill |
With match: all, a base feature is repeated once per match, so Central Puddle Park appears
four times. The geometry isn't cut: each copy is the whole park. To split it along the district
boundaries, use intersect_overlay instead.
attribute_join¶
Looks up a value in another table by matching columns, like a spreadsheet VLOOKUP. No geometry is involved, so the other source can be a plain CSV.
base: places
steps:
- type: attribute_join
source: categories
left: category
right: code
fields: {category_label: label}
name |
category |
category_label |
|---|---|---|
| Mallard Museum | MUS | Museum |
| Puddle Lane Café | CAF | Café |
| Quackington Library | LIB | Library |
| Soggy Crust Café | CAF | Café |
| Duckling Swim School | SCH | School |
| Breadcrumb Hill | PRK | Park |
| Featherhill Library | LIB | Library |
| Museum of Rubber Ducks | MUS | Museum |
| Reedmoor Pond Café | CAF | Café |
| Old Duck House | '' | NULL |
| Old Lighthouse | LND | NULL |
- A row matches when
left(evaluated on the base row) equalsright(a column in the lookup source).leftis a SQL expression, soupper(category)or a literal like'MUS'also work. - Rows without a match keep NULL: the Old Duck House's category is empty, and
LNDisn't in the CSV. - Each row picks up at most one match. If the lookup column has duplicates, which one is used isn't defined, so make it unique.
- Matching is exact, including case. For a single translated column, a file codelist is an alternative that can ignore case.
nearest_neighbor¶
Copies columns from the closest feature in another source, whether or not they touch.
base: places
steps:
- type: nearest_neighbor
source: stops
max_distance: 300
distance_field: stop_distance_m
fields: {nearest_stop: stop_name}
After: each place is linked to its nearest stop. The Old Lighthouse has no stop within 300 m.
name |
nearest_stop |
stop_distance_m |
|---|---|---|
| Mallard Museum | Crumb Market | 108.2 |
| Puddle Lane Café | Nest Gate | 143.2 |
| Quackington Library | Nest Gate | 153 |
| Soggy Crust Café | Webfoot Lane | 108.2 |
| Duckling Swim School | Webfoot Lane | 126.5 |
| Breadcrumb Hill | Feather Road | 156.2 |
| Featherhill Library | Feather Road | 197 |
| Museum of Rubber Ducks | Reed Loop | 122.1 |
| Reedmoor Pond Café | Paddle Boat Pier | 164 |
| Old Duck House | Reed Loop | 200 |
| Old Lighthouse | NULL | NULL |
distance_fieldadds a column with the distance, in working-CRS units (metres here): how far each duck has to waddle to its stop.max_distancelimits the search: the Old Lighthouse's nearest stop is over 300 m away, so it gets NULL. Setting a radius also makes the step much faster on large data, because without it every base feature is compared with every source feature.- Distances need a projected working CRS; duck soup refuses to measure in degrees.
intersect_overlay¶
Cuts base features along the boundaries of another source, producing one row per overlapping pair with the shared area as its geometry.
After: Central Puddle Park is cut into four pieces along the district borders. The other parks each lie in one district and stay whole.
name |
district |
area_m2 |
|---|---|---|
| Bulrush Fields | Reedmoor | 36000 |
| Central Puddle Park | Featherhill | 5600 |
| Central Puddle Park | Mallard Quay | 16800 |
| Central Puddle Park | Old Puddleton | 9600 |
| Central Puddle Park | Reedmoor | 9800 |
| Kingfisher Meadow | Mallard Quay | 34000 |
| Willow Wallow | Featherhill | 22400 |
- Central Puddle Park becomes four pieces, one per district, each carrying that district's name. Use this when you need "how much park is in each district?".
- Base features that overlap nothing are dropped, and pairs that only touch along an edge are ignored.
- The row count can grow quickly when many features overlap.
line_overlay¶
Cuts lines where other lines lie on top of them, and copies those lines' attributes onto
the shared stretches. Everything else is kept, so the whole network comes out again and can go
into the next line_overlay.
Here the footpath network collects attributes from two datasets: the lamp register
(lit_paths, which has gaps) and the boardwalk survey (boardwalks, just a few stretches).
base: paths
steps:
- type: line_overlay
source: lit_paths
fields: {lamps: lamps}
tolerance: 0.1
- type: line_overlay
source: boardwalks
fields: {material: material}
tolerance: 0.1
footpaths
lit (lit_paths)
boardwalk (boardwalks)
Each gap in After is where one row ends and the next begins: 10 pieces in all. Mill Lane and Waddle Bridge only end on Riverside Walk, so it has no gap there.
name |
lamps |
material |
length_m |
|---|---|---|---|
| Mill Lane | gas | NULL | 266 |
| Reed Way | LED | NULL | 256 |
| Reed Way | NULL | NULL | 563.1 |
| Reed Way | NULL | recycled plastic | 204.8 |
| Riverside Walk | NULL | NULL | 153.6 |
| Riverside Walk | LED | NULL | 307.1 |
| Riverside Walk | LED | oak | 204.8 |
| Riverside Walk | LED | NULL | 102.5 |
| Riverside Walk | NULL | NULL | 255.9 |
| Waddle Bridge | NULL | oak | 140 |
- Riverside Walk is lit in the middle, and part of that lit stretch is also a boardwalk, so it comes out as five pieces: unlit, lit, lit and oak, lit, unlit. Each piece is its own row, and neighbouring pieces meet exactly.
- Every metre of the network is still there: the pieces add up to the original paths. The geometry is always the footpath's own; the other two datasets only decide where the cuts go and what each piece gets.
- Waddle Bridge ends on Riverside Walk and Reed Way, but they are not cut there: lines that only meet or cross don't count as lying on each other.
tolerance: 0.1is needed here. The lamp register and the boardwalk survey were digitised separately, and their lines lie a few centimetres off the footpaths. Without a tolerance nothing would match, and nothing would getlampsormaterial. With it, lines within 10 cm of each other count as the same line, and a lamp line stopping 3 cm before the end of Mill Lane still lights all of it.
Geometry: change the shapes¶
make_points¶
Makes point geometry from two coordinate columns, so a table without geometry can be joined, buffered and mapped like any other layer.
base: sightings
steps:
- type: make_points
x: lon
y: lat
crs: EPSG:4326
rejects: unplaced_sightings
- type: spatial_join
source: districts
fields: {district: district_name}
species |
seen |
district |
|---|---|---|
| Mallard | 2026-04-02 | Old Puddleton |
| Mute swan | 2026-04-03 | Reedmoor |
| Teal | 2026-04-05 | Mallard Quay |
| Mallard | 2026-04-09 | Featherhill |
- The sightings come from a CSV with
lonandlatas text.make_pointscasts them to numbers and builds the points inEPSG:4326, reprojected to the working CRS, so thespatial_joinafter it can find each sighting's district. - The Goosander came without coordinates. It gets no point, and with
rejectsit goes to theunplaced_sightingslayer instead of on down the chain. - A CSV whose coordinates are already numbers doesn't need this step: set
x_fieldandy_fieldon the source.make_pointsis for coordinates that turn up partway through a pipeline, say from anhttp_lookupgeocoder.
buffer¶
Grows each geometry by a distance (or shrinks polygons, with a negative distance).
After: each stop is a 150 m circle.
- The distance is in working-CRS units: 150 m here, roughly how far a duck will waddle for a bus. Like other distance operations, it needs a projected working CRS.
- Points become circles and lines become corridors. Attributes are unchanged.
- To buffer a copy of the data and keep the originals, use a snapshot branch or a
derived source with
buffer:.
centroid¶
Replaces each geometry with its centre point.
After: each park is replaced by its centre point. The dashed outlines are the parks they came from.
Useful for labelling, or for joining polygons to other polygons by "where is its middle?" instead of by overlap. For a curved or C-shaped polygon, the centroid can fall outside the shape itself.
clip¶
Keeps only the part of each feature that lies inside a mask.
derived_sources:
- {id: old_puddleton, from: districts, where: "district_name = 'Old Puddleton'"}
base: parks
steps:
- {type: clip, source: old_puddleton}
After: only the corner of Central Puddle Park inside Old Puddleton (teal, dashed) is left. The dashed grey parks are dropped.
name |
area_m2 |
|---|---|
| Central Puddle Park | 9600 |
- Here the mask is a derived source: the
districtssource filtered to Old Puddleton, created without adding a step to the main chain. - Only Central Puddle Park reaches into Old Puddleton, so only that corner of it survives. Features outside the mask are dropped entirely.
- If a feature overlaps several mask features, it's clipped by the first one it matches. To
clip to an area made of several polygons, use a mask that is a single feature: filter it down
to one (as here), or
dissolveit in a separate pipeline and use that output layer as the mask source.
erase¶
Cuts away the part of each feature that lies inside another source. It's the opposite of
clip.
After: the river's strip is cut out of Central Puddle Park, leaving it in two parts. The other parks don't touch the river.
name |
area_m2 |
|---|---|
| Central Puddle Park | 28600 |
| Willow Wallow | 22400 |
| Kingfisher Meadow | 34000 |
| Bulrush Fields | 36000 |
- The River Waddle runs through Central Puddle Park, so the park loses that strip (its area drops from about 41,800 m² to 28,600 m², the missing third was river all along) and may become a multi-part polygon.
- Features that don't touch the river pass through unchanged. A feature erased completely is dropped.
- Unlike
clip,erasesubtracts all the matching features at once.
dissolve¶
Merges features that share the same values, combining their geometries into one.
base: districts
steps:
- type: dissolve
by:
- ward
aggregates:
- {from: population, how: sum}
- {to: districts, from: district_name, how: all}
After: the four districts are merged into their two wards.
ward |
population |
districts |
area_ha |
|---|---|---|---|
| Downstream | 10500 | Featherhill, Reedmoor | 40 |
| Upstream | 20500 | Mallard Quay, Old Puddleton | 40 |
- The four districts become the two wards, Upstream and Downstream. Leave
byout to merge everything into a single feature (a town boundary, for example). - Only the
bykeys and theaggregatesare kept, since the other columns have no single value per ward. An aggregate says how to combine them:how: sumadds up the districts' populations, andhow: alllists their names.most_common,average,min,max,countand others work the same way, and so does a SQL aggregate inexpr. See Keeping other columns. - A key can also be a SQL expression with a name, such as
{to: initial, expr: "left(district_name, 1)"}, to group by a computed value.
combine_lines¶
Joins lines that meet end to end into longer lines, one per connected chain with the same values.
base: paths
steps:
- type: line_overlay
source: lit_paths
fields: {lamps: lamps}
tolerance: 0.1
- type: line_overlay
source: boardwalks
fields: {material: material}
tolerance: 0.1
- type: combine_lines
by:
- name
- lamps
aggregates:
- {to: pieces, how: count}
- {to: materials, from: material, how: all}
Before is what the step gets: the 10 pieces of the two line_overlay steps, a gap between each. After, pieces of the same path with the same lighting that meet end to end are one line again: 7 lines.
name |
lamps |
pieces |
materials |
length_m |
|---|---|---|---|---|
| Mill Lane | gas | 1 | NULL | 266 |
| Reed Way | LED | 1 | NULL | 256 |
| Reed Way | NULL | 2 | recycled plastic | 767.9 |
| Riverside Walk | NULL | 1 | NULL | 153.6 |
| Riverside Walk | LED | 3 | oak | 614.3 |
| Riverside Walk | NULL | 1 | NULL | 255.9 |
| Waddle Bridge | NULL | 1 | oak | 140 |
- The two
line_overlaysteps cut the paths into 10 pieces, by lighting and by boardwalk. Combining bynameandlampsrejoins the pieces only the boardwalks split, giving 7 lines. The lit stretch of Riverside Walk is one line again, made of 3 pieces. - Riverside Walk's two unlit stretches stay two features: they don't touch.
dissolvewould have merged them into one multi-part line. - The
aggregatesare computed per chain, sopiecescounts the pieces in each line, andmaterialslists the boardwalks it crosses. - Lines are joined only where one ends and the next begins, the way they run. Set
preserve_direction: falseto let a line be reversed to fit.
split_lines¶
Cuts lines where they cross or touch each other, so every junction becomes an end point: the noding a network needs before routing or counting segments.
Each gap in After is a cut: Riverside Walk where Mill Lane and Waddle Bridge meet it, and Reed Way where the bridge ends on it.
name |
length_m |
|---|---|
| Riverside Walk | 307.2 |
| Riverside Walk | 307.2 |
| Riverside Walk | 409.6 |
| Mill Lane | 266 |
| Reed Way | 614.3 |
| Reed Way | 409.6 |
| Waddle Bridge | 140 |
- Mill Lane ends on Riverside Walk, and Waddle Bridge starts on it, so Riverside Walk is cut in two places and comes out as 3 pieces. The lines that only end there aren't cut.
- Waddle Bridge was meant to end on Reed Way, but after the trip through longitude and
latitude its end misses by millimetres, as separately digitised lines usually do.
tolerance: 0.1cuts Reed Way there anyway; without it, Reed Way stays in one piece. - Each piece keeps its line's attributes, and the pieces are the line's own geometry, so
neighbouring pieces share their end points exactly. Give
sourceto cut at another layer's lines or polygon edges instead (paths split at district borders, say).
clean_coverage¶
Makes polygons that should fit edge to edge actually fit. The reed beds were surveyed zone by zone: Sedge Shallows reaches 1.2 m into Cattail Corner, and Cattail Corner stops up to a metre short of Reedy Reach, leaving a thin gap between them.
name |
area_m2 |
overlap_m2 |
|---|---|---|
| Sedge Shallows | 19263 | 0 |
| Cattail Corner | 18842 | 0 |
| Reedy Reach | 19495 | 0 |
- The overlap is gone (
overlap_m2is the area each zone shares with the others), and the gap, narrower thanmax_gap_width, is filled. The three zones now cover their 57 600 m² exactly once. - A gap is a hole enclosed by the polygons; an inlet open to the outside isn't filled.
snap_distancealso snaps edges that are nearly, but not exactly, on top of each other.
simplify¶
Removes vertices, keeping every edge within tolerance of the original.
base: reedbeds
steps:
- {type: clean_coverage, max_gap_width: 3}
- {type: simplify, tolerance: 6, shared_boundaries: true}
The dots are the vertices. After simplifying, each wiggly edge keeps a fraction of them, and the edge two zones share is still a single edge.
name |
vertices |
area_m2 |
|---|---|---|
| Sedge Shallows | 18 | 19261 |
| Cattail Corner | 31 | 18855 |
| Reedy Reach | 18 | 19484 |
- The reed beds' wiggly edges have a vertex every 5 metres. Simplified with a 6 m tolerance, each zone keeps a fraction of them.
shared_boundaries: truesimplifies the zones together, so an edge two of them share is simplified once and stays shared: the areas still add up to 57 600 m², with no gaps or slivers between the zones. Simplified one at a time, the two sides of a shared edge would come out differently.- Shared-boundary simplification needs polygons that don't overlap, which is why
clean_coverageruns first.keep_outer_boundary: truewould leave the outer edge of the reed beds as it is.
snap_to_grid¶
Rounds every vertex to a grid, here a coarse 50 m one so the jump is visible.
After: each place has moved to the nearest point of a 50 m grid. The grey dots are where they were.
name |
x |
y |
|---|---|---|
| Mallard Museum | 200 | 650 |
| Puddle Lane Café | 300 | 700 |
| Quackington Library | 400 | 600 |
| Soggy Crust Café | 650 | 700 |
| Duckling Swim School | 800 | 650 |
| Breadcrumb Hill | 150 | 200 |
| Featherhill Library | 350 | 100 |
| Museum of Rubber Ducks | 700 | 150 |
| Reedmoor Pond Café | 900 | 200 |
| Old Duck House | 450 | 250 |
| Old Lighthouse | 1250 | 500 |
- Each place moves to the nearest grid point; the grey dots are where they were. A grid of
0.01(centimetres) is the usual choice, to drop the spurious decimals a reprojection leaves behind. - The geometry stays valid. A polygon narrower than a grid cell collapses to an empty geometry, and its row is kept.
fill_holes¶
Removes the holes in polygons, or with max_area only the small ones.
Before: the parks (teal) are erased from the districts. After: the small hole in Featherhill is filled (dashed); the two larger ones stay. The square in the middle isn't a hole: Central Puddle Park took a corner off each of four districts.
district_name |
holes |
area_ha |
|---|---|---|
| Old Puddleton | 0 | 19.04 |
| Mallard Quay | 1 | 14.92 |
| Featherhill | 0 | 19.44 |
| Reedmoor | 1 | 15.42 |
- Erasing the parks punches a hole in three districts (Central Puddle Park, on the
corner, only takes a bite out of the edges). With
max_area: 30000, Willow Wallow's 22 400 m² hole in Featherhill is filled; Kingfisher Meadow's and Bulrush Fields' are larger and stay. - Without
max_area, every hole is filled.
eliminate_slivers¶
Merges small polygons into a neighbour. Overlaying the parks with the districts cuts Central Puddle Park, which straddles the town centre, into four pieces; the one in Featherhill (5 600 m², teal below) is too small to keep.
base: parks
steps:
- type: intersect_overlay
source: districts
fields: {district: district_name}
- {type: eliminate_slivers, max_area: 6000}
Before: the pieces the overlay cuts the parks into, the sliver in teal. After: it's part of the Old Puddleton piece above it.
name |
district |
area_m2 |
|---|---|---|
| Bulrush Fields | Reedmoor | 36000 |
| Central Puddle Park | Mallard Quay | 16800 |
| Central Puddle Park | Old Puddleton | 15200 |
| Central Puddle Park | Reedmoor | 9800 |
| Kingfisher Meadow | Mallard Quay | 34000 |
| Willow Wallow | Featherhill | 22400 |
- The sliver merges into the neighbour it shares the longest border with: the Old
Puddleton piece, which grows from 9 600 to 15 200 m². The sliver's row is gone; the
neighbour keeps its attributes.
merge_into: largestpicks the largest neighbour instead. wherepicks the polygons by a condition, alone or together withmax_area. A sliver with no neighbour to merge into stays as it is.
explode¶
Splits multipart features into a row per part.
kind |
part |
area_m2 |
|---|---|---|
| meadow | 1 | 36000 |
| meadow | 2 | 34000 |
| park | 1 | 22400 |
| park | 2 | 41800 |
- The
dissolvebykindmakes two multipolygons: the two meadows and the two parks, none of them touching.explodeturns each into its parts again, with the feature's attributes, andpart_fieldnumbers them. - A feature that is already a single part stays one row.
Rows: keep some, add more, or set a copy aside¶
filter¶
Drops the rows where a SQL condition is false.
base: places
steps:
- type: spatial_join
source: districts
fields: {district: district_name}
- {type: filter, where: "district IS NOT NULL AND bread_crumbs >= 20000"}
After: the places that pass the condition. The grey ones are dropped.
name |
district |
bread_crumbs |
|---|---|---|
| Mallard Museum | Old Puddleton | 52000 |
| Quackington Library | Old Puddleton | 75000 |
| Breadcrumb Hill | Featherhill | 30000 |
| Featherhill Library | Featherhill | 21000 |
- The condition is any DuckDB SQL expression that returns true/false, using the columns
available at that point in the chain, including ones pulled in by earlier steps (
districthere). - Rows where the condition is NULL are dropped too. Nobody has counted the crumbs at the
Duckling Swim School, so
bread_crumbs >= 20000is NULL rather than false, and the row goes. - To split rows into several outputs rather than drop them, keep them all and give each
output layer its own
filter.
remove_duplicates¶
Keeps the first row of each set of duplicates.
name |
category |
|---|---|
| Mallard Museum | MUS |
| Puddle Lane Café | CAF |
| Quackington Library | LIB |
| Duckling Swim School | SCH |
| Breadcrumb Hill | PRK |
| Old Duck House | '' |
| Old Lighthouse | LND |
- One place per category: the first in row order. The four others (two more cafés, a
second library and the Museum of Rubber Ducks) go to the
second_in_categorylayer. geometry: truecompares the shapes instead of, or as well as, the columns: two features with the same geometry are duplicates whatever vertex they start at.
merge¶
Appends the rows of another source to the chain.
Before: the two new places (teal) and the chain's own. After: all of them are rows of the chain.
name |
category |
opened |
bread_crumbs |
|---|---|---|---|
| Mallard Museum | MUS | 1887 | 52000 |
| Harbour Fish Market | MKT | NULL | NULL |
| Pop-up Duck Gallery | MUS | NULL | NULL |
- Columns are matched by name.
new_placeshas noopenedorbread_crumbs, so those are NULL for the two new rows. A column only the new source has is added, NULL for the existing rows. - Merged rows only go through the steps that come after the merge. Put the merge first if everything should be joined and filtered the same way.
snapshot¶
Names the chain's current state so later steps can join against it, or keep working on a copy of it (a branch) while the main chain carries on.
This example finds the places within 250 m of a crumb hotspot (a place handing out 50,000+ bread crumbs a year), where the ducks are bound to gather:
base: places
steps:
- {type: snapshot, id: crumb_hotspots}
- {type: filter, branch: crumb_hotspots, where: "bread_crumbs >= 50000"}
- {type: buffer, branch: crumb_hotspots, distance: 250}
- type: spatial_join
source: crumb_hotspots
predicate: within
match: all
fields: {near_hotspot: name}
- {type: filter, where: "near_hotspot IS NULL OR near_hotspot <> name"}
After: the two crumb hotspots' 250 m circles. A place inside another hotspot's circle gets near_hotspot; the red ones have no hotspot nearby.
name |
near_hotspot |
|---|---|
| Mallard Museum | Quackington Library |
| Puddle Lane Café | Mallard Museum |
| Puddle Lane Café | Quackington Library |
| Quackington Library | Mallard Museum |
| Soggy Crust Café | Quackington Library |
What happens, step by step:
snapshotcopies the places into a branch calledcrumb_hotspots. The main chain is unchanged.filterandbufferwithbranch: crumb_hotspotsreduce the copy to the two hotspots and turn them into 250 m circles. The main chain still holds all eleven places as points.- The
spatial_joinon the main chain usessource: crumb_hotspots, so each place is checked against the circles.match: allkeeps one row per circle a place falls in. - The last
filterdrops each hotspot's match with its own circle.
Output layers are always written from the main chain. A branch is only useful as the source
of a later step.
Attributes: set columns between steps¶
attributes¶
Adds, overwrites or removes columns partway through the chain, for a later step to use.
base: places
steps:
- type: attributes
columns:
- {to: opened, from: opened, cast: INTEGER}
- {to: era, expr: "CASE WHEN opened < 1950 THEN 'historic' ELSE 'modern' END"}
drop:
- inspected
- {type: filter, where: "era = 'historic'"}
name |
opened |
era |
|---|---|---|
| Old Lighthouse | 1820 | historic |
| Mallard Museum | 1887 | historic |
| Old Duck House | 1901 | historic |
columnstakes the same rules as the mapping.openedis text in the source, and the first column overwrites it with a number (cast: INTEGER).- The columns are set in order, so
eracan use theopenedvalue set just above it. - The
filterafter it can then useera, a column that doesn't exist in the source. Without the attributes step, the filter would have to repeat the wholeCASEexpression. - You only list what you change. Every column you don't mention (
name,category,bread_crumbs, …) is kept as it is and goes on to the next step.dropremoves the ones you don't want, hereinspected.