Skip to content

Bump org.locationtech.proj4j:proj4j from 1.3.0 to 1.4.1#23

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/maven/org.locationtech.proj4j-proj4j-1.4.1
Open

Bump org.locationtech.proj4j:proj4j from 1.3.0 to 1.4.1#23
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/maven/org.locationtech.proj4j-proj4j-1.4.1

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Apr 23, 2026

Copy link
Copy Markdown
Contributor

Bumps org.locationtech.proj4j:proj4j from 1.3.0 to 1.4.1.

Release notes

Sourced from org.locationtech.proj4j:proj4j's releases.

1.4.1

1.4.1 - 2025-06-15

Fixed

  • External GridDefinition read fix #121

1.4.0

This release contains a new proj4j-geoapi module, lots of thanks to @​desruisseaux for making this contribution 🎉

1.4.0 - 2025-03-31

Fixed

  • LCC ProjectInverse adjustment, BasicCoordinateTransform.transform cleanup #117

Added

  • GeoAPI wrappers for PROJ4J #115
Changelog

Sourced from org.locationtech.proj4j:proj4j's changelog.

[1.4.1] - 2025-06-15

Fixed

  • External GridDefinition read fix #121

[1.4.0] - 2025-03-31

Fixed

  • LCC ProjectInverse adjustment, BasicCoordinateTransform.transform cleanup #117

Added

  • GeoAPI wrappers for PROJ4J #115
Commits

Dependabot compatibility score

You can trigger a rebase of this PR by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Note
Automatic rebases have been disabled on this pull request as it has been open for over 30 days.

