use std::collections::{BTreeMap, HashMap}; use std::io::Cursor; use gotham::{ helpers::http::response::create_response, hyper::{Body, Response}, state::{FromState, State}, }; use geo::{Point, Rect, LineString}; use serde::Serialize; use mime::{APPLICATION_JSON, IMAGE_PNG}; use http::StatusCode; use chrono::{Local, NaiveDate}; use cairo::{ImageSurface, Context}; use crate::{tile_style, 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.)) } } struct Shape { style: &'static tile_style::Style, path: LineString, } pub fn get_tile(state: State) -> (State, Response) { let te = TileExtractor::borrow_from(&state); let zoom = te.zoom; let bounds = te.to_rect(); println!("get_tile@{} {:?}", zoom, bounds); let app_state = AppState::borrow_from(&state); let result = { let mut db = app_state.db.lock().unwrap(); db.query("SELECT geo, attrs FROM osm_ways WHERE box(polygon(pclose(geo))) && $1::box", &[ &bounds.grow(1.2) ]).unwrap() }; const TILE_SIZE: i32 = 256; let (w, h) = (TILE_SIZE, TILE_SIZE); let s = ImageSurface::create(cairo::Format::ARgb32, w, h).unwrap(); let ctx = Context::new(&s).unwrap(); ctx.set_antialias(cairo::Antialias::Fast); ctx.set_line_join(cairo::LineJoin::Round); ctx.set_line_cap(cairo::LineCap::Butt); // background ctx.set_source_rgba(0.5, 0.5, 0.5, 1.); ctx.fill().unwrap(); let pctx = PaintContext { ctx, w: w as f64, h: h as f64, bounds, }; let mut shapes: BTreeMap> = BTreeMap::new(); for row in result { let tags = if let serde_json::Value::Object(tags) = row.get(1) { tags.into_iter() .filter_map(|(k, v)| if let serde_json::Value::String(s) = v { Some((k, s)) } else { None }) .collect::>() } else { continue; }; tile_style::find(&zoom, &tags) .map(|style| { let path: LineString = row.get(0); let shape = Shape { style, path }; shapes.entry(style.z_index) .or_default() .push(shape); }); } for shapes in shapes.values() { for Shape { style, path } in shapes { pctx.ctx.save().unwrap(); // TODO: multipolygon relations if path.is_closed() { style.fill.map(|(r, g, b, a)| { pctx.ctx.set_source_rgba(r, g, b, a); pctx.create_path(path); pctx.ctx.close_path(); pctx.ctx.fill().unwrap(); }); } pctx.ctx.restore().unwrap(); pctx.ctx.save().unwrap(); style.stroke.map(|(size, (r, g, b, a))| { pctx.ctx.set_line_width(size as f64 / 600. / pctx.bounds.width()); pctx.ctx.set_source_rgba(r, g, b, a); pctx.create_path(path); pctx.ctx.stroke().unwrap(); }); pctx.ctx.restore().unwrap(); } } let mut buffer = Cursor::new(vec![]); s.write_to_png(&mut buffer).unwrap(); let res = create_response(&state, StatusCode::OK, IMAGE_PNG, buffer.into_inner()); (state, res) } struct PaintContext { ctx: Context, bounds: Rect, w: f64, h: f64, } impl PaintContext { pub fn lonlat_to_xy(&self, lon: f64, lat: f64) -> (f64, f64) { ((lon - self.bounds.min().x) * self.w / self.bounds.width(), (self.bounds.max().y - lat) * self.h / self.bounds.height()) } pub fn create_path(&self, path: &LineString) { for (i, point) in path.points_iter().enumerate() { let (x, y) = self.lonlat_to_xy(point.x(), point.y()); if i == 0 { self.ctx.move_to(x, y); } else { self.ctx.line_to(x, y); } } } }