Compare commits

...

9 Commits

Author SHA1 Message Date
Paul van Tilburg 3fb899d1fd
Update the changelog 2022-05-10 15:05:57 +02:00
Paul van Tilburg f0cc54f074
Bump the version to 0.2.2 2022-05-10 14:58:21 +02:00
Paul van Tilburg ac653ef0c9
Cargo update 2022-05-10 14:57:03 +02:00
Paul van Tilburg e204e7905c
Update to Rocket 0.5-rc.2 2022-05-10 14:55:57 +02:00
Paul van Tilburg 08cdfe1e1c Merge pull request 'Fix timestamps for map samples not being correct' (#22) from 21-fix-ts-map-samples into main
Reviewed-on: #22
2022-05-10 14:26:38 +02:00
Paul van Tilburg 89395f21f6
Introduce RetrievedMaps; refactor around it
The `RetrievedMaps` struct captures the image and its metadata:
the last modification time and the base timestamp for the maps.

* No longer store the last modification time, called "stamp" before,
  separately in the `Maps` struct
* Update methods on `Maps` to use the `RetrievedMaps` structs and
  the timestamp base in particular for sampling and map marking
* Update the `MapsRefresh` implemention to use the last modification
  time
* Rename some variables from `map` to `image` in the helper functions
  for consistency
* Update tests and documentation
2022-05-10 14:19:09 +02:00
Paul van Tilburg ff9f1ac371
Parse timestamp base from filename 2022-05-10 13:21:21 +02:00
Paul van Tilburg 4a6eeab787
Fix sample/item being out of the combined series time range
For example, if there are 24 valid pollen samples and 20 valid air
quality items, the maximum pollen sample could be de 23th, but the
resulting combined series will only cover 20 items. So, it is should not
return that, but only look in the first 20 pollen samples for the
maximum sample.
2022-05-10 12:26:10 +02:00
Paul van Tilburg ab4b0bba72
Fix valid samples/items being discarded too early
A forecasted sample/item may be for example timestamped at 14:00.
For hourly forecasts, it will still be valid until 14:59:59, not
14:29:59.
2022-05-10 12:24:26 +02:00
7 changed files with 471 additions and 333 deletions

View File

@ -7,7 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.2.1] - 2002-05-08 ## [0.2.2] - 2022-05-10
### Changed
* Switch to Rocket 0.5 RC2
### Fixed
* Fix timestamps for map samples not being correct (AQI, PAQI, UVI metrics) (#22)
* Valid samples/items will no longer be discarded too early
## [0.2.1] - 2022-05-08
### Added ### Added
@ -28,7 +39,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.1...HEAD [Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.2...HEAD
[0.2.2]: https://git.luon.net/paul/sinoptik/compare/v0.2.1...v0.2.2
[0.2.1]: https://git.luon.net/paul/sinoptik/compare/v0.2.0...v0.2.1 [0.2.1]: https://git.luon.net/paul/sinoptik/compare/v0.2.0...v0.2.1
[0.2.0]: https://git.luon.net/paul/sinoptik/compare/v0.1.0...v0.2.0 [0.2.0]: https://git.luon.net/paul/sinoptik/compare/v0.1.0...v0.2.0
[0.1.0]: https://git.luon.net/paul/sinoptik/commits/tag/v0.1.0 [0.1.0]: https://git.luon.net/paul/sinoptik/commits/tag/v0.1.0

492
Cargo.lock generated
View File

@ -23,6 +23,41 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "aes-gcm"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.12.1" version = "0.12.1"
@ -132,12 +167,6 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base-x"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -162,6 +191,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "0.2.17" version = "0.2.17"
@ -219,7 +257,7 @@ dependencies = [
"lazy_static", "lazy_static",
"once_cell", "once_cell",
"thiserror", "thiserror",
"tokio 1.18.1", "tokio 1.18.2",
] ]
[[package]] [[package]]
@ -294,6 +332,15 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "color-eyre" name = "color-eyre"
version = "0.6.1" version = "0.6.1"
@ -327,20 +374,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "const_fn"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.15.1" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [ dependencies = [
"aes-gcm",
"base64",
"hkdf",
"hmac",
"percent-encoding", "percent-encoding",
"time 0.2.27", "rand",
"sha2",
"subtle",
"time 0.3.9",
"version_check", "version_check",
] ]
@ -360,6 +408,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.2" version = "1.3.2"
@ -414,6 +471,16 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "csv" name = "csv"
version = "1.1.6" version = "1.1.6"
@ -436,6 +503,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.13.4" version = "0.13.4"
@ -514,10 +590,15 @@ dependencies = [
] ]
[[package]] [[package]]
name = "discard" name = "digest"
version = "1.0.4" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "either" name = "either"
@ -757,6 +838,16 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "generic-array"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "geo-types" name = "geo-types"
version = "0.6.2" version = "0.6.2"
@ -796,6 +887,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "ghash"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]] [[package]]
name = "gif" name = "gif"
version = "0.11.3" version = "0.11.3"
@ -852,7 +953,7 @@ dependencies = [
"http", "http",
"indexmap", "indexmap",
"slab", "slab",
"tokio 1.18.1", "tokio 1.18.2",
"tokio-util 0.7.1", "tokio-util 0.7.1",
"tracing", "tracing",
] ]
@ -884,6 +985,24 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hkdf"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.7" version = "0.2.7"
@ -976,7 +1095,7 @@ dependencies = [
"itoa 1.0.1", "itoa 1.0.1",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
"socket2 0.4.4", "socket2 0.4.4",
"tokio 1.18.1", "tokio 1.18.2",
"tower-service", "tower-service",
"tracing", "tracing",
"want", "want",
@ -1004,7 +1123,7 @@ dependencies = [
"bytes 1.1.0", "bytes 1.1.0",
"hyper 0.14.18", "hyper 0.14.18",
"native-tls", "native-tls",
"tokio 1.18.1", "tokio 1.18.2",
"tokio-native-tls", "tokio-native-tls",
] ]
@ -1115,9 +1234,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "jpeg-decoder" name = "jpeg-decoder"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be7ef4b99870f0c9f2fc2f20dbef72707e2bcca675bb9196734cf433e999b0c5" checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
dependencies = [ dependencies = [
"rayon", "rayon",
] ]
@ -1261,7 +1380,7 @@ dependencies = [
"kernel32-sys", "kernel32-sys",
"libc", "libc",
"log", "log",
"miow 0.2.2", "miow",
"net2", "net2",
"slab", "slab",
"winapi 0.2.8", "winapi 0.2.8",
@ -1269,16 +1388,14 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"miow 0.3.7",
"ntapi",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"winapi 0.3.9", "windows-sys",
] ]
[[package]] [[package]]
@ -1293,15 +1410,6 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "multer" name = "multer"
version = "2.0.2" version = "2.0.2"
@ -1317,7 +1425,7 @@ dependencies = [
"memchr", "memchr",
"mime", "mime",
"spin", "spin",
"tokio 1.18.1", "tokio 1.18.2",
"tokio-util 0.6.9", "tokio-util 0.6.9",
"version_check", "version_check",
] ]
@ -1360,15 +1468,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1421,10 +1520,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "object" name = "num_threads"
version = "0.28.3" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1435,6 +1543,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.40" version = "0.10.40"
@ -1488,27 +1602,25 @@ checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [ dependencies = [
"instant",
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
] ]
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.8.5" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"winapi 0.3.9", "windows-sys",
] ]
[[package]] [[package]]
@ -1644,18 +1756,24 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.16" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.38" version = "1.0.38"
@ -1866,7 +1984,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"tokio 1.18.1", "tokio 1.18.2",
"tokio-native-tls", "tokio-native-tls",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
@ -1877,9 +1995,9 @@ dependencies = [
[[package]] [[package]]
name = "rocket" name = "rocket"
version = "0.5.0-rc.1" version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
@ -1905,10 +2023,10 @@ dependencies = [
"serde_json", "serde_json",
"state", "state",
"tempfile", "tempfile",
"time 0.2.27", "time 0.3.9",
"tokio 1.18.1", "tokio 1.18.2",
"tokio-stream", "tokio-stream",
"tokio-util 0.6.9", "tokio-util 0.7.1",
"ubyte", "ubyte",
"version_check", "version_check",
"yansi", "yansi",
@ -1916,9 +2034,9 @@ dependencies = [
[[package]] [[package]]
name = "rocket_codegen" name = "rocket_codegen"
version = "0.5.0-rc.1" version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47"
dependencies = [ dependencies = [
"devise", "devise",
"glob", "glob",
@ -1932,19 +2050,18 @@ dependencies = [
[[package]] [[package]]
name = "rocket_http" name = "rocket_http"
version = "0.5.0-rc.1" version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2"
dependencies = [ dependencies = [
"cookie", "cookie",
"either", "either",
"futures",
"http", "http",
"hyper 0.14.18", "hyper 0.14.18",
"indexmap", "indexmap",
"log", "log",
"memchr", "memchr",
"mime",
"parking_lot",
"pear", "pear",
"percent-encoding", "percent-encoding",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
@ -1953,8 +2070,8 @@ dependencies = [
"smallvec", "smallvec",
"stable-pattern", "stable-pattern",
"state", "state",
"time 0.2.27", "time 0.3.9",
"tokio 1.18.1", "tokio 1.18.2",
"uncased", "uncased",
] ]
@ -1964,15 +2081,6 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.6" version = "1.0.6"
@ -2036,21 +2144,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.137" version = "1.0.137"
@ -2095,20 +2188,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sha1" name = "sha2"
version = "0.6.1" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
dependencies = [ dependencies = [
"sha1_smol", "cfg-if 1.0.0",
"cpufeatures",
"digest",
] ]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@ -2129,7 +2218,7 @@ dependencies = [
[[package]] [[package]]
name = "sinoptik" name = "sinoptik"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"assert_float_eq", "assert_float_eq",
"assert_matches", "assert_matches",
@ -2201,15 +2290,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "standback"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "state" name = "state"
version = "0.5.3" version = "0.5.3"
@ -2219,55 +2299,6 @@ dependencies = [
"loom", "loom",
] ]
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde",
"serde_derive",
"serde_json",
"sha1",
"syn",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -2275,10 +2306,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "subtle"
version = "1.0.92" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2361,41 +2398,21 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.2.27" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
dependencies = [ dependencies = [
"const_fn", "itoa 1.0.1",
"libc", "libc",
"standback", "num_threads",
"stdweb",
"time-macros", "time-macros",
"version_check",
"winapi 0.3.9",
] ]
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.1.1" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
dependencies = [
"proc-macro-hack",
"time-macros-impl",
]
[[package]]
name = "time-macros-impl"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"standback",
"syn",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
@ -2432,14 +2449,14 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.18.1" version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
dependencies = [ dependencies = [
"bytes 1.1.0", "bytes 1.1.0",
"libc", "libc",
"memchr", "memchr",
"mio 0.8.2", "mio 0.8.3",
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
@ -2467,7 +2484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [ dependencies = [
"native-tls", "native-tls",
"tokio 1.18.1", "tokio 1.18.2",
] ]
[[package]] [[package]]
@ -2478,7 +2495,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
"tokio 1.18.1", "tokio 1.18.2",
] ]
[[package]] [[package]]
@ -2516,7 +2533,7 @@ dependencies = [
"futures-sink", "futures-sink",
"log", "log",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
"tokio 1.18.1", "tokio 1.18.2",
] ]
[[package]] [[package]]
@ -2529,7 +2546,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
"tokio 1.18.1", "tokio 1.18.2",
"tracing", "tracing",
] ]
@ -2637,6 +2654,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]] [[package]]
name = "ubyte" name = "ubyte"
version = "0.10.1" version = "0.10.1"
@ -2686,6 +2709,16 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array",
"subtle",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.2" version = "2.2.2"
@ -2856,6 +2889,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.7.0" version = "0.7.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "sinoptik" name = "sinoptik"
version = "0.2.1" version = "0.2.2"
authors = [ authors = [
"Admar Schoonen <admar@luon.net", "Admar Schoonen <admar@luon.net",
"Paul van Tilburg <paul@luon.net>" "Paul van Tilburg <paul@luon.net>"
@ -20,7 +20,7 @@ csv = "1.1.6"
geocoding = "0.3.1" geocoding = "0.3.1"
image = "0.24.1" image = "0.24.1"
reqwest = { version = "0.11.9", features = ["json"] } reqwest = { version = "0.11.9", features = ["json"] }
rocket = { version = "0.5.0-rc.1", features = ["json"] } rocket = { version = "0.5.0-rc.2", features = ["json"] }
[dev-dependencies] [dev-dependencies]
assert_float_eq = "1.1.3" assert_float_eq = "1.1.3"

View File

@ -10,8 +10,7 @@
use std::future::Future; use std::future::Future;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use rocket::http::ContentType; use rocket::response::Responder;
use rocket::response::content::Custom;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{get, routes, Build, Rocket, State}; use rocket::{get, routes, Build, Rocket, State};
@ -25,6 +24,10 @@ pub(crate) mod maps;
pub(crate) mod position; pub(crate) mod position;
pub(crate) mod providers; pub(crate) mod providers;
#[derive(Responder)]
#[response(status = 200, content_type = "image/png")]
struct PngImageData(Vec<u8>);
/// 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(
@ -61,11 +64,11 @@ async fn map_address(
address: String, address: String,
metric: Metric, metric: Metric,
maps_handle: &State<MapsHandle>, maps_handle: &State<MapsHandle>,
) -> Option<Custom<Vec<u8>>> { ) -> Option<PngImageData> {
let position = resolve_address(address).await?; let position = resolve_address(address).await?;
let image_data = mark_map(position, metric, maps_handle).await; let image_data = mark_map(position, metric, maps_handle).await;
image_data.map(|id| Custom(ContentType::PNG, id)) image_data.map(PngImageData)
} }
/// Handler for showing the current map with the geocoded position for a specific metric. /// Handler for showing the current map with the geocoded position for a specific metric.
@ -77,11 +80,11 @@ async fn map_geo(
lon: f64, lon: f64,
metric: Metric, metric: Metric,
maps_handle: &State<MapsHandle>, maps_handle: &State<MapsHandle>,
) -> Option<Custom<Vec<u8>>> { ) -> Option<PngImageData> {
let position = Position::new(lat, lon); let position = Position::new(lat, lon);
let image_data = mark_map(position, metric, maps_handle).await; let image_data = mark_map(position, metric, maps_handle).await;
image_data.map(|id| Custom(ContentType::PNG, id)) image_data.map(PngImageData)
} }
/// Sets up Rocket. /// Sets up Rocket.
@ -111,12 +114,15 @@ mod tests {
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
use rocket::serde::json::Value as JsonValue; use rocket::serde::json::Value as JsonValue;
use super::maps::RetrievedMaps;
use super::*; use super::*;
fn maps_stub(map_count: u32) -> DynamicImage { fn maps_stub(map_count: u32) -> RetrievedMaps {
let map_color = Rgba::from([73, 218, 33, 255]); // First color from map key. let map_color = Rgba::from([73, 218, 33, 255]); // First color from map key.
let image =
DynamicImage::ImageRgba8(RgbaImage::from_pixel(820 * map_count, 988, map_color));
DynamicImage::ImageRgba8(RgbaImage::from_pixel(820 * map_count, 988, map_color)) RetrievedMaps::new(image)
} }
fn maps_handle_stub() -> MapsHandle { fn maps_handle_stub() -> MapsHandle {

View File

@ -22,7 +22,7 @@ async fn main() -> Result<()> {
select! { select! {
result = rocket.launch() => { result = rocket.launch() => {
result? result.map(|_| ())?
} }
result = maps_refresher => { result = maps_refresher => {
shutdown.notify(); shutdown.notify();

View File

@ -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, Utc}; use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat, Pixel, Rgb, Rgba}; use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat, Pixel, Rgb, Rgba};
use reqwest::Url; use reqwest::Url;
use rocket::serde::Serialize; use rocket::serde::Serialize;
@ -113,26 +113,20 @@ trait MapsRefresh {
fn is_uvi_stale(&self) -> bool; fn is_uvi_stale(&self) -> bool;
/// Updates the pollen maps. /// Updates the pollen maps.
fn set_pollen(&self, result: Option<(DynamicImage, DateTime<Utc>)>); fn set_pollen(&self, result: Option<RetrievedMaps>);
/// Updates the UV index maps. /// Updates the UV index maps.
fn set_uvi(&self, result: Option<(DynamicImage, DateTime<Utc>)>); fn set_uvi(&self, result: Option<RetrievedMaps>);
} }
/// Container type for all in-memory cached maps. /// Container type for all in-memory cached maps.
#[derive(Debug)] #[derive(Debug, Default)]
pub(crate) struct Maps { pub(crate) struct Maps {
/// The pollen maps (from Buienradar). /// The pollen maps (from Buienradar).
pub(crate) pollen: Option<DynamicImage>, pub(crate) pollen: Option<RetrievedMaps>,
/// The timestamp the pollen maps were last refreshed.
pollen_stamp: DateTime<Utc>,
/// The UV index maps (from Buienradar). /// The UV index maps (from Buienradar).
pub(crate) uvi: Option<DynamicImage>, pub(crate) uvi: Option<RetrievedMaps>,
/// The timestamp the UV index maps were last refreshed.
uvi_stamp: DateTime<Utc>,
} }
impl Maps { impl Maps {
@ -141,12 +135,9 @@ impl Maps {
/// It contains an [`DynamicImage`] per maps type, if downloaded, and the timestamp of the last /// It contains an [`DynamicImage`] per maps type, if downloaded, and the timestamp of the last
/// update. /// update.
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let now = Utc::now();
Self { Self {
pollen: None, pollen: None,
pollen_stamp: now,
uvi: None, uvi: None,
uvi_stamp: now,
} }
} }
@ -156,16 +147,18 @@ impl Maps {
/// the current moment or if the provided position is not within the bounds of the map. /// the current moment or if the provided position is not within the bounds of the map.
pub(crate) fn pollen_mark(&self, position: Position) -> Option<DynamicImage> { pub(crate) fn pollen_mark(&self, position: Position) -> Option<DynamicImage> {
self.pollen.as_ref().and_then(|maps| { self.pollen.as_ref().and_then(|maps| {
let map = map_at( let image = &maps.image;
maps, let stamp = maps.timestamp_base;
self.pollen_stamp, let marked_image = map_at(
image,
stamp,
POLLEN_MAP_INTERVAL, POLLEN_MAP_INTERVAL,
POLLEN_MAP_COUNT, POLLEN_MAP_COUNT,
Utc::now(), Utc::now(),
)?; )?;
let coords = project(&map, POLLEN_MAP_REF_POINTS, position)?; let coords = project(&marked_image, POLLEN_MAP_REF_POINTS, position)?;
Some(mark(map, coords)) Some(mark(marked_image, coords))
}) })
} }
@ -176,16 +169,12 @@ impl Maps {
/// in the series of maps. /// in the series of maps.
pub(crate) fn pollen_samples(&self, position: Position) -> Option<Vec<Sample>> { pub(crate) fn pollen_samples(&self, position: Position) -> Option<Vec<Sample>> {
self.pollen.as_ref().and_then(|maps| { self.pollen.as_ref().and_then(|maps| {
let map = maps.view(0, 0, maps.width() / UVI_MAP_COUNT, maps.height()); let image = &maps.image;
let map = image.view(0, 0, image.width() / UVI_MAP_COUNT, image.height());
let coords = project(&*map, POLLEN_MAP_REF_POINTS, position)?; let coords = project(&*map, POLLEN_MAP_REF_POINTS, position)?;
let stamp = maps.timestamp_base;
sample( sample(image, stamp, POLLEN_MAP_INTERVAL, POLLEN_MAP_COUNT, coords)
maps,
self.pollen_stamp,
POLLEN_MAP_INTERVAL,
POLLEN_MAP_COUNT,
coords,
)
}) })
} }
@ -195,16 +184,12 @@ impl Maps {
/// the current moment or if the provided position is not within the bounds of the map. /// the current moment or if the provided position is not within the bounds of the map.
pub(crate) fn uvi_mark(&self, position: Position) -> Option<DynamicImage> { pub(crate) fn uvi_mark(&self, position: Position) -> Option<DynamicImage> {
self.uvi.as_ref().and_then(|maps| { self.uvi.as_ref().and_then(|maps| {
let map = map_at( let image = &maps.image;
maps, let stamp = maps.timestamp_base;
self.uvi_stamp, let marked_image = map_at(image, stamp, UVI_MAP_INTERVAL, UVI_MAP_COUNT, Utc::now())?;
UVI_MAP_INTERVAL, let coords = project(&marked_image, POLLEN_MAP_REF_POINTS, position)?;
UVI_MAP_COUNT,
Utc::now(),
)?;
let coords = project(&map, POLLEN_MAP_REF_POINTS, position)?;
Some(mark(map, coords)) Some(mark(marked_image, coords))
}) })
} }
@ -215,16 +200,12 @@ impl Maps {
/// in the series of maps. /// in the series of maps.
pub(crate) fn uvi_samples(&self, position: Position) -> Option<Vec<Sample>> { pub(crate) fn uvi_samples(&self, position: Position) -> Option<Vec<Sample>> {
self.uvi.as_ref().and_then(|maps| { self.uvi.as_ref().and_then(|maps| {
let map = maps.view(0, 0, maps.width() / UVI_MAP_COUNT, maps.height()); let image = &maps.image;
let map = image.view(0, 0, image.width() / UVI_MAP_COUNT, image.height());
let coords = project(&*map, UVI_MAP_REF_POINTS, position)?; let coords = project(&*map, UVI_MAP_REF_POINTS, position)?;
let stamp = maps.timestamp_base;
sample( sample(image, stamp, UVI_MAP_INTERVAL, UVI_MAP_COUNT, coords)
maps,
self.uvi_stamp,
UVI_MAP_INTERVAL,
UVI_MAP_COUNT,
coords,
)
}) })
} }
} }
@ -233,60 +214,66 @@ impl MapsRefresh for MapsHandle {
fn is_pollen_stale(&self) -> bool { fn is_pollen_stale(&self) -> bool {
let maps = self.lock().expect("Maps handle mutex was poisoned"); let maps = self.lock().expect("Maps handle mutex was poisoned");
Utc::now().signed_duration_since(maps.pollen_stamp) match &maps.pollen {
> Duration::seconds(POLLEN_MAP_COUNT as i64 * POLLEN_MAP_INTERVAL) Some(pollen_maps) => {
Utc::now().signed_duration_since(pollen_maps.mtime)
> Duration::seconds(POLLEN_MAP_COUNT as i64 * POLLEN_MAP_INTERVAL)
}
None => false,
}
} }
fn is_uvi_stale(&self) -> bool { fn is_uvi_stale(&self) -> bool {
let maps = self.lock().expect("Maps handle mutex was poisoned"); let maps = self.lock().expect("Maps handle mutex was poisoned");
Utc::now().signed_duration_since(maps.uvi_stamp) match &maps.uvi {
> Duration::seconds(UVI_MAP_COUNT as i64 * UVI_MAP_INTERVAL) Some(uvi_maps) => {
Utc::now().signed_duration_since(uvi_maps.mtime)
> Duration::seconds(UVI_MAP_COUNT as i64 * UVI_MAP_INTERVAL)
}
None => false,
}
} }
fn needs_pollen_refresh(&self) -> bool { fn needs_pollen_refresh(&self) -> bool {
let maps = self.lock().expect("Maps handle mutex was poisoned"); let maps = self.lock().expect("Maps handle mutex was poisoned");
maps.pollen.is_none() match &maps.pollen {
|| Utc::now() Some(pollen_maps) => {
.signed_duration_since(maps.pollen_stamp) Utc::now()
.num_seconds() .signed_duration_since(pollen_maps.mtime)
> POLLEN_INTERVAL .num_seconds()
> POLLEN_INTERVAL
}
None => true,
}
} }
fn needs_uvi_refresh(&self) -> bool { fn needs_uvi_refresh(&self) -> bool {
let maps = self.lock().expect("Maps handle mutex was poisoned"); let maps = self.lock().expect("Maps handle mutex was poisoned");
maps.uvi.is_none() match &maps.uvi {
|| Utc::now() Some(uvi_maps) => {
.signed_duration_since(maps.uvi_stamp) Utc::now()
.num_seconds() .signed_duration_since(uvi_maps.mtime)
> UVI_INTERVAL .num_seconds()
} > UVI_INTERVAL
fn set_pollen(&self, result: Option<(DynamicImage, DateTime<Utc>)>) {
if result.is_some() || self.is_pollen_stale() {
let mut maps = self.lock().expect("Maps handle mutex was poisoned");
if let Some((pollen, pollen_stamp)) = result {
maps.pollen = Some(pollen);
maps.pollen_stamp = pollen_stamp
} else {
maps.pollen = None
} }
None => true,
} }
} }
fn set_uvi(&self, result: Option<(DynamicImage, DateTime<Utc>)>) { fn set_pollen(&self, retrieved_maps: Option<RetrievedMaps>) {
if result.is_some() || self.is_uvi_stale() { if retrieved_maps.is_some() || self.is_pollen_stale() {
let mut maps = self.lock().expect("Maps handle mutex was poisoned"); let mut maps = self.lock().expect("Maps handle mutex was poisoned");
maps.pollen = retrieved_maps;
}
}
if let Some((uvi, uvi_stamp)) = result { fn set_uvi(&self, retrieved_maps: Option<RetrievedMaps>) {
maps.uvi = Some(uvi); if retrieved_maps.is_some() || self.is_uvi_stale() {
maps.uvi_stamp = uvi_stamp let mut maps = self.lock().expect("Maps handle mutex was poisoned");
} else { maps.uvi = retrieved_maps;
maps.uvi = None
}
} }
} }
} }
@ -331,23 +318,23 @@ fn map_key_histogram() -> MapKeyHistogram {
/// ///
/// Returns [`None`] if it encounters no known colors in any of the samples. /// Returns [`None`] if it encounters no known colors in any of the samples.
fn sample<I: GenericImageView<Pixel = Rgba<u8>>>( fn sample<I: GenericImageView<Pixel = Rgba<u8>>>(
maps: &I, image: &I,
stamp: DateTime<Utc>, stamp: DateTime<Utc>,
interval: i64, interval: i64,
count: u32, count: u32,
coords: (u32, u32), coords: (u32, u32),
) -> Option<Vec<Sample>> { ) -> Option<Vec<Sample>> {
let (x, y) = coords; let (x, y) = coords;
let width = maps.width() / count; let width = image.width() / count;
let height = maps.height(); let height = image.height();
let max_sample_width = (width - x).min(MAP_SAMPLE_SIZE[0]); let max_sample_width = (width - x).min(MAP_SAMPLE_SIZE[0]);
let max_sample_height = (height - y).min(MAP_SAMPLE_SIZE[1]); let max_sample_height = (height - y).min(MAP_SAMPLE_SIZE[1]);
let mut samples = Vec::with_capacity(count as usize); let mut samples = Vec::with_capacity(count as usize);
let mut time = stamp; let mut time = stamp;
let mut offset = 0; let mut offset = 0;
while offset < maps.width() { while offset < image.width() {
let map = maps.view( let map = image.view(
x.saturating_sub(MAP_SAMPLE_SIZE[0] / 2) + offset, x.saturating_sub(MAP_SAMPLE_SIZE[0] / 2) + offset,
y.saturating_sub(MAP_SAMPLE_SIZE[1] / 2), y.saturating_sub(MAP_SAMPLE_SIZE[1] / 2),
max_sample_width, max_sample_width,
@ -381,12 +368,39 @@ fn sample<I: GenericImageView<Pixel = Rgba<u8>>>(
Some(samples) Some(samples)
} }
/// A retrieved image with some metadata.
#[derive(Debug)]
pub(crate) struct RetrievedMaps {
/// The image data.
pub(crate) image: DynamicImage,
/// The date/time the image was last modified.
pub(crate) mtime: DateTime<Utc>,
/// The starting date/time the image corresponds with.
pub(crate) timestamp_base: DateTime<Utc>,
}
impl RetrievedMaps {
#[cfg(test)]
pub(crate) fn new(image: DynamicImage) -> Self {
let mtime = Utc::now();
let timestamp_base = Utc::now();
Self {
image,
mtime,
timestamp_base,
}
}
}
/// Retrieves an image from the provided URL. /// Retrieves an image from the provided URL.
/// ///
/// This returns [`None`] if it fails in either performing the request, parsing the `Last-Modified` /// This returns [`None`] if it fails in either performing the request, parsing the `Last-Modified`
/// reponse HTTP header, retrieving the bytes from the image or loading and the decoding the data /// reponse HTTP header, retrieving the bytes from the image or loading and the decoding the data
/// into [`DynamicImage`]. /// into [`DynamicImage`].
async fn retrieve_image(url: Url) -> Option<(DynamicImage, DateTime<Utc>)> { async fn retrieve_image(url: Url) -> Option<RetrievedMaps> {
// TODO: Handle or log errors! // TODO: Handle or log errors!
let response = reqwest::get(url).await.ok()?; let response = reqwest::get(url).await.ok()?;
let mtime = response let mtime = response
@ -396,11 +410,23 @@ async fn retrieve_image(url: Url) -> Option<(DynamicImage, DateTime<Utc>)> {
.map(chrono::DateTime::parse_from_rfc2822)? .map(chrono::DateTime::parse_from_rfc2822)?
.map(DateTime::<Utc>::from) .map(DateTime::<Utc>::from)
.ok()?; .ok()?;
let timestamp_base = {
let path = response.url().path();
let (_, filename) = path.rsplit_once('/')?;
let (timestamp_str, _) = filename.split_once("__")?;
let timestamp = NaiveDateTime::parse_from_str(timestamp_str, "%Y%m%d%H%M").ok()?;
DateTime::<Utc>::from_utc(timestamp, Utc)
};
let bytes = response.bytes().await.ok()?; let bytes = response.bytes().await.ok()?;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
if let Ok(image) = image::load_from_memory_with_format(&bytes, ImageFormat::Png) { if let Ok(image) = image::load_from_memory_with_format(&bytes, ImageFormat::Png) {
Some((image, mtime)) Some(RetrievedMaps {
image,
mtime,
timestamp_base,
})
} else { } else {
None None
} }
@ -412,7 +438,7 @@ async fn retrieve_image(url: Url) -> Option<(DynamicImage, DateTime<Utc>)> {
/// Retrieves the pollen maps from Buienradar. /// Retrieves the pollen maps from Buienradar.
/// ///
/// See [`POLLEN_BASE_URL`] for the base URL and [`retrieve_image`] for the retrieval function. /// See [`POLLEN_BASE_URL`] for the base URL and [`retrieve_image`] for the retrieval function.
async fn retrieve_pollen_maps() -> Option<(DynamicImage, DateTime<Utc>)> { async fn retrieve_pollen_maps() -> Option<RetrievedMaps> {
let timestamp = format!("{}", chrono::Local::now().format("%y%m%d%H%M")); let timestamp = format!("{}", chrono::Local::now().format("%y%m%d%H%M"));
let mut url = Url::parse(POLLEN_BASE_URL).unwrap(); let mut url = Url::parse(POLLEN_BASE_URL).unwrap();
url.query_pairs_mut().append_pair("timestamp", &timestamp); url.query_pairs_mut().append_pair("timestamp", &timestamp);
@ -424,7 +450,7 @@ async fn retrieve_pollen_maps() -> Option<(DynamicImage, DateTime<Utc>)> {
/// Retrieves the UV index maps from Buienradar. /// Retrieves the UV index maps from Buienradar.
/// ///
/// See [`UVI_BASE_URL`] for the base URL and [`retrieve_image`] for the retrieval function. /// See [`UVI_BASE_URL`] for the base URL and [`retrieve_image`] for the retrieval function.
async fn retrieve_uvi_maps() -> Option<(DynamicImage, DateTime<Utc>)> { async fn retrieve_uvi_maps() -> Option<RetrievedMaps> {
let timestamp = format!("{}", chrono::Local::now().format("%y%m%d%H%M")); let timestamp = format!("{}", chrono::Local::now().format("%y%m%d%H%M"));
let mut url = Url::parse(UVI_BASE_URL).unwrap(); let mut url = Url::parse(UVI_BASE_URL).unwrap();
url.query_pairs_mut().append_pair("timestamp", &timestamp); url.query_pairs_mut().append_pair("timestamp", &timestamp);
@ -438,35 +464,35 @@ async fn retrieve_uvi_maps() -> Option<(DynamicImage, DateTime<Utc>)> {
/// This returns [`None`] if `instant` is too far in the future with respect to the number of /// This returns [`None`] if `instant` is too far in the future with respect to the number of
/// cached maps. /// cached maps.
fn map_at( fn map_at(
maps: &DynamicImage, image: &DynamicImage,
maps_stamp: DateTime<Utc>, stamp: DateTime<Utc>,
interval: i64, interval: i64,
count: u32, count: u32,
instant: DateTime<Utc>, instant: DateTime<Utc>,
) -> Option<DynamicImage> { ) -> Option<DynamicImage> {
let duration = instant.signed_duration_since(maps_stamp); let duration = instant.signed_duration_since(stamp);
let offset = (duration.num_seconds() / interval) as u32; let offset = (duration.num_seconds() / interval) as u32;
// Check if out of bounds. // Check if out of bounds.
if offset >= count { if offset >= count {
return None; return None;
} }
let width = maps.width() / count; let width = image.width() / count;
Some(maps.crop_imm(offset * width, 0, width, maps.height())) Some(image.crop_imm(offset * width, 0, width, image.height()))
} }
/// Marks the provided coordinates on the map using a horizontal and vertical line. /// Marks the provided coordinates on the map using a horizontal and vertical line.
fn mark(mut map: DynamicImage, coords: (u32, u32)) -> DynamicImage { fn mark(mut image: DynamicImage, coords: (u32, u32)) -> DynamicImage {
let (x, y) = coords; let (x, y) = coords;
for py in 0..map.height() { for py in 0..image.height() {
map.put_pixel(x, py, Rgba::from([0x00, 0x00, 0x00, 0x70])); image.put_pixel(x, py, Rgba::from([0x00, 0x00, 0x00, 0x70]));
} }
for px in 0..map.width() { for px in 0..image.width() {
map.put_pixel(px, y, Rgba::from([0x00, 0x00, 0x00, 0x70])); image.put_pixel(px, y, Rgba::from([0x00, 0x00, 0x00, 0x70]));
} }
map image
} }
/// Projects the provided geocoded position to a coordinate on a map. /// Projects the provided geocoded position to a coordinate on a map.
@ -476,7 +502,7 @@ fn mark(mut map: DynamicImage, coords: (u32, u32)) -> DynamicImage {
/// ///
/// Returns [`None`] if the resulting coordinate is not within the bounds of the map. /// Returns [`None`] if the resulting coordinate is not within the bounds of the map.
fn project<I: GenericImageView>( fn project<I: GenericImageView>(
map: &I, image: &I,
ref_points: [(Position, (u32, u32)); 2], ref_points: [(Position, (u32, u32)); 2],
pos: Position, pos: Position,
) -> Option<(u32, u32)> { ) -> Option<(u32, u32)> {
@ -495,7 +521,7 @@ fn project<I: GenericImageView>(
let scale_y = ((ref1_y - ref2_y) as f64) / (ref2_merc_y - ref1_merc_y); let scale_y = ((ref1_y - ref2_y) as f64) / (ref2_merc_y - ref1_merc_y);
let y = ((ref2_merc_y - mercator_y(pos.lat_as_rad())) * scale_y + ref2_y as f64).round() as u32; let y = ((ref2_merc_y - mercator_y(pos.lat_as_rad())) * scale_y + ref2_y as f64).round() as u32;
if map.in_bounds(x, y) { if image.in_bounds(x, y) {
Some((x, y)) Some((x, y))
} else { } else {
None None
@ -548,13 +574,13 @@ pub(crate) async fn run(maps_handle: MapsHandle) {
println!("🕔 Refreshing the maps (if necessary)..."); println!("🕔 Refreshing the maps (if necessary)...");
if maps_handle.needs_pollen_refresh() { if maps_handle.needs_pollen_refresh() {
let result = retrieve_pollen_maps().await; let retrieved_maps = retrieve_pollen_maps().await;
maps_handle.set_pollen(result); maps_handle.set_pollen(retrieved_maps);
} }
if maps_handle.needs_uvi_refresh() { if maps_handle.needs_uvi_refresh() {
let result = retrieve_uvi_maps().await; let retrieved_maps = retrieve_uvi_maps().await;
maps_handle.set_uvi(result); maps_handle.set_uvi(retrieved_maps);
} }
sleep(REFRESH_INTERVAL).await; sleep(REFRESH_INTERVAL).await;

View File

@ -50,10 +50,10 @@ fn merge(
let mut pollen_samples = pollen_samples; let mut pollen_samples = pollen_samples;
let mut aqi_items = aqi_items; let mut aqi_items = aqi_items;
// Only retain samples/items that have timestamps that are at least half an hour ago. // Only retain samples/items that have timestamps that are at least an hour ago.
let now = Utc::now(); let now = Utc::now();
pollen_samples.retain(|smp| smp.time.signed_duration_since(now).num_seconds() > -1800); pollen_samples.retain(|smp| smp.time.signed_duration_since(now).num_seconds() > -3600);
aqi_items.retain(|item| item.time.signed_duration_since(now).num_seconds() > -1800); aqi_items.retain(|item| item.time.signed_duration_since(now).num_seconds() > -3600);
// Align the iterators based on the (hourly) timestamps! // Align the iterators based on the (hourly) timestamps!
let pollen_first_time = pollen_samples.first()?.time; let pollen_first_time = pollen_samples.first()?.time;
@ -81,14 +81,17 @@ fn merge(
} }
// Find the maximum sample/item of each series. // Find the maximum sample/item of each series.
// Note: Unwrapping is possible because each series has at least an item otherwise `.first` // Note 1: Unwrapping is possible because each series has at least an item otherwise `.first`
// would have failed above. // would have failed above.
let pollen_max = pollen_samples // Note 2: Ensure that the maximum sample/item is in scope of the time range covered by the
// combined items.
let zip_len = std::cmp::min(pollen_samples.len(), aqi_items.len());
let pollen_max = pollen_samples[..zip_len]
.iter() .iter()
.max_by_key(|sample| sample.score) .max_by_key(|sample| sample.score)
.cloned() .cloned()
.unwrap(); .unwrap();
let aqi_max = aqi_items let aqi_max = aqi_items[..zip_len]
.iter() .iter()
.max_by_key(|item| (item.value * 1_000.0) as u32) .max_by_key(|item| (item.value * 1_000.0) as u32)
.cloned() .cloned()
@ -206,7 +209,7 @@ mod tests {
let merged = super::merge(shifted_pollen_samples, aqi_items.clone()); let merged = super::merge(shifted_pollen_samples, aqi_items.clone());
assert!(merged.is_some()); assert!(merged.is_some());
let (paqi, max_pollen, max_aqi) = merged.unwrap(); let (paqi, max_pollen, max_aqi) = merged.unwrap();
assert_eq!(paqi, Vec::from([Item::new(t_1, 2.9), Item::new(t_2, 3.0),])); assert_eq!(paqi, Vec::from([Item::new(t_1, 2.9), Item::new(t_2, 3.0)]));
assert_eq!(max_pollen, BuienradarSample::new(t_2, 3)); assert_eq!(max_pollen, BuienradarSample::new(t_2, 3));
assert_eq!(max_aqi, LuchtmeetnetItem::new(t_1, 2.9)); assert_eq!(max_aqi, LuchtmeetnetItem::new(t_1, 2.9));
@ -222,10 +225,25 @@ mod tests {
let merged = super::merge(pollen_samples.clone(), shifted_aqi_items); let merged = super::merge(pollen_samples.clone(), shifted_aqi_items);
assert!(merged.is_some()); assert!(merged.is_some());
let (paqi, max_pollen, max_aqi) = merged.unwrap(); let (paqi, max_pollen, max_aqi) = merged.unwrap();
assert_eq!(paqi, Vec::from([Item::new(t_1, 3.0), Item::new(t_2, 2.9),])); assert_eq!(paqi, Vec::from([Item::new(t_1, 3.0), Item::new(t_2, 2.9)]));
assert_eq!(max_pollen, BuienradarSample::new(t_1, 3)); assert_eq!(max_pollen, BuienradarSample::new(t_1, 3));
assert_eq!(max_aqi, LuchtmeetnetItem::new(t_2, 2.9)); assert_eq!(max_aqi, LuchtmeetnetItem::new(t_2, 2.9));
// The maximum sample/item should not be later then the interval the PAQI items cover.
let merged = super::merge(pollen_samples[..3].to_vec(), aqi_items.clone());
assert!(merged.is_some());
let (paqi, max_pollen, max_aqi) = merged.unwrap();
assert_eq!(paqi, Vec::from([Item::new(t_0, 1.1)]));
assert_eq!(max_pollen, BuienradarSample::new(t_0, 1));
assert_eq!(max_aqi, LuchtmeetnetItem::new(t_0, 1.1));
let merged = super::merge(pollen_samples.clone(), aqi_items[..3].to_vec());
assert!(merged.is_some());
let (paqi, max_pollen, max_aqi) = merged.unwrap();
assert_eq!(paqi, Vec::from([Item::new(t_0, 1.1)]));
assert_eq!(max_pollen, BuienradarSample::new(t_0, 1));
assert_eq!(max_aqi, LuchtmeetnetItem::new(t_0, 1.1));
// Merging fails because the samples/items are too far (6 hours) apart. // Merging fails because the samples/items are too far (6 hours) apart.
let shifted_aqi_items = aqi_items let shifted_aqi_items = aqi_items
.iter() .iter()