Core Sources
ArraySource
ArraySource
Bases: DataSource
Wrap a numpy array that is already in memory.
This is the primary integration point for QGIS and other environments that manage rasters internally and cannot rely on file I/O.
If crs and transform are omitted, the array is treated as
pre-aligned to the BN's grid — align_to_grid will return it as-is
when its shape matches the target grid, just like ConstantSource
broadcasts when it has no spatial metadata.
Example — with CRS metadata (full georeferencing):
import numpy as np
from affine import Affine
slope = np.random.rand(100, 200).astype("float32") * 45.0
transform = Affine(0.01, 0, 10.0, 0, -0.01, 70.0)
source = geobn.ArraySource(slope, crs="EPSG:4326", transform=transform)
Example — pre-aligned array (no CRS needed):
# After fetching a DEM and computing slope analytically, the result
# is already on the BN grid — pass it directly without CRS metadata.
dem = bn.fetch_raw(geobn.WCSSource(...))
slope_deg = compute_slope(dem) # same shape as the BN grid
bn.set_input("slope_angle", geobn.ArraySource(slope_deg))
ConstantSource
ConstantSource
Bases: DataSource
Broadcast a single scalar value across the entire domain.
The 1×1 sentinel array is recognised by align_to_grid and expanded to the reference grid shape, so no explicit spatial metadata is needed.
Example:
# Apply a uniform 30 cm recent snowfall across the entire domain
source = geobn.ConstantSource(30.0)
bn.set_input("recent_snow", source)
RasterSource
RasterSource
Bases: DataSource
Read a local GeoTIFF file.
rasterio is used only to open the file and is discarded immediately; the returned RasterData contains only plain numpy/affine objects.
Example:
URLSource
URLSource
Bases: DataSource
Fetch a GeoTIFF from an HTTP/HTTPS URL and return it as plain data.
The file is streamed into an in-memory buffer so nothing is written to disk. rasterio is the only component that sees the raw bytes; the returned RasterData contains only numpy / affine objects.
Parameters
url: HTTP/HTTPS URL pointing to a GeoTIFF file. timeout: HTTP request timeout in seconds. cache_dir: Optional path to a directory for caching the fetched raster on disk. On a cache hit the HTTP request is skipped entirely.
Supports optional disk caching.
Example:
source = geobn.URLSource(
"https://example.com/data/slope.tif",
cache_dir="cache/",
)
bn.set_input("slope_angle", source)
PointGridSource
PointGridSource
Bases: DataSource
Sample a user-supplied callable over an N×N lat/lon grid.
Builds a regular grid of sample_points × sample_points WGS84 points
covering the inference bounding box, calls fn(lat, lon) at each point,
and assembles the results into a coarse EPSG:4326 raster.
align_to_grid() then bilinearly resamples this raster to the reference
grid resolution.
This is the generic primitive for any point-queryable data source (weather
APIs, elevation services, custom models). Pass a lambda or a regular
function as fn; return float("nan") or None for missing values.
Parameters
fn:
Callable with signature (lat: float, lon: float) -> float | None.
Return float("nan") or None for positions with no data.
sample_points:
Number of sample points per axis. Total API calls = sample_points².
Default is 5 (25 calls). Use 1 for a single-point broadcast (equivalent
to ConstantSource but fetched dynamically).
delay:
Seconds to sleep between successive calls. Default 0.05 s — enough to
be polite to most free REST APIs without slowing batch runs noticeably.
PointGridSource is the generic primitive for any point-queryable data source.
Pass any callable that accepts (lat, lon) and returns a float.
Example — Open-Meteo precipitation:
import requests
import geobn
def fetch_precipitation(lat: float, lon: float) -> float:
"""Query Open-Meteo historical archive for daily precipitation."""
resp = requests.get(
"https://archive-api.open-meteo.com/v1/archive",
params={
"latitude": lat,
"longitude": lon,
"start_date": "2024-01-01",
"end_date": "2024-01-01",
"daily": "precipitation_sum",
"timezone": "UTC",
},
timeout=10,
)
resp.raise_for_status()
values = resp.json().get("daily", {}).get("precipitation_sum", [None])
v = values[0]
return float(v) if v is not None else float("nan")
source = geobn.PointGridSource(fn=fetch_precipitation, sample_points=5)
bn.set_input("precipitation", source)
Example — MET Norway ocean forecast:
import requests
def sea_temperature(lat: float, lon: float) -> float:
resp = requests.get(
"https://api.met.no/weatherapi/oceanforecast/2.0/complete",
params={"lat": lat, "lon": lon},
headers={"User-Agent": "my-app/1.0"},
timeout=10,
)
if resp.status_code == 422:
return float("nan") # outside ocean coverage
resp.raise_for_status()
ts = resp.json().get("properties", {}).get("timeseries", [])
if not ts:
return float("nan")
val = ts[0]["data"]["instant"]["details"].get("sea_water_temperature")
return float(val) if val is not None else float("nan")
source = geobn.PointGridSource(fn=sea_temperature, sample_points=5, delay=0.1)
bn.set_input("sea_temp", source)