Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_geom/examples/cells_svg.rs

4.6 KiB, 1 run

created by r1870400018:59714, 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 the cell grid on a globe and on a flat map, as an SVG.
2//!
3//! ```text
4//! cargo run -p oxedyne_fe2o3_geom --example cells_svg -- [--out cells.svg]
5//! ```
6//!
7//! The left of the picture is an orthographic globe turned to Perth, the right a Web Mercator
8//! strip of the whole world; both show the six cube faces in bold, level 2 in full and level 4
9//! over the globe's middle, all through [`Viewport::project_rings`], the routine a map draws
10//! with. Nothing here reads a file.
11
12use oxedyne_fe2o3_geom::{
13 cell::{
14 self,
15 Cell,
16 },
17 proj::{
18 EARTH_RADIUS_M,
19 Projection,
20 RingMode,
21 ScreenPaths,
22 Viewport,
23 },
24};
25
26use oxedyne_fe2o3_core::prelude::*;
27
28use std::fmt::Write as _;
29
30fn path_d(paths: &ScreenPaths, dx: f64, dy: f64) -> String {
31 let mut d = String::new();
32 for i in 0..paths.len() {
33 if let Some((pts, closed)) = paths.path(i) {
34 for (k, p) in pts.chunks(2).enumerate() {
35 let _ = write!(d, "{}{:.1} {:.1}", if k == 0 { "M" } else { "L" },
36 p[0] as f64 + dx, p[1] as f64 + dy);
37 }
38 if closed {
39 d.push('Z');
40 }
41 }
42 }
43 d
44}
45
46fn level(l: u8, segs: u32) -> Outcome<Vec<Vec<[f64; 3]>>> {
47 let n = 1u32 << l;
48 let mut out = Vec::new();
49 for face in 0..6u8 {
50 for i in 0..n {
51 for j in 0..n {
52 out.push(res!(Cell::from_face_ij(face, l, i, j)).outline(segs));
53 }
54 }
55 }
56 Ok(out)
57}
58
59fn main() -> Outcome<()> {
60 let mut out_path = "cells.svg".to_string();
61 let mut it = std::env::args().skip(1);
62 while let Some(a) = it.next() {
63 match a.as_str() {
64 "--out" => out_path = res!(it.next().ok_or_else(|| err!("--out wants a path."; Input, Missing))),
65 other => return Err(err!("Unknown argument {:?}.", other; Input, Invalid)),
66 }
67 }
68 let (gw, mw, h) = (560.0, 900.0, 560.0);
69 let globe = res!(Viewport::new(Projection::Orthographic, -31.9535, 115.8571, 0.0,
70 EARTH_RADIUS_M / 260.0, gw, h));
71 let flat = res!(Viewport::new(Projection::WebMercator, 0.0, 115.8571, 0.0,
72 std::f64::consts::TAU * EARTH_RADIUS_M / mw, mw, h));
73 let faces = res!(level(0, 32));
74 let l2 = res!(level(2, 8));
75 // Level 4 only over the middle of the globe, found with the covering the map would use.
76 let (c, _) = globe.bounding_cap();
77 let near = res!(cell::cover_cap(c, 0.6, 4, 2_000));
78 let l4: Vec<Vec<[f64; 3]>> = near.iter().map(|cell| cell.outline(4)).collect();
79 let home = res!(Cell::at(-31.9535, 115.8571, 4));
80
81 let mut s = String::new();
82 let (tw, th) = (gw + mw + 20.0, h + 50.0);
83 let _ = write!(s, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"{}\" \
84 viewBox=\"0 0 {} {}\">\n<rect width=\"100%\" height=\"100%\" fill=\"#f4f1ea\"/>\n\
85 <text x=\"10\" y=\"26\" font-family=\"sans-serif\" font-size=\"15\">Cube-sphere cells: \
86 the six faces (bold), level 2, level 4 near Perth ({} cells covered), and Perth's own \
87 level-4 cell (yellow)</text>\n", tw, th, tw, th, near.len());
88 for (view, dx) in [(globe, 0.0), (flat, gw + 20.0)] {
89 let dy = 50.0;
90 // Each panel is clipped to itself, since the flat map carries a margin off its edge.
91 let _ = write!(s, "<clipPath id=\"p{}\"><rect x=\"{}\" y=\"{}\" width=\"{}\" \
92 height=\"{}\"/></clipPath>\n<g clip-path=\"url(#p{})\">\n",
93 dx as u32, dx, dy, view.w, view.h, dx as u32);
94 if view.kind == Projection::Orthographic {
95 let _ = write!(s, "<circle cx=\"{}\" cy=\"{}\" r=\"260\" fill=\"#bcd9ea\"/>\n",
96 dx + gw / 2.0, dy + h / 2.0);
97 } else {
98 let _ = write!(s, "<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" fill=\"#bcd9ea\"/>\n",
99 dx, dy, mw, h);
100 }
101 let mut pick = ScreenPaths::new();
102 res!(view.project_rings(&[home.outline(8)], RingMode::Fill, 0.0, 0.3, &mut pick));
103 let _ = write!(s, "<path d=\"{}\" fill=\"#ffd400\" stroke=\"#b08f00\"/>\n", path_d(&pick, dx, dy));
104 for (rings, width, colour) in [(&l4, 0.5, "#5aa9bf"), (&l2, 0.8, "#0096b4"), (&faces, 2.0, "#004e63")] {
105 let mut out = ScreenPaths::new();
106 res!(view.project_rings(rings, RingMode::Outline, 0.0, 0.3, &mut out));
107 let _ = write!(s, "<path d=\"{}\" fill=\"none\" stroke=\"{}\" stroke-width=\"{}\"/>\n",
108 path_d(&out, dx, dy), colour, width);
109 }
110 s.push_str("</g>\n");
111 }
112 s.push_str("</svg>\n");
113 res!(std::fs::write(&out_path, s), File, Write);
114 println!("wrote {}", out_path);
115 Ok(())
116}