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 | |
| 12 | use 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 | |
| 26 | use oxedyne_fe2o3_core::prelude::*; |
| 27 | |
| 28 | use std::fmt::Write as _; |
| 29 | |
| 30 | fn 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 | |
| 46 | fn 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 | |
| 59 | fn 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 | } |