1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
//! # Unofficial high-level safe Rust bindings to ecCodes library
//!
//! [](https://github.com/ScaleWeather/eccodes)
//! [](https://crates.io/crates/eccodes)
//! [](https://choosealicense.com/licenses/apache-2.0/) \
//! [](https://deps.rs/repo/github/ScaleWeather/eccodes)
//! 
//! 
//!
//! This crate contains (mostly) safe high-level bindings for ecCodes library.
//! Bindings can be considered safe mainly because all crate structures
//! take ownership of the data in memory before passing the raw pointer to the ecCodes.
//!
//! **Currently only operations on GRIB files are supported.**
//!
//! **Version 0.14 introduces breaking changes, [check them below](#changes-in-version-014)!**
//!
//! Because of the ecCodes library API characteristics theses bindings are
//! rather thick wrapper to make this crate safe and convenient to use.
//!
//! This crate officially supports mainly Linux platforms same as the ecCodes library.
//! But it is possible to install ecCodes on `MacOS` and this crate successfully compiles and all tests pass.
//!
//! If you want to see more features released quicker do not hesitate
//! to contribute and check out [Github repository](https://github.com/ScaleWeather/eccodes).
//!
//! [ecCodes](https://confluence.ecmwf.int/display/ECC/ecCodes+Home) is an open-source library
//! for reading and writing GRIB and BUFR files developed by [European Centre for Medium-Range Weather Forecasts](https://www.ecmwf.int/).
//!
//! ## Errors and panics
//!
//! This crate aims to return error whenever possible, even if the error is caused by implementation bug.
//! As ecCodes is often used in scientific applications with long and extensive jobs,
//! this allows the user to handle the error in the way that suits them best and not risk crashes.
//!
//! All error descriptions are provided in the [`errors`] module.
//! Destructors, which cannot panic, report errors through `tracing` and `log` crate.
//!
//! None of the functions in this crate explicitly panics.
//! However, users should be aware that dependencies (eg. `ndarray`) might panic in some edge cases.
//!
//! ## Safety
//!
//! This crate aims to be as safe as possible and a lot of effort has been put into testing its safety.
//! Moreover, pointers are always checked for null before being dereferenced.
//!
//! That said, neither main developer nor contributors have expertise in unsafe Rust and bugs might have
//! slipped through. We are also not responsible for bugs in the ecCodes library.
//!
//! **For critical applications always perform extensive testing before using this crate in production.**
//!
//! If you find a bug or have a suggestion, feel free to discuss it on Github.
//!
//! ## Usage
//!
//! To access a GRIB file you need to create [`CodesFile`] with one of the provided constructors.
//!
//! ecCodes represents GRIB files as a set of separate messages, each containing data fields at specific time and level.
//! Messages are represented here by a generic [`CodesMessage`](codes_message::CodesMessage) structure, but you shouldn't use it directly.
//! Instead use [`RefMessage`], [`ArcMessage`] or [`BufMessage`] to operations - check the docs for more information when to use each.
//!
//! To obtain `CodesMessage` from `CodesFile` you need to create an instance of [`RefMessageIter`] or [`ArcMessageIter`] using
//! [`CodesFile::ref_message_iter()`] or [`CodesFile::arc_message_iter()`].
//! Those structures implement [`FallibleIterator`], please check its documentation if you are not familiar with it.
//!
//! `CodesMessage` implements several methods to access the data as needed, most of those can be called directly.
//! Almost all methods can be called on any `CodesMessage`, except for [`KeyWrite`] operations, which can be called only on [`BufMessage`]
//! to avoid confusion if written keys are save to file or not.
//!
//! Data contained by `CodesMessage` is represented as *keys* (like in dictionary).
//! Keys can be read with static types using [`read_key()`](KeyRead::read_key) or with [dynamic types](codes_message::DynamicKeyType)
//! using [`read_key_dynamic()`](codes_message::CodesMessage::read_key_dynamic).
//! To discover what keys are present in a message use [`KeysIterator`](KeysIterator).
//!
//! With `ndarray` feature (enabled by default) you can also read `CodesMessage` into `ndarray` using [`to_ndarray()`](codes_message::CodesMessage::to_ndarray)
//! and [`to_lons_lats_values()`](codes_message::CodesMessage::to_lons_lats_values).
//!
//! You can use [`CodesNearest`] to get the data values of four nearest gridpoints for given coordinates.
//!
//! To modify keys within the message use [`write_key_unchecked()`](KeyWrite::write_key_unchecked).
//! To save that modified message use [`write_to_file()`](codes_message::CodesMessage::write_to_file).
//!
//! #### Example 1 - Reading GRIB file
//!
//! In this example we are reading mean sea level pressure for 4 gridpoints nearest to
//! Reykjavik (64.13N, -21.89E) for 1st June 2021 00:00 UTC from ERA5 Climate Reanalysis.
//!
//! ```
//! use eccodes::{CodesFile, FallibleIterator, KeyRead, ProductKind};
//!
//! # fn main() -> anyhow::Result<()> {
//! // Open the GRIB file and create the CodesFile
//! let mut handle = CodesFile::new_from_file("./data/iceland.grib", ProductKind::GRIB)?;
//! // Use iterator to find a message with shortName "msl" and typeOfLevel "surface"
//! // We can use while let or for_each() to iterate over the messages
//! while let Some(msg) = handle.ref_message_iter().next()? {
//! // We need to specify the type of key we read
//! let short_name: String = msg.read_key("shortName")?;
//! let type_of_level: String = msg.read_key("typeOfLevel")?;
//! if short_name == "msl" && type_of_level == "surface" {
//! // Create CodesNearest for given message
//! let nearest_gridpoints = msg
//! .codes_nearest()?
//! // Find the nearest gridpoints to Reykjavik
//! .find_nearest(64.13, -21.89)?;
//! // Print value and distance of the nearest gridpoint
//! println!(
//! "value: {}, distance: {}",
//! nearest_gridpoints[3].value, nearest_gridpoints[3].distance
//! );
//! }
//! }
//! # Ok(())
//! # }
//! ```
//!
//! #### (New in 0.14) Example 2 - Concurrent read
//!
//! This example shows how `ArcMessage` can be used to do concurrent operations
//! on different message within one file and on the same message as well.
//!
//! ```
//! use eccodes::{CodesError, CodesFile, FallibleIterator, KeyRead, ProductKind};
//! use std::sync::Arc;
//!
//! # fn main() -> anyhow::Result<()> {
//! // We open the file as before
//! let handle = CodesFile::new_from_file("./data/iceland-levels.grib", ProductKind::GRIB)?;
//!
//! // Note different mutability - RefMessageIter required the handle to be mutable
//! let mut arc_msg_gen = handle.arc_message_iter();
//!
//! let mut join_handles = vec![];
//!
//! while let Some(msg) = arc_msg_gen.next()? {
//! // ArcMessage is Send+Sync
//! let msg1 = Arc::new(msg);
//! let msg2 = msg1.clone();
//!
//! // For each message we spawn two threads then we do operations simulataneously
//! join_handles.push(std::thread::spawn(move || msg1.read_key("shortName")));
//! join_handles.push(std::thread::spawn(move || msg2.read_key("typeOfLevel")));
//! }
//!
//! // Now we collect and print the results
//! let read_results = join_handles
//! .into_iter()
//! // In production you should probably handle this join error
//! .map(|jh| jh.join().unwrap())
//! .collect::<Result<Vec<String>, CodesError>>()?;
//!
//! println!("{read_results:?}");
//!
//! # Ok(())
//! # }
//! ```
//!
//! #### Example 3 - Writing GRIB files and indexing
//!
//! The crate provides basic support for setting `CodesMessage` keys
//! and writing GRIB files. To create a new custom message we need to copy
//! existing one from other GRIB file, modify the keys and write to new file.
//!
//! In this example we are computing the temperature at 850hPa as an average
//! of 900hPa and 800hPa and writing it to a new file. This example also shows
//! how you can create a simple index for a file to get messages we need.
//! To read values from a message you can also use [`to_ndarray()`](codes_message::CodesMessage::to_ndarray).
//!
//! ```
//! use anyhow::Context;
//! use eccodes::{CodesFile, FallibleIterator, KeyRead, KeyWrite, ProductKind};
//! use std::{collections::HashMap, fs::remove_file, path::Path};
//!
//! # fn main() -> anyhow::Result<()> {
//! // Start by opening the file and creating CodesFile
//! let file_path = Path::new("./data/iceland-levels.grib");
//! let mut handle = CodesFile::new_from_file(file_path, ProductKind::GRIB)?;
//!
//! // To build the index we need to collect all messages
//! let messages = handle.ref_message_iter().collect::<Vec<_>>()?;
//! let mut msg_index = HashMap::new();
//! msg_index.reserve(messages.len());
//!
//! // Now we can put the messages into a hashmap and index them by shortName and level
//! for msg in messages.into_iter() {
//! // all messages in this grib are on the same level type
//! let short_name: String = msg.read_key("shortName")?;
//! let level: i64 = msg.read_key("level")?;
//!
//! msg_index.insert((short_name, level), msg);
//! }
//!
//! // Now we can get the values from messages we need
//! let t_800: Vec<f64> = msg_index
//! .get(&("t".to_string(), 800))
//! .context("message missing")? // we use anyhow context for convienience
//! .read_key("values")?;
//! let t_900: Vec<f64> = msg_index
//! .get(&("t".to_string(), 800))
//! .context("message missing")?
//! .read_key("values")?;
//!
//! // We will also clone t at 700hPa to edit it
//! let mut t_850_msg = msg_index
//! .get(&("t".to_string(), 700))
//! .context("message missing")?
//! .try_clone()?;
//!
//! // Compute temperature at 850hPa
//! let t_850_values: Vec<f64> = t_800
//! .iter()
//! .zip(t_900.iter())
//! .map(|t| (t.0 + t.1) / 2.0)
//! .collect();
//!
//! // Edit appropriate keys in the cloned (editable) message
//! t_850_msg.write_key_unchecked("level", 850)?;
//! t_850_msg.write_key_unchecked("values", t_850_values.as_slice())?;
//!
//! // Save the message to a new file without appending
//! t_850_msg.write_to_file(Path::new("iceland-850.grib"), false)?;
//! # remove_file(Path::new("iceland-850.grib")).unwrap();
//! # Ok(())
//! # }
//! ```
//!
//! ## Changes in version 0.14
//!
//! 1. `experimental_index` feature has been removed - users are encouraged to create their own indexes as shown above or use iterator filtering
//! 2. `message_ndarray` feature has been renamed to `ndarray`
//! 3. `CodesHandle` has been renamed to `CodesFile`
//! 4. `KeyedMessage` has been replaced with generic `CodesMessage` - `RefMessage` has the most similar behaviour to `CodesMessage`]
//! 5. `write_key` is now `write_key_unchecked`
//! 6. Dependency on `FallibleStreamingIterator` has been removed
//!
//! ## Feature Flags
//!
//! - `ndarray` - enables support for converting [`CodesMessage`](codes_message::CodesMessage) to [`ndarray::Array`].
//! This feature is enabled by default. It is currently tested only with simple lat-lon grids.
//!
//! - `docs` - builds the crate without linking ecCodes, particularly useful when building the documentation
//! on [docs.rs](https://docs.rs/). For more details check documentation of [eccodes-sys](https://crates.io/crates/eccodes-sys).
//!
//! To build your own crate with this crate as dependency on docs.rs without linking ecCodes add following lines to your `Cargo.toml`
//!
//! ```text
//! [package.metadata.docs.rs]
//! features = ["eccodes/docs"]
//! ```
pub use ;
pub use ;
pub use ;
pub use CodesError;
pub use ;
pub use ;