Bumps [org.locationtech.proj4j:proj4j](https://github.com/locationtech/proj4j) from 1.3.0 to 1.4.1.
- [Release notes](https://github.com/locationtech/proj4j/releases)
- [Changelog](https://github.com/locationtech/proj4j/blob/master/CHANGELOG.md)
- [Commits](locationtech/proj4j@v1.3.0...v1.4.1)

---
updated-dependencies:
- dependency-name: org.locationtech.proj4j:proj4j
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file java Pull requests that update java code labels Apr 23, 2026
mjohns-databricks pushed a commit that referenced this pull request Jun 24, 2026
…sobands)

Two render fixes for the H3-isobands notebook:

1. PIXEL_SIZE_DEG — added to params cell (#6) as ~3 px per hex edge at
   SF latitude (h3.average_hexagon_edge_length / 3 / cos-scaled degrees).
   Passed to rst_h3_gridspec(pixel_size=PIXEL_SIZE_DEG) in cell #18 so
   per-band tiles have solid hex footprints rather than scattered specks.

2. composite="depth" — stack render cell (#28) now calls
   plot_raster(..., composite="depth") to show coverage depth (0..N
   viridis gradient) instead of an RGB-of-presence-masks composite that
   reads mostly black.  Markdown cell #27 updated to describe the
   coverage-depth view.  Markdown cell #23 notes that the finer pixel
   size produces solid single-band footprints.

No outputs (cluster-only notebook; outputs cleared).

Co-authored-by: Isaac
mjohns-databricks added a commit that referenced this pull request Jun 24, 2026
…agrams (#42)

* docs(spec): gbx.viz + pyrx escape-hatches design

Design for a tier-agnostic gbx.viz module ([viz] extra: matplotlib + geopandas +
folium + mapclassify) promoting the EO-series notebook helpers -- plot_raster /
plot_file (decimation + 2-98% percentile stretch + nodata masking) and as_gdf /
cells_as_gdf (Spark DF -> GeoDataFrame for .explore()) -- plus two Python-only
pyrx escape-hatches (tile_to_numpy, generalized rst_apply). Drops generate_cells;
keeps set_conf_safe + band-table ETL notebook-local. Pending user review.

Co-authored-by: Isaac

* docs(plan): gbx.viz + pyrx escape-hatches implementation plan

Co-authored-by: Isaac

* feat(viz): [viz] extra + package skeleton + dep guard

Adds gbx.viz package (importable skeleton for later tasks), assert_viz_available()
guard (raises ImportError with install hint if matplotlib/geopandas missing), and
the [viz] optional-dependency extra in pyproject.toml. Pins matplotlib==3.10.9,
geopandas==1.1.3, folium==0.20.0, mapclassify==2.10.0 in requirements-pyrx-ci.in
(latest on corp proxy) and regenerates the hash-pinned lock (83 packages). 2 tests
pass.

Co-authored-by: Isaac

* feat(viz): raster decimation + percentile-stretch pipeline

Add _raster.py with _decimated_read, _needs_percentile_stretch, and
_percentile_stretch helpers; test_raster.py with 3 TDD tests (3 passed).

* feat(viz): plot_raster + plot_file public raster plotters

Append _render, plot_raster, plot_file to viz/_raster.py; export
from viz/__init__.py. Matplotlib/rasterio are lazy-imported inside
each plotter; assert_viz_available() guards the public API. Agg
backend forced when headless. Also fixes unused pytest import (F401)
left from Task 2 and adds missing # noqa: E402 annotations so
flake8 is fully clean for all viz files.

Co-authored-by: Isaac

* fix(viz): robust headless backend selection in _render

Replace unreliable get_current_fig_manager() probe with a correct
headless guard: select Agg before pyplot import only when pyplot has
not yet been imported (no prior use() lock-in), MPLBACKEND is unset,
and no display is present (DISPLAY/WAYLAND_DISPLAY absent).  Databricks
notebooks pre-import pyplot with their own backend, so they are never
overridden.

Co-authored-by: Isaac

* feat(viz): as_gdf + cells_as_gdf Spark→GeoDataFrame adapters

Adds viz._vector with as_gdf (WKT column → GeoDataFrame, EPSG:4326,
max_rows guard with truncation warning) and cells_as_gdf (H3 bigint cell
ids → boundary polygons via h3 v4 int_to_str + cell_to_boundary).
Exports both from viz.__init__. 3/3 tests green.

Co-authored-by: Isaac

* feat(pyrx): tile_to_numpy + rst_apply escape-hatches

Add two Python-only escape-hatches in pyrx/core/escape.py:
- tile_to_numpy(tile_or_bytes): drops a collected tile or raw bytes to
  a numpy ndarray (all bands) for host-side exploration.
- rst_apply(tile_col, fn, returnType): applies an arbitrary rasterio
  callable per-row via a dynamic @udf; null tile -> null.

Both are re-exported on pyrx.functions (noqa: F401) so callers use
`from databricks.labs.gbx.pyrx.functions import tile_to_numpy, rst_apply`.
Neither is SQL-registered; binding-parity count is unchanged (154).

Co-authored-by: Isaac

* ci(viz): run test/viz in the lightweight tier (heavy skips)

Add "viz" to _LIGHT_TEST_DIRS in test/conftest.py (heavy CI phase
collect_ignore exclusion) and to the pytest dir list in the light CI
action. Clean-venv verification against requirements-pyrx-ci.txt
confirmed 829 passed, 2 skipped, RC=0 — all viz deps already in lock.

Co-authored-by: Isaac

* docs(viz): gbx.viz page + raster escape-hatches section

Co-authored-by: Isaac

* docs(viz): correct dev-container-match note in light lock comment

Co-authored-by: Isaac

* docs(eo-series): migrate notebooks onto gbx.viz + pyrx escape-hatches; drop library.py

The gbx.viz module ([viz] extra) and the pyrx escape-hatches (rst_apply,
tile_to_numpy) now provide, as first-class package APIs, the helpers the
eo-series previously carried in a local library.py + config_nb defs.

- config_nb.ipynb: install geobrix[light,stac,viz] (folium/mapclassify/geopandas
  now come via [viz], not a manual %pip); import plot_raster/plot_file/as_gdf/
  cells_as_gdf from databricks.labs.gbx.viz and rst_apply/tile_to_numpy from
  pyrx.functions; drop the local as_gdf/cells_as_gdf defs and the `import library`;
  keep the one surviving constant (FILENAME_TIMESTAMP_FORMAT) inline.
- library.py: DELETED. Everything in it is now superseded — plot_raster/plot_file
  by gbx.viz, to_numpy_arr/rasterio_lambda by tile_to_numpy/rst_apply,
  generate_cells was dead heavy-only, _set_conf_safe duplicated config_nb's
  set_conf_safe, FILE_SIZE_THRESHOLD was unused.
- 01-04: call the package functions directly (plot_raster/plot_file, the bare
  FILENAME_TIMESTAMP_FORMAT). nb03's raster->timeseries projection moves from
  library.rasterio_lambda("tile.raster", fn) to rst_apply("tile", fn) — rst_apply
  takes the tile struct and opens tile["raster"] itself (not the raw bytes column).
- README: drop the library.py row; note viz helpers come from gbx.viz; [light,stac,viz].

Source cells migrated; notebooks need re-execution on a cluster to refresh outputs
(they read /Volumes and run on Serverless/classic, not locally).

Co-authored-by: Isaac

* docs(eo-series): executed notebooks on gbx.viz + cleaned config_nb

Re-run on Serverless against the [viz]-enabled wheel. config_nb drops the now-dead
imports (geopandas/matplotlib.pyplot/rasterio.MemoryFile/io.BytesIO — superseded by
gbx.viz) and the library.py autoreload lines; installs geobrix[light,stac,viz].
Notebooks 01-04 carry refreshed outputs from the package-based viz/escape-hatch APIs.

Co-authored-by: Isaac

* docs(eo-series): refresh notebooks doc page + README for gbx.viz migration

Bring docs/docs/notebooks/eo-series.mdx and the series README in line with the
gbx.viz migration: drop all library.py references (file deleted), install
geobrix[light,stac,viz] (+ the viz extras matplotlib/geopandas/folium/mapclassify),
note visualization helpers come from databricks.labs.gbx.viz, and replace the
raster->timeseries `rasterio_lambda` mention with the `rst_apply` escape-hatch.
Option-2 (heavyweight) now flips only config_nb.ipynb.

Co-authored-by: Isaac

* docs(spec): H3 cell rasterizer (rst_h3_rasterize_agg + rst_h3_gridspec) design

Design for a DGGS-cell rasterizer: rasterize a set of H3 cell ids (+ optional
value) into a raster tile via pixel-centroid burn (the inverse of
rst_h3_rastertogrid). Grouped aggregator rst_h3_rasterize_agg (heavy UDAF + light
grouped pandas_udf, light SQL returns BINARY per the light-agg convention; default
value = 1/NoData presence mask; default 4326 with optional projected srid + auto
extent/pixel-size, full overrides).

rst_h3_gridspec defines the complete SHARED grid/canvas (snapped origin + pixel
size + dims + srid) once over the union of all thresholds, so every band
rasterizes to a byte-identical transform and stacks cleanly via rst_frombands_agg
(no half-pixel drift). Implemented as a scalar per-cell bbox + native min/max +
snap (both tiers; avoids the grouped-pandas_udf struct-return limit). Quadbin/BNG
variants are follow-ons. Validation: CI round-trip vs rastertogrid + partition
property + a committed FCC fixed-wireless subset; DEM elevation-isoband notebook
for the full polygons->polyfill->rasterize->stack demo. Pending user review.

Co-authored-by: Isaac

* docs(plan): H3 cell rasterizer implementation plan

Co-authored-by: Isaac

* feat(pyrx): H3 cell rasterize core (centroid burn + gridspec)

Adds cellraster.py with pure-function rasterization primitives:
_h3_str (signed-Long normalization), _resolution (uniform-res guard),
cell_bbox, snap_bounds (lattice-aligned snapping, DRY helper for Task 2),
compute_gridspec (kring-padded, snapped 8-tuple), and cells_to_raster
(pixel-centroid burn to float64 GTiff bytes). 5/5 tests green.

Note: brief's half-pixel centroid expansion pre-snap caused the
single-cell kring_pad=0 case to straddle two lattice slots (→2x2);
removed expansion — snap_bounds correctly gives 1x1 for a single point.

* fix(pyrx): drop redundant H3 round-trip in cells_to_raster hot loop

latlng_to_cell already returns canonical lowercase hex (same format
as lut keys built via _h3_str); int_to_str(str_to_int(...)) is a
no-op. Removing two calls per pixel from the hot path.

Also adds a note to snap_bounds docstring that pixel_size is not
included in its return tuple and must be threaded separately by callers.

Co-authored-by: Isaac

* feat(pyrx): gbx_h3_cell_bbox scalar + rst_h3_gridspec helper

Adds the scalar gbx_h3_cell_bbox UDF (returns STRUCT<xmin,ymin,xmax,ymax>
for one H3 cell with optional kring_pad neighbourhood expansion) and the
rst_h3_gridspec() DataFrame helper (snapped shared canvas per group of
H3 cells). Both are Serverless-safe: no spark.conf.set, no _jvm access,
no sparkContext/rdd usage. The snap arithmetic in rst_h3_gridspec exactly
mirrors cellraster.compute_gridspec (floor/ceil lattice alignment), verified
by test_rst_h3_gridspec_matches_core.

Co-authored-by: Michael Johns

* fix(pyrx): rst_h3_gridspec auto-pixel-size parity + empty-input guard

- Add test_rst_h3_gridspec_auto_pixel_size_matches_core: verifies that
  the pixel_size=None path (edge-length + lat-cosine formula) in the
  Spark helper produces width, height, xmin, ymin, and pixel_size
  within 1e-9 of cellraster.compute_gridspec. Test passes as-is —
  midlat derivation was already equivalent between the two paths.

- Add test_rst_h3_gridspec_empty_raises: empty DataFrame with the right
  schema must raise ValueError("empty cell set"), mirroring the core.

- Fix rst_h3_gridspec empty-input guard: was silently falling back to
  pixel_size=1.0 (via _res=None branch). Now raises ValueError
  immediately on first()=None, matching compute_gridspec's guard.

Co-authored-by: Isaac

* feat(pyrx): rst_h3_rasterize_agg grouped aggregator

Adds _rst_h3_rasterize_agg_udf (pandas_udf, BinaryType), the public
rst_h3_rasterize_agg Column wrapper (returns tile struct via _as_tile_udf),
and registers gbx_rst_h3_rasterize_agg in _sql_aggregators.

Presence-mask default (value omitted -> 1.0), explicit-extent path for
aligned band stacking, Serverless-safe (no spark.conf/_jvm). 2 tests green.

Co-authored-by: Isaac

* refactor(pyrx): drop dead value-None branch in rst_h3_rasterize_agg udf

Co-authored-by: Isaac

* docs(function-info): register gbx_rst_h3_rasterize_agg + gbx_h3_cell_bbox

Add both new H3-cell-rasterizer functions to the canonical registered
list, doc SQL examples, function-info.json (regenerated), and binding-
parity test. Also adds h3_/gbx_h3_ prefix to generate-function-info.py
MODULES + PACKAGE_PREFIXES so the generator discovers h3_*_sql_example
functions in rasterx_functions_sql.py.

Binding-parity counts: Python 156, function-info 156, Scala 153 (the
2 new names are Scala-absent by design until Task 7 adds the JVM side).

Co-authored-by: Isaac

* fix(function-info): h3_cell_bbox SQL example args + rasterize_agg ordering

- h3_cell_bbox_sql_example: add missing 4th arg kring_pad=0 to SQL call
  (registered UDF requires all 4 positional args: cellid, srid, mode, kring_pad);
  drop non-standard L long-literal suffixes from VALUES clause
- registered_functions.txt (both copies): move gbx_rst_h3_rasterize_agg before
  gbx_rst_h3_rastertogridavg (alphabetical: 'rasterize_' < 'rastertogrid')
- Regenerate function-info.json (156 entries)
- Both doc-tests green; binding parity unchanged (Scala 153/156, known Task 7 gap)

Co-authored-by: Isaac

* test(pyrx): H3 rasterize round-trip + partition validation

Add CI validation tests for rst_h3_rasterize_agg (T1-T4):
- Round-trip: SRTM DEM -> gbx_rst_h3_rastertogridavg (LATERAL UDTF,
  band 1) -> cells_to_raster; >99% of burned pixels match cell measures.
- Partition: polyfill polygon -> rasterize_agg; every pixel centroid
  burned <=> its h3 cell is in the input set (exact bijection).

Uses LATERAL join for the light-tier UDTF (flat band/cellID/measure
rows), not array[0] indexing which is the heavy-tier shape.

* test(pyrx): sound nearest-value round-trip oracle + partition pixel-count bounds

Fix 1: replace searchsorted-nearest (insertion-point, not nearest) with
a correct two-neighbour min-distance check; widen atol from 1e-6 to 1e-3
to match float32 GeoTIFF quantization. 0/N pixels off against current impl.

Fix 2: add burned_count lower/upper bounds after the biconditional loop so
an all-NoData or all-burned tile cannot pass vacuously.

Co-authored-by: Isaac

* test(pyrx): FCC fixed-wireless H3 rasterize fixture + test

Curates 400-row Miami-Dade (block_geoid 12086, provider 230008) subset
from FCC BDC unlicensed fixed-wireless data; commits as
test/pyrx/data/fcc_uflw_miamidade_subset.csv. Adds
test_h3_rasterize_fcc.py: groupBy(provider, speed).agg(rst_h3_rasterize_agg)
on real res-8 H3 cells, asserting >=1 burned pixel per tile.

Co-authored-by: Isaac

* test(pyrx): multi-speed-tier FCC fixture for the rasterize-per-threshold analog

Previous fixture was homogeneous (all 25 Mbps, single provider), so the
groupBy(provider, speed) produced only 1 tile. Re-curate to span 5 tiers
(10, 25, 45, 100, 160 Mbps), 60 rows each, 6 distinct (provider, speed)
groups, all from Miami-Dade block_geoid prefix 12086.

Strengthen test to assert >= 4 distinct speed tiers in the loaded CSV and
>= 4 tiles after aggregation, with per-tile burn-pixel assertion unchanged.

Co-authored-by: Isaac

* feat(rasterx): RST_H3_RasterizeAgg UDAF + gbx_h3_cell_bbox (heavy tier)

Add the heavyweight Scala tier for the H3 cell rasterizer, closing the
binding-parity gap left by the lightweight tier (Tasks 1-6):

- RST_H3_RasterizeAgg: TypedImperativeAggregate that accumulates
  (cellId, value) pairs per group and, on eval, burns them into one
  GTiff tile by pixel-centroid mapping (the inverse of
  RST_H3_RasterToGrid's affine). Resolution is derived from the cells
  (errors on mixed); the grid is taken from an explicit extent when
  fully supplied, else snapped/lattice-aligned from the cell set +
  kring_pad (port of pyrx cellraster.compute_gridspec). NoData=-9999.0;
  a null value burns the 1.0 presence mask. Param order and semantics
  mirror the light rst_h3_rasterize_agg.
- RST_H3_CellBBox (gbx_h3_cell_bbox): scalar returning
  STRUCT<xmin,ymin,xmax,ymax> from the cell centroid (centroids) or
  hexagon envelope (spatial_envelope), reprojected to srid, with
  optional kring_pad expansion. Mirrors the light _h3_cell_bbox_udf.
- Register both in rasterx.functions + Column helpers; per-pixel and
  gridspec reprojection go through GDALManager-guarded OSR transforms.

Test: RST_H3_RasterizeAggTest (7 tests, direct-execute, no Spark)
asserts every covered pixel maps back into the input cell set, the
presence mask, empty-group null, mixed-resolution error, serde
roundtrip, and both cell-bbox modes. Binding parity 156/156.

Co-authored-by: Isaac

* test(rasterx): heavy<->light H3 rasterize parity (JAR-gated)

Adds test_h3_rasterize_parity.py: rasterizes a res-9 H3 cell set onto the
SAME explicit grid (from cellraster.compute_gridspec) via both tiers and
asserts the covered-pixel masks are identical. Module-level skip when JAR
is absent. Ran green (1 passed in 20.33s) after JAR rebuild.

Co-authored-by: Isaac

* feat(rasterx): heavy Python wrappers for rst_h3_rasterize_agg + gbx_h3_cell_bbox

Add two Python wrappers to the heavy-tier rasterx/functions.py that
were absent despite the underlying SQL functions being registered by
the Scala UDAF (Task 7):

- rst_h3_rasterize_agg(cellid, value, srid, pixel_size, xmin, ymin,
  xmax, ymax, width, height, mode, kring_pad) → calls
  gbx_rst_h3_rasterize_agg with all 12 positional args; mirrors the
  12-arg pyrx light-tier signature.
- gbx_h3_cell_bbox(cellid, srid=4326, mode="centroids", kring_pad=0)
  → calls gbx_h3_cell_bbox; matches the public name used by the pyrx
  light tier with the same optional-arg defaults.

Update test_h3_rasterize_parity.py to invoke the new
rx.rst_h3_rasterize_agg wrapper directly (previously bypassed via
f.call_function), validating the wrapper end-to-end.

Verified: 1 passed (parity test ran green with JAR), bindings 156/156,
black/isort clean on both changed files (E501 suppressed project-wide).

* style(test): black-normalize _LIGHT_TEST_DIRS list in conftest

Pre-existing container-black noncompliance (host black left the long line);
normalize so the CI lint gate stays green. Semantically identical.

Co-authored-by: Isaac

* docs(rasterx): rst_h3_rasterize_agg + gbx_h3_cell_bbox + rst_h3_gridspec

Add three new entries to the RasterX function reference page:

- rst_h3_rasterize_agg (Aggregator Functions): H3 cell-to-raster burn
  aggregator with full BINARY warning block and CodeFromTest reference.
  Inverse of rst_h3_rastertogrid*; cross-references rst_frombands_agg
  for band stacking.
- gbx_h3_cell_bbox (Grid Functions H3): scalar bounding-box function for
  one H3 cell in the given CRS; drives the canvas extent params for
  rst_h3_rasterize_agg.
- rst_h3_gridspec (Grid Functions H3): Python-only helper note explaining
  how to compute a shared snapped canvas for aligned multi-band stacking
  via rst_frombands_agg / rst_merge_agg.

Also bumps section counts (Aggregators 6→7, H3 Grid 5→7) and adds
rst_h3_rasterize_agg to the Overview bullet.

Co-authored-by: Isaac

* docs(notebooks): H3 cell rasterize + stacking demo (DEM isobands)

Full pipeline example: SRTM DEM → elevation isobands → h3.polygon_to_cells
polyfill → rst_h3_gridspec shared canvas → rst_h3_rasterize_agg per-band
tiles → rst_frombands_agg multi-band stack → plot_raster visualization.
Includes README with telco/coverage-analysis analogy and runtime instructions.
Runs on Serverless (geobrix[light,viz] only, no JAR/GDAL init script).

Co-authored-by: Isaac

* docs(rasterx): correct rst_h3_gridspec API doc + reconcile spec snapping wording

- Fix rst_h3_gridspec MDX entry: was claiming a non-existent signature
  `rx.rst_h3_gridspec(cellids, srid, mode, kring_pad)` returning a plain
  dict with width_px/height_px keys; replace with the real DataFrame helper
  signature (df, cell_col, *group_cols, srid, pixel_size, mode, kring_pad)
  and the actual STRUCT<xmin,ymin,xmax,ymax,pixel_size,width,height,srid>
  return type (matching _GRID_SCHEMA in pyrx/functions.py).
- Correct the "both execution tiers" claim: rst_h3_gridspec is lightweight
  (pyrx) only; add a pointer to the gbx_h3_cell_bbox scalar + min/max
  aggregate recipe for heavyweight users.
- Fix spec (2026-06-23-h3-cell-rasterizer-design.md): centroid-mode bounds
  description removed the "±half-pixel / land on pixel centers" sentence;
  replaced with the actual floor/ceil snap that shipped (matching the
  implementation in _snap_to_grid).

Co-authored-by: Isaac

* docs(rasterx): diagram + release-notes + tile-output/table for H3 rasterizer

Fix three QC-judge failures blocking push:
- diagram-coverage (D4a/D4b): add rst_h3_rasterize_agg to Aggregators
  card and bump all three "107 functions/SQL functions" strings to 108;
  re-render SVG + PNG
- doc-coverage D4 (tile-output): rst_h3_rasterize_agg_sql_example_output
  changed from bare [BINARY] to {null, <raster bytes>, {driver -> GTiff,
  ...}} matching the tileDataType struct the UDAF returns
- doc-coverage D5 (table-alignment): h3_cell_bbox_sql_example_output
  ASCII table re-aligned to canonical column widths (18 + 31 chars)
- release-notes-functions: add bullet for gbx_rst_h3_rasterize_agg and
  gbx_h3_cell_bbox (+ rst_h3_gridspec light helper) under v0.4.0

Co-authored-by: Isaac

* docs(notebooks): rename to h3_rasterize_isobands + auto-stage DEM cell

Rename h3_rasterize_demo.ipynb -> h3_rasterize_isobands.ipynb. Add a "Stage the
sample DEM" cell that idempotently downloads SRTM N40W073 from the public AWS
Terrain Tiles dataset and writes it to DEM_PATH on first run — node-local temp +
rasterio .hgt->GTiff (no gdal_translate CLI, Serverless-safe) + FUSE-safe
shutil.copy to the Volume. Carries the user's path edits (geobrix[light,viz]
wheel + DEM_PATH on the geospatial_docs sample-data Volume); README requirements
updated to match (auto-staging, real paths).

Co-authored-by: Isaac

* fix(test): guard h3/rasterio/pandas imports in heavy<->light parity test

The cross-tier parity test lives in test/rasterx (heavy-collected) but needs the
light-tier deps (h3/rasterio/pandas via pyrx) in addition to the JAR. The heavy
CI build python env has the JAR but not the light deps, so the module-top
`import h3` errored at COLLECTION (ModuleNotFoundError), failing the heavy build.
The dev container masked it (it has both tiers). Guard the light imports with
pytest.importorskip so heavy CI skips the module cleanly; it still runs where the
JAR + light deps coexist (dev container, cluster). Verified: collection with h3
blocked -> skipped (no error); flake8 clean.

Co-authored-by: Isaac

* feat(viz): grid_as_gdf canvas helper + cells_as_gdf dissolve_by

Adds grid_as_gdf(grid, srid=None) — converts an rst_h3_gridspec grid
struct (Row or dict) to a 1-row EPSG:4326 GeoDataFrame containing the
canvas rectangle, with optional pyproj reprojection for non-4326 SRIDs
and pixel_size/width/height metadata columns carried through.

Adds dissolve_by= keyword arg to cells_as_gdf: when set to a column
that is already in extra_cols, returns one dissolved polygon per
distinct value (union footprint) instead of one row per cell.  Raises
ValueError if dissolve_by is not in extra_cols.

9/9 tests pass; black/isort/flake8 clean on changed files.

Co-authored-by: Isaac

* docs(notebooks): render DEM/polyfill/grid via gbx.viz in isobands demo

Wire the three render steps into h3_rasterize_isobands using the viz helpers
(grid_as_gdf + cells_as_gdf dissolve_by added in the same change set):
- Step 1: plot_file(DEM_PATH) — source DEM.
- Step 2: cells_as_gdf(..., dissolve_by="band_level").plot — polyfill, dissolved
  per band (one footprint polygon per band, efficient vs one-row-per-cell).
- Step 3: grid_as_gdf(g) overlaid on the per-band footprints via folium
  .explore(m=...) — the shared canvas framing the cells (multi-layer on one map).

Co-authored-by: Isaac

* docs(notebooks): switch isobands demo to San Francisco DEM (N37W123)

Replace NYC tile (N40W073, −85..90 m, 1 band) with SF Bay Area tile
(N37W123); chosen band params give 6–10 well-populated elevation bands
over SF urban+hill terrain.

Co-authored-by: Isaac

* docs(notebooks): clear stale NYC outputs from SF isobands demo

Clean source state after the N37W123 swap (subagent left stale NYC image
outputs). User re-runs on the cluster to populate SF renders.

Co-authored-by: Isaac

* docs(notebooks): SF-specific narrative + distributed-scaling notes for isobands demo

- Intro (cell 0): ground terrain in SF facts (Twin Peaks ~280 m, Mt Tamalpais
  ~784 m, sea level baseline → eight 100 m bands); add SF-specific framing for
  the telco analogy (hilly coastal city, terrain shadows RF coverage).
- Runtime requirement (cell 0): fix "DEM is read from the Volume" →
  "tile is fetched by the staging cell (public AWS Terrain Tiles, idempotent)".
- Cell 5 (DEM parameters): remove "ships with the GeoBrix sample-data Volume";
  reword to reflect the staging-cell download flow accurately.
- Cell 9 (Step 1 intro): call out explicitly that DEM read + isoband extraction
  run on the driver (single node); note Spark stages begin in Step 3.
- Cell 13 (Step 2 intro): same driver/single-node clarity; expand the
  production-scale note to name binaryFile + gtiff_gbx reader pattern.
- Cell 29 (Summary): annotate step table with (driver)/(Spark); add
  "Scaling to many tiles" bullet pointing at binaryFile/gtiff_gbx multi-tile
  distributed-polyfill direction.

Co-authored-by: Isaac

* feat(viz): plot_raster composite="depth" coverage-depth mode

Add `composite` keyword to `plot_raster`, `plot_file`, and the internal
`_render` helper.  `composite="auto"` (default) preserves existing
behaviour (1 band → viridis; 3+ → RGB).  `composite="depth"` computes
per-pixel band coverage count via the new `_coverage_depth` helper —
turns a stack of binary presence masks into a bright 0..N viridis
gradient instead of a dark RGB composite; uncovered pixels are masked
transparent.

TDD: 5 new tests in test_raster.py covering the depth helper (masked
array path, plain-ndarray/nodata path, known-geometry verification) and
the public API (figure produced for depth mode; auto mode unchanged).
All 10 viz tests pass.

Co-authored-by: Isaac

* docs(notebooks): finer pixel size + coverage-depth stack render (SF isobands)

Two render fixes for the H3-isobands notebook:

1. PIXEL_SIZE_DEG — added to params cell (#6) as ~3 px per hex edge at
   SF latitude (h3.average_hexagon_edge_length / 3 / cos-scaled degrees).
   Passed to rst_h3_gridspec(pixel_size=PIXEL_SIZE_DEG) in cell #18 so
   per-band tiles have solid hex footprints rather than scattered specks.

2. composite="depth" — stack render cell (#28) now calls
   plot_raster(..., composite="depth") to show coverage depth (0..N
   viridis gradient) instead of an RGB-of-presence-masks composite that
   reads mostly black.  Markdown cell #27 updated to describe the
   coverage-depth view.  Markdown cell #23 notes that the finer pixel
   size produces solid single-band footprints.

No outputs (cluster-only notebook; outputs cleared).

Co-authored-by: Isaac

* fix(viz): render constant single-band masks (presence) with visible background

When a single-band raster holds only one distinct valid value (e.g. an H3
presence mask: 1.0 where covered, NoData=-9999 everywhere else), matplotlib's
Normalize ends up with vmin == vmax.  That makes the normalizer degenerate:
every valid pixel maps to 0.5 in the colormap but the figure background is
white, so the image appears blank.

Fix: add _single_band_clim(valid) which detects the constant-value case and
returns an explicit non-degenerate (vmin, vmax) pair (mapping the constant to
the upper end of a [0, const] or [0, 1] interval).  The single-band branch of
_render now extracts the unmasked values, calls _single_band_clim, and passes
vmin/vmax only when needed.  ax.set_facecolor("whitesmoke") ensures masked
border pixels are visually distinct from the white figure background.

Five new tests: four _single_band_clim unit tests (constant-1, constant-0,
varying, empty) plus an integration test that constructs a real presence-mask
GTiff and asserts a figure is produced.

Co-authored-by: Isaac

* docs(notebooks): materialize band tiles to session temp table

Replace the lazy .orderBy() with a session-scoped CREATE TEMP TABLE so the
expensive per-pixel rasterize runs once and steps 8/9 read it back instead of
recomputing. This is the Serverless-idiomatic .cache() replacement (.cache and
.persist are unavailable there); the table auto-drops at session end. Requires
Serverless or DBR 18.1+ (not dedicated/single-user clusters). Also strip trailing
newlines from all cells.

Co-authored-by: Isaac

* docs(notebooks): inspect highest-two band coverage shapes

A single band is a binary presence mask (one colour), so the low/dense bands
render as a flat filled canvas. Show the highest two thresholds instead -- their
footprints cluster on the hilltops, making the coverage shape (and the rasterizer's
correctness) actually visible. Reads from the band_tiles temp table, no recompute.

Co-authored-by: Isaac

* docs(notebooks): inspect mid-coverage bands (clear footprint shape)

Highest bands (63/173 cells) are a sparse speck on the 499x505 canvas and the
lowest fills it uniformly -- both read as "empty/flat". Auto-pick two mid-coverage
bands (8-45% of the densest) so the footprint is a substantial, clearly-shaped
region that shrinks with elevation. Clear cell 24's stale output.

Co-authored-by: Isaac

* fix(viz): draw single-band masks via imshow, not rasterio show()

rasterio.plot.show() renders a constant-valued single band (an H3 presence mask,
all 1.0) as a blank plot and ignores the explicit vmin/vmax, so every per-band
inspection looked empty. Render the single-band branch with ax.imshow + an explicit
plotting_extent instead -- it honors the clim and the masked array (NoData ->
transparent over the facecolor). Replace the figure-exists check with a real
regression test asserting the footprint is actually drawn (non-degenerate clim +
coloured pixels in the rasterized buffer); the old check passed while blank.

Verified locally across full/mid/sparse coverage and a continuous DEM-like raster.

Co-authored-by: Isaac

* style(pyrx): clear flake8 dead code + docker-black reformat

Remove unused imports (math/numpy in _h3_cell_bbox_udf, math in rst_h3_gridspec)
and the dead _mode_val capture (mode is already applied via the bbox UDF), plus an
unused numpy import in test_core_cellraster. Reformat test_vector_raster_bridge with
in-container black (a prior host-black pass diverged from CI's black). Clears the CI
Python-lint gate; no behaviour change.

Co-authored-by: Isaac

* feat(viz): plot_mask_layers overlay + use it in isobands cell 8

Add plot_mask_layers(layers, colors=, ...): overlay several single-band presence-
mask tiles on one axes, each a solid colour with a legend (NoData transparent over a
grey facecolor). Tiles must share a grid; draw order is largest-first so nested
coverage stays visible. Notebook cell 8 now overlays the two mid-coverage bands on a
single plot instead of two separate viridis figures. Regression test asserts two
overlaid AxesImages, a 2-entry legend, and both requested colours present in the
rasterized buffer.

Co-authored-by: Isaac

* docs(notebooks): match polyfill markdown to undissolved render + viz summary

Cell 16 described dissolve_by as active while the code renders per-cell (kept
deliberately: per-cell tooltips read better at this size). Reword to the per-cell
render and add a Note pointing at dissolve_by="band_level" for larger sets. Update
the summary table's Visualize rows to the actual calls (plot_mask_layers,
plot_raster(composite="depth")).

Co-authored-by: Isaac

* docs: H3 rasterize example page + viz functions + release notes

- New docs/docs/notebooks/h3-rasterize.mdx (registered in sidebars.js) documenting
  the SF Bay Area H3 rasterize -> band-stack example.
- viz.mdx: document plot_mask_layers, grid_as_gdf, composite="depth", and the
  cells_as_gdf dissolve_by option; update the import list.
- beta-release-notes: add gbx.viz module bullet + H3 rasterize example bullet.
- raster-functions: add explicit {#h3-grid} anchor so release-notes link resolves.
- README: retarget the example to the San Francisco DEM, the session temp table,
  and the new viz helpers.

Validated with a full Docusaurus build (no broken links).

Co-authored-by: Isaac

* docs(raster-functions): link the H3 rasterize notebook from rst_h3_rasterize_agg

Add a "Worked example" tip in the rst_h3_rasterize_agg section pointing at the new
H3 Rasterize notebook, cross-linking rst_h3_gridspec, rst_frombands_agg, and gbx.viz.

Co-authored-by: Isaac

* bench: add rst_h3_rasterize_agg to the cluster benchmark (both tiers)

New "h3_aggregate" input kind: a fixed 331-cell res-9 H3 set on a hardcoded
explicit grid, mirrored byte-for-byte in spec.py (_H3RAGG_*) and BenchDispatch.scala
(h3Ragg*) so the light and heavy legs burn the identical cells onto the identical
canvas. Python: FnSpec (dggs, spark-path, fingerprint) + _h3_aggregate_df group
builder + runner wiring. Scala: DGGS category, h3Aggregate set/inputKind, heavy
aggregate case (UDAF -> gbx_rst_fromcontent tile struct), HeavyRunner group branch,
BenchDispatchTest count 106->107. Cross-tier masks verified byte-identical (0 px)
via the local JAR; Scala bench suite green (20 tests).

Co-authored-by: Isaac

* bench: heavy rst_h3_rasterize_agg returns a tile struct, do not wrap in fromcontent

The heavy UDAF's dataType is tileDataType(BinaryType) -- a tile STRUCT, like
rst_rasterize_agg -- so the cluster heavy leg must call gbx_rst_h3_rasterize_agg
directly (the consistency collect reads `raster` off the struct). Wrapping it in
gbx_rst_fromcontent (a BINARY->struct helper, only needed for the lightweight SQL
form) fed a struct where bytes were expected and aborted the distributed agg with
"[INTERNAL_ERROR] Couldn't find method eval". Match the rst_rasterize_agg pattern.

Co-authored-by: Isaac

* fix(pyrx): rst_h3_rasterize_agg null value column burns presence 1.0, not NaN

A null in a TYPED (Double) value column arrives in the pandas_udf as np.nan, and
`np.nan is not None` is True, so the presence guard burned float(np.nan)=NaN instead
of 1.0. Guard with pd.isna. The cluster benchmark caught this as a heavy(1.0)-vs-
light(NaN) cross-tier divergence (the value-omitted path was already correct, which
is why the JAR-gated parity test passed). Regression test added with an explicit
nullable DoubleType value column.

Co-authored-by: Isaac

* docs(benchmarking): add rst_h3_rasterize_agg spark-path row (heavy 1.5x, exact)

Cluster bench (1000 groups, fixed 20 workers): heavy 1.50 ms/tile vs light 2.26
ms/tile (heavy ~1.5x faster), cross-tier parity exact. Footnoted because the H3
rasterizer's workload (331-cell groups -> 39x24 output) differs from the 1024² rows,
so the cross-tier ratio + exact parity are the comparable takeaways.

Co-authored-by: Isaac

* docs(performance): classify rst_h3_rasterize_agg + the custom-grid family

Bring the lightweight implementation-techniques page current: add
rst_h3_rasterize_agg to the grouped-aggregate UDF table, and a "GridX (pygx) --
custom grid" section in the Arrow scalar tab mirroring the quadbin/BNG families
(scalar cell-ops as pandas_udf, array polyfill/kring as plain @udf). Regular scalar
UDFs (metadata/accessors, h3_cell_bbox) remain intentionally unlisted.

Co-authored-by: Isaac

* docs(images): regenerate landscape function-categories PNG (108 fns, H3)

The landscape SVG already carried rst_h3_rasterize_agg + the 108 count, but its
screenshot PNG was last rendered pre-H3 (107). Re-screenshot from the current SVG so
the slide asset matches; the portrait PNG (used on the docs page) was already current.

Co-authored-by: Isaac

* docs(examples): hero diagrams at the top of each example notebook

eo-series 01-04: embed the existing eo-series-0N.png banner after the intro cell.
xView + h3-rasterize: new resources/images/example-diagrams.py (reuses the
eo-series.py diagram framework via importlib) renders xview-clipping.png and
h3-rasterize.png, embedded after each notebook's intro cell and in their READMEs.
Includes the executed-notebook + banner edits supplied for the example set.

Co-authored-by: Isaac

* docs(images): refresh eo-series diagrams to the lightweight tier

Update the four eo-series notebook diagrams' chips + captions to match the migrated
notebooks: shapefile_ogr→shapefile_gbx, pystac_client→StacClient (01); the deleted
download_band/update_assets flow → StacClient.download / StacClient.repair (02);
gdal reader→gtiff_gbx, rasterio_lambda→rst_apply (03); gdal writer→gtiff_gbx (04).
Tier-agnostic built-ins (h3_tessellateaswkb, st_*) and still-used functions kept.
Re-rendered all four PNGs. Also includes the h3 notebook's first-cell edit.

Co-authored-by: Isaac

---------

Co-authored-by: Michael Johns <user.name>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file java Pull requests that update java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants