tiles: refactor into tile_style

This commit is contained in:
Astro 2021-08-27 19:26:09 +02:00
parent 490009bc42
commit 2374097d40
4 changed files with 143 additions and 62 deletions

View File

@ -24,6 +24,7 @@ use serde::Deserialize;
mod area;
mod trees;
mod tile_style;
mod tiles;
#[derive(Clone, StateData)]

86
server/src/tile_style.rs Normal file
View File

@ -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
}

View File

@ -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();
}

View File

@ -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: '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 30,
}).addTo(map);