Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Backend heatmap #566

Merged
merged 44 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
863fec3
simply heatmap returning matrix of 0.5
kitzbergerg Jul 3, 2023
0db085b
only calculate score for intersection with map_polygon, add TODOs
kitzbergerg Jul 3, 2023
f3e4200
update utoipa API doc
kitzbergerg Jul 3, 2023
ba0839a
move heatmap to plant_layer
kitzbergerg Jul 3, 2023
fde4b78
add rustdoc
kitzbergerg Jul 3, 2023
0eaff84
return image from heatmap endpoint, throw error on non existant map i…
kitzbergerg Jul 6, 2023
81ecd3a
fix clippy errors
kitzbergerg Jul 6, 2023
c146127
set geometry of map in frontend
kitzbergerg Jul 6, 2023
5a75959
rename map_geom field to geometry
kitzbergerg Jul 6, 2023
bc75d9c
fix tests by adding geometry to maps
kitzbergerg Jul 6, 2023
7a0fe79
add test for heatmap
kitzbergerg Jul 6, 2023
32064ed
Merge branch 'master' into backend_heatmap
kitzbergerg Jul 6, 2023
878e39e
fix error in frontend
kitzbergerg Jul 6, 2023
fbe0f5f
use bounding box and granularity to calculate heatmap
kitzbergerg Jul 8, 2023
b0f9ce4
add colors to heatmap, fix index out of bounds
kitzbergerg Jul 8, 2023
1098e1f
add more detailed heatmap tests
kitzbergerg Jul 8, 2023
9530eba
fix clippy errors
kitzbergerg Jul 8, 2023
3a86842
add score based on plants relation on layer
kitzbergerg Jul 8, 2023
c839a35
refactor SQL queries and add better doc
kitzbergerg Jul 8, 2023
c8204ef
fix tests and add negative test
kitzbergerg Jul 8, 2023
0b5c826
add heatmap to changelog
kitzbergerg Jul 8, 2023
e4e0052
swap coordinate system to be (0,0) at top left
kitzbergerg Jul 9, 2023
fd28fba
adjust heatmap down.sql
kitzbergerg Jul 9, 2023
6a77ade
Merge branch 'master' into backend_heatmap
kitzbergerg Jul 11, 2023
e9070da
add test protocol for manual test to doc
kitzbergerg Jul 11, 2023
6ca9089
update test protocol
kitzbergerg Jul 11, 2023
b555522
update test protocol according to suggestions
kitzbergerg Jul 14, 2023
fb6c459
update frontend according to suggestions
kitzbergerg Jul 14, 2023
aaa1919
update heatmap sql according to suggestions
kitzbergerg Jul 14, 2023
c012c33
add more detailed heatmap endpoint description
kitzbergerg Jul 14, 2023
f302321
use integers instead of floats for bounding box and granularity
kitzbergerg Jul 14, 2023
0df1e8e
improve rustdoc error message on heatmap
kitzbergerg Jul 14, 2023
fae9e89
Merge branch 'master' of github.com:ElektraInitiative/PermaplanT into…
kitzbergerg Jul 14, 2023
7f7357a
improve rustdoc error message on heatmap in service
kitzbergerg Jul 14, 2023
21235f9
add doc about how to update the schema.patch file
kitzbergerg Jul 14, 2023
cd9e36a
fix mdbook error
kitzbergerg Jul 14, 2023
35078f2
add doc about the coordinate system
kitzbergerg Jul 15, 2023
bd2d722
move coordinate system doc to solution
kitzbergerg Jul 15, 2023
439ba82
allow for updating the maps geometry
kitzbergerg Jul 15, 2023
1646929
change values in SQL to floats
kitzbergerg Jul 15, 2023
a6ee00f
change from brown to grey for low scores in heatmap
kitzbergerg Jul 15, 2023
934edc6
fix clippy errors
kitzbergerg Jul 15, 2023
aa97b0d
Merge branch 'master' into backend_heatmap
kitzbergerg Jul 15, 2023
652904e
set geometry of UpdateMap as Option in typeshare
kitzbergerg Jul 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/migrations/2023-07-03-165000_heatmap/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP FUNCTION calculate_score;
ALTER TABLE maps DROP COLUMN map_geom;
30 changes: 30 additions & 0 deletions backend/migrations/2023-07-03-165000_heatmap/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Your SQL goes here
ALTER TABLE maps ADD COLUMN map_geom GEOMETRY(POLYGON, 4326) NOT NULL;

