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}