Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_geom/tests/world.rs

8.9 KiB, 2 runs

created by r1870400018:59724, 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//! The world file and the spherical simplifier.
2//!
3//! The external check is byte identity with Ochre's `world.bin`, which Python wrote: run
4//! `cargo run --release -p oxedyne_fe2o3_geom --example world_gen -- --compare
5//! <ochre>/src/web/assets/world.bin` with Natural Earth cached. It needs the downloads, so it
6//! is not a unit test; what is here needs nothing but the coarse fixture that example wrote.
7
8use oxedyne_fe2o3_geom::{
9 proj::{
10 EARTH_RADIUS_M,
11 unit_vec,
12 },
13 world::{
14 self,
15 Label,
16 Layer,
17 LayerKind,
18 World,
19 MAGIC,
20 Q,
21 },
22};
23
24use oxedyne_fe2o3_core::prelude::*;
25
26use std::collections::HashSet;
27
28const WORLD: &[u8] = include_bytes!("data/world_coarse.bin");
29
30#[test]
31fn test_the_coarse_world_reads_back_as_the_world_00() -> Outcome<()> {
32 let w = res!(world::read(WORLD));
33 let named: Vec<&str> = w.layers.iter().map(|l| l.name.as_str()).collect();
34 req!(named, vec!["land", "lakes", "borders"]);
35 req!(w.layers[0].kind, LayerKind::Fill);
36 req!(w.layers[2].kind, LayerKind::Stroke);
37 req!(w.layers[0].tol_m, 30_000.0);
38 req!(w.details(), 1);
39 // Ochre's report for this level: 190 land rings of 4202 vertices, 11 lakes of 129, 183
40 // borders of 559.
41 for (layer, rings, verts) in [(0usize, 190usize, 4202usize), (1, 11, 129), (2, 183, 559)] {
42 let l = &w.layers[layer];
43 req!(l.rings.len(), rings, "{} has {} rings.", l.name, l.rings.len());
44 let n: usize = l.rings.iter().map(|r| r.len()).sum();
45 req!(n, verts, "{} has {} vertices.", l.name, n);
46 for r in &l.rings {
47 for (lat, lng) in r {
48 let real = lat.abs() <= 90.0 && lng.abs() <= 540.0;
49 req!(real, true, "{}, {} is not on the Earth.", lat, lng);
50 }
51 }
52 }
53 // And writing what was read gives the same bytes.
54 let again = res!(world::write(&w));
55 req!((again == WORLD), true, "The world did not write back as it was read.");
56 // Reading one level alone gives that level.
57 let only = res!(world::read_detail(WORLD, Some(0)));
58 req!(only, w);
59 let none = res!(world::read_detail(WORLD, Some(1)));
60 req!(none.layers.len(), 0);
61 Ok(())
62}
63
64#[test]
65fn test_every_position_written_is_a_source_position_01() -> Outcome<()> {
66 // A ring with steps long enough to need a jump, one across the antimeridian, and a vertex
67 // that rounds onto its neighbour.
68 let ring = vec![
69 (10.0, 170.0), (10.4, 179.9), (10.4, -179.9), (-5.0, -170.0),
70 (-5.000_001, -170.000_001), (-20.0, 150.0), (10.0, 170.0),
71 ];
72 let w = World { layers: vec![Layer {
73 name: "land".to_string(), kind: LayerKind::Fill, detail: 0, tol_m: 1_000.0,
74 rings: vec![ring.clone()], labels: Vec::new(),
75 }]};
76 let bytes = res!(world::write(&w));
77 req!(&bytes[..8], &MAGIC[..]);
78 req!(u16::from_le_bytes([bytes[8], bytes[9]]), world::VERSION_RINGS);
79 let back = res!(world::read(&bytes));
80 let source: HashSet<(i64, i64)> = ring.iter()
81 .map(|(a, b)| ((a * Q).round() as i64, (b * Q).round() as i64))
82 .collect();
83 let got = &back.layers[0].rings[0];
84 req!(got.len(), ring.len() - 1, "The duplicate vertex was not dropped.");
85 for (lat, lng) in got {
86 let q = ((lat * Q).round() as i64, (lng * Q).round() as i64);
87 // Longitude may come back a turn away, walked the short way round.
88 let turned = (q.0, (q.1 + 180 * 100_000).rem_euclid(360 * 100_000) - 180 * 100_000);
89 let known = source.contains(&q) || source.contains(&turned);
90 req!(known, true, "{}, {} is not a position the ring was given.", lat, lng);
91 }
92 Ok(())
93}
94
95#[test]
96fn test_labels_round_trip_in_version_2_02() -> Outcome<()> {
97 let labels = vec![
98 Label { lat: -31.9535, lng: 115.8571, rank: 0, name: "Perth".to_string() },
99 Label { lat: 35.6895, lng: 139.6917, rank: 0, name: "東京".to_string() },
100 Label { lat: 64.8378, lng: -147.7164, rank: 4, name: "Fairbanks".to_string() },
101 ];
102 let mut w = res!(world::read(WORLD));
103 w.layers.push(Layer {
104 name: "places".to_string(), kind: LayerKind::Label, detail: 0, tol_m: 0.0,
105 rings: Vec::new(), labels: labels.clone(),
106 });
107 let bytes = res!(world::write(&w));
108 req!(u16::from_le_bytes([bytes[8], bytes[9]]), world::VERSION_LABELS);
109 let back = res!(world::read(&bytes));
110 req!(back, w);
111 let again = res!(world::write(&back));
112 req!((again == bytes), true, "A labelled world did not write back as it was read.");
113 let places = res!(back.layer("places", 0).ok_or_else(|| err!("No places."; Test)));
114 req!(places.labels[1].name.as_str(), "東京");
115 Ok(())
116}
117
118#[test]
119fn test_a_world_file_that_is_not_one_is_refused_03() -> Outcome<()> {
120 req!(world::read(b"nothing at all").is_err(), true, "Rubbish was accepted.");
121 let mut ochre = WORLD.to_vec();
122 ochre[..8].copy_from_slice(b"OCHRWRLD");
123 req!(world::read(&ochre).is_err(), true, "Another magic was accepted.");
124 let mut later = WORLD.to_vec();
125 later[8] = 9;
126 req!(world::read(&later).is_err(), true, "A later version was accepted.");
127 let cut = &WORLD[..WORLD.len() / 2];
128 req!(world::read(cut).is_err(), true, "A truncated file was accepted.");
129 let mut long = WORLD.to_vec();
130 long.push(0);
131 req!(world::read(&long).is_err(), true, "A file with a trailing byte was accepted.");
132 // A label layer in a version 1 file.
133 let mut mislabelled = WORLD.to_vec();
134 mislabelled[12] = 2;
135 req!(world::read(&mislabelled).is_err(), true, "Labels in version 1 were accepted.");
136 let named = World { layers: vec![Layer {
137 name: "coastline".to_string(), kind: LayerKind::Stroke, detail: 0, tol_m: 0.0,
138 rings: Vec::new(), labels: Vec::new(),
139 }]};
140 req!(world::write(&named).is_err(), true, "A name longer than seven bytes was written.");
141 Ok(())
142}
143
144#[test]
145fn test_the_simplifier_keeps_every_edge_within_its_tolerance_04() -> Outcome<()> {
146 // A wobbly line round a quarter of the equator: every dropped vertex lies within the
147 // tolerance of the chord between the kept vertices either side of it.
148 let n = 4_000;
149 let pts: Vec<[f64; 3]> = (0..n).map(|k| {
150 let t = k as f64 / n as f64;
151 unit_vec(0.3 * (t * 157.0).sin() + 0.05 * (t * 1_031.0).cos(), 90.0 * t)
152 }).collect();
153 for eps_m in [1_000.0, 6_000.0, 30_000.0] {
154 let keep = world::simplify_sphere(&pts, eps_m);
155 req!(keep[0], 0);
156 req!(keep[keep.len() - 1], n - 1);
157 let fewer = keep.len() < n && keep.len() > 2;
158 req!(fewer, true, "At {} m, {} of {} vertices were kept.", eps_m, keep.len(), n);
159 let eps = eps_m / EARTH_RADIUS_M;
160 for pair in keep.windows(2) {
161 let (a, b) = (pts[pair[0]], pts[pair[1]]);
162 let d = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
163 let dd = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
164 for k in pair[0] + 1..pair[1] {
165 let p = pts[k];
166 let t = (((p[0] - a[0]) * d[0] + (p[1] - a[1]) * d[1] + (p[2] - a[2]) * d[2]) / dd)
167 .clamp(0.0, 1.0);
168 let off = ((p[0] - a[0] - t * d[0]).powi(2) + (p[1] - a[1] - t * d[1]).powi(2)
169 + (p[2] - a[2] - t * d[2]).powi(2)).sqrt();
170 req!((off <= eps), true, "At {} m a dropped vertex is {} m off.",
171 eps_m, off * EARTH_RADIUS_M);
172 }
173 }
174 }
175 // Nothing to do below three vertices, or at no tolerance.
176 req!(world::simplify_sphere(&pts[..2], 1_000.0), vec![0, 1]);
177 req!(world::simplify_sphere(&pts[..5], 0.0), vec![0, 1, 2, 3, 4]);
178 Ok(())
179}
180
181#[test]
182fn test_the_detail_drawn_follows_the_scale_and_worlds_merge_05() -> Outcome<()> {
183 let ring = |detail: u8, tol_m: f64, name: &str| Layer {
184 name: name.to_string(), kind: LayerKind::Fill, detail, tol_m,
185 rings: vec![vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]], labels: Vec::new(),
186 };
187 let places = |n: usize| Layer {
188 name: "places".to_string(), kind: LayerKind::Label, detail: 0, tol_m: 0.0,
189 rings: Vec::new(),
190 labels: (0..n).map(|k| Label { lat: 0.0, lng: k as f64, rank: 0, name: fmt!("{}", k) }).collect(),
191 };
192 let mut w = World { layers: vec![ring(0, 30_000.0, "land"), places(2)] };
193 req!(World::default().detail_for(1_000.0, 2.5), None::<u8>);
194 req!(w.detail_for(1_000.0, 2.5), Some(0));
195 let fine = World { layers: vec![ring(1, 6_000.0, "land"), ring(1, 6_000.0, "lakes"), places(5)] };
196 w.merge(fine);
197 req!(w.details(), 2);
198 req!(w.layers.len(), 4);
199 // The finer file's places replace the coarse file's rather than doubling them.
200 req!(w.layer("places", 0).map(|l| l.labels.len()), Some(5));
201 // 30 km at 2.5 px wants 12 km a pixel; 6 km wants 2.4 km; finer than that, the finest.
202 req!(w.detail_for(12_000.0, 2.5), Some(0));
203 req!(w.detail_for(11_999.0, 2.5), Some(1));
204 req!(w.detail_for(2_400.0, 2.5), Some(1));
205 req!(w.detail_for(10.0, 2.5), Some(1));
206 Ok(())
207}