backend/service/
plant_layer.rs

1//! Service layer for plant layer.
2
3use std::io::Cursor;
4
5use actix_http::StatusCode;
6use chrono::Utc;
7use image::{ImageBuffer, Rgba};
8
9use crate::{
10    config::data::SharedPool,
11    error::ServiceError,
12    model::{
13        dto::{HeatMapQueryParams, RelationSearchParameters, RelationsDto},
14        entity::plant_layer,
15        r#enum::heatmap_color::HeatmapColor,
16    },
17    service::application_settings,
18};
19
20use super::application_settings::HeatmapColors;
21
22/// Generates a heatmap signaling ideal locations for planting the plant.
23/// The return values are raw bytes of an PNG image.
24///
25/// # Errors
26/// * If the connection to the database could not be established.
27/// * If no map with id `map_id` exists.
28/// * 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.
29/// * If no plant with id `plant_id` exists.
30/// * If the image could not be parsed to bytes.
31pub async fn heatmap(
32    map_id: i32,
33    query_params: HeatMapQueryParams,
34    pool: &SharedPool,
35) -> Result<Vec<u8>, ServiceError> {
36    let mut conn = pool.slow_pool.get().await?;
37
38    let result = plant_layer::heatmap(
39        map_id,
40        query_params.plant_layer_id,
41        query_params.shade_layer_id,
42        query_params.hydrology_layer_id,
43        query_params.soil_layer_id,
44        query_params.plant_id,
45        query_params.date.unwrap_or_else(|| Utc::now().date_naive()),
46        &mut conn,
47    )
48    .await?;
49
50    let heatmap_colors = application_settings::get_heatmap_colors(&mut conn).await?;
51    let buffer = matrix_to_image(&result, heatmap_colors)?;
52
53    Ok(buffer)
54}
55
56/// Parses the matrix of scores with values 0-1 to raw bytes of a PNG image.
57#[allow(
58    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
59    clippy::indexing_slicing,           // ok, because size of image is generated using matrix width and height
60    clippy::cast_sign_loss              // ok, because we only care about positive values
61)]
62fn matrix_to_image(
63    matrix: &[Vec<(HeatmapColor, f32)>],
64    colors: HeatmapColors,
65) -> Result<Vec<u8>, ServiceError> {
66    let (width, height) = (matrix[0].len(), matrix.len());
67    let mut imgbuf = ImageBuffer::new(width as u32, height as u32);
68
69    let (red, orange, green, black) = colors;
70
71    for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
72        let (color, relevance) = &matrix[y as usize][x as usize];
73        let alpha = (relevance * 255.0) as u8;
74
75        *pixel = match color {
76            HeatmapColor::Red => Rgba([red.0, red.1, red.2, alpha]),
77            HeatmapColor::Orange => Rgba([orange.0, orange.1, orange.2, alpha]),
78            HeatmapColor::Green => Rgba([green.0, green.1, green.2, alpha]),
79            HeatmapColor::Black => Rgba([black.0, black.1, black.2, alpha]),
80        };
81    }
82
83    let mut buffer: Vec<u8> = Vec::new();
84    imgbuf
85        .write_to(&mut Cursor::new(&mut buffer), image::ImageOutputFormat::Png)
86        .map_err(|err| ServiceError::new(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string()))?;
87    Ok(buffer)
88}
89
90/// Get spatial relations of a certain plant.
91///
92/// # Errors
93/// * If the connection to the database could not be established.
94/// * If the SQL query failed.
95pub async fn find_relations(
96    search_query: RelationSearchParameters,
97    pool: &SharedPool,
98) -> Result<RelationsDto, ServiceError> {
99    let mut conn = pool.get().await?;
100    let result = plant_layer::find_relations(search_query, &mut conn).await?;
101    Ok(result)
102}