Core API Reference
core/ has no QGIS dependency and no I/O beyond raster/vector reads — it is
usable standalone from a notebook or test, and is what the processing/
algorithms wrap. Runtime dependencies are numpy, pyproj, and rasterio only
(see requirements.txt); inside QGIS, numpy and GDAL already ship with the
application.
core.geometry
AEQD projection and ray sampling.
aeqd_proj4(lon0, lat0)— PROJ string for an AEQD projection centered on(lon0, lat0).transformer_to_aeqd(lon0, lat0)/transformer_from_aeqd(lon0, lat0)—pyproj.Transformerto/from the local AEQD plane.ray_points(azimuth_deg, max_range_m, step_m)→(s, x, y)— range and local-plane coordinates along one ray.sstarts atstep_m, not 0 — the radar's own cell is not part of the propagation path.native_step(dem_transform, dem_crs, lat0=None)— native ground sampling distance of a DEM in meters. Requireslat0whendem_crsis geographic (degrees), since meters-per-degree-longitude varies with latitude.
core.dem
DEM access with explicit nodata handling.
Dem(path, vertical_datum=None)— opens a single-band elevation raster (via rasterio), reads the whole band into memory once (a sweep samples hundreds of azimuths per site; re-reading from disk each time was the original bottleneck)..sample_lonlat(lons, lats, nodata_policy="raise")— sample elevation at WGS84 points, reprojecting internally.nodata_policy:"raise"(default, raisesDemNodataErroron any nodata sample) or"block"(returns+inffor nodata cells — terrain that fully blocks the beam). Never silently returns 0 or NaN..transform,.crs,.close(), context-manager support.
DemNodataError(ValueError)— raised per the policy above.raster_cell_centers_lonlat(transform, crs, height, width)— WGS84 lon/lat of every cell center of a raster grid; used by Discover/Network to turn a mask or importance raster's own grid into query points.
core.horizon
Accumulated terrain-horizon angle and minimum visible height. See Methodology for the derivation.
apparent_terrain_height(z, s, h0, k, earth_radius=EARTH_RADIUS_M)horizon_angle(z, s, h0, k, earth_radius=EARTH_RADIUS_M)— running-maxθ(s).min_visible_height(z, s, h0, k, theta=None, earth_radius=EARTH_RADIUS_M)—H_min(s); reusesthetaif already computed.
core.blockage
Partial beam blockage (Bech et al. 2003). See Methodology.
beam_height(s, h0, elevation_rad, k, earth_radius=EARTH_RADIUS_M)half_power_radius(s, beamwidth_rad)—a(s).beam_block_frac(y, a)— per-binPBB.cum_beam_block_frac(pbb, axis=-1)—CBB, running max along range. Never publishPBBdirectly — see module docstring.
core.engine
The per-site sweep, factored out for reuse by Evaluate and the Fase 0 notebook.
sweep_site(dem, lon0, lat0, h0, k, elevation_deg, beamwidth_deg, max_range_m, azimuth_step_deg=1.0, range_step_m=None, nodata_policy="block")— full 360° sweep. Returns a dict:azimuths_deg,s_m(shared range axis),theta_rad,h_min_m,cbb,lons_deg,lats_deg— each(n_azimuths, n_ranges)exceptazimuths_deg/s_m.h_min_at_point(dem, radar_lon, radar_lat, h0, k, query_lon, query_lat, range_step_m=None, nodata_policy="block")— exactH_minat one arbitrary point, along the exact bearing (used by Network instead of interpolating betweensweep_site's fixed azimuths). Returnsh0if the query point is closer than one range step.site_merit(dem, importance_band, importance_transform, importance_crs, importance_nodata, lon0, lat0, h0, k, elevation_deg, beamwidth_deg, max_range_m, azimuth_step_deg=1.0, thresholds=(1000.0, 3000.0), blocked_sector_threshold=0.5)— importance-weighted merit + largest blocked sector for one candidate; the two headline numbers the Robustness matrix sweeps over. RaisesValueErrorif the candidate's sweep doesn't overlap the importance layer at all — callers sweeping many candidates should catch this per candidate.
core.importance
sample_importance_along_rays(band, transform, crs, nodata, lons, lats)— importance value at each(lon, lat);0.0outside the raster extent or at nodata (unlikecore.dem, missing importance is a modeling choice, not a silent-bug risk, so it doesn't raise).
core.metrics
Merit figures and penalties — plain array reductions, no I/O. Callers supply
h_min/importance on a shared grid and an explicit mask where needed.
weighted_visible_fraction(h_min, importance, threshold_m, mask=None)merit_scores(h_min, importance, thresholds=(1000.0, 3000.0), mask=None)— dict of{threshold: weighted_visible_fraction}, deliberately never collapsed into one score.largest_contiguous_blocked_sector_deg(cbb_by_azimuth, azimuth_step_deg, threshold=0.5)— azimuths treated as circular.network_residual_gap_fraction(h_min_stack, importance, threshold_m=3000.0, mask=None, axis=0)— area seen by no radar in a stacked (n_radars, ...)h_minarray.
All raise ValueError (via the private _weighted_fraction) if the total
importance weight over valid cells is zero or non-finite — there is no
default that silently returns a meaningless 0/0.
core.manifest
Reproducibility manifest. See Methodology.
sha256_file(path, chunk_size=1<<20)— streamed digest, safe for multi-GB DEMs.plugin_version(metadata_path=...)— readsmetadata.txt'sversion=line.gdal_version()— viarasterio.__gdal_version__; inside QGIS this is the application's own bundled GDAL.build_manifest(params, input_layers, plugin_version_override=None)— assembles{timestamp_utc, plugin_version, numpy_version, gdal_version, parameters, input_layers}, hashing every path ininput_layers.write_manifest(path, manifest)— pretty-printed JSON.