Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
Paul van Tilburg | ba1b27fd66 | |
Paul van Tilburg | 94c29cad71 | |
Paul van Tilburg | 8e5a1ef305 | |
Paul van Tilburg | 6c39cac26e | |
Paul van Tilburg | f1ee03d96c | |
Paul van Tilburg | 98f60cba89 | |
Paul van Tilburg | 45ee951601 | |
Paul van Tilburg | f4e7c82b53 | |
Paul van Tilburg | a29d7f3535 | |
Paul van Tilburg | c86f001fee | |
Paul van Tilburg | 88c59cdb1f | |
Paul van Tilburg | cad766b520 | |
Paul van Tilburg | e62699c102 | |
Paul van Tilburg | f32f67dbf4 | |
Paul van Tilburg | d1e43a7aa7 | |
Paul van Tilburg | c2450267e0 | |
Paul van Tilburg | 087ecf00f1 | |
Paul van Tilburg | f8ea25c516 | |
Paul van Tilburg | f830d34464 | |
Paul van Tilburg | ff10cc19e8 | |
Paul van Tilburg | 1211fea46a | |
Paul van Tilburg | 182521aab7 | |
Paul van Tilburg | dadf5d3147 | |
Paul van Tilburg | 4b506541f3 | |
Paul van Tilburg | 47e28a7098 |
|
@ -2,7 +2,8 @@ name: "Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: "v*"
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
@ -20,22 +21,16 @@ jobs:
|
||||||
echo "Releasing version: $VERSION"
|
echo "Releasing version: $VERSION"
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install Rust stable toolchain
|
- name: Get the release notes from the changelog
|
||||||
uses: https://github.com/actions-rs/toolchain@v1
|
run: |
|
||||||
with:
|
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||||
profile: minimal
|
RELEASE_NOTES=$(sed -n -e "/^## \[$VERSION\]/,/^## \[/{//"'!'"p;}" CHANGELOG.md | sed -e '1d;$d')
|
||||||
toolchain: stable
|
echo "Release notes:"
|
||||||
override: true
|
echo
|
||||||
|
echo "$RELEASE_NOTES"
|
||||||
- name: Install cargo-deb
|
echo "RELEASE_NOTES<<$EOF" >> "$GITHUB_ENV"
|
||||||
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
|
echo "$RELEASE_NOTES" >> "$GITHUB_ENV"
|
||||||
with:
|
echo "$EOF" >> "$GITHUB_ENV"
|
||||||
packages: cargo-deb
|
|
||||||
|
|
||||||
- name: Run cargo-deb
|
|
||||||
uses: https://github.com/actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: deb
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
|
@ -47,11 +42,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
# This is available by default.
|
# This is available by default.
|
||||||
api_key: '${{ secrets.RELEASE_TOKEN }}'
|
api_key: '${{ secrets.RELEASE_TOKEN }}'
|
||||||
files: target/debian/sinoptik*.deb
|
files: FIXME
|
||||||
title: 'Release ${{ env.VERSION }}'
|
title: 'Release ${{ env.VERSION }}'
|
||||||
|
body: '${{ env.RELEASE_NOTES }}'
|
||||||
|
|
||||||
release-crate:
|
release-crate:
|
||||||
name: "Release crate"
|
name: "Release Rust crate"
|
||||||
runs-on: debian-latest
|
runs-on: debian-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
|
@ -80,3 +76,37 @@ jobs:
|
||||||
with:
|
with:
|
||||||
command: publish
|
command: publish
|
||||||
args: --registry luon
|
args: --registry luon
|
||||||
|
|
||||||
|
release-deb:
|
||||||
|
name: "Release Debian package"
|
||||||
|
runs-on: debian-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Rust stable toolchain
|
||||||
|
uses: https://github.com/actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install cargo-deb
|
||||||
|
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
|
||||||
|
with:
|
||||||
|
packages: cargo-deb
|
||||||
|
|
||||||
|
- name: Run cargo-deb
|
||||||
|
uses: https://github.com/actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: deb
|
||||||
|
|
||||||
|
- name: Publish Debian package
|
||||||
|
env:
|
||||||
|
DEB_REPO_TOKEN: '${{ secrets.DEB_REPO_TOKEN }}'
|
||||||
|
run: |
|
||||||
|
curl --config <(printf "user=%s:%s" paul "${DEB_REPO_TOKEN}") \
|
||||||
|
--upload-file target/debian/sinoptik*.deb \
|
||||||
|
https://git.luon.net/api/packages/paul/debian/pool/bookworm/main/upload
|
||||||
|
|
64
CHANGELOG.md
64
CHANGELOG.md
|
@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.12] - 2024-05-09
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
* Updated dependencies, fixes security advisiories:
|
||||||
|
* [RUSTSEC-2024-0019](https://rustsec.org/advisories/RUSTSEC-2024-0019)
|
||||||
|
* [RUSTSEC-2024-0332](https://rustsec.org/advisories/RUSTSEC-2024-0332)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update dependency on `cached`, `chrono-tz`, `image` and `reqwest`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix tests; reduce required accuracy for geocoded coordinates again and
|
||||||
|
don't run background map updates during tests
|
||||||
|
|
||||||
|
## [0.2.11] - 2024-02-27
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
* Updated dependencies, fixes security advisories:
|
||||||
|
* [RUSTSEC-2024-0003](https://rustsec.org/advisories/RUSTSEC-2024-0003)
|
||||||
|
* [RUSTSEC-2023-0072](https://rustsec.org/advisories/RUSTSEC-2024-0072)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix clippy issue
|
||||||
|
* Tweak/fix tests; reduce required accuracy for geocoded coordinates
|
||||||
|
|
||||||
|
## [0.2.10] - 2023-11-03
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
* Update dependencies
|
||||||
|
([RUSTSEC-2020-0071](https://rustsec.org/advisories/RUSTSEC-2020-0071.html),
|
||||||
|
[RUSTSEC-2023-0044](https://rustsec.org/advisories/RUSTSEC-2023-0044.html))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Switch to Rocket 0.5 RC4
|
||||||
|
* Update dependency on `cached`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix clippy issues
|
||||||
|
|
||||||
|
## [0.2.9] - 2023-08-25
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update release Gitea Actions workflow; add seperate job to release Debian
|
||||||
|
package to the new repository
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
* Update dependencies ([RUSTSEC-2023-0044](https://rustsec.org/advisories/RUSTSEC-2023-0044))
|
||||||
|
|
||||||
## [0.2.8] - 2023-06-05
|
## [0.2.8] - 2023-06-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -131,7 +189,11 @@ 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.8...HEAD
|
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.12...HEAD
|
||||||
|
[0.2.12]: https://git.luon.net/paul/sinoptik/compare/v0.2.11...v0.2.12
|
||||||
|
[0.2.11]: https://git.luon.net/paul/sinoptik/compare/v0.2.10...v0.2.11
|
||||||
|
[0.2.10]: https://git.luon.net/paul/sinoptik/compare/v0.2.9...v0.2.10
|
||||||
|
[0.2.9]: https://git.luon.net/paul/sinoptik/compare/v0.2.8...v0.2.9
|
||||||
[0.2.8]: https://git.luon.net/paul/sinoptik/compare/v0.2.7...v0.2.8
|
[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
|
||||||
|
|
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.8"
|
version = "0.2.12"
|
||||||
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,13 +12,13 @@ repository = "https://git.luon.net/paul/sinoptik"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cached = { version = "0.44.0", features = ["async"] }
|
cached = { version = "0.51.3", features = ["async"] }
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
chrono-tz = "0.8.1"
|
chrono-tz = "0.9.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
geocoding = "0.4.0"
|
geocoding = "0.4.0"
|
||||||
image = "0.24.1"
|
image = { version = "0.25.1", default-features = false, features = ["png"]}
|
||||||
reqwest = { version = "0.11.9", features = ["json"] }
|
reqwest = { version = "0.12.4", 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"
|
||||||
|
|
||||||
|
|
44
src/lib.rs
44
src/lib.rs
|
@ -54,11 +54,11 @@ pub(crate) enum Error {
|
||||||
|
|
||||||
/// Failed to merge AQI & pollen items.
|
/// Failed to merge AQI & pollen items.
|
||||||
#[error("Failed to merge AQI & pollen items: {0}")]
|
#[error("Failed to merge AQI & pollen items: {0}")]
|
||||||
Merge(#[from] self::providers::combined::MergeError),
|
Merge(#[from] providers::combined::MergeError),
|
||||||
|
|
||||||
/// Failed to retrieve or sample the maps.
|
/// Failed to retrieve or sample the maps.
|
||||||
#[error("Failed to retrieve or sample the maps: {0}")]
|
#[error("Failed to retrieve or sample the maps: {0}")]
|
||||||
Maps(#[from] self::maps::Error),
|
Maps(#[from] maps::Error),
|
||||||
|
|
||||||
/// No geocoded position could be found.
|
/// No geocoded position could be found.
|
||||||
#[error("No geocoded position could be found")]
|
#[error("No geocoded position could be found")]
|
||||||
|
@ -185,10 +185,8 @@ async fn version() -> Result<Json<VersionInfo>> {
|
||||||
Ok(Json(VersionInfo::new()))
|
Ok(Json(VersionInfo::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up Rocket.
|
/// Sets up Rocket without fairings.
|
||||||
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
fn rocket_core(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
let maps_refresher = maps::run(Arc::clone(&maps_handle));
|
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
|
@ -201,6 +199,14 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.manage(maps_handle)
|
.manage(maps_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up Rocket.
|
||||||
|
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||||
|
let rocket = rocket_core(Arc::clone(&maps_handle));
|
||||||
|
let maps_refresher = maps::run(maps_handle);
|
||||||
|
|
||||||
|
rocket
|
||||||
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
|
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// We don't care about the join handle nor error results?
|
// We don't care about the join handle nor error results?
|
||||||
|
@ -263,8 +269,8 @@ mod tests {
|
||||||
let response = client.get("/forecast?address=eindhoven").dispatch();
|
let response = client.get("/forecast?address=eindhoven").dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||||
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.4392648, 1e-8);
|
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.448557, 1e-1);
|
||||||
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.478633, 1e-8);
|
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.450123, 1e-1);
|
||||||
assert_matches!(json["time"], JsonValue::Number(_));
|
assert_matches!(json["time"], JsonValue::Number(_));
|
||||||
assert_matches!(json.get("AQI"), None);
|
assert_matches!(json.get("AQI"), None);
|
||||||
assert_matches!(json.get("NO2"), None);
|
assert_matches!(json.get("NO2"), None);
|
||||||
|
@ -281,8 +287,8 @@ mod tests {
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||||
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.4392648, 1e-8);
|
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.448557, 1e-1);
|
||||||
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.478633, 1e-8);
|
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.450123, 1e-1);
|
||||||
assert_matches!(json["time"], JsonValue::Number(_));
|
assert_matches!(json["time"], JsonValue::Number(_));
|
||||||
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
|
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
|
||||||
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
|
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
|
||||||
|
@ -338,7 +344,8 @@ mod tests {
|
||||||
fn map_address() {
|
fn map_address() {
|
||||||
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
||||||
let maps_handle_clone = Arc::clone(&maps_handle);
|
let maps_handle_clone = Arc::clone(&maps_handle);
|
||||||
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
|
let client =
|
||||||
|
Client::tracked(rocket_core(maps_handle)).expect("Not a valid Rocket instance");
|
||||||
|
|
||||||
// No maps available yet.
|
// No maps available yet.
|
||||||
let response = client
|
let response = client
|
||||||
|
@ -366,18 +373,15 @@ mod tests {
|
||||||
|
|
||||||
// No metric selected, don't know which map to show?
|
// No metric selected, don't know which map to show?
|
||||||
let response = client.get("/map?address=eindhoven").dispatch();
|
let response = client.get("/map?address=eindhoven").dispatch();
|
||||||
assert_eq!(response.status(), Status::NotFound);
|
assert_eq!(response.status(), Status::UnprocessableEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_geo() {
|
fn map_geo() {
|
||||||
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
||||||
let maps_handle_clone = Arc::clone(&maps_handle);
|
let maps_handle_clone = Arc::clone(&maps_handle);
|
||||||
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
|
let client =
|
||||||
|
Client::tracked(rocket_core(maps_handle)).expect("Not a valid Rocket instance");
|
||||||
// No metric passed, don't know which map to show?
|
|
||||||
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
|
|
||||||
assert_eq!(response.status(), Status::NotFound);
|
|
||||||
|
|
||||||
// No maps available yet.
|
// No maps available yet.
|
||||||
let response = client.get("/map?lat=51.4&lon=5.5&metric=pollen").dispatch();
|
let response = client.get("/map?lat=51.4&lon=5.5&metric=pollen").dispatch();
|
||||||
|
@ -395,8 +399,12 @@ mod tests {
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::PNG));
|
assert_eq!(response.content_type(), Some(ContentType::PNG));
|
||||||
|
|
||||||
|
// ... but not if it is out of bounds.
|
||||||
|
let response = client.get("/map?lat=0.0&lon=0.0&metric=pollen").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
// No metric passed, don't know which map to show?
|
// No metric passed, don't know which map to show?
|
||||||
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
|
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
|
||||||
assert_eq!(response.status(), Status::NotFound);
|
assert_eq!(response.status(), Status::UnprocessableEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/maps.rs
13
src/maps.rs
|
@ -8,7 +8,7 @@ use std::f64::consts::PI;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use chrono::serde::ts_seconds;
|
use chrono::serde::ts_seconds;
|
||||||
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
use chrono::{DateTime, Duration, NaiveDateTime, TimeZone, Utc};
|
||||||
use image::{
|
use image::{
|
||||||
DynamicImage, GenericImage, GenericImageView, ImageError, ImageFormat, Pixel, Rgb, Rgba,
|
DynamicImage, GenericImage, GenericImageView, ImageError, ImageFormat, Pixel, Rgb, Rgba,
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ pub(crate) enum Error {
|
||||||
|
|
||||||
/// Failed to join a task.
|
/// Failed to join a task.
|
||||||
#[error("Failed to join a task: {0}")]
|
#[error("Failed to join a task: {0}")]
|
||||||
Join(#[from] rocket::tokio::task::JoinError),
|
Join(#[from] tokio::task::JoinError),
|
||||||
|
|
||||||
/// Did not find any known (map key) colors in samples.
|
/// Did not find any known (map key) colors in samples.
|
||||||
#[error("Did not find any known colors in samples")]
|
#[error("Did not find any known colors in samples")]
|
||||||
|
@ -393,7 +393,7 @@ fn sample<I: GenericImageView<Pixel = Rgba<u8>>>(
|
||||||
.expect("Maximum color is always a map key color") as u8;
|
.expect("Maximum color is always a map key color") as u8;
|
||||||
|
|
||||||
samples.push(Sample { time, score });
|
samples.push(Sample { time, score });
|
||||||
time += chrono::Duration::seconds(interval);
|
time += Duration::seconds(interval);
|
||||||
offset += width;
|
offset += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ async fn retrieve_image(url: Url) -> Result<RetrievedMaps> {
|
||||||
.ok_or_else(|| Error::InvalidImagePath(path.to_owned()))?;
|
.ok_or_else(|| Error::InvalidImagePath(path.to_owned()))?;
|
||||||
let timestamp = NaiveDateTime::parse_from_str(timestamp_str, "%Y%m%d%H%M")?;
|
let timestamp = NaiveDateTime::parse_from_str(timestamp_str, "%Y%m%d%H%M")?;
|
||||||
|
|
||||||
DateTime::<Utc>::from_utc(timestamp, Utc)
|
Utc.from_utc_datetime(×tamp)
|
||||||
};
|
};
|
||||||
let bytes = response.bytes().await?;
|
let bytes = response.bytes().await?;
|
||||||
|
|
||||||
|
@ -575,10 +575,7 @@ pub(crate) async fn mark_map(
|
||||||
|
|
||||||
// Encode the image as PNG image data.
|
// Encode the image as PNG image data.
|
||||||
let mut image_data = Cursor::new(Vec::new());
|
let mut image_data = Cursor::new(Vec::new());
|
||||||
match image.write_to(
|
match image.write_to(&mut image_data, ImageFormat::Png) {
|
||||||
&mut image_data,
|
|
||||||
image::ImageOutputFormat::from(image::ImageFormat::Png),
|
|
||||||
) {
|
|
||||||
Ok(()) => Ok(image_data.into_inner()),
|
Ok(()) => Ok(image_data.into_inner()),
|
||||||
Err(err) => Err(crate::Error::from(Error::from(err))),
|
Err(err) => Err(crate::Error::from(Error::from(err))),
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ pub(crate) async fn resolve_address(address: String) -> Result<Position> {
|
||||||
let points: Vec<Point<f64>> = osm.forward(&address)?;
|
let points: Vec<Point<f64>> = osm.forward(&address)?;
|
||||||
|
|
||||||
points
|
points
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or(Error::NoPositionFound)
|
.ok_or(Error::NoPositionFound)
|
||||||
.map(Position::from)
|
.map(Position::from)
|
||||||
})
|
})
|
||||||
|
|
|
@ -108,7 +108,7 @@ fn merge(
|
||||||
// value.
|
// value.
|
||||||
let items = pollen_samples
|
let items = pollen_samples
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(aqi_items.into_iter())
|
.zip(aqi_items)
|
||||||
.map(|(pollen_sample, aqi_item)| {
|
.map(|(pollen_sample, aqi_item)| {
|
||||||
let time = pollen_sample.time;
|
let time = pollen_sample.time;
|
||||||
let value = (pollen_sample.score as f32).max(aqi_item.value);
|
let value = (pollen_sample.score as f32).max(aqi_item.value);
|
||||||
|
|
Loading…
Reference in New Issue