louis-e/arnis · error
select_level_for_cell_size called with empty levels
Error message
select_level_for_cell_size called with empty levels
What it means
This panic is an internal invariant check in select_level_for_cell_size. The function picks the elevation resolution level whose meters-per-pixel best matches the requested cell size, and it cannot do so if the provider was configured with zero resolution levels. The library deliberately panics (with a comment saying 'this is a bug') because an empty level list can only result from a misconfigured provider, not from runtime data conditions.
Source
Thrown at src/elevation/providers/fixed_tile.rs:227
/// whose native pixels are no more than 1.5× finer than the output
/// cell. The factor tolerates a modest amount of *downsampling* from
/// the source (up to 1.5× finer-than-needed) before we give up on it
/// and step to the next coarser level, which avoids pulling dense
/// LiDAR tiles we'd immediately average away. Upsampling the other
/// direction (output finer than source) is unbounded by this rule —
/// if the user asks for 0.4 m cells on a 1 m source, the condition
/// `1.0 * 1.5 ≥ 0.4` holds easily and we use the 1 m level with
/// bilinear fill-in.
///
/// `levels` must be ordered finest-to-coarsest. When no level qualifies
/// the coarsest is returned as a fallback.
pub(super) fn select_level_for_cell_size<R: Resolution + Copy>(
levels: &[R],
cell_size_m: f64,
) -> R {
if levels.is_empty() {
// Caller must configure at least one level; this is a bug.
panic!("select_level_for_cell_size called with empty levels");
}
if !cell_size_m.is_finite() || cell_size_m <= 0.0 {
return levels[0];
}
for &level in levels {
if level.meters_per_pixel() * 1.5 >= cell_size_m {
return level;
}
}
*levels.last().unwrap()
}
/// Approximate physical bbox dimensions in meters. Precise enough for
/// resolution-level selection.
pub(super) fn bbox_dimensions_m(bbox: &LLBBox) -> (f64, f64) {
let mid_lat = (bbox.min().lat() + bbox.max().lat()) * 0.5;
let mid_lat_cos = mid_lat.to_radians().cos().abs().max(1e-6);
let width_deg = bbox.max().lng() - bbox.min().lng();View on GitHub (pinned to 34048924d9)
Solutions
- Configure the provider with at least one resolution level (levels ordered finest-to-coarsest) before calling fetch.
- Validate the levels list at provider construction time and fail fast (return an error or assert) instead of letting the panic surface deep inside fetch_fixed_tile_grid.
- If levels come from config deserialization, add a validation step after parsing that rejects empty level lists with a clear user-facing message.
Example fix
// before
let provider = FixedTileProvider { levels: vec![] };
provider.fetch_fixed_tile_grid(...); // panics
// after
let provider = FixedTileProvider { levels: vec![Level::res1m(), Level::res10m()] };
assert!(!provider.levels.is_empty(), "provider requires at least one resolution level");
provider.fetch_fixed_tile_grid(...); Defensive patterns
Strategy: validation
Validate before calling
if provider.levels.is_empty() {
return Err("elevation provider must be configured with at least one resolution level".into());
}
let grid = provider.fetch_fixed_tile_grid(...)?; Type guard
fn has_levels<R: Resolution>(levels: &[R]) -> bool { !levels.is_empty() } Try / catch
// panics are not catchable in normal Rust; validate before calling
let result = std::panic::catch_unwind(|| provider.fetch_fixed_tile_grid(...));
match result { Ok(grid) => grid, Err(_) => fallback_grid() } Prevention
- Validate provider config (non-empty levels) immediately after construction/deserialization
- Keep levels ordered finest-to-coarsest as the API documents
- Add a unit test asserting constructor rejects empty level lists
When it happens
Trigger: Calling fetch_fixed_tile_grid (directly or via a public fetch API) on a fixed-tile elevation provider whose configured levels slice is empty — e.g. a provider constructed with no resolution levels, or levels filtered out during configuration/deserialization.
Common situations: A provider config struct built programmatically and left with an empty levels vec; a config file where all level entries were removed or failed to deserialize into the levels list; a refactor that changed level discovery to return an empty vec without validating it.
Related errors
- provider chain is never empty
- Earth has no PDS raster
- Elevation data not available
- Failed to get main window
- Error while starting the application UI (Tauri)
AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03).
Data as JSON: /api/errors/113422b56b5b3d90.
Report an issue: GitHub.