Skip to content

rasterize: honour input order across geometry types (#2064)#2077

Merged
brendancol merged 2 commits into
mainfrom
issue-2064
May 18, 2026
Merged

rasterize: honour input order across geometry types (#2064)#2077
brendancol merged 2 commits into
mainfrom
issue-2064

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

merge='first' / merge='last' now decide a pixel's value by the global input position of the geometry, not by the internal polygon -> line -> point burn phase.

Before: [(Point(...), 9), (Polygon(...), 1)] with merge='last' returned 9 at the shared pixel because points were always burned after polygons. The user supplied the polygon as the last input, so they expected 1.

Now: input order wins, on all four backends (numpy, cupy, dask+numpy, dask+cupy).

Mechanics

  • _classify_geometries returns a per-type int64 global-index array alongside the existing props arrays. GeometryCollection unpacking inherits the parent's input position.
  • A new int64 order array (one per output raster / per tile) tracks which input index currently owns each pixel. Burn kernels consult should_write(is_first, new_idx, cur_idx) before any write.
  • Built-in merges dispatch to (merge_fn, should_write_fn) pairs. 'first' uses should_write_first (smallest index wins), 'last' uses should_write_last (largest index wins), commutative merges (max, min, sum, count) use should_write_any and keep their old behaviour.
  • User callables keep the public (pixel, props, is_first) signature, paired with the always-write predicate (current behaviour for callables).
  • Dask tiles slice the per-type global-idx arrays with the same boolean masks used for props.

Closes #2064

Test plan

  • New TestMergeOrderAcrossTypes class: point-before-polygon, three-types-reverse-order, both 'first' and 'last', commutative-merge regression, dask+numpy path.
  • Full rasterize suite (numpy + dask paths): 252 passed, 2 skipped (CuPy unavailable).
  • GPU end-to-end check pending CI runner with CUDA.

merge='first'/'last' decides a pixel's value by the global input
position of the geometry, not by the polygon -> line -> point burn
phase.

Before: [(Point, 9), (Polygon, 1)] with merge='last' returned 9 at
the shared pixel because points always burned after polygons.  Now
it returns 1 because the polygon is the last input.

The fix:

- _classify_geometries returns a per-type int64 global-index array
  alongside the existing props arrays.  GeometryCollection unpacking
  inherits the parent input position.
- A new int64 ``order`` array tracks which input index currently
  owns each pixel.  Per-type burn kernels (scanline, lines, points)
  consult ``should_write(is_first, new_idx, cur_idx)`` before any
  write, so ordered merges gate by global index and commutative
  merges keep their old behaviour.
- Built-in merges dispatch to (merge_fn, should_write) pairs.  User
  callables keep the public (pixel, props, is_first) signature
  paired with the always-write predicate.
- All four backends (numpy, cupy, dask+numpy, dask+cupy) take the
  new plumbing.  Dask tiles slice the per-type global-idx arrays
  with the same boolean masks used for props.

Closes #2064
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 18, 2026
Address review nits with two regression tests:

- GeometryCollection sub-geoms share the parent's input index.
  When two sub-geoms (polygon + point) of the same GC compete for
  a pixel, the gate sees new_idx == cur_idx and skips, so the
  first-burned (polygon) wins.  Documents the policy.

- User callables for merge keep the pre-2064 "last-burned-wins"
  semantics because they pair with the always-write predicate.
  Built-in 'last' returns the polygon (last input); the callable
  returns the point (last burned).
@brendancol brendancol merged commit 23ec22a into main May 18, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rasterize: merge='first'/'last' ignores input order across geometry types

1 participant