Skip to main content

Release Notes

Current version: 0.5.2

The changes on this page are relative to 0.1.0 (and earlier).

This page tracks API and naming changes across GeoBrix releases — the single place to look up what changed and why.


What's new in v0.5.2

Under development

v0.5.2 is under active development — the functions and documentation on this page may change before release.

Adds LiDAR point-cloud ingestion and DSM/CHM surface-modelling functions.

LiDAR reader — lidar_gbx (new)

  • New lidar_gbx Spark DataSource reads LAS and LAZ point-cloud files into a Spark DataFrame. Two modes: metadata (one row per file, header-level statistics, cheap on large archives) and points (one row per point). Options: classFilter (keep specific LAS classification codes), returnFilter (keep a specific return number), decimate (keep every N-th point), dimensions (return only a subset of the point columns, read at the source), chunkSize. Lightweight only; Serverless-safe. Point reads are columnar and skip empty or unreadable files, so full-density (decimate=1) point clouds read without stalling. See LiDAR Reader.

Point-cloud DSM functions (new)

The four registered functions below ship in both tiers — heavyweight RasterX and lightweight pyrx — so they take part in the same heavy-vs-light parity comparison as the rest of the raster family; rx.bin_points_tiled is a lightweight-tier Python helper.

  • gbx_rst_binpoints — bin parallel ARRAY<DOUBLE> x/y/z columns into a Float32 DSM raster. Each pixel carries a configurable statistic (max, min, mean, median, count, or percentile:<p>) over all points whose centroid falls in the cell. Empty cells carry NoData.

  • gbx_rst_binpoints_agg — streaming aggregator variant: one scalar (x, y, z) point per row, binned into a DSM per group. The SQL aggregate returns BINARY (raster bytes); wrap with gbx_rst_fromcontent(<agg>, 'GTiff') to rebuild a tile struct. The Python wrapper rx.rst_binpoints_agg(...) returns the full tile struct.

  • rx.bin_points_tiled (lightweight Python helper) — bounded tiled point binning built on gbx_rst_binpoints_agg: peak memory scales with the raster grid (width × height), not the point count, so a full-density (decimate=1) tile bins without exhausting a worker. It returns the same tile struct and matches groupBy(...).agg(rst_binpoints_agg(...)) for max/min/mean/count.

  • gbx_rst_isoband — reclassify a raster band into value bins and return one WKB polygon per contiguous patch. Returns ARRAY<STRUCT<geom_wkb BINARY, band INT, lower DOUBLE, upper DOUBLE>>. Useful for height-band polygons from a CHM or elevation isobands from a DEM.

  • gbx_rst_chm — compute a Canopy Height Model: warp the DSM onto the DEM grid, subtract, and clamp negatives to zero. NoData propagates from either input.

Geom-aware grid functions — GeometryCollection inputs

  • GeometryCollection inputs to the geom-aware grid functions (gbx_*_geomkring / gbx_*_geomkloop and their explode variants) are now processed as the union of their members, matching MULTI* behavior — polygon, line, and point members all contribute (previously non-polygon members were dropped or the result was empty). As with a standalone point or line, point and line members contribute only under coverage = coveras.

Geom-aware grid functions — performance improvement for large polygons

  • gbx_quadbin_geomkring / gbx_quadbin_geomkloop, gbx_bng_geomkring / gbx_bng_geomkloop, and gbx_custom_geomkring / gbx_custom_geomkloop (and their *explode variants) now scale with the polygon's boundary length rather than its area. For large polygons, the previous implementation enumerated every cell inside the polygon before identifying the boundary band — a cost proportional to the polygon's area. The new implementation traces the polygon boundary ring directly, classifying only cells near the perimeter. For a polygon that covers tens of thousands of cells at the target resolution, large-polygon boundary-out calls are dramatically faster (10–100× depending on grid and resolution), and medium-polygon throughput is 2–10× higher. The cell sets returned are unchanged — this is a pure performance improvement with no effect on results.

  • Behaviour fix for non-rectangular polygons in the heavyweight tier. For non-rectangular polygons, the geom-aware kring/kloop functions in the heavyweight (Scala/Spark) tier previously returned a slightly larger boundary band than the lightweight tier due to an over-count in the outer-perimeter seed calculation. The heavyweight tier now uses the same boundary-tracing seed as the lightweight tier, so the two tiers produce identical cell sets for all polygon shapes.


What's new in v0.5.1

Extends the raster-grid family with coverage and assignment controls, on top of v0.5.0. The new Grid Raster Tessellation page documents the unified coverage / assignment / fill model that every raster-to-grid function now shares — start there for the concepts behind the changes below.

note

v0.5.1 was re-cut shortly after its initial release to refine the new geometry-aware kring/kloop behaviors — boundary/hole semantics, grid-aligned robustness, point/line support, and the coverage parameter. The descriptions below reflect the current behavior.

Raster-to-grid — coverage and assignment (breaking changes)

  • gbx_rst_{h3,quadbin,bng}_rastertogrid* now default to coverage=complete (breaking behavior change). Previously, only cells that contained at least one valid pixel were returned. With coverage=complete, every cell whose area overlaps the raster extent is returned — cells with valid pixels carry a numeric measure, and cells covered by the extent but containing only NoData are returned with a NULL measure (or 0.0 for count). Queries that assumed the result set was restricted to cells with data will now receive extra rows. Add coverage='sparse' to any call that should preserve the old drop-empty behavior: gbx_rst_h3_rastertogridavg(tile, resolution, 'sparse').

  • New assignment parameter on gbx_rst_{h3,quadbin,bng}_rastertogrid*. Controls how pixels are assigned to cells:

    • centroid (default) — a pixel belongs to the cell that contains its centroid. Each pixel contributes to exactly one cell. This is the behavior prior to this release.
    • covering — a pixel contributes to every cell whose area it overlaps, weighted by the fraction of the pixel's area that falls within each cell. The total contribution across all cells equals the original pixel value, so sum and count are mass-conserving: summing across all overlapping cells recovers the original pixel total. Use covering when accurate spatial aggregation near cell boundaries matters more than sharp per-cell attribution.

    Full signature: gbx_rst_{grid}_rastertogridavg(tile, resolution, [coverage], [assignment]). See Grid Raster Tessellation for the full coverage/assignment/fill model and per-grid specifics.

  • gbx_rst_{h3,quadbin,bng}_rastertogridcount now returns DOUBLE (was integer). With area-weighted assignment (covering), count is the sum of area fractions, which is fractional for pixels that straddle a cell boundary. The return type is DOUBLE on both tiers for both assignment modes. Queries that cast the count column to integer or use COUNT(*) aggregation over the result should be updated.

Raster tessellate — parameter rename and new coverage control (breaking changes)

  • gbx_rst_{h3,quadbin,bng}_tessellate: the mode parameter is renamed to assignment (breaking, no alias). The existing values (centroid, covering) are unchanged; only the parameter name changes. Code passing a named mode= argument must be updated to assignment=. Positional calls with two arguments (tile, resolution) are unaffected.

  • gbx_rst_{h3,quadbin,bng}_tessellate: new optional coverage parameter and changed 2-argument default. A new coverage parameter (values complete / sparse) follows assignment in the signature: (tile, resolution, [assignment], [coverage]). The 2-argument form (tile, resolution) now defaults to assignment='centroid', coverage='complete' — previously it behaved like covering. To restore the pre-release default, pass assignment='covering' explicitly.

CRS-handling fix for BNG raster-to-grid

  • A CRS-less raster fed to gbx_rst_bng_rastertogrid* is no longer automatically warped to EPSG:27700. Previously, BNG raster-to-grid silently treated a tile with no declared CRS as if it were in EPSG:4326 and reprojected it to EPSG:27700 before aggregating — a different default than the H3 and quadbin variants, which pass a CRS-less tile through as grid-native. BNG now matches: a CRS-less tile is treated as already in the grid's native coordinate system (EPSG:27700 for BNG) and is not warped. This only affects tiles that carry no CRS metadata; tiles with an explicit CRS continue to be reprojected as before.

Custom-grid raster operations (new)

  • User-defined custom grids now support the full raster-to-grid surface. The eight raster-to-grid reducers, tessellate generator, and rasterize aggregator that were previously available for H3, quadbin, and BNG are now also available for user-defined custom grids: gbx_rst_custom_rastertogrid{avg,count,max,min,median,sum,variance,stddev}, gbx_rst_custom_tessellate, and gbx_rst_custom_rasterize_agg. Use these when your workflow requires a national, project-specific, or other custom projected grid rather than one of the discrete global grid systems. The coverage and assignment parameters introduced for the H3/quadbin/BNG variants are available here too, with the same defaults (coverage='complete', assignment='centroid'). The signatures extend the H3/quadbin/BNG form (tile, resolution, [coverage], [assignment]) by inserting a grid_struct as the second argument: (tile, grid_struct, resolution, [coverage], [assignment]), where grid_struct is produced by gbx_custom_grid. Available in both the heavyweight and lightweight tiers. See Raster Functions.

Cross-raster combine and alignment (new)

  • New gbx_rst_combine{min,max,median,sum,stddev,count} — pixel-wise reductions over raster stacks. Six new functions extend the existing gbx_rst_combineavg with additional pixel-wise reductions over an ARRAY<tile>: per-pixel minimum, maximum, median, sum, population standard deviation, and pixel count. Pass collect_list(tile) for a streamed group-by, or array(t1, t2, ...) for a fixed list. Each function operates band-by-band, excludes NoData cells from the reduction (all-NoData pixels yield NoData in the output), and requires the input tiles to share the same grid (same CRS, extent, and pixel dimensions — use gbx_rst_align_to to satisfy this precondition). Available on both the heavyweight and lightweight tiers. See Raster Functions § Multi-raster operations.

  • New gbx_rst_align_to — snap a tile to a reference grid. gbx_rst_align_to(tile, reference_tile) resamples tile to match the CRS, extent, and pixel grid of reference_tile using nearest-neighbour resampling, so the two tiles land on exactly the same grid without fabricating interpolated values. The primary use case is preparing inputs before combining them with gbx_rst_combine*. Available on both the heavyweight and lightweight tiers. See Raster Functions.

NetCDF reader — multi-dimension handling (new)

  • The lightweight netcdf_gbx raster reader gains dimIndex, fanout, and bandDim options for CF variables with extra (non-spatial) dimensions such as time and level.

    • dimIndex pins one or more leading dimensions to a specific index — .option("dimIndex", "time=2,level=1") — reading a single slice. Absent dimensions fall back to index 0 (with a warning when the dimension has more than one slice); an out-of-range index raises ValueError.
    • fanout expands one or more dimensions across all their indices, emitting one (source, tile) row per index combination — .option("fanout", "time,level"). It composes with dimIndex (fanned dimensions expand; the rest stay pinned).
    • bandDim stacks one dimension's slices as bands in a single multi-band tile — .option("bandDim", "time") turns a (time=3, lat, lon) variable into one 3-band tile; an estimated decode over ~256 MiB warns and suggests fanout instead.

    A dimension may appear in only one of the three (conflicts raise ValueError). A variable that reaches the reader with an unrecognized extra dimension now emits a per-variable UserWarning (previously a hard ValueError), so a file mixing such variables still reads. See NetCDF Reader.

Grid-native cell-space fill (new)

  • New gbx_{h3,quadbin,bng,custom}_cellfill — gap-filling over a grid-indexed table. Four new grouped aggregators — gbx_h3_cellfill, gbx_quadbin_cellfill, gbx_bng_cellfill, and gbx_custom_cellfill (one per grid system) — fill cells whose value is NULL by interpolating from neighbouring cells in the same group. Call as a GROUP BY aggregate: gbx_h3_cellfill(cellid, value[, k[, method[, power]]]). The optional k parameter (default 1) controls the neighbourhood radius in k-rings; method selects the interpolation strategy — 'mean' (simple unweighted average of valid neighbours, default) or 'idw' (inverse-distance-weighted average); power (default 2.0) is the IDW distance exponent. Each function returns ARRAY<STRUCT<cellid, value DOUBLE>>, where filled and original cells are both present. Useful for smoothing patchy raster-to-grid output, imputing sparse sensor grids, or filling NoData-derived nulls in any grid-indexed table — no coordinate projection is needed, all interpolation happens in cell space. BNG cell IDs are STRING; all others are BIGINT. Available in both the heavyweight and lightweight tiers. See GridX Functions.

