diff --git a/server/src/main.rs b/server/src/main.rs index d881fbb..4f82fb5 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -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 { + Point::new(self.lon(), self.lat()) + } + + pub fn to_rect(&self) -> Rect { + 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::() + route.get("/heatmap/:zoom/:x/:y/tile.png") + .with_path_extractor::() .to(trees::get_heatmap); route.get("/area/:x/:y") .with_path_extractor::() diff --git a/server/src/trees.rs b/server/src/trees.rs index b113aaa..1469080 100644 --- a/server/src/trees.rs +++ b/server/src/trees.rs @@ -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 { + 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) { } pub fn get_heatmap(state: State) -> (State, Response) { - 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) { 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) { 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 { diff --git a/server/static/app.js b/server/static/app.js index 48c0ba1..a67b6d2 100644 --- a/server/static/app.js +++ b/server/static/app.js @@ -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",