trees: add TileExtractor for heatmap
This commit is contained in:
parent
2a10109d7f
commit
d009f29756
|
@ -3,6 +3,7 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gotham_derive;
|
extern crate gotham_derive;
|
||||||
|
|
||||||
|
use core::f64::consts::PI;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
handler::assets::FileOptions,
|
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() {
|
fn main() {
|
||||||
const DB_URL: &str = "host=10.233.1.2 dbname=treeadvisor user=treeadvisor password=123";
|
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| {
|
let router = build_router(chain, pipelines, |route| {
|
||||||
route.get("/heatmap/:x1/:y1/:x2/:y2")
|
route.get("/heatmap/:zoom/:x/:y/tile.png")
|
||||||
.with_path_extractor::<AreaExtractor>()
|
.with_path_extractor::<TileExtractor>()
|
||||||
.to(trees::get_heatmap);
|
.to(trees::get_heatmap);
|
||||||
route.get("/area/:x/:y")
|
route.get("/area/:x/:y")
|
||||||
.with_path_extractor::<PointExtractor>()
|
.with_path_extractor::<PointExtractor>()
|
||||||
|
|
|
@ -5,13 +5,27 @@ use gotham::{
|
||||||
hyper::{Body, Response},
|
hyper::{Body, Response},
|
||||||
state::{FromState, State},
|
state::{FromState, State},
|
||||||
};
|
};
|
||||||
use geo::Point;
|
use geo::{Point, Rect};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use mime::{APPLICATION_JSON, IMAGE_PNG};
|
use mime::{APPLICATION_JSON, IMAGE_PNG};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use chrono::{Local, NaiveDate};
|
use chrono::{Local, NaiveDate};
|
||||||
use cairo::{ImageSurface, Context};
|
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)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Tree {
|
struct Tree {
|
||||||
|
@ -119,12 +133,12 @@ pub fn get_tree(state: State) -> (State, Response<Body>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_heatmap(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 app_state = AppState::borrow_from(&state);
|
||||||
let result = {
|
let result = {
|
||||||
let mut db = app_state.db.lock().unwrap();
|
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", &[
|
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()
|
]).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 age = planted.map(|planted| ((Local::today().naive_local() - planted).num_days()) / 365);
|
||||||
|
|
||||||
let (r, g, b) = temperature(1. - score);
|
let (r, g, b) = temperature(1. - score);
|
||||||
ctx.set_source_rgba(r, g, b, 0.1);
|
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 / pe.w();
|
let radius = 1.5 + (0.4 + age.unwrap_or(50) as f64 / 80.).min(1.4) * 0.02 / rect.width();
|
||||||
ctx.arc((point.x() - pe.x1) * (w as f64) / pe.w(), (pe.y2 - point.y()) * (h as f64) / pe.h(),
|
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);
|
radius, 0., 2. * core::f64::consts::PI);
|
||||||
ctx.fill().unwrap();
|
ctx.fill().unwrap();
|
||||||
}
|
}
|
||||||
|
@ -158,7 +172,7 @@ pub fn get_heatmap(state: State) -> (State, Response<Body>) {
|
||||||
fn temperature(mut x: f64) -> (f64, f64, f64) {
|
fn temperature(mut x: f64) -> (f64, f64, f64) {
|
||||||
x = x.max(0.).min(1.);
|
x = x.max(0.).min(1.);
|
||||||
|
|
||||||
const MAX: f64 = 0.85;
|
const MAX: f64 = 0.95;
|
||||||
if x < 0.4 {
|
if x < 0.4 {
|
||||||
(MAX * x / 0.4, MAX, 0.0)
|
(MAX * x / 0.4, MAX, 0.0)
|
||||||
} else if x < 0.8 {
|
} else if x < 0.8 {
|
||||||
|
|
|
@ -19,10 +19,10 @@ var HeatmapLayer = L.TileLayer.extend({
|
||||||
return ["", "heatmap", x1, y1, x2, y2].join("/");
|
return ["", "heatmap", x1, y1, x2, y2].join("/");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
(new HeatmapLayer({
|
L.tileLayer('/heatmap/{z}/{x}/{y}/tile.png', {
|
||||||
maxZoom: TREES_MIN_ZOOM - 1,
|
maxZoom: TREES_MIN_ZOOM - 1,
|
||||||
opacity: 0.4,
|
opacity: 0.8,
|
||||||
})).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
var treeIcon = L.icon({
|
var treeIcon = L.icon({
|
||||||
iconUrl: "tree.png",
|
iconUrl: "tree.png",
|
||||||
|
|
Loading…
Reference in New Issue