use crate::core::{IntersectableSurface, SurfaceIntersection, Well};
use crate::foundation::Result;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntersectionDiagnostic {
pub well: String,
pub bore: String,
pub reason: String,
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct WellIntersectionSet {
pub hits: Vec<SurfaceIntersection>,
pub skipped: Vec<IntersectionDiagnostic>,
pub failed: Vec<IntersectionDiagnostic>,
}
impl WellIntersectionSet {
pub fn summary(&self) -> (usize, usize, usize) {
(self.hits.len(), self.skipped.len(), self.failed.len())
}
}
pub struct WellsView<'a> {
wells: Vec<&'a Well>,
}
impl<'a> WellsView<'a> {
pub(crate) fn new(wells: Vec<&'a Well>) -> WellsView<'a> {
WellsView { wells }
}
pub fn filter(&self, pred: impl Fn(&Well) -> bool) -> WellsView<'a> {
WellsView::new(self.wells.iter().copied().filter(|w| pred(w)).collect())
}
pub fn iter(&self) -> impl Iterator<Item = &'a Well> {
self.wells.clone().into_iter()
}
pub fn tops(&self, name: &str) -> WellsView<'a> {
self.filter(|w| w.top(name).is_some())
}
pub fn intersection<S: IntersectableSurface + ?Sized>(
&self,
surface: &S,
tolerance: f64,
) -> Result<WellIntersectionSet> {
self.evaluate(surface, tolerance, false)
}
pub fn intersections<S: IntersectableSurface + ?Sized>(
&self,
surface: &S,
tolerance: f64,
) -> Result<WellIntersectionSet> {
self.evaluate(surface, tolerance, true)
}
fn evaluate<S: IntersectableSurface + ?Sized>(
&self,
surface: &S,
tolerance: f64,
all: bool,
) -> Result<WellIntersectionSet> {
let _ = surface.intersection_mesh()?;
let mut out = WellIntersectionSet::default();
for well in &self.wells {
let mut any_trajectory = false;
for bore in well.sidetracks() {
if bore.trajectories().is_empty() {
continue;
}
any_trajectory = true;
let result = if all {
bore.intersections(surface, tolerance)
} else {
bore.intersection(surface, tolerance)
.map(|hit| hit.into_iter().collect())
};
match result {
Ok(hits) if hits.is_empty() => out.skipped.push(IntersectionDiagnostic {
well: well.id.clone(),
bore: bore.label.clone(),
reason: "outside_or_no_intersection".into(),
message: "trajectory does not intersect the finite surface geometry".into(),
}),
Ok(hits) => out.hits.extend(
hits.into_iter()
.map(|hit| hit.identify(Some(&well.id), Some(&bore.label), None)),
),
Err(error) => out.failed.push(IntersectionDiagnostic {
well: well.id.clone(),
bore: bore.label.clone(),
reason: if error.to_string().contains("coplanar") {
"coplanar".into()
} else if error.to_string().contains("crosses the surface") {
"multiple_intersections".into()
} else {
"intersection_error".into()
},
message: error.to_string(),
}),
}
}
if !any_trajectory {
out.skipped.push(IntersectionDiagnostic {
well: well.id.clone(),
bore: String::new(),
reason: "missing_trajectory".into(),
message: "well has no positioned bore".into(),
});
}
}
out.hits.sort_by(|a, b| {
a.well
.cmp(&b.well)
.then(a.bore.cmp(&b.bore))
.then(a.md.total_cmp(&b.md))
});
Ok(out)
}
pub fn len(&self) -> usize {
self.wells.len()
}
pub fn is_empty(&self) -> bool {
self.wells.is_empty()
}
}