Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
Paul van Tilburg | 07e0701106 | |
Paul van Tilburg | 91d5500c86 | |
Paul van Tilburg | 9b3c11ee76 | |
Paul van Tilburg | 27e1ac726c | |
Paul van Tilburg | 3047cf74c2 | |
Paul van Tilburg | 44474aa545 | |
Paul van Tilburg | 50b0e94839 | |
Paul van Tilburg | 1010311403 | |
Paul van Tilburg | d16699636b | |
Paul van Tilburg | 38fb28c248 | |
Paul van Tilburg | 7c2b012e95 | |
Paul van Tilburg | ab6001f072 | |
Paul van Tilburg | 9742331f6d |
|
@ -21,9 +21,6 @@ jobs:
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Use sparse Cargo index for crates.io
|
|
||||||
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
|
|
||||||
|
|
||||||
- name: Run cargo check
|
- name: Run cargo check
|
||||||
uses: https://github.com/actions-rs/cargo@v1
|
uses: https://github.com/actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -27,9 +27,6 @@ jobs:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Use sparse Cargo index for crates.io
|
|
||||||
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
|
|
||||||
|
|
||||||
- name: Install cargo-deb
|
- name: Install cargo-deb
|
||||||
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
|
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
|
||||||
with:
|
with:
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.8] - 2023-06-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Print the version on lift off (#30)
|
||||||
|
* Add a `/version` endpoint to the API (#30)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update dependency on `cached`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Properly attribute the PAQI metric in its description(s)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
* No longer provide a map for the PAQI metric; the map used is only for pollen
|
||||||
|
|
||||||
## [0.2.7] - 2023-05-26
|
## [0.2.7] - 2023-05-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -112,7 +131,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|
||||||
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.7...HEAD
|
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.8...HEAD
|
||||||
|
[0.2.8]: https://git.luon.net/paul/sinoptik/compare/v0.2.7...v0.2.8
|
||||||
[0.2.7]: https://git.luon.net/paul/sinoptik/compare/v0.2.6...v0.2.7
|
[0.2.7]: https://git.luon.net/paul/sinoptik/compare/v0.2.6...v0.2.7
|
||||||
[0.2.6]: https://git.luon.net/paul/sinoptik/compare/v0.2.5...v0.2.6
|
[0.2.6]: https://git.luon.net/paul/sinoptik/compare/v0.2.5...v0.2.6
|
||||||
[0.2.5]: https://git.luon.net/paul/sinoptik/compare/v0.2.4...v0.2.5
|
[0.2.5]: https://git.luon.net/paul/sinoptik/compare/v0.2.4...v0.2.5
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sinoptik"
|
name = "sinoptik"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
authors = [
|
authors = [
|
||||||
"Admar Schoonen <admar@luon.net",
|
"Admar Schoonen <admar@luon.net",
|
||||||
"Paul van Tilburg <paul@luon.net>"
|
"Paul van Tilburg <paul@luon.net>"
|
||||||
|
@ -12,7 +12,7 @@ repository = "https://git.luon.net/paul/sinoptik"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cached = { version = "0.42.0", features = ["async"] }
|
cached = { version = "0.44.0", features = ["async"] }
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
chrono-tz = "0.8.1"
|
chrono-tz = "0.8.1"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
|
@ -22,6 +22,9 @@ reqwest = { version = "0.11.9", features = ["json"] }
|
||||||
rocket = { version = "0.5.0-rc.3", features = ["json"] }
|
rocket = { version = "0.5.0-rc.3", features = ["json"] }
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
vergen = { version = "8.2.1", default-features = false, features = ["build", "git", "gitcl"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_float_eq = "1.1.3"
|
assert_float_eq = "1.1.3"
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
|
@ -42,7 +45,8 @@ Currently supported metrics are:
|
||||||
* O₃ concentration (per hour, from Luchtmeetnet)
|
* O₃ concentration (per hour, from Luchtmeetnet)
|
||||||
* Particulate matter (PM10) concentration (per hour, from Luchtmeetnet)
|
* Particulate matter (PM10) concentration (per hour, from Luchtmeetnet)
|
||||||
* Pollen (per hour, from Buienradar)
|
* Pollen (per hour, from Buienradar)
|
||||||
* Pollen/air quality index (per hour, from Buienradar)
|
* Pollen/air quality index (per hour, combined from Buienradar and
|
||||||
|
Luchtmeetnet)
|
||||||
* Precipitation (per 5 minutes, from Buienradar)
|
* Precipitation (per 5 minutes, from Buienradar)
|
||||||
* UV index (per day, from Buienradar)
|
* UV index (per day, from Buienradar)
|
||||||
|
|
||||||
|
|
28
README.md
28
README.md
|
@ -11,7 +11,8 @@ Currently supported metrics are:
|
||||||
* O₃ concentration (per hour, from [Luchtmeetnet])
|
* O₃ concentration (per hour, from [Luchtmeetnet])
|
||||||
* Particulate matter (PM10) concentration (per hour, from [Luchtmeetnet])
|
* Particulate matter (PM10) concentration (per hour, from [Luchtmeetnet])
|
||||||
* Pollen (per hour, from [Buienradar])
|
* Pollen (per hour, from [Buienradar])
|
||||||
* Pollen/air quality index (per hour, from [Buienradar])
|
* Pollen/air quality index (per hour, combined from [Buienradar] and
|
||||||
|
[Luchtmeetnet])
|
||||||
* Precipitation (per 5 minutes, from [Buienradar])
|
* Precipitation (per 5 minutes, from [Buienradar])
|
||||||
* UV index (per day, from [Buienradar])
|
* UV index (per day, from [Buienradar])
|
||||||
|
|
||||||
|
@ -216,6 +217,31 @@ an address fails or if the position is out of bounds of the map, nothing is
|
||||||
returned (HTTP 404). If the maps cannot/have not been downloaded or cached yet,
|
returned (HTTP 404). If the maps cannot/have not been downloaded or cached yet,
|
||||||
a service unavailable error is returned (HTTP 503).
|
a service unavailable error is returned (HTTP 503).
|
||||||
|
|
||||||
|
## Version API endpoint
|
||||||
|
|
||||||
|
The `/version` API endpoint provides information of the current version and
|
||||||
|
build of the service. This can be used to check if it needs to be updated.
|
||||||
|
Again, there is no path and no query parameters, just:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version responses
|
||||||
|
|
||||||
|
The response uses the JSON format and typically looks like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.2.7",
|
||||||
|
"timestamp": "2023-05-29T13:34:34.701323159Z",
|
||||||
|
"git_sha": "bb5962d",
|
||||||
|
"git_timestamp": "2023-05-29T15:32:17.000000000+02:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
(Build and git information in example output may be out of date.)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Sinoptik is licensed under the MIT license (see the `LICENSE` file or
|
Sinoptik is licensed under the MIT license (see the `LICENSE` file or
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use vergen::EmitBuilder;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Generate the `cargo:` instructions to fill the appropriate environment variables.
|
||||||
|
EmitBuilder::builder().all_build().all_git().emit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
58
src/lib.rs
58
src/lib.rs
|
@ -21,6 +21,7 @@ use rocket::fairing::AdHoc;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::Responder;
|
use rocket::response::Responder;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
use rocket::{get, routes, Build, Request, Rocket, State};
|
use rocket::{get, routes, Build, Request, Rocket, State};
|
||||||
|
|
||||||
use self::forecast::{forecast, Forecast, Metric};
|
use self::forecast::{forecast, Forecast, Metric};
|
||||||
|
@ -84,13 +85,41 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type that defaults to [`Error`] as the default error type.
|
|
||||||
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
#[derive(Responder)]
|
||||||
#[response(content_type = "image/png")]
|
#[response(content_type = "image/png")]
|
||||||
struct PngImageData(Vec<u8>);
|
struct PngImageData(Vec<u8>);
|
||||||
|
|
||||||
|
/// Result type that defaults to [`Error`] as the default error type.
|
||||||
|
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// The version information as JSON response.
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct VersionInfo {
|
||||||
|
/// The version of the build.
|
||||||
|
version: String,
|
||||||
|
|
||||||
|
/// The timestamp of the build.
|
||||||
|
timestamp: String,
|
||||||
|
|
||||||
|
/// The (most recent) git SHA used for the build.
|
||||||
|
git_sha: String,
|
||||||
|
|
||||||
|
/// The timestamp of the last git commit used for the build.
|
||||||
|
git_timestamp: String,
|
||||||
|
}
|
||||||
|
impl VersionInfo {
|
||||||
|
/// Retrieves the version information from the environment variables.
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
version: String::from(env!("CARGO_PKG_VERSION")),
|
||||||
|
timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
|
||||||
|
git_sha: String::from(&env!("VERGEN_GIT_SHA")[0..7]),
|
||||||
|
git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handler for retrieving the forecast for an address.
|
/// Handler for retrieving the forecast for an address.
|
||||||
#[get("/forecast?<address>&<metrics>")]
|
#[get("/forecast?<address>&<metrics>")]
|
||||||
async fn forecast_address(
|
async fn forecast_address(
|
||||||
|
@ -150,6 +179,12 @@ async fn map_geo(
|
||||||
image_data.map(PngImageData)
|
image_data.map(PngImageData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the version information.
|
||||||
|
#[get("/version", format = "application/json")]
|
||||||
|
async fn version() -> Result<Json<VersionInfo>> {
|
||||||
|
Ok(Json(VersionInfo::new()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets up Rocket.
|
/// Sets up Rocket.
|
||||||
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
let maps_refresher = maps::run(Arc::clone(&maps_handle));
|
let maps_refresher = maps::run(Arc::clone(&maps_handle));
|
||||||
|
@ -157,7 +192,13 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![forecast_address, forecast_geo, map_address, map_geo],
|
routes![
|
||||||
|
forecast_address,
|
||||||
|
forecast_geo,
|
||||||
|
map_address,
|
||||||
|
map_geo,
|
||||||
|
version
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.manage(maps_handle)
|
.manage(maps_handle)
|
||||||
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
|
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
|
||||||
|
@ -166,6 +207,15 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
let _refresher = rocket::tokio::spawn(maps_refresher);
|
let _refresher = rocket::tokio::spawn(maps_refresher);
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
.attach(AdHoc::on_liftoff("Version", |_| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let name = env!("CARGO_PKG_NAME");
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
let git_sha = &env!("VERGEN_GIT_SHA")[0..7];
|
||||||
|
|
||||||
|
println!("🌁 Started {name} v{version} (git @{git_sha})");
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up Rocket and the maps cache refresher task.
|
/// Sets up Rocket and the maps cache refresher task.
|
||||||
|
|
21
src/maps.rs
21
src/maps.rs
|
@ -78,16 +78,16 @@ type MapKeyHistogram = HashMap<Rgb<u8>, u32>;
|
||||||
/// Note that the actual score starts from 1, not 0 as per this array.
|
/// Note that the actual score starts from 1, not 0 as per this array.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
const MAP_KEY: [[u8; 3]; 10] = [
|
const MAP_KEY: [[u8; 3]; 10] = [
|
||||||
[0x49, 0xDA, 0x21],
|
[0x49, 0xDA, 0x21], // #49DA21
|
||||||
[0x30, 0xD2, 0x00],
|
[0x30, 0xD2, 0x00], // #30D200
|
||||||
[0xFF, 0xF8, 0x8B],
|
[0xFF, 0xF8, 0x8B], // #FFF88B
|
||||||
[0xFF, 0xF6, 0x42],
|
[0xFF, 0xF6, 0x42], // #FFF642
|
||||||
[0xFD, 0xBB, 0x31],
|
[0xFD, 0xBB, 0x31], // #FDBB31
|
||||||
[0xFD, 0x8E, 0x24],
|
[0xFD, 0x8E, 0x24], // #FD8E24
|
||||||
[0xFC, 0x10, 0x3E],
|
[0xFC, 0x10, 0x3E], // #FC103E
|
||||||
[0x97, 0x0A, 0x33],
|
[0x97, 0x0A, 0x33], // #970A33
|
||||||
[0xA6, 0x6D, 0xBC],
|
[0xA6, 0x6D, 0xBC], // #A66DBC
|
||||||
[0xB3, 0x30, 0xA1],
|
[0xB3, 0x30, 0xA1], // #B330A1
|
||||||
];
|
];
|
||||||
|
|
||||||
/// The Buienradar map sample size.
|
/// The Buienradar map sample size.
|
||||||
|
@ -567,7 +567,6 @@ pub(crate) async fn mark_map(
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
|
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
|
||||||
let image = match metric {
|
let image = match metric {
|
||||||
Metric::PAQI => maps.pollen_mark(position),
|
|
||||||
Metric::Pollen => maps.pollen_mark(position),
|
Metric::Pollen => maps.pollen_mark(position),
|
||||||
Metric::UVI => maps.uvi_mark(position),
|
Metric::UVI => maps.uvi_mark(position),
|
||||||
_ => return Err(crate::Error::UnsupportedMetric(metric)),
|
_ => return Err(crate::Error::UnsupportedMetric(metric)),
|
||||||
|
|
Loading…
Reference in New Issue