async_geotiff/io/
scale_offset.rs1fn items(xml: &str) -> Vec<(String, usize, String)> {
8 use quick_xml::events::Event;
9
10 let mut reader = quick_xml::Reader::from_str(xml);
11 reader.config_mut().trim_text(true);
12 let mut out = Vec::new();
13 let mut current: Option<(String, usize)> = None;
14
15 loop {
16 match reader.read_event() {
17 Ok(Event::Start(e)) if e.name().as_ref() == b"Item" => {
18 let mut name = String::new();
19 let mut sample = 0usize;
20 for attr in e.attributes().flatten() {
21 let Ok(value) = attr.decode_and_unescape_value(reader.decoder()) else {
22 continue;
23 };
24 match attr.key.as_ref() {
25 b"name" => name = value.into_owned(),
26 b"sample" => sample = value.parse().unwrap_or(0),
27 _ => {}
28 }
29 }
30 current = (!name.is_empty()).then_some((name, sample));
31 }
32 Ok(Event::Text(e)) => {
33 if let Some((name, sample)) = current.take() {
34 if let Ok(value) = e.decode() {
35 out.push((name, sample, value.trim().to_string()));
36 }
37 }
38 }
39 Ok(Event::End(e)) if e.name().as_ref() == b"Item" => {
40 current = None;
41 }
42 Ok(Event::Eof) => break,
43 Err(_) => break,
44 _ => {}
45 }
46 }
47 out
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct ScaleOffset {
53 pub scale: f64,
55 pub offset: f64,
57}
58
59impl ScaleOffset {
60 pub const IDENTITY: Self = Self {
62 scale: 1.0,
63 offset: 0.0,
64 };
65
66 pub fn apply(self, raw: f64) -> f64 {
68 raw * self.scale + self.offset
69 }
70
71 pub fn is_identity(self) -> bool {
73 self == Self::IDENTITY
74 }
75}
76
77pub fn parse_scale_offset(gdal_metadata: Option<&str>, bands: usize) -> Vec<ScaleOffset> {
79 let parsed = gdal_metadata.map(items).unwrap_or_default();
80 let get = |name: &str, b: usize| -> Option<f64> {
81 parsed
82 .iter()
83 .find(|(n, s, _)| n == name && *s == b)
84 .and_then(|(_, _, v)| v.parse::<f64>().ok())
85 };
86 (0..bands)
87 .map(|b| ScaleOffset {
88 scale: get("SCALE", b).unwrap_or(1.0),
89 offset: get("OFFSET", b).unwrap_or(0.0),
90 })
91 .collect()
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 const XML: &str = r#"<GDALMetadata>
99 <Item name="SCALE" sample="0">0.0001</Item>
100 <Item name="OFFSET" sample="0">-0.1</Item>
101 <Item name="SCALE" sample="1">2.0</Item>
102 </GDALMetadata>"#;
103
104 #[test]
105 fn parses_per_band_scale_offset() {
106 let so = parse_scale_offset(Some(XML), 3);
107 assert_eq!(so.len(), 3);
108 assert_eq!(
109 so[0],
110 ScaleOffset {
111 scale: 0.0001,
112 offset: -0.1
113 }
114 );
115 assert_eq!(
117 so[1],
118 ScaleOffset {
119 scale: 2.0,
120 offset: 0.0
121 }
122 );
123 assert_eq!(so[2], ScaleOffset::IDENTITY); }
125
126 #[test]
127 fn none_metadata_is_all_identity() {
128 assert_eq!(
129 parse_scale_offset(None, 2),
130 vec![ScaleOffset::IDENTITY, ScaleOffset::IDENTITY]
131 );
132 }
133
134 #[test]
135 fn malformed_xml_is_identity() {
136 assert_eq!(
137 parse_scale_offset(Some("<not xml"), 1),
138 vec![ScaleOffset::IDENTITY]
139 );
140 }
141}