eccodes/
codes_nearest.rs

1//! Definition and associated functions of `CodesNearest`
2//! used for finding nearest gridpoints in `KeyedMessage`
3
4use 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/// The structure used to find nearest gridpoints in `KeyedMessage`.
17#[derive(Debug)]
18pub struct CodesNearest<'a> {
19    nearest_handle: *mut codes_nearest,
20    parent_message: &'a KeyedMessage,
21}
22
23/// The structure returned by [`CodesNearest::find_nearest()`].
24/// Should always be analysed in relation to the coordinates requested in `find_nearest()`.
25#[derive(Copy, Clone, PartialEq, Debug, Default)]
26pub struct NearestGridpoint {
27    ///Index of this gridpoint
28    pub index: i32,
29    ///Latitude of this gridpoint in degrees north
30    pub lat: f64,
31    ///Longitude of this gridpoint in degrees east
32    pub lon: f64,
33    /// Distance between requested point and this gridpoint in kilometers
34    pub distance: f64,
35    ///Value of parameter at this gridpoint contained by `KeyedMessage` in corresponding units
36    pub value: f64,
37}
38
39impl KeyedMessage {
40    /// Creates a new instance of [`CodesNearest`] for the `KeyedMessage`.
41    /// [`CodesNearest`] can be used to find nearest gridpoints for given coordinates in the `KeyedMessage`
42    /// by calling [`find_nearest()`](crate::CodesNearest::find_nearest).
43    ///
44    /// # Errors
45    ///
46    /// This function returns [`CodesInternal`](crate::errors::CodesInternal) when
47    /// internal nearest handle cannot be created.
48    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    ///Function to get four [`NearestGridpoint`]s of a point represented by requested coordinates.
60    ///
61    ///The inputs are latitude and longitude of requested point in respectively degrees north and
62    ///degreed east.
63    ///
64    ///### Example
65    ///
66    ///```
67    ///  use eccodes::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags};
68    /// # use std::path::Path;
69    /// use eccodes::FallibleStreamingIterator;
70    /// # use anyhow::Context;
71    /// # fn main() -> anyhow::Result<()> {
72    /// let file_path = Path::new("./data/iceland.grib");
73    /// let product_kind = ProductKind::GRIB;
74    ///
75    /// let mut handle = CodesHandle::new_from_file(file_path, product_kind)?;
76    /// let msg = handle.next()?.context("no message")?;
77    ///
78    /// let c_nearest = msg.codes_nearest()?;
79    /// let out = c_nearest.find_nearest(64.13, -21.89)?;
80    /// # Ok(())
81    /// # }
82    ///```
83    ///
84    ///### Errors
85    ///
86    ///This function returns [`CodesInternal`](crate::errors::CodesInternal) when
87    ///one of ecCodes function returns the non-zero code.
88    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}