3 releases
new 0.1.2 | Apr 28, 2025 |
---|---|
0.1.1 | Apr 28, 2025 |
0.1.0 | Apr 26, 2025 |
#133 in Geospatial
47 downloads per month
165KB
2.5K
SLoC
Meteostat for Rust
The Weather's Record Keeper - In Rust!
This crate provides a convenient asynchronous Rust interface for accessing historical weather and climate data from Meteostat, leveraging their publicly available bulk data interface. It allows fetching data for thousands of weather stations worldwide.
Meteostat is a free and open provider of weather & climate data. They do the hard work of collecting, processing, and providing the data. This crate is simply a Rust client for their bulk API. Please consider supporting Meteostat if you find their data useful: Donate to Meteostat.
Take a look at yesterday's temperatures or discover the weather hundreds of years ago, right from your Rust application.
Key Features
- Fetch by Station ID: Get weather data for a specific Meteostat station ID.
- Fetch by Location: Get weather data for a location, specified by a latitude and longitude.
- Find Nearby Stations: Search for stations near coordinates, optionally filtering by distance and required data availability (inventory).
- Multiple Frequencies: Supports Hourly, Daily, Monthly, and Climate Normals data.
- Efficient Data Handling: Returns data as Polars
LazyFrame
s, allowing for powerful, memory-efficient filtering and manipulation before collecting results. - Convenient Filtering: Includes a
MeteostatFrameFilterExt
trait for easy filtering ofLazyFrame
s by date, year, or datetime ranges. - Automatic Caching: Downloads and caches station metadata and weather data files locally to speed up subsequent requests and reduce load on Meteostat's servers.
- Asynchronous: Built with
tokio
for non-blocking I/O.
Installation
Add meteostat
to your Cargo.toml
dependencies:
cargo add meteostat
Basic Usage
Here's a quick example demonstrating fetching data by station ID and by location:
use meteostat::{Meteostat, LatLon, Frequency, MeteostatError, MeteostatFrameFilterExt};
use polars::prelude::*;
use chrono::{DateTime, NaiveDate, Utc};
use std::str::FromStr; // For parsing DateTime<Utc>
#[tokio::main]
async fn main() -> Result<(), MeteostatError> {
// Initialize the client (uses default cache directory)
let client = Meteostat::new().await?;
// --- Example: Get data for a location ---
let berlin_center = LatLon(52.52, 13.40);
let hourly_lazy = client
.from_location()
.location(berlin_center)
.frequency(Frequency::Hourly)
.call()
.await?;
// Filter for a specific date
let start_datetime = DateTime::<Utc>::from_str("2022-01-10T00:00:00Z").unwrap(); // Jan 10 2022 00:00:00 UTC
let end_datetime = DateTime::<Utc>::from_str("2022-01-10T23:59:59Z").unwrap(); // Jan 10 2022 23:59:59 UTC
let specific_day_hourly = hourly_lazy
.filter_hourly(start_datetime, end_datetime)
.collect()?;
println!("Hourly data near Berlin for 2022-01-10:\n{}", specific_day_hourly.head(Some(5)));
Ok(())
}
(See more examples in the examples directory)
Data Handling
Polars LazyFrame
All weather data fetching methods (from_station
, from_location
) return a Polars LazyFrame
. This allows you to:
- Chain operations: Apply filters, selections, aggregations, etc., without loading the entire dataset into memory immediately.
- Optimize queries: Polars optimizes the plan before execution.
- Collect when ready: Use
.collect()?
to execute the plan and get aDataFrame
in memory.
This is particularly beneficial when dealing with potentially large historical datasets.
Caching
The crate automatically caches downloaded data to avoid redundant downloads and respect Meteostat's resources:
- Station Metadata: The list of all stations (
stations/lite.json.gz
) is downloaded once and cached. - Weather Data: Individual station data files (e.g.,
hourly/10637.csv.gz
) are downloaded and cached per station and frequency.
By default, cache files are stored in your system's standard cache directory (e.g., ~/.cache/meteostat_rs
on Linux,
%LOCALAPPDATA%/meteostat_rs_cache
on Windows).
You can specify a custom cache location using Meteostat::with_cache_folder(path)
.
Filtering Data Frames
The MeteostatFrameFilterExt
trait
provides convenient methods for filtering the LazyFrame
s returned by this crate based on date and time.
use meteostat::{Meteostat, Frequency, MeteostatError, MeteostatFrameFilterExt};
use polars::prelude::*;
use chrono::{NaiveDate, Utc, TimeZone};
#[tokio::main]
async fn main() -> Result<(), MeteostatError> {
let client = Meteostat::new().await?;
let station_id = "10637"; // Schiphol
// --- Filter Daily Data by Year ---
let daily_lazy = client.from_station().station(station_id).frequency(Frequency::Daily).call().await?;
let daily_2022 = daily_lazy.filter_daily_by_year(2022)?.collect()?;
println!("Daily data for 2022:\n{}", daily_2022.head(Some(3)));
// --- Filter Hourly Data by Datetime Range ---
let hourly_lazy = client.from_station().station(station_id).frequency(Frequency::Hourly).call().await?;
let start_dt = Utc.with_ymd_and_hms(2023, 5, 1, 6, 0, 0).unwrap(); // May 1st 2023, 06:00 UTC
let end_dt = Utc.with_ymd_and_hms(2023, 5, 1, 12, 0, 0).unwrap(); // May 1st 2023, 12:00 UTC
let hourly_morning = hourly_lazy.filter_hourly(start_dt, end_dt).collect()?;
println!("\nHourly data for morning of 2023-05-01:\n{}", hourly_morning.head(Some(3)));
// --- Get a Single Daily Row ---
let daily_lazy_again = client.from_station().station(station_id).frequency(Frequency::Daily).call().await?;
let specific_date = NaiveDate::from_ymd_opt(2023, 10, 26).unwrap();
let single_day = daily_lazy_again.get_daily_row(specific_date).collect()?;
println!("\nDaily data for {}:\n{}", specific_date, single_day);
Ok(())
}
See the
MeteostatFrameFilterExt
documentation
for all available filtering methods (filter_daily
, filter_monthly
, filter_climate
, get_hourly_row
, etc.).
Data Source and Attribution
- All weather data is sourced from Meteostat.
- This crate uses Meteostat's free bulk data interface. No API key is required.
API Documentation
Full API documentation is available on docs.rs.
Example: Plotting Data
You can easily use the DataFrame
output with plotting libraries like plotlars
.
// Requires the 'examples' feature: cargo run --example graph_data --features examples
use std::error::Error;
use meteostat::{Frequency, LatLon, Meteostat, MeteostatFrameFilterExt};
use plotlars::{Line, LinePlot, Plot, Rgb, Text};
use polars::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let meteostat = Meteostat::new().await?;
let location = LatLon(52.118641, 5.185589); // De Bilt, Netherlands
let weather_data: DataFrame = meteostat
.from_location()
.location(location)
.frequency(Frequency::Daily)
.call()
.await?
.filter_daily_by_year(2023)?
.collect()?;
println!(
"Daily Data for De Bilt (2023):\n{}",
weather_data.head(Some(5))
);
plot_temperature(&weather_data);
Ok(())
}
fn plot_temperature(dataset: &DataFrame) {
LinePlot::builder()
.data(&dataset)
.x("date")
.y("tavg") // Average temperature
.additional_lines(vec!["tmin", "tmax"]) // Min and Max temps
.colors(vec![
Rgb(120, 120, 120),
Rgb(69, 143, 196),
Rgb(199, 115, 42),
])
.lines(vec![Line::Solid, Line::Dot, Line::Dot])
.width(3.0)
.plot_title(
Text::from("Temperature at De Bilt (2023)")
.font("Arial")
.size(18),
)
.build()
.plot();
}
To run this specific example, enable the examples
feature:
cargo run --example graph_data --features examples
(This will generate a plot similar to the one shown at the top of this README)
Contributing
Contributions, bug reports, and feature requests are welcome! Please feel free to open an issue or submit a pull request on the GitHub repository.
License
This crate is licensed under the Apache License 2.0. See the LICENSE file for details.
Dependencies
~50–85MB
~1.5M SLoC