Grid operation parity (new)

  • Quadbin and custom grids now have kloop and distance functions, completing parity across all four grid families. gbx_quadbin_kloop(cellid, k) returns the hollow ring of quadbin cells at exactly Chebyshev distance k from the input cell (the complement of the filled gbx_quadbin_kring) — matching the kloop functions already available for H3 and BNG. gbx_custom_kloop(cellid, grid_struct, k) provides the same hollow-ring operation for user-defined custom grids. gbx_custom_distance(cellid1, grid_struct, cellid2) returns the Chebyshev grid distance between two custom-grid cells, closing a gap that existed since the custom grid was introduced in v0.4.0. Available in both the heavyweight and lightweight tiers. See GridX Functions.

Geometry-aware kring/kloop across all grid families (new)

  • BNG gbx_bng_geomkring / gbx_bng_geomkloop (and their *explode variants) gain an optional mode parameter. The new mode argument controls how cells that touch the geometry boundary are classified. The default is 'boundary-out' (outward band around the geometry; see the mode table below), so existing three-argument calls keep working; mode defaults to 'boundary-out' when omitted. Available in both the heavyweight and lightweight tiers.

    The six mode values are:

    ModeBehaviour
    'boundary-out'Default. The geometry's boundary band (k=0) plus the outward band — cells within k steps outside the boundary. The geometry's fully-interior cells are not in the result. Under the default coverage='coveras' (and 'core') k=0 is the whole straddling band; under 'polyfill' it is the centroid-outside cells (see the coverage note).
    'boundary-in'Inward setback band — cells within k steps inside the outer boundary, stopping at holes.
    'boundary-in-ignore-holes'Like 'boundary-in' but the polygon is treated as solid (holes are not subtracted), so the band can cross a hole.
    'hole-in'Fills each hole inward from its edge, up to k steps.
    'hole-out'Band in the solid around each hole, up to k steps, bounded by the outer geometry.
    'hole-out-ignore-geom'Band outward from each hole, up to k steps, not bounded by the outer geometry.
  • New gbx_quadbin_geomkring, gbx_quadbin_geomkloop, gbx_quadbin_geomkringexplode, gbx_quadbin_geomkloopexplode — geometry-aware expansion for quadbin. These four functions follow the same contract as the BNG variants: polyfill a WGS84 geometry into quadbin cells at a given zoom level, then expand by k dilation steps using the optional mode parameter. Cell IDs are BIGINT (quadbin format). Available in both the heavyweight and lightweight tiers. See GridX Functions.

  • New gbx_custom_geomkring, gbx_custom_geomkloop, gbx_custom_geomkringexplode, gbx_custom_geomkloopexplode — geometry-aware expansion for custom grids. Same contract as the quadbin and BNG variants, extended with a grid struct argument (from gbx_custom_grid) that identifies the custom coordinate system. The geometry must be in the grid's native CRS. Cell IDs are BIGINT. Available in both the heavyweight and lightweight tiers. See GridX Functions.

  • New gbx_h3_geomkring, gbx_h3_geomkloop, gbx_h3_geomkringexplode, gbx_h3_geomkloopexplode — geometry-aware expansion for H3 (light-only). These four functions are lightweight-tier only — there is no heavy (Scala) implementation. Like the other grids, they take a geometry directly and do the whole job in pure Python via the h3 library — h3.polygon_to_cells_experimental for the covering set (holes handled natively) and grid_disk for the neighbour walk — so gbx_h3_geomkring(geom, resolution, k, [mode]) is a single function callable identically from SQL and PySpark (it runs on Serverless, classic clusters, and locally). Use the Python column API for the most natural experience:

    from databricks.labs.gbx.gridx.h3.functions import geomkring
    df.withColumn("kring", geomkring("geom_col", resolution=12, k=1))

    All six dilation modes work, including the hole-mode variants ('hole-in', 'hole-out', 'hole-out-ignore-geom'). Geometry input is WKB BINARY or WKT STRING. Cell IDs are BIGINT (H3 format). See GridX Functions.

  • Point and line geometries are supported, not just polygons. geomkring/geomkloop accept POINT/LINESTRING (and their MULTI* forms) — a point covers its containing cell, a line covers the cells it crosses, and boundary-out expands outward from there. (Under the default coverage='coveras'; 'polyfill'/'core' return empty for 0-/1-dimensional inputs — see the coverage note below.)

  • Boundary/hole behavior. boundary-* modes operate on the geometry's outer boundary only — polygon holes are handled exclusively by the hole-* modes. boundary-out returns the boundary band at k=0 plus the outward band, excluding the fully-interior cells (its k=0 follows the coverage: coveras/core give the whole straddling band, polyfill the centroid-outside cells; the outward k≥1 rings are identical across coverages). All modes are robust to grid-aligned geometries (edges lying exactly on cell boundaries), and the heavyweight and lightweight tiers produce identical cell sets across every mode × coverage combination.

