trees: add TileExtractor for heatmap

This commit is contained in:
Astro 2021-08-26 17:35:55 +02:00
parent 2a10109d7f
commit d009f29756
3 changed files with 62 additions and 13 deletions

View File

@ -3,6 +3,7 @@
#[macro_use]
extern crate gotham_derive;
use core::f64::consts::PI;
use std::sync::{Arc, Mutex};
use gotham::{
handler::assets::FileOptions,
@ -78,6 +79,40 @@ impl PointExtractor {
}
}
#[derive(Debug, Deserialize, StateData, StaticResponseExtender)]
pub struct TileExtractor {
zoom: i32,
x: u32,
y: u32,
}
impl TileExtractor {
fn lon(&self) -> f64 {
360. * self.x as f64 / 2f64.powi(self.zoom) - 180.
}
fn lat(&self) -> f64 {
(PI * (1f64 - 2f64 * self.y as f64 / 2f64.powi(self.zoom))).sinh().atan() * 180. / PI
}
pub fn south_west(&self) -> Self {
TileExtractor {
zoom: self.zoom,
x: self.x + 1,
y: self.y + 1,
}
}
pub fn to_point(&self) -> Point<f64> {
Point::new(self.lon(), self.lat())
}
pub fn to_rect(&self) -> Rect<f64> {
let sw = self.south_west();
Rect::new(self.to_point(), sw.to_point())
}
}
fn main() {
const DB_URL: &str = "host=10.233.1.2 dbname=treeadvisor user=treeadvisor password=123";
@ -94,8 +129,8 @@ fn main() {
)
);
let router = build_router(chain, pipelines, |route| {
route.get("/heatmap/:x1/:y1/:x2/:y2")
.with_path_extractor::<AreaExtractor>()
route.get("/heatmap/:zoom/:x/:y/tile.png")
.with_path_extractor::<TileExtractor>()
.to(trees::get_heatmap);
route.get("/area/:x/:y")
.with_path_extractor::<PointExtractor>()

View File

@ -5,13 +5,27 @@ use gotham::{
hyper::{Body, Response},
state::{FromState, State},
};
use geo::Point;
use geo::{Point, Rect};
use serde::Serialize;
use mime::{APPLICATION_JSON, IMAGE_PNG};
use http::StatusCode;
use chrono::{Local, NaiveDate};
use cairo::{ImageSurface, Context};
use crate::{AppState, AreaExtractor, IdExtractor};
use crate::{AppState, AreaExtractor, IdExtractor, TileExtractor};
trait Grow {
fn grow(&self, factor: f64) -> Self;
}
impl Grow for Rect<f64> {
fn grow(&self, factor: f64) -> Self {
let c = self.center();
let w = self.width();
let h = self.height();
Rect::new((c.x - factor * w / 2., c.y - factor * h / 2.),
(c.x + factor * w / 2., c.y + factor * h / 2.))
}
}
#[derive(Debug, Serialize)]
struct Tree {
@ -119,12 +133,12 @@ pub fn get_tree(state: State) -> (State, Response<Body>) {
}
pub fn get_heatmap(state: State) -> (State, Response<Body>) {
let pe = AreaExtractor::borrow_from(&state);
let rect = TileExtractor::borrow_from(&state).to_rect();
let app_state = AppState::borrow_from(&state);
let result = {
let mut db = app_state.db.lock().unwrap();
db.query("SELECT coord, score, planted FROM trees WHERE coord <@ $1::box AND SCORE IS NOT NULL", &[
&pe.grow(0.2).to_rect()
&rect.grow(1.2)
]).unwrap()
};
@ -141,9 +155,9 @@ pub fn get_heatmap(state: State) -> (State, Response<Body>) {
let age = planted.map(|planted| ((Local::today().naive_local() - planted).num_days()) / 365);
let (r, g, b) = temperature(1. - score);
ctx.set_source_rgba(r, g, b, 0.1);
let radius = 1.5 + (0.4 + age.unwrap_or(50) as f64 / 80.).min(1.4) * 0.02 / pe.w();
ctx.arc((point.x() - pe.x1) * (w as f64) / pe.w(), (pe.y2 - point.y()) * (h as f64) / pe.h(),
ctx.set_source_rgba(r, g, b, 0.5);
let radius = 1.5 + (0.4 + age.unwrap_or(50) as f64 / 80.).min(1.4) * 0.02 / rect.width();
ctx.arc((point.x() - rect.min().x) * (w as f64) / rect.width(), (rect.max().y - point.y()) * (h as f64) / rect.height(),
radius, 0., 2. * core::f64::consts::PI);
ctx.fill().unwrap();
}
@ -158,7 +172,7 @@ pub fn get_heatmap(state: State) -> (State, Response<Body>) {
fn temperature(mut x: f64) -> (f64, f64, f64) {
x = x.max(0.).min(1.);
const MAX: f64 = 0.85;
const MAX: f64 = 0.95;
if x < 0.4 {
(MAX * x / 0.4, MAX, 0.0)
} else if x < 0.8 {

View File

@ -19,10 +19,10 @@ var HeatmapLayer = L.TileLayer.extend({
return ["", "heatmap", x1, y1, x2, y2].join("/");
},
});
(new HeatmapLayer({
L.tileLayer('/heatmap/{z}/{x}/{y}/tile.png', {
maxZoom: TREES_MIN_ZOOM - 1,
opacity: 0.4,
})).addTo(map);
opacity: 0.8,
}).addTo(map);
var treeIcon = L.icon({
iconUrl: "tree.png",