1use std::ptr::null_mut;
5
6use eccodes_sys::codes_nearest;
7use log::error;
8
9use crate::{
10 intermediate_bindings::{
11 codes_grib_nearest_delete, codes_grib_nearest_find, codes_grib_nearest_new,
12 },
13 CodesError, KeyedMessage,
14};
15
16#[derive(Debug)]
18pub struct CodesNearest<'a> {
19 nearest_handle: *mut codes_nearest,
20 parent_message: &'a KeyedMessage,
21}
22
23#[derive(Copy, Clone, PartialEq, Debug, Default)]
26pub struct NearestGridpoint {
27 pub index: i32,
29 pub lat: f64,
31 pub lon: f64,
33 pub distance: f64,
35 pub value: f64,
37}
38
39impl KeyedMessage {
40 pub fn codes_nearest(&self) -> Result<CodesNearest, CodesError> {
49 let nearest_handle = unsafe { codes_grib_nearest_new(self.message_handle)? };
50
51 Ok(CodesNearest {
52 nearest_handle,
53 parent_message: self,
54 })
55 }
56}
57
58impl CodesNearest<'_> {
59 pub fn find_nearest(&self, lat: f64, lon: f64) -> Result<[NearestGridpoint; 4], CodesError> {
89 let output_points;
90
91 unsafe {
92 output_points = codes_grib_nearest_find(
93 self.parent_message.message_handle,
94 self.nearest_handle,
95 lat,
96 lon,
97 )?;
98 }
99
100 Ok(output_points)
101 }
102}
103
104#[doc(hidden)]
105impl Drop for CodesNearest<'_> {
106 fn drop(&mut self) {
107 unsafe {
108 codes_grib_nearest_delete(self.nearest_handle).unwrap_or_else(|error| {
109 error!(
110 "codes_grib_nearest_delete() returned an error: {:?}",
111 &error
112 );
113 });
114 }
115
116 self.nearest_handle = null_mut();
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use std::path::Path;
123
124 use anyhow::{Context, Result};
125 use fallible_streaming_iterator::FallibleStreamingIterator;
126
127 use crate::{CodesHandle, ProductKind};
128
129 #[test]
130 fn find_nearest() -> Result<()> {
131 let file_path1 = Path::new("./data/iceland.grib");
132 let file_path2 = Path::new("./data/iceland-surface.grib");
133 let product_kind = ProductKind::GRIB;
134
135 let mut handle1 = CodesHandle::new_from_file(file_path1, product_kind)?;
136 let msg1 = handle1.next()?.context("Message not some")?;
137 let nrst1 = msg1.codes_nearest()?;
138 let out1 = nrst1.find_nearest(64.13, -21.89)?;
139
140 let mut handle2 = CodesHandle::new_from_file(file_path2, product_kind)?;
141 let msg2 = handle2.next()?.context("Message not some")?;
142 let nrst2 = msg2.codes_nearest()?;
143 let out2 = nrst2.find_nearest(64.13, -21.89)?;
144
145 assert!(out1[0].value > 10000.0);
146 assert!(out2[3].index == 551);
147 assert!(out1[1].lat == 64.0);
148 assert!(out2[2].lon == -21.75);
149 assert!(out1[0].distance > 15.0);
150
151 Ok(())
152 }
153
154 #[test]
155 fn destructor() -> Result<()> {
156 let file_path = Path::new("./data/iceland.grib");
157 let product_kind = ProductKind::GRIB;
158
159 let mut handle = CodesHandle::new_from_file(file_path, product_kind)?;
160 let current_message = handle.next()?.context("Message not some")?;
161
162 let _nrst = current_message.codes_nearest()?;
163
164 drop(_nrst);
165
166 testing_logger::validate(|captured_logs| {
167 assert_eq!(captured_logs.len(), 1);
168 assert_eq!(captured_logs[0].body, "codes_grib_nearest_delete");
169 assert_eq!(captured_logs[0].level, log::Level::Trace);
170 });
171
172 drop(handle);
173
174 testing_logger::validate(|captured_logs| {
175 assert_eq!(captured_logs.len(), 1);
176 assert_eq!(captured_logs[0].body, "codes_handle_delete");
177 assert_eq!(captured_logs[0].level, log::Level::Trace);
178 });
179
180 Ok(())
181 }
182}