CREATE OR REPLACE FUNCTION calculate_score(map_id INTEGER, plant_id INTEGER, num_rows INTEGER, num_cols INTEGER)
RETURNS TABLE(score FLOAT, x INTEGER, y INTEGER) AS $$
DECLARE
map_geometry GEOMETRY(POLYGON, 4326);
cell GEOMETRY;
BEGIN
SELECT map_geom FROM maps WHERE id = map_id INTO map_geometry;

-- TODO: include plant in calculation (e.g. for shade)
FOR i IN 0..num_rows-1 LOOP
FOR j IN 0..num_cols-1 LOOP
cell := ST_Translate(ST_GeomFromEWKT('SRID=4326;POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'), i, j);

IF ST_Intersects(cell, map_geometry) THEN
-- TODO: calculate score
score := 0.5;
ELSE
score := 0.0;
END IF;
x := i;
y := j;

RETURN NEXT;
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql;
16 changes: 7 additions & 9 deletions backend/src/config/api_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use actix_web::web;
use utoipa::{
openapi::security::{AuthorizationCode, Flow, OAuth2, Password, Scopes, SecurityScheme},
openapi::security::{AuthorizationCode, Flow, OAuth2, Scopes, SecurityScheme},
Modify, OpenApi,
};
use utoipa_swagger_ui::SwaggerUi;
Expand Down Expand Up @@ -72,6 +72,7 @@ struct PlantsApiDoc;
#[derive(OpenApi)]
#[openapi(
paths(
map::heatmap,
map::find,
map::find_by_id,
map::create
Expand Down Expand Up @@ -186,14 +187,11 @@ impl Modify for SecurityAddon {
let components = openapi.components.as_mut().unwrap();

let config = &Config::get().openid_configuration;
let oauth2 = OAuth2::new([
Flow::AuthorizationCode(AuthorizationCode::new(
config.authorization_endpoint.clone(),
config.token_endpoint.clone(),
Scopes::new(),
)),
Flow::Password(Password::new(config.token_endpoint.clone(), Scopes::new())),
]);
let oauth2 = OAuth2::new([Flow::AuthorizationCode(AuthorizationCode::new(
config.authorization_endpoint.clone(),
config.token_endpoint.clone(),
Scopes::new(),
))]);
components.add_security_scheme("oauth2", SecurityScheme::OAuth2(oauth2));
}
}
1 change: 1 addition & 0 deletions backend/src/config/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
)
.service(
web::scope("/maps")
.service(map::heatmap)
.service(map::find)
.service(map::find_by_id)
.service(map::create)
Expand Down
24 changes: 23 additions & 1 deletion backend/src/controller/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,31 @@ use actix_web::{

use crate::config::auth::user_info::UserInfo;
use crate::config::data::AppDataInner;
use crate::model::dto::{MapSearchParameters, PageParameters};
use crate::model::dto::{HeatMapQueryParams, MapSearchParameters, PageParameters};
use crate::{model::dto::NewMapDto, service};

#[utoipa::path(
context_path = "/api/maps",
kitzbergerg marked this conversation as resolved.
Show resolved Hide resolved
params(
HeatMapQueryParams
),
responses(
(status = 200, description = "Generate a heatmap to find ideal locations to plant the plant", body = Vec<Vec<f64>>)
),
security(
("oauth2" = [])
)
)]
#[get("/heatmap")]
pub async fn heatmap(
query_params: Query<HeatMapQueryParams>,
app_data: Data<AppDataInner>,
) -> Result<HttpResponse> {
// TODO: figure out where to put endpoint
let response = service::map::heatmap(query_params.into_inner(), &app_data).await?;
Ok(HttpResponse::Ok().json(response))
}

