Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_geom/examples/mvt_svg.rs

5.3 KiB, 1 run

created by r1870400018:60367, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Draws a vector tile as an SVG, styled by class, to show what the decoder hands a painter.
2//!
3//! ```text
4//! cargo run -p oxedyne_fe2o3_geom --example mvt_svg -- --mvt TILE.mvt --zoom Z [--out tile.svg]
5//! ```
6//!
7//! The tile is a decompressed Mapbox Vector Tile in the Protomaps basemap schema. Features are
8//! sorted into classes by [`mvt::classify`] -- land use, water, buildings, roads by kind, rail
9//! -- and painted in rule order, which is the order a map paints them.
10
11use oxedyne_fe2o3_geom::mvt::{
12 self,
13 GeomKind,
14 StyleRule,
15};
16
17use oxedyne_fe2o3_core::prelude::*;
18
19use std::fmt::Write as _;
20
21// Class, fill, stroke, stroke width in pixels at 1024 across.
22const PAINT: [(u16, &str, &str, f64); 11] = [
23 (11, "#f4f1ea", "none", 0.0), // land, over the sea the background is
24 (1, "#dfe9cf", "none", 0.0), // parks and grass
25 (2, "#c9dcb8", "none", 0.0), // wood
26 (3, "#bcd9ea", "none", 0.0), // water
27 (4, "#e3ddd3", "#cfc6b8", 0.6), // buildings
28 (5, "none", "#e6e0d6", 1.2), // paths
29 (6, "none", "#ffffff", 2.4), // minor roads
30 (7, "none", "#f7e7b4", 4.0), // major roads
31 (8, "none", "#f3c98b", 5.5), // highways
32 (9, "none", "#9a9486", 1.4), // rail
33 (10, "none", "#8fb3c8", 1.2), // ferries
34];
35
36fn rule(layer: &str, values: &[&str], class: u16) -> StyleRule {
37 StyleRule {
38 layer: layer.to_string(),
39 key: "kind".to_string(),
40 values: values.iter().map(|v| v.to_string()).collect(),
41 min_zoom: 0,
42 class,
43 }
44}
45
46fn main() -> Outcome<()> {
47 let (mut path, mut out, mut zoom) = (None, "tile.svg".to_string(), 14u8);
48 let mut it = std::env::args().skip(1);
49 while let Some(a) = it.next() {
50 let v = res!(it.next().ok_or_else(|| err!("{} wants a value.", a; Input, Missing)));
51 match a.as_str() {
52 "--mvt" => path = Some(v),
53 "--out" => out = v,
54 "--zoom" => zoom = res!(v.parse::<u8>(), Input, Invalid),
55 other => return Err(err!("Unknown argument {:?}.", other; Input, Invalid)),
56 }
57 }
58 let path = res!(path.ok_or_else(|| err!("--mvt is needed."; Input, Missing)));
59 let bytes = res!(std::fs::read(&path), File, Read);
60 let tile = res!(mvt::decode(&bytes));
61 let rules = vec![
62 rule("earth", &["earth"], 11),
63 rule("landuse", &["park", "grass", "garden", "playground", "pitch", "golf_course", "cemetery"], 1),
64 rule("landuse", &["wood", "forest", "scrub"], 2),
65 rule("water", &[], 3),
66 rule("buildings", &[], 4),
67 rule("roads", &["path"], 5),
68 rule("roads", &["minor_road", "other"], 6),
69 rule("roads", &["major_road"], 7),
70 rule("roads", &["highway"], 8),
71 rule("roads", &["rail"], 9),
72 rule("roads", &["ferry"], 10),
73 ];
74 let hits = mvt::classify(&tile, &rules, zoom, Some("min_zoom"));
75 let size = 1024.0;
76 let mut s = String::new();
77 let _ = write!(s, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{0}\" height=\"{1}\" \
78 viewBox=\"0 0 {0} {1}\">\n<rect width=\"100%\" height=\"100%\" fill=\"#bcd9ea\"/>\n\
79 <clipPath id=\"t\"><rect width=\"{0}\" height=\"{0}\"/></clipPath>\n<g clip-path=\"url(#t)\">\n",
80 size, size + 30.0);
81 let mut drawn = 0usize;
82 for (class, fill, stroke, width) in PAINT.iter() {
83 let mut d = String::new();
84 for (c, li, fi) in &hits {
85 if c != class {
86 continue;
87 }
88 let layer = &tile.layers[*li];
89 let f = &layer.features[*fi];
90 // A fill class paints polygons and a stroke class lines: the water layer also
91 // carries river and strait lines for labels, which filled would paint wedges.
92 let fills = *fill != "none";
93 let fits = match f.kind {
94 GeomKind::Polygon => fills,
95 GeomKind::LineString => !fills,
96 _ => false,
97 };
98 if !fits {
99 continue;
100 }
101 let k = size / layer.extent as f64;
102 for ring in res!(f.geometry()) {
103 for (i, (x, y)) in ring.iter().enumerate() {
104 let _ = write!(d, "{}{:.1} {:.1}", if i == 0 { "M" } else { "L" }, *x as f64 * k, *y as f64 * k);
105 }
106 if f.kind == GeomKind::Polygon {
107 d.push('Z');
108 }
109 }
110 drawn += 1;
111 }
112 let _ = write!(s, "<path d=\"{}\" fill=\"{}\" stroke=\"{}\" stroke-width=\"{}\" \
113 stroke-linecap=\"round\" stroke-linejoin=\"round\" fill-rule=\"nonzero\"/>\n",
114 d, fill, stroke, width);
115 }
116 let _ = write!(s, "</g>\n<text x=\"8\" y=\"{}\" font-family=\"sans-serif\" font-size=\"14\">\
117 {} -- {} features drawn of {}, decoded by fe2o3_geom::mvt. Map data (c) OpenStreetMap \
118 contributors (ODbL), Protomaps build.</text>\n</svg>\n", size + 21.0,
119 std::path::Path::new(&path).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default(),
120 drawn, tile.layers.iter().map(|l| l.features.len()).sum::<usize>());
121 res!(std::fs::write(&out, s), File, Write);
122 println!("wrote {} ({} features drawn)", out, drawn);
123 Ok(())
124}