Skip to content

Commit dc0c3c4

Browse files
authored
Add support for generic altimeter with min/max samples (#409)
1 parent 7c45733 commit dc0c3c4

File tree

7 files changed

+136
-6
lines changed

7 files changed

+136
-6
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10+
## [2.13.0]
11+
12+
### Added
13+
14+
- Support for generic altimeter with min/max samples
15+
16+
### Changed
17+
1018
- Remove unused dependencies found by `cargo-shear`
1119
- Update Rust version from `1.90` to `1.92`
1220

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ members = ["crates/*"]
2121

2222
[workspace.package]
2323
authors = ["SkyTEM Surveys", "Marc Beck König"]
24-
version = "2.12.0"
24+
version = "2.13.0"
2525
edition = "2024"
2626
rust-version = "1.88.0"
2727
license = "MIT OR Apache-2.0"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::util::read_any_attribute_to_string;
2+
use chrono::{DateTime, NaiveDateTime, Utc};
3+
use hdf5::types::VarLenUnicode;
4+
use ndarray::Array1;
5+
use plotinator_log_if::{
6+
hdf5::SkytemHdf5,
7+
prelude::{GeoSpatialDataBuilder, Plotable},
8+
rawplot::RawPlot,
9+
};
10+
use serde::{Deserialize, Serialize};
11+
use std::path::Path;
12+
13+
const ALTITUDE_VALID_RANGE: (f64, f64) = (0.0, 500.0);
14+
15+
#[derive(Debug, Clone, Serialize, Deserialize)]
16+
pub struct AltimeterMinMax {
17+
starting_timestamp_utc: DateTime<Utc>,
18+
dataset_description: String,
19+
raw_plots: Vec<RawPlot>,
20+
metadata: Vec<(String, String)>,
21+
}
22+
23+
impl AltimeterMinMax {
24+
fn process_sensor(
25+
h5: &hdf5::File,
26+
sensor_id: u8,
27+
sensor_type: &str,
28+
raw_plots: &mut Vec<RawPlot>,
29+
) -> anyhow::Result<()> {
30+
let times: Vec<u64> = h5.dataset(&format!("timestamp_{sensor_id}"))?.read_raw()?;
31+
32+
for (suffix, dataset_prefix) in [("min", "height_min"), ("max", "height_max")] {
33+
let heights: Array1<f32> = h5
34+
.dataset(&format!("{dataset_prefix}_{sensor_id}"))?
35+
.read_1d()?;
36+
let heights: Vec<f64> = heights.into_iter().map(|h| h.into()).collect();
37+
let legend_name = format!("{sensor_type}-{suffix}-{sensor_id}");
38+
39+
if let Some(plot) = GeoSpatialDataBuilder::new(legend_name)
40+
.timestamp(&times)
41+
.altitude_from_laser(heights)
42+
.altitude_valid_range(ALTITUDE_VALID_RANGE)
43+
.build_into_rawplot()?
44+
{
45+
raw_plots.push(plot);
46+
}
47+
}
48+
49+
Ok(())
50+
}
51+
}
52+
53+
impl SkytemHdf5 for AltimeterMinMax {
54+
const DESCRIPTIVE_NAME: &str = "Generic Altimeter Min/Max";
55+
56+
fn from_path(path: impl AsRef<Path>) -> anyhow::Result<Self> {
57+
let h5 = hdf5::File::open(path)?;
58+
let sensor_count = h5.attr("sensor_count")?.read_scalar::<u8>()?;
59+
let sensor_type = h5
60+
.attr("sensor_type")?
61+
.read_scalar::<VarLenUnicode>()?
62+
.to_string();
63+
let starting_timestamp = h5
64+
.attr("timestamp")?
65+
.read_scalar::<VarLenUnicode>()?
66+
.to_string();
67+
let starting_timestamp_utc: DateTime<Utc> =
68+
NaiveDateTime::parse_from_str(&starting_timestamp, "%Y%m%d_%H%M%S")?.and_utc();
69+
70+
let metadata: Vec<(String, String)> = h5
71+
.attr_names()?
72+
.into_iter()
73+
.filter_map(|attr_name| {
74+
let attr = h5.attr(&attr_name).ok()?;
75+
let attr_val = read_any_attribute_to_string(&attr).ok()?;
76+
Some((attr_name, attr_val))
77+
})
78+
.collect();
79+
80+
let mut raw_plots = vec![];
81+
for sensor_id in 1..=sensor_count {
82+
Self::process_sensor(&h5, sensor_id, &sensor_type, &mut raw_plots)?;
83+
}
84+
85+
Ok(Self {
86+
starting_timestamp_utc,
87+
dataset_description: "Generic Altimeter(s) min/max".to_owned(),
88+
raw_plots,
89+
metadata,
90+
})
91+
}
92+
}
93+
94+
impl Plotable for AltimeterMinMax {
95+
fn raw_plots(&self) -> &[RawPlot] {
96+
&self.raw_plots
97+
}
98+
99+
fn first_timestamp(&self) -> DateTime<Utc> {
100+
self.starting_timestamp_utc
101+
}
102+
103+
fn descriptive_name(&self) -> &str {
104+
Self::DESCRIPTIVE_NAME
105+
}
106+
107+
fn labels(&self) -> Option<&[plotinator_log_if::prelude::PlotLabels]> {
108+
None
109+
}
110+
111+
fn metadata(&self) -> Option<Vec<(String, String)>> {
112+
Some(self.metadata.clone())
113+
}
114+
}

crates/plotinator-hdf5/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod altimeter;
2+
pub mod altimeter_minmax;
23
pub mod bifrost;
34
pub mod frame_altimeters;
45
pub mod frame_gps;

crates/plotinator-supported-formats/src/hdf5.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,5 @@ define_supported_hdf5_formats! {
101101
NjordIns => plotinator_hdf5::njord_ins::NjordIns,
102102
Tsc => plotinator_hdf5::tsc::Tsc,
103103
Altimeter => plotinator_hdf5::altimeter::Altimeter,
104+
AltimeterMinMax => plotinator_hdf5::altimeter_minmax::AltimeterMinMax,
104105
}

src/app/plot_app/supported_formats_table.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ const HDF5_LOG_FORMATS: &[LogFormat<'_>] = &[
125125
link: "https://github.com/luftkode/frame-altimeter",
126126
subitems: None,
127127
},
128+
LogFormat {
129+
title: "Generic Altimeter Min/Max",
130+
description: "Any altimeter implemented with the generic HDF5 altimeter format with min/max readings",
131+
link: "https://github.com/luftkode/frame-altimeter",
132+
subitems: None,
133+
},
128134
];
129135

130136
/// Draws a single log format entry

0 commit comments

Comments
 (0)