|
3 | 3 |
|
4 | 4 | use ndarray::{Array2, Array3, s}; |
5 | 5 |
|
6 | | -use crate::{CodesError, KeyRead, KeyedMessage, errors::MessageNdarrayError}; |
| 6 | +use crate::{ |
| 7 | + AtomicMessage, CodesError, KeyedMessage, |
| 8 | + atomic_message::{ArrayKeyRead, ScalarKeyRead}, |
| 9 | + codes_handle::ThreadSafeHandle, |
| 10 | + errors::MessageNdarrayError, |
| 11 | + keyed_message::KeyRead, |
| 12 | +}; |
7 | 13 |
|
8 | 14 | /// Struct returned by [`KeyedMessage::to_lons_lats_values()`] method. |
9 | 15 | /// The arrays are collocated, meaning that `longitudes[i, j]` and `latitudes[i, j]` are the coordinates of `values[i, j]`. |
@@ -138,15 +144,135 @@ impl KeyedMessage<'_> { |
138 | 144 | } |
139 | 145 | } |
140 | 146 |
|
| 147 | +impl<S: ThreadSafeHandle> AtomicMessage<S> { |
| 148 | + /// Converts the message to a 2D ndarray. |
| 149 | + /// |
| 150 | + /// Returns ndarray where first dimension represents y coordinates and second dimension represents x coordinates, |
| 151 | + /// ie. `[lat, lon]`. |
| 152 | + /// |
| 153 | + /// Common convention for grib files on regular lon-lat grid assumes that: |
| 154 | + /// index `[0, 0]` is the top-left corner of the grid: |
| 155 | + /// x coordinates are increasing with the i index, |
| 156 | + /// y coordinates are decreasing with the j index. |
| 157 | + /// |
| 158 | + /// This convention can be checked with `iScansNegatively` and `jScansPositively` keys - |
| 159 | + /// if both are false, the above convention is used. |
| 160 | + /// |
| 161 | + /// Requires the keys `Ni`, `Nj` and `values` to be present in the message. |
| 162 | + /// |
| 163 | + /// Tested only with simple lat-lon grids. |
| 164 | + /// |
| 165 | + /// # Errors |
| 166 | + /// |
| 167 | + /// - When the required keys are not present or if their values are not of the expected type |
| 168 | + /// - When the number of values mismatch with the `Ni` and `Nj` keys |
| 169 | + #[cfg_attr(docsrs, doc(cfg(feature = "message_ndarray")))] |
| 170 | + pub fn to_ndarray(&self) -> Result<Array2<f64>, CodesError> { |
| 171 | + let ni: i64 = self.read_key("Ni")?; |
| 172 | + let ni = usize::try_from(ni).map_err(MessageNdarrayError::from)?; |
| 173 | + |
| 174 | + let nj: i64 = self.read_key("Nj")?; |
| 175 | + let nj = usize::try_from(nj).map_err(MessageNdarrayError::from)?; |
| 176 | + |
| 177 | + let vals: Vec<f64> = self.read_key("values")?; |
| 178 | + if vals.len() != (ni * nj) { |
| 179 | + return Err(MessageNdarrayError::UnexpectedValuesLength(vals.len(), ni * nj).into()); |
| 180 | + } |
| 181 | + |
| 182 | + let j_scanning: i64 = self.read_key("jPointsAreConsecutive")?; |
| 183 | + |
| 184 | + if ![0, 1].contains(&j_scanning) { |
| 185 | + return Err(MessageNdarrayError::UnexpectedKeyValue( |
| 186 | + "jPointsAreConsecutive".to_owned(), |
| 187 | + ) |
| 188 | + .into()); |
| 189 | + } |
| 190 | + |
| 191 | + let j_scanning = j_scanning != 0; |
| 192 | + |
| 193 | + let shape = if j_scanning { (ni, nj) } else { (nj, ni) }; |
| 194 | + let vals = Array2::from_shape_vec(shape, vals).map_err(MessageNdarrayError::from)?; |
| 195 | + |
| 196 | + if j_scanning { |
| 197 | + Ok(vals.reversed_axes()) |
| 198 | + } else { |
| 199 | + Ok(vals) |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + /// Same as [`KeyedMessage::to_ndarray()`] but returns the longitudes and latitudes alongside values. |
| 204 | + /// Fields are returned as separate arrays in [`RustyCodesMessage`]. |
| 205 | + /// |
| 206 | + /// Compared to `to_ndarray` this method has performance overhead as returned arrays may be cloned. |
| 207 | + /// |
| 208 | + /// This method requires the `latLonValues`, `Ni` and `Nj` keys to be present in the message. |
| 209 | + /// |
| 210 | + /// # Errors |
| 211 | + /// |
| 212 | + /// - When the required keys are not present or if their values are not of the expected type |
| 213 | + /// - When the number of values mismatch with the `Ni` and `Nj` keys |
| 214 | + #[cfg_attr(docsrs, doc(cfg(feature = "message_ndarray")))] |
| 215 | + pub fn to_lons_lats_values(&self) -> Result<RustyCodesMessage, CodesError> { |
| 216 | + let ni: i64 = self.read_key("Ni")?; |
| 217 | + let ni = usize::try_from(ni).map_err(MessageNdarrayError::from)?; |
| 218 | + |
| 219 | + let nj: i64 = self.read_key("Nj")?; |
| 220 | + let nj = usize::try_from(nj).map_err(MessageNdarrayError::from)?; |
| 221 | + |
| 222 | + let latlonvals: Vec<f64> = self.read_key("latLonValues")?; |
| 223 | + |
| 224 | + if latlonvals.len() != (ni * nj * 3) { |
| 225 | + return Err( |
| 226 | + MessageNdarrayError::UnexpectedValuesLength(latlonvals.len(), ni * nj * 3).into(), |
| 227 | + ); |
| 228 | + } |
| 229 | + |
| 230 | + let j_scanning: i64 = self.read_key("jPointsAreConsecutive")?; |
| 231 | + |
| 232 | + if ![0, 1].contains(&j_scanning) { |
| 233 | + return Err(MessageNdarrayError::UnexpectedKeyValue( |
| 234 | + "jPointsAreConsecutive".to_owned(), |
| 235 | + ) |
| 236 | + .into()); |
| 237 | + } |
| 238 | + |
| 239 | + let j_scanning = j_scanning != 0; |
| 240 | + |
| 241 | + let shape = if j_scanning { |
| 242 | + (ni, nj, 3_usize) |
| 243 | + } else { |
| 244 | + (nj, ni, 3_usize) |
| 245 | + }; |
| 246 | + |
| 247 | + let mut latlonvals = |
| 248 | + Array3::from_shape_vec(shape, latlonvals).map_err(MessageNdarrayError::from)?; |
| 249 | + |
| 250 | + if j_scanning { |
| 251 | + latlonvals.swap_axes(0, 1); |
| 252 | + } |
| 253 | + |
| 254 | + let (lats, lons, vals) = |
| 255 | + latlonvals |
| 256 | + .view_mut() |
| 257 | + .multi_slice_move((s![.., .., 0], s![.., .., 1], s![.., .., 2])); |
| 258 | + |
| 259 | + Ok(RustyCodesMessage { |
| 260 | + longitudes: lons.into_owned(), |
| 261 | + latitudes: lats.into_owned(), |
| 262 | + values: vals.into_owned(), |
| 263 | + }) |
| 264 | + } |
| 265 | +} |
| 266 | + |
141 | 267 | #[cfg(test)] |
142 | 268 | mod tests { |
143 | 269 | use fallible_iterator::FallibleIterator; |
144 | 270 | use float_cmp::assert_approx_eq; |
145 | 271 |
|
146 | 272 | use super::*; |
147 | | - use crate::DynamicKeyType; |
148 | 273 | use crate::ProductKind; |
149 | 274 | use crate::codes_handle::CodesHandle; |
| 275 | + use crate::keyed_message::DynamicKeyType; |
150 | 276 | use std::path::Path; |
151 | 277 |
|
152 | 278 | #[test] |
|
0 commit comments