tiles: refactor into tile_style
This commit is contained in:
parent
490009bc42
commit
2374097d40
|
@ -24,6 +24,7 @@ use serde::Deserialize;
|
|||
|
||||
mod area;
|
||||
mod trees;
|
||||
mod tile_style;
|
||||
mod tiles;
|
||||
|
||||
#[derive(Clone, StateData)]
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub enum Selector {
|
||||
MinZoom(i32),
|
||||
MaxZoom(i32),
|
||||
HasTag(&'static str),
|
||||
TagEquals(&'static str, &'static str),
|
||||
And(&'static [Selector]),
|
||||
Or(&'static [Selector]),
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
fn matches(&self, zoom: &i32, tags: &HashMap<String, String>) -> bool {
|
||||
match self {
|
||||
Selector::MinZoom(min_zoom) =>
|
||||
zoom >= min_zoom,
|
||||
Selector::MaxZoom(max_zoom) =>
|
||||
zoom <= max_zoom,
|
||||
Selector::HasTag(name) =>
|
||||
tags.contains_key(*name),
|
||||
Selector::TagEquals(name, value) =>
|
||||
tags.get(*name).map(|s| s.as_str()) == Some(*value),
|
||||
Selector::And(selectors) =>
|
||||
selectors.iter().all(|selector| selector.matches(zoom, tags)),
|
||||
Selector::Or(selectors) =>
|
||||
selectors.iter().any(|selector| selector.matches(zoom, tags)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Color = (f64, f64, f64, f64);
|
||||
|
||||
pub struct Style {
|
||||
pub z_index: i32,
|
||||
pub stroke: Option<(f64, Color)>,
|
||||
pub fill: Option<Color>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
const DEFAULT: Style = Style {
|
||||
z_index: 0,
|
||||
stroke: None,
|
||||
fill: None,
|
||||
};
|
||||
}
|
||||
|
||||
const STYLES: &[(Selector, Style)] = &[
|
||||
(Selector::HasTag("tunnel"),
|
||||
Style { z_index: -10, ..Style::DEFAULT }),
|
||||
|
||||
(Selector::TagEquals("highway", "motorway"),
|
||||
Style { z_index: 10, stroke: Some((60., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::TagEquals("highway", "trunk"),
|
||||
Style { z_index: 9, stroke: Some((40., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::TagEquals("highway", "primary"),
|
||||
Style { z_index: 8, stroke: Some((35., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::And(&[Selector::TagEquals("highway", "secondary"), Selector::MinZoom(12)]),
|
||||
Style { z_index: 7, stroke: Some((30., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::And(&[Selector::TagEquals("highway", "tertiary"), Selector::MinZoom(14)]),
|
||||
Style { z_index: 6, stroke: Some((20., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::And(&[Selector::TagEquals("highway", "residential"), Selector::MinZoom(16)]),
|
||||
Style { z_index: 5, stroke: Some((15., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::And(&[Selector::TagEquals("highway", "living_street"), Selector::MinZoom(16)]),
|
||||
Style { z_index: 4, stroke: Some((12., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
(Selector::And(&[Selector::HasTag("highway"), Selector::MinZoom(18)]),
|
||||
Style { z_index: 3, stroke: Some((10., (1., 1., 1., 1.))), ..Style::DEFAULT }),
|
||||
|
||||
(Selector::And(&[Selector::HasTag("railway"), Selector::MinZoom(10)]),
|
||||
Style { z_index: 20, stroke: Some((3., (0., 0., 0., 1.))), ..Style::DEFAULT }),
|
||||
|
||||
(Selector::Or(&[Selector::HasTag("waterway"), Selector::TagEquals("natural", "water")]),
|
||||
Style { z_index: -2, fill: Some((0.5, 0.5, 0.7, 1.)), ..Style::DEFAULT }),
|
||||
|
||||
(Selector::And(&[Selector::HasTag("building"), Selector::MinZoom(17)]),
|
||||
Style { z_index: 30, fill: Some((0.7, 0.7, 0.7, 1.)), stroke: Some((1., (0., 0., 0., 1.))), ..Style::DEFAULT }),
|
||||
];
|
||||
|
||||
pub fn find(zoom: &i32, tags: &HashMap<String, String>) -> Option<&'static Style> {
|
||||
for (selector, style) in STYLES {
|
||||
if selector.matches(zoom, tags) {
|
||||
return Some(&style);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io::Cursor;
|
||||
use gotham::{
|
||||
helpers::http::response::create_response,
|
||||
|
@ -11,7 +11,7 @@ use mime::{APPLICATION_JSON, IMAGE_PNG};
|
|||
use http::StatusCode;
|
||||
use chrono::{Local, NaiveDate};
|
||||
use cairo::{ImageSurface, Context};
|
||||
use crate::{AppState, AreaExtractor, IdExtractor, TileExtractor};
|
||||
use crate::{tile_style, AppState, AreaExtractor, IdExtractor, TileExtractor};
|
||||
|
||||
trait Grow {
|
||||
fn grow(&self, factor: f64) -> Self;
|
||||
|
@ -27,11 +27,16 @@ impl Grow for Rect<f64> {
|
|||
}
|
||||
}
|
||||
|
||||
struct Shape {
|
||||
style: &'static tile_style::Style,
|
||||
path: LineString<f64>,
|
||||
}
|
||||
|
||||
pub fn get_tile(state: State) -> (State, Response<Body>) {
|
||||
let te = TileExtractor::borrow_from(&state);
|
||||
let zoom = te.zoom;
|
||||
let bounds = te.to_rect();
|
||||
println!("get_tile {:?}", bounds);
|
||||
println!("get_tile@{} {:?}", zoom, bounds);
|
||||
let app_state = AppState::borrow_from(&state);
|
||||
let result = {
|
||||
let mut db = app_state.db.lock().unwrap();
|
||||
|
@ -40,11 +45,16 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
]).unwrap()
|
||||
};
|
||||
|
||||
const TILE_SIZE: i32 = 512;
|
||||
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,
|
||||
|
@ -52,9 +62,9 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
h: h as f64,
|
||||
bounds,
|
||||
};
|
||||
|
||||
|
||||
let mut shapes: BTreeMap<i32, Vec<Shape>> = BTreeMap::new();
|
||||
for row in result {
|
||||
let path: LineString<f64> = row.get(0);
|
||||
let tags =
|
||||
if let serde_json::Value::Object(tags) = row.get(1) {
|
||||
tags.into_iter()
|
||||
|
@ -67,18 +77,45 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
|||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
pctx.ctx.save().unwrap();
|
||||
if tags.contains_key(&"tunnel".to_string()) {
|
||||
// ignore tunnels
|
||||
} if tags.contains_key(&"highway".to_string()) {
|
||||
draw_highway(&pctx, &path, &tags);
|
||||
} else if tags.contains_key(&"waterway".to_string()) || tags.get(&"natural".to_string()).map(|s| s.as_str()) == Some("water") {
|
||||
draw_waterway(&pctx, &path, &tags);
|
||||
} else if zoom > 14 && tags.contains_key(&"building".to_string()) {
|
||||
draw_building(&pctx, &path, &tags);
|
||||
|
||||
tile_style::find(&zoom, &tags)
|
||||
.map(|style| {
|
||||
let path: LineString<f64> = 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();
|
||||
}
|
||||
pctx.ctx.restore().unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Cursor::new(vec![]);
|
||||
|
@ -111,48 +148,3 @@ impl PaintContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_highway(pctx: &PaintContext, path: &LineString<f64>, tags: &HashMap<String, String>) {
|
||||
// println!("highway: {:?} {:?}", path, tags);
|
||||
let size = match tags.get("highway").map(|s| s.as_str()) {
|
||||
Some("highway") => 60,
|
||||
Some("trunk") => 40,
|
||||
Some("primary") => 25,
|
||||
Some("secondary") => 15,
|
||||
Some("tertiary") => 10,
|
||||
Some("residential") => 8,
|
||||
Some("living_street") => 5,
|
||||
Some(highway) => {
|
||||
println!("highway: {}", highway);
|
||||
3
|
||||
}
|
||||
None => unreachable!(),
|
||||
};
|
||||
pctx.ctx.set_line_width(size as f64 / 350. / pctx.bounds.width());
|
||||
pctx.ctx.set_source_rgba(1., 1., 1., (size as f64 / 10.).min(1.));
|
||||
pctx.create_path(path);
|
||||
pctx.ctx.stroke().unwrap();
|
||||
}
|
||||
|
||||
fn draw_waterway(pctx: &PaintContext, path: &LineString<f64>, tags: &HashMap<String, String>) {
|
||||
pctx.ctx.set_source_rgba(0.2, 0.2, 0.8, 1.0);
|
||||
|
||||
pctx.create_path(path);
|
||||
if path.is_closed() {
|
||||
pctx.ctx.close_path();
|
||||
pctx.ctx.fill().unwrap();
|
||||
} else {
|
||||
pctx.ctx.stroke().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_building(pctx: &PaintContext, path: &LineString<f64>, tags: &HashMap<String, String>) {
|
||||
pctx.create_path(path);
|
||||
pctx.ctx.close_path();
|
||||
|
||||
pctx.ctx.set_source_rgba(0.4, 0.4, 0.4, 0.8);
|
||||
pctx.ctx.fill().unwrap();
|
||||
|
||||
pctx.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0);
|
||||
pctx.ctx.stroke().unwrap();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
const TREES_MIN_ZOOM = 17;
|
||||
|
||||
var map = L.map('map').setView([51.05, 13.75], TREES_MIN_ZOOM, {
|
||||
// maxZoom: 20,
|
||||
});
|
||||
|
||||
L.tileLayer('/tiles/{z}/{x}/{y}/tile.png', {
|
||||
attribution: '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
|
||||
maxZoom: 30,
|
||||
}).addTo(map);
|
||||
|
|
Loading…
Reference in New Issue