Spatial filtering
SpatialFilter cleans the source points before interpolation. It removes
invalid rows and, when enabled, global outliers and local spatial outliers,
using a global-then-local approach developed for high-density agricultural data
(Maldaner et al., 2018; Maldaner and Molin, 2020).
You usually do not need to call it yourself. apbase.Map runs the filter
internally before comparing interpolation methods. Use SpatialFilter when
you want to inspect the filtered data or tune the filtering stage.
from apbase.filtering import SpatialFilter
spatial_filter = SpatialFilter(
filter_level="light",
max_neighbors=120,
cell_factor=3,
).fit(x, y, z)
x_filtered, y_filtered, z_filtered = spatial_filter.filter()
use apbase::filtering::{FilterLevel, SpatialFilter, SpatialFilterOptions};
let mut spatial_filter = SpatialFilter::new(SpatialFilterOptions {
filter_level: FilterLevel::Light,
max_neighbors: 120,
cell_factor: 3,
..Default::default()
})?;
spatial_filter.fit(&x, &y, &z)?;
let (x_filtered, y_filtered, z_filtered) = spatial_filter.filter()?;
What it does
Filtering happens in two passes:
- Global IQR pass (optional,
apply_global_iqr=Trueby default): one interquartile-range check over the source data removes extreme readings, such as sensor faults, before spatial checks begin. This follows the global filtering step in (Maldaner et al., 2018). - Local pass: each point is compared with nearby points within
radius, capped atmax_neighbors.filter_levelcontrols how strict the local threshold is, following the local filtering steps in (Maldaner et al., 2018).
Parameters
x, y : array_like
: Source coordinates passed to fit. SpatialFilter does not convert
longitude/latitude: pass projected coordinates, in the same unit as
radius.
z : array_like
: Source values. Must have the same length as x and y.
radius : float, optional
: Local search radius. If omitted, a variogram is fitted and range / 3 is
used.
filter_level : {"light", "moderate", "strict", "aggressive"}, default "light"
: Local outlier threshold, from loosest to strictest.
apply_global_iqr : bool, default True
: Run the global IQR pass before local filtering.
max_neighbors : int, default 120
: Neighbor cap per point for the local pass. 0 uses every point within
radius.
cell_factor : int, default 3
: Speed setting for the neighbor search. Points are indexed in square cells
of size radius / cell_factor; higher values give smaller cells, so fewer
out-of-range points are scanned, at the cost of more cells in memory.
dedup_radius, dedup_z_tol : float, default 0.0
: Optional duplicate handling for dense GPS logs. Leave disabled unless
repeated near-identical points are causing local variance collapse.
Reading results
After fit, you can inspect the result in three ways:
filter(): filtered(x, y, z)arrays (only the points that passed).mask(): boolean keep-mask aligned with the original input.statistics: per-point diagnostics (pct_diff,local_z,local_prob) for every input point, including ones that were dropped.
Duplicate-heavy sources
Point sources with many near-duplicate coordinates, such as repeated GPS fixes
in high-frequency yield-monitor logs (Maldaner and Molin, 2020), can collapse local
variance toward zero and create gaps after filtering. dedup_radius and
dedup_z_tol collapse near-duplicate clusters to one representative only
while building local neighborhood statistics. Every input point is still
scored individually. Leave both at 0.0 unless you see this pattern in your
data.
See apbase.filtering.SpatialFilter in the API reference for
every parameter, including the less commonly tuned ones
(candidate_fraction, global_iqr_whisker).