tiles: refactor into tile_style
This commit is contained in:
parent
490009bc42
commit
2374097d40
|
@ -24,6 +24,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
mod area;
|
mod area;
|
||||||
mod trees;
|
mod trees;
|
||||||
|
mod tile_style;
|
||||||
mod tiles;
|
mod tiles;
|
||||||
|
|
||||||
#[derive(Clone, StateData)]
|
#[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 std::io::Cursor;
|
||||||
use gotham::{
|
use gotham::{
|
||||||
helpers::http::response::create_response,
|
helpers::http::response::create_response,
|
||||||
|
@ -11,7 +11,7 @@ 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, TileExtractor};
|
use crate::{tile_style, AppState, AreaExtractor, IdExtractor, TileExtractor};
|
||||||
|
|
||||||
trait Grow {
|
trait Grow {
|
||||||
fn grow(&self, factor: f64) -> Self;
|
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>) {
|
pub fn get_tile(state: State) -> (State, Response<Body>) {
|
||||||
let te = TileExtractor::borrow_from(&state);
|
let te = TileExtractor::borrow_from(&state);
|
||||||
let zoom = te.zoom;
|
let zoom = te.zoom;
|
||||||
let bounds = te.to_rect();
|
let bounds = te.to_rect();
|
||||||
println!("get_tile {:?}", bounds);
|
println!("get_tile@{} {:?}", zoom, bounds);
|
||||||
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();
|
||||||
|
@ -40,11 +45,16 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
||||||
]).unwrap()
|
]).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
const TILE_SIZE: i32 = 512;
|
const TILE_SIZE: i32 = 256;
|
||||||
let (w, h) = (TILE_SIZE, TILE_SIZE);
|
let (w, h) = (TILE_SIZE, TILE_SIZE);
|
||||||
let s = ImageSurface::create(cairo::Format::ARgb32, w, h).unwrap();
|
let s = ImageSurface::create(cairo::Format::ARgb32, w, h).unwrap();
|
||||||
let ctx = Context::new(&s).unwrap();
|
let ctx = Context::new(&s).unwrap();
|
||||||
ctx.set_antialias(cairo::Antialias::Fast);
|
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 {
|
let pctx = PaintContext {
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -52,9 +62,9 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
||||||
h: h as f64,
|
h: h as f64,
|
||||||
bounds,
|
bounds,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut shapes: BTreeMap<i32, Vec<Shape>> = BTreeMap::new();
|
||||||
for row in result {
|
for row in result {
|
||||||
let path: LineString<f64> = row.get(0);
|
|
||||||
let tags =
|
let tags =
|
||||||
if let serde_json::Value::Object(tags) = row.get(1) {
|
if let serde_json::Value::Object(tags) = row.get(1) {
|
||||||
tags.into_iter()
|
tags.into_iter()
|
||||||
|
@ -67,18 +77,45 @@ pub fn get_tile(state: State) -> (State, Response<Body>) {
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
pctx.ctx.save().unwrap();
|
tile_style::find(&zoom, &tags)
|
||||||
if tags.contains_key(&"tunnel".to_string()) {
|
.map(|style| {
|
||||||
// ignore tunnels
|
let path: LineString<f64> = row.get(0);
|
||||||
} if tags.contains_key(&"highway".to_string()) {
|
let shape = Shape { style, path };
|
||||||
draw_highway(&pctx, &path, &tags);
|
shapes.entry(style.z_index)
|
||||||
} else if tags.contains_key(&"waterway".to_string()) || tags.get(&"natural".to_string()).map(|s| s.as_str()) == Some("water") {
|
.or_default()
|
||||||
draw_waterway(&pctx, &path, &tags);
|
.push(shape);
|
||||||
} else if zoom > 14 && tags.contains_key(&"building".to_string()) {
|
});
|
||||||
draw_building(&pctx, &path, &tags);
|
}
|
||||||
|
|
||||||
|
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![]);
|
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;
|
const TREES_MIN_ZOOM = 17;
|
||||||
|
|
||||||
var map = L.map('map').setView([51.05, 13.75], TREES_MIN_ZOOM, {
|
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',
|
attribution: '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
|
||||||
maxZoom: 30,
|
maxZoom: 30,
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
Loading…
Reference in New Issue