use crate::foundation::{GeoError, Result};
use crate::io::{is_null_sentinel, PointData, DEFAULT_NULL_1E30};
use indexmap::IndexMap;
use std::path::Path;
#[derive(Debug, Clone)]
pub(crate) struct EarthVisionNode {
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) z: f64,
pub(crate) column: Option<f64>,
pub(crate) row: Option<f64>,
}
#[derive(Debug)]
pub(crate) struct EarthVisionGridData {
pub(crate) nodes: Vec<EarthVisionNode>,
pub(crate) grid_size: Option<(usize, usize)>,
}
impl EarthVisionGridData {
fn finite_point_data(&self) -> Result<PointData> {
let indexed = self
.nodes
.iter()
.any(|node| node.column.is_some() || node.row.is_some());
let mut coords = Vec::new();
let mut columns = Vec::new();
let mut rows = Vec::new();
for node in &self.nodes {
if !node.z.is_finite() {
continue;
}
coords.push([node.x, node.y, node.z]);
if indexed {
columns.push(node.column.unwrap_or(f64::NAN));
rows.push(node.row.unwrap_or(f64::NAN));
}
}
let mut attrs = IndexMap::new();
if indexed {
attrs.insert("column".to_string(), columns);
attrs.insert("row".to_string(), rows);
}
PointData::new(coords, attrs)
}
}
pub fn load_earthvision_grid(path: &Path) -> Result<PointData> {
load_earthvision_grid_all(path)?.finite_point_data()
}
pub(crate) fn load_earthvision_grid_all(path: &Path) -> Result<EarthVisionGridData> {
let text = crate::io::decode_latin1(&std::fs::read(path)?);
let mut null = DEFAULT_NULL_1E30;
let mut nodes = Vec::new();
let mut grid_size = None;
let mut saw_marker = false;
for line in text.lines() {
let s = line.trim();
if s.is_empty() {
continue;
}
if let Some(comment) = s.strip_prefix('#') {
let up = comment.to_ascii_uppercase();
if up.contains("EARTHVISION")
|| up.contains("GRID_SIZE")
|| up.contains("GRID_SPACE")
|| up.contains("FIELD:")
{
saw_marker = true;
}
if up.contains("GRID_SIZE") {
let sizes: Vec<usize> = comment
.split(|c: char| !c.is_ascii_digit())
.filter(|token| !token.is_empty())
.filter_map(|token| token.parse().ok())
.collect();
if sizes.len() >= 2 {
grid_size = Some((sizes[0], sizes[1]));
}
}
if up.contains("NULL") {
if let Some(v) = comment
.split(|c: char| c == ':' || c.is_whitespace())
.filter_map(|t| t.parse::<f64>().ok())
.next_back()
{
null = v;
}
}
continue;
}
let nums: Vec<f64> = s
.split(|c: char| c.is_whitespace() || c == ',')
.filter(|t| !t.is_empty())
.filter_map(|t| t.parse::<f64>().ok())
.collect();
if nums.len() < 3 {
continue;
}
let z = if is_null_sentinel(nums[2], null) {
f64::NAN
} else {
nums[2]
};
nodes.push(EarthVisionNode {
x: nums[0],
y: nums[1],
z,
column: nums.get(3).copied(),
row: nums.get(4).copied(),
});
}
if !saw_marker && nodes.is_empty() {
return Err(GeoError::Format(
"EarthVision grid: no header markers and no data rows".into(),
));
}
Ok(EarthVisionGridData { nodes, grid_size })
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn reads_nodes_and_drops_nulls() {
let body = "\
# Type: scattered data
# Field: 1 X
# Field: 2 Y
# Field: 3 Z meters
# Grid_size: 2 x 2
# Null_value: 1.0e30
# End:
100.0 200.0 -50.0
110.0 200.0 -51.0
100.0 210.0 1.0e30
110.0 210.0 -52.5
";
let p = std::env::temp_dir().join("petekio_ev_grid.EarthVisionGrid");
std::fs::File::create(&p)
.unwrap()
.write_all(body.as_bytes())
.unwrap();
let all = load_earthvision_grid_all(&p).unwrap();
assert_eq!(all.nodes.len(), 4);
assert!(all.nodes[2].z.is_nan());
assert_eq!((all.nodes[2].x, all.nodes[2].y), (100.0, 210.0));
assert_eq!(all.grid_size, Some((2, 2)));
let (pts, attrs) = all.finite_point_data().unwrap().into_parts();
assert_eq!(pts.len(), 3); assert_eq!(pts[0], [100.0, 200.0, -50.0]);
assert_eq!(pts[2], [110.0, 210.0, -52.5]);
assert!(attrs.is_empty());
}
#[test]
fn preserves_petrel_column_row_fields() {
let body = "\
# Type: scattered data
# Field: 1 x
# Field: 2 y
# Field: 3 z meters
# Field: 4 column
# Field: 5 row
# Grid_size: 2 x 2
# End:
100.0 200.0 -50.0 1 1
110.0 200.0 -51.0 2 1
100.0 210.0 -52.0 1 2
110.0 210.0 -53.0 2 2
";
let p = std::env::temp_dir().join("petekio_ev_grid_indexed.EarthVisionGrid");
std::fs::File::create(&p)
.unwrap()
.write_all(body.as_bytes())
.unwrap();
let (pts, attrs) = load_earthvision_grid(&p).unwrap().into_parts();
assert_eq!(pts.len(), 4);
assert_eq!(attrs["column"], vec![1.0, 2.0, 1.0, 2.0]);
assert_eq!(attrs["row"], vec![1.0, 1.0, 2.0, 2.0]);
}
}