include modified weatherapi, use forcast, use worst rain detection
This commit is contained in:
1
client/openweathermap/CNAME
Normal file
1
client/openweathermap/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
openweathermap.thats-software.com
|
22
client/openweathermap/Cargo.toml
Normal file
22
client/openweathermap/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
authors = ["Patrick Hoffmann <pat@thats-software.com>"]
|
||||
description = "easy access current weather data from OpenWeatherMap"
|
||||
documentation = "https://docs.rs/openweathermap"
|
||||
edition = "2018"
|
||||
homepage = "https://openweathermap.thats-software.com"
|
||||
keywords = ["weather", "openweathermap"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "openweathermap"
|
||||
readme = "README.md"
|
||||
version = "0.2.4"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = {version = "0.3.1", features = ["executor"]}
|
||||
http = {version = "0.2.4"}
|
||||
rand = {version = "0.8.3"}
|
||||
regex = {version = "1.4.6"}
|
||||
reqwest = {version = "0.11.3", default-features = false, features = ["blocking"]}
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
serde_json = {version = "1.0"}
|
21
client/openweathermap/LICENSE
Normal file
21
client/openweathermap/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Patrick Hoffmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
141
client/openweathermap/README.md
Normal file
141
client/openweathermap/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# openweathermap [](https://github.com/fightling/openweathermap/actions/workflows/rust.yml)
|
||||
|
||||
...is a *rust crate* which lets you easily access current weather data from [OpenWeatherMap](https://openweathermap.org/). This is an *unofficial* extension I have made to learn *rust* a little but I hope you have fun with it.
|
||||
|
||||
## Contents
|
||||
|
||||
<!-- MDTOC maxdepth:6 firsth1:2 numbering:0 flatten:0 bullets:1 updateOnSave:1 -->
|
||||
|
||||
- [Contents](#contents)
|
||||
- [How to use](#how-to-use)
|
||||
- [Get continuous weather updates](#get-continuous-weather-updates)
|
||||
- [First: Start polling](#first-start-polling)
|
||||
- [Then: Get weather updates](#then-get-weather-updates)
|
||||
- [Nothing New: `None`](#nothing-new-none)
|
||||
- [Weather Update: `CurrentWeather`](#weather-update-currentweather)
|
||||
- [Some Error: `Err`](#some-error-err)
|
||||
- [Get weather just once](#get-weather-just-once)
|
||||
- [Reference Documentation](#reference-documentation)
|
||||
- [Links](#links)
|
||||
- [Website](#website)
|
||||
- [*github* repository](#github-repository)
|
||||
- [on *crates.io*](#on-cratesio)
|
||||
- [License](#license)
|
||||
|
||||
<!-- /MDTOC -->
|
||||
|
||||
## How to use
|
||||
|
||||
First add this crate to your dependencies in you `Cargo.toml` file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
openweathermap = "0.2.4"
|
||||
```
|
||||
### Get continuous weather updates
|
||||
|
||||
Then use the crate in your rust source file by calling `openweathermap::init()` which returns a receiver object.
|
||||
You can then use this receiver object to call `openweathermap::update()` to get weather updates like in the following example:
|
||||
|
||||
```rust
|
||||
extern crate openweathermap;
|
||||
|
||||
use openweathermap::{init,update};
|
||||
|
||||
fn main() {
|
||||
// start our observatory via OWM
|
||||
let receiver = &init("Berlin,DE", "metric", "en", "<APIKEY>", 10);
|
||||
loop {
|
||||
match update(receiver) {
|
||||
Some(response) => match response {
|
||||
Ok(current) => println!(
|
||||
"Today's weather in {} is {}",
|
||||
current.name.as_str(),
|
||||
current.weather[0].main.as_str()
|
||||
),
|
||||
Err(e) => println!("Could not fetch weather because: {}", e),
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### First: Start polling
|
||||
|
||||
`init()` spawns a thread which then will periodically poll *OpenWeatherMap* for the latest current weather report.
|
||||
You then can use `update()` to ask for it.
|
||||
|
||||
#### Then: Get weather updates
|
||||
|
||||
There are three possible kinds of result you get from `update()` which you will have to face:
|
||||
|
||||
##### Nothing New: `None`
|
||||
|
||||
`update()` returns `None` if there is currently no new update available.
|
||||
Which means: **You wont get any update twice!**
|
||||
In other words: `update()` is not caching the last weather update for you.
|
||||
|
||||
##### Weather Update: `CurrentWeather`
|
||||
|
||||
If a new update was downloaded by the polling thread `update()` returns some `CurrentWeather` object.
|
||||
`CurrentWeather` is a nested `struct` with the already parsed json properties.
|
||||
Those are well described [here](https://openweathermap.org/current#parameter).
|
||||
|
||||
##### Some Error: `Err`
|
||||
On error `update()` returns some `String` object which includes a brief error description.
|
||||
|
||||
Errors may occur...
|
||||
- initially while **there is no update yet** you will get an `Err` which includes exactly the String `"loading..."` (predefined in `openweathermap::LOADING`).
|
||||
- if a **server error** response was received (e.g. `401 Unauthorized` if an **invalid API key** was used).
|
||||
- on **json errors** while parsing the response from *OpenWeatherMap*.
|
||||
|
||||
### Get weather just once
|
||||
|
||||
If you need the weather just once you may use the method `weather()` which envelopes `init()` and `update()` into one single synchronous or asynchronous call.
|
||||
After the first successful weather update the spawned thread will stop immediately and you get the result in return.
|
||||
|
||||
```rust
|
||||
extern crate openweathermap;
|
||||
use openweathermap::blocking::weather;
|
||||
|
||||
fn main() {
|
||||
// start our observatory via OWM
|
||||
match &weather("Berlin,DE", "metric", "en", "<APIKEY>") {
|
||||
Ok(current) => println!(
|
||||
"Today's weather in {} is {}",
|
||||
current.name.as_str(),
|
||||
current.weather[0].main.as_str()
|
||||
),
|
||||
Err(e) => println!("Could not fetch weather because: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
There is a *blocking* and a *non-blocking* variant of `weather()`:
|
||||
|
||||
- The above example uses the synchronous (*blocking*) variant `openweathermap::blocking::weather` which wont return until there is a new update.
|
||||
- If you like to deal with the returned *future* by yourself just use `openweathermap::weather` and asynchronously await the result until there is any.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
Beside this introduction there is a reference documentation which can be found [here](https://docs.rs/openweathermap).
|
||||
|
||||
## Links
|
||||
|
||||
### Website
|
||||
|
||||
This README tastes better at [openweathermap.thats-software.com](https://openweathermap.thats-software.com).
|
||||
|
||||
### *github* repository
|
||||
|
||||
For the source code see [this repository](https://github.com/fightling/openweathermap) at *github.com*.
|
||||
|
||||
### on *crates.io*
|
||||
|
||||
Published at [*crates.io*](https://crates.io/crates/openweathermap).
|
||||
|
||||
## License
|
||||
|
||||
openweathermap is licensed under the *MIT license* (LICENSE-MIT or http://opensource.org/licenses/MIT)
|
132
client/openweathermap/src/api.rs
Normal file
132
client/openweathermap/src/api.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Location coordinates
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Coord {
|
||||
/// geo location, longitude
|
||||
pub lon: f64,
|
||||
/// geo location, latitude
|
||||
pub lat: f64,
|
||||
}
|
||||
|
||||
/// Weather condition description
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Weather {
|
||||
/// Weather condition id
|
||||
pub id: u64,
|
||||
/// Group of weather parameters (Rain, Snow, Extreme etc.)
|
||||
pub main: String,
|
||||
/// Weather condition
|
||||
pub description: String,
|
||||
/// Weather icon id
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
/// Detailed weather report
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Main {
|
||||
/// Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
|
||||
pub temp: f64,
|
||||
/// Temperature. This temperature parameter accounts for the human perception of weather.
|
||||
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
|
||||
pub feels_like: f64,
|
||||
/// Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa
|
||||
pub pressure: f64,
|
||||
/// Humidity, %
|
||||
pub humidity: f64,
|
||||
/// Minimum temperature at the moment.
|
||||
/// This is minimal currently observed temperature (within large megalopolises and urban areas).
|
||||
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
|
||||
pub temp_min: f64,
|
||||
/// Maximum temperature at the moment.
|
||||
/// This is maximal currently observed temperature (within large megalopolises and urban areas).
|
||||
/// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
|
||||
pub temp_max: f64,
|
||||
/// Atmospheric pressure on the sea level, hPa
|
||||
pub sea_level: Option<f64>,
|
||||
/// Atmospheric pressure on the ground level, hPa
|
||||
pub grnd_level: Option<f64>,
|
||||
}
|
||||
|
||||
/// Detailed wind report
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Wind {
|
||||
/// Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour.
|
||||
pub speed: f64,
|
||||
/// Wind direction, degrees (meteorological)
|
||||
pub deg: f64,
|
||||
/// Wind gust. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour
|
||||
pub gust: Option<f64>,
|
||||
}
|
||||
|
||||
/// Detailed clouds report
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Clouds {
|
||||
/// Cloudiness, %
|
||||
pub all: f64,
|
||||
}
|
||||
|
||||
/// Rain or snow volume report
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Volume {
|
||||
/// Volume for the last 1 hour, mm
|
||||
#[serde(rename = "1h")]
|
||||
pub h1: Option<f64>,
|
||||
/// Volume for the last 3 hours, mm
|
||||
#[serde(rename = "3h")]
|
||||
pub h3: Option<f64>,
|
||||
}
|
||||
|
||||
/// Additional information
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Sys {
|
||||
/// Internal parameter
|
||||
#[serde(rename = "type")]
|
||||
pub type_: Option<u64>,
|
||||
/// Internal parameter
|
||||
pub id: Option<u64>,
|
||||
/// Internal parameter
|
||||
pub message: Option<f64>,
|
||||
/// Country code (GB, JP etc.)
|
||||
pub country: String,
|
||||
/// Sunrise time, unix, UTC
|
||||
pub sunrise: i64,
|
||||
/// Sunset time, unix, UTC
|
||||
pub sunset: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
/// current weather report in a nested struct
|
||||
pub struct CurrentWeather {
|
||||
/// report origin coordinates
|
||||
pub coord: Coord,
|
||||
/// vector with one item of weather condition descriptions
|
||||
pub weather: Vec<Weather>,
|
||||
/// Internal parameter
|
||||
pub base: String,
|
||||
/// detailed weather report
|
||||
pub main: Main,
|
||||
/// Visibility, meter
|
||||
pub visibility: u64,
|
||||
/// detailed wind report
|
||||
pub wind: Wind,
|
||||
/// detailed clouds report
|
||||
pub clouds: Clouds,
|
||||
/// detailed rain report
|
||||
pub rain: Option<Volume>,
|
||||
/// detailed snow report
|
||||
pub snow: Option<Volume>,
|
||||
/// Time of data calculation, unix, UTC
|
||||
pub dt: i64,
|
||||
/// additional information
|
||||
pub sys: Sys,
|
||||
/// Shift in seconds from UTC
|
||||
pub timezone: i64,
|
||||
/// City ID
|
||||
pub id: u64,
|
||||
/// City name
|
||||
pub name: String,
|
||||
/// Internal parameter
|
||||
pub cod: u64,
|
||||
}
|
||||
|
94
client/openweathermap/src/forecast.rs
Normal file
94
client/openweathermap/src/forecast.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use serde::{Deserialize, __private::de};
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Forecast {
|
||||
pub city: City,
|
||||
pub cod: String,
|
||||
pub message: f64,
|
||||
pub cnt: f64,
|
||||
pub list: Vec<List>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct City {
|
||||
pub id: f64,
|
||||
pub name: String,
|
||||
pub coord: Coord,
|
||||
pub country: String,
|
||||
pub population: f64,
|
||||
pub timezone: f64,
|
||||
pub sunrise: i64,
|
||||
pub sunset: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Coord {
|
||||
pub lon: f64,
|
||||
pub lat: f64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct List {
|
||||
pub dt: i64,
|
||||
pub main: Main,
|
||||
pub weather: Vec<Weather>,
|
||||
pub wind: Wind,
|
||||
pub clouds: Clouds,
|
||||
pub pop: f64,
|
||||
pub rain: Option<Rain>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct Wind{
|
||||
pub speed:f64,
|
||||
pub deg:f64,
|
||||
pub gust:f64
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Rain{
|
||||
#[serde(rename = "3h")]
|
||||
pub three_hours:f64
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Clouds{
|
||||
all:f64
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct Main {
|
||||
pub temp: f64,
|
||||
pub feels_like: f64,
|
||||
pub temp_min: f64,
|
||||
pub temp_max: f64,
|
||||
pub pressure: f64,
|
||||
pub sea_level: f64,
|
||||
pub grnd_level: f64,
|
||||
pub humidity: f64,
|
||||
pub temp_kf: f64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FeelsLike {
|
||||
pub day: f64,
|
||||
pub night: f64,
|
||||
pub eve: f64,
|
||||
pub morn: f64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Weather {
|
||||
pub id: f64,
|
||||
pub main: String,
|
||||
pub description: String,
|
||||
pub icon: String,
|
||||
}
|
369
client/openweathermap/src/lib.rs
Executable file
369
client/openweathermap/src/lib.rs
Executable file
@@ -0,0 +1,369 @@
|
||||
extern crate reqwest;
|
||||
extern crate serde_json;
|
||||
|
||||
use forecast::Forecast;
|
||||
use futures::executor;
|
||||
use http::StatusCode;
|
||||
use regex::Regex;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
mod api;
|
||||
pub mod forecast;
|
||||
pub use api::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Receiver object you get from `init()` and have top handle to `update()`.
|
||||
pub type Receiver = mpsc::Receiver<Result<CurrentWeather, String>>;
|
||||
|
||||
/// Receiver object you get from `init_forecast()` and have top handle to `update()`.
|
||||
pub type ForecastReceiver = mpsc::Receiver<Result<Forecast, String>>;
|
||||
|
||||
/// Loading error messaage you get at the first call of `update()`.
|
||||
pub const LOADING: &str = "loading...";
|
||||
|
||||
/// Spawns a thread which fetches the current weather from
|
||||
/// [openweathermap.org](https://openweathermap.org) periodically.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// - `poll_mins`: Update interval:
|
||||
/// - `> 0`: duration of poll period in minutes (`10` is recommended)
|
||||
/// - `= 0`: thread will terminate after the first successful update.
|
||||
/// #### Return value
|
||||
/// - `openweathermap::Receiver`: Handle this to `openweathermap::update()` to get the latest weather update.
|
||||
///
|
||||
/// The return value is a `mpsc` *channel receiver*:
|
||||
/// ```rust
|
||||
/// pub type Receiver = std::sync::mpsc::Receiver<Result<openweathermap::CurrentWeather, String>>;
|
||||
/// ```
|
||||
|
||||
pub fn init(location: &str, units: &str, lang: &str, api_key: &str, poll_mins: u64) -> Receiver {
|
||||
// generate correct request URL depending on city is id or name
|
||||
let url = match location.parse::<u64>().is_ok() {
|
||||
true => format!(
|
||||
"http://api.openweathermap.org/data/2.5/weather?id={}&units={}&lang={}&appid={}",
|
||||
location, units, lang, api_key
|
||||
),
|
||||
false => {
|
||||
let re = Regex::new(r"(-?\d+\.\d+)\s*,\s*(-?\d+\.\d+)").unwrap();
|
||||
match re.captures(&location) {
|
||||
Some(caps) => format!("http://api.openweathermap.org/data/2.5/weather?lat={}&lon={}&units={}&lang={}&appid={}",
|
||||
caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), units, lang, api_key ),
|
||||
None => format!(
|
||||
"http://api.openweathermap.org/data/2.5/weather?q={}&units={}&lang={}&appid={}",
|
||||
location, units, lang, api_key ),
|
||||
}
|
||||
}
|
||||
};
|
||||
// fork thread that continuously fetches weather updates every <poll_mins> minutes
|
||||
let period = Duration::from_secs(60 * poll_mins);
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
tx.send(Err(LOADING.to_string())).unwrap_or(());
|
||||
loop {
|
||||
match reqwest::blocking::get(&url) {
|
||||
Ok(response) => match response.status() {
|
||||
StatusCode::OK => match serde_json::from_str(&response.text().unwrap()) {
|
||||
Ok(w) => {
|
||||
tx.send(Ok(w)).unwrap_or(());
|
||||
if period == Duration::new(0, 0) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(period);
|
||||
}
|
||||
Err(e) => tx.send(Err(e.to_string())).unwrap_or(()),
|
||||
},
|
||||
_ => tx.send(Err(response.status().to_string())).unwrap_or(()),
|
||||
},
|
||||
Err(_e) => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
// return receiver that provides the updated weather as json string
|
||||
return rx;
|
||||
}
|
||||
|
||||
/// Spawns a thread which fetches the forecast from
|
||||
/// [openweathermap.org](https://openweathermap.org) periodically.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// - `poll_mins`: Update interval:
|
||||
/// - `> 0`: duration of poll period in minutes (`10` is recommended)
|
||||
/// - `= 0`: thread will terminate after the first successful update.
|
||||
/// #### Return value
|
||||
/// - `openweathermap::Receiver`: Handle this to `openweathermap::update()` to get the latest weather update.
|
||||
///
|
||||
/// The return value is a `mpsc` *channel receiver*:
|
||||
/// ```rust
|
||||
/// pub type Receiver = std::sync::mpsc::Receiver<Result<openweathermap::CurrentWeather, String>>;
|
||||
/// ```
|
||||
|
||||
pub fn init_forecast(
|
||||
location: &str,
|
||||
units: &str,
|
||||
lang: &str,
|
||||
api_key: &str,
|
||||
poll_mins: u64,
|
||||
days: u32,
|
||||
) -> ForecastReceiver {
|
||||
// generate correct request URL depending on city is id or name
|
||||
let url = match location.parse::<u64>().is_ok() {
|
||||
true => format!(
|
||||
"http://api.openweathermap.org/data/2.5/forecast?id={}&units={}&lang={}&appid={}&cnt={}",
|
||||
location, units, lang, api_key, days
|
||||
),
|
||||
false => {
|
||||
let re = Regex::new(r"(-?\d+\.\d+)\s*,\s*(-?\d+\.\d+)").unwrap();
|
||||
match re.captures(&location) {
|
||||
Some(caps) => format!("http://api.openweathermap.org/data/2.5/forecast?lat={}&lon={}&units={}&lang={}&appid={}&cnt={}",
|
||||
caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), units, lang, api_key, days ),
|
||||
None => format!(
|
||||
"http://api.openweathermap.org/data/2.5/forecast?q={}&units={}&lang={}&appid={}&cnt={}",
|
||||
location, units, lang, api_key, days ),
|
||||
}
|
||||
}
|
||||
};
|
||||
// fork thread that continuously fetches weather updates every <poll_mins> minutes
|
||||
let period = Duration::from_secs(60 * poll_mins);
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
tx.send(Err(LOADING.to_string())).unwrap_or(());
|
||||
loop {
|
||||
//let fake: Result<Forecast, serde_json::Error> = serde_json::from_str(include_str!("example.json"));
|
||||
//tx.send(Result::Ok(fake.unwrap())).unwrap();
|
||||
|
||||
match reqwest::blocking::get(&url) {
|
||||
Ok(response) => match response.status() {
|
||||
StatusCode::OK => match serde_json::from_str(&response.text().unwrap()) {
|
||||
Ok(w) => {
|
||||
tx.send(Ok(w)).unwrap_or(());
|
||||
if period == Duration::new(0, 0) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(period);
|
||||
}
|
||||
Err(e) => tx.send(Err(e.to_string())).unwrap_or(()),
|
||||
},
|
||||
_ => tx.send(Err(response.status().to_string())).unwrap_or(()),
|
||||
},
|
||||
Err(_e) => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
// return receiver that provides the updated weather as json string
|
||||
return rx;
|
||||
}
|
||||
|
||||
/// Get current weather update that the spawned thread could fetched.
|
||||
/// #### Parameters
|
||||
/// - `receiver`: the *channel receiver* from preceded call to `openweathermap::init()`
|
||||
/// #### Returng value
|
||||
/// - ⇒ `None`: No update available
|
||||
/// - ⇒ `Some(Result)`: Update available
|
||||
/// - ⇒ `Ok(CurrentWeather)`: Weather information in a nested struct called `CurrentWeather`
|
||||
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
|
||||
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
|
||||
/// - e.g. `401 Unauthorized`: if your API key is invalid
|
||||
/// - some json parser error message if response from OpenWeatherMap could not be parsed
|
||||
pub fn update(receiver: &Receiver) -> Option<Result<CurrentWeather, String>> {
|
||||
match receiver.try_recv() {
|
||||
Ok(response) => Some(response),
|
||||
Err(_e) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_forecast(receiver: &ForecastReceiver) -> Option<Result<Forecast, String>> {
|
||||
match receiver.try_recv() {
|
||||
Ok(response) => Some(response),
|
||||
Err(_e) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch current weather update once and stop thread immediately after success.
|
||||
/// Returns the result in a *future*.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// #### Return value
|
||||
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
|
||||
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
|
||||
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
|
||||
/// - e.g. `401 Unauthorized` if your API key is invalid
|
||||
/// - some json parser error message if response from OpenWeatherMap could not be parsed
|
||||
pub async fn weather(
|
||||
location: &str,
|
||||
units: &str,
|
||||
lang: &str,
|
||||
api_key: &str,
|
||||
) -> Result<CurrentWeather, String> {
|
||||
let r = init(location, units, lang, api_key, 0);
|
||||
loop {
|
||||
match update(&r) {
|
||||
Some(response) => match response {
|
||||
Ok(current) => return Ok(current),
|
||||
Err(e) => {
|
||||
if e != LOADING {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch forecast weather update once and stop thread immediately after success.
|
||||
/// Returns the result in a *future*.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name: may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID: which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates: given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// #### Return value
|
||||
/// - ⇒ `Ok(Forecast)`: weather information in a nested struct called `CurrentWeather`
|
||||
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
|
||||
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
|
||||
/// - e.g. `401 Unauthorized` if your API key is invalid
|
||||
/// - some json parser error message if response from OpenWeatherMap could not be parsed
|
||||
pub async fn forecast(
|
||||
location: &str,
|
||||
units: &str,
|
||||
lang: &str,
|
||||
api_key: &str,
|
||||
days: u32,
|
||||
) -> Result<Forecast, String> {
|
||||
let r = init_forecast(location, units, lang, api_key, 0, days);
|
||||
loop {
|
||||
match update_forecast(&r) {
|
||||
Some(response) => match response {
|
||||
Ok(forecast) => return Ok(forecast),
|
||||
Err(e) => {
|
||||
if e != LOADING {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// synchronous functions
|
||||
pub mod blocking {
|
||||
use super::*;
|
||||
/// Fetches a weather update once and stops the thread immediately after success then returns the update.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// #### Return value
|
||||
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
|
||||
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
|
||||
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
|
||||
/// - e.g. `401 Unauthorized` if your API key is invalid
|
||||
/// - some json parser error message if response from OpenWeatherMap could not be parsed
|
||||
pub fn weather(
|
||||
location: &str,
|
||||
units: &str,
|
||||
lang: &str,
|
||||
api_key: &str,
|
||||
) -> Result<CurrentWeather, String> {
|
||||
// wait for result
|
||||
executor::block_on(super::weather(location, units, lang, api_key))
|
||||
}
|
||||
|
||||
/// Fetches a weather update once and stops the thread immediately after success then returns the update.
|
||||
/// #### Parameters
|
||||
/// - `location`: Can be a city name, a city ID or a geographical coordinate:
|
||||
/// - city name may be followed by comma separated state code and/or country code (e.g. `"Berlin,DE"`).
|
||||
/// - city ID which can be found at [this](https://openweathermap.org/find) where you will get link that includes the ID
|
||||
/// - e.g. `"2950159"` for Berlin, Germany
|
||||
/// - coordinates given by comma separated latitude and longitude (e.g. `"52.5244,13.4105"`). |
|
||||
/// - `units`: One of the following:
|
||||
/// - `"metric"`: meters, m/s, °C, etc.
|
||||
/// - `"imperial"`: miles, mi/s, °F, etc.
|
||||
/// - `"standard"`: meters, m/s, K, etc.
|
||||
/// - `lang`: Language code:
|
||||
/// - `"en"`: for English
|
||||
/// - `"de"`: for German
|
||||
/// - see [this list](https://openweathermap.org/current#multi) for all available language codes
|
||||
/// - `api_key`: Your API key which you can get [here](https://openweathermap.org/price)
|
||||
/// #### Return value
|
||||
/// - ⇒ `Ok(CurrentWeather)`: weather information in a nested struct called `CurrentWeather`
|
||||
/// (see also [*OpenWeatherMap* documentation](https://openweathermap.org/current#parameter) for details)
|
||||
/// - ⇒ `Err(String)`: Error message about any occured http or json issue
|
||||
/// - e.g. `401 Unauthorized` if your API key is invalid
|
||||
/// - some json parser error message if response from OpenWeatherMap could not be parsed
|
||||
pub fn forecast(
|
||||
location: &str,
|
||||
units: &str,
|
||||
lang: &str,
|
||||
api_key: &str,
|
||||
days: u32,
|
||||
) -> Result<Forecast, String> {
|
||||
// wait for result
|
||||
executor::block_on(super::forecast(location, units, lang, api_key, days))
|
||||
}
|
||||
}
|
2273
client/openweathermap/src/tests.rs
Normal file
2273
client/openweathermap/src/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user