Add an extra map handler for showing lat/lon

* Split of the positioning and drawing code to the `draw_position`
  helper method
* Move the positioning and drawing code to a Tokia task
This commit is contained in:
Paul van Tilburg 2022-02-18 23:18:50 +01:00
parent a03573d20d
commit 7c24f73937
1 changed files with 75 additions and 31 deletions

View File

@ -58,50 +58,86 @@ async fn forecast_geo(
Json(forecast)
}
/// Handler for showing the current map with the geocoded position for a specific metric.
/// Handler for showing the current map with the geocoded position of an address for a specific
/// metric.
///
/// Note: This handler is mosly used for debugging purposes!
#[get("/map?<address>&<metric>")]
async fn show_map(
async fn show_map_address(
address: String,
metric: Metric,
maps_handle: &State<MapsHandle>,
) -> Option<Custom<Vec<u8>>> {
let position = resolve_address(address).await?;
let image_data = draw_position(position, metric, maps_handle).await;
image_data.map(|id| Custom(ContentType::PNG, id))
}
/// Handler for showing the current map with the geocoded position for a specific metric.
///
/// Note: This handler is mosly used for debugging purposes!
#[get("/map?<lat>&<lon>&<metric>", rank = 2)]
async fn show_map_geo(
lat: f64,
lon: f64,
metric: Metric,
maps_handle: &State<MapsHandle>,
) -> Option<Custom<Vec<u8>>> {
let position = Position::new(lat, lon);
let image_data = draw_position(position, metric, maps_handle).await;
image_data.map(|id| Custom(ContentType::PNG, id))
}
/// Draws a crosshair on a map for the given position.
///
/// The map that is used is determined by the metric.
// FIXME: Maybe move this to the `maps` module?
async fn draw_position(
position: Position,
metric: Metric,
maps_handle: &MapsHandle,
) -> Option<Vec<u8>> {
use image::{GenericImage, Rgba};
use std::io::Cursor;
let position = resolve_address(address).await?;
let now = Instant::now();
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
let (mut image, coords) = match metric {
Metric::PAQI => (maps.pollen_at(now)?, maps.pollen_project(position)),
Metric::Pollen => (maps.pollen_at(now)?, maps.pollen_project(position)),
Metric::UVI => (maps.uvi_at(now)?, maps.uvi_project(position)),
_ => return None, // Unsupported metric
};
drop(maps);
let maps_handle = Arc::clone(maps_handle);
tokio::task::spawn_blocking(move || {
let now = Instant::now();
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
let (mut image, coords) = match metric {
Metric::PAQI => (maps.pollen_at(now)?, maps.pollen_project(position)),
Metric::Pollen => (maps.pollen_at(now)?, maps.pollen_project(position)),
Metric::UVI => (maps.uvi_at(now)?, maps.uvi_project(position)),
_ => return None, // Unsupported metric
};
drop(maps);
if let Some((x, y)) = coords {
for py in 0..(image.height() - 1) {
image.put_pixel(x, py, Rgba::from([0x00, 0x00, 0x00, 0x70]));
if let Some((x, y)) = coords {
for py in 0..(image.height() - 1) {
image.put_pixel(x, py, Rgba::from([0x00, 0x00, 0x00, 0x70]));
}
for px in 0..(image.width() - 1) {
image.put_pixel(px, y, Rgba::from([0x00, 0x00, 0x00, 0x70]));
}
}
for px in 0..(image.width() - 1) {
image.put_pixel(px, y, Rgba::from([0x00, 0x00, 0x00, 0x70]));
}
}
// Encode the image as PNG image data.
let mut image_data = Cursor::new(Vec::new());
image
.write_to(
&mut image_data,
image::ImageOutputFormat::from(image::ImageFormat::Png),
)
.ok()?;
// Encode the image as PNG image data.
// FIXME: This encoding call blocks the worker thread!
let mut image_data = Cursor::new(Vec::new());
image
.write_to(
&mut image_data,
image::ImageOutputFormat::from(image::ImageFormat::Png),
)
.ok()?;
Some(Custom(ContentType::PNG, image_data.into_inner()))
Some(image_data.into_inner())
})
.await
.ok()
.flatten()
}
/// Starts the main maps refresh loop and sets up and launches Rocket.
@ -117,7 +153,15 @@ async fn main() -> Result<()> {
let rocket = rocket::build()
.manage(maps_handle)
.mount("/", routes![forecast_address, forecast_geo, show_map])
.mount(
"/",
routes![
forecast_address,
forecast_geo,
show_map_address,
show_map_geo
],
)
.ignite()
.await?;
let shutdown = rocket.shutdown();