Choosing an Execution Tier
One API, two engines. Every GeoBrix raster function exists in two interchangeable
Execution Tiers, behind the same rst_* Python names and gbx_rst_* SQL names:
- Lightweight (
pyrx) — pure Python/rasterio, no JAR, no init script. Runs everywhere: Serverless, standard/shared clusters, ARM, Lakeflow declarative pipelines. Works with bytes-free virtual tiles so huge rasters ingest without out-of-memory. The recommended default. - Heavyweight (
rasterx) — JVM-native (Scala + GDAL JNI) on a classic x86 cluster. Operates on materialized (binary) tiles.
Both tiers cover the full raster function set and agree within tolerance across the benchmark suite — so switching is a one-line import change and the rest of your code is identical. Pick the tier that fits your environment; the sections below cover what each provides and how to choose.
The one-line swap
from databricks.labs.gbx.rasterx import functions as rx # Heavyweight tier
from databricks.labs.gbx.pyrx import functions as rx # Lightweight tier — same alias
# everything below is identical across tiers:
df.select(rx.rst_slope("tile", unit="degrees"))
After an explicit rx.register(spark), the SQL names are identical too (gbx_rst_*), so SQL is portable across tiers.
The one-line import swap is symmetric, but the install is not. The lightweight tier installs a runtime-pinned extra (e.g. [light_env6] on Serverless env 6; see Installation for the full extras table) via %pip — no JAR, no init script. The heavyweight tier additionally requires the GeoBrix JAR as a cluster library and the GDAL init script on a classic x86 cluster — the wheel alone will not resolve the import or the JVM expressions. See Installation for the heavyweight setup.
Function availability
Both tiers implement the full raster set — every rst_* function, including the BNG and quadbin
raster-grid functions — so the tier choice is about environment, not capability. The remaining
heavyweight-only surfaces are narrow: the vector OGR readers (shapefile_ogr, geojson_ogr, …),
the conforming triangulation mode, and the heavy pmtiles DataSource writer. For the per-function
breakdown see the Raster Functions availability section; for the tier tile
model (virtual vs. materialized), see Virtual Tiles.
Registering a subset (only=)
register() installs every gbx_* SQL name for the tier. To register just the functions a session uses, pass only= (lightweight tiers — pyrx, pygx, pyvx):
from databricks.labs.gbx.pyrx import functions as rx
rx.register(spark, only=["rst_slope", "rst_clip"]) # just these two
rx.register(spark) # all (default)
Names are case-insensitive and accept either the SQL name (gbx_rst_slope) or the short form (rst_slope). An unrecognized name raises ValueError (typo guard). only=[] registers nothing.
Readers and writers register through a separate entry point and take only= too — selected by format name (with or without the _gbx suffix):
from databricks.labs.gbx.ds import register as ds_register
ds_register.register(spark, only=["raster_gbx", "gtiff_gbx"]) # just these formats
ds_register.register(spark, only=["shapefile"]) # 'shapefile' -> 'shapefile_gbx'
ds_register.register(spark) # all readers/writers (default)
Mixing tiers per function. Because both tiers share the gbx_* names (last registration wins), you can register the heavyweight set and then override individual functions with the lightweight implementation:
from databricks.labs.gbx.rasterx import functions as heavy
from databricks.labs.gbx.pyrx import functions as light
heavy.register(spark) # all heavy gbx_rst_*
light.register(spark, only=["rst_slope"]) # gbx_rst_slope now lightweight
The reverse — re-registering a few heavy functions over a lightweight session — is not yet available; only= is currently a lightweight-tier feature (heavy registers its full set). Mixing works for materialized tiles (raster bytes present) — both tiers share the same GTiff payload, so a bytes-carrying tile produced by one tier flows into a function from the other. A lightweight virtual tile (bytes-free path+window) must be materialized before a heavyweight function can use it; see Virtual tiles and the light→heavy bridge.
Tradeoffs
The lightweight raster tier is, in effect, distributed rasterio: see how much of the rasterio/GDAL surface is already distributed — and what stays heavyweight-only for now.
| Aspect | Heavyweight (rasterx) | Lightweight (pyrx) |
|---|---|---|
| Install | Init script + JAR | Volume-staged wheel (%pip or cluster library) |
| Native GDAL | System/PPA install | Bundled with rasterio (nothing to install) |
| ARM support | x86 only | x86 and ARM |
| Serverless / shared clusters / Lakeflow SDP | Not supported | Supported |
| Execution model | JVM-native (Scala + GDAL JNI) | Python-worker UDFs (rasterio + NumPy) |
| Tile model | Materialized (binary) tiles only | Materialized and bytes-free virtual tiles (lazy windowed reads; no ingest OOM) |
| Driver coverage | Full custom GDAL build | rasterio's bundled build (narrower) |
| SQL default arguments | Supported | Pass all arguments explicitly |
| Function coverage | Full raster set | Full raster set — every rst_* function, including the BNG/quadbin raster-grid functions |
| Readers / Writers | gtiff_gdal, gdal, OGR readers | raster_gbx / gtiff_gbx native Python DataSource V2 reader + writer (no JAR); vector OGR readers still heavy-only |
How to choose
Start with Lightweight (pyrx). It installs as a single wheel (%pip or cluster library) with no GDAL and no JAR, runs everywhere — serverless, standard/shared clusters, ARM, and Lakeflow declarative pipelines — and covers the full raster set: every rst_* function, including the BNG and quadbin raster-grid functions. For most raster work it is the recommended default.
Choose Heavyweight (rasterx) in three cases:
- Your environment is already JVM/GDAL-based — you have the init script and JAR in place, or you specifically want JVM-native execution on a dedicated cluster.
- You need vector OGR readers or format-specific GDAL options — the OGR vector readers (
shapefile_ogr,geojson_ogr,gpkg_ogr, …) and the genericgdalreader with exotic driver options are heavy-only. For raster I/O, the lightweight tier ships nativeraster_gbx/gtiff_gbxreaders and a writer (no JAR; see Lightweight Raster Readers). - You need the heavy-only
conformingtriangulation mode — GridX is now fully lightweight: the quadbin (gbx_quadbin_*), BNG (gbx_bng_*), and custom-grid (gbx_custom_*) functions all run in both tiers via the lightweightpygxpackage. The full VectorX function set is now available in the lightweightpyvxtier — vector-tile encoding (gbx_st_asmvt,gbx_st_asmvt_pyramid), TIN surface modeling (gbx_st_triangulate,gbx_st_interpolateelevation*), and legacy-geometry migration (gbx_st_legacyaswkb); only the Steiner-pointconformingtriangulation mode is heavyweight-only (the defaultconstrainedmode runs in both tiers). See GridX and VectorX Function Reference.
The vector OGR readers, the conforming triangulation mode, and the heavy pmtiles DataSource writer are the remaining heavyweight-only surfaces. Raster I/O, the full VectorX function set, all of GridX (quadbin, BNG, and custom grids), and the gbx_pmtiles_agg aggregate are now available in both tiers; heavyweight's unique surface is expected to keep narrowing.
Performance
The one-line swap keeps your code identical across tiers. The lightweight tier is functionally complete for raster — it implements every rst_* function, including the BNG and quadbin raster-grid functions listed below — and for VectorX, implementing every gbx_st_* function (MVT, TIN, legacy migration).
Raster-grid surface (BNG/quadbin) — tier status:
| Function family | Tier | Notes |
|---|---|---|
gbx_rst_bng_rastertogrid{avg,count,max,min,median,sum,variance,stddev} | Both | Light support via pyrx (pygx._bng cell math + rasterio, EPSG:27700 auto-warp, STRING cell IDs) |
gbx_rst_quadbin_rastertogrid{avg,count,max,min,median,sum,variance,stddev} | Both | Light support available via pyrx |
gbx_rst_quadbin_tessellate, gbx_rst_bng_tessellate | Both | Light support via pyrx (pygx._quadbin / _bng cell math + rasterio) |
gbx_rst_quadbin_rasterize_agg, gbx_rst_bng_rasterize_agg | Both | Light support via pyrx (pygx._quadbin / _bng cell math + rasterio) |
On per-operation timing the lightweight tier is competitive-to-faster for the large majority of functions. Band math is dramatically faster, because the heavyweight tier shells out to a subprocess there. Terrain, focal filters, and discrete-grid (H3/quadbin) aggregation are a few times faster following recent vectorization. A small number of algorithm-bound operations (for example viewshed) are slower on the lightweight tier but remain sub-second in absolute terms. Metadata accessors are sub-millisecond on both.
Across the benchmark suite the two tiers agree within tolerance on 115 of 116 functions. Only one differs, at raster edges: rst_convolve differs slightly — the heavyweight tier applies a GDAL block-halo convolution that no single lightweight boundary mode reproduces exactly; interior values match. All three tessellation generators (rst_h3_tessellate, rst_quadbin_tessellate, rst_bng_tessellate) use a positive-area covering keep-test on both tiers: a cell is emitted iff its geometry has greater-than-zero area overlap with the raster — a cell that merely touches the raster along a boundary edge or corner (zero pixel overlap) is excluded, while a within-extent cell whose pixels are all NoData is still emitted (NoData renders in place, it does not punch a gap into the mosaic). With that shared semantic the three tessellators agree exactly across tiers (identical cell sets), including on grid-aligned tiles where the raster edges land on cell boundaries. The BNG and quadbin raster-grid reducers (rst_bng_rastertogrid{avg,count,max,min,median,sum,variance,stddev}) were measured on a British-National-Grid tile with real cells and agree within tolerance across tiers; the two *_rasterize_agg aggregators agree exactly. See Benchmarking for the full per-function heavy-vs-light results and how to run the benchmark on a cluster or locally.
Virtual tiles and the light→heavy bridge
The lightweight tier is for light (virtual) raster tiles; the heavyweight tier is for heavy
(binary) raster tiles. Both tiers share the same v2 tile struct — the difference is in the
raster field: a virtual tile has raster = null (bytes-free, path + window backed); a
materialized tile carries raster bytes (raster is not null).
The heavyweight tier accepts both v1 and v2 materialized tiles as input and always emits
the v2 tile struct. Passing a virtual tile (bytes-free, path-backed) to a heavyweight function
raises a clear error telling you to materialize it in the lightweight tier first — either with
materialize=True, or by writing via any raster writer and reading the output back. The JVM cannot
lazily read from a Unity Catalog Volume FUSE path the way a Python worker can, so the materialization
step is required before crossing the tier boundary.
If a heavy function does not produce the expected result from a light-tier tile, the tile may be virtual (no bytes). You have two options:
- Stay in the lightweight tier. For most raster work,
pyrxcovers the full function set and runs everywhere (serverless, standard clusters, ARM). If you don't specifically need the JVM execution model, there is no reason to cross to heavy. - Materialize before crossing. If you do need a heavyweight function: call a tile-returning
lightweight function with
materialize=Trueto produce a bytes-carrying tile, or write via any raster writer (raster_gbx,gtiff_gbx,cog_gbx) and read the output back — every writer is a materialization boundary.
The lightweight-tier rst_* functions accept three optional force-output params (virtualize_dir,
virtualize_prefix, materialize) to control this; the heavyweight tier has none of them. See
Virtual-tile force-output params for the full param
reference.
Virtual↔materialized advice
When working with virtual tiles in the lightweight tier, the key question is: which operations need pixels, and which can stay lazy?
| Operation type | Behavior under auto | Notes |
|---|---|---|
Metadata accessors (rst_width, rst_height, rst_srid, rst_boundingbox, rst_format, rst_georeference, rst_metadata, rst_rotation, rst_scalex, rst_scaley, rst_skewx, rst_skewy, rst_numbands, rst_type, rst_getnodata, rst_upperleftx, rst_upperlefty) | Free on virtual tiles — no pixels read | The header is opened lazily; .read() is never called |
Pixel accessors / stats (rst_avg, rst_min, rst_max, rst_median, rst_pixelcount, rst_summary, rst_histogram, rst_sample, rst_isempty) | Read the window (transient materialize), return scalars/arrays | Pixels are materialized for the computation only; no bytes in the output row |
Reference / passthrough tile ops (rst_clip, rst_setsrid, rst_initnodata, rst_band, identity rst_transform where target CRS == source CRS) | Reference/passthrough class — record instructions or clip references; no new pixels read | rst_initnodata, rst_setsrid, and rst_band record a pending instruction on the virtual tile and stay bytes-free; the instruction is applied at the next read. rst_clip and identity rst_transform record a region reference. None of these ops produce new pixels on a virtual tile. virtualize_dir has no meaningful effect on them. |
Pixel-producing tile ops (slope, aspect, hillshade, terrain, focal, mapalgebra, spectral indices, rasterize, resample, non-identity rst_transform, rst_merge / rst_combineavg / rst_frombands) | Materialize and return bytes | Pass virtualize_dir to write the computed result to a durable path and get a light virtual row — the only way these return a virtual tile |
Writers (raster_gbx, gtiff_gbx, cog_gbx) | Always a materialization boundary | A virtual DataFrame is directly writable — writers auto-materialize; cog_gbx converts whole-file virtual tiles path-direct (no bytes round-trip) |
| Crossing to heavy | Materialize first | Heavy consumes only materialized tiles — stay in light (recommended), or materialize first (materialize=True, or write + read back) before handing off |
Short rule: chain deferrable ops as long as you like; when you need pixels (or need to cross to heavy), materialize at that point — not before.