Geometry-aware kring/kloop — new coverage parameter

  • All gbx_{h3,bng,quadbin,custom}_geomkring/geomkloop/*explode functions gain an optional trailing coverage parameter (after mode), controlling how a cell is judged to belong to a region:

    coverageMeaning
    'coveras'Default. A cell belongs if it overlaps the region — inclusive, so boundary cells are kept (no false negatives).
    'polyfill'A cell belongs if its centre is inside the region (standard polyfill).
    'core'A cell belongs only if it is entirely inside the region (strictest; no false positives).

    The nesting is coveras ⊇ polyfill ⊇ core; the default 'coveras' is the most inclusive (boundary cells kept, no false negatives). Point/line geometries are covered only under 'coveras' (a 0-/1-dimensional geometry has no cell whose centre is inside it or that fully contains it, so 'polyfill' and 'core' return empty for them). Available in both tiers; heavy and light agree across all coverage × mode combinations.

Covering assignment performance improvement

  • Covering assignment is now computed over cell boundaries only. The covering assignment mode (available on all gbx_rst_{h3,quadbin,bng,custom}_rastertogrid* and *_tessellate functions) previously computed area fractions by evaluating overlap for every pixel in the raster extent. It now uses a boundary-driven algorithm that processes only cells that intersect the raster perimeter, reducing computation proportionally to the boundary-to-interior ratio. For rasters with large solid interiors — where most cells are fully covered — this is significantly faster. Results are numerically identical; this is a performance improvement only.

Dependency pins refreshed (v0.5.0 artifacts regenerated)

  • After the initial v0.5.0 release the light-tier extras' dependency pins were refreshed, and the v0.5.0 release artifacts were regenerated against that update. The runtime-pinned light_* extras (light_env5/light_env6, light_dbr17light_dbr19, and their _all variants) pin their transitive dependencies to the versions already present in the matching Databricks base, so an _all install on Serverless env5/env6 changes no preinstalled packages (%pip output stays quiet); the source-specific downloaders [earthdata] (NASA Earthdata / EMIT) and [overture] (Overture Maps) are opt-in extras outside _all. The v0.5.0 assets — lightweight wheel, heavyweight JAR, docs bundle, and the pre-built GDAL native tarball — were regenerated to carry these pins, and v0.5.1 carries them forward. See Installation.

Reader and operator fixes

  • raster_gbx: splitStrategy / sizeInMB now bound tile size on the default virtual-tiles path. On the lightweight raster_gbx reader these budget options were silently ignored while virtualTiles stayed at its default "true" — a large raster came through as a single whole-image tile, with no split and no warning, and then out-of-memoried the first pixel-touching operation (rst_sample, aggregations). The decoded-byte budget now bounds each virtual tile the same way it already bounded materialized tiles, so a raster whose decoded footprint exceeds the budget is split into multiple virtual window tiles regardless of virtualTiles. splitStrategy/sizeInMB now behave identically on both paths. See Raster Readers.
  • gbx_rst_updatetype (heavyweight) now selects a compression predictor that matches the output datatype. Converting a Float raster to Byte failed because the TIFF predictor was derived from the input datatype — PREDICTOR=3 (Float32/Float64-only) — while the output was Byte, so gdal_translate rejected the creation options (PREDICTOR=3 is only supported with Float32 or Float64). When a translate/calc command sets an explicit output type (-ot / --type), the predictor is now chosen from that type (1 for Byte/Int8, 3 for Float32/Float64, 2 otherwise). The lightweight rst_updatetype was already correct.

What's new in v0.5.0

Introduces virtual tiles — a bytes-free way to read and process huge rasters without out-of-memory — unifies both execution tiers on one tile struct, and adds the COG preparation lane (file_gbx + cog_gbx) with VRT mosaics for large-raster workflows, tapping the new Databricks FILE type for governed, memory-safe file access where the runtime provides it, on top of v0.4.3.

Packaging

  • Packaging: [light] and [light_all] are removed in 0.5.0 (breaking change, no alias). Every light-tier install now picks an explicit, runtime-pinned extra. On Serverless: install geobrix[light_env6] (environment v6, recommended) or geobrix[light_env5] (env 5). On classic clusters: install geobrix[light_dbr17], geobrix[light_dbr18], or geobrix[light_dbr19] to match your runtime. Full-feature-set variants (_all) are available for each extra. See Installation.
  • Light-tier dependencies are now pinned to the Databricks base. All runtime-pinned light extras (light_env5, light_env6, light_dbr17light_dbr19) pin their transitive dependencies to the versions already present in the corresponding Databricks base environment. Installing GeoBrix Light no longer unexpectedly upgrades preinstalled packages, and %pip install output is quieter.
  • earthaccess and the EMIT downloader moved from [stac] to the new [earthdata] extra (breaking change). Installing geobrix[...,stac] no longer pulls in earthaccess. To use EmitDownloader or any NASA Earthdata / LP DAAC client, add the [earthdata] extra — e.g. geobrix[light_env6,earthdata]. The [earthdata] extra is not included in _all bundles because earthaccess updates fsspec, which can conflict with cluster preinstalled packages.
  • [overture] is now an opt-in extra, excluded from _all. The Overture Maps downloader (overturemaps>=1.0) ships behind the [overture] extra and is no longer included in _all bundles. Like [earthdata], it changes a preinstalled base package on install — overturemaps 1.x bumps click — so it is opt-in. Add it explicitly when you need it: geobrix[light_env6,overture]. _all is now [light_envN/_dbrN] + [stac] + [vizx] — generic, quiet features only. Source-specific downloaders ([earthdata], [overture], and any future additions) follow this opt-in rule.

Virtual tiles & tile struct

  • Virtual tiles — bytes-free windowed reads for large rasters. The lightweight cog_gbx / raster_gbx / gtiff_gbx readers can emit virtual tiles: each row carries a source path + pixel window instead of the raster bytes, and pixels are read lazily, one window at a time, only when an operation needs them. A virtual-tile row is ~100 bytes versus 148–527 KB of materialized bytes (~1,400–5,000× smaller), so fanning a multi-gigabyte raster into many tiles no longer accumulates into a Serverless out-of-memory failure. Opt in with .option("virtualTiles", "true"). See the new Virtual Tiles page.
  • Light raster readers now default to virtual tiles (breaking behavior change). The lightweight raster readers (raster_gbx, gtiff_gbx, cog_gbx) previously materialized tile bytes by default; they now emit virtual tiles by default. Code that reads a raster and immediately operates on tile.raster bytes — or passes the tile to a heavyweight function — will see null bytes until the tile is materialized. Pass .option("virtualTiles", "false") to restore the old behavior, or call a downstream rst_* function with materialize=True.
  • The Tile Structure output shape widens from the 3-field struct to the v2 8-field struct. The tile struct gains five fields — path, window, clip_polygon, clip_crs, crs — alongside cellid, raster, and metadata, and raster is now nullable (null on a virtual tile). The same 8-field struct is produced and consumed by both tiers, so consumers that assumed the 3-field shape need updating. On a materialized tile the new fields are provenance (what was applied to the bytes); on a virtual tile they are instructions (applied on read).
  • Every lightweight rst_* function is virtual-tile-aware. Functions consume virtual or materialized tiles through one shared open path: metadata accessors answer from the header without reading pixels, reference/passthrough ops (rst_clip, rst_setsrid, identity rst_transform) stay virtual, and pixel-producing ops materialize only the window they need. Tile-returning functions gain three optional force-output params — virtualize_dir, virtualize_prefix, materialize — so you can flip a result back to a bytes-free virtual row (or force bytes) at any step. See Virtual↔materialized advice.
  • Both tiers accept v1 and v2 tiles and always emit v2. The heavyweight (rasterx) and lightweight (pyrx) tiers both read the legacy 3-field tile and the new 8-field tile, and every function emits the v2 struct — so output composes directly across tiers. Heavyweight operates only on materialized (binary) tiles as of now, so a virtual tile passed to a heavyweight function raises a clear materialize-first error (materialize=True, or write it out and read it back). The lightweight tier is for both light (virtual) and materialized tiles. See Virtual tiles and the light→heavy bridge.

COG preparation lane

  • New file_gbx reader — path lister. Lists files in a directory as path-reference rows (path, name, extension, size, modificationTime), without loading any raster content. extension is lowercase, no leading dot, and NULL for files without an extension. Options: filterRegex, recursiveFileLookup. This is the entry point for the COG preparation pipeline. See File Lister.
  • New cog_gbx writer — master-COG preparation. Takes path-reference rows from file_gbx and converts each source file to a spec-valid Cloud-Optimized GeoTIFF using GDAL's driver="COG" creation path. Each output COG carries internal tiling and pre-built overview levels. Options: cogBlockSize (default 512), cogOverviewResampling (default AVERAGE), cogCompression (default DEFLATE), plus driverMode for driver-orchestrated large-file preparation. Output files pass rio_cogeo.cogeo.cog_validate. See COG Writer and Large Rasters.
  • New cog_gbx reader — COG-aware windowed read. Reads Cloud-Optimized GeoTIFFs into the shared tile schema, issuing range-reads that fetch only the bytes a window needs. The reader windowing surface is tileSize (regular grid) + overlapPercent, arbitrary clipPolygons (emit only the tiles intersecting each polygon, with clipCrs), or explicit pixel windows — lists are passed as JSON strings over .option(). See COG Reader and Readers overview.
  • Raster reader splitStrategy defaults to none. The raster_gbx, gtiff_gbx, and cog_gbx readers default to one whole-image tile per file; splitting is opt-in via .option("splitStrategy", "serverless") / "classic", and the sizeInMB power-user override remains available. For large rasters the recommended pattern is prepare-then-read: list with file_gbx, prepare master COGs once with the cog_gbx writer, then read any AOI window cheaply with virtual tiles. See Large Rasters.
  • VRT mosaics — tile a large source into bounded mini-COGs + a portable index. The cog_gbx writer's vrtMosaic mode splits each source into bounded mini-COGs — read window-by-window, so the source is never fully held in RAM — and writes a lightweight, portable mosaic.vrt index over them; each mini-COG is small by construction, so a source too big to hold as one COG (or in per-task memory) is processed safely on Serverless. Point raster_gbx (or cog_gbx) at a mosaic.vrt and the reader expands it into one virtual tile per member, so every rst_* function processes the mosaic per-tile; clipPolygons restricts the expansion to only the members intersecting an area of interest. mint_vrt builds a transient index over an ad-hoc tile list for on-demand windowed reads, and mosaic.vrt opens in any GDAL tool (QGIS, gdalinfo, rio-tiler) with no GeoBrix needed. Native pixel tiling (gridSystem="none") with tileSize / overlapPercent; quadbin cell-aligned tiling (gridSystem="quadbin" + gridResolution) reprojects to EPSG:3857 and tags each cell with GBX_CELLID, surfacing in tile.metadata["cellid"] after expansion; VRT is optional (writeVrt="false" writes tiles only). H3 grid-aligned mode (gridSystem="h3") reprojects to EPSG:4326, clips each cell to its hexagon, and tags every member with GBX_CELLID (h3index), surfacing in tile.metadata["cellid"] for equi-join unification with h3-indexed tabular data. BNG grid-aligned mode (gridSystem="bng") reprojects to EPSG:27700 and tags each cell with GBX_CELLID (BNG string id), surfacing in tile.metadata["cellid"] for equi-join unification with BNG-indexed data (Great Britain only). See the VRT & Mosaics page.

CRS & coordinate systems

  • VectorX CRS functions (gbx_st_crs, gbx_st_setcrs, gbx_st_transformcrs) — both tiers. Three new functions complement the Databricks built-ins st_srid / st_setsrid / st_transform with authority-string CRS handling: gbx_st_crs(geom) returns the geometry's CRS as a canonical authority string (e.g. EPSG:4326, ESRI:54008), or NULL for a geometry with no SRID. gbx_st_setcrs(geom, crs) stamps a CRS onto a geometry as a label-only change — coordinates are not moved; it raises on an authority-less CRS (raw WKT or PROJ4) because a geometry can only carry an integer SRID. gbx_st_transformcrs(geom, target_crs[, source_crs]) reprojects coordinates; an authority-coded target (EPSG:n / ESRI:n) stamps n on the result, while an authority-less target reprojects and clears the stale SRID; an optional third argument supplies a source CRS for a geometry that carries no SRID. All three accept WKB/EWKB/WKT/EWKT input; the SQL surface returns BINARY (gbx_st_crs returns STRING). Available on both the heavyweight (vectorx) and lightweight (pyvx) tiers. See VectorX Functions.
  • RasterX CRS functions (gbx_rst_crs, gbx_rst_setcrs, gbx_rst_transformcrs) — both tiers. The raster counterparts of the same idea: they take and return CRS strings, so a non-EPSG coordinate system survives a round trip that the integer-SRID functions (gbx_rst_srid / gbx_rst_setsrid / gbx_rst_transform) cannot represent. gbx_rst_crs(tile) returns the tile's CRS as an authority string (EPSG:4326, EPSG:32618) when it carries one, and its full WKT when it does not — so it always returns a value where gbx_rst_srid gives 0 for a CRS with no EPSG code. gbx_rst_setcrs(tile, crs) relabels the tile's spatial reference without warping pixels — the georeference (upperLeftX, scaleX, …) and the pixel dimensions are unchanged, which is what you want when a file arrived with missing or wrong CRS metadata. gbx_rst_transformcrs(tile, target_crs) genuinely reprojects: it resamples the pixel grid, so both the georeference and the raster dimensions change (a 64×64 EPSG:4326 tile becomes 55×72 in EPSG:3857). Both writers accept an authority code (EPSG:3857, ESRI:54008), WKT, or PROJ4, and an int-castable string ('4326') is treated as an EPSG code. Available on both the heavyweight (rasterx) and lightweight (pyrx) tiers. See Raster Functions.
  • Custom PROJ grid-shift registration (gbx.register_proj_grids). Stage your NTv2, NADCON, or PROJ geoid grid files to a Unity Catalog Volume, call gbx.register_proj_grids(spark, "/Volumes/<catalog>/<schema>/proj-grids") once at session start, and every lightweight-tier CRS transform finds them automatically on every worker — resolving the silent accuracy loss that occurs when PROJ cannot locate a datum grid. Volume-hosted grids are a lightweight-tier capability; on the heavyweight tier, stage grids to a cluster-local path instead. See Registering custom grid files.

VectorX geometry operations

  • VectorX antimeridian functions (gbx_st_shiftlongitude, gbx_st_wrapx, gbx_st_split) — lightweight tier. Three "distributed shapely" geometry functions that reproduce the PostGIS antimeridian-normalization pattern for lon/lat data crossing the ±180° meridian. gbx_st_shiftlongitude(geom) shifts longitudes from [-180, 180] into [0, 360]; gbx_st_wrapx(geom, wrap_x_origin, wrap_direction) wraps X coordinates back across a meridian; gbx_st_split(geom, blade_geom) splits a geometry by a blade, returning a GEOMETRYCOLLECTION. Composed with the Databricks built-in ST_Dump, they turn a polygon straddling the antimeridian into a clean multi-part geometry. All three accept WKB/EWKB/WKT/EWKT and return BINARY, preserving the input CRS. Available on the lightweight (pyvx) tier. See Antimeridian.
  • VectorX geometry validity functions (gbx_st_makevalid, gbx_st_explainvalidity) — lightweight tier. Two "distributed shapely" geometry functions that fill the diagnosis and repair gap in the product's native ST_IsValid. gbx_st_makevalid(geom[, level]) repairs a geometry to OGC-SFS validity using GEOS make_valid; the default linework level also applies orient_polygons so output passes the product's stricter ST_IsValid (which enforces ring orientation in addition to OGC topology); pass level='ogc' for OGC-only repair without orientation normalization. gbx_st_explainvalidity(geom) returns a JSON string {valid, reason, code, location} diagnosing the SFS violation class, a stable reason code for GROUP BY analysis, and the coordinate where the violation occurs. Both accept WKB/EWKB/WKT/EWKT; gbx_st_makevalid preserves the input CRS. Available on the lightweight (pyvx) tier. See Geometry Validity.
  • VectorX geometry cleaning functions (gbx_st_simplifypreservetopology, gbx_st_removerepeatedpoints, gbx_st_reduceprecision, gbx_st_node, gbx_st_snap) — lightweight tier. Five "distributed shapely" functions for cleaning and normalizing already-valid geometry. gbx_st_simplifypreservetopology(geom, tolerance) applies the Douglas-Peucker algorithm with topology preservation — no ring collapsing, no polygon splits — the topology-safe counterpart to the product's st_simplify. gbx_st_removerepeatedpoints(geom[, tolerance]) removes consecutive duplicate (or within-tolerance near-duplicate) vertices. gbx_st_reduceprecision(geom, grid_size) snaps all coordinates to a precision grid (PostGIS ST_SnapToGrid equivalent), using GEOS set_precision(mode="valid_output") so the result stays valid. gbx_st_node(geom) nodes linework at all self- and pairwise intersections — a prerequisite for polygonization or topologically clean network analysis. gbx_st_snap(geom, reference, tolerance) moves vertices of geom onto reference where within tolerance, aligning features that should share boundaries. All five accept WKB/EWKB/WKT/EWKT and return BINARY. Available on the lightweight (pyvx) tier. See Geometry Cleaning.
  • VectorX coverage validity functions (gbx_st_coverageisvalid, gbx_st_coverageinvalidedges, coverage_simplify) — lightweight tier. Coverage validity addresses a class of spatial defect that single-geometry validity cannot catch: overlaps and gaps between polygons that are each individually valid. gbx_st_coverageisvalid(geom, gap_width) is a SQL grouped-aggregate that returns true when the polygon group has no overlaps and no gaps narrower than gap_width — use it with GROUP BY to identify which coverage groups pass. gbx_st_coverageinvalidedges(geom, gap_width) aggregates the group and returns the boundary segments that violate the coverage as BINARY (WKB/EWKB), useful for spatial debugging and highlighting problem zones on a map; empty geometry when the coverage is clean. coverage_simplify(df, group_col, geom_col, tolerance) is a Python-API helper that simplifies the whole coverage as a unit — shared edges between adjacent polygons are preserved exactly in both simplified polygons (topology-preserving; N rows in → N rows out). All three accept WKB/EWKB/WKT/EWKT for geometry inputs. Available on the lightweight (pyvx) tier. See Coverage Validity.

What's new in v0.4.3

Completes both-tier parity for the raster-grid family, adds heavyweight NetCDF readers and a lightweight NetCDF writer, and improves the documentation — on top of v0.4.2.

  • Raster BNG and quadbin grid functions are now both-tier. The nine raster-grid functions added for quadbin and BNG in v0.4.0 — five BNG reducers (gbx_rst_bng_rastertogrid{avg,count,max,min,median}), two tessellate generators (gbx_rst_quadbin_tessellate, gbx_rst_bng_tessellate), and two rasterize aggregators (gbx_rst_quadbin_rasterize_agg, gbx_rst_bng_rasterize_agg) — now run on the lightweight pyrx tier too, matching the heavyweight tier on cell set, clip windows, and measures. Every RasterX function is now available in both tiers. The lightweight BNG reducers use a vectorized NumPy encoder that runs ~3.7–4.8× the heavyweight per-tile speed. See Raster Functions and Choosing an Execution Tier.
  • New raster-grid reducers: sum, variance, and stddev (all three grids, both tiers). gbx_rst_{h3,quadbin,bng}_rastertogrid{sum,variance,stddev} extend the rastertogrid reducer family (avg/count/max/min/median). variance and stddev are population statistics (÷ n) from a numerically-stable two-pass algorithm; cross-tier measures match within tolerance. See Raster Functions.
  • Heavyweight NetCDF readers netcdf_gdal (raster) and netcdf_ogr (DSG vector). Two GDAL/OGR-backed readers join the lightweight netcdf_gbx. netcdf_gdal reads CF grid variables into the shared (source, tile) schema (one row per grid variable); netcdf_ogr reads CF Discrete Sampling Geometry features into the shared vector schema. Both are read-only and decode CF scale_factor/add_offset to physical values, so the tiers agree within floating-point tolerance. netcdf_gdal targets regular/projected grids; flattening a swath to per-cell points remains a lightweight netcdf_gbx vector-mode capability. See NetCDF Reader.
  • Lightweight netcdf_gbx writer (CF raster grids and CF-DSG points). df.write.format("netcdf_gbx").save(path) writes CF-compliant .nc files — grid files in the default raster mode, or CF Discrete Sampling Geometry point files with .option("mode","vector"). .option("singleFile","true") consolidates sharded parts into one file (raster mode merges distinct variables that share one grid), and .option("merge","true") merges .nc files already present in the output directory; keepParts / fileName / partPrefix tune the output. Round-trips physical values, CRS, and NoData; JAR-free. See NetCDF Writer.
  • NetCDF reader behavior changes. (1) variable / variables is now an optional filter across all three readers — a bare load returns all readable variables (previously netcdf_gbx raised). (2) netcdf_gdal now maps a scaled variable's _FillValue to NaN on read, so masked cells no longer decode as spurious values, matching netcdf_gbx. See NetCDF Reader.
  • Heavy XYZ tiles now emit RGBA to match the lightweight tier (behavior change). gbx_rst_tilexyz and gbx_rst_xyzpyramid on the heavyweight tier now produce display tiles (PNG / WebP → RGBA, JPEG → RGB) with an alpha channel from the valid-data mask, so internal NoData renders transparent instead of opaque black — the two tiers now agree. This changes the heavy output band count (e.g. a single-band source yields a 4-band RGBA PNG); consumers that read those bytes as a raw single-band raster are affected. See Raster Functions.
  • Documentation: RasterX as distributed rasterio, section landing pages, and the PMTiles reader. A new Rasterio Distributed page positions the RasterX lightweight tier as distributed rasterio + a best-of-breed raster stack; each top-level docs section gained a single shareable landing page; the lightweight pmtiles_gbx reader is now documented at PMTiles Reader; and the docs UI shows a color cue when the heavyweight tier tab is selected.

What's new in v0.4.2

Adds the Genie Map example app, plus correctness and consistency fixes, on top of v0.4.1.

  • Genie Map — interactive methane-map Databricks App. A new example app that turns the GeoBrix-processed Permian methane gold data (the Vapor-Eyes geospatial_docs.vapor_eyes_lf schema) into an interactive map with two ways to explore the same data side by side: move the map (every pan/zoom runs parameterized viewport SQL and redraws H3 hotspot / well-density hexagons, well points, and EMIT plume points), and ask a question (a natural-language prompt routes to a curated Genie Space — a geometry answer lands as a new map layer, and a feature-level answer becomes a chart whose selection cross-filters the map, and vice-versa). Client is React + kepler.gl; server is Databricks AppKit. Grew out of the Data + AI Summit 2026 session Scaling Geospatial Analytics at S&P Global Energy: From Billions of Points to AI-Powered Map Agents with Databricks by Hubert Boguski (S&P Global) and Michael Johns (Databricks). See Genie Map.
  • Raster value reducers return NULL for all-nodata bands (behavior change). gbx_rst_max, gbx_rst_min, gbx_rst_avg, and gbx_rst_median now return SQL NULL for a band with zero valid pixels — for example an H3 covering-tessellation cell that clips only NoData — on both the lightweight and heavyweight tiers. Previously the lightweight tier returned NaN and the heavyweight tier returned 0.0; neither was catchable or aggregation-safe. NaN silently passed WHERE measure IS NOT NULL and could overwrite a real value in MAX() during a GROUP BY seam reconciliation (NaN sorts greater than everything); 0.0 was indistinguishable from a genuine zero. The new NULL is catchable via WHERE measure IS NULL and ignored by aggregates like MAX/MIN/AVG. gbx_rst_pixelcount is unchanged — an all-nodata band still returns 0 (a count of zero is meaningful). Relatedly, the lightweight gbx_rst_isempty is now all-nodata-aware: a dimensionally-valid raster whose every band is entirely NoData now returns true, matching the heavyweight tier. See Raster Functions.

What's new in v0.4.1

Adds the satellite-data readers and clients for atmospheric/methane workflows and a full worked example, on top of the v0.4.0 lightweight tier.

  • netcdf_gbx reader. A net-new lightweight vector DataSource that transcodes a netCDF-4 swath (e.g. a Sentinel-5P TROPOMI L2 granule) directly to one point per ground pixel — no regridding, with science/quality variables (e.g. qa_value) passed through untouched — so H3 binning or quality filtering runs on the native per-pixel measurements. Registered via databricks.labs.gbx.ds. See netCDF Reader.
  • Earthdata client (databricks.labs.gbx.earthdata). A NASA Earthdata Login token client for authenticated access to LP DAAC products (used by the EMIT downloader). See the EMIT Downloader.
  • Methane-source sample downloaders (databricks.labs.gbx.sample). Three new AOI-driven, Serverless-safe stagers alongside the v0.4.0 Overture/NAIP/3DEP helpers, sharing the same discover → download → read shape: TropomiDownloader (Sentinel-5P TROPOMI L2 CH₄, via Planetary Computer), EmitDownloader (EMIT L2B CH₄ enhancement + plume-complex products from NASA LP DAAC, Earthdata-authenticated), and WellsDownloader (Texas Railroad Commission WellSHL surface-hole locations). See the Sentinel-5P TROPOMI, EMIT, and TX RRC Wells downloader pages.
  • Vapor-Eyes — Permian methane monitoring example. A new end-to-end example in two flavors. An interactive five-notebook detection cascade — screen with Sentinel-5P, detect at the strongest hotspot with Sentinel-2 SWIR, quantify with EMIT, attribute to Texas Railroad Commission operator well pads, and synthesize a shareable PMTiles portfolio. And a production Lakeflow Declarative Pipeline + AI/BI dashboard — a bi-temporal bronze→silver→gold medallion with SCD2 wells and as-of operator attribution, a current (through 2026) Carbon Mapper rated-plume layer, a defensible "leakiest operators" leaderboard (ranked by detection count + mean/max emission rate, not summed flow), a four-page native-geometry map dashboard (H3 hotspot and play/county rollup choropleths plus a Carbon Mapper point map), Permian context geometries (EIA shale plays + Census TIGER counties), and a sharded PMTiles portfolio for a custom app. Both flavors run on the lightweight tier over Serverless. See Vapor-Eyes.

What's new in v0.4.0

Per-version highlights; full migration tables are in the per-component sections below.

  • Lightweight execution tier (pyrx, pygx, pyvx). A pure-Python implementation of the GeoBrix API that needs no JAR and no init script, and runs on serverless compute, standard (shared) clusters, Lakeflow declarative pipelines, and ARM. It keeps the same function names and the same gbx_* SQL after register, so switching tiers is a one-line import change. RasterX (pyrx, on rasterio) implements every rst_* function; GridX (pygx) covers quadbin, BNG, and custom grids; VectorX (pyvx) covers MVT, TIN surface modeling, and legacy-geometry migration. With this release GridX and VectorX are fully both-tier — the lightweight tier reaches 1:1 parity with the heavyweight one across all three packages. See Choosing an Execution Tier.
    • Serverless support is verified and documented. geobrix[light] installs and runs on Databricks Serverless (environment v5), standard (shared) clusters, and ARM. Install with the quoted PEP 508 named form — %pip install "geobrix[light] @ file:///Volumes/.../geobrix-0.4.0-py3-none-any.whl" — not the path-with-extra form ('…whl[light]'), which fails on Serverless because %pip writes the surrounding quotes into the requirement and pip reads [light] as part of the filename. mapbox-vector-tile is pinned to 2.1.x so its protobuf dependency stays <6 (Spark Connect compatibility on Serverless), and idna is pinned <3.8 to avoid a core-package-change notice. See Installation.
    • Geometry inputs accept WKB, EWKB, WKT, and EWKT consistently. Every geometry-accepting function in both tiers now decodes all four encodings through a single shared decoder. Previously some lightweight functions accepted only WKB.
    • Geometry×raster operations align to the raster CRS and handle non-overlap gracefully. gbx_rst_clip, gbx_rst_sample, and gbx_rst_viewshed reproject the input geometry from its SRID to the raster's CRS (matching the heavyweight GDAL behavior), so a geometry in a different CRS clips/samples the correct region. A geometry that does not overlap the raster now returns null / empty instead of raising an error.
  • Raster reader default changed to no-split (sizeInMB = -1, behavior change since v0.3.0). The gdal / gtiff_gdal (heavyweight) and raster_gbx / gtiff_gbx (lightweight) readers now default sizeInMB to -1 — one whole-image tile per file — instead of auto-splitting large rasters at 16 MB. Set a positive sizeInMB to opt back into tiling for parallel processing of large files. See Raster Readers.
  • Lightweight raster writer and source-column parity. The lightweight gtiff_gbx writer accepts the nameCol option for deterministic output filenames, matching the heavyweight GDAL writer. The lightweight raster reader's source column is now dbfs:-scheme-qualified to match binaryFile and the heavyweight reader, so DataFrames join cleanly across tiers; lightweight file operations strip the scheme internally.
  • gbx_rst_fromfile is lightweight-tier only — registered into SQL on the heavyweight tier when geobrix[light] is present. On Databricks the executor JVM cannot read a Unity Catalog Volume (/Volumes/...) FUSE path — the UC credential is held only by Spark's user-scoped Python worker — so rst_fromfile is implemented solely in the lightweight tier (a pyrx Python loader) and has no heavyweight Scala expression. With geobrix[light] installed it is callable from Python (rx.rst_fromfile) and from SQL (gbx_rst_fromfile) regardless of tier: the heavyweight package's register(spark) specially registers the SQL name as the Python UDF. Without [light] it is not registered and the Python binding raises with guidance. For a tier-agnostic path on any compute, read the bytes with spark.read.format("binaryFile") and build the tile with gbx_rst_fromcontent. See Raster Functions § Constructors.
  • Vector tile encoding (gbx_st_asmvt). First VectorX expression-level function — aggregates features into MVT protobuf bytes for slippy-map publishing. See VectorX § Vector tile output.
  • Vector tile pyramid (gbx_st_asmvt_pyramid). Generator function: emits one row per (z, x, y) tile that input geometries intersect, encoded as MVT bytes. Composes with gbx_pmtiles_agg for end-to-end vector publishing pipelines. Builds on gbx_st_asmvt and shares the same web-mercator tile math as gbx_rst_xyzpyramid. See VectorX § Vector tile output.
  • Quadbin grid math (10 functions). New gridx/quadbin subpackage adds CARTO quadbin v0 support — gbx_quadbin_pointascell, gbx_quadbin_aswkb, gbx_quadbin_centroid, gbx_quadbin_resolution, gbx_quadbin_polyfill, gbx_quadbin_kring, gbx_quadbin_tessellate, gbx_quadbin_cellunion, gbx_quadbin_cellunion_agg, gbx_quadbin_distance. Cell IDs are 64-bit Long; coordinates are EPSG:4326 lon/lat; output geometry is EWKB SRID=4326. Cell encoding matches the CARTO quadbin-py reference implementation (cross-checked at 5 reference points). See GridX § Quadbin.
  • PMTiles output (gbx_pmtiles_agg UDAF + .write.format("pmtiles") DataSource). Native Scala PMTiles v3 encoder packages raster (PNG/JPG/WebP) or vector (MVT) tile pyramids into a single deployable blob. Aggregator path for tilesets that fit in a Spark cell (~100 MiB tile payload / 2 GiB cell limit); DataSource for larger pyramids streamed to a file via a partitioned commit protocol. Container is content-agnostic — tile bytes pass through verbatim, no GDAL/OGR dependency. Auto-detects tile type from magic bytes (PNG / JPEG / WebP / otherwise MVT). Heavyweight read is not supported; spark.read.format("pmtiles") raises a friendly error pointing at the JS / Python pmtiles clients. The lightweight pmtiles_gbx reader is a supported read path, though — it reads tiles back out of an existing archive (source="archive") or builds a tile mosaic pyramid from rasters (source="raster"); see the PMTiles Reader. The gbx_pmtiles_agg aggregate is available in both the heavyweight and lightweight tiers; the .write.format("pmtiles") DataSource (for larger streamed pyramids) remains heavyweight-only. See PMTiles.
  • Concurrent-safe lightweight writers. The lightweight vector and PMTiles writers now isolate their two-phase staging per write: concurrent jobs — or multiple users — writing to the same output location can no longer see or overwrite one another's in-progress data, and scratch left behind by an interrupted job is reclaimed automatically on a later write to the same location. The PMTiles writer previously staged into a fixed shared directory (a concurrency hazard) and now uses a unique hidden namespace per write. See Writers.
  • Full raster-grid surface for quadbin and BNG (9 functions). The raster-grid API that H3 has had since v0.3.0 is now complete for the other two discrete-global-grid families on the heavy tier. Quadbin adds two new operations: gbx_rst_quadbin_tessellate (one clipped chip per overlapping quadbin cell — a streaming generator) and gbx_rst_quadbin_rasterize_agg (burn per-cell values back into a raster — the inverse of rastertogrid). BNG adds the matching full surface: five reducers (gbx_rst_bng_rastertogrid{avg,count,max,min,median}), a tessellate generator (gbx_rst_bng_tessellate), and a rasterize aggregator (gbx_rst_bng_rasterize_agg). BNG functions automatically reproject the input raster to EPSG:27700 — no upstream rst_transform needed. BNG resolution accepts integer indices ±1..±6 or string keys ("1km", "100m"); cell IDs are STRING. gbx_rst_bng_tessellate in particular enables BNG-scale raster tiling for computer-vision model inference — aligning aerial or satellite imagery to Ordnance Survey 1 km or 100 m grid cells. Closes #49. The rasterize aggregators use −9999.0 as the band-registered NoData sentinel (same as H3). These nine functions shipped heavy-tier first; their lightweight pyrx implementations follow in v0.4.2 (see the v0.4.2 note above), bringing every RasterX function to both tiers. See Raster Functions.
  • Raster→quadbin aggregators (5 functions). gbx_rst_quadbin_rastertogrid{avg,count,max,min,median} extend the H3 aggregation pattern to CARTO quadbin v0 cells. Natural fit for raster heatmaps that render in slippy-map viewers — cells align with the same XYZ pyramid that PMTiles / MVT readers consume. Resolution capped at z=20. See Raster Functions.
  • Web-mercator XYZ tile output (3 functions). gbx_rst_to_webmercator reprojects a raster to EPSG:3857 (default bilinear); gbx_rst_tilexyz(tile, z, x, y, [format, size, resampling]) renders a single XYZ tile to PNG / JPEG / WEBP bytes (returns BinaryType; out-of-extent tiles get a transparent PNG, not null); gbx_rst_xyzpyramid(tile, min_z, max_z, ...) is a generator that explodes one raster into one row per intersecting (z, x, y) tile across a zoom range. max_z capped at 20; total tile-count across zoom range capped at 10^6. Foundation for the PMTiles publishing pipeline. See Raster Functions.
  • Vector↔raster bridge (gbx_rst_rasterize, gbx_rst_polygonize). Two reciprocal RasterX functions that span GeoBrix's vector and raster worlds. gbx_rst_rasterize(geom, value, xmin, ymin, xmax, ymax, width_px, height_px, srid) burns a vector geometry into a fresh GTiff-backed raster tile at the given extent / resolution (pixels inside the geometry carry value, pixels outside are NoData = -9999.0). gbx_rst_polygonize(tile, [band, [connectedness]]) extracts ARRAY<struct(geom_wkb BINARY, value DOUBLE)> from tile — one feature per contiguous value region, NoData pixels excluded. The pair composes: polygonize(rasterize(geom, v, ...)) returns at least one feature with value v covering approximately the same area as the input geom, with edges quantized to the pixel grid. See Raster Functions § Vector bridge.
  • Terrain analysis (7 functions). gbx_rst_slope, gbx_rst_aspect, gbx_rst_hillshade, gbx_rst_tri, gbx_rst_tpi, gbx_rst_roughness, gbx_rst_color_relief — all thin wrappers over gdal.DEMProcessing. Each takes a single-band DEM tile and returns a derived tile (Float32 for slope/aspect/TRI/TPI/roughness, Byte for hillshade, RGB(A) Byte for color_relief). Defaults mirror the gdaldem CLI (hillshade NW sun at 315° azimuth, 45° altitude; slope in degrees). Foundation for terrain-derived workflows — solar exposure, viewshed pre-processing, watershed and runoff analysis, road grading. See Raster Functions § Terrain.
  • Slope and hillshade auto-scale from the raster CRS (breaking default on geographic rasters). gbx_rst_slope and gbx_rst_hillshade (and the lightweight prx.rst_slope / prx.rst_hillshade) now derive the horizontal scale from the raster's coordinate reference system by default, matching GDAL gdaldem. On geographic (lat/long, e.g. EPSG:4326) rasters the scale is computed from latitude (degree→metre), so a global or geographic DEM produces correct, non-saturated slope and shading without any extra argument; on projected (metre) rasters output is unchanged. Previously these two ran unscaled on geographic input, which over-steepened and saturated the result. This changes the default output for geographic rasters to the GDAL-consistent value. To pin a specific scale, pass it explicitly — gbx_rst_slope(tile, 'degrees', 111120) for a degree grid, or prx.rst_slope(tile, xscale=..., yscale=...) / prx.rst_hillshade(tile, xscale=..., yscale=...). gbx_rst_aspect is a direction and is unaffected. See Raster Functions § Terrain.
  • Spectral indices (5 functions). gbx_rst_evi, gbx_rst_savi, gbx_rst_ndwi, gbx_rst_nbr, plus a generic gbx_rst_index(tile, formula_name, band_map) — all compositions over gbx_rst_mapalgebra. Each takes user-supplied 1-based band indices, builds a per-pixel formula string, and dispatches to gdal_calc; output is a single-band Float32 GTiff sized to the input extent. The generic dispatcher ships built-in NDVI, GNDVI, MSAVI, red-edge NDVI, NDMI, and NDSI formulae and is the entry point users should reach for first for any named multi-band index; the four specialized expressions surface EVI / SAVI / NDWI / NBR with their canonical coefficient defaults (EVI: L=1.0, C1=6.0, C2=7.5, G=2.5 per MODIS; SAVI: L=0.5) so vegetation, water and burn-severity workflows compose without a hand-written formula string. See Raster Functions § Spectral indices.
  • Resample and IDW interpolation (5 functions). Three resample wrappers (gbx_rst_resample by multiplicative factor, gbx_rst_resample_to_size to explicit pixel dims, gbx_rst_resample_to_res to explicit ground resolution) all delegate to gdal.Warp with -tr / -ts plus -r <algorithm>. Two IDW functions — gbx_rst_gridfrompoints (arrays in one row) and its UDAF counterpart gbx_rst_gridfrompoints_agg (one point per row) — both delegate to gdal.Grid with the invdist:power=<p>:max_points=<m> algorithm and produce a single-band Float64 GTiff tile of the requested extent / size / SRID. Algorithm names match the gdalwarp -r set (near, bilinear, cubic, cubicspline, lanczos, average, mode, max, min, med, q1, q3); IDW defaults are power=2.0, max_pts=12, NoData -9999.0. See Raster Functions.
  • Pixel ops + extraction (7 functions). gbx_rst_fillnodata (fill NoData holes via inverse-distance from valid neighbors), gbx_rst_sample(tile, geom) (per-band pixel values at a geometry), gbx_rst_setsrid (stamp an EPSG code without reprojecting), gbx_rst_histogram (per-band bucket counts via band.GetHistogram), gbx_rst_threshold(tile, op, value) (binarize 0/1 via map-algebra), gbx_rst_buildoverviews(tile, levels, [resampling]) (add pyramid overview levels), and gbx_rst_band(tile, bandIndex) (extract a single band). Common per-pixel and per-tile operations missing from v0.3.0; each is a thin wrapper over the matching GDAL primitive. See Raster Functions.
  • Analysis (4 functions). gbx_rst_cog_convert(tile, [compression, [blocksize, [overview_resampling]]]) re-layouts a tile as a Cloud Optimized GeoTIFF via gdal.Translate -of COG (HTTP-range-friendly serving from object storage). gbx_rst_proximity(tile, [target_values, [distunits, [max_distance]]]) computes a Float32 distance raster via gdal.ComputeProximity — distance to the nearest non-NoData (or matching target_values) source pixel, in CRS units or pixels. gbx_rst_contour(tile, levels, [interval, [base, [attr_field]]]) extracts contour LineStrings via gdal.ContourGenerateEx, returning ARRAY<struct(geom_wkb BINARY, value DOUBLE)> — pass non-empty levels for fixed values or array() plus positive interval for equal-step contours. gbx_rst_viewshed(tile, observer_geom, observer_height, [target_height, [max_distance]]) computes a binary visibility mask (Byte raster, 255 visible / 0 invisible) from a DEM and an observer POINT via gdal.ViewshedGenerate. See Raster Functions.
  • TIN DTM rasters (2 functions). gbx_rst_dtmfromgeoms (array of Z-valued points and optional breaklines in one row) and gbx_rst_dtmfromgeoms_agg (streaming — one point per row, grouped by extent). Both build a constrained-Delaunay TIN and rasterize it to a Float64 GTiff DTM over a bbox at a pixel grid; cells outside the triangulated hull get NoData. Useful for deriving a continuous elevation surface from scattered survey points or LiDAR mass points. See Raster Functions § Constructors.
  • VectorX TIN surface modeling (3 functions). gbx_st_triangulate (emit one triangle polygon per row from a constrained-Delaunay TIN), gbx_st_interpolateelevationbbox (sample the TIN on a pixel grid over an explicit bounding box), and gbx_st_interpolateelevationgeom (sample on a grid anchored to a geometry's bounding box with explicit cell sizes) — all generators returning WKB geometries. Useful for exposing the raw triangulation and interpolated elevation points for vector-side workflows. See VectorX § Triangulation and elevation.
  • Streaming aggregators (3 functions). gbx_rst_rasterize_agg (burn geom/value pairs into one tile per group), gbx_rst_frombands_agg (collect ordered per-band tiles into one multi-band tile per group), and gbx_quadbin_cellunion_agg (dissolve a column of quadbin cell IDs into one MultiPolygon per group). Group-by / UDAF forms that stream rows instead of requiring a pre-collected array, suited for large partitions. See Raster Functions § Aggregators and GridX § Quadbin.
  • H3 cell rasterizer (gbx_rst_h3_rasterize_agg, gbx_h3_cell_bbox). gbx_rst_h3_rasterize_agg is a grouped aggregator (both tiers) that burns a set of H3 cells — one row per cell with an optional value — into a single GTiff-encoded raster tile per group, using pixel-centroid assignment. It is the inverse of the gbx_rst_h3_rastertogrid* family: where those extract per-cell statistics from an existing raster, this one synthesizes a raster from H3-indexed values. Extent and grid dimensions are either supplied explicitly or derived automatically from the cell set. gbx_h3_cell_bbox is a scalar function that returns a STRUCT<xmin DOUBLE, ymin DOUBLE, xmax DOUBLE, ymax DOUBLE> bounding box for a single H3 cell in the requested EPSG, optionally expanded by a k-ring pad. The lightweight Python API also ships rst_h3_gridspec, a helper that derives the canonical raster extent and pixel grid from a collection of H3 cells at a given resolution — useful for computing consistent grid parameters before calling the aggregator. See Raster Functions § H3 grid.
  • Custom grids (7 functions). gbx_custom_grid (define a user-specified regular grid from extent + resolution + SRID), gbx_custom_pointascell, gbx_custom_cellaswkb, gbx_custom_cellaswkt, gbx_custom_centroid, gbx_custom_polyfill, gbx_custom_kring. Index and tessellate against an arbitrary projected grid (for example a national or project-specific tiling) when H3, BNG, or quadbin cells do not match the required cell geometry. Available in both the heavyweight and lightweight (pygx) tiers, with exact cross-tier cell-ID and cell-set parity. See GridX § Custom Grid Functions.
  • gbx_rst_initnodata now works on multi-band rasters (behavior change since v0.3.0). Initializing NoData on a raster with more than one band previously raised an error; only single-band rasters were supported. gbx_rst_initnodata now initializes the NoData value correctly across all bands of a multi-band raster. Output for single-band rasters is unchanged. See Raster Functions.
  • gbx_rst_derivedband / gbx_rst_derivedband_agg return a single derived band for multi-band inputs (behavior change since v0.3.0). On a multi-band input, these functions previously returned one derived band per input band (an N-band output). They now apply the pixel function across all bands and return a single-band Float64 result, matching the documented single-band contract. Output for single-band inputs is unchanged. See Raster Functions.
  • gbx_bng_geomkring / gbx_bng_geomkloop accept string resolutions (consistency fix). These two functions now accept BNG string resolution keys (for example '1km', '100m') in addition to integer indices, matching gbx_bng_pointascell and the lightweight tier. Integer-index behavior is unchanged. See GridX § BNG.
  • Lightweight grouped aggregators return BINARY where the heavyweight tier returns a struct. For grouped aggregators whose heavyweight form returns a tile or chip struct (the rst_*_agg family, gbx_bng_cellunion_agg / gbx_bng_cellintersection_agg, and gbx_quadbin_cellunion_agg), the lightweight SQL form returns the serialized BINARY payload instead — a PySpark limitation (a grouped pandas_udf cannot return a struct type). Re-wrap the result with the matching scalar constructor to recover the struct. The Python DataFrame and Scala APIs are unaffected. See the per-function notes in Raster Functions and GridX.
  • gbx_custom_pointascell rejects a non-finite Y coordinate (fix). A NaN northing was previously not validated (a duplicate easting check), so it was only incidentally rejected with a misleading out-of-bounds message. Both tiers now reject a NaN Y with a clear error, matching the X-coordinate guard.
  • Lightweight STAC client (databricks.labs.gbx.stac.StacClient). A Serverless-safe client for distributed SpatioTemporal Asset Catalog (STAC) workflows — search (fan an area-of-interest DataFrame out across a catalog, one row per item/asset), download (resilient, validated asset fetch: re-signs each attempt, read-validates the bytes, retries with backoff, and skips already-valid files), and repair (re-download only the invalid rows via a Delta MERGE). Catalog-agnostic with pluggable signing, defaulting to Microsoft Planetary Computer. Ships behind the opt-in geobrix[light,stac] extra (adds pystac-client, planetary-computer) and imports cleanly on Serverless environment v5. See STAC Client.
  • AOI-driven sample downloaders (databricks.labs.gbx.sample). Three helpers stage open geospatial data to a Unity Catalog Volume with a shared discover → download → read shape, distributed and Serverless-safe: OvertureClient (Overture Maps buildings / places, via the Overture STAC catalog + overturemaps CLI), NaipDownloader (NAIP aerial imagery), and DemDownloader (USGS 3DEP elevation). NaipDownloader and DemDownloader wrap StacClient on Microsoft Planetary Computer and window each asset to the AOI on read. See Overture, NAIP, and 3DEP.
  • Visualization helpers (databricks.labs.gbx.vizx). A tier-agnostic, opt-in (geobrix[vizx]) module for inspecting GeoBrix outputs in a notebook. plot_raster / plot_file render a tile or file (auto-decimate, percentile-stretch, single-band viridis or multi-band RGB), and accept composite="depth" to render a multi-band presence stack as a per-pixel coverage-depth gradient (bright where many bands cover a pixel) instead of a mostly-black RGB. plot_mask_layers overlays several single-band mask tiles on one axes — each a solid colour with a legend — for multi-threshold coverage views. as_gdf / cells_as_gdf / grid_as_gdf adapt Spark DataFrames (geometry rows, H3 cell ids with an optional dissolve_by, or a rst_h3_gridspec grid struct) to GeoPandas for .plot() / .explore() maps. plot_static renders Spark- or GeoPandas-derived geometries (or DGGS cells) as a GitHub-renderable matplotlib figure over a basemap, and plot_interactive is its interactive twin — a folium pan/zoom map that automatically falls back to a raster image overlay at scale (where a bare .explore() would hang) and renders inline in Databricks via displayHTML. Single-band presence masks (constant value) now render as a solid footprint over a light background rather than a blank plot. See Visualization.
  • Inline PMTiles + COG viewers (plot_pmtiles, plot_cog, pmtiles_info). plot_pmtiles renders a PMTiles archive (raster or vector, auto-detected from the header) directly in a notebook — a self-contained MapLibre GL JS + pmtiles.js page with the archive base64-embedded as an in-browser FileSource, so there is no tile server; it falls back to a static image when the archive exceeds the notebook cell-output ceiling (~4–5 MB after displayHTML inflation), or drops the densest zooms with interactive_fit="downzoom". plot_cog renders a Cloud-Optimized GeoTIFF over a contextily basemap; pmtiles_info reports an archive's header (tile type, zoom range, bounds). See PMTiles viewers.
  • Example notebooks default to the lightweight tier. The EO Series and xView walkthroughs now run on the lightweight API (pyrx / pygx / pyvx plus the gbx_* DataSource readers and writers) by default, so they execute on Databricks Serverless (environment v5) with no JAR and no init script; each notebook calls out the one-line import to switch back to the heavyweight tier. The EO Series uses the new StacClient for its Planetary Computer search, download, and repair steps. See EO Series and xView.
  • H3 cell rasterize example notebook. A complete polygon → H3 polyfill → per-band rasterize → multi-band stack walkthrough on a San Francisco Bay Area DEM, treating elevation isobands as a stand-in for signal-strength coverage tiers (a telco coverage-analysis pattern). Exercises rst_h3_gridspec, rst_h3_rasterize_agg, and rst_frombands_agg, materializes the per-band tiles into a session-scoped temp table, and uses the gbx.vizx helpers (plot_mask_layers, plot_raster(composite="depth")) to inspect the result. See H3 Rasterize.
  • Helios distributed-tiling notebook series. A four-notebook solar site-selection walkthrough over one San Francisco AOI: building footprints → vector PMTiles (NB01), a NAIP aerial basemap → raster PMTiles (NB02), 3DEP terrain → COG catalog + hillshade PMTiles + a per-H3-cell solar score (NB03), and a distributed sharded PMTiles mosaic with a mosaic.json manifest for client-side assembly (NB04). Runs on the lightweight tier / Serverless with no JAR, dogfooding gbx_st_asmvt_pyramid, gbx_rst_xyzpyramid, gbx_pmtiles_agg, the sample downloaders, and the gbx.vizx PMTiles viewers. See Helios.
  • Grid explode functions now return cellid (lowercase) in the output struct (breaking schema change). The generator functions gbx_bng_kringexplode, gbx_bng_kloopexplode, gbx_bng_geomkringexplode, gbx_bng_geomkloopexplode, and gbx_bng_tessellateexplode previously returned a struct column named cellId (camelCase). The column is now cellid (all-lowercase), matching the tile-struct field name, the chip-struct field name, and the Databricks product convention. SQL queries that reference the result by field name (result.cellId) need to be updated to result.cellid.

What's new in v0.3.0

Released 2026-05-26. Per-version highlights; full migration tables are in the per-component sections below.

  • rst_clip CRS axis-order fix (all-black clips). GDAL 3+ defaults EPSG-imported SpatialReferences to authority-compliant axis order (lat/lon for EPSG:4326), which silently swapped axes against JTS/Databricks WKT/WKB cutlines so the clip missed the raster entirely. The reprojection now clones the source/destination SpatialReferences and forces OAMS_TRADITIONAL_GIS_ORDER before the OGR transform; caller-owned SpatialReferences are not mutated.
  • EWKT / EWKB support for rst_clip. JTS.fromWKT / JTS.fromWKB auto-detect EWKT/EWKB; new JTS.toEWKT / JTS.toEWKB helpers emit SRID-preserving forms. rst_clip reprojects the cutline when its SRID differs from the raster CRS, and falls back to the raster's CRS (Mosaic-compatible) when the SRID is 0 / unresolvable.
  • rst_transform rejects invalid SRIDs. targetSrid <= 0 and unresolvable EPSG codes now surface a clear error via tile metadata error_message instead of returning a raster with an uninitialized CRS.
  • /vsimem/ path-handling hardening. rst_memsize / rst_unlink / GDAL writer in-memory byte fetch now use startsWith("/vsimem/") (not contains) and null-check GetMemFileBuffer, so datasets whose description embeds the substring (e.g. NetCDF subdataset selectors) aren't mis-routed through the in-memory branch.
  • tile.raster bytes are always self-contained (no VRT payloads). Three RasterX operations — MergeRasters (gbx_rst_merge, gbx_rst_merge_agg), MergeBands (gbx_rst_frombands), and PixelCombineRasters (gbx_rst_derivedband, gbx_rst_derivedband_agg, gbx_rst_combineavg, gbx_rst_combineavg_agg) — used to return tiles whose metadata("driver") claimed VRT even though the on-disk file was a materialized GTiff. That mis-tag propagated through RasterDriver.writeToBytes (which keys both the tempfile extension AND the -of flag in the inner gdal_translate call off metadata.driver), causing the serialized tile.raster payload to be VRT XML referencing a /vsimem/ tempfile only reachable on the producing executor. Single-node testing passed by accident; multi-executor clusters hit file not found when the VRT was opened elsewhere. Fix: GDALTranslate.executeTranslate now records the output dataset's driver in its returned metadata (not the input's), and RasterDriver.writeToBytes defensively coerces VRT to GTiff on serialization + sniffs the result to refuse shipping VRT bytes. Regression coverage in RST_NoVrtPayloadTest.
  • PixelCombineRasters pixel function now actually fires (combineavg / derivedband were silently returning one of the inputs). gbx_rst_combineavg, gbx_rst_combineavg_agg, gbx_rst_derivedband, and gbx_rst_derivedband_agg build a multi-source VRT, inject a <PixelFunctionLanguage>Python</...> band, and re-open it for gdal_translate. The previous implementation re-opened the VRT before mutating the XML file, so the in-memory Dataset handle never saw the pixel function; gdal.Translate then fell back to a default multi-source mosaic (last-source-wins per pixel). On co-extensive inputs (e.g. a monthly EO time-series), the output silently equaled one of the inputs — non-deterministic per partition in a distributed setting, producing visible tile-of-different-years patchwork on multi-executor clusters. Fix: PixelCombineRasters.combine now injects the pixel function before the VRT is re-opened, and pre-creates the per-JVM NodeFilePathUtil.rootPath staging dir itself (previously only ClipToGeom did, so combineavg would file not found if it was the first op to hit a fresh JVM). Regression coverage: RST_AggregationsTest "CombineAvg actually averages pixel values" (two constant rasters 50 + 100 → output 75).
  • gbx_rst_merge_agg overlap winner is now deterministic. When merging tiles whose extents overlap, the mosaic is last-wins, so the result depends on the order tiles are folded. The aggregator previously ordered tiles by their GDAL dataset description to make that order stable, but for the in-memory (BinaryType) tiles a groupBy().agg() produces, the description is a per-open /vsimem/<uuid> path — so the fold order, and therefore the overlap winner, varied from run to run. The aggregator now orders tiles by their raw serialized content (the GTiff bytes each tile carries) — a total order intrinsic to the tile with no ties for distinct content and no random per-open component — so one tile reliably wins the overlap regardless of fold order, and the result is identical across the heavyweight and lightweight tiers (both sort on the identical bytes). This also fixes overlapping tiles that share the same geotransform origin, which an origin-based key could not separate. Non-overlapping mosaics are unaffected. Regression coverage: RST_AggEvalTest deterministic same-origin and offset merge cases.
  • Friendly error on ARRAY<tile>-function misuse. Calling gbx_rst_combineavg, gbx_rst_merge, gbx_rst_frombands, or gbx_rst_mapalgebra on a single tile column (instead of an ARRAY<tile> like collect_list(tile)) used to surface as a raw ClassCastException: StructType cannot be cast to ArrayType from inside Catalyst analysis — untraceable from a notebook. The four expressions now route through RST_ExpressionUtil.arrayOfTileRasterType, which raises a clean IllegalArgumentException naming the function, the actual type received, and (where applicable) the aggregator companion the user likely wanted, e.g. gbx_rst_combineavg expects ARRAY<tile> (e.g. collect_list(tile) or array(t1, t2, ...)), but received STRUCT<...>. To aggregate the column across rows, use gbx_rst_combineavg_agg(tile).
  • Docs: GDAL_VRT_ENABLE_PYTHON for custom GDAL code paths. Built-in combineavg / derivedband calls auto-enable VRT Python via the in-process GDALManager.withVrtPython bracket — no cluster config needed. The RasterX Function Reference § VRT Python pixel functions section documents how to enable the same evaluation in your own GDAL calls (Python gdal.SetConfigOption, cluster spark.executorEnv, or the JVM withVrtPython helper) and points to the TRUSTED_MODULES variant for less-trusted VRT sources. A cross-reference is added in Security § 6 explaining why GeoBrix ships the option NO by default.
  • gbx_rst_derivedband / gbx_rst_derivedband_agg numerical-correctness regression coverage. These functions share the PixelCombineRasters code path with combineavg, so they were silently no-opping in the same way (returning one of the inputs unchanged on co-extensive stacks). The ordering fix above repairs both call sites, but the existing tests only checked that the result wasn't null — they would have passed either way. This release adds explicit pixel-value assertions: RST_AggregationsTest covers the in-process RST_DerivedBand path with a doubling pyfunc and a 3-input numpy-mean pyfunc, and RST_AggEvalTest covers the Spark-aggregation rst_derivedband_agg path end-to-end (three constant-Byte tiles 10/20/30 with a "mean × 2" pyfunc must yield 40 across the result tile). Two previously-passing tests used def myfunc(x): return x * 2 — an invalid VRT pixel-function signature — and were updated to the canonical (in_ar, out_ar, xoff, yoff, xsize, ysize, raster_xsize, raster_ysize, buf_radius, gt, **kwargs) shape; they only "passed" before because the pyfunc never actually ran.
  • gbx_rst_combineavg / gbx_rst_combineavg_agg math corrected (NoData, valid zeros, rounding). With the pixel function now firing (previous bullet), several latent bugs in the average kernel surface and are fixed in this release. The pyfunc used to sum every source value blindly — including each band's NoData sentinel (e.g. 255 on Byte EO products) — and counted only strictly-positive cells in the divisor (np.sum(stacked > 0, axis=0)), which (a) inflated the numerator with NoData and (b) wrongly excluded valid 0 measurements from the divisor. It also used np.divide(..., casting='unsafe'), which truncates rather than rounds when casting back to an integer output dtype (Byte / UInt16), producing systematic underbias on integer EO stacks. Now the kernel reads each source band's declared NoData (via BandAccessors.getNoDataValue, baked into the pyfunc source as a literal list at VRT-write time), masks NoData cells out of both sum and divisor, includes valid 0s, uses float64 internally, and rounds-to-nearest-even before the unsafe cast when the output dtype is integer. The bogus np.clip(out_ar, stacked.min(), stacked.max(), ...) (the bounds were contaminated by NoData sentinels) is removed. When at least one input declares NoData, that value is also stamped on the output band so downstream GetNoDataValue reports all-NoData pixels. Regression coverage in RST_AggregationsTest: "excludes declared NoData from both sum and divisor", "counts valid 0 cells in the divisor", "rounds (not truncates) when casting to integer output".
  • Scalar args without f.lit(...). Python wrappers auto-wrap bool / int / float / bytes; Scala adds typed overloads. SQL was already natively-typed. String literals still wrap in f.lit(...) per pyspark's column-ref convention. Details and migration examples in Scalar values vs lit(...) wrapping.
  • Example notebooks — EO Series, xView, and enablement diagrams. New end-to-end walkthroughs under docs/examples/ covering EO time-series, xView object-detection rasters, and RasterX architecture diagrams.
  • Supply-chain hardening (lockdown). Jobs pinned to the Databricks-hardened runner group (org-level allowlist, ephemeral VMs, constrained secret access); every Maven dependency, transitive dep, plugin, and plugin dependency is PGP-verified against .maven-keys.list before any compile or test execution; pip and Maven routed through JFrog with OIDC; init script + pinned package versions vetted; new Security page in the docs.
  • Pre-built, hash-verified GDAL bundle. The GDAL native install path is now a CI-built tarball (geobrix-gdal-artifacts-v<version>-noble.tar.gz + matching .sha256 sidecar, attached to each release alongside a versioned geobrix-gdal-init.sh). Cluster start drops from ~15 minutes (legacy PPA dance per boot) to ~30–90 seconds (verify sidecar → extract → dpkg -i). Trust chain is now four layers: CI-side GPG fingerprint pin → per-file SHA256SUMS inside the tarball → outer .sha256 sidecar in the staging Volume → the Volume's write ACL. The legacy on-cluster path is preserved as scripts/geobrix-gdal-init-ppa.sh for bundle bootstrapping. Bundle is amd64 / x86_64 only (Intel or AMD CPUs); ARM-based instance types — AWS Graviton, Ampere, Apple Silicon — are not supported. See Installation and the rationale on the Security page.

Conventions:

  • baseline — Name or behavior before the change (what to search for in old code or docs).
  • Notes — Short reason (e.g. standardize across languages, underscore standardization, _geometry → _geom).

General

BaselineCurrentNotes
Python import geobrix.*databricks.labs.gbx.*Match Scala package and published artifact; avoid namespace clashes.
Extra underscores in function names (multi-word parts spelled with _)Single underscore between prefix and compound (e.g. rst_pixelwidth, gbx_bng_cellarea)Underscore standardization: one leading prefix, then one compound word; no _ inside the operation name.
Non-Column value args required f.lit(...) / lit(...) wrapping (e.g. rst_clip(tile, geom, f.lit(True)), bng_pointascell(pt, f.lit(1)))Plain Python/Scala non-string scalars accepted directly (e.g. rst_clip(tile, geom, True), rst_transform(tile, 4326), bng_pointascell(pt, 1))Matches Mosaic/DBR built-in ergonomics for booleans/numerics. Python wrappers auto-wrap bool/int/float/bytes via f.lit; Scala adds typed overloads. Strings still follow pyspark's column-ref convention — rx.rst_width("tile") is still f.col("tile"); wrap in f.lit(...) for string literals (e.g. driver=f.lit("GTiff")).

All specific function renames from that standardization are listed in the component tables below.


RasterX

BaselineCurrentNotes
(GDAL reader output column) pathsourceDocs/tests aligned to GDAL reader output column name.
rst_band_metadata / gbx_rst_band_metadatarst_bandmetadata / gbx_rst_bandmetadataUnderscore standardization.
rst_bounding_box / gbx_rst_bounding_boxrst_boundingbox / gbx_rst_boundingboxUnderscore standardization.
rst_pixel_width / gbx_rst_pixel_widthrst_pixelwidth / gbx_rst_pixelwidthUnderscore standardization.
rst_pixel_height / gbx_rst_pixel_heightrst_pixelheight / gbx_rst_pixelheightUnderscore standardization.
rst_num_bands / gbx_rst_num_bandsrst_numbands / gbx_rst_numbandsUnderscore standardization.
rst_pixel_count / gbx_rst_pixel_countrst_pixelcount / gbx_rst_pixelcountUnderscore standardization.
rst_scale_x / gbx_rst_scale_xrst_scalex / gbx_rst_scalexUnderscore standardization.
rst_scale_y / gbx_rst_scale_yrst_scaley / gbx_rst_scaleyUnderscore standardization.
rst_upper_left_x / gbx_rst_upper_left_xrst_upperleftx / gbx_rst_upperleftxUnderscore standardization.
rst_upper_left_y / gbx_rst_upper_left_yrst_upperlefty / gbx_rst_upperleftyUnderscore standardization.
rst_geo_reference / gbx_rst_geo_referencerst_georeference / gbx_rst_georeferenceUnderscore standardization.
rst_get_nodata / gbx_rst_get_nodatarst_getnodata / gbx_rst_getnodataUnderscore standardization.
rst_get_subdataset / gbx_rst_get_subdatasetrst_getsubdataset / gbx_rst_getsubdatasetUnderscore standardization.
rst_mem_size / gbx_rst_mem_sizerst_memsize / gbx_rst_memsizeUnderscore standardization.
rst_sub_datasets / gbx_rst_sub_datasetsrst_subdatasets / gbx_rst_subdatasetsUnderscore standardization.
rst_combine_avg_agg / gbx_rst_combine_avg_aggrst_combineavg_agg / gbx_rst_combineavg_aggUnderscore standardization.
rst_derived_band_agg / gbx_rst_derived_band_aggrst_derivedband_agg / gbx_rst_derivedband_aggUnderscore standardization.
rst_from_content / gbx_rst_from_contentrst_fromcontent / gbx_rst_fromcontentUnderscore standardization.
rst_from_file / gbx_rst_from_filerst_fromfile / gbx_rst_fromfileUnderscore standardization.
rst_from_bands / gbx_rst_from_bandsrst_frombands / gbx_rst_frombandsUnderscore standardization.
rst_make_tiles / gbx_rst_make_tilesrst_maketiles / gbx_rst_maketilesUnderscore standardization.
rst_re_tile / gbx_rst_re_tilerst_retile / gbx_rst_retileUnderscore standardization.
rst_separate_bands / gbx_rst_separate_bandsrst_separatebands / gbx_rst_separatebandsUnderscore standardization.
rst_to_overlapping_tiles / gbx_rst_to_overlapping_tilesrst_tooverlappingtiles / gbx_rst_tooverlappingtilesUnderscore standardization.
rst_init_nodata / gbx_rst_init_nodatarst_initnodata / gbx_rst_initnodataUnderscore standardization.
rst_is_empty / gbx_rst_is_emptyrst_isempty / gbx_rst_isemptyUnderscore standardization.
rst_map_algebra / gbx_rst_map_algebrarst_mapalgebra / gbx_rst_mapalgebraUnderscore standardization.
rst_raster_to_world_coord / gbx_rst_raster_to_world_coord (and X/Y variants)rst_rastertoworldcoord / gbx_rst_rastertoworldcoord (and X/Y)Underscore standardization.
rst_world_to_raster_coord / gbx_rst_world_to_raster_coord (and X/Y variants)rst_worldtorastercoord / gbx_rst_worldtorastercoord (and X/Y)Underscore standardization.
rst_as_format / gbx_rst_as_formatrst_asformat / gbx_rst_asformatUnderscore standardization.
rst_combine_avg / gbx_rst_combine_avgrst_combineavg / gbx_rst_combineavgUnderscore standardization.
rst_h3_raster_to_grid_avg (and Count/Max/Min/Median)rst_h3_rastertogridavg (and Count/Max/Min/Median)Underscore standardization.
rst_bandmetadata(tile) (single arg)rst_bandmetadata(tile, band)Required band parameter added; use e.g. rst_bandmetadata("tile", f.lit(1)).
rst_fromfile raster field was StringType (path) with metadata.size = -1rst_fromfile raster field is BinaryType (file bytes) with real metadata.sizerst_fromfile now reads the file into the tile, so tiles are self-contained and downstream ops (e.g. rst_clip) no longer produce orphan temp paths. Matches rst_fromcontent and the GDAL reader.
Default output compression was ZSTD (TIFF tag 50000)Default output compression is DEFLATE (baseline TIFF)ZSTD output was not decodable by Java ImageIO and broke the Databricks image preview after operators like rst_clip. DEFLATE is universally previewable and (with PREDICTOR=2/3) still compresses well. Override per-call via tile metadata compression key.

GridX (BNG)

BaselineCurrentNotes
bng_eastnortasbng (Python) / gbx_bng_eastnortasbng (SQL)bng_eastnorthasbng / gbx_bng_eastnorthasbngStandardize across languages (Python had typo; Scala already eastnorth).
bng_cell_area / gbx_bng_cell_areabng_cellarea / gbx_bng_cellareaUnderscore standardization.
bng_cell_intersection / gbx_bng_cell_intersectionbng_cellintersection / gbx_bng_cellintersectionUnderscore standardization.
bng_cell_union / gbx_bng_cell_unionbng_cellunion / gbx_bng_cellunionUnderscore standardization.
bng_euclidean_distance / gbx_bng_euclidean_distancebng_euclideandistance / gbx_bng_euclideandistanceUnderscore standardization.
bng_point_as_bng / gbx_bng_point_as_bngbng_pointascell / gbx_bng_pointascellUnderscore standardization; Renamed for clarity: point → cell (not "point as BNG").
bng_cell_intersection_agg / gbx_bng_cell_intersection_aggbng_cellintersection_agg / gbx_bng_cellintersection_aggUnderscore standardization.
bng_cell_union_agg / gbx_bng_cell_union_aggbng_cellunion_agg / gbx_bng_cellunion_aggUnderscore standardization.
bng_geometry_kring / gbx_bng_geometry_kringbng_geomkring / gbx_bng_geomkring_geometry → _geom in name.
bng_geometry_kloop / gbx_bng_geometry_kloopbng_geomkloop / gbx_bng_geomkloop_geometry → _geom in name.
bng_geometry_kring_explode / gbx_bng_geometry_kring_explodebng_geomkringexplode / gbx_bng_geomkringexplode_geometry → _geom + underscore standardization.
bng_geometry_kloop_explode / gbx_bng_geometry_kloop_explodebng_geomkloopexplode / gbx_bng_geomkloopexplode_geometry → _geom + underscore standardization.
bng_k_ring / gbx_bng_k_ringbng_kring / gbx_bng_kringUnderscore standardization.
bng_k_loop / gbx_bng_k_loopbng_kloop / gbx_bng_kloopUnderscore standardization.
bng_k_ring_explode / gbx_bng_k_ring_explodebng_kringexplode / gbx_bng_kringexplodeUnderscore standardization.
bng_k_loop_explode / gbx_bng_k_loop_explodebng_kloopexplode / gbx_bng_kloopexplodeUnderscore standardization.
bng_tessellate_explode / gbx_bng_tessellate_explodebng_tessellateexplode / gbx_bng_tessellateexplodeUnderscore standardization.

VectorX

BaselineCurrentNotes
(Schema/column) _geometry_geomStandardize geometry column suffix across readers and examples.
st_legacy_as_wkb / gbx_st_legacy_as_wkbst_legacyaswkb / gbx_st_legacyaswkbUnderscore standardization.

Readers

BaselineCurrentNotes
shapefileshapefile_ogrReader namespace: format + engine to avoid conflicts with other Spark extensions.
geojsongeojson_ogrSame.
ogr_gpkggpkg_ogrSame; consistent format_engine order.
file_gdbfile_gdb_ogrSame.
(none)gtiff_gdalNew reader: named GDAL reader for GeoTIFF; use instead of gdal with option("driver", "GTiff").
info

Reader renames above landed in 0.2.0; earlier 0.1.x releases may still expose the baseline names in some contexts.


Scalar values vs lit(...) wrapping

Previously, every non-Column argument had to be wrapped in f.lit(...) (Python) or lit(...) (Scala). That was a regression from Mosaic/DBR built-ins, where booleans and numerics can be passed as plain values. In 0.3.0, plain scalars are accepted across Python, Scala, and SQL bindings.

Python — wrappers accept Column or scalar (bool/int/float/bytes); non-string scalars are auto-wrapped with f.lit(...). Strings still follow pyspark's column-reference convention (bare string ≈ f.col(name)); wrap in f.lit("...") to pass a string literal.

# ✅ Before 0.3.0 — required f.lit for every value
rx.rst_clip("tile", "geom", f.lit(True))
rx.rst_transform("tile", f.lit(4326))
bx.bng_pointascell("pt", f.lit(1))
bx.bng_pointascell("pt", f.lit("1km"))

# ✅ 0.3.0 — scalars accepted directly
rx.rst_clip("tile", "geom", True)
rx.rst_transform("tile", 4326)
bx.bng_pointascell("pt", 1)
bx.bng_pointascell("pt", f.lit("1km")) # string literal — still wrap in f.lit

Scala — typed overloads added for Boolean / Int / Double / String value parameters. Column args (e.g. geometry, tile) still take Column.

// ✅ 0.3.0 — scalar overloads resolve without lit(...)
rst_clip(col("tile"), col("geom"), cutlineAllTouched = true)
rst_transform(col("tile"), 4326)
bng_pointascell(col("pt"), 1)
bng_pointascell(col("pt"), "1km")

SQL — values are already natively accepted by Spark SQL; no change needed:

SELECT gbx_rst_clip(tile, geom, true) FROM ...;
SELECT gbx_bng_pointascell(pt, 1) FROM ...;
SELECT gbx_bng_pointascell(pt, '1km') FROM ...;

When you still need f.lit(...) in Python:

  • String literals: rx.rst_fromfile(f.lit("/path/to.tif"), f.lit("GTiff")) — a bare string is treated as a column reference.
  • Nulls / explicit typing: e.g. f.lit(None).cast("double").

How to use this page

  • Migrating code: Search for the baseline name in your code or config; replace with Current and apply any behavior notes.
  • Docs or tests: After a change, add one row here so future readers know what changed and why.
  • Housekeeping: Keep per-version sections here as the canonical change log; prune superseded interim notes as versions settle.

Notable improvements and fixes

  • Python package rename: Imports changed from geobrix.* to databricks.labs.gbx.* to align with Scala and the published artifact; update all import statements and environment references.
  • Init script / NumPy: Init script updated to install NumPy 2.x so GDAL Python array operations execute correctly; fixes runtime failures in gbx_rst_mapalgebra and gbx_rst_ndvi when used with array-based paths.
  • Error handling: Functions that previously threw exceptions during execution now surface errors more clearly (e.g. return null or a controlled default with error messages captured) instead of failing with opaque stack traces.
  • RasterX rst_bandmetadata: A required band argument was added; call as rst_bandmetadata(tile, band) (e.g. rst_bandmetadata("tile", f.lit(1))) in Python/SQL/Scala.
  • GDAL reader column: Raster DataFrames from the GDAL reader use the column name source (not path) for the file path; update any code or docs that assumed path.
  • BNG aggregators (bng_cellunion_agg, bng_cellintersection_agg): Fixed a bug where aggregation buffers were shared across partitions (and across tests in the same JVM), causing incorrect core flags when running full test suites or with multiple partitions. Each partition now gets a fresh buffer. Chip fields are resolved by type/name in the union aggregator for robustness to struct field order. Test expectation corrected for “all core chips” intersection: result is now correctly documented as core=true (whole cell).
  • rst_clip axis-order fix for EPSG-imported CRS (fixes all-black clips): When the clip geometry's CRS was set via an EPSG code (plain rst_transform-style input, EWKT SRID=4326;..., or EWKB with SRID), GDAL 3+ defaults that SpatialReference to authority-compliant axis order — for EPSG:4326 that means (latitude, longitude). JTS / Databricks / most GIS tooling emit WKT/WKB coordinates in traditional (x, y) = (lon, lat) order, so the reprojection inside rst_clip was silently swapping the axes (e.g. -80 14 interpreted as lat=-80, near the south pole) and the cutline missed the raster entirely, producing all-black output. OSRTransformGeometry.transform now clones both source and destination SpatialReferences and forces OAMS_TRADITIONAL_GIS_ORDER on the clones before running the OGR transform, so JTS-origin WKB is interpreted correctly. Caller-owned SpatialReferences are not mutated.
  • EWKT / EWKB support for raster clip (CRS mismatch handling): rst_clip now accepts EWKT (SRID=<epsg>;<WKT>) and EWKB (PostGIS extended WKB) in addition to plain WKT/WKB. Semantics:
    • Plain WKT / WKB (no SRID): the geometry is assumed to already be in the raster's CRS; no reprojection is performed.
    • EWKT / EWKB (SRID set and resolvable via EPSG): the geometry's CRS is used and, if it differs from the raster's CRS, the cutline is reprojected before clipping.
    • If the SRID is 0 or not a valid EPSG code, the code falls back to the raster's CRS (same as the plain case) — this restores Mosaic-compatible behavior but no longer silently produces an empty/black clip when a caller forgets to set the SRID. JTS.fromWKT / JTS.fromWKB now auto-detect EWKT/EWKB; new JTS.toEWKT / JTS.toEWKB helpers emit SRID-preserving forms. Plain toWKT / toWKB output is unchanged (OGC, no SRID).
  • rst_transform invalid SRID: rst_transform(tile, targetSrid) now rejects targetSrid <= 0 and EPSG codes that GDAL cannot resolve with a clear error (surfaced in tile metadata error_message) instead of returning a raster with an uninitialized CRS.
  • /vsimem/ path handling hardening: rst_memsize / rst_unlink and the GDAL writer's in-memory byte fetch now use startsWith("/vsimem/") (not contains) and null-check GetMemFileBuffer, so datasets whose description happens to embed the substring (e.g. NetCDF subdataset selectors) are no longer mis-routed through the in-memory branch.