Skip to content

Repository files navigation

geolibre-rust

npm version PyPI version npm downloads CI license Open In Colab

A pure-Rust geospatial toolkit for GeoLibre, built on opengeos/whitebox-wasm (the WASM-ready fork of whitebox_next_gen) and compiled to WebAssembly. It is a superset of whitebox-wasm: everything that package offers, plus new GeoLibre-authored tools.

The published npm package (geolibre-wasm) ships two layers:

  • Browser library (. export, wasm-bindgen) -- typed in-memory APIs for GeoTIFF/COG read+write, projections, vector, LiDAR, and topology (GeoTiffReader, CogBuilder, CogStream, ...). Same surface as whitebox-wasm.
  • Tool runner (./tools export, WASI) -- the whitebox tool registry plus GeoLibre's own tools, run over an in-memory /work filesystem via @bjorn3/browser_wasi_shim.

No server, no GDAL, no native install. Use it from JavaScript (npm geolibre-wasm) or Python (PyPI geolibre-wasm). New tools live in the geolibre-tools crate and are registered alongside whitebox's, so GeoLibre sees them through the same interface as the built-ins.

Try it in the browser

demo/index.html is a self-contained page that loads every tool manifest, renders a parameter form for whichever tool you pick, and runs it on a sample DEM (or your own GeoTIFF) entirely in the browser via the WASI runner.

./build.sh          # once, to produce npm/geolibre-cli.wasm and npm/tools.mjs
./demo/serve.sh     # serve on http://localhost:8000 (pass a port to override)

Open the printed URL, filter the tool list, fill in the auto-generated form, and click Run to see the exit code, stdout, output files, and a download link. serve.sh stages the runtime (npm/tools.mjs, npm/geolibre-cli.wasm) and the sample raster (examples/sample.tif) next to the page in a temp directory, so the repo's demo/ stays clean; Ctrl-C stops the server and cleans up.

Self-host with Docker

The same demo ships as a container image so you can host it yourself. The image is a static site (nginx) — every tool still runs in the visitor's browser, so there's no server-side compute, GDAL, or database.

Pull the published image with Docker Compose:

docker compose up -d        # serves on http://localhost:8080

Or build and run it straight from source (needs only Docker — the Rust/WASM toolchain lives inside the build):

docker build -t geolibre-rust .
docker run --rm -p 8080:80 geolibre-rust

Images are published to ghcr.io/opengeos/geolibre-rust on each release (and on v* tags); :latest tracks the most recent release. To build from source via Compose instead of pulling, uncomment build: . in docker-compose.yml.

Architecture

crates/geolibre-wasm   wasm-bindgen browser library  -> geolibre_wasm{.js,_bg.wasm,.d.ts}  (npm ".")
crates/geolibre-cli    WASI tool runner              -> geolibre-cli.wasm + tools.mjs       (npm "./tools")
crates/geolibre-tools  new Tool impls (raster_normalize, ...), registered by geolibre-cli

JS (browser/Node)                WASI binary (geolibre-cli.wasm)
-----------------                --------------------------------
tools.mjs                        crates/geolibre-cli (main.rs)
  write inputs -> /work    -->     argv -> ToolArgs (JSON)
  argv ["slope", "--..."]  -->     ToolRegistry::run
  read new files from /work <--      register_default_tools (whitebox)
                                     + geolibre_tools (new tools)
                                   tool writes via std::fs to /work

GeoLibre-authored tools

In addition to the whitebox suite, geolibre-tools ships cloud-native I/O and rendering tools that the whitebox suite lacks (all pure-Rust, running in WASM):

Tool id What it does
create_underpass Build the mask polygons and cut the decorative gap in the lower feature at a road/rail crossing (like ArcGIS's Create Underpass). GeoLibre already shipped create_overpass, which handles the mirror case — masking the lower line so the upper one reads continuous — and the two are not interchangeable: an underpass communicates by the break itself, so a network styled with only create_overpass renders every grade separation identically, bridge and tunnel portal alike. Masks are rectangles oriented along the above line (margin_along x margin_across); the below geometry is then clipped against them with a Liang-Barsky pass in each mask's local frame, and overlapping masks merge so closely-spaced crossings cut one clean gap instead of leaving slivers. min_angle skips near-parallel crossings, where the mask orientation is unstable and the resulting gap meaningless. Emits both the masks and the trimmed lines, with both participants' feature ids. ArcGIS's where_clause is not implemented — filter the above layer upstream.
euclidean_direction The compass azimuth from every cell toward its nearest source cell, plus the companion distance and back-direction rasters (like ArcGIS's Euclidean Direction). The bundled suite shipped two thirds of this family — euclidean_distance (how far) and euclidean_allocation (which source) — but never the bearing, so evacuation-direction surfaces, orientation toward an outlet, and wind-fetch bearing to the nearest shoreline could not be expressed at all. The nearest source is found with an exact Euclidean distance transform (Felzenszwalb & Huttenlocher's lower-envelope method) extended to carry the nearest source's coordinates, not just its distance — so bearings are exact rather than chamfer approximations, and anisotropic cell sizes are handled by scaling each axis into map units. Degrees run clockwise from north with 0 reserved for source cells and due north reported as 360, matching ArcGIS so results are directly comparable. Supplying barriers invalidates the straight-line transform, so the tool switches to a multi-source Dijkstra over the 8-connected grid that routes around obstacles; cells enclosed by barriers stay no-data.
block_statistics Reduce a raster over non-overlapping blocks and write each block's value back to every cell in it, at the input resolution (like ArcGIS's Block Statistics). This fills the missing quadrant of the neighbourhood-reduction family: focal_statistics uses an overlapping moving window, zonal_statistics/zonal_characterization need an explicit zone layer, and cell_statistics reduces across a stack. The bundled aggregate_raster is not a substitute — it changes the cell size and offers only mean/sum/min/max/range, whereas keeping the original grid is the entire point: input - block_statistics(input, mean) is the standard local-residual/anomaly surface, and expressing it through aggregate_raster needs a resample round-trip that reintroduces interpolation error. Adds the categorical statistics that family lacks (majority, minority, median, variety), with ties resolved to the lowest value so results are reproducible across platforms and WASM. Neighbourhoods: rectangle, circle, annulus, wedge; the kernel-file irregular/weight shapes are rejected with a clear error rather than silently falling back to a rectangle.
focal_flow Encode, per cell, which of its eight neighbours flow into it as an 8-bit mask (like ArcGIS's Focal Flow). The bundled hydrology suite is uniformly downsloped8_pointer, d8_flow_accum, dinf_flow_accum, fd8_flow_accum, mdinf_flow_accum, rho8_flow_accum all answer where a cell sends its water. This answers the inverse, and is not a D8 pointer read backwards: it is not restricted to one steepest direction, so every neighbour above threshold is recorded and convergence into pits, sinks and saddles is captured where D8 collapses it to a single arbitrary path. Bit weights run counter-clockwise from east (1, 2, 4, 8, 16, 32, 64, 128); 0 is a local high and 255 a pit, matching ArcGIS. Off-grid and no-data neighbours contribute no bit. Applies to any continuous surface, not just elevation.
summarize_categorical_raster Tabulate the cell count, area and share of every class in a categorical raster, one row set per slice for multi-band stacks, optionally restricted to an area of interest (like ArcGIS's Summarize Categorical Raster). The nearest bundled tool, raster_area, writes class totals back into the raster cells rather than emitting a table and is strictly single-band, so it cannot produce the per-date class-area series that every land-cover-change chart is built from — which matters because GeoLibre already ships the tools that produce such stacks (analyze_changes_ccdc, landtrendr, interpolate_from_spatiotemporal_points) and had nothing to summarise one. cross_tabulation compares two rasters against each other and zonal_characterization demands a zone layer; neither gives per-slice totals for a single stack. Cell area is integrated per row for geographic (EPSG:4326) rasters, since a cell's ground area shrinks toward the poles — treating degrees as a constant area is the classic bug here and is pinned by a unit test.
kernel_interpolation_with_barriers Interpolate points with a moving-window kernel-weighted polynomial fit in which distance is the shortest path around absolute barriers (like ArcGIS's Kernel Interpolation With Barriers). GeoLibre ships diffusion_interpolation_with_barriers, the other barrier-aware interpolator, and they are genuinely different: diffusion integrates a heat equation to steady state with barriers as no-flux boundaries, while this measures geodesic distance and then applies an explicit, inspectable kernel — local_polynomial_interpolation with straight-line distance swapped for around-the-barrier distance. ArcGIS ships both for that reason. Every other interpolator in either registry (idw_interpolation, radial_basis_function_interpolation, thin_plate_spline, natural_neighbour_interpolation, the kriging family, local_polynomial_interpolation) uses Euclidean distance, so all of them happily interpolate a shoreline salinity value straight across a headland or a groundwater level across a fault. One bounded Dijkstra per observation supplies the distances; the local order-0/1 fit is a 3x3 normal-equation solve with a ridge term, so no linear-algebra crate is needed. Cells enclosed by barriers with no observation inside are written no-data rather than silently extrapolated.
generate_subset_polygons Partition a dense point layer into compact, non-overlapping polygons that tile its extent, each holding between a minimum and maximum number of points (like ArcGIS's Generate Subset Polygons) — the standard preprocessing step for interpolating a very large point set. Each near miss fails differently: voronoi_diagram gives one cell per point rather than a grouping, the bundled rectangular_grid_from_*/hexagonal_grid_from_* tile on a fixed geometry and so leave sparse areas empty and dense areas overloaded, build_balanced_zones balances an attribute under contiguity constraints (a far heavier optimisation), create_spatially_balanced_points selects a sample instead of partitioning, and group_by_proximity returns no polygons and honours no size bounds. The split is a k-d tree cut at the median coordinate rather than the geometric midpoint, so both halves get roughly equal counts regardless of density gradient, and it stops before either child would fall under the minimum. Where the two bounds genuinely cannot both hold, the undersized subsets are reported rather than silently violating the minimum. Deterministic — no RNG, so WASM and native agree exactly.
inside_3d Determine which 3D features fall inside closed volumetric containers, with an optional contained length or vertex fraction (like ArcGIS's Inside 3D). Rounds 14-15 built a real 3D suite — buffer_3d, near_3d, idw_3d, simplify_3d_line, minimum_bounding_volume, generate_points_along_3d_lines — yet the most basic volumetric question was unanswerable, because every containment predicate in either registry (select_by_location, spatial_join, clip) projects to the XY plane: a sensor 200 m above a footprint tests as inside the building, and a pipe beneath a parcel tests as inside the parcel. Containment is ray casting with Möller-Trumbore triangle intersection, and the trap it has to survive is not exotic — any axis-aligned ray through the middle of a quad face strikes that face's diagonal and is counted by both adjoining triangles, flipping the parity. Hits are therefore taken with an inclusive edge test and deduplicated by ray parameter, collapsing each shared-edge hit back to the single crossing it physically is. Ray directions are derived from the query point's own coordinates rather than an RNG, so WASM and native agree exactly. Line targets are clipped span-by-span for a true contained length; solid targets get the vertex-based verdict (exact solid intersection is union_3d's job).
voxel_isosurface Extract a triangulated isosurface at one or more thresholds from a 3D voxel field (like ArcGIS's Export Voxel Isosurface). GeoLibre could already build 3D fields — idw_3d, interpolate_from_spatiotemporal_points, the kriging family — but had no way to turn one into geometry you can look at, because the whole contouring suite (contours_from_raster, contour_with_barriers, percentile_contours, volume_percentile_contours) extracts lines from a plane. Deliberately marching tetrahedra, not marching cubes: the classic 256-entry case table has ambiguous configurations that leave holes unless resolved consistently, and a hand-transcribed table cannot be verified by inspection, whereas splitting each cell into six tetrahedra leaves only two topological cases, no ambiguity, and a watertight result — asserted directly by a test that every edge is shared by exactly two triangles. Polygon order comes from tet topology rather than a geometric sort, because a tetrahedron's four crossing points are not coplanar and sorting them by angle around their centroid tears the mesh. Winding is set by orienting each triangle against the local field gradient, so there is no winding table to get wrong. Vertices are placed by linear interpolation (midpoints make surfaces blocky) and welded on edge identity, sidestepping float equality. Output is the triangle-mesh convention of buffer_3d/minimum_bounding_volume, so it feeds straight into inside_3d.
union_3d Combine overlapping closed 3D solids into a single overlap-corrected volume, plus per-pair overlap volumes (like ArcGIS's Union 3D). The 2D overlay suite is complete (union, intersect, erase, identity, symmetrical_difference, clip) but has no 3D counterpart anywhere, and the cost is concrete: the moment solids overlap, volumes stop being additive, so summing buffer_3d capsules around a pipe network or per-source plume envelopes double-counts every intersection, while polygon_volume and surface_volume only measure against a reference plane. Per-solid volumes are exact (signed-tetrahedron summation) and disjoint groups take that exact path; the union itself is estimated by voxel occupancy over a shared grid whose voxels are kept cubic regardless of the bounding box's aspect ratio, with the resolution reported alongside the answer rather than the accuracy being implied. This is a deliberate scope choice: an exact mesh boolean's arrangement and coplanar-face handling is a floating-point robustness minefield, most callers want the number rather than the merged mesh, and the estimate provably converges (a test asserts finer sampling is closer on a non-convex L-shaped union). Open meshes bound no volume and are skipped and counted, not silently measured.
simplify_by_circular_arcs Simplify lines and polygons by replacing vertex runs with straight segments and true circular arcs (like ArcGIS's Simplify By Straight Lines And Circular Arcs; mode=tangent covers Simplify By Tangent Segments). Every simplifier in either registry — simplify_features, simplify_shared_edges, simplify_building, simplify_3d_line, smooth_natural_features — is vertex-removal only, which is the wrong model for built infrastructure: cul-de-sac bulbs, highway curves, roundabouts, cadastral curves and tank footprints are designed as arcs and arrive densified into dozens of near-collinear vertices, so Douglas-Peucker forces a choice between a visibly faceted curve and an unacceptable vertex count. Circles are fitted by algebraic (Kåsa) least squares — a closed-form 3x3 solve — but accepted on the true maximum orthogonal deviation, because the algebraic fit is biased for short low-curvature runs and validating on the real distance is what keeps the tolerance guarantee honest. Candidates are grown greedily as far as tolerance allows; min_arc_angle and max_radius reject near-flat arcs without stopping the search, since swept angle grows with the run and a short candidate is routinely below the threshold even when the full arc is well above it. Fitted arcs are re-densified to a chord tolerance on write, since the vector model has no arc primitive. Features are simplified independently, so use simplify_shared_edges where coverage safety outranks curve fidelity.
multilook Reduce SAR speckle by incoherently averaging adjacent looks in range and azimuth, producing square-pixel intensity imagery (like ArcGIS's Multilook). SAR is the largest domain gap in the catalog — neither registry ships a single SAR/InSAR tool — and this is the entry point of essentially every SAR chain. The bundled speckle filters (lee_filter, enhanced_lee_filter, refined_lee_filter) operate on already-detected intensity, so they had no in-catalog path to the data they were designed for. The one substantive correctness trap: detection (I² + Q²) must happen before averaging — averaging complex samples is coherent summation and gives a completely different result, so a test pins two opposite-phase samples averaging to 1, not 0. Complex single-look data arrives as a two-band I/Q pair, a convention this tool establishes and the rest of the SAR chain inherits. auto_looks derives the look counts from the pixel-spacing ratio so output ground pixels come out square; db output guards log10 against zero, writing no-data instead of -inf, which would otherwise poison every downstream statistic. The equivalent number of looks is reported, since that is the parameter the bundled speckle filters take.
sar_coherence Compute normalised interferometric coherence (and the interferometric phase) between two co-registered complex SAR acquisitions (like ArcGIS's Compute Coherence, with the phase output covering Generate Interferogram). Coherence measures how similar the scattering geometry stayed between passes rather than how bright the scene is, so it detects change that intensity differencing cannot see at all — building collapse, flooding under vegetation, deforestation, landslides, harvest, ice motion — in all weather, day or night. Nothing in either registry uses SAR phase: GeoLibre's change detectors (detect_feature_changes, detect_image_anomalies, landtrendr, analyze_changes_ccdc, image_regression) are all optical/intensity-domain, and the bundled speckle filters work on detected intensity that has already discarded phase. The correctness trap is the numerator: it is the magnitude of the complex sum, not the sum of magnitudes, and the two differ by exactly the phase stability being measured — confusing them yields a map that reads ≈1 everywhere, so tests pin both that a constant phase shift stays fully coherent and that random relative phase decorrelates. bias_correction removes the estimator's known upward bias at small windows. Deliberately scoped: inputs must already be co-registered — co-registration, orbit correction and phase unwrapping need orbit state vectors and external metadata and are separate problems.
generate_breach_lines Emit the polyline along which each depression polygon would be breached to drain over a DEM (like ArcGIS's Generate Breach Lines): for each depression the tool rasterizes the footprint, locates its pit (lowest interior cell), then runs a Dijkstra sweep outward over a per-method cost field — minimum_breaching_cost (cumulative excavation to bring each cell down to the pit level), shortest_path (unit cost) or minimum_elevation_change — stopping at the first cell that lies outside the depression and below the pit, i.e. somewhere the water can actually go, and traces the back-link chain to a Z-valued line carrying BREACH_LEN, MAX_CUT, CUT_VOLUME, INLET_Z and OUTLET_Z. Optional connection_points constrain where a breach may terminate, and max_length leaves over-long depressions unbreached. The bundled breach_depressions_least_cost / breach_single_cell_pits / topological_breach_burn all breach in raster space only — they write a modified DEM and discard the path they carved — so the alignment an engineer actually needs was unavailable. Pairs directly with GeoLibre's delineate_depressions, whose output is exactly this tool's input.
spatially_constrained_multivariate_clustering Partition features into spatially contiguous, attribute-homogeneous regions with SKATER (like ArcGIS's Spatially Constrained Multivariate Clustering): build the contiguity graph (contiguity_edges rook, contiguity_edges_corners queen, or knn for layers whose features do not touch), weight its edges by distance in the optionally z-standardised analysis-field space, build a minimum spanning forest, then repeatedly cut the tree edge whose removal buys the largest reduction in total within-cluster sum of squared deviations. Every cluster is connected by construction — the property k-means cannot give you. constraint (feature_count / attribute_value) with min_constraint / max_constraint skips cuts that would create an undersized cluster and forces extra splits past number_of_clusters when one busts its cap; outputs CLUSTER_ID plus per-variable R², pseudo-F and an optional per-cluster summary table. GeoLibre's multivariate_clustering is unconstrained k-means whose clusters scatter across the map, build_balanced_zones optimises balance rather than homogeneity, and the bundled dbscan/hdbscan/k_means_clustering are density- or distance-based on point geometry — none is a regionalization.
adjust_stream_to_raster Move stream polyline vertices onto the flow path implied by a DEM (like ArcGIS's Adjust Stream To Raster) — the inverse of stream burning, for when the DEM is the more accurate dataset and carving the streams in would gouge false channels across the true valley floor. Derives D8 pointers and flow accumulation, snaps each line's vertices to the nearest channelised cell within snap_distance (nearest, not highest-accumulation: accumulation grows monotonically downstream, so maximising it would drag every node to the downstream edge of its window and shorten the line), then traces the pointer chain between consecutive nodes. Where the trace fails to arrive it falls back to a least-cost route that prefers high-accumulation cells and records a split point, so the failure is visible rather than silently smoothed. Emits Z-valued adjusted lines with SNAP_FROM/SNAP_TO/ROUTED, plus optional stream-cell and D8 pointer rasters. GeoLibre's enforce_river_monotonicity and the bundled burn_streams/burn_streams_at_roads/raise_walls/topological_breach_burn all bend the raster to match the vector; nothing did the reverse.
optimal_corridor_connections Compute the network of fixed-width least-cost corridor polygons connecting every input region across a cost surface (like ArcGIS's Optimal Corridor Connections): a Dijkstra cost-distance sweep from each region, the cheapest meeting cell per region pair, a minimum spanning tree to decide which pairs actually need a corridor (or all_pairs), a back-link trace to the corridor centerline, and a BooleanOps union of per-segment capsules to thicken it to corridor_width with rounded joints. Barriers are impassable and — when no cost raster is supplied — widen the analysis extent, since a barrier is precisely the thing a corridor must detour around. Outputs corridor polygons with ACCUM_COST/LENGTH/AREA plus optional centerlines. GeoLibre's cost_connectivity (ArcGIS Optimal Region Connections) returns zero-width lines, and corridor returns an accumulated-cost surface for one pair at a time, leaving the user to threshold and vectorise by hand; neither produces the ready-to-use corridor network that habitat-connectivity and greenway planning actually deliver.
local_polynomial_interpolation Fit an order-1/2/3 polynomial inside a kernel-weighted moving neighbourhood at every cell (like ArcGIS's Local Polynomial Interpolation), producing a prediction surface, a prediction standard error surface, or a condition number surface. Neighbours come from the vendored kdtree (fixed bandwidth, or adaptive as the distance to the neighbors-th nearest sample); kernels are exponential/gaussian/quartic/epanechnikov/fifth_order/constant; the design is built in local coordinates centred on the target cell, which both conditions the system far better and makes the prediction simply the intercept. The standard error follows from the weighted residual variance and [(XᵀWX)⁻¹]₀₀; the condition number is √(λmax/λmin) of the normal matrix via a cyclic Jacobi sweep (no linear-algebra crate), and condition_number refuses to predict where the local design is worse-conditioned than the threshold. The only polynomial fit anywhere in either registry was the bundled trend_surface, which is a global polynomial — one equation for the whole extent. Reuses the local weighted least-squares machinery already in geographically_weighted_regression/mgwr.
calculate_transformation_errors Fit a similarity/affine/projective transformation from displacement links and report each link's residual plus the overall RMSE (like ArcGIS's Calculate Transformation Errors). Each link line's first vertex is the source and its last the target; the normal equations are solved by Gaussian elimination with partial pivoting (at most 8×8, so no linear-algebra crate), the fitted model is applied back to every source, and the output carries RESIDUAL_X, RESIDUAL_Y, RESIDUAL and ERR_SHARE (that link's share of the total squared error), with a count of links beyond 3σ. Degenerate control (collinear or coincident) is reported rather than silently producing a garbage fit. GeoLibre ships an unusually complete conflation family — transform_features, rubbersheet_features, edgematch_features, align_features, propagate_displacement, integrate, snap_tracks — but none of them told you whether the control you fed them was any good; a single mistyped control point dominates the RMSE and now shows up before the transformation is applied rather than after.
construct_sight_lines Build the observer-to-target sight-line geometry that visibility analysis consumes (like ArcGIS's Construct Sight Lines): pairs observer points with target features by full cross product or by a shared join_field, samples linear and areal targets along their boundary every sample_distance (so a ridgeline or footprint yields a fan rather than one line to its centroid), applies observer/target height offsets to the endpoint Z, and emits two-vertex lines carrying OBSERVER_ID, TARGET_ID, DISTANCE (2D or true 3D) and optionally compass AZIMUTH and VERT_ANGLE. GeoLibre's line_of_sight takes lines and reports visibility along them, and the bundled viewshed/visibility_index work from observer points against a surface — nothing generated the line layer, so line_of_sight was unusable without hand-building its input. This is the missing first step of the pipeline construct_sight_lines → line_of_sight, and it is pure geometry: no surface sampling, no raster work.
dissolve_route_events Merge route events that touch or overlap along the same route into single measure spans (like ArcGIS's Dissolve Route Events): group by route (and, in dissolve mode, by the dissolve-field tuple so non-matching spans never merge), sort by from-measure, and sweep — extending the running span while the next event starts within tolerance of the current end, taking the union of overlapping ranges. concatenate mode instead merges any touching spans on a route and joins the distinct dissolve-field values with a separator. Point events (from == to) fall out of the same sweep, rows without usable measures are skipped and counted rather than aborting the run, and reversed measures are tolerated. This is the cleanup step that belongs immediately after an overlay: the bundled route_event_overlay fragments every span at each boundary, leaving a shattered table full of adjacent duplicate-attribute rows, and nothing in either registry reassembled it. Completes the linear-referencing family alongside create_routes, locate_lines_along_routes and transform_route_events.
stack_profile Sample several surface rasters along the same polyline and emit one long-form table (LINE_ID, FIRST_DIST, FIRST_Z, SRC_ID, SRC_NAME) so the surfaces can be compared in cross-section (like ArcGIS's Stack Profile). The line is densified once at sample_distance (defaulting to the finest input cell size, always including both endpoints) and every surface is read at exactly those stations, bilinearly or nearest, so all series share one distance axis — the property that makes the output joinable and plottable. Stations falling on NoData in a given surface are emitted with a null Z rather than dropped, keeping the axes aligned across surfaces, and a per-surface valid-sample count is reported. The bundled profile samples one surface along a line and image_stack_profile samples a raster stack at points; running profile N times gives N tables on N independently-sampled axes that cannot be joined without resampling, which is exactly the bare-earth-vs-DSM-vs-design-surface comparison this replaces.
subset_features Randomly split a feature layer into a training subset and its complement (like ArcGIS's Subset Features), by percentage or absolute count, via a partial Fisher-Yates shuffle driven by a seeded splitmix64 stream — never rand or Date::now, so the same seed reproduces the same split in the browser and natively. group_field stratifies, preserving each group's share of the training set, which is the difference between an honest validation and one that accidentally holds out an entire class. Both outputs carry every input attribute plus ORIG_FID so results can be joined back, and oversized requests are clamped rather than erroring. The repo has grown a real modelling and validation family — classification_accuracy_assessment, compute_accuracy_for_object_detection, presence_only_prediction, forest_based_forecast, maximum_likelihood_classification, generalized_linear_regression — and every one of them assumed the caller already held out data; the bundled random_sample works on rasters and lidar_classify_subset is lidar-only.
pivot_table Reshape a long-form attribute table to wide (like ArcGIS's Pivot Table): one output row per unique combination of the identity fields, one column per distinct value of pivot_field, filled from value_field, with aggregate (first/sum/mean/min/max/count) resolving cells backed by several source records. Distinct pivot values are collected in first-seen order and sanitised into unique, safe column names; missing combinations are written null rather than zero (a fabricated observation); the column count is capped so a high-cardinality pivot field fails loudly instead of emitting 50k columns. The catalog could already aggregate long-form data (summary_statistics does GROUP BY, the bundled cross_tabulation does raster class tables) but could not reshape it — a hard blocker in practice, since multivariate_clustering, dimension_reduction, spatially_constrained_multivariate_clustering, similarity_search and calculate_composite_index all require one row per feature with one column per variable, while census-by-year, land-cover-area-by-class and sensor-reading-by-parameter tables all arrive one row per entity-variable pair.
merge_divided_roads Replace matched pairs of divided road lanes with single centerlines while preserving intersection connectivity (like ArcGIS's Merge Divided Roads). Candidate pairs must share a merge_field value, run near-anti-parallel, and stay within merge_distance along their whole length — so two roads that merely cross are not merged; the midline comes from sampling both carriageways by fractional arc length and averaging, so correspondence falls out of the parameterisation with no skeletonisation. Junction preservation is the part that matters: node degrees are counted over every vertex, not just endpoints (side streets usually T into a carriageway mid-span), and each node touched by three or more paths gets a connector stub re-attaching it to the new centerline — without it the result would be geometrically prettier and topologically broken. character_field protects features from merging, and optional displacement polygons feed propagate_displacement while a lineage table maps outputs back to sources. Deliberately overlaps collapse_dual_lines_to_centerline, which is a general dual-line collapse (its natural home is hydrography) and is neither carriageway-aware nor junction-preserving.
minimum_bounding_volume Compute the minimum enclosing volume around 3D features (like ArcGIS's Minimum Bounding Volume): convex_hull via an incremental 3D quickhull (seed tetrahedron, then delete each point's visible faces and re-triangulate the horizon), sphere via a Ritter seed over all axis-extreme pairs followed by Badoiu-Clarkson refinement — with the radius taken from the final centre so enclosure is guaranteed by construction rather than carried forward — or an axis-aligned envelope. Works per feature, per group_field, or over all input; volume comes from the divergence theorem and area from the triangle-area sum. Since the vector model has no multipatch type, the solid is written as its triangulated faces (one Z-valued polygon per face, all sharing an MBV_ID), which is renderable and measurable. Every bounding-geometry tool in either registry was strictly 2D — the bundled minimum_bounding_box, minimum_bounding_circle and minimum_bounding_envelope all discard Z — so the volume of a point-cloud cluster, plume or lidar return set was unavailable. concave_hull is deliberately deferred rather than approximated.
generate_points_along_3d_lines Generate evenly spaced points along 3D lines measured in true 3D distance (like ArcGIS's Generate Points Along 3D Lines), by distance, percentage of each line's 3D length, or a per-feature distance_field, with Z interpolated within the segment each sample lands on, optional end points, and an optional CHAINAGE field carrying cumulative 3D distance. Sampling is driven by cumulative path distance rather than a per-segment remainder, so a sample landing exactly on a vertex is emitted once instead of being lost between the two segments that share it. The bundled points_along_lines measures arc length in 2D, so on a steep line the emitted points are unevenly spaced in reality — the error is 1/cos(slope), already ~15% at a 30° grade and worse on pipelines, transmission lines, ski runs and mountain trails, which is exactly the geometry where correct spacing matters because the points drive a profile, an inspection schedule or chainage markers. Reports length_ratio_3d_2d, quantifying what the 2D tool would have got wrong.
split_by_features Clip an input layer against each zone of a polygon split layer, writing one output file per distinct split_field value (like ArcGIS's Split): zones sharing a value are dissolved first (so a county represented by a mainland polygon plus three islands yields one file rather than four that overwrite each other), polygons are cut with BooleanOps intersection, lines with BooleanOps::clip, and points by the shared ray-casting containment test. Zones producing no features are skipped rather than written empty, and the count is reported so a silent miss stays visible; output names are sanitised and de-duplicated, and the format is inherited from the existing writer path (geojson/shp/gpkg/parquet/csv). GeoLibre's split_by_attributes splits by the layer's own attribute and the bundled clip clips against a whole layer at once — neither performs the standard "one file per county / per tile / per basin" bulk export, which needs the split geometry and the split name to come from a second layer.
summarize_percent_change Count incidents from a current and a previous period within each area feature and report COUNT_DIFF, PERCENT_CHANGE and a CHANGE_CLASS (like ArcGIS's Summarize Percent Change), with an optional search_radius that also counts incidents near an area rather than only inside it. One deliberate correctness choice: where the previous count is zero the percent change is written null, not 0 and not 100 — a ratio against zero has no finite value, and reporting one is the standard way this class of tool misleads — and those areas are classed new instead, which is the information the analyst actually wants; a drop to zero is -100 / eliminated. Non-point incident geometry is summarized at its centroid rather than silently dropped. The crime/incident family was well represented (detect_incidents, eighty_twenty_analysis, collect_events, trace_proximity_events, find_dwell_locations) but nothing performed the plainest question in the domain; summarize_within aggregates one point layer so doing it by hand means running it twice and joining, and emerging_hot_spot_analysis answers a far more elaborate question.
idw_3d Volumetric inverse-distance interpolation of 3D point observations into a stack of elevation levels — one raster band per level, with band elevations recorded in the raster metadata (like ArcGIS's IDW3D). The critical parameter is elev_inflation_factor: vertical and horizontal correlation scales in volumetric data differ by orders of magnitude, so z is scaled once, up front, before the 3D kdtree is built and before any distance is computed — an un-inflated distance mixes a sample 500 m away laterally with one 500 m below and produces nonsense, which is why this cannot be faked by running 2D IDW once per layer. Exact hits short-circuit to the sample value, search_radius bounds the neighbourhood, and output_cv_features re-predicts each sample with itself excluded so the reported cv_rmse is honest rather than trivially zero. Every interpolator in both registries was 2.5D — the bundled idw_interpolation, the kriging family, natural_neighbour_interpolation, thin_plate_spline, and GeoLibre's empirical_bayesian_kriging, optimal_interpolation and interpolate_from_spatiotemporal_points all collapse the vertical dimension.
build_seamlines Compute where overlapping rasters should be cut so a mosaic joins along a minimally visible seam (like ArcGIS's Build Seamlines), emitting one non-overlapping seamline polygon per image that together tile the covered extent exactly once — ready to hand to mosaic_with_feathering as per-image clip masks. voronoi assigns each cell to the nearest footprint centroid, order gives priority to the first (or last) raster, and radiometry/edge_detection solve a genuine least-cost seam: within each two-image overlap, a Dijkstra run perpendicular to the line joining the two centroids finds the route minimising the radiometric mismatch |v_a − v_b| it must cross (edge-aware mode discounting that cost where the image already has a strong gradient, so the seam follows real features instead of cutting across them), then each side of that route goes to the nearer image. A per-cell "closest to the consensus" rule cannot work here — for a pair, both values are equidistant from their own mean, so the test is always a tie. min_region_size absorbs isolated specks; regions are polygonised by unioning horizontal runs in a balanced pairwise merge. The bundled mosaic and mosaic_with_feathering decide overlap by simple ordering and blending and never compute where the cut ought to go, which shows badly on date-mismatched or seasonally different imagery.
classification_accuracy_assessment Score a classified map against field-collected reference points and report a confusion matrix with accuracy metrics (like ArcGIS's Compute Confusion Matrix, paired with Update Accuracy Assessment Points): for each point the predicted (map) class is found by a nearest-cell lookup into a classified raster (input) — categorical, so no interpolation — or read from a classified_field the points already carry, then cross-tabulated against the ground-truth class_field (labels rounded to integers) over the sorted set of observed classes. Emits overall_accuracy, per-class producers_accuracy (diagonal ÷ reference-row total) and users_accuracy (diagonal ÷ map-column total), the full confusion_matrix (rows = truth, cols = map), and Cohen's kappa = (p₀ − pₑ)/(1 − pₑ); the reference points are echoed out annotated with REF_CLASS, MAP_CLASS, and CORRECT (1/0). The bundled kappa_index / evaluate_object_classification_accuracy are raster-vs-raster only (they need a fully labelled reference raster), so this point-based accuracy assessment — the workflow practitioners actually use to validate a classifier — was genuinely absent.
diffusion_interpolation_with_barriers Interpolate scattered point samples to a raster by simulating heat diffusion that flows around absolute no-flux barriers rather than through them (like ArcGIS's Diffusion Interpolation With Barriers): samples are rasterized onto the output grid as fixed (Dirichlet) cells, then every free cell relaxes toward the mean of its non-barrier 4-neighbours over number_iterations Jacobi sweeps of the Laplace/heat equation with bandwidth (λ ∈ (0,0.25]) as the per-iteration relaxation weight. Barriers (barriers polylines/polygons) are rasterized into a blocked mask that is treated as a no-flux (Neumann) boundary — blocked and off-grid neighbours are excluded and the local average is renormalised — so influence decays with diffusion distance, and the maximum principle keeps every cell within the sample range. Blocked cells and grid edges reflect; blocked cells are written as no-data. This is a distinct kernel from GeoLibre's own interpolate_with_barriers, which routes influence along the shortest non-barrier path (cost/geodesic-distance IDW/spline) rather than by diffusion, and from the bundled anisotropic_diffusion_filter, which is a raster smoother on an already-dense image rather than a sparse-point interpolator. For a geographic (EPSG:4326) input the diffusion runs in a local metre frame so the stencil is isotropic; the output is georeferenced back to degrees.
compare_spatial_weights Rank candidate spatial-weights conceptualizations by how strongly each captures spatial clustering (like ArcGIS's Compare Neighborhood Conceptualizations): for each analysis field in input_fields × each candidate in methods (knn, fixed_distance_band, contiguity_edges, contiguity_edges_corners, delaunay) it builds the row-standardized neighbour structure and computes Global Moran's I with its normal z-score (Var_norm closed form) and two-sided p-value (Φ via a pure-Rust erf), then emits a table ranked by descending z-score (method, field, morans_i, expected_i, z_score, p_value, mean_neighbors) plus the single best_method (highest mean z across fields). Contiguity methods drop automatically on non-polygon input and point methods on non-point input; degenerate rows (n<3 or zero variance) emit null statistics. Reuses generate_spatial_weights_matrix' neighbour builders to answer the least-guided decision behind global_morans_i/getis_ord_gi_star/local_morans_i_lisa: which W to use. Output is an inspectable table (or CSV).
spatial_eigenvector_filtering Generate Moran eigenvectors (spatial filters) from a spatial weights matrix and append the autocorrelated components as explanatory-variable fields MEV1, MEV2, … (like ArcGIS's Decompose Spatial Structure (MESF) / Create Spatial Component Explanatory Variables): builds a symmetric binary connectivity matrix C from contiguity_edges (rook), contiguity_edges_corners (queen), or symmetrized knn; double-centres it in closed form (B_ij = C_ij − r_i − r_j + g, i.e. M C M); and eigendecomposes B with a pure-Rust cyclic-Jacobi solver (no LAPACK/nalgebra) — plane rotations sweep the off-diagonal to zero, accumulating orthonormal eigenvectors. Each eigenvector's Moran's I is (n/S0)·λ; those with I ≥ min_autocorrelation (default 0.25) are kept, ranked descending, and capped at max_components (default 15). The kept columns are unit-length and mean-zero (orthogonal to the constant vector), letting a plain OLS/GLR model absorb residual spatial autocorrelation without a full spatial-lag/GWR model — the decomposition generate_spatial_weights_matrix never provides. Inputs above 1500 features are rejected (the eigensolver is O(n³)).
enforce_river_monotonicity Carve a DEM so elevations decrease monotonically downstream along mapped river polylines (like ArcGIS Enforce River Monotonicity, 3D Analyst) — a hydro-enforcement step for hydraulic and flood modeling. For each line it bilinearly samples DEM Z at both endpoints, orients the traversal from the higher-Z endpoint to the lower-Z one (so a line drawn either direction yields the same result), densifies to sample_distance (≈ one sample per cell), then marches downstream carrying a running_min elevation ceiling that drops by at least tolerance per step (default 0 = flat-allowed, non-increasing; positive = strictly decreasing) and follows the terrain wherever it descends faster. Each visited cell is carved to min(existing, ceiling) — never raised — leaving all off-river cells untouched. The bundled fill_depressions / fill_burn remove closed pits but do not enforce a monotone downstream river profile along a known river vector. Emits rivers_processed, cells_lowered, max_drop, and total_drop.
calculate_grid_convergence_angle Stamp each feature with the grid convergence angle — the angle between grid north and true north — computed from its centroid longitude/latitude and a central meridian via the transverse-Mercator formula γ = atan(tan(λ − λ0)·sin(φ)), so north arrows, labels, and directional symbols can be rotated correctly on a projected map (like ArcGIS's Calculate Grid Convergence Angle); the central meridian defaults per feature to its own UTM-zone mid-meridian or can be set explicitly, and the result is written as geographic (degrees clockwise from north) or arithmetic (90 − geographic, counter-clockwise from east). This is distinct from the bundled convergence_index, which measures terrain flow convergence on a DEM and says nothing about grid-vs-true north. Pure math with no PROJ; geometry is read as lon/lat degrees (EPSG:4326) and v1 supports geographic input only.
maximum_likelihood_classification Gaussian maximum-likelihood (Bayesian) supervised classifier for a multiband image (like ArcGIS's Maximum Likelihood Classification): from an integer-labelled training raster it estimates each class's mean vector and covariance, then assigns every pixel to the class with the largest Gaussian discriminant `g_c(x)=ln P_c − ½ln
focal_statistics Slide a shaped neighborhood over one raster band and reduce the cells inside it with a single statistic (like ArcGIS's Focal Statistics): the neighborhood is a rectangle (widthxheight cells), circle (radius), annulus (inner_radius..radius), or wedge (radius swept from start_angle to end_angle, arithmetic degrees with 0 = east), precomputed once as a set of (dr, dc) cell offsets. Both numeric statistics — mean, maximum, minimum, median, range, std, sum, percentile (numpy-interpolated at percentile_value) — and categorical statistics — majority, minority, variety (smallest value wins ties) — are supported, so the same tool smooths a DEM and finds the modal class of a land-cover raster. ignore_nodata (default true) reduces over just the valid cells in each window; when false any no-data cell in the window forces a no-data output, and windows with no valid cell always yield no-data. The whitebox bundle ships ~30 fixed image filters and a cross-raster modal overlay but nothing offering shaped neighborhoods or per-window categorical majority/minority/variety over a single raster. Row-parallel via the dispatch_worker fan-out (serial on wasm32). Validated on real dem.tif: the 3x3 rectangle mean matches SciPy uniform_filter to 3e-4 over the valid interior, and majority on a categorical zones raster matches the SciPy modal filter cell-for-cell.
multicriteria_overlay Combine a stack of co-registered criterion rasters into one suitability surface with rank-based MCDA (like ArcGIS's Multicriteria Overlay): each criterion is min-max rescaled to a 0..1 benefit surface, then combined per cell by weighted_sum (Σ wᵢnᵢ), weighted_geometric_mean (Π nᵢ^wᵢ), owa (Ordered Weighted Averaging — criteria re-sorted per cell and combined via order weights that tune ORness/ANDness; equal order weights reduce to the weighted mean), or topsis (closeness D⁻/(D⁺+D⁻) to the positive/negative ideal points, in [0,1]), with per-raster weights and from_scale/to_scale rescaling. Adds the rank-based TOPSIS/OWA methods that fuzzy_overlay (fuzzy logic) and the bundled weighted_overlay/weighted_sum (crisp linear) don't cover; no-data propagates across the stack.
surface_volume Integrate one raster surface against a horizontal reference plane, reporting 2D area, 3D surface area, and volume for the portion ABOVE and/or BELOW the plane (like ArcGIS 3D Analyst's Surface Volume): each qualifying cell contributes cellArea to the 2D footprint, cellArea·√(1 + (dz/dx)² + (dz/dy)²) (its finite-difference tilt) to the draped 3D area, and `
generate_spatial_weights_matrix Emit a reusable long-format neighbour/weights table (origin_id, neighbor_id, weight) from a chosen conceptualization (like ArcGIS's Generate Spatial Weights Matrix): knn, fixed_distance_band, inverse_distance (weight 1/d^exponent), contiguity_edges (rook) / contiguity_edges_corners (queen), or delaunay, keyed by a unique-ID field, with optional row_standardization so each origin's weights total 1. KNN/distance-band search uses the vendored kdtree; contiguity reuses polygon_neighbors' endpoint-keyed shared-edge machinery. The reusable weights artifact the spatial-stats tools (global_morans_i, getis_ord_gi_star, local_morans_i_lisa) each rebuild internally and discard — output is an inspectable table (or CSV), not ESRI's proprietary .swm.
calculate_distance_band Report the minimum, average, and maximum distance at which every feature has at least neighbors neighbours (like ArcGIS's Calculate Distance Band from Neighbor Count): for each feature the vendored kdtree finds the distance to its k-th nearest neighbour (euclidean or manhattan), and the tool returns the min/avg/max of those distances. Using the maximum as a band guarantees no feature is left with fewer than neighbors neighbours — the threshold diagnostic to run before getis_ord_general_g, incremental Moran's I, or a fixed_distance_band weights matrix. generate_spatial_weights_matrix builds the matrix but never reports this band. Optional CSV of the per-feature k-th-neighbour distance.
point_statistics Rasterize a moving-window statistic of a point-feature attribute (like ArcGIS's Point Statistics): for every output cell a circle or rectangle neighborhood of radius collects the values of a numeric field from the points inside it and reduces them to mean/majority/maximum/median/minimum/minority/range/std/sum/variety. Points are scattered onto the cells whose neighborhood contains them, so overlaps are exact; empty-neighborhood cells are no-data. Output is a raster over the input's radius-padded extent, with geographic input handled in a local metre frame. Complements the density-only line_density/heat_map and the vector-only neighborhood_summary_statistics.
kml_to_features Read a .kml file (or a .kmz, which is just a zipped KML) into GIS features (like ArcGIS's KML To Layer): stream every <Placemark> and split its geometry into up to three EPSG:4326 layers — points_output, lines_output, polygons_output — each carrying the placemark's name and description (XML entities and CDATA resolved). <Point>, <LineString>, and <Polygon> (with <innerBoundaryIs> holes) are parsed, coordinates read in KML's lon,lat[,alt] order, and <MultiGeometry> is flattened into one feature per primitive; any layer with no path is returned as a memory:// handle. Pure-Rust quick-xml pull-parsing plus zip (deflate) for KMZ — no GDAL, WASM-clean; KML is a top-tier public interchange format that neither the repo nor the bundled suite read before. Validated on a hand-authored San Francisco sample: 8 placemarks split into 4 points / 2 lines / 2 polygons, all geometries valid, coordinates and the polygon hole preserved exactly (.kmz parses identically).
graphic_buffer Buffer features with a choice of end-cap and corner-join styles (like ArcGIS's Graphic Buffer): cap ∈ {round, square, butt/flat} closes line/point ends and join ∈ {round, miter, bevel} turns corners, with a classic miter_limit ratio (default 10) beyond which a sharp mitered corner is beveled — translated to the offsetter's minimum-sharp-angle threshold via 2·asin(1/miter_limit). The squared/mitered buffer geometry the bundled round-only buffer_vector / multiple_ring_buffer cannot produce: e.g. a square-capped point becomes a 2·distance square (area 4d², larger than the round circle's πd²) and mitered polygon corners stay sharp right angles instead of rounding off. dissolve unions all buffers into one; the default keeps one buffer per feature with attributes. Offsetting + cap/join construction + self-union via geo's pure-Rust Buffer (i_overlay), no GDAL/GEOS.
features_to_gpx Write point features as GPX 1.1 waypoints (<wpt>) and line features as GPX tracks (<trk> with one <trkseg> per line part, exploded into ordered <trkpt> vertices) — the export companion to GPX reading (like ArcGIS's Features To GPX). Chosen attribute fields are mapped onto the standard GPX metadata elements: name_field<name>, description_field<desc>, z_field<ele> (falling back to a geometry Z when present), and date_field<time> (an ISO-8601 timestamp); any field left unset is omitted, so the output is always schema-valid. Serialized with the quick-xml writer (correct attribute/text escaping, no external service); GPX is always WGS84 so inputs are assumed EPSG:4326, and polygon/other geometries are skipped and reported as a count. Provide input and an output .gpx path. Validated on real data: exporting the 109 us_cities points reproduces 109 waypoints whose lon/lat match the source to 0 (exact), a 5-line WGS84 cell-sector layer round-trips to 5 tracks / 10 track points, and every one of 28 movement-track <time> stamps re-parses identical to the input.
reclassify_field Classify a numeric vector attribute into class bins — the vector-attribute twin of the raster slice/reclass family (like ArcGIS's Reclassify Field): read one numeric field, compute class breaks with equal_interval, defined_interval (fixed class width), quantile, natural_breaks (Fisher–Jenks goodness-of-variance-fit), std_dev (sigma-width bands centred on the mean), or geometric_interval (constant-ratio widths, auto-shifted to stay positive), and write a 1-based integer class_field onto every feature, optionally with a break_field recording the upper class limit. Null / non-finite values get a null class rather than poisoning the break fit; the break vector and per-class counts are returned. Feeds the color_polygons / render_vector_png choropleth pipeline — the dedicated classifier the raster reclass family and the multi-purpose transform_fields (bin) never covered (adds Jenks, defined and geometric intervals).
points_to_line Build polyline features from points, one line per distinct line_field value with vertices ordered by sort_field (like ArcGIS's Points To Line): the ArcGIS-idiomatic sibling of points_to_path — parameters use the ArcGIS names line_field / sort_field / close_line, and each output line carries the line_field value under its own field name (plus a point_count) rather than a generic group column. With no line_field all points form a single line; with no sort_field input order is kept; close_line appends the first vertex to close a ring; single-point groups are skipped. Complements the inverse points_along_lines and the line-merging create_routes. Validated on real route-point data: grouped stop points build one line per route, vertex counts equal the per-group point counts, and vertex order matches the sort field exactly.
collect_events Collapse exactly-coincident incident points into one weighted point per location carrying an ICOUNT count of the events there (like ArcGIS's Collect Events): a single pass hashes each point's coordinate (snapped to tolerance map units so genuine duplicates coincide despite float noise) into a map counting occurrences and keeping the first-seen location, then emits one point per unique location with ICOUNT set. The standard preprocessing that turns raw event points into the weighted inputs hot-spot tools expect — where eliminate_coincident_points removes duplicates and aggregate_points merges by a distance threshold into polygons, neither produces the count-bearing point collapse. sum(ICOUNT) always equals the input point count.
slice_raster Classify a continuous raster into N ordinal integer zones (like ArcGIS's Slice): pick a break method — equal_interval (equal-width bands), equal_area (quantile, ~equal cell counts per zone), natural_breaks (Fisher–Jenks dynamic program over a binned histogram, minimising within-class variance, O(k·bins²) and deterministic), geometric_interval (log-spaced widths for skewed data), or std_dev (breaks at mean ± m·(σ·class_interval_size), data-driven zone count) — and emit an I32 zone raster with ids running from base_output_zone. Fills a real gap: the bundled reclass family only offered equal-interval and quantile — natural-breaks (Jenks), geometric-interval, and standard-deviation classification existed nowhere. Feeds render_raster_png.
gpx_to_features Read a .gpx (GPS Exchange Format) file into a waypoint point layer (from <wpt>) and/or a track/route polyline layer (one line per <trkseg> track and per <rte> route), carrying the ele/time/name attributes GPS receivers record (like ArcGIS's GPX To Features). GPX is the single most common consumer / field-GPS exchange format, yet nothing in the repo or the bundled whitebox suite read it. The XML is streamed with the pure-Rust quick-xml pull parser (already in the dependency graph, no C deps), keeping it inside the GDAL/GEOS/PROJ-free WASM-first stack; the ambiguous <name> is routed to its enclosing point/track/route by parse context, and self-closing <trkpt/> elements are handled. GPX is always WGS84, so output is EPSG:4326. Provide points_output, lines_output, or both. Validated on a real hand-authored 5-waypoint / 1-track / 1-route sample: the point count equals the <wpt> count and the track vertex count equals the <trkpt> count.
summary_statistics SQL-style GROUP BY over one or more case fields (like ArcGIS's Summary Statistics; count-only reproduces Frequency): for each unique case-field combination, compute a list of (field, statistic) aggregates — count, sum, mean, min, max, std (sample), first, last — plus a group record count, emitting a pure attribute table (one row per group). mean/std use Welford's online algorithm for one-pass numerical stability; passing no case field yields a single grand-total row. The daily-driver attribute rollup the bundled vector_summary_statistics (single field, whole layer) and cross_tabulation (two-field count) never covered.
median_center The robust geometric-median center (like ArcGIS's Median Center): the Weiszfeld point that minimizes summed (optionally weighted) Euclidean distance to all input points — seeded from the mean center and iterated to convergence. Unlike the outlier-sensitive mean center it resists outliers, and unlike central_feature the result need not be an input point. Optional weight_field, case_field grouping (one median per group), and attribute_fields whose per-group median is appended as <field>_med.
flip_line Reverse the vertex order (start↔end) of polyline features to flip their digitized direction (like ArcGIS's Flip Line): routine direction normalization for digitizing cleanup and hydrologic / transport network editing, where arcs were captured pointing the wrong way (a stream that must flow downhill, a one-way road digitized against its travel direction). Each LineString's coordinate vector is reversed in place — the former end vertex becomes the start — so the geometry traced is identical and only its orientation flips; MultiLineString features reverse both each part and the part order. Non-line features (points, polygons) pass through untouched and attributes are preserved exactly; the run reports flipped_count. The bundled flip_image reflects rasters only — there is no vector line-direction reversal in either catalog. Validated on 291 real road lines: all 291 had their start↔end endpoints swapped, the coordinate list of each exactly equals the reversed input, and every line's length is conserved (max delta < 1e-13).
cell_records_to_sectors Build antenna coverage geometry from cell-tower points (like ArcGIS's Cell Site Records To Feature Class / Generate Sector Lines): from each tower's azimuth, beamwidth, and radius (read per-tower from attribute fields or supplied as scalar defaults) emit a wedge coverage polygon (apex + arc sampled at segments) or a line bisector sector, with omnidirectional beamwidth (≥360°) yielding a full circle. Opens a new domain — nothing in either catalog built azimuth/beamwidth coverage wedges. All tower attributes are preserved and each sector carries its resolved azimuth/beamwidth/radius (and polygon area); pure trigonometry, feeds render_vector_png / vector_to_pmtiles.
estimate_time_to_event Estimate the time until an event via a Kaplan-Meier survival curve (like ArcGIS's Estimate Time To Event): from a duration field and an event/censoring field, build the non-parametric survival function S(t) = ∏(1 − dᵢ/nᵢ) — censored observations shrink the risk set without dropping the curve — and add each feature's survival probability km_survival at its own duration, its stratum's median time-to-event km_median (Null when "not reached"), and its km_stratum label. An optional stratify_field fits a separate curve per group (treatment vs. control). Survival analysis was absent from both catalogs and is distinct from the regression/forecast family already shipped.
transform_route_events Re-reference linear point events measured against one route system onto a different (re-versioned) route system, within a cluster tolerance (like ArcGIS's Transform Route Events): a geometric composition of two classic linear-referencing steps — recover each event's XY by walking its source route (matched on route_id_field) to the event's measure, then relocate that XY onto the nearest target_routes polyline within cluster_tolerance, emitting the target route id (t_route_id), the along-route target measure (t_measure), and the perpendicular offset (transfer_dist). Measures follow the Create Routes LENGTH convention (cumulative planar distance from each route's first vertex; stored M not required); multi-part and multi-feature routes sharing an id are concatenated in input order. Events with no source-route match, an invalid measure, or no target within tolerance are dropped and counted. Distinct from route recalibration (same routes) and the bundled event overlay/split/dissolve tools (single route system). Validated on a real 8-road network: the identity transform reproduces source measures to 9e-10 m and recovers event XY to 4e-9 m over 40 events; a +12 m parallel-shifted target yields perpendicular offsets bounded by the shift.
eighty_twenty_analysis Snap incident points into weighted locations and compute Pareto concentration — "X% of incidents occur at Y% of locations" (like ArcGIS's Eighty Twenty Analysis): a grid-hashed union-find over cluster_tolerance collapses coincident/near incidents into weighted locations (each incident counts 1, or a weight_field value), which are ranked by count so a cumulative-incident walk tags every location with its rank, cumulative_pct, and a head/tail band at the threshold (default 80%) crossover. The concentration statistic the clustering/hot-spot tools (aggregate_points, colocation_analysis, emerging_hot_spot_analysis) never produce.
extract_locations_from_text Scan a blob of free-form text for embedded coordinates and emit one WGS84 point per hit (like ArcGIS's Extract Locations From Text, coordinate branch — the document/address-locator sibling needs a geocoding service and is out of scope): hand-written state-machine scanners recognise decimal degrees (38.8977, -77.0365), degrees-decimal-minutes (38°53.862'N 077°02.190'W), degrees-minutes-seconds (38°53'51.72"N 077°02'11.40"W), and MGRS/USNG grid references (18SUJ2348006479), pairing an adjacent latitude with its longitude and validating MGRS through the same grid math as convert_coordinate_notation. Each point carries the matched substring, its notation, lat/lon, and character offset; a notations filter restricts which patterns run. No regex dependency and no geocoding — where convert_coordinate_notation reformats a coordinate already attached to a feature, this finds coordinates loose in prose.
create_cartographic_partitions Produce adaptive, non-overlapping partition polygons that each hold roughly a target feature_count (denser where the data clusters) so large generalization / masking / conflict-detection jobs can be tiled coverage-safely (like ArcGIS's Create Cartographic Partitions). Each feature is reduced to a representative point (centroid, else bbox centre), then a cell covering the whole extent is recursively median-split (kd-tree style) on its longer axis until every cell holds at most feature_count points — so tight clusters carve into many small cells while empty space stays as one large cell. Because every split cuts a rectangle in two, the leaf partitions exactly tile the extent (no gaps, no overlaps) and the per-partition counts sum to the placed feature count. Complements the uniform fishnet grid_index_features and attribute-rebalancing build_balanced_zones, neither of which is density-adaptive. Deterministic, WASM-safe.
calculate_utm_zone Stamp each feature with the optimal UTM zone (1–60) and WGS84 EPSG code (326## north, 327## south) computed from its centroid, so an adaptive-projection map series can pick the least-distorted projection per sheet (like ArcGIS's Calculate UTM Zone). The zone is floor((lon + 180) / 6) + 1 on the area-weighted centroid, with the two standard exceptions applied: Norway (zone 32 widened west across 6°E for 56°–64°N) and Svalbard (odd zones 31/33/35/37 widened, even zones suppressed, for 72°–84°N). Polygons use their shoelace centroid, lines/points their vertex mean; features with no geometry pass through with null fields. Pure math with no PROJ — where convert_coordinate_notation formats a coordinate string, this assigns a projected-CRS identity. Geometry is read as lon/lat degrees (EPSG:4326); a projected input CRS is rejected.
locate_lines_along_routes Overlay line or polygon features on measured routes to produce from/to-measure line events (like ArcGIS's Locate Features Along Routes for line/polygon events): densify each feature so no gap exceeds the search tolerance, project every sample onto each route, and emit an event carrying RID, FMEAS, and TMEAS for the located interval, with geometry cut from the route so its length equals TMEAS − FMEAS (measure conservation). Complements the bundled locate_points_along_routes (points-only, single-measure) and feeds the route-event/overlay tooling.
optimal_interpolation Correct a background (prior) raster field with point observations via the optimal-interpolation / data-assimilation analysis step x_a = x_b + B Hᵀ (H B Hᵀ + R)⁻¹ (y − H x_b) (like ArcGIS's Optimal Interpolation): the background is bilinearly sampled at each observation to form the innovation y − H x_b, a localized per-cell neighbourhood of nearby observations is gathered with a vendored kdtree radius query out to 3·correlation_length, and a small Cholesky solve of (P + R) w = d — with P = background_error_variance · exp(−½(r/L)²) a Gaussian spatial covariance and R the per-point/obs_error_variance observation error — yields the analysis increment c·w and an optional analysis-error-variance raster σ_a² = σ_b² − c(P+R)⁻¹c (≤ σ_b², equal to it where no observation is in range). Unlike every bundled interpolator (idw, the kriging family, radial_basis_function, natural_neighbor, empirical_bayesian_kriging), which build a surface from points alone, this is the first that blends a prior gridded field with observations — the sequential data-assimilation update.
calculate_adjacent_fields Populate eight directional neighbour-sheet fields (N, NE, E, SE, S, SW, W, NW) on each tile of a map-series index grid with the adjoining page's name — the "continued on sheet …" margin labels a map book needs (like ArcGIS's Calculate Adjacent Fields): reuse polygon_neighbors' endpoint-keyed edge decomposition to find each tile's rook (edge-sharing → cardinal slot) and queen (corner-only → diagonal slot) neighbours, then slot each by the sign/dominant axis of the centroid-to-centroid vector (exact for axis-aligned pages, aspect-ratio-robust; nearest wins on ties). Preserves input geometry and attributes; include_diagonal=false writes only the four cardinal fields; snap_tolerance matches near-coincident borders. The map-book labelling step missing after grid_index_features / strip_map_index_features.
dendrogram Hierarchical class-separability merge tree over cluster signatures (like ArcGIS's Dendrogram). multivariate_clustering and the bundled k_means_clustering emit class assignments with no way to judge whether the classes are actually distinct; this reports, at each average-linkage merge, which classes joined and at what distance, so near-duplicate classes surface as very early merges and min_merge_distance becomes the headline diagnostic. distance = variance normalizes by pooled within-class spread; standardize z-scores each field so a large-range predictor cannot dominate. Optional plain-text tree rendering.
densify_sampling_network Propose new field-sample locations at the cells of highest kriging prediction error, honoring a minimum-spacing inhibition distance (like ArcGIS's Densify Sampling Network). Consumes a prediction standard-error raster — the kind produced by ordinary kriging or the repo's empirical_bayesian_kriging — collects every valid cell as a candidate (optionally clipped to a mask polygon), then greedily accepts the largest remaining error whose cell center is at least inhibition_distance map units from every already-accepted point, until count points are placed. Where create_spatially_balanced_points and create_spatial_sampling_locations spread points to fill space, this targets the least-known areas of a surface so the largest remaining uncertainty is measured first; each output point carries its selection rank and pred_error. Deterministic (max-error-first, no RNG); output CRS inherited from the raster.
feature_vertices_to_points Create a point layer from selected feature vertices — ALL, START, END, BOTH_ENDS, MID (the arc-length midpoint of each line/ring boundary, which need not be a vertex), or DANGLE (line endpoints not shared with any other line's endpoint) — driven by a single point_location parameter (like ArcGIS's Feature Vertices To Points). Each feature is decomposed into parts (one per LineString, per polygon ring with the closing duplicate dropped, or per point); the selector then emits points that carry a parent orig_fid plus all copied source attributes so they join straight back. Where the bundled extract_nodes covers only the ALL case and dangles are otherwise only reported inside topology validation, this materialises every selective mode as a point layer. DANGLE reuses endpoint-degree counting (nodes of degree 1) — the same idea topology validation uses. Validated on real road data: START yields exactly one point per line part, and ALL yields the full input vertex count.
create_overpass Build knockout mask polygons (and optional wing-tick decoration lines) where an above line crosses a below line, so the lower line can be masked at the crossing to read as a bridge/overpass (like ArcGIS's Create Overpass / Create Underpass): find every proper crossing, then emit at each an oriented rectangle centered on the crossing and aligned with the above line — 2·margin_along long by 2·margin_across wide — plus optional perpendicular end-ticks or parallel casing lines. The road-map cartographic primitive that pairs with resolve_road_conflicts.
features_to_gtfs Write point/line GIS features back out as GTFS text files (like ArcGIS's Features To GTFS Stops / Features To GTFS Shapes): the reverse of gtfs_to_features, closing the transit editor round-trip. Point features become stops.txt (stop_id, stop_name, stop_lat, stop_lon); line features become shapes.txt, each LineString exploded into ordered shape_pt_lat/shape_pt_lon/shape_pt_sequence rows with a cumulative haversine shape_dist_traveled in metres. Existing stop_id/stop_name/shape_id attribute fields (exactly what gtfs_to_features emits) are reused so a feed can be imported, edited, and exported without losing its identifiers; otherwise stable sequential ids are generated. Pure quote-aware CSV emit — no dependency, no external service; GTFS is always WGS84 so inputs are assumed EPSG:4326. Provide stops_input, shapes_input, or both, plus an output_dir. Validated on the real BART feed: re-exporting the gtfs_to_features stop points and route shapes reproduces 287 stops and 28 shapes, and every re-emitted stop coordinate matches the original feed to 1e-6°.
adjust_3d_z Apply a Z-unit conversion, vertical offset, and/or multiplicative factor to the Z ordinates of already-3D features (like ArcGIS's Adjust 3D Z): every vertex is transformed as z' = z * unit_conversion * factor + offset, where from_unit/to_unit presets (meters, feet, us_feet, centimeters, millimeters, kilometers, miles, yards, inches) set the unit scale, factor handles vertical exaggeration or a datum scale, and offset is an additive shift in target units. Planimetry (X/Y) and attributes are preserved untouched; 2D vertices pass through unchanged. Complements interpolate_shape (which drapes features onto a surface) as the Z-transform half of a 3D pipeline. Validated on a real Strava GPX summit track (2310 elevation points, meters→feet): output Z equals input elevation × 3.280839895 to machine precision at every vertex, with X/Y unchanged.
attribute_uncertainty Propagate per-feature attribute uncertainty by seeded Monte-Carlo simulation (like ArcGIS's Attribute Uncertainty): each feature's estimate (value_field) is drawn iterations times from a NORMAL or UNIFORM error distribution parameterised by a symmetric error_field (standard error / half-width) or an explicit lower_field/upper_field bound pair (converted to a normal sd via the 95% z-score), then summarised into appended <value>_mc_mean/_mc_std/_mc_p5/_mc_p50/_mc_p95/_mc_cv fields. All randomness is a per-feature-reseeded splitmix64 stream (Box–Muller normals), so results are bit-for-bit reproducible for a fixed seed in native and WASM builds — the error-propagation counterpart to the deterministic point-sampling in create_spatial_sampling_locations, carrying census/survey margins of error forward instead of discarding them.
split_line_at_point Split polyline features at the locations of point features that fall within a search_radius (like ArcGIS's Split Line At Point): each nearby point is projected onto the nearest line segment and, where the projection lands strictly inside the line, a node is inserted and the line is emitted as one LineString feature per resulting sub-segment, each carrying the parent attributes plus src_fid and seg_index. Lines with no point within the radius pass through unchanged. The bundled split_with_lines splits at line intersections and split_vector_lines by max segment length — neither splits at arbitrary point locations (gauges, junctions, access points, sampling stations). Length is conserved (sub-segments sum to the parent line length).
directional_trend Detect a global/anisotropic trend before variography (like ArcGIS's Directional Trend): project sample points onto a chosen compass azimuth and least-squares fit a 1st-3rd-order polynomial of attribute value vs. distance, reporting the coefficients and R² (variance explained). azimuth=determine sweeps 0-179° for the bearing of maximum explained variance; the output points carry proj_dist, value, fitted, and residual — the value-vs-distance table to scatter-plot.
evaluate_bin_sizes Diagnostic sweep of candidate aggregation bin sizes (like ArcGIS's Evaluate Bin Sizes). optimized_hot_spot_analysis, optimized_outlier_analysis and emerging_hot_spot_analysis all bin points before testing but pick the size by internal heuristic with no diagnostic exposed, leaving the modifiable areal unit problem unexamined. Per candidate it reports total bins tiling the extent, occupied bins, occupancy ratio, mean/median/max per bin and the coefficient of variation (which separates a clustered layer from a uniform one). The generated sweep is anchored on the mean nearest-neighbour distance, so it stays scale-appropriate without guessing a magnitude.
excel_to_table Read a worksheet from an .xlsx/.xls/.xlsb/.ods workbook into a non-spatial attribute table for downstream joins and field transforms (like ArcGIS's Excel To Table): sheet selects a worksheet by name or 0-based index, cell_range restricts the imported block in A1 notation (e.g. B2:E40), and field_names_row picks the 1-based header row (0 = no header, auto-named Field1…). Each column's storage type is inferred across the data rows — all-integer → Integer, any float → Float, all-boolean → Boolean, otherwise Text — and empty cells become nulls. Reading spreadsheets is new to the repo and the bundled suite; the pure-Rust calamine reader keeps it inside the GDAL/GEOS/PROJ-free, WASM-first stack. Validated on a 109-city × 6-column workbook: every row and column round-trips, types infer correctly, and the pop_max total (153,835,823) is preserved exactly.
add_surface_information Append per-feature surface statistics from an elevation raster as attribute fields — without changing the geometry (like ArcGIS's Add Surface Information): points get Z; lines/polygons get SLength (3D surface/perimeter length); polygons get SArea (3D surface area, ∑ cell area·√(1+fx²+fy²)); and MIN_MAX_MEAN_Z / MIN_MAX_AVG_SLOPE add Min_Z/Max_Z/Mean_Z and Min_Slope/Max_Slope/Avg_Slope (over line vertices or the raster cells inside a polygon). The pure-annotation counterpart to interpolate_shape (which drapes geometry into 3D): tag existing trails, parcels, or footprints with terrain metrics in place. Select property groups via properties.
create_routes Build m-enabled route features from line features and a route-identifier field (like ArcGIS's Create Routes, Linear Referencing): group the input lines by route_id_field, stitch each route's segments end-to-end (greedy nearest-endpoint walk starting at a free end) into a single ordered polyline, and assign linear measures (m-values) along it. measure_source chooses how measures are derived — LENGTH (0..geometric length), ONE_FIELD (start read from from_measure_field, end = start + length), or TWO_FIELDS (start/end from fields, interpolated by cumulative length) — with measure_factor/measure_offset scaling and shifting them, and ignore_gaps optionally excluding jumps between disjoint segments. Per-vertex m-values are written into the output geometry (OGC m-enabled coordinates) and each route carries from_m, to_m, length, and n_segments. This is the missing entry point of the linear-referencing pipeline — the bundled event/calibration tools assume routes already exist. Validated on a real 16-segment road network: dissolved to 8 routes with to_m − from_m == length exactly for every route and total length conserved.
exploratory_interpolation Run several interpolation methods, leave-one-out cross-validate each, and rank them by prediction accuracy or bias (like ArcGIS's Exploratory Interpolation): for each of idw (inverse-distance weighting), nearest (nearest-neighbour/Thiessen), trend1 (first-order polynomial trend surface) and trend2 (second-order trend surface), hold out every sample in turn, predict it from the rest, and report per-method LOOCV metrics — mean error ME (bias), MAE, RMSE, the error range, and the predicted-vs-observed Pearson r — then rank by the chosen criterion (rmse/mae/me). Optionally fits the winning method on all samples and writes its prediction raster (output_raster). Where the bundled idw_interpolation/kriging/thin_plate_spline/natural_neighbour_interpolation each interpolate with one method, this orchestrates them into one ranked comparison and subsumes a standalone cross-validation request. Trend fits use a hand-rolled normal-equation Gaussian-elimination solve; RMSSE is out of scope for v1 (it needs a kriging prediction variance these deterministic methods don't produce). Verified on 109 US cities: the first-order trend recovers the exactly-linear latitude field to machine precision (LOOCV RMSE 2.3e-14°, Pearson r 1.0, ranked #1) while nearest (1.72°) and IDW (3.07°) trail.
getis_ord_general_g Global Getis-Ord General G — one z-score and p-value for the whole dataset telling you whether the high values or the low values are globally clustered (like ArcGIS's High/Low Clustering (Getis-Ord General G)): G = ΣΣ wᵢⱼxᵢxⱼ / ΣΣ xᵢxⱼ with the analytic Getis-Ord (1992) expectation and randomization variance from the weight sums S0/S1/S2 and the first four moments of x. Weights are distance_band (binary, default max-nearest-neighbour band), k_nearest, or polygon queen/rook contiguity, with optional row_standardize. The global complement to the bundled local getis_ord_gi_star; matches PySAL's esda.G exactly.
matched_filter_target_detection Score every pixel of a multiband image against a known target spectrum using the scene statistics to suppress the background — Constrained Energy Minimization (cem, matched filter CEM(x)=(dᵀR⁻¹x)/(dᵀR⁻¹d) off the scene autocorrelation) or the Adaptive Coherence Estimator (ace, a CFAR coherence in [0,1] off the mean-centred covariance) — with an optional percentile-threshold detection mask (like ArcGIS's Detect Target Using Spectra (CEM/ACE)). Complements the unsupervised detect_image_anomalies (RX, no signature) and the angle-based bundled spectral_angle_mapper/spectral_library_matching (which ignore scene statistics); a pixel matching the target exactly scores 1.0. Covariance/autocorrelation inverted once with a ridge-stabilized Gauss–Jordan solve.
trim_line Delete short dangling line segments — overshoots and spurs — from a vectorized line layer (like ArcGIS's Trim Line): the standard cleanup after raster-to-vector line extraction or topology import, where stray stubs shorter than a dangle_length tolerance hang off the network. Endpoints are snapped onto a grid (snap_tolerance) to build a node graph; a segment's free ends are those touching a degree-1 node. A segment with one free end is a true dangle and is trimmed when shorter than dangle_length; a segment with no free end is interior and always kept however short (it is not a dangle, so this never breaks a network unlike a plain length filter); a both-ends-free isolated short line is kept by default (keep_short, matching ArcGIS's KEEP_SHORT) or removed when keep_short is false. Decisions use the original topology in a single pass. The bundled Whitebox tools do not cover this: fix_dangling_arcs snaps dangles rather than deleting them, prune_vector_streams/remove_short_streams are stream-network specific, and remove_spurs is raster. Validated on a real OSM road network: trimming stub dangles removed only degree-1 segments and left every interior segment (including short ones) intact.
fill_missing_values Estimate and fill null values in a numeric attribute field from spatial (and optionally temporal) neighbours (like ArcGIS's Fill Missing Values): for every feature whose fill_field is null (SQL NULL or a non-finite float), gather neighbouring features that have a value — either the k nearest (neighbourhood = knn) or every feature within search_radius (distance_band), optionally restricted to a time_window around the target — and combine them with a selectable estimator (mean/median/min/max, or temporal_trend, a least-squares line over the neighbours' (time, value) pairs evaluated at the target's own time). Imputed rows are marked with a boolean flag field (default imputed) and the run reports filled_count / remaining_null_count; a null with no eligible neighbour is left null and unflagged. No imputation tool exists in either catalog — the interpolation suite (IDW/kriging/TPS) fits a surface, not an attribute table's nulls. Deterministic (no RNG); brute-force O(n²) neighbour scan. Validated on real US-cities data: with latitude knocked out for 28 of 109 cities and re-imputed from the 8 nearest neighbours, the recovered values match ground truth to MAE 2.6° / RMSE 5.0° over a 51.6° latitude span, the only large errors being the geographically isolated Hawaii/Alaska cities that have no nearby neighbour.
time_series_smoothing Smooth an in-sample time series per location and append the smoothed value (like ArcGIS's Time Series Smoothing): group features by id_field, order each group by time_field, then run a moving_average (window of window points placed backward/centered/forward, partial windows clamped at the ends) or an adaptive-bandwidth local_linear LOESS (nearest bandwidth points, tricube-weighted degree-1 fit evaluated at each point's time) over the ordered series. Geometry and all original attributes are preserved; the smoothed values land in a new output_field. Where the space-time-cube time_series_forecast projects a series ahead, this smooths the observed series in place — nothing bundled did that.
combine Assign a unique integer zone id to each unique combination of values across two or more co-registered categorical rasters (like ArcGIS's Combine): a single cell-wise pass hashes the input-value tuple to a dense 1-based id, emitting a unique-condition-units (HRU) raster plus a value-attribute table (value, count, one column per input). Any cell that is no-data in any input is no-data out. The multi-raster overlay the bundled cross_tabulation (a two-raster contingency table only) can't produce — the standard suitability / hydrologic-response-unit building block.
reproject_raster Reproject (warp) a raster into a target EPSG CRS, with selectable resampling.
assign_projection_raster Assign an EPSG CRS to a raster's metadata without warping its cells (for data whose coordinates are already in that CRS but carry a missing/wrong projection tag).
assign_projection_vector Assign an EPSG CRS to a vector layer without reprojecting its geometries.
assign_projection_lidar Assign an EPSG CRS to a LiDAR point cloud (LAS/LAZ/COPC) without reprojecting its points.
render_raster_png Render a raster band to a PNG through a colormap (viridis/magma/turbo/terrain/grayscale); no-data becomes transparent.
raster_to_tiles Slice a raster into a Web Mercator (EPSG:3857) XYZ PNG tile pyramid for web maps.
write_pmtiles Render a raster into a single PMTiles archive (the Web Mercator PNG pyramid as one file).
vector_to_pmtiles Pack a vector layer (GeoJSON, Shapefile, GeoPackage, FlatGeobuf, GeoParquet, ...) into a single PMTiles archive of Mapbox Vector Tiles, ready to style in MapLibre. Clipping, per-zoom simplification and MVT encoding come from freestiler.
pmtiles_extract Extract a bbox/zoom subset of a PMTiles archive into a new self-contained archive (e.g. an offline basemap from a Protomaps planet build). The browser library exposes the same engine as PmtilesExtractor, driven by host fetch range requests.
spectral_index Compute a spectral index (NDVI, NDWI, NDBI, NBR, EVI, SAVI) from a multi-band raster.
regularize_building_footprints Normalize noisy building footprint polygons into regular shapes (like ArcGIS's Regularize Building Footprint): snap walls to right angles, right angles + 45° diagonals, straighten at any angle, or fit a best-fit circle; features that can't be regularized within the tolerance keep their original shape and are flagged in a status field.
regularize_adjacent_building_footprint Regularize a group of adjacent footprints to one shared orientation so shared walls stay parallel and collinear (like ArcGIS's Regularize Adjacent Building Footprint): derive a group dominant angle θ (wall-length-weighted 4θ circular mean), snap every edge to the group axis set — right_angles {θ, θ+90} or right_angles_and_diagonals (adds θ+45, θ+135) — rebuild each footprint as the intersections of its wall lines, and snap wall offsets to a shared precision grid so nearby parallel walls become collinear. Groups come from a group field or, absent one, from boundary adjacency within adjacency_distance. The group-consistent counterpart of the per-building regularize_building_footprints, which picks a per-building axis and lets neighbours drift out of alignment (on a real 60-building block this cut the dominant-orientation spread from ~12.6° to ~0.5°). Distances in map units.
smooth_natural_features Smooth pixelated polygons and jagged lines (raster-to-vector outputs such as land cover, water bodies, or vegetation masks) into natural-looking curves — like Smoothify: Douglas–Peucker de-noising plus Chaikin corner cutting, with each polygon's original area restored afterwards.
integrate Snap all vertices across all features within a cluster tolerance to a shared location so nearly-coincident shared boundaries become exactly coincident (like ArcGIS's Integrate + the editing Snap tool): grid-hashed union-find vertex clustering → move each vertex to its cluster centroid, then optionally insert T-junction vertices where one feature's vertex lands on another's segment. The topology-cleaning precondition the coverage-safe family (*_shared_edges, polygon_neighbors, eliminate_polygons) all assume — the bundled snap_endnodes only does polyline endpoints.
transform_features Apply a plain affine transform to every geometry in a vector layer (like ArcGIS's Rotate / Mirror / Shift / Rescale, combined): compose a per-axis scale, an optional mirror across the X or Y axis, a counter-clockwise rotation, and a final shift, all pivoting about a chosen anchor (CENTROID bounding-box center, ORIGIN, or an explicit XY point). Handles every OGC geometry type, preserves attributes/feature order/Z-M, and reports area_scale = |scale_x·scale_y| (so rotation/mirror conserve area) — the geometric transform missing from the whitebox suite, where affine exists only inside raster GCP warping.
eliminate_polygons Merge sliver polygons into a neighbor (like ArcGIS's Eliminate): select slivers by maximum area and/or a simple attribute query (with an optional exclude filter to protect features), then dissolve each into the neighbor sharing the longest border or having the largest area. The classic cleanup after an overlay or raster-to-vector conversion; slivers with no polygon neighbor are kept and reported.
eliminate_polygon_part Remove interior holes and small outer parts of (multi)polygons whose area is below a threshold, preserving each feature and its attributes (like ArcGIS's Eliminate Polygon Part): test each ring by absolute AREA (min_area) or PERCENT of the feature's total outer area (percentage); part_option=CONTAINED_ONLY (default) drops only holes, while ANY also drops small exterior parts but never the single largest part. Where the bundled remove_polygon_holes strips all holes unconditionally and eliminate_polygons merges whole slivers into neighbors, this drops only sub-threshold speckle holes/islands — the cleanup after polygonize on raster-derived polygons. Pure geo ring-area arithmetic; non-polygons pass through.
simplify_3d_line Reduce the vertex count of 3D polylines with a Z-aware Douglas-Peucker (like ArcGIS's Simplify 3D Line): the split metric is the perpendicular distance from a vertex to the 3D chord, ‖(p−a)×(b−a)‖ / ‖b−a‖, so a vertex that is planimetrically redundant but vertically significant (a ridge crest under a draped line) survives — where the bundled simplify_features measures deviation in plan view and flattens the profile. Retained vertices keep their Z exactly, endpoints are always kept, z_factor scales Z for mismatched vertical units, and 2D vertices degrade to plain Douglas-Peucker. The repo's first Z-aware geometry operator.
simplify_building Orthogonality-preserving footprint vertex reduction with a minimum-area drop (like ArcGIS's Simplify Building) — the missing middle step of the raster-to-vector cleanup pipeline. regularize_building_footprints fixes edge orientation but never reduces vertex count, and the bundled simplify_features is angle-agnostic Douglas-Peucker that rounds off the right angles making a building read as a building. Here each vertex's removal cost is its boundary displacement scaled by a penalty that rises sharply near 90°, so stair-steps collapse while corners survive. minimum_area drops unrenderable footprints, optionally leaving a point.
simplify_shared_edges Simplify a polygon coverage while keeping boundaries shared between adjacent polygons coincident (like ArcGIS's Simplify Shared Edges): build an arc–node topology, Douglas–Peucker each shared arc exactly once, and reassemble every polygon — so no gaps or slivers open up the way per-feature simplify_features would. Optionally leave the outer boundary untouched, and snap nearly-coincident vertices first.
smooth_shared_edges Smooth a polygon coverage while keeping boundaries shared between adjacent polygons coincident (like ArcGIS's Smooth Shared Edges): the smoothing twin of simplify_shared_edges — build the same arc–node topology, smooth each shared arc exactly once (paek Gaussian-kernel smoothing or bezier Chaikin corner-cutting) with junction nodes pinned, and reassemble — so curving a coverage never opens gaps or slivers the way per-feature smooth_natural_features would. Optionally leave the outer boundary untouched, and snap nearly-coincident vertices first.
cartogram Distort polygons so area is proportional to an attribute value (like ArcGIS's Cartogram toolset): non_contiguous (scale each polygon about its centroid, preserving shape and conserving total area) or dorling (proportional circles placed at centroids with force-directed overlap removal). Output feeds straight into render_vector_png / vector_to_pmtiles.
buffer_3d Volumetric buffers around 3D points and lines (like ArcGIS's Buffer 3D): spheres at points, capsules (round) or cylinders (flat) along segments. The bundled buffer_vector returns a 2D polygon regardless of Z, so the buffer of a line at 100 m and one at 500 m are identical in plan view — useless for clearance envelopes. Since wbvector has no solid type, the result is emitted as a triangulated surface: a MultiPolygon of 3-vertex XYZ triangles that round-trips through GeoJSON. Overlapping buffers are deliberately not unioned (no 3D boolean op exists here) and unioned: false / representation say so in the output rather than hiding it.
build_balanced_zones Group contiguous polygons into balanced zones (like ArcGIS's Build Balanced Zones / Spatially Constrained Multivariate Clustering): a SKATER spanning-tree partition (contiguity graph → minimum spanning forest → greedy edge cutting) that keeps every zone connected while balancing feature count, an attribute sum, or attribute homogeneity. Rook/queen contiguity; deterministic, no RNG. For districting, sales territories, and ecological regionalization.
similarity_search Rank candidate features by attribute similarity to one or more reference features (like ArcGIS's Similarity Search): z-standardize the chosen numeric fields over the combined distribution, build the reference profile, and score every candidate by Euclidean distance or cosine similarity — most / least / both ends, with per-field standardized differences. "Find the tracts most like this one" for site selection and market analysis; the attribute-space ranking the bundled classifiers (which predict classes) don't do.
geographically_weighted_regression Local linear regression with distance-decay kernel weights (like ArcGIS's Geographically Weighted Regression): fit a separate weighted least-squares model at every feature (gaussian or bisquare kernel, fixed or adaptive bandwidth, optionally AICc-optimized), producing per-feature coefficients, local R², residuals and predictions, plus global AICc / R² diagnostics.
mgwr Multiscale GWR (like ArcGIS's Multiscale Geographically Weighted Regression (MGWR)): extends geographically_weighted_regression so every explanatory term gets its own bandwidth instead of one shared bandwidth for all terms — warm-start with single-bandwidth GWR, then back-fit each term's local coefficient against its AICc-optimal (golden-section-searched) bandwidth until the residual sum of squares stops improving. Per-feature coefficients, local R², residuals and a local condition-number multicollinearity diagnostic, plus each term's optimal bandwidth with a local/regional/global interpretation.
hdbscan Hierarchical density-based clustering — HDBSCAN* (like the HDBSCAN option of ArcGIS's Density-based Clustering): core-distance density estimate → mutual-reachability minimum spanning tree → single-linkage dendrogram → condensed cluster tree → excess-of-mass selection, with per-point cluster_id (−1 noise), membership probability, and outlier_score. Handles variable density with no epsilon, unlike the bundled dbscan. Deterministic.
optics_clustering Multi-scale density-based clustering via OPTICS (like the OPTICS option of ArcGIS's Density-based Clustering): computes each point's core distance (to its min_features_cluster-th neighbour, optionally capped at search_distance), builds the reachability ordering, and extracts clusters of varying density with the ξ-steep method (Ankerst et al.) — cluster_sensitivity (0-100, higher → more clusters) maps to ξ = 1 − sensitivity/100. Output copies the points with cluster_id (−1 noise) and reachability. The multi-scale third member of the DBSCAN/HDBSCAN/OPTICS trio the bundled dbscan and authored hdbscan don't cover; algorithm mirrors scikit-learn's OPTICS. Deterministic; O(n²).
colocation_analysis Local colocation quotient between two point categories (like ArcGIS's Colocation Analysis): for each category-A point, the kernel-weighted fraction of its k nearest neighbours that are category B over B's global share (Leslie & Kronenfeld) — CLQ>1 = A drawn toward B, <1 = avoids — with a seeded conditional-permutation p-value and a colocated/isolated class. The asymmetric two-population association the single-population ripleys_k/nearest_neighbour_index can't measure. Deterministic.
ripleys_k Multi-distance point-pattern analysis (like ArcGIS's Multi-Distance Spatial Cluster Analysis / Ripley's K): the K/L function across distance bands with Monte-Carlo complete-spatial-randomness envelopes (deterministic seeded RNG), to detect clustering (L above the envelope) or dispersion (below) across scales. Outputs a distance/observed/expected/envelope table.
incremental_spatial_autocorrelation Global Moran's I across a series of increasing distance bands, with the z-score curve and its first/maximum peaks (like ArcGIS's Incremental Spatial Autocorrelation): binary fixed-distance weights, Esri's randomization variance (S0/S1/S2 + kurtosis), per-band distance, morans_i, expected_i, variance, z, p table. The defensible way to pick a clustering distance band for getis_ord_gi_star, which the single-scale bundled global_morans_i can't give.
central_feature The two Measuring Geographic Distributions members directional_distribution lacks (ArcGIS's Central Feature / Linear Directional Mean): central_feature returns the actual input feature (with its attributes) minimizing total (optionally weighted, euclidean/manhattan) distance to all others; linear_directional_mean gives the circular mean bearing (or undirected orientation), circular variance, and mean length of a set of lines as a single mean-vector line. case_field grouping.
calculate_missing_z_values Fill placeholder or absent Z values along 3D features (like ArcGIS's Calculate Missing Z Values) by interpolating from the valid Z on either side. Interior gaps interpolate against cumulative 2D distance along the feature, not vertex index — index interpolation distorts Z badly on unevenly spaced geometry such as GPS tracks. Leading/trailing gaps have one neighbour only and are extended (extrapolate) or left and counted, so vertices_unfilled distinguishes "nothing to do" from "could not fill". Repairs the input that add_surface_information / polygon_volume / surface_volume otherwise choke on; fill_missing_values imputes attributes, not geometry.
calculate_motion_statistics Annotate timestamped track points with motion statistics (like ArcGIS's Calculate Motion Statistics): group points by track_field, sort by time_field, and add per-point seq, segment and cumulative distance, dt, elapsed time, instantaneous speed, trailing-window avg_speed, accel, bearing (degrees from north), and an idle flag (mover barely moved over the look-back window). Distances are haversine metres for a geographic CRS, CRS units otherwise; original attributes are preserved. The per-point movement derivative the reconstruct/snap/trace track tools don't emit.
sort_features Reorder a vector layer by attribute fields or along a Hilbert space-filling curve (like ArcGIS's Sort, including its spatial-sort methods): method=hilbert sorts by the Hilbert-curve distance of each feature's bounding-box centre over the dataset extent, clustering spatially-near records so write_geoparquet row-group/bbox statistics prune better and vector_to_pmtiles packs more local features per tile; method=attribute sorts by a field:asc/desc list with a spatial tiebreak. Optionally writes the curve index as a field. Reuses the same Hilbert mapping the GeoParquet writer already uses; the bundled suite only sorts LiDAR.
calculate_central_meridian_and_parallels Compute a per-feature central meridian and two standard parallels from each feature's geographic extent (like ArcGIS's Calculate Central Meridian And Parallels), for atlas pages whose extent is too wide or too high-latitude for UTM. Parallels are inset from the north/south edges by standard_offset (default Esri's 1/6 rule). Features straddling the antimeridian are unwrapped before the midpoint is taken — a naive mean of 175°E and −175°E lands on 0°, the wrong side of the planet — and the result is normalized back to [−180, 180]. Completes the map-series helpers alongside calculate_utm_zone and calculate_grid_convergence_angle, feeding grid_index_features / strip_map_index_features page grids.
calculate_composite_index Combine several numeric attributes into a single composite index (like ArcGIS's Calculate Composite Index): per-variable scaling (min-max / z-score / percentile) with an optional :reverse for variables where high = worse, a weighted combination (mean / sum / geometric mean), and output rescaling (0–1, 0–100, or z-score). Emits the index, its index_rank and index_pctl, and a <field>_scaled column per variable. The vector-side sibling of the raster fuzzy_overlay — the standard build for vulnerability / deprivation / SDG indices, which the bundled weighted_overlay/weighted_sum can only do on rasters.
calculate_rates Compute smoothed rates from count and population fields (like ArcGIS's Calculate Rates): crude (count/pop×per), eb_global (Marshall global empirical Bayes — shrink each crude rate toward the global mean, more for smaller populations), or eb_spatial (shrink toward a local reference rate over each area's k-nearest neighbours). Emits crude_rate, smooth_rate, and a Poisson rate_se. The rate-stabilization step the hot-spot statistics (getis_ord_gi_star, local_morans_i_lisa) assume but the bundled suite can't produce — small-population noise no longer dominates the map.
color_polygons Assign each polygon a small integer colour index so no two adjacent polygons share a value (like ArcGIS's Calculate Color Theorem Field): build shared-edge (rook) or shared-corner (queen) contiguity, then colour with the DSATUR heuristic, staying within 4–6 colours on planar maps. Writes a 1-based color_id field for instant choropleth-safe styling of parcels, admin units, or build_balanced_zones output through render_vector_png / PMTiles. Reuses the shared-edge adjacency machinery from polygon_neighbors; snap_tolerance matches near-coincident borders in unclean coverages.
dice Split polygons or polylines with more than a vertex_limit (default 10000) into a grid of smaller pieces (like ArcGIS's Dice), so downstream overlay and tiling don't choke on million-vertex geometries: an adaptive quadtree recursively quarters each oversized feature's bounding box and intersects the geometry with every quadrant (polygon parts via geo BooleanOps, line parts via Liang–Barsky clipping) until each piece is under the limit. Features under the limit pass through untouched; attributes copy to every piece. The vertex-count safety valve the area-based subdivide_polygon and cutter-based split_with_lines don't provide, e.g. before vector_to_pmtiles.
spatial_outlier_detection Score points by their Local Outlier Factor (LOF) — how isolated each point is relative to the local density of its k nearest neighbours (like ArcGIS's Spatial Outlier Detection): computes k-distance, reachability distance, local reachability density, then LOF = mean(LRD(neighbour)/LRD(point)), and flags the top percent_outlier% (or points above an explicit threshold). LOF ≈ 1 is an inlier, ≫ 1 an outlier. The continuous per-point outlier score DBSCAN's binary noise label and the elevation-only lidar_remove_outliers can't give.
bivariate_spatial_association Lee's L global and local statistic for where two continuous variables co-vary spatially (like ArcGIS's Bivariate Spatial Association): row-standardised k-nearest-neighbour weights, mean-centred variables and their spatial lags give global L = Σ lx·ly / (√Σzx²·√Σzy²) and local L_i = n·lx_i·ly_i / (…), with a High-High/High-Low/Low-High/Low-Low class per feature and a seeded permutation-test p-value. The continuous-field counterpart of the categorical colocation_analysis, and the bivariate complement to the bundled univariate global_morans_i/local_morans_i_lisa.
generate_trend_raster Fit a per-pixel temporal trend across a time series of co-registered rasters (like ArcGIS's Generate Trend Raster): linear gives OLS slope, intercept, and R²; mann_kendall gives the non-parametric Mann-Kendall trend test with Sen's slope and a two-sided p-value (tie- and continuity-corrected normal approximation). Pixels with fewer than min_valid valid observations become no-data. The per-pixel temporal trend the bundled trend_surface (spatial polynomial) and change_vector_analysis (two-date) can't produce — the workhorse of NDVI/temperature change monitoring, pairing with spectral_index and detect_image_anomalies.
warp_raster Georeference a raster from ground control points (like ArcGIS's Warp): fit an order 1/2/3 polynomial from col,row,x,y GCP pairs (source pixel → world coordinate) by least squares, then resample the image into a new georeferenced grid (nearest/bilinear), reporting per-GCP residuals and RMS error. The raster half of the conflation/registration story the vector rubbersheet_features already covers — and the only way to georeference scanned maps or drone frames in a stack with no GDAL (the bundled thin_plate_spline interpolates points, it doesn't warp images).
weighted_voronoi Weighted Voronoi (dominance / market-area) allocation raster (like ArcGIS's Generate Weighted Voronoi): assign each cell to the site with the smallest weighted distance — multiplicative (d/w, Apollonius: larger weight → larger territory), additive (d-w), or power (d²-w²). Output is a categorical raster of 1-based site indices over the sites' padded extent; polygonise with raster_to_vector_polygons for vector market areas. The unequal-site version of the bundled voronoi_diagram, producing the curved boundaries no exact-geometry library in the stack can.
pycnophylactic_interpolation Tobler's mass-preserving areal interpolation (like ArcGIS's Areal Interpolation): turn zone-aggregated counts (e.g. census population) into a smooth density raster whose per-zone cell sums still equal the input totals. Zones are rasterised and seeded uniformly, then iterated — 3×3 mean smoothing followed by a per-zone additive mass correction with non-negativity redistribution — until convergence. The smooth alternative to the uniform-density assumption of apportion_polygon; absent from the bundled suite (kriging interpolates point samples, not areal aggregates).
cost_connectivity Least-cost network connecting multiple sites over a cost surface (like ArcGIS's Cost Connectivity / Optimal Region Connections): a multi-source Dijkstra allocates every cell to its nearest source (accumulated cost + back-link); the min-cost crossing between each pair of allocation regions defines their least-cost path; connections=mst returns the minimum spanning tree that connects all sites at minimum total cost, all_neighbors every adjacent-region path. Paths are polylines with from/to ids and cost. The least-cost network the bundled single-source cost_distance and GeoLibre path_distance/corridor can't build — for wildlife corridors and infrastructure planning.
locate_regions Find the best contiguous region(s) of a target area from a suitability raster (like ArcGIS's Locate Regions): best-first region growing from the highest-suitability seeds, where each candidate cell's score blends suitability with a compactness penalty (shape 0..1 → rounder regions), grown to a target cell count; the next region seeds outside a min_distance buffer of the ones already chosen. Output is a raster of 1-based region ids with per-region area and mean suitability. The siting step that turns a fuzzy_overlay/weighted_overlay surface into actual regions — which clump (labels existing regions) and thresholding (fragmented blobs) can't.
edgematch_features Connect line datasets across a tile/sheet boundary (like ArcGIS's Generate Edgematch Links + Edgematch Features): match dangling endpoints (line ends not shared with another feature) one-to-one within a tolerance by distance — optionally disambiguated by attribute agreement on match_fields — then reconcile each pair (midpoint moves both ends to their midpoint, move_endpoint snaps the second onto the first), with an optional links layer for QA. The cross-feature one-to-one matching the blind bundled snap_endnodes lacks; completes the conflation suite (integrate, rubbersheet_features, detect_feature_changes).
landtrendr LandTrendr temporal segmentation of a yearly image series (like ArcGIS's Analyze Changes Using LandTrendr): per pixel, despike then fit a piecewise-linear trajectory by greedy vertex insertion (up to max_segments), and report the greatest disturbance — a drop (direction=loss) or rise (gain) in a vegetation index — as its year (primary output), magnitude, and duration. The per-pixel change-history segmentation the two-date change_vector_analysis and single-trend generate_trend_raster can't do; feed it spectral_index NBR/NDVI stacks for forest-disturbance mapping.
local_outlier_analysis Space-time Local Outlier Analysis — Anselin Local Moran's I on an H3 space-time cube (like ArcGIS's Local Outlier Analysis): bin timestamped points into H3 cells × time steps, standardise, and compute each bin's local Moran's I against its space-time neighbourhood (spatial kring × ± time_window), with a seeded permutation p-value; classify each bin High-High/Low-Low cluster or High-Low/Low-High outlier, and emit one H3 polygon per cell summarising cluster vs. outlier bins over time. The space-time extension of the bundled spatial-only local_morans_i_lisa; reuses the cube machinery from emerging_hot_spot_analysis.
collapse_hydro_polygon Collapse narrow water polygons to centerlines while keeping wide reaches as polygons (like ArcGIS's Collapse Hydro Polygon): trace each polygon's centerline along its principal axis (PCA of the boundary), taking the midpoint of the polygon's perpendicular cross-section at stations spaced sample_distance apart — which also measures the local width — and collapse to a line where the median width is at or below collapse_width, routing wider reaches to an optional retained polygon layer. The width-thresholded polygon→line generalization the line-casing collapse_dual_lines_to_centerline and raster river_centerlines don't do.
change_point_detection Detect abrupt shifts in the time series at each location of an H3 space-time cube (like ArcGIS's Change Point Detection): bin timestamped points into H3 cells × time steps, then segment each cell's series by binary segmentation on a mean-shift or linear-slope-shift cost — accepting splits whose gain beats a BIC-style penalty scaled by sensitivity (method=auto) or taking a fixed num_change_points (method=defined). Each H3 cell reports its change count, the year of its largest change, and the segment means. The temporal segmentation the two-date change_vector_analysis and monotonic Mann-Kendall tests can't do; reuses the cube machinery from emerging_hot_spot_analysis.
time_series_forecast Per-location forecasting on an H3 space-time cube (like ArcGIS's Exponential Smoothing Forecast / Curve Fit Forecast): bin timestamped points into H3 cells × time steps, then forecast each cell's series steps ahead by Holt's exponential smoothing or polynomial (linear/parabolic) curve fits — model=auto picks per cell the lowest hold-out-RMSE model. Each H3 cell reports the chosen model, next-step and final-step forecasts, a 90% confidence half-width, and the validation RMSE. Completes the space-time cube suite with the temporal forecasting the bundled spatial trend_surface can't do.
reconstruct_tracks Turn timestamped points into movement track polylines (like ArcGIS's Reconstruct Tracks / Find Dwell Locations): group by track_field, sort by time_field, split on time/distance gaps, and emit per-track stats (duration, length, mean/max speed, haversine distances for geographic CRS). With a dwells output it also finds dwell locations — where the mover stayed within a radius for a minimum time. The first trajectory/movement tool in the suite; tracks feed straight into vector_to_pmtiles / H3 binning.
emerging_hot_spot_analysis Space-time hot spot trends on an H3 space-time cube (like ArcGIS's Emerging Hot Spot Analysis + Create Space Time Cube): bin timestamped points into H3 cells × time steps, compute the Getis-Ord Gi* z-score per bin over a space-time neighborhood (spatial k-ring × ± time window), run a Mann-Kendall trend test on each cell's Gi* series, and classify every cell into the Esri categories (new / consecutive / intensifying / persistent / diminishing / sporadic / oscillating / historical hot or cold spot, or no pattern). Adds the temporal dimension the bundled spatial-only getis_ord_gi_star lacks; deterministic, no RNG; output H3 polygons render straight through h3_to_vector / PMTiles.
expand_shrink Grow (expand) or shrink selected classes of a categorical raster by N cells, leaving other classes intact (like ArcGIS's Expand / Shrink): iterative 8-connected dilation/erosion where boundary cells adopt the most frequent selected (expand) or non-selected (shrink) neighbour class, with no-data and the raster edge as barriers. The class-aware morphology the binary buffer_raster/nibble can't do — strengthens the raster→vector cleanup pipeline before polygonize.
boundary_clean Smooth categorical-raster zone boundaries and remove classification speckle (like ArcGIS's Boundary Clean / Majority Filter): a majority mode replaces each cell with the most frequent value among its 4- or 8-connected neighbours when that value reaches a majority/half threshold (removing isolated speckle), and an expand_shrink mode smooths boundaries with a priority-ordered expansion pass followed by a shrink pass (larger, smaller, or unsorted zones win). No-data cells are barriers; ties break toward the smaller class. The generalization step the bundled clump/nibble and GeoLibre expand_shrink don't cover — cleans classified rasters before raster_to_vector_polygonsregularize_building_footprints / smooth_natural_features.
solar_radiation Incoming solar radiation (Wh/m²) over a DEM for a date range (like ArcGIS's Raster Solar Radiation): per-cell slope/aspect (Horn), 16-sector horizon shading + sky-view factor, and integration of direct-normal irradiance I0·E0·τ^m projected onto the slope plus an isotropic diffuse term, over sampled days × time steps. Optional direct/diffuse component rasters. The flagship insolation tool the bundled horizon_angle/shadow_image building blocks never integrate into energy units. Deterministic (dates are parameters).
cul_de_sac_masks Scale-aware knockout masks at cul-de-sac bulbs in a road network (like ArcGIS's Cul-De-Sac Masks), completing the masking family with feature_outline_masks and intersecting_layers_masks. A bulb is detected as a terminal loop — an edge returning to its own node, where that node also carries a stem — which is what separates a turning circle from a plain dead-end stub; degree-1 endpoint detection alone would wrongly mask every dangle. symbol_width and margin are page dimensions (page_unit: points/mm/inches) converted through the full chain — page unit to page metres, times reference_scale for ground metres, then map_units_per_meter for CRS units. Skipping the first step is a factor-2835 error for points: a 1 pt casing at 1:24,000 covers 8.47 m of ground, not 24,000.
cut_fill Volumetric change between two DEM surfaces (like ArcGIS's Cut Fill / Surface Volume): a signed elevation-change raster (Δz = after − before, or surface − a reference plane) plus cut, fill, and net volumes (Σ |Δz| × cell area), with optional contiguous cut/fill region labelling and a per-region volume CSV. For earthworks, erosion/deposition, stockpiles, and lidar change detection.
line_of_sight Point-to-point visibility over a DEM (like ArcGIS's Line Of Sight / Construct Sight Lines): for each observer→target pair, walk the DEM along the segment tracking the running maximum vertical angle, and emit the sight line split into contiguous visible and obstructed LineString segments plus a target-visible flag. Answers "can A see B, and where does the view break" — the per-pair vector question the raster viewshed / skyline_analysis can't. Observer/target heights, all-to-all or pair_field matching.
corridor Least-cost corridor between two accumulated-cost surfaces (like ArcGIS's Corridor / Least Cost Corridor): sum two cost_distance rasters into a corridor surface whose every cell is the cost of the cheapest A→cell→B path through it, then optionally threshold (absolute or percent-above-minimum) to the swath of near-optimal routes the bundled cost suite (cost_distance/cost_pathway, which give a single path) can't. Direct (two cost rasters) or convenience mode (one friction raster + two source rasters, accumulated internally). For wildlife corridors and route planning.
interpolate_from_spatiotemporal_points Bin timestamped observations into calendar time slices and interpolate each onto one shared grid (like ArcGIS's Interpolate From Spatiotemporal Points). Every other interpolator in the stack — bundled idw_interpolation, kriging, thin_plate_spline, shipped interpolate_with_barriers — handles a single snapshot only. The shared extent and cell size are what make the slices co-registered and a per-pixel temporal model meaningful, so the output feeds generate_trend_raster / multidimensional_anomaly / analyze_changes_ccdc directly. Monthly/quarterly/yearly bins use real calendar arithmetic, and a slice below min_points emits nodata rather than a misleading surface.
interpolate_shape Drape points/lines/polygons on a surface raster (like ArcGIS's Interpolate Shape + Add Surface Information): densify each geometry at a sample interval, sample Z per vertex (bilinear or nearest), write true 3D geometry, and add surface metrics — z_min/z_max/z_mean, 3D surface length, and length-weighted average slope. The bridge between the repo's raster (terrain) and vector halves — trail profiles, pipeline lengths, slope-aware routing — that no bundled point-sampling tool provides for lines/polygons.
generate_transects_along_lines Walk each line at a fixed interval and emit perpendicular transect lines of a given length, centred or offset (like ArcGIS's Generate Transects Along Lines): the standard sampling structure for shoreline-change (DSAS-style), riparian surveys, and cross-section extraction — the bundled points_along_lines only places points. Each transect carries its parent line id, distance-along-line, and bearing; pairs with interpolate_shape for terrain cross-sections.
collapse_dual_lines_to_centerline Collapse paired dual carriageways into single centerlines (like ArcGIS's Collapse Dual Lines To Centerline / Merge Divided Roads): detect roughly parallel line pairs within a width band (directed-overlap parallelism test on densified vertices, optional attribute match), replace each pair with the ordered midpoint centerline, and snap the new endpoints back to surviving lines so the network stays routable. Completes the road-generalization arc with thin_road_network; the bundled river_centerlines only works from polygons/rasters, not paired line features.
rubbersheet_features Warp a vector layer to align with a target using displacement links (like ArcGIS's Rubbersheet Features + Generate Rubbersheet Links): a Delaunay-TIN piecewise-affine transform (self-contained Bowyer–Watson) that lands control points exactly on their targets and deforms everything between smoothly, with IDW falloff outside the hull (or IDW everywhere). Links come from a links line layer or are auto-generated by matching input to a target. The conflation transform step detect_feature_changes was built to feed — absent from the bundled suite.
detect_feature_changes Match two line datasets and classify each feature's change (like ArcGIS's Detect Feature Changes): each update line is matched to the nearest base line by symmetric discrete Hausdorff distance within search_distance, then labelled unchanged / spatial / attribute / spatial_attribute / new, with unmatched base lines emitted as deleted. Carries change_type, match_id, and match_dist. The vector change-detection / conflation entry point absent from the bundled suite (its change_vector_analysis is raster).
snap_tracks Sequence-aware map matching of GPS tracks to a road network (like ArcGIS's Snap Tracks): a per-track Viterbi dynamic program trading off snap distance (emission) against route continuity (transition =
remove_overlap_multiple Reallocate every overlap among a set of polygons to exactly one feature, producing a gap-free, overlap-free partition of their union (like ArcGIS's Remove Overlap (Multiple)): the incremental geo BooleanOps overlay from count_overlapping_features finds each shared region, then a grid divides it either by centre line (each point goes to the feature it lies deepest inside) or by nearest generator centroid (Thiessen), conserving total area exactly. count_overlapping_features only counts overlaps — nothing in the bundled suite resolves them, as trade areas and service territories require.
forest_based_forecast Non-parametric per-location forecasting via a random forest over lag windows (like ArcGIS's Forest-based Forecast). time_series_forecast offers only parametric models (linear/parabolic/exponential-smoothing/ARIMA), none of which represent threshold effects or regime changes. Predictors are the previous time_window values and forecasting is recursive. Bootstrapping and feature subsetting run off an explicit seed through a splitmix64 generator, since the WASM path has no ambient RNG and reproducibility is required; with validation_steps the forest is refit on the truncated series so the reported RMSE never sees the data it is judged on.
fuzzy_overlay Rescale a raster to a 0..1 fuzzy-membership surface (linear/gaussian/small/large/ms_small/ms_large) or combine several membership rasters with fuzzy AND/OR/PRODUCT/SUM/GAMMA (like ArcGIS's Fuzzy Membership + Fuzzy Overlay): the standard multi-criteria suitability workflow — site selection, habitat modeling — as pure cell-wise math with no-data propagation. The bundled weighted_overlay/weighted_sum are crisp reclass-and-add and fuzzy_knn_classification is a classifier; nothing does fuzzy suitability.
aggregate_points Group points that fall within an aggregation distance of each other into one polygon per cluster (like ArcGIS's Aggregate Points): a grid-hashed union-find single-link clustering (equivalent to DBSCAN with min_samples=1) whose clusters become convex hulls or buffered-union footprints, each carrying point_count and optional per-cluster field sums. The point analogue of aggregate_polygons/delineate_built_up_areas; the bundled vector_hex_binning imposes an arbitrary grid and concave_hull produces a single hull for the whole layer.
generate_od_links Draw straight origin-destination desire lines from origin points to destination points (like ArcGIS's Generate Origin-Destination Links): pair them by a shared id_field, within a search_distance, and/or to the num_nearest (rules combine), each link carrying origin/destination ids and length. The bundled OD tools (network_od_cost_matrix, multimodal_od_cost_matrix) output cost tables, not the geometry used for flow maps and catchment spider diagrams; pairs with render_vector_png/vector_to_pmtiles.
generate_near_table For each input feature, list its closest_count nearest near-layer features (or every near feature within search_radius) as a proximity table (like ArcGIS's Generate Near Table): each row carries IN_FID, NEAR_FID, planar NEAR_DIST, NEAR_RANK, and optional NEAR_ANGLE (arithmetic bearing) and NEAR_X/NEAR_Y, emitted as a Point layer at the matched near location so it renders with render_vector_png. Neighbour search uses the vendored kdtree; a self-join (same layer) never matches a feature to itself. The bundled near writes only a single NEAR_FID/NEAR_DIST per feature — no k-nearest, angle, or near-XY.
near_3d True 3D nearest-neighbour search (like ArcGIS's Near 3D): reports near_dist, the nearest point, bearing/vertical angle and per-axis deltas. The bundled near and shipped generate_near_table measure in plan view, so two utility lines crossing at the same map location but 8 m apart vertically are reported 0 m apart — inverting the answer for the clearance questions 3D proximity exists to settle. Candidates come from a k = 3 kd-tree over near-feature vertices, then the winner is refined exactly against the segment (a vertex-only answer is wrong for long segments). Self-join skips self-matches; missing Z degrades to planar.
neighborhood_summary_statistics For each feature, summarize numeric fields over its neighbours — k nearest, within a distance band, or shared-edge (rook) contiguity — adding <field>_nbr_mean/median/std/min/max/sum and a neighbour count, optionally inverse-distance weighted (like ArcGIS's Neighborhood Summary Statistics). The "spatial lag" columns every weights-based workflow (spatial-regression prep, smoothing, anomaly screening) starts from; the bundled suite computes global/local indices (global_morans_i, getis_ord_gi_star) but never exports the neighbour statistics themselves.
disperse_markers Spread coincident or overlapping point symbols apart to a minimum center-to-center min_spacing (like ArcGIS's Disperse Markers): a grid-hashed union-find clusters points closer than min_spacing, then each cluster's centroid is held fixed while members are laid out on expanded (default, concentric rings + bounded local-repulsion refinement), ring, cross, or square, each sized so every pair clears the spacing. Singletons and non-point geometries pass through untouched; outputs carry orig_x/orig_y/displaced. Keeps stacked incident/well/address points individually visible at map scale — nothing in the bundled suite or aggregate_points/heat_map displaces individual points.
storage_capacity Sweep water-surface elevations over a DEM and report the flooded surface area and storage volume at each level — the stage-area-volume curve used in reservoir and detention design — per polygon zone or over the whole DEM (like ArcGIS's Storage Capacity). One masked pass per level accumulates area = Σ cell_area and volume = Σ(level − z)·cell_area, output as a CSV. The bundled impoundment_size_index only evaluates dam siting; nothing produced the elevation→area→volume table for a given basin. Hydrology-identity fit alongside cut_fill and the depression/sink tools.
fill_spill_merge Route a finite volume of surface water across a DEM to produce realistic lake / inundation extents — volume-aware partial depression filling with spill and merge (a from-scratch port of RichDEM's Fill-Spill-Merge, Barnes, Callaghan & Wickert 2020, written from the paper — no GPL code copied). Water is supplied as a uniform depth (water_level) or a per-cell raster (surface_water); it flows downhill into pits, each depression fills only as far as its water allows, excess spills into the neighbouring depression, and adjacent lakes merge. Outputs the standing water depth, the hydraulic surface (dem + wtd), and a flood-extent mask; grid edges and NoData act as free outlets, with an optional ocean_level for coastal baselines. Unlike the bundled whitebox fill/breach tools (which assume unlimited water and raise every pit to its spill point), this conserves a given water volume — nothing else in the suite does.
find_space_time_matches Match features between two timestamped point layers that fall within a spatial search_distance and a time_window of each other (before/after/either), emitting matched pairs with distance and signed delta_t (like ArcGIS's Find Space Time Matches): crimes↔calls, sightings↔tracks, incidents↔sensor events. The bundled spatial_join/near are space-only and emerging_hot_spot_analysis/reconstruct_tracks handle time within one layer; nothing joined two layers on space × time. Time fields are numeric or ISO-8601.
create_spatially_balanced_points Generate spatially well-spread (quasi-random Halton) sample points inside a constraint polygon, optionally weighted by an inclusion-probability raster, each tagged with a balanced sample_order so any prefix is itself balanced (like ArcGIS's Create Spatially Balanced Points). The bundled random_points_in_polygon is plain uniform (clumps and voids); nothing did balanced or probability-weighted sampling — the standard design for field surveys and monitoring networks. Deterministic (seeded, WASM-safe); pairs with generate_transects_along_lines.
find_dwell_locations Detect where a moving entity stayed put (like ArcGIS's Find Dwell Locations): maximal runs of track fixes staying within distance_tolerance of the run's running centroid for at least time_tolerance. Testing against the running centroid rather than the first fix keeps slow GPS wander around a parked vehicle in one dwell instead of fragmenting it. Emits the constituent fixes, mean centers, convex hulls, or every fix tagged with a dwell id (−1 while moving). The single-track stop primitive the movement suite was missing: find_meeting_locations needs min_participants and cannot see a lone idling vehicle; detect_incidents triggers on attribute conditions, not spatial persistence.
find_identical Find features that are identical on geometry and/or chosen fields, grouping duplicates (like ArcGIS's Find Identical / Delete Identical): a canonical geometry key (all vertices, optionally snapped to xy_tolerance) plus field-value keys, single-pass hashed into groups. report adds dup_group/dup_seq columns; delete keeps the first of each group. The bundled remove_duplicates is LiDAR-only and eliminate_coincident_points is points-only — nothing deduplicated arbitrary vector features after a merge/append.
path_distance Accumulated least-cost distance where each 8-connected step pays the true 3-D surface distance from a DEM (√(planar² + Δz²)) times a slope-dependent vertical factor times friction (like ArcGIS's Path Distance): Tobler's hiking function (default), linear/sym/inverse-linear, or a binary max-slope cutoff. The bundled cost_distance is planar and slope-blind; this extends corridor's Dijkstra with elevation-aware step costs for realistic hiking/wildlife/access travel surfaces.
time_series_clustering Cluster the cells of an H3 space-time cube by the similarity of their time series — raw value, z-normalized profile, or correlation (1 − r) — with deterministic multi-restart k-medoids, so "places that evolve alike" group together (like ArcGIS's Time Series Clustering). Reuses emerging_hot_spot_analysis' H3×time binning, which classifies each cell independently; nothing grouped cells by their whole temporal trajectory. Output is H3 polygons with cluster_id and an is_medoid flag.
trace_proximity_events Find intervals where two moving tracks were within search_distance of each other for at least min_duration (proximity events — convoy/meeting/contact detection), and optionally trace transitive downstream contacts from seed entities with a generation number (like ArcGIS's Trace Proximity Events). Positions are linearly interpolated on the union timeline and the squared separation solved exactly (a quadratic per interval) for the within-distance runs; tracing is a temporal BFS over the event graph. reconstruct_tracks builds the tracks — this analyzes the interactions between them.
find_meeting_locations Detect gatherings — places and time windows where at least min_participants distinct tracks are together (within search_distance) for at least min_meeting_duration (like ArcGIS's Find Meeting Locations): bin timestamped points into time_step slices, cluster each slice by connected components of the within-distance graph (kd-tree + union-find), keep components with enough distinct track_field values, link qualifying components across consecutive slices whose centroids stay within search_distance, then emit a convex-hull area polygon and a representative point per meeting with participants, start_time, end_time, and duration (dropping meetings outside the duration bounds). The N-track co-occurrence the pairwise trace_proximity_events and single-track reconstruct_tracks dwell detection don't cover. Deterministic; distances in map units.
detect_image_anomalies Score every pixel of a multiband image by its squared Mahalanobis distance to the scene's (or a moving window's) band statistics — the unsupervised Reed–Xiaoli (RX) anomaly detector — with an optional percentile-threshold mask (like ArcGIS's Detect Image Anomalies). The bundled remote-sensing suite transforms imagery (principal_component_analysis, minimum_noise_fraction, linear_spectral_unmixing) but never scored anomalies; RX needs no training data and complements spectral_index. Covariance inverted with a hand-rolled Gauss–Jordan solve.
resolve_building_conflicts Displace, shrink, or hide building footprints that graphically conflict with symbolized road barriers (and each other) for small-scale mapping, tagging each with a status (like ArcGIS's Resolve Building Conflicts): barriers are buffered by barrier_width/2 + gap, conflicting buildings are pushed away from the nearest barrier and relaxed apart over several passes, and any that still cannot be placed are shrunk toward min_size or hidden. The displacement-cartography piece completing the generalization family (regularize_building_footprints, delineate_built_up_areas, thin_road_network, collapse_dual_lines_to_centerline).
thin_road_network Generalize a road network for small-scale display (like ArcGIS's Thin Road Network): hide short, low-hierarchy roads while preserving connectivity — a candidate road is thinned only if it is not a bridge in the current network, so the visible network keeps its connected components. Non-destructive (flags each road visible/thinned) or filtered. Pairs with download_osm_vector.
subdivide_polygon Divide each polygon into equal-area parts (a set number, or a target area each) using straight parallel cuts at a given angle (like ArcGIS's Subdivide Polygon): rotate into the cut frame, binary-search each cut position where the area to its left hits the target (monotonic → fast bisection), and clip strips with geo BooleanOps. No bundled equivalent — for parcel pre-division, sampling frames, and splitting oversized polygons for tiling/parallel processing.
split_by_attributes Split a vector layer into one output file per unique value (or combination) of the given field(s) (like ArcGIS's Split By Attributes): group features by key, write output_dir/<sanitized_value>.<format> (geojson/fgb/parquet/shp) preserving schema, geometry type, and CRS, plus a split_summary.csv. The per-class / per-year / per-county partitioning chore no bundled tool does.
polygon_neighbors Build a polygon adjacency (contiguity) table (like ArcGIS's Polygon Neighbors): decompose every boundary into endpoint-keyed edges, and for each pair of polygons emit their shared-border length and node_count (point-touches) — length>0 = edge/rook neighbours, length==0, node_count>0 = corner-only neighbours. both_sides for one or two rows per pair, optional snapping. The contiguity data (for spatial weights, redistricting QA, region-merge) the bundled topology-rule checks never export.
count_overlapping_features Flatten overlapping polygons into disjoint regions, each attributed with the number of features covering it (like ArcGIS's Count Overlapping Features): an incremental geo BooleanOps overlay (intersection/difference) that tracks coverage depth and the covering feature ids per region, with an optional min_count filter and a region→id CSV. For buffer-coverage analysis, service-area redundancy, and imagery-footprint dedup — the bundled overlaps is only a boolean predicate.
non_maximum_suppression Keep, in each overlap cluster, only the highest-confidence_score_field polygon and discard lower-scored detections whose intersection-over-union (IoU) exceeds max_overlap_ratio (like ArcGIS's Non Maximum Suppression): a greedy descending-score sweep with geo BooleanOps IoU (intersection ÷ union) and bounding-box prefiltering, optionally scoped so only same-class_value_field detections suppress each other. The score-based deduplicator for object-detection / OBIA output — count_overlapping_features counts overlaps and keeps them all, resolve_building_conflicts displaces rather than removes, and find_identical matches only exact duplicates. Surviving features keep all input attributes.
apportion_polygon Transfer numeric attributes from a source polygon layer onto a target polygon layer, apportioned by area of overlap (like ArcGIS's Apportion Polygon / Areal Interpolation): each source value is split among the targets it overlaps in proportion to intersection area (or area × a target weight field), normalised so the full value is distributed — dasymetric reaggregation between incompatible zone systems. The value-transfer step tabulate_intersection stops short of; reuses the same geo BooleanOps overlay.
tabulate_intersection Vector-on-vector zonal summary (like ArcGIS's Tabulate Intersection and the core of Summarize Within): apportion a class layer across zone polygons — polygons summarized by intersected area, points by count — reporting the measure, its percentage of each zone, and area-weighted sum_fields. Answers "how much of each class, or how many points, fall inside each zone".
summarize_within Summarize a layer's attributes inside an existing polygon layer (like ArcGIS's Summarize Within), weighting each summarized feature by the fraction of itself that falls inside the zone — intersected area for polygons (geo BooleanOps), clipped length for lines, whole counts for points. Statistics sum/mean/min/max/stddev/count accumulate with a weighted Welford update, and an optional group_field emits one row per category per zone. Because the weight is a fraction of the source feature, zones that tile the input reproduce the original totals exactly. Fills the gap between summarize_nearby (buffers only), tabulate_intersection (areas but no statistics) and the bundled spatial_join (no proportional split).
summarize_nearby Buffer input features at one or more straight-line distances and summarize a second layer within each buffer (like ArcGIS's Summarize Nearby): cumulative distance disks (geo Buffer) around every input feature, then a count of nearby features, intersected area (polygon summaries), and area-weighted sum_fields (sum + mean) — combining multiple_ring_buffer + tabulate_intersection in one pass. Straight-line mode only (driving distance/time need a network service).
directional_distribution Descriptive geographic-distribution statistics (like ArcGIS's Measuring Geographic Distributions): mean center, median center (Weiszfeld), central feature, standard distance (circle), and the standard deviational ellipse (from the coordinate covariance eigenvectors) — with optional weighting and per-group case_field output.
multiple_ring_buffer Buffer features at a list of distances into concentric bands (like ArcGIS's Multiple Ring Buffer): non-overlapping rings (donuts) or nested disks, optionally dissolved across features per distance, with the band distance stored as an attribute. The everyday proximity-zone tool (catchments, impact bands, setbacks).
aggregate_polygons Combine polygons within an aggregation distance into larger polygons (like ArcGIS's Aggregate Polygons): a morphological closing (dilate–erode by half the aggregation_distance) fuses nearby polygons, with minimum-area and minimum-hole-size filters and an optional barrier layer (lines or polygons) that aggregation may not cross. Pairs with regularize_building_footprints; the general-purpose sibling of delineate_built_up_areas. Each output carries a part_count of the source polygons it merges.
delineate_built_up_areas Generalize dense building footprints into smooth settlement / urban-extent polygons (like ArcGIS's Delineate Built-Up Areas): a morphological closing (dilate–erode by half the grouping_distance) fuses footprints within the grouping distance into one built-up area, with rounded, cartographic boundaries. Filter the result by a minimum building count and minimum area, fill small interior holes, and optionally simplify the boundary. The natural sequel to AI building-footprint extraction.
write_geoparquet Convert any supported vector format to GeoParquet, Hilbert-sorted with a bbox covering column and ZSTD compression by default.
read_geoparquet Read GeoParquet and convert it to another vector format (or store it in memory).
vector_convert Convert a vector dataset between formats (the output extension picks the driver).
render_vector_png Draw a vector layer (points/lines/polygons) to a PNG map image.
find_argument_statistics Per-pixel argument statistics over a raster stack — one multiband raster or a list of co-registered rasters (like ArcGIS's Find Argument Statistics): for each pixel find the slice index (or dates value, e.g. day-of-year) of the maximum (argmax), minimum (argmin), or ordered-median (median_position), or the count of slices past a threshold/comparison (duration) or the longest consecutive run that pass (longest_run). Answers "when does NDVI peak", "how many weeks below X", "longest dry spell" — the argument-position questions the value-aggregating bundled cell-statistics and image_stack_profile can't, complementing the per-pixel generate_trend_raster and landtrendr.
las_height_metrics Rasterize per-cell canopy height metrics from lidar heights (like ArcGIS's LAS Height Metrics): bin above-ground point heights into cell_size cells and, for each, emit mean, std, skewness, kurtosis (excess), median absolute deviation (mad), and any height percentiles (e.g. 50/95/99) as a multi-band raster (band order returned in bands). Points below min_height (ground/noise) are excluded and cells below min_points are no-data. Normalize first with height_above_ground. The forestry moment/percentile suite the bundled height_above_ground (heights only) and lidar_point_stats (basic count/elevation) don't emit in one pass.
cell_statistics Per-pixel statistic across a stack of aligned rasters — one multiband raster or a list of co-registered rasters (like ArcGIS's Cell Statistics): for each cell reduce the values at that position across all layers into mean, majority, maximum, median, minimum, minority, percentile (with percentile_value), range, std (population), sum, or variety. ignore_nodata (default true) skips nulls per cell; when false a single null makes the cell no-data. This is the local multi-raster reducer the bundled sum_overlay/weighted_sum (sum only) lack, and the stack-value complement to the neighbourhood focal_statistics (one raster), per-zone zonal_histogram, and position-only find_argument_statistics.
multidimensional_anomaly Per-slice temporal anomaly cube from a raster time stack — one multiband raster or a list of co-registered rasters (like ArcGIS's Generate Multidimensional Anomaly): for each pixel compute a temporal baseline (mean/std or median across the slice dimension) and rewrite every time slice as its deviation from that baseline — difference_from_mean, percent_of_mean, z_score, or difference_from_median — emitting an anomaly stack with one band per input slice. An optional reference_range (1-based inclusive start-end) restricts the baseline to a climatological normal period while anomalies are still emitted for every slice. Unlike detect_image_anomalies (spatial/spectral Mahalanobis in one scene), find_argument_statistics (position, not value), or the bundled overlay ops (which collapse the stack to one layer), this yields the per-slice temporal deviation.
detect_incidents Flag where a condition starts and ends along each track (like ArcGIS's Detect Incidents): group timestamped points by track_field, sort by time_field, and evaluate a small start_condition comparison (<field> <op> <number>, optional two-clause AND) over a numeric attribute — an incident opens at the first matching point and, without an end_condition, spans the maximal run that keeps matching, or with one stays open (points marked ongoing) until the end condition fires. Emits every point copied through with incident id / status / sequence / duration (mode=points) or one polyline per episode (mode=segments). Turns the per-point speed/acceleration from calculate_motion_statistics into discrete episodes; nothing bundled extracts condition intervals along a track.
kernel_density_ratio Relative-risk ratio of two kernel density surfaces (like ArcGIS's Calculate Kernel Density Ratio): evaluate a numerator point layer (cases) and a denominator point layer (population at risk) with the same quartic/biweight kernel and a shared bandwidth, then divide cell-by-cell over the padded union extent. A denominator_floor writes no-data where the population is too thin so no inf/NaN leaks, and log_ratio natural-log-transforms the surface so over/under-representation is symmetric around 0; haversine metres for a geographic CRS. The density-ratio the single-layer bundled heat_map cannot express — the standard epidemiology/crime relative-risk map.
pairwise_comparison_weights Derive criterion weights from an Analytic Hierarchy Process (AHP) pairwise-comparison matrix on Saaty's 1-9 scale (like ArcGIS's Assign Weights By Pairwise Comparison): the weights are the normalized principal eigenvector recovered by power iteration (no linear-algebra crate), emitted as a criterion, weight, rank table with a consistency check — principal eigenvalue lambda_max, consistency index CI = (lambda_max−n)/(n−1), and consistency ratio CR = CI/RI[n] against Saaty's random-index table, warning when CR > 0.1. The matrix is a JSON 2-D array or a labeled/plain CSV (input). The missing front-end of the suitability stack — it derives the weights calculate_composite_index / fuzzy_overlay / bundled weighted_overlay consume.
line_density Density of linear features — length per unit area — in a circular neighborhood around each raster cell (like ArcGIS's Line Density): for every output cell, clip each line segment to the search_radius circle with a closed-form segment-circle intersection, sum the clipped lengths (× an optional weight_field), and divide by the neighborhood area (πr²). Output is a density raster over the input's radius-padded extent, in per-map-unit or km²/mi²/m² units (area_units), with geographic input handled in a local metre frame. The vector-line density the bundled point-only heat_map (KDE) and categorical edge_density can't produce — for road, stream, and fault density.
feature_outline_masks Generate cartographic mask polygons at a margin around features (like ArcGIS's Feature Outline Masks / Intersecting Layers Masks): exact buffers the feature outline, convex_hull dilates the convex hull, and box expands the bounding envelope — each mask fully containing its source and carrying source_fid/mask_kind. An optional masked_layer clips masks to only where a second polygon layer overlaps. The masking toolset the bundled suite lacks (unsharp_masking is an image filter); masks feed straight into render_vector_png label haloes.
intersecting_layers_masks Generate cartographic mask polygons at a margin around features of a masked_layer, but only where they intersect a second masking_layer (like ArcGIS's Intersecting Layers Masks): the coverage-aware, pairwise sibling of feature_outline_masks — mask contour labels only where roads cross them, not everywhere. Each masked feature is bbox-pruned then exact-tested with geo::Intersects; matches emit an exact (buffered outline), convex_hull, or box mask that fully contains its source and carries source_fid/mask_kind. Reports mask_count and non_intersecting_count.
dimension_reduction Principal-component analysis over numeric feature attributes (like ArcGIS's Dimension Reduction): optionally z-score the selected fields, form the correlation (standardized) or covariance matrix, and eigen-decompose it with a hand-rolled cyclic Jacobi rotation — no linear-algebra crate — then project features onto the leading components, appending PC1..PCk scores and emitting an eigenvalue / variance-explained / cumulative / per-variable-loadings report table. Keep count comes from num_components, a min_variance cumulative target, or all components. The bundled principal_component_analysis is imagery-band-only; this decorrelates the attribute inputs that feed calculate_composite_index, similarity_search, and build_balanced_zones.
local_bivariate_relationships For each feature, classify how two variables relate across its local neighbourhood — not significant, positive/negative linear, concave, convex, or undefined (like ArcGIS's Local Bivariate Relationships): gather each feature's neighbors nearest points, fit local linear y=a+b·x and quadratic y=a+b·x+c·x² models, measure dependence by the Gaussian entropy reduction ΔH=-½·ln(1−R²), and test significance with a seeded conditional permutation test before classifying the form from the fits. The local, form-aware complement to the global bivariate_spatial_association (Lee's L) and the continuous-variable counterpart of the categorical colocation_analysis; no bundled tool maps local bivariate form.
grid_index_features Build a map-book / atlas index grid of page-sized rectangles with page names (like ArcGIS's Grid Index Features, plus a mode=strip variant of Strip Map Index Features): tile an extent (explicit or from the input layer) into pages sized by tile_width/tile_height or derived from a paper page_size preset × map_scale, aligned to an origin, named alphanumeric (column letter + row number, A1/B3) or sequential, with intersect_only dropping pages that miss the data (bbox prefilter + geo intersection). Strip mode walks a route line placing overlapping rectangles rotated to the local bearing. Unlike the bundled bare-fishnet rectangular_grid_from_*, it carries real page semantics (page_name, row, col, page_number) for cartographic map-series output.
repair_geometry Detect and fix invalid polygon geometry (like ArcGIS's Repair Geometry / Check Geometry): remove duplicate/consecutive vertices, drop degenerate rings and null/empty parts, re-wind rings to the OGC convention (exterior CCW, holes CW) with geo's Orient, and resolve self-intersections by taking each polygon's geo unary_union self-union — a bow-tie splits into its two clean lobes. Validity is judged by geo's OGC Validation. With check_only it reports a per-feature problem_code/problem_desc instead of editing. The bundled clean_vector only drops bad geometries; this actually repairs them.
convert_coordinate_notation Convert each feature's coordinate between decimal degrees (DD), degrees-minutes-seconds (DMS), degrees-decimal-minutes (DDM), UTM, and MGRS/USNG, writing a new field in the target notation (like ArcGIS's Convert Coordinate Notation): read the point geometry as lon/lat or parse a coordinate string field, and optionally rebuild geometry as an EPSG:4326 point. Pure grid math — a closed-form Krüger transverse-Mercator series (WGS84) for UTM plus MGRS grid-zone/latitude-band and 100km-square lettering — so no PROJ and no new dependency; nothing in the bundled suite converts coordinate strings, its projection tools reproject whole rasters/layers.
interpolate_with_barriers Interpolate point measurements to a raster while respecting absolute barriers — shorelines, ridges, faults, walls (like ArcGIS's Kernel Interpolation With Barriers / Spline With Barriers): rasterize the barrier polylines/polygons into an impassable mask, replace the straight-line sample→cell distance with a cost/geodesic distance from a per-source 8-connected Dijkstra over the free space (the least-cost engine of path_distance/cost_connectivity/corridor), then apply IDW (w=1/d^power) or a first-order local-polynomial Gaussian-kernel fit on those geodesic distances; cells unreachable within radius become no-data. The barrier-aware interpolator the barrier-blind bundled idw_interpolation, kriging, natural_neighbour_interpolation, and thin_plate_spline can't be.
generalized_linear_regression Fit one global regression of a dependent field on explanatory fields by iteratively reweighted least squares (like ArcGIS's Generalized Linear Regression): gaussian (OLS), poisson (log link, counts), or logistic (logit link, 0/1). Writes glr_estimated / glr_residual / glr_std_resid per feature and a full diagnostics report — per-term coefficient, standard error, t/z, probability and VIF for multicollinearity, plus AICc, R²/deviance & McFadden pseudo-R², and the studentized Koenker (Breusch–Pagan) heteroskedasticity test (optional report CSV). The global companion to the local geographically_weighted_regression; the bundled regressions are raster image classifiers, not attribute regression with inferential statistics.
time_series_cross_correlation Per-cell lagged cross-correlation between two aligned space-time raster stacks (like ArcGIS's Time Series Cross Correlation / Image Analyst Multidimensional Raster Correlation): each stack is a multiband raster or comma-separated single-band list; per cell, optionally detrend (per-series OLS) and/or deseasonalize (period season_length), then pair A[t] with B[t+lag] over the valid overlap and compute Pearson r for every lag in min_lag..=max_lag, emitting the best lag and its (signed) correlation — plus optional lag-0 correlation and a pseudo p-value that corrects for serial autocorrelation via the Pyper & Peterman effective sample size and a Fisher z-transform. The two-variable, lag-aware complement to the single-series cube tools (generate_trend_raster, change_point_detection, time_series_forecast); nothing bundled relates two variables through time.
darcy_flow Steady-state groundwater Darcy velocity field from head, transmissivity, and effective-porosity rasters (like ArcGIS's Darcy Flow / Particle Track): apply Darcy's law cell by cell, v = -(T/n)·∇head with a 3×3 Horn finite-difference head gradient, emitting a Darcy-velocity magnitude raster and a down-gradient direction raster (azimuth degrees). With a seeds point layer it also runs advective particle tracking — a midpoint (RK2) integration of the velocity field into one streamline polyline per seed. The first hydrogeology/groundwater tool in the suite; the ~791 bundled whitebox IDs have none.
predict_using_trend_raster Evaluate the slope + intercept rasters fitted by generate_trend_raster at one or more times (like ArcGIS's Predict Using Trend Raster), turning a stored per-pixel trend back into predicted surfaces via v(t) = b + m·t. Times come from an explicit times list or a start/end/interval range; a cell is predicted only where both coefficients are valid, so no-data propagates from either input. The consumer half of generate_trend_raster, whose coefficients nothing else in the registry reads (the bundled trend_surface is a spatial polynomial).
porous_puff Analytic advection-dispersion of an instantaneous contaminant slug through groundwater (like ArcGIS's Porous Puff): march the puff centre from a release point (x/y) along darcy_flow's magnitude/direction velocity field at the retarded seepage speed magnitude/porosity/retardation, accumulating elapsed time and travelled path length L (extrapolating in a straight line once the field can no longer be sampled), then evaluate the closed-form 2-D Gaussian puff C = M·exp(-decay·t) / (2π·σL·σT·n·b·R) · exp(-x'²/2σL² - y'²/2σT²) in path-aligned coordinates, with σL²=2·αL·L, σT²=2·αT·L and n/b (porosity/thickness) either constants or co-registered rasters. A comma-separated time list emits one raster per elapsed time. Completes the groundwater pair with darcy_flow; no bundled whitebox tool models solute transport.
geotagged_photos_to_points Build a WGS-84 point layer from a folder of geotagged photos (like ArcGIS's GeoTagged Photos To Points): walk the folder (optionally recursively), read each JPEG/TIFF's EXIF header — metadata only, no pixels decoded — convert the GPS latitude/longitude degree-minute-second rationals plus N/S/E/W reference to decimal degrees, and emit one point per photo carrying its path, capture datetime, altitude, camera direction (GPS image bearing), and camera make/model. only_geotagged skips images with no GPS fix (or, when false, keeps them with a null geometry). No bundled whitebox-wasm tool reads photo EXIF; output points drop straight into render_vector_png / vector_to_pmtiles.
space_time_kernel_density Kernel density of timestamped points in space and time — one density band per time slice, a multiband raster (like ArcGIS's Space Time Kernel Density): bin the time axis at time_step, then deposit each point through a separable spatial (Epanechnikov/quartic, radius spatial_bandwidth) × temporal (triangular/Epanechnikov, half-width temporal_bandwidth) kernel that is mass-normalized so a band's integrated density equals the weighted count of points inside its temporal window. Distances are haversine metres for geographic input, planar for projected; band time ranges are written to the raster metadata. The time-aware companion to the bundled 2-D heat_map and the density estimator missing from the emerging_hot_spot_analysis space-time-cube suite.
analyze_changes_ccdc CCDC continuous change detection on a dense image time series (like ArcGIS's Analyze Changes Using CCDC): per pixel, fit a harmonic (seasonal) OLS model — constant + linear trend + harmonic_order sine/cosine pairs at the annual frequency, solved from the normal equations with no linear-algebra crate — then slide through time flagging observations whose residual exceeds change_threshold×RMSE and declare a break after min_consecutive flags, refitting a fresh segment from there. Emits a break-count raster plus optional first-break-date and initial-segment RMSE/slope/first-harmonic-amplitude rasters. The seasonal, multi-break change history the yearly landtrendr and single-trend generate_trend_raster can't model and the two-date change_vector_analysis/pca_based_change_detection never attempt.
collapse_road_detail Collapse small road loops and jogs (roundabouts, dogbones, offset jogs) below a diameter threshold into single through-connections for small-scale display (like ArcGIS's Collapse Road Detail): reuse thin_road_network's junction-graph assembly, delete single-feature closed rings under collapse_distance, and for each edge close its smallest cycle via a shortest alternate path — if that loop's bounding diameter is under the threshold, merge its junctions to the loop centroid and reconnect the incident roads there. Merging nodes and dropping only cycle-internal edges keeps the connected-component count unchanged; an optional road_class_field limits collapse to same-class loops. Completes the road-generalization suite with thin_road_network and collapse_dual_lines_to_centerline, which never removed enclosed loop detail.
topo_to_raster Interpolate a hydrologically correct DEM from contour lines, spot-height points, and optional stream lines (like ArcGIS's Topo To Raster, the ANUDEM method): every contour segment and spot height becomes a fixed cell, and the rest of the grid is solved by successive over-relaxation of a blended biharmonic (thin-plate, minimum-curvature) / Laplacian (membrane) operator weighted by tension, so contours are not terraced the way the bundled IDW/TIN/spline interpolators terrace them. Drainage enforcement then removes spurious interior sinks (reusing the Wang & Liu fill behind extract_sinks) so the surface drains to its edges, with optional stream burning. The catalog's first drainage-aware DEM interpolator.
presence_only_prediction MaxEnt-style species/habitat-suitability modelling from presence points + explanatory rasters (like ArcGIS's Presence-only Prediction (MaxEnt)): sample a seeded, reproducible background set over the raster stack, standardise each covariate and apply a linear/quadratic/hinge basis expansion, then fit an L1-regularized (lasso) logistic model of presence-vs-background by proximal-gradient descent (no linear-algebra crate) and predict a probability-of-presence surface plus a variable-importance report. The first fitted suitability model in the suite — turns the un-fitted fuzzy_overlay/calculate_composite_index overlays into a model learned from occurrences, a genuine first for the browser (the ecology community otherwise needs the Java MaxEnt jar or R).
resolve_road_conflicts Displace roads whose symbolized (drawn-width) representations overlap or crowd at a target scale (like ArcGIS's Resolve Road Conflicts / Propagate Displacement): conflicts are detected where two centrelines are closer than half the sum of their symbol widths (symbol_width/symbol_width_field, converted from page mm via scale) plus a gap, and the lower-hierarchy road (hierarchy_field, lower value = more important) is iteratively pushed away along the local normal with endpoint-pinned, smoothed vertex shifts so junctions and connectivity survive — optionally emitting displacement links that rubbersheet_features can propagate onto companion layers. The road-vs-road sibling of resolve_building_conflicts; no bundled tool does displacement cartography.
propagate_displacement Carry the shift resolve_road_conflicts (or any two-point links layer) introduced onto nearby features so spatial relationships survive generalization (like ArcGIS's Propagate Displacement): the sparse links vectors are interpolated into a smooth field with inverse-distance weighting (rubbersheet_features's IDW core, without a border), then applied per feature by adjustment_stylesolid/preserve_orientation rigidly translate the whole feature by the field sampled once or averaged over its footprint (so buildings and parcels keep their right angles), auto (default) does that for points/polygons but bends flexible lines vertex by vertex with arc-length smoothing. search_distance caps how far a link's influence reaches so unrelated features are left in place. The propagation half of ArcGIS's Resolve Road Conflicts pairing that resolve_road_conflicts didn't attempt.
empirical_bayesian_kriging Interpolate a point field via many overlapping local subsets, each fitting its own semivariogram whose uncertainty is captured by simulating perturbed semivariograms from a seeded deterministic RNG, then blending subset kriging predictions/variances by distance to produce a prediction raster plus an optional prediction-standard-error raster (like ArcGIS's Empirical Bayesian Kriging): the point extent is tiled into overlapping local subsets (subset_size/overlap), each subset's power/linear/exponential semivariogram is fit by weighted least squares (a hand-rolled Cholesky solve), simulations perturbed draws are krig-solved (a hand-rolled Gauss–Jordan matrix inverse — no linear-algebra crate), and cells blend their nearest subsets' law-of-total-variance mean/spread. Distinct from the bundled ordinary_kriging/simple_kriging/universal_kriging/ordinary_cokriging, which all fit a single manually-accepted global (or one moving-window) semivariogram instead of simulating model uncertainty from many local ones.
gaussian_geostatistical_simulations Produce num_realizations equiprobable conditional realizations of a field by sequential Gaussian simulation (like ArcGIS's Gaussian Geostatistical Simulations): fit (or supply) an exponential/spherical/gaussian semivariogram, then for each realization visit grid cells in a seeded random order and, at each cell, simple-krige from the nearest max_neighbors known values (conditioning points and cells already simulated in this realization) to get an estimate and variance, drawing N(estimate, variance) with a seeded RNG. Emits an num_realizations-band raster plus optional ensemble output_mean/output_std. Unlike the single smoothed surface from empirical_bayesian_kriging / ordinary_kriging, or the unconditional turning_bands_simulation, each realization honours the data and reproduces the input variogram — the right input for Monte-Carlo uncertainty propagation. Fully deterministic (seeded splitmix64 + Box–Muller, no Date::now); hand-rolled Gaussian-elimination kriging solve, no linear-algebra crate.
exploratory_regression Search over every combination of candidate explanatory fields for a properly-specified OLS model (like ArcGIS's Exploratory Regression): enumerate k-sized subsets of explanatory_fields for k in [min_vars, max_vars], fit each with the same normal-equation OLS core as generalized_linear_regression, and screen on adjusted R², coefficient significance, VIF multicollinearity, Jarque–Bera residual normality, and — only for models that already pass those cheap screens — residual Moran's I over k-nearest-neighbour weights (the same Esri randomization-variance formula incremental_spatial_autocorrelation uses, generalized from symmetric fixed-distance weights to asymmetric kNN weights). A combination-count budget with logged, never-silent truncation bounds worst-case search cost. Reports every evaluated model, the ranked passing subset, and a per-variable significance/sign summary. generalized_linear_regression fits the one model you specify; this finds it.
causal_inference_analysis Estimate the causal effect of a binary treatment_field on an outcome_field, adjusting for confounding_fields instead of just reporting a raw association (like ArcGIS's Causal Inference Analysis): fit a logistic propensity model (IRLS), then estimate the Average Treatment Effect by ps_matching (nearest-neighbour caliper matching on the propensity score via a 1-D kdtree), ipw (stabilized, truncated inverse-probability weights), or regression_adjustment (outcome ~ treatment + confounders), with a seeded bootstrap confidence interval and a per-confounder standardized-mean-difference balance table before/after adjustment (warns when balance still fails balance_threshold). Reports the naive difference-in-means alongside the ATE to show the confounding bias removed; add_spatial_confounders widens the confounder set with feature coordinates and k-nearest-neighbour-averaged confounders. Everything else in the catalog answers "is X associated with Y"; this is the one tool that asks "did X cause Y".
align_features Conform source line/polygon edges to a nearby trusted target layer within a search_distance (like ArcGIS's Align Features), link-free: grid-index the target's edges, pull each source vertex within range toward its nearest target projection, then smooth the raw step-function displacement with an arc-length-windowed moving average along its own line/ring so partial alignment tapers smoothly instead of kinking at the search-distance boundary. An optional match_field/target_match_field pair restricts pairing by attribute; shared polygon boundaries are displaced once (cached by vertex) so coincident edges stay coincident. Reports per-feature align_max_disp/align_mean_disp. The interior-vertex conflation the bundled snap_endnodes (endpoints only) and snap_points_to_network (points only) can't do.
multivariate_clustering Cluster vector features by their attribute profile, ignoring location (like ArcGIS's Multivariate Clustering): z-score standardize the analysis fields, then group rows with seeded k-means++ + Lloyd iterations (default) or k-medoids (PAM on the full distance matrix, method=kmedoids); omit num_clusters (or pass 0) to auto-evaluate k = 2..15 and keep the k with the highest Calinski–Harabasz pseudo-F. Writes cluster_id (−1 for rows missing a field value) and a per-feature silhouette score, and reports per-cluster field means plus the pseudo-F curve in optimal-k mode. All randomness is a seeded splitmix64 stream — deterministic in WASM. The raster-only k_means_clustering, density-based dbscan/hdbscan, and spatially-constrained build_balanced_zones all cluster on something other than plain feature attributes.
table_to_geometry Build line or ellipse features from tabular fields (like ArcGIS's XY To Line / Bearing Distance To Line / Table To Ellipse): mode=xy_to_line reads start_x/start_y/end_x/end_y field pairs, mode=bearing_distance reads an origin x/y plus bearing (degrees clockwise from north) and distance, and mode=ellipse reads a center x/y plus semi-axis fields major/minor and an azimuth; line_type selects planar (straight Cartesian segments), rhumb (constant-bearing loxodrome), or geodesic (default — shortest path on the WGS84 ellipsoid, via geo 0.33's pure-Rust Geodesic/Rhumb metric spaces, a Karney/geographiclib port that needs no feature gate), with vertex_spacing densifying curved lines and sampling ellipse outlines. The catalog's first tool that builds geometry from attribute fields rather than transforming existing geometry — the staple ArcGIS workflow for turning tabular sighting/telemetry/error-ellipse data into mappable features.
transform_fields Standardize, transform, and encode attribute fields for analysis-ready tables (like ArcGIS's Standardize Field / Transform Field / Encode Field): apply one named transform to every field in fieldszscore/minmax/robust (median/IQR) scaling, log/log1p/sqrt/inverse (domain-guarded to null, not NaN or a panic), boxcox (lambda fit by golden-section search on the profile log-likelihood, or a fixed boxcox_lambda), bin (equal_interval/quantile/std_dev classification into bins integer classes), or onehot (one 0/1 column per distinct category, capped at 50 with an other bucket). New fields are appended as <field><suffix>; drop_input removes the originals. Returns the fitted mean/sd, median/IQR, lambda, breaks, or categories per field so the transform is documentable and, where relevant, invertible — the shared attribute-prep step ahead of calculate_composite_index, dimension_reduction, and generalized_linear_regression.
detect_graphic_conflict Find where the symbolized (drawn-width) extents of two feature layers — or one layer against itself — graphically overlap or crowd, without moving anything (like ArcGIS's Detect Graphic Conflict): buffer input by half symbol_width (and conflict, if given, by half conflict_symbol_width) plus half conflict_distance using geo's pure-Rust Buffer trait, bbox-prune candidate pairs, and intersect surviving buffers with BooleanOps — every non-empty intersection becomes a conflict polygon tagged with both source feature ids and its overlap area. For same-layer line inputs, line_connection_allowance subtracts a disc around any shared endpoint so ordinary end-to-end connectivity isn't flagged. The QA/review counterpart to resolve_road_conflicts/resolve_building_conflicts, which move geometry instead of just reporting conflicts; count_overlapping_features sees only raw geometry, never symbol width.
geodetic_densify Insert vertices along the true geodesic or rhumb (constant-bearing) path between existing vertices of a geographic (lon/lat) line or polygon layer (like ArcGIS's Geodetic Densify): geodetic_type=geodesic (default, Karney's ellipsoidal geodesic via geo's pure-Rust geographiclib-rs port) or rhumb/loxodrome, spaced by max_segment_length meters or an exact vertices_per_segment count. Long segments in lon/lat coordinates bow toward the pole along the true geodesic instead of cutting a misleading planar chord; antimeridian-crossing line output is split into a MultiLineString at ±180°. The geographic-aware complement to the planar densify_features-style interpolation nothing else in the catalog does correctly for lon/lat data.
strip_map_index_features Generate a numbered sequence of rotated, overlapping page rectangles that follow a linear feature — a strip-map atlas index for a highway, river, pipeline, or trail (like ArcGIS's Strip Map Index Features): walk each line's arc length, and every page_length * (1 - overlap) units fit a chord between the entry/exit stations, centre a page_length × page_width rectangle on it, and rotate to the chord bearing (or snap to the nearest horizontal/vertical axis for orientation=horizontal/vertical). Pages carry a sequential page_number (from start_page), source line_id, and rotation. The route-following complement to grid_index_features's regular fishnet, which slices a corridor at arbitrary angles instead of along it.
zonal_histogram Tabulate the full distribution of a value raster's cells within each zone, as a zones x classes/bins cross-tab CSV table (like ArcGIS's Zonal Histogram): classes mode (default) keys each rounded value as its own class for categorical rasters, bins mode buckets continuous values into bins equal-width buckets computed from the data, and percent reports each class's share of the zone instead of raw cell counts. Distinct from the bundled zonal_statistics (moments only, no shape) and cross_tabulation (needs two categorical rasters); an optional long_output emits the same table as zone,class,count,percent rows. v1 requires zones to be a raster — rasterizing a polygon zone layer is not yet implemented.
extract_scanned_features Vectorize a binary (scanned or thresholded) raster into clean lines or polygons — the ArcScan successor, the front end to the raster-to-vector cleanup pipeline (like ArcGIS's Extract Scanned Lines / Extract Scanned Polygons): binarize by foreground_value/threshold, drop foreground components under noise_size and fill background holes under hole_size, then for lines Zhang–Suen thin to a 1-px skeleton, trace it into polylines breaking only at junctions (degree ≠ 2), bridge nearly-collinear endpoint gaps within gap_distance, and simplify (Douglas–Peucker); polygons vectorizes the cleaned regions via the repo's own polygonize. All classical image processing (no deep learning). Where the bundled raster_to_vector_lines traces raw cell edges, this recovers true centrelines — validated by rasterizing a real road network to a binary "scan" and re-extracting it: 100 % of the extracted centrelines fall within 2 cells of a real road, and 99.9 % of the network is recovered.
gtfs_to_features Parse a standard GTFS transit feed into GIS features (like ArcGIS's GTFS Stops/Shapes To Features + Calculate Transit Service Frequency): stop points from stops.txt and route lines from shapes.txt (ordered by shape_pt_sequence and attributed with the route via trips.txt/routes.txt), plus an optional per-stop n_departures service-frequency count from stop_times.txt within an optional HH:MM:SS window (GTFS times may exceed 24:00). Pure CSV parsing (quote-aware) + geometry assembly — no dependency, no external service; GTFS is always WGS84 so output is EPSG:4326, dropping straight into the web map. Neither the repo nor the bundled suite touched transit data before. input is an extracted feed directory (unzipped); validated on the real BART feed — 287 stops, 28 route shapes, 112,306 departures — matching a pandas parse exactly.
create_spatial_sampling_locations Generate sample points over a polygon study area with the classic survey designs (like ArcGIS's Create Spatial Sampling Locations): simple_random (uniform rejection sampling, optional min_distance spacing), stratified (partition by each feature or a strata_field, allocate num_samples across strata equal/proportional to area/by a population_field total, then sample within each), systematic (a square/hexagon/triangle lattice at bin_size spacing clipped to the area), and cluster (randomly pick num_clusters tessellation bins and sample within them). All randomness is a seeded splitmix64 stream (deterministic, WASM-safe); point-in-polygon uses geo's Contains. Complements the repo's GRTS create_spatially_balanced_points and the bundled random_points_in_polygon, which offer neither stratification, regular lattices, nor cluster designs. Validated on US states: 100 % of points fall inside, min_distance is honoured, and proportional allocation matches area shares to within one sample.
compute_accuracy_for_object_detection Score detections against ground truth by IoU matching (like ArcGIS's Compute Accuracy For Object Detection): per-class TP/FP/FN, precision, recall, average precision and overall mAP. Within each class, detections are ranked by confidence and greedily matched to the highest-IoU unmatched truth above min_iou; AP integrates the precision-recall curve with the standard right-to-left precision envelope, so a false positive ranked above a true positive lowers the score where a plain precision/recall pair would not. Completes the detection pipeline with non_maximum_suppression; classification_accuracy_assessment is pixel-level and has no notion of instance matching.
contour_with_barriers Trace iso-value contour lines of a surface that terminate at barrier features (faults, cliffs, coastlines) instead of crossing them (like ArcGIS's Contour With Barriers): marching squares over the cell-centre grid, with the barrier vector layer rasterized onto the grid so any marching-squares block touching a barrier (or no-data) cell is skipped — contours stop cleanly at the barrier — and the resulting segments chained into polylines. Levels come from an interval (+ base) or an explicit levels list; output lines carry a level attribute, and barriers as lines or polygon boundaries are all honoured. Where the bundled contours_from_raster ignores barriers (contours bleed across features that should break them), this respects them — the contouring complement to the shipped interpolate_with_barriers. Verified on a real barrier surface: with the barrier, 0 of the contours that previously crossed it still do.
percentile_contours Emit nested polygons enclosing the most extreme cells of a continuous surface — a density/KDE surface, suitability, or elevation (like ArcGIS's Value Percentile Contours / Volume Percentile Contours): the standard way to turn a kernel-density surface into 50/90/95 % home-range or hotspot footprints. mode=value (default) selects cells at or above the p-th percentile value (the top (100−p)% by count); mode=volume thresholds on cumulative magnitude so the selection carries the top (100−p)% of the mass. Each percentiles entry is thresholded into a mask, vectorized with the repo's own polygonize (cell-edge rings, holes preserved), and tagged with its percentile/threshold; higher percentiles nest inside lower ones, and optional smooth applies Douglas–Peucker. Where the bundled contours_from_raster traces fixed z-levels, this computes percentile-of-value or percentile-of-mass thresholds. Polygon areas match the selected-cell fractions exactly, and volume p90 carries 10 % of the mass (verified on a real KDE surface).
spatial_association_between_zones Measure how strongly two categorical zone rasters of the same area correspond — land cover vs. ecoregions, predicted vs. observed classes (like ArcGIS's Spatial Association Between Zones): from the joint contingency table it computes the information-theoretic V-measurehomogeneity (how completely each zone-2 class falls inside one zone-1 class), completeness (the symmetric direction), and their harmonic mean v_measure (0 = independent partitions, 1 = identical up to relabelling) — plus per-zone-1-class association scores and dominant-overlap shares in an optional CSV. Where the bundled cross_tabulation gives only the raw counts, this turns them into an interpretable strength; entropies use natural logs so h/c/v/mutual-information match scikit-learn's homogeneity_completeness_v_measure exactly (verified to 1e-6 on real NLCD-style data). v1 takes two rasters on the same grid; polygon-zoning overlay is future work.
merge_lines_by_pseudo_node Dissolve chains of line segments that meet at pseudo-nodes — nodes where exactly two line ends meet (degree 2) — into single continuous polylines, while preserving true intersections (like ArcGIS's Merge Lines By Pseudo Node / Unsplit Line): the standard topology cleanup after raster stream/road vectorization or tiled imports, where one real road arrives chopped into many small segments. Endpoints are snapped onto a grid (snap_tolerance) to build a node graph; chains are extended through degree-2 nodes only, so a T- or X-junction always ends a chain, and dissolve_fields further stops a merge where a listed attribute changes (a highway will not fuse with the street it touches end-to-end). Closed pseudo-node loops collapse to one closed line. Each output feature keeps its first segment's attributes plus a merged_count; non-line features pass through. Where the bundled merge_line_segments stitches coincident endpoints purely geometrically (ignoring junctions and attributes), this is pseudo-node- and attribute-aware — matching Shapely's linemerge exactly on a real 64-segment road network (→ 47 lines, total length conserved to 1e-11).
identify_narrow_polygons Flag polygons that are narrower than a width_tolerance somewhere — slivers and pinched necks — as a QA step before elimination or collapse (like ArcGIS's Identify Narrow Polygons): a morphological opening at radius tolerance/2 (erode then dilate via geo's pure-Rust Buffer) keeps only the parts wide enough to hold a disk of that radius, so the area it removes (narrow_area) is the total area of the too-thin portions — a full sliver opens to nothing, a neck loses the neck, a fat polygon loses only minor corner rounding. Each feature is annotated with is_narrow, narrow_area, and an estimated min_width (bisection on the opening), with a min_narrow_area floor (default tolerance²) suppressing corner-rounding noise and narrow_only to keep just the flags. Where the bundled filter_vector_features_by_area keys on area alone (and so misses a long, thin polygon whose area is large), this measures local width — what eliminate_polygons/collapse_hydro_polygon actually act on. Expects valid polygon input (run repair_geometry first on raster-vectorized coverages).
points_to_path Connect points into polyline features, ordered by a field and optionally grouped into one path per value (like QGIS's Points to Path / ArcGIS's Points To Line): order_field sets the connection sequence within each path (numeric values sort numerically, text lexicographically, or by natural_sort so a9 < a10; omit it for input feature order), group_field partitions the points into separate paths, and close_path appends the first vertex to close a ring. Each output line carries its group value, the begin/end order values, and the vertex num_points. The bundled suite has no general points-to-line converter — points_along_lines goes the other way, and the repo's reconstruct_tracks is time-specific (it requires a timestamp and adds gap-splitting/dwell detection); this is the plain, general operation. Validated on real GTFS data: six BART trips' stop points join into route paths matching the pandas-ordered stop sequences exactly.
zonal_geometry Per-zone geometric measures from a categorical zone raster (like ArcGIS's Zonal Geometry / Zonal Geometry As Table): for every distinct zone value it computes area, perimeter, thickness (2× the largest inscribed radius via a chamfer distance transform), centroid_x/centroid_y, and the standard-deviational ellipse major_axis/minor_axis + orientation (from the coordinate covariance). Raster mode paints the chosen measure back into each zone; as_table mode returns one row per zone with every measure. The zone-shape third leg of the zonal family the bundled value-based zonal_statistics and distribution-based zonal_histogram don't provide.
zonal_characterization Statistics for several value rasters over one zone raster in a single pass, each with its own statistic (like ArcGIS's Zonal Characterization). The bundled zonal_statistics takes one value raster with one statistic, so a covariate stack means running it per raster and joining by hand, re-indexing the zone grid each time. Streaming statistics (mean/sum/min/max/range/stddev via Welford) allocate nothing; order statistics (median/percentile/majority/minority/variety) retain values only for the rasters that ask for them. ignore_nodata=false nulls a whole zone rather than silently averaging around the gap.
zonal_fill Fill each zone of a categorical raster with the minimum value of a co-registered weight raster found along that zone's boundary (like ArcGIS's Zonal Fill): boundary cells are those touching a different zone, no-data, or the grid edge; every zone's interior is set to its boundary minimum. A constrained-surface building block (flood each zone up to the lowest point on its rim) absent from the bundled suite — fill is flow-based and zonal_statistics only summarizes.
calculate_polygon_main_angle Write each polygon's dominant orientation to an angle field (like ArcGIS's Calculate Polygon Main Angle): the long-side direction of the polygon's minimum-area bounding rectangle (rotating calipers over the convex hull), in the arithmetic (CCW from east; default), geographic (CW from north), or graphic (CW from east) convention, modulo 180°. The per-polygon vector orientation the bundled raster-only patch_orientation lacks — feeds marker/label rotation and pairs with the footprint pipeline (regularize_building_footprints, regularize_adjacent_building_footprint).
band_collection_statistics Per-band min/max/mean/std plus the cross-band covariance and correlation matrices for a stack of co-registered rasters (like ArcGIS's Band Collection Statistics): per-band stats use each band's valid cells; the matrices use only cells valid in all bands. The multivariate summary (PCA / classification precursor, band-redundancy diagnostic) missing from the suite, and the per-band complement to the per-pixel cell_statistics. brief mode returns per-band stats only.
line_statistics Rasterize a neighborhood statistic of a line attribute (like ArcGIS's Line Statistics): each output cell reduces the line segments clipped to its search_radius circle to mean (length-weighted), majority/minority (value holding most/least length), median (length-weighted), maximum/minimum/range, variety, or total length. The attribute-surface complement to line_density (which gives length-per-area) — for mean-speed or dominant-road-class surfaces the bundled suite can't produce.
optimized_hot_spot_analysis Auto-aggregate raw incidents (fishnet grid count or snap to weighted points), auto-select the analysis distance band by peak clustering intensity, run Getis-Ord Gi*, and report a False Discovery Rate–corrected hot/cold-spot bin (Gi_Bin −3…3) plus GiZScore/GiPValue (like ArcGIS's Optimized Hot Spot Analysis). The workflow wrapper — aggregation, band selection, multiple-testing correction — the raw bundled getis_ord_gi_star leaves to the user. With an analysis_field the features are analyzed directly (weighted).
optimized_outlier_analysis The Local Moran companion to optimized_hot_spot_analysis: auto-aggregate incidents, auto-select the distance band, run Anselin Local Moran's I, and report FDR-corrected cluster/outlier types COType (HH/LL/HL/LH) plus LMiIndex/LMiZScore/LMiPValue (like ArcGIS's Optimized Outlier Analysis). Distinct from the raw bundled local_morans_i_lisa, the space-time local_outlier_analysis, and the density-based spatial_outlier_detection — this is the non-temporal Anselin Local Moran with automatic aggregation, band selection, and FDR.
polygon_volume Per-polygon volume and 3D surface area of a surface raster above and/or below each feature's own reference-plane elevation (height_field), like ArcGIS 3D Analyst's Polygon Volume. For every polygon, cells whose centers fall inside contribute |z − plane|·cellArea to the prism volume, cellArea to area_2d, and the draped cellArea·√(1 + (dz/dx)² + (dz/dy)²) tilt to surface_area, split into _above/_below when direction = both. The zonal counterpart of surface_volume (whole-raster, one plane) — neither cut_fill, storage_capacity, nor the bundled zonal_statistics performs per-polygon above/below-plane volumetric integration.
hot_spot_analysis_comparison Statistically compare two Getis-Ord hot-spot result layers (like ArcGIS's Hot Spot Analysis Comparison): match their locations by a match_field or nearest centroid within tolerance, classify each transition from the per-feature bin_field significance (hot_to_hot, hot_to_cold, none_to_hot, …), and run a seeded permutation test of overall agreement (agreement_fraction, p_value). The pattern-change comparison the bundled getis_ord_gi_star and the repo's optimized_hot_spot_analysis/emerging_hot_spot_analysis don't provide; the permutation RNG is deterministically seeded for WASM.
group_by_proximity Tag every input feature with a GROUP_ID for single-linkage spatial connected components — features within spatial_near_distance (near) or touching (intersects), optionally only when they share an attribute_field value (like ArcGIS GeoAnalytics' Group By Proximity). Union-find over a bbox-pruned min-distance graph; unlike the density clusterers (hdbscan, optics, bundled dbscan) it needs no density parameters, drops no noise, and preserves each feature's geometry.
feature_to_line Planarize line / polygon-boundary features into noded, non-overlapping two-vertex line segments split at every mutual and self intersection (like ArcGIS's Feature To Line), each tagged with its src_fid. An x-sweep prunes the pairwise segment-intersection search; T-junctions and collinear-overlap endpoints are noded too, with a cluster_tolerance snap. The topology-building precursor the bundled polygons_to_lines (rings only) and split_vector_lines (split by a break layer) don't provide; merge_lines_by_pseudo_node is the inverse.
split_raster Split one raster into standalone tiles — an N×M grid (count), fixed-pixel tiles (size), or one clip per polygon (polygons) — with optional pixel overlap (like ArcGIS's Split Raster). Each tile preserves CRS, cell size, and its georeferenced origin; polygon mode sets cells outside the feature to no-data. Unlike raster_to_tiles/write_pmtiles (web-tile pyramids), this writes independent raster files for ML chipping, parallel processing, and data delivery.
surface_parameters Slope, aspect, or curvature (mean/profile/tangential/plan/gaussian/casorati/contour_geodesic_torsion) from a DEM via a least-squares quadratic/biquadratic surface fit over a chosen neighborhood_distance (like ArcGIS's Surface Parameters). Center partial derivatives (fx,fy,fxx,fyy,fxy) feed the standard second-order surface formulas; adaptive shrinks the window next to data gaps. The scale-adaptive counterpart to the bundled fixed 3×3 slope/aspect/curvature tools — parameters can be computed at coarser scales than a single cell.
rescale_by_function Rescale a continuous raster onto a common suitability scale via a transfer function (linear, inverse_linear, power, exponential, logarithmic, logistic, gaussian, near, small, large, symmetric_linear) with optional below/above-threshold cut-offs and an arbitrary from_scale/to_scale range (like ArcGIS's Rescale By Function). Exposes the standalone rescale step that fuzzy_overlay bundles into its overlay combination.
calculate_transit_service_frequency Windowed GTFS transit service frequency (like ArcGIS's Calculate Transit Service Frequency): for a service date resolved through calendar.txt/calendar_dates.txt (weekday via Sakamoto + date-range + exceptions) and a start_time/duration_minutes window, count the scheduled trips (arrivals or departures) serving each stop (points) or route line (polylines from shapes.txt) and report n_trips and trips_per_hour. The date-and-window-aware analytic beyond gtfs_to_features' raw departure counts — extends the repo's GTFS suite.

It also ships pure-Rust ports of the DEM depression/mount algorithms from opengeos/lidar (no GDAL, RichDEM, SciPy, or scikit-image dependency; they run in WASM):

Tool id Source What it does
dem_filter filtering.py Mean / median / Gaussian smoothing of a DEM.
extract_sinks filling.py Wang & Liu fill, then group filled cells into sinks larger than min_size; emits sink/region/depth/filled rasters, an attribute CSV, and region polygons (vector_output, GeoJSON).
delineate_depressions slicing.py Level-set slicing of a sink raster into a nested-depression hierarchy; emits id/level rasters, a CSV, and depression polygons (vector_output, GeoJSON).
delineate_mounts mounts.py Flip the DEM, then run the sink + depression pipeline to delineate nested elevated features (rasters, CSV, and GeoJSON).

Typical chain (over the WASI /work filesystem or via paths):

extract_sinks --input=dem.tif --output=sink.tif --min_size=100
delineate_depressions --input=sink.tif --output=dep_id.tif --level_output=dep_level.tif

Depression filling reuses a port of whitebox's Wang & Liu priority-flood (kept inside geolibre-tools so the crate stays free of a wbtools_oss dependency). The morphological attributes (perimeter, axes, eccentricity, orientation) mirror scikit-image's regionprops. The vector_output parameter polygonizes the label raster into GeoJSON (one feature per connected component, holes preserved, RFC 7946 winding) in the source CRS, with the attribute table joined onto each feature -- a pure-Rust replacement for the gdal.Polygonize + GeoPackage join in the Python original.

Adding a new tool

  1. Add a module with a wbcore::Tool impl under crates/geolibre-tools/src/ (see raster_normalize.rs for the template: metadata / validate / run, reading and writing rasters by path).
  2. Push it into the list returned by geolibre_tools() in crates/geolibre-tools/src/lib.rs.
  3. Declare each parameter's schema in geolibre_param_schemas() (same file): add a match arm mapping the tool id to its params, e.g. input -> ToolParamSchema::input_raster(), output -> ToolParamSchema::output_raster() (or output(ToolDatasetSchema::File) for a non-dataset file), a count -> scalar_integer(), a factor -> scalar_float(), a flag -> bool(), a fixed choice -> enum_values(&[...]). This is what makes geolibre manifests emit an accurate io_role/data_kind/schema per param, so host UIs route raster/vector inputs and render the right widget. Without it, the manifest falls back to keyword inference, which mis-types scalars whose description mentions a dataset (a flag that "sorts features" would read as a vector input). A unit test (every_tool_has_explicit_param_schemas) fails if a tool's param is left without a schema.
  4. Rebuild (./build.sh); it appears in listTools() automatically.

The crate depends only on wbcore plus the data crates a tool needs (e.g. wbraster), so the same tools can later back a native CLI or the Python sidecar, not just WASM.

The data boundary is the WASI virtual filesystem: inputs are placed under /work, tools read/write there via ordinary std::fs, and any new file is returned to JS as a Uint8Array. Raster outputs are Cloud Optimized GeoTIFFs.

CLI contract

geolibre list                 # print every tool id
geolibre manifests            # print all tool manifests as JSON (param schemas)
geolibre manifest <id>        # print one manifest as JSON
geolibre version              # print the runner version
geolibre <tool> [--k=v ...]   # run a tool over /work

--key=value, --key value, and bare --flag are all accepted. Values are type-inferred: true/false -> bool, numbers -> number, everything else (including /work/... paths) -> string.

Build

rustup target add wasm32-wasip1
sudo apt-get install -y binaryen   # provides wasm-opt
./build.sh                         # -> npm/geolibre-cli.wasm

The whitebox_next_gen crates are referenced as path dependencies in crates/geolibre-cli/Cargo.toml. Switch them to git or published versions before releasing (note wbtools_oss is publish = false, so git or vendoring is required).

Use from JavaScript

Note: the repository is geolibre-rust (the Rust source), but the published npm package is geolibre-wasm (the WASM artifact), mirroring whitebox-wasm.

npm install geolibre-wasm

Browser library (the . export) -- typed GeoTIFF/projection/vector/LiDAR APIs:

import init, { GeoTiffReader, CogBuilder, version } from "geolibre-wasm";

await init(); // load the wasm-bindgen module
const r = new GeoTiffReader(tiffBytes);   // Uint8Array
console.log(r.width, r.height, r.bands, r.epsg);
const band0 = r.read_band_f64(0);          // Float64Array

Vector data without JSON in the middle

Reading a vector layer as GeoJSON hands JavaScript a string: the consumer pays for JSON.parse and then for re-encoding the parsed objects into the typed arrays a GPU actually wants. For a few hundred thousand features that dominates load time, so the same layer can also come back in binary form. All three paths share one reader, so any supported format (including GeoParquet, read straight from the buffer -- no server, no filesystem) works with any of them:

import init, {
  vector_to_geojson,   // string        -- portable, needs JSON.parse
  vector_to_binary,    // typed arrays  -- deck.gl binary attributes
  vector_to_arrow_ipc, // Uint8Array    -- GeoArrow record batch
  vector_formats,
} from "geolibre-wasm";

await init();
vector_formats(); // geojson,topojson,gml,gpx,kml,flatgeobuf,geopackage,kmz,geoparquet

// deck.gl's binary layout: no string is ever produced.
const b = vector_to_binary(parquetBytes, "geoparquet");
const layer = new GeoJsonLayer({
  data: {
    points: {
      type: "Point",
      positions: { value: b.point_positions(), size: b.position_size },
      featureIds: { value: b.point_feature_ids(), size: 1 },
      globalFeatureIds: { value: b.point_global_feature_ids(), size: 1 },
      properties: [],
    },
    lines: {}, polygons: {},
  },
});
b.free(); // release the WASM-side buffers when done

// Or one GeoArrow record batch, shared by the query engine and the renderer.
const ipc = vector_to_arrow_ipc(fgbBytes, "flatgeobuf");
const table = tableFromIPC(ipc);              // apache-arrow, zero-copy
await conn.insertArrowFromIPCStream(ipc, { name: "roads" }); // DuckDB-WASM

Attributes come back columnar rather than as one JS object per feature: schema_json lists the fields, numeric_column(i) returns a Float64Array, and text_column(i) / text_column_offsets(i) return UTF-8 bytes with their split points. Geometry is delivered as positions plus the index arrays deck.gl expects (line_path_indices(), polygon_indices(), polygon_primitive_polygon_indices()), one set per geometry class.

Both binary paths are cargo features of geolibre-wasm, on by default: arrow (Arrow IPC) and geoparquet (reading .parquet from bytes). Together they take the browser artifact from ~4.5 MB to ~6.4 MB (1.2 -> 1.9 MB gzipped); building with --no-default-features returns it to the smaller size and leaves vector_to_binary available.

demo/benchmark.html times all three paths against each other on the same source bytes.

Tool runner (the ./tools export) -- the whitebox + GeoLibre tool suite:

import { runTool, listTools } from "geolibre-wasm/tools";

const tools = await listTools();

const { files } = await runTool("slope", {
  args: ["--input=/work/dem.tif", "--output=/work/slope.tif", "--units=degrees"],
  input: { "dem.tif": demBytes }, // Uint8Array
});
const slopeCog = files["slope.tif"]; // Uint8Array (COG GeoTIFF)

An input value may also be an http(s) URL string, fetched for you (whole file, no range reads) -- the same for raster and vector inputs:

await runTool("write_geoparquet", {
  args: ["--input=/work/in.geojson", "--output=/work/out.parquet"],
  input: { "in.geojson": "https://example.com/data/cities.geojson" },
});

Use from Python

The python/ package (geolibre-wasm on PyPI, import geolibre_wasm) runs the same WASI tool runner in-process via wasmtime, mirroring the JS ./tools API. No native install, GDAL, or server.

Try it in your browser, no setup: Open the quickstart notebook in Google Colab (examples/geolibre_wasm.ipynb) -- it reads and processes a real DEM and building footprints end to end.

pip install geolibre-wasm
import geolibre_wasm as gl

tools = gl.list_tools()                 # every tool id
manifests = gl.list_manifests()         # schemas + "source": geolibre|whitebox

res = gl.run_tool(
    "slope",
    # Paths in `args` refer to the tool's sandbox (/work), NOT your host disk.
    # `input` files are placed at /work/<name>; `res.files` keys are relative
    # to /work. Mixing in host paths (e.g. /content on Colab) will not work.
    args=["--input=/work/dem.tif", "--output=/work/slope.tif", "--units=degrees"],
    input={"dem.tif": open("dem.tif", "rb").read()},   # -> /work/dem.tif
)
assert res.exit_code == 0, res.stdout                  # surfaces tool errors
open("slope.tif", "wb").write(res.files["slope.tif"])  # key is relative to /work

Each input value may be bytes, an http(s) URL (downloaded for you), or a local file path -- the same for raster and vector inputs:

gl.run_tool(
    "write_geoparquet",
    args=["--input=/work/in.geojson", "--output=/work/out.parquet"],
    input={"in.geojson": "https://example.com/data/cities.geojson"},
)

The runtime .wasm is downloaded from the matching release on first use (or set GEOLIBRE_WASM). See python/README.md for details.

Recipes: reading and processing various formats

The examples below use the Python API; the JavaScript runTool takes the same args and input (just camelCase). Each input value can be bytes, an http(s) URL, or a local path. Output files come back in result.files keyed by their /work-relative path. These all run against the real tool suite.

Vector (GeoParquet, GeoJSON, FlatGeobuf, Shapefile, GeoPackage, ...)

import geolibre_wasm as gl

# Convert GeoJSON -> GeoParquet (Hilbert-sorted, bbox covering, ZSTD by default)
gj = open("cities.geojson", "rb").read()
gl.run_tool("write_geoparquet",
            args=["--input=/work/in.geojson", "--output=/work/out.parquet"],
            input={"in.geojson": gj})

# Read GeoParquet -> any vector format (driver picked from the output extension:
# .geojson, .fgb, .shp, .gpkg, ...). Omit --output to keep it in memory.
res = gl.run_tool("read_geoparquet",
                  args=["--input=/work/in.parquet", "--output=/work/out.fgb"],
                  input={"in.parquet": "https://example.com/data.parquet"})
open("out.fgb", "wb").write(res.files["out.fgb"])

# Buffer features, then simplify (Douglas-Peucker)
res = gl.run_tool("buffer_vector",
                  args=["--input=/work/in.geojson", "--distance=25", "--output=/work/buf.geojson"],
                  input={"in.geojson": gj})
res = gl.run_tool("simplify_features",
                  args=["--input=/work/buf.geojson", "--tolerance=5", "--output=/work/simple.geojson"],
                  input={"buf.geojson": res.files["buf.geojson"]})

# Add geometry attributes (area / perimeter / centroid, ...)
gl.run_tool("add_geometry_attributes",
            args=["--input=/work/in.geojson", "--area=true", "--centroid=true",
                  "--output=/work/attrs.geojson"],
            input={"in.geojson": gj})

reproject_vector works the same, but the input must carry a source CRS (a Shapefile .prj, or a GeoParquet/GeoPackage with CRS metadata), e.g. args=["--input=/work/in.fgb", "--epsg=3857", "--output=/work/out.fgb"].

LiDAR point clouds (LAS / LAZ)

import geolibre_wasm as gl

cloud = open("cloud.las", "rb").read()        # or a .laz, or an http(s) URL

# Summary report (point count, bounds, density, ...). Output must be .txt/.html.
res = gl.run_tool("lidar_info",
                  args=["--input=/work/cloud.las", "--output=/work/info.txt"],
                  input={"cloud.las": cloud})
print(res.files["info.txt"].decode())

# Rasterize to a DEM (IDW) -> Cloud Optimized GeoTIFF
res = gl.run_tool("lidar_idw_interpolation",
                  args=["--input=/work/cloud.las", "--resolution=1.0", "--output=/work/dtm.tif"],
                  input={"cloud.las": cloud})
open("dtm.tif", "wb").write(res.files["dtm.tif"])

# Drop unwanted classes (comma-delimited list; e.g. exclude 1=unclassified, 7=noise)
gl.run_tool("filter_lidar_classes",
            args=["--input=/work/cloud.las", "--excluded_classes=1,7", "--output=/work/clean.las"],
            input={"cloud.las": cloud})

# Export points to a Shapefile
gl.run_tool("las_to_shapefile",
            args=["--input=/work/cloud.las", "--output=/work/points.shp"],
            input={"cloud.las": cloud})

Raster (GeoTIFF / COG)

dem = open("dem.tif", "rb").read()            # or an http(s) URL to a COG

# Warp to Web Mercator, then render a PNG preview through a colormap
gl.run_tool("reproject_raster",
            args=["--input=/work/dem.tif", "--epsg=3857", "--output=/work/merc.tif"],
            input={"dem.tif": dem})
gl.run_tool("render_raster_png",
            args=["--input=/work/dem.tif", "--colormap=terrain", "--output=/work/preview.png"],
            input={"dem.tif": dem})

Run gl.list_tools() for all 740+ tool ids and gl.list_manifests() for each tool's parameters and provenance ("source": "geolibre" | "whitebox").

GeoLibre integration

The interface is byte-compatible with the existing whitebox-wasm/tools client:

  1. Add geolibre-wasm to packages/processing/package.json.
  2. Add it to optimizeDeps.exclude in apps/geolibre-desktop/vite.config.ts (required for the new URL("./*.wasm", import.meta.url) glue).
  3. Point packages/processing/src/wasm-client.ts's lazy import("whitebox-wasm/tools") at geolibre-wasm/tools, or add a sibling client and a source toggle in ProcessingDialog.tsx.

listManifests() is a value-add over the legacy package: it lets GeoLibre build tool dialogs (parameter schemas, raster_in/vector_in roles) fully offline, without the Python sidecar.

License

MIT. See LICENSE.

About

whitebox_next_gen geospatial tools (plus new GeoLibre tools) compiled to WebAssembly (WASI) for in-browser use in GeoLibre

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages