237 lines
7.6 KiB
Rust
237 lines
7.6 KiB
Rust
use std::collections::{BTreeMap, HashMap};
|
|
use std::io::Cursor;
|
|
use std::rc::Rc;
|
|
use std::time::Instant;
|
|
use gotham::{
|
|
helpers::http::response::create_response,
|
|
hyper::{Body, Response},
|
|
state::{FromState, State},
|
|
};
|
|
use geo::{LineString, Rect};
|
|
use mime::IMAGE_PNG;
|
|
use http::StatusCode;
|
|
use cairo::{ImageSurface, Context};
|
|
use crate::{tile_style, AppState, 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.))
|
|
}
|
|
}
|
|
|
|
struct Shape {
|
|
style: &'static tile_style::Style,
|
|
path: LineString<f64>,
|
|
excludes: Rc<Vec<LineString<f64>>>,
|
|
}
|
|
|
|
pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|
let mut times = vec![Instant::now()];
|
|
let te = TileExtractor::borrow_from(&state);
|
|
let zoom = te.zoom;
|
|
let bounds = te.to_rect();
|
|
let app_state = AppState::borrow_from(&state);
|
|
|
|
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<i32, Vec<Shape>> = BTreeMap::new();
|
|
|
|
times.push(Instant::now());
|
|
let ways_result = app_state.with_db(|db|
|
|
db.query("SELECT geo, attrs FROM osm_ways WHERE box(polygon(pclose(geo))) && $1::box", &[
|
|
&bounds.grow(1.2)
|
|
]).unwrap()
|
|
);
|
|
times.push(Instant::now());
|
|
for row in ways_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::<HashMap<String, String>>()
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
tile_style::find(&zoom, &tags)
|
|
.map(|style| {
|
|
let path: LineString<f64> = row.get(0);
|
|
let shape = Shape { style, path: path.clone(), excludes: Rc::new(vec![]) };
|
|
shapes.entry(style.z_index)
|
|
.or_default()
|
|
.push(shape);
|
|
});
|
|
}
|
|
times.push(Instant::now());
|
|
let multis_result = app_state.with_db(|db| {
|
|
let multi_members = db.query("SELECT id, m_role, path(m_geo) FROM osm_multipolygon_members WHERE box(m_geo) && $1::box", &[
|
|
&bounds.grow(1.2)
|
|
]).unwrap();
|
|
let mut members_by_id: HashMap<i64, Vec<(String, LineString<f64>)>> = HashMap::new();
|
|
for row in multi_members {
|
|
members_by_id.entry(row.get(0))
|
|
.or_default()
|
|
.push((row.get(1), row.get(2)));
|
|
}
|
|
let ids: Vec<i64> = members_by_id.keys()
|
|
.cloned()
|
|
.collect();
|
|
let multis = db.query("SELECT id, attrs FROM osm_multipolygons WHERE id=ANY($1)", &[&ids])
|
|
.unwrap();
|
|
multis.into_iter().map(move |row| {
|
|
let id = row.get(0);
|
|
let attrs = row.get(1);
|
|
let members = members_by_id.remove(&id).unwrap();
|
|
(id, attrs, members)
|
|
})
|
|
});
|
|
times.push(Instant::now());
|
|
let mut multis_count = 0usize;
|
|
for (_id, attrs, members) in multis_result {
|
|
multis_count += 1;
|
|
|
|
let tags =
|
|
if let serde_json::Value::Object(tags) = attrs {
|
|
tags.into_iter()
|
|
.filter_map(|(k, v)| if let serde_json::Value::String(s) = v {
|
|
Some((k, s))
|
|
} else {
|
|
None
|
|
})
|
|
.collect::<HashMap<String, String>>()
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
tile_style::find(&zoom, &tags)
|
|
.map(|style| {
|
|
let filter_members = |target_role| members.iter().filter_map(move |(m_role, m_geo)| {
|
|
if m_role == target_role {
|
|
Some(m_geo)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
let inners: Rc<Vec<_>> = Rc::new(
|
|
filter_members("inner")
|
|
.cloned()
|
|
.collect()
|
|
);
|
|
for path in filter_members("outer") {
|
|
// TODO: expensive?
|
|
let inners = inners.clone();
|
|
let shape = Shape { style, path: path.clone(), excludes: inners };
|
|
shapes.entry(style.z_index)
|
|
.or_default()
|
|
.push(shape);
|
|
}
|
|
});
|
|
}
|
|
|
|
times.push(Instant::now());
|
|
for shapes in shapes.values() {
|
|
for Shape { style, path, excludes } in shapes {
|
|
pctx.ctx.save().unwrap();
|
|
|
|
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();
|
|
if ! excludes.is_empty() {
|
|
pctx.ctx.clip_preserve();
|
|
for exclude in excludes.iter() {
|
|
pctx.create_path(exclude);
|
|
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();
|
|
}
|
|
}
|
|
|
|
times.push(Instant::now());
|
|
let mut buffer = Cursor::new(vec![]);
|
|
s.write_to_png(&mut buffer).unwrap();
|
|
times.push(Instant::now());
|
|
let res = create_response(&state, StatusCode::OK, IMAGE_PNG, buffer.into_inner());
|
|
times.push(Instant::now());
|
|
let mut last_time = None;
|
|
let mut periods = vec![];
|
|
for time in times {
|
|
if let Some(last_time) = last_time {
|
|
periods.push(time - last_time);
|
|
}
|
|
last_time = Some(time);
|
|
}
|
|
println!("get_tile@{} {} ways {} multipolygons {} shapes {:?}", zoom, "unknown" /*ways_result.len()*/, multis_count, shapes.values().map(|shapes| shapes.len()).sum::<usize>(), periods);
|
|
(state, res)
|
|
}
|
|
|
|
struct PaintContext {
|
|
ctx: Context,
|
|
bounds: Rect<f64>,
|
|
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<f64>) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|