backend/controller/
plant_layer.rs

1//! Plant layer endpoints.
2
3use actix_web::{
4    get,
5    web::{Path, Query},
6    HttpResponse, Result,
7};
8
9use crate::{
10    config::data::SharedPool,
11    model::dto::{HeatMapQueryParams, RelationSearchParameters},
12    service::plant_layer,
13};
14
15/// Endpoint for generating a heatmap signaling ideal locations for planting the plant.
16///
17/// Red pixels signal areas where the plant shouldn't be planted, while green areas signal ideal locations.
18/// The more transparent the location is the less data there is to support the claimed preference.
19///
20/// The resulting heatmap does represent actual coordinates, meaning the pixel at (0,0) is not necessarily at coordinates (0,0).
21/// Instead the image has to be moved and scaled to fit inside the maps boundaries.
22/// This means the lower left corner of the heatmap has to be moved/scaled to the (`x_min,y_min`) coordinate, while the upper right corner has to be moved/scaled to (`x_max,y_max`).
23///
24/// Here is pseudocode for how to move the map to the correct place in the frontend:
25///
26/// ```js
27/// // 1. Extract the polygon object from the map:
28/// let polygon = map.geometry;
29///
30/// // 2. Calculate the min and max coordinates for the map:
31/// let x_min = Math.min(...polygon.rings[0].map(point => point.x));
32/// let y_min = Math.min(...polygon.rings[0].map(point => point.y));
33/// let x_max = Math.max(...polygon.rings[0].map(point => point.x));
34/// let y_max = Math.max(...polygon.rings[0].map(point => point.y));
35///
36/// // 3. Calculate the map's width and height:
37/// let map_width = x_max - x_min;
38/// let map_height = y_max - y_min;
39///
40/// // 4. Fetch the heatmap image from the server and draw it on the canvas.
41/// let heatmapResponse = await fetch('/path/to/your/heatmap/endpoint');
42/// let heatmapBlob = await heatmapResponse.blob();
43///
44/// // From https://konvajs.org/docs/shapes/Image.html.
45/// var imageObj = new Image();
46/// imageObj.onload = function () {
47///     var heatmap = new Konva.Image({
48///         x: x_min,
49///         y: y_min,
50///         image: imageObj,
51///         width: map_width,
52///         height: map_height,
53///     });
54///     // add the shape to the layer
55///     layer.add(heatmap);
56///   };
57/// imageObj.src = URL.createObjectURL(heatmapBlob);
58/// ```
59///
60/// # Errors
61/// * If the connection to the database could not be established.
62#[utoipa::path(
63    context_path = "/api/maps/{map_id}/layers/plants",
64    params(
65        ("map_id" = i32, Path, description = "The id of the map the layer is on"),
66        HeatMapQueryParams
67    ),
68    responses(
69        (status = 200, description = "Returns the heatmap.", body = Vec<u8>, content_type = "image/png")
70    ),
71    security(
72        ("oauth2" = [])
73    )
74)]
75#[get("/heatmap")]
76pub async fn heatmap(
77    query_params: Query<HeatMapQueryParams>,
78    map_id: Path<i32>,
79    pool: SharedPool,
80) -> Result<HttpResponse> {
81    let response =
82        plant_layer::heatmap(map_id.into_inner(), query_params.into_inner(), &pool).await?;
83    Ok(HttpResponse::Ok().content_type("image/png").body(response))
84}
85
86/// Endpoint for finding spatial relations of a certain plant.
87///
88/// # Errors
89/// * If the connection to the database could not be established.
90#[utoipa::path(
91    context_path = "/api/maps/{map_id}/layers/plants",
92    params(
93        ("map_id" = i32, Path, description = "The id of the map the layer is on (TODO: currently unused)"),
94        RelationSearchParameters
95    ),
96    responses(
97        (status = 200, description = "Find relations to given plant", body = RelationsDto)
98    ),
99    security(
100        ("oauth2" = [])
101    )
102)]
103#[get("/relations")]
104pub async fn find_relations(
105    search_query: Query<RelationSearchParameters>,
106    pool: SharedPool,
107) -> Result<HttpResponse> {
108    let response = plant_layer::find_relations(search_query.into_inner(), &pool).await?;
109    Ok(HttpResponse::Ok().json(response))
110}