1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
//! Service layer for plant layer.
use std::io::Cursor;
use actix_http::StatusCode;
use actix_web::web::Data;
use chrono::Utc;
use image::{ImageBuffer, Rgba};
use crate::{
config::data::AppDataInner,
error::ServiceError,
model::{
dto::{HeatMapQueryParams, RelationSearchParameters, RelationsDto},
entity::plant_layer,
},
};
/// Generates a heatmap signaling ideal locations for planting the plant.
/// The return values are raw bytes of an PNG image.
///
/// # Errors
/// * If the connection to the database could not be established.
/// * If no map with id `map_id` exists.
/// * If no layer with id `layer_id` exists, if the layer is not a plant layer or if the layer is not part of the map.
/// * If no plant with id `plant_id` exists.
/// * If the image could not be parsed to bytes.
pub async fn heatmap(
map_id: i32,
query_params: HeatMapQueryParams,
app_data: &Data<AppDataInner>,
) -> Result<Vec<u8>, ServiceError> {
let mut conn = app_data.pool.get().await?;
let result = plant_layer::heatmap(
map_id,
query_params.plant_layer_id,
query_params.shade_layer_id,
query_params.plant_id,
query_params.date.unwrap_or_else(|| Utc::now().date_naive()),
&mut conn,
)
.await?;
let buffer = matrix_to_image(&result)?;
Ok(buffer)
}
/// Parses the matrix of scores with values 0-1 to raw bytes of a PNG image.
#[allow(
clippy::cast_possible_truncation, // ok, because size of matrix shouldn't ever be larger than u32 and casting to u8 in image should remove floating point values
clippy::indexing_slicing, // ok, because size of image is generated using matrix width and height
clippy::cast_sign_loss // ok, because we only care about positive values
)]
fn matrix_to_image(matrix: &Vec<Vec<(f32, f32)>>) -> Result<Vec<u8>, ServiceError> {
let (width, height) = (matrix[0].len(), matrix.len());
let mut imgbuf = ImageBuffer::new(width as u32, height as u32);
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
let (preference, relevance) = matrix[y as usize][x as usize];
// The closer data is to 1 the greener it gets.
let red = preference.mul_add(-255.0, 255.0);
let green = preference * 255.0;
let blue = 0.0_f32;
// For some reason every relevance value returned by the database is between
// (about) 0.5 and 1 while it should be between 0 and 1.
//
// Unfortunately I could not figure out why this is the case and therefore just
// rescaled the relevance value accordingly.
// - Moritz (badnames)
let alpha = (relevance - 0.5) * 512.0;
*pixel = Rgba([red as u8, green as u8, blue as u8, alpha as u8]);
}
let mut buffer: Vec<u8> = Vec::new();
imgbuf
.write_to(&mut Cursor::new(&mut buffer), image::ImageOutputFormat::Png)
.map_err(|err| ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()))?;
Ok(buffer)
}
/// Get spatial relations of a certain plant.
///
/// # Errors
/// * If the connection to the database could not be established.
/// * If the SQL query failed.
pub async fn find_relations(
search_query: RelationSearchParameters,
app_data: &Data<AppDataInner>,
) -> Result<RelationsDto, ServiceError> {
let mut conn = app_data.pool.get().await?;
let result = plant_layer::find_relations(search_query, &mut conn).await?;
Ok(result)
}