server tiles: add labels
This commit is contained in:
parent
1b23f8c71d
commit
fe5e68736f
|
@ -34,6 +34,7 @@ pub struct Style {
|
|||
pub z_index: i32,
|
||||
pub stroke: Option<(f64, Color)>,
|
||||
pub fill: Option<Color>,
|
||||
pub label: Option<f64>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
|
@ -41,6 +42,7 @@ impl Style {
|
|||
z_index: 0,
|
||||
stroke: None,
|
||||
fill: None,
|
||||
label: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,7 +51,7 @@ const STYLES: &[(Selector, Style)] = &[
|
|||
Style { z_index: -10, ..Style::DEFAULT }),
|
||||
|
||||
(Selector::TagEquals("landuse", "forest"),
|
||||
Style { z_index: -8, fill: Some((0.2, 0.7, 0.2, 0.8)), ..Style::DEFAULT }),
|
||||
Style { z_index: -8, fill: Some((0.2, 0.7, 0.2, 0.8)), label: Some(20.), ..Style::DEFAULT }),
|
||||
(Selector::TagEquals("leisure", "park"),
|
||||
Style { z_index: -8, fill: Some((0.3, 0.7, 0.3, 0.8)), ..Style::DEFAULT }),
|
||||
(Selector::TagEquals("landuse", "meadow"),
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
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::{Rect, LineString};
|
||||
use geo::{LineString, Point, Rect};
|
||||
use mime::IMAGE_PNG;
|
||||
use http::StatusCode;
|
||||
use cairo::{ImageSurface, Context};
|
||||
|
@ -26,17 +27,54 @@ impl Grow for Rect<f64> {
|
|||
}
|
||||
}
|
||||
|
||||
fn bounding_box(path: &LineString<f64>) -> Option<Rect<f64>> {
|
||||
let mut x1 = None;
|
||||
let mut x2 = None;
|
||||
let mut y1 = None;
|
||||
let mut y2 = None;
|
||||
for point in path.points_iter() {
|
||||
#[inline]
|
||||
fn update<F: FnOnce(f64) -> bool>(m: &mut Option<f64>, n: f64, f: F) {
|
||||
match *m {
|
||||
None => *m = Some(n),
|
||||
Some(ref mut o) => {
|
||||
if f(*o) {
|
||||
*m = Some(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let x = point.x();
|
||||
let y = point.y();
|
||||
update(&mut x1, x, |x1| x < x1);
|
||||
update(&mut y1, y, |y1| y < y1);
|
||||
update(&mut x2, x, |x2| x > x2);
|
||||
update(&mut y2, y, |y2| y > y2);
|
||||
}
|
||||
Some(Rect::new((x1?, y1?), (x2?, y2?)))
|
||||
}
|
||||
|
||||
fn center(rect: &Rect<f64>) -> Point<f64> {
|
||||
((rect.min() + rect.max()) / 2.).into()
|
||||
}
|
||||
|
||||
struct Shape {
|
||||
style: &'static tile_style::Style,
|
||||
path: LineString<f64>,
|
||||
excludes: Rc<Vec<LineString<f64>>>,
|
||||
}
|
||||
|
||||
struct Label {
|
||||
font_size: f64,
|
||||
pos: Point<f64>,
|
||||
text: String,
|
||||
}
|
||||
|
||||
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();
|
||||
println!("get_tile@{} {:?}", zoom, bounds);
|
||||
let app_state = AppState::borrow_from(&state);
|
||||
|
||||
const TILE_SIZE: i32 = 256;
|
||||
|
@ -46,6 +84,13 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
ctx.set_antialias(cairo::Antialias::Fast);
|
||||
ctx.set_line_join(cairo::LineJoin::Round);
|
||||
ctx.set_line_cap(cairo::LineCap::Butt);
|
||||
|
||||
let mut font_options = cairo::FontOptions::new().unwrap();
|
||||
font_options.set_antialias(cairo::Antialias::Good);
|
||||
font_options.set_hint_style(cairo::HintStyle::Full);
|
||||
font_options.set_hint_metrics(cairo::HintMetrics::On);
|
||||
ctx.set_font_options(&font_options);
|
||||
|
||||
// background
|
||||
ctx.set_source_rgba(0.5, 0.5, 0.5, 1.);
|
||||
ctx.fill().unwrap();
|
||||
|
@ -57,36 +102,39 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
bounds,
|
||||
};
|
||||
let mut shapes: BTreeMap<i32, Vec<Shape>> = BTreeMap::new();
|
||||
let mut labels = vec![];
|
||||
|
||||
let result = app_state.with_db(|db|
|
||||
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()
|
||||
);
|
||||
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::<HashMap<String, String>>()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
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, excludes: Rc::new(vec![]) };
|
||||
let shape = Shape { style, path: path.clone(), excludes: Rc::new(vec![]) };
|
||||
shapes.entry(style.z_index)
|
||||
.or_default()
|
||||
.push(shape);
|
||||
});
|
||||
}
|
||||
let result = app_state.with_db(|db| {
|
||||
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();
|
||||
|
@ -108,7 +156,11 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
(id, attrs, members)
|
||||
})
|
||||
});
|
||||
for (_id, attrs, members) in result {
|
||||
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()
|
||||
|
@ -130,19 +182,37 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
} else {
|
||||
None
|
||||
}
|
||||
}).cloned();
|
||||
let inners: Rc<Vec<_>> = Rc::new(filter_members("inner").collect());
|
||||
});
|
||||
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, excludes: inners };
|
||||
let shape = Shape { style, path: path.clone(), excludes: inners };
|
||||
shapes.entry(style.z_index)
|
||||
.or_default()
|
||||
.push(shape);
|
||||
|
||||
if let Some(bbox) = bounding_box(&path) {
|
||||
let center = center(&bbox);
|
||||
style.label.and_then(|font_size| {
|
||||
tags.get("name:de")
|
||||
.or(tags.get("name"))
|
||||
.map(|text| labels.push(Label {
|
||||
font_size,
|
||||
pos: center,
|
||||
text: text.to_string(),
|
||||
}))
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
times.push(Instant::now());
|
||||
for shapes in shapes.values() {
|
||||
for Shape { style, path, excludes } in shapes {
|
||||
pctx.ctx.save().unwrap();
|
||||
|
@ -179,9 +249,37 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
}
|
||||
}
|
||||
|
||||
times.push(Instant::now());
|
||||
|
||||
for Label { font_size, pos, text } in labels.into_iter() {
|
||||
let (x, y) = pctx.lonlat_to_xy(pos.x(), pos.y());
|
||||
let te = if let Ok(te) = pctx.ctx.text_extents(&text) {
|
||||
te
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
pctx.ctx.move_to(x - te.width / 2., y - te.height / 2.);
|
||||
pctx.ctx.set_source_rgba(0., 0., 0., 1.);
|
||||
pctx.ctx.set_font_size(font_size);
|
||||
let _ = pctx.ctx.show_text(&text);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue