use std::cmp::max;
use chrono::NaiveDate;
use diesel::{
debug_query,
pg::Pg,
sql_types::{Array, Date, Float, Integer, Uuid as SqlUuid},
CombineDsl, ExpressionMethods, QueryDsl, QueryResult, QueryableByName,
};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use log::{debug, trace};
use uuid::Uuid;
use crate::{
model::{
dto::{RelationDto, RelationSearchParameters, RelationsDto},
r#enum::spatial_relation_type::SpatialRelationType,
},
schema::spatial_relations,
};
#[derive(Debug, Clone, QueryableByName)]
struct BoundingBox {
#[diesel(sql_type = Integer)]
x_min: i32,
#[diesel(sql_type = Integer)]
y_min: i32,
#[diesel(sql_type = Integer)]
x_max: i32,
#[diesel(sql_type = Integer)]
y_max: i32,
}
#[derive(Debug, Clone, QueryableByName)]
struct HeatMapElement {
#[diesel(sql_type = Float)]
preference: f32,
#[diesel(sql_type = Float)]
relevance: f32,
#[diesel(sql_type = Integer)]
x: i32,
#[diesel(sql_type = Integer)]
y: i32,
}
#[allow(
clippy::cast_sign_loss, clippy::indexing_slicing, clippy::cast_possible_truncation, )]
pub async fn heatmap(
map_id: i32,
plant_layer_id: Uuid,
shade_layer_id: Uuid,
plant_id: i32,
date: NaiveDate,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<Vec<(f32, f32)>>> {
let bounding_box_query =
diesel::sql_query("SELECT * FROM calculate_bbox($1)").bind::<Integer, _>(map_id);
debug!("{}", debug_query::<Pg, _>(&bounding_box_query));
let bounding_box = bounding_box_query.get_result::<BoundingBox>(conn).await?;
let granularity = calculate_granularity(&bounding_box);
let query =
diesel::sql_query("SELECT * FROM calculate_heatmap($1, $2, $3, $4, $5, $6, $7, $8, $9)")
.bind::<Integer, _>(map_id)
.bind::<Array<SqlUuid>, _>(vec![plant_layer_id, shade_layer_id])
.bind::<Integer, _>(plant_id)
.bind::<Date, _>(date)
.bind::<Integer, _>(granularity)
.bind::<Integer, _>(bounding_box.x_min)
.bind::<Integer, _>(bounding_box.y_min)
.bind::<Integer, _>(bounding_box.x_max)
.bind::<Integer, _>(bounding_box.y_max);
debug!("{}", debug_query::<Pg, _>(&query));
let result = query.load::<HeatMapElement>(conn).await?;
let num_cols =
(f64::from(bounding_box.x_max - bounding_box.x_min) / f64::from(granularity)).floor();
let num_rows =
(f64::from(bounding_box.y_max - bounding_box.y_min) / f64::from(granularity)).floor();
let mut heatmap = vec![vec![(0.0, 0.0); num_cols as usize]; num_rows as usize];
for HeatMapElement {
preference,
relevance,
x,
y,
} in result
{
heatmap[y as usize][x as usize] = (preference, relevance);
}
trace!("{heatmap:#?}");
Ok(heatmap)
}
const NUMBER_OF_SQUARES: f64 = 10000.0;
fn calculate_granularity(bounding_box: &BoundingBox) -> i32 {
let width = bounding_box.x_max - bounding_box.x_min;
let height = bounding_box.y_max - bounding_box.y_min;
#[allow(clippy::cast_possible_truncation)] let granularity = (f64::from(width * height) / NUMBER_OF_SQUARES).sqrt() as i32;
max(1, granularity)
}
pub async fn find_relations(
search_query: RelationSearchParameters,
conn: &mut AsyncPgConnection,
) -> QueryResult<RelationsDto> {
let query = spatial_relations::table
.select((spatial_relations::plant2, spatial_relations::relation))
.filter(spatial_relations::plant1.eq(&search_query.plant_id))
.union(
spatial_relations::table
.select((spatial_relations::plant1, spatial_relations::relation))
.filter(spatial_relations::plant2.eq(&search_query.plant_id)),
);
debug!("{}", debug_query::<Pg, _>(&query));
let relations = query
.load::<(i32, SpatialRelationType)>(conn)
.await?
.into_iter()
.map(|(id, relation)| RelationDto { id, relation })
.collect();
Ok(RelationsDto {
id: search_query.plant_id,
relations,
})
}