1use std::cmp::max;
4
5use crate::schema::sql_types::HeatmapColor as SqlHeatmapColor;
6use chrono::NaiveDate;
7use diesel::{
8 debug_query,
9 pg::Pg,
10 sql_types::{Array, Date, Float, Integer, Uuid as SqlUuid},
11 CombineDsl, ExpressionMethods, QueryDsl, QueryResult, QueryableByName,
12};
13use diesel_async::{AsyncPgConnection, RunQueryDsl};
14use log::{debug, trace};
15use uuid::Uuid;
16
17use crate::{
18 model::{
19 dto::{RelationDto, RelationSearchParameters, RelationsDto},
20 r#enum::{heatmap_color::HeatmapColor, spatial_relation_type::SpatialRelationType},
21 },
22 schema::spatial_relations,
23};
24
25#[derive(Debug, Clone, QueryableByName)]
27struct BoundingBox {
28 #[diesel(sql_type = Integer)]
30 x_min: i32,
31 #[diesel(sql_type = Integer)]
33 y_min: i32,
34 #[diesel(sql_type = Integer)]
36 x_max: i32,
37 #[diesel(sql_type = Integer)]
39 y_max: i32,
40}
41
42#[derive(Debug, Clone, QueryableByName)]
44struct HeatMapElement {
45 #[diesel(sql_type = SqlHeatmapColor)]
47 color: HeatmapColor,
48 #[diesel(sql_type = Float)]
50 relevance: f32,
51 #[diesel(sql_type = Integer)]
53 x: i32,
54 #[diesel(sql_type = Integer)]
56 y: i32,
57}
58
59#[allow(
66 clippy::cast_sign_loss, clippy::indexing_slicing, clippy::cast_possible_truncation, clippy::too_many_arguments, )]
71pub async fn heatmap(
72 map_id: i32,
73 plant_layer_id: Uuid,
74 shade_layer_id: Uuid,
75 hydrology_layer_id: Uuid,
76 soil_layer_id: Uuid,
77 plant_id: i32,
78 date: NaiveDate,
79 conn: &mut AsyncPgConnection,
80) -> QueryResult<Vec<Vec<(HeatmapColor, f32)>>> {
81 let bounding_box_query =
83 diesel::sql_query("SELECT * FROM calculate_bbox($1)").bind::<Integer, _>(map_id);
84 debug!("{}", debug_query::<Pg, _>(&bounding_box_query));
85 let bounding_box = bounding_box_query.get_result::<BoundingBox>(conn).await?;
86
87 let granularity = calculate_granularity(&bounding_box);
88
89 let query =
91 diesel::sql_query("SELECT * FROM calculate_heatmap($1, $2, $3, $4, $5, $6, $7, $8, $9)")
92 .bind::<Integer, _>(map_id)
93 .bind::<Array<SqlUuid>, _>(vec![
94 plant_layer_id,
95 shade_layer_id,
96 hydrology_layer_id,
97 soil_layer_id,
98 ])
99 .bind::<Integer, _>(plant_id)
100 .bind::<Date, _>(date)
101 .bind::<Integer, _>(granularity)
102 .bind::<Integer, _>(bounding_box.x_min)
103 .bind::<Integer, _>(bounding_box.y_min)
104 .bind::<Integer, _>(bounding_box.x_max)
105 .bind::<Integer, _>(bounding_box.y_max);
106 debug!("{}", debug_query::<Pg, _>(&query));
107 let result = query.load::<HeatMapElement>(conn).await?;
108
109 let num_cols =
112 (f64::from(bounding_box.x_max - bounding_box.x_min) / f64::from(granularity)).floor();
113 let num_rows =
114 (f64::from(bounding_box.y_max - bounding_box.y_min) / f64::from(granularity)).floor();
115 let mut heatmap = vec![vec![(HeatmapColor::Green, 0.0); num_cols as usize]; num_rows as usize];
116 for HeatMapElement {
117 color,
118 relevance,
119 x,
120 y,
121 } in result
122 {
123 heatmap[y as usize][x as usize] = (color, relevance);
124 }
125
126 trace!("{heatmap:#?}");
127 Ok(heatmap)
128}
129
130const NUMBER_OF_SQUARES: f64 = 10000.0;
132
133fn calculate_granularity(bounding_box: &BoundingBox) -> i32 {
135 let width = bounding_box.x_max - bounding_box.x_min;
136 let height = bounding_box.y_max - bounding_box.y_min;
137
138 #[allow(clippy::cast_possible_truncation)] let granularity = (f64::from(width * height) / NUMBER_OF_SQUARES).sqrt() as i32;
143
144 max(1, granularity)
145}
146
147pub async fn find_relations(
152 search_query: RelationSearchParameters,
153 conn: &mut AsyncPgConnection,
154) -> QueryResult<RelationsDto> {
155 let query = spatial_relations::table
156 .select((spatial_relations::plant2, spatial_relations::relation))
157 .filter(spatial_relations::plant1.eq(&search_query.plant_id))
158 .union(
159 spatial_relations::table
160 .select((spatial_relations::plant1, spatial_relations::relation))
161 .filter(spatial_relations::plant2.eq(&search_query.plant_id)),
162 );
163 debug!("{}", debug_query::<Pg, _>(&query));
164 let relations = query
165 .load::<(i32, SpatialRelationType)>(conn)
166 .await?
167 .into_iter()
168 .map(|(id, relation)| RelationDto { id, relation })
169 .collect();
170 Ok(RelationsDto {
171 id: search_query.plant_id,
172 relations,
173 })
174}