use std::collections::BTreeSet;
use crate::topology::labels::LabelSet;
use crate::topology::point::PointId;
pub const BOUNDARY_CLASS_LABEL: &str = "boundary_class";
pub const BOUNDARY_ROLE_LABEL: &str = "boundary_role";
pub const VERTICAL_LAYER_LABEL: &str = "vertical_layer";
pub const WET_DRY_MASK_LABEL: &str = "wet_dry_mask";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum BoundaryClass {
FreeSurface = 1,
Bed = 2,
Open = 3,
}
impl BoundaryClass {
pub const fn code(self) -> i32 {
self as i32
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum OpenBoundaryRole {
Inflow = 11,
Outflow = 12,
Tidal = 13,
}
impl OpenBoundaryRole {
pub const fn code(self) -> i32 {
self as i32
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum WetDryMask {
Wet = 1,
Dry = 0,
}
impl WetDryMask {
pub const fn code(self) -> i32 {
self as i32
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerticalCoordinateSystem {
Sigma,
ZLayer,
}
#[derive(Clone, Copy, Debug)]
pub struct CoastalValidationOptions {
pub require_complete_boundary_partition: bool,
pub require_open_role_on_open_boundary: bool,
pub require_complete_vertical_coverage: bool,
}
impl Default for CoastalValidationOptions {
fn default() -> Self {
Self {
require_complete_boundary_partition: false,
require_open_role_on_open_boundary: true,
require_complete_vertical_coverage: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CoastalMetadataError {
BoundaryClassOverlap { points: Vec<PointId> },
MissingBoundaryClass { points: Vec<PointId> },
UnexpectedBoundaryClass { points: Vec<PointId> },
MissingOpenBoundaryRole { points: Vec<PointId> },
OpenRoleWithoutOpenClass { points: Vec<PointId> },
MissingVerticalLayer { points: Vec<PointId> },
}
pub trait CoastalLabelQueries {
fn free_surface_points(&self) -> Vec<PointId>;
fn bed_points(&self) -> Vec<PointId>;
fn open_boundary_points(&self) -> Vec<PointId>;
fn inflow_points(&self) -> Vec<PointId>;
fn outflow_points(&self) -> Vec<PointId>;
fn tidal_points(&self) -> Vec<PointId>;
fn vertical_layer_points(&self, layer_index: i32) -> Vec<PointId>;
fn wet_points(&self) -> Vec<PointId>;
fn dry_points(&self) -> Vec<PointId>;
}
impl CoastalLabelQueries for LabelSet {
fn free_surface_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_CLASS_LABEL, BoundaryClass::FreeSurface.code())
}
fn bed_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_CLASS_LABEL, BoundaryClass::Bed.code())
}
fn open_boundary_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_CLASS_LABEL, BoundaryClass::Open.code())
}
fn inflow_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_ROLE_LABEL, OpenBoundaryRole::Inflow.code())
}
fn outflow_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_ROLE_LABEL, OpenBoundaryRole::Outflow.code())
}
fn tidal_points(&self) -> Vec<PointId> {
self.stratum_points(BOUNDARY_ROLE_LABEL, OpenBoundaryRole::Tidal.code())
}
fn vertical_layer_points(&self, layer_index: i32) -> Vec<PointId> {
self.stratum_points(VERTICAL_LAYER_LABEL, layer_index)
}
fn wet_points(&self) -> Vec<PointId> {
self.stratum_points(WET_DRY_MASK_LABEL, WetDryMask::Wet.code())
}
fn dry_points(&self) -> Vec<PointId> {
self.stratum_points(WET_DRY_MASK_LABEL, WetDryMask::Dry.code())
}
}
pub fn validate_coastal_metadata(
labels: &LabelSet,
expected_boundary_points: Option<&[PointId]>,
expected_vertical_points: Option<&[PointId]>,
options: CoastalValidationOptions,
) -> Result<(), CoastalMetadataError> {
let fs: BTreeSet<_> = labels.free_surface_points().into_iter().collect();
let bed: BTreeSet<_> = labels.bed_points().into_iter().collect();
let open: BTreeSet<_> = labels.open_boundary_points().into_iter().collect();
let mut overlaps = BTreeSet::new();
overlaps.extend(fs.intersection(&bed).copied());
overlaps.extend(fs.intersection(&open).copied());
overlaps.extend(bed.intersection(&open).copied());
if !overlaps.is_empty() {
return Err(CoastalMetadataError::BoundaryClassOverlap {
points: overlaps.into_iter().collect(),
});
}
if options.require_complete_boundary_partition
&& let Some(expected) = expected_boundary_points
{
let expected: BTreeSet<_> = expected.iter().copied().collect();
let mut labeled = BTreeSet::new();
labeled.extend(fs.iter().copied());
labeled.extend(bed.iter().copied());
labeled.extend(open.iter().copied());
let missing: Vec<_> = expected.difference(&labeled).copied().collect();
if !missing.is_empty() {
return Err(CoastalMetadataError::MissingBoundaryClass { points: missing });
}
let extra: Vec<_> = labeled.difference(&expected).copied().collect();
if !extra.is_empty() {
return Err(CoastalMetadataError::UnexpectedBoundaryClass { points: extra });
}
}
let inflow: BTreeSet<_> = labels.inflow_points().into_iter().collect();
let outflow: BTreeSet<_> = labels.outflow_points().into_iter().collect();
let tidal: BTreeSet<_> = labels.tidal_points().into_iter().collect();
let mut open_roles = BTreeSet::new();
open_roles.extend(inflow.iter().copied());
open_roles.extend(outflow.iter().copied());
open_roles.extend(tidal.iter().copied());
if options.require_open_role_on_open_boundary {
let missing_roles: Vec<_> = open.difference(&open_roles).copied().collect();
if !missing_roles.is_empty() {
return Err(CoastalMetadataError::MissingOpenBoundaryRole {
points: missing_roles,
});
}
}
let non_open_role_points: Vec<_> = open_roles.difference(&open).copied().collect();
if !non_open_role_points.is_empty() {
return Err(CoastalMetadataError::OpenRoleWithoutOpenClass {
points: non_open_role_points,
});
}
if options.require_complete_vertical_coverage
&& let Some(expected) = expected_vertical_points
{
let expected: BTreeSet<_> = expected.iter().copied().collect();
let labeled: BTreeSet<_> = labels
.iter()
.filter_map(|(name, point, _)| (name == VERTICAL_LAYER_LABEL).then_some(point))
.collect();
let missing: Vec<_> = expected.difference(&labeled).copied().collect();
if !missing.is_empty() {
return Err(CoastalMetadataError::MissingVerticalLayer { points: missing });
}
}
Ok(())
}