/// Endpoint for fetching or searching all [`Map`](crate::model::entity::Map).
/// Search parameters are taken from the URLs query string (e.g. .../api/maps?is_inactive=false&per_page=5).
/// If no page parameters are provided, the first page is returned.
Expand Down
7 changes: 7 additions & 0 deletions backend/src/model/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,10 @@ pub enum SuggestionType {
/// Suggests plants based on diversity criteria.
Diversity,
}

#[typeshare]
#[derive(Debug, Deserialize, IntoParams)]
pub struct HeatMapQueryParams {
pub map_id: i32,
pub plant_id: i32,
}
31 changes: 31 additions & 0 deletions backend/src/model/dto/new_map_impl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Contains the implementation of [`NewMapDto`].

use postgis_diesel::types::{Point, Polygon};
use uuid::Uuid;

use crate::model::entity::NewMap;
Expand All @@ -8,8 +9,38 @@ use super::NewMapDto;

impl From<(NewMapDto, Uuid)> for NewMap {
fn from((new_map, owner_id): (NewMapDto, Uuid)) -> Self {
let mut map_geom = Polygon::new(Some(4326));
map_geom.add_points(vec![
Point {
x: 0.0,
y: 0.0,
srid: None,
},
Point {
x: 5.0,
y: 0.0,
srid: None,
},
Point {
x: 5.0,
y: 5.0,
srid: None,
},
Point {
x: 0.0,
y: 5.0,
srid: None,
},
Point {
x: 0.0,
y: 0.0,
srid: None,
},
]);

Self {
name: new_map.name,
map_geom,
creation_date: new_map.creation_date,
deletion_date: new_map.deletion_date,
last_visit: new_map.last_visit,
Expand Down
5 changes: 5 additions & 0 deletions backend/src/model/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use chrono::NaiveDateTime;
use diesel::QueryableByName;
use diesel::{Identifiable, Insertable, Queryable};
use postgis_diesel::types::Point;
use postgis_diesel::types::Polygon;
use uuid::Uuid;

use crate::schema::{layers, maps, plants, seeds};
Expand Down Expand Up @@ -742,6 +743,8 @@ pub struct Map {
pub location: Option<Point>,
/// The id of the owner of the map.
pub owner_id: Uuid,
/// The geometry of the map.
pub map_geom: Polygon<Point>,
}

/// The `NewMap` entity.
Expand Down Expand Up @@ -774,6 +777,8 @@ pub struct NewMap {
pub location: Option<Point>,
/// The id of the owner of the map.
pub owner_id: Uuid,
/// The geometry of the map.
pub map_geom: Polygon<Point>,
}

/// The `Layer` entity.
Expand Down
39 changes: 37 additions & 2 deletions backend/src/model/entity/map_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

use diesel::dsl::sql;
use diesel::pg::Pg;
use diesel::sql_types::Float;
use diesel::sql_types::{Double, Float, Integer};
use diesel::{
debug_query, BoolExpressionMethods, ExpressionMethods, PgTextExpressionMethods, QueryDsl,
QueryResult,
QueryResult, QueryableByName,
};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use log::debug;
Expand All @@ -22,7 +22,42 @@ use crate::{

use super::{Map, NewMap};

#[derive(Debug, Clone, QueryableByName)]
pub struct HeatMapElement {
#[diesel(sql_type = Double)]
pub score: f64,
#[diesel(sql_type = Integer)]
pub x: i32,
#[diesel(sql_type = Integer)]
pub y: i32,
}

impl Map {
pub async fn heatmap(
map_id: i32,
plant_id: i32,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<Vec<f64>>> {
// TODO: Compute from the maps geometry
let num_rows = 10; // TODO: Calculate number of rows
let num_cols = 10; // TODO: Calculate number of columns

let query = diesel::sql_query("SELECT * FROM calculate_score($1, $2, $3, $4)")
.bind::<Integer, _>(map_id)
.bind::<Integer, _>(plant_id)
.bind::<Integer, _>(num_rows)
.bind::<Integer, _>(num_cols);

let result = query.load::<HeatMapElement>(conn).await?;

// TODO: figure out how to handle actual values (return matrix to frontend, create image, return matrix as binary?)
let mut heatmap = vec![vec![0.0; num_cols as usize]; num_rows as usize];
for HeatMapElement { score, x, y } in result {
heatmap[x as usize][y as usize] = score;
}
Ok(heatmap)
}

/// Get the top maps matching the search query.
///
/// Can be filtered by `is_inactive` and `owner_id` if provided in `search_parameters`.
Expand Down
23 changes: 14 additions & 9 deletions backend/src/schema.patch
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
diff --git a/backend/src/schema.rs b/backend/src/schema.rs
kitzbergerg marked this conversation as resolved.
Show resolved Hide resolved
index 16c1826d..d33481b0 100644
--- a/backend/src/schema.rs
+++ b/backend/src/schema.rs
@@ -14,16 +14,12 @@ pub mod sql_types {
pub struct Fertility;
--- src/schema.rs 2023-07-03 18:30:12.781088814 +0200
+++ "src/schema copy.rs" 2023-07-03 18:31:06.642898879 +0200
@@ -15,20 +15,12 @@

#[derive(diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "flower_type"))]
pub struct FlowerType;

- #[derive(diesel::sql_types::SqlType)]
#[derive(diesel::sql_types::SqlType)]
- #[diesel(postgres_type(name = "geography"))]
- pub struct Geography;
-
#[derive(diesel::sql_types::SqlType)]
- #[derive(diesel::sql_types::SqlType)]
- #[diesel(postgres_type(name = "geometry"))]
- pub struct Geometry;
-
- #[derive(diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "growth_rate"))]
pub struct GrowthRate;

#[derive(diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "herbaceous_or_woody"))]
@@ -86,14 +82,13 @@ diesel::table! {
pub struct HerbaceousOrWoody;
@@ -104,16 +96,15 @@
is_alternative -> Bool,
}
}

diesel::table! {
use postgis_diesel::sql_types::Geography;
+ use postgis_diesel::sql_types::Geometry;
use diesel::sql_types::*;
use super::sql_types::PrivacyOptions;
- use super::sql_types::Geography;
- use super::sql_types::Geometry;

maps (id) {
id -> Int4,
Expand Down
11 changes: 10 additions & 1 deletion backend/src/service/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use actix_web::web::Data;
use uuid::Uuid;

use crate::config::data::AppDataInner;
use crate::model::dto::{MapSearchParameters, Page};
use crate::model::dto::{HeatMapQueryParams, MapSearchParameters, Page};
use crate::model::dto::{NewLayerDto, PageParameters};
use crate::model::entity::Layer;
use crate::model::r#enum::layer_type::LayerType;
Expand All @@ -19,6 +19,15 @@ use crate::{
/// Defines which layers should be created when a new map is created.
const LAYER_TYPES: [LayerType; 2] = [LayerType::Base, LayerType::Plants];

pub async fn heatmap(
query_params: HeatMapQueryParams,
app_data: &Data<AppDataInner>,
) -> Result<Vec<Vec<f64>>, ServiceError> {
let mut conn = app_data.pool.get().await?;
let result = Map::heatmap(query_params.map_id, query_params.plant_id, &mut conn).await?;
Ok(result)
}

/// Search maps from the database.
///
/// # Errors
Expand Down