1use crate::MapError;
4use clipper2_rust::{ClipType, Clipper64, FillRule, Path64, Paths64, Point64, PolyTree64};
5use serde::Serialize;
6
7pub const METHOD_ID: &str = "deterministic-zero-depth-contours/1";
8pub const PRECISION: f64 = 1000.;
9pub type Result<T> = std::result::Result<T, MapError>;
10#[derive(Clone, Copy)]
13pub struct Limits {
14 pub max_working_bytes: usize,
15}
16impl Limits {
17 fn admit(self, vertices: usize, paths: usize) -> Result<()> {
18 let estimate = vertices
19 .checked_mul(256)
20 .and_then(|n| paths.checked_mul(128).and_then(|m| n.checked_add(m)))
21 .and_then(|n| n.checked_add(65536))
22 .ok_or_else(|| {
23 MapError::Budget("boundary contour planned memory exceeds budget".into())
24 })?;
25 if estimate > self.max_working_bytes {
26 Err(MapError::Budget(
27 "boundary contour planned memory exceeds budget".into(),
28 ))
29 } else {
30 Ok(())
31 }
32 }
33}
34#[derive(Clone, Copy)]
35pub struct Grid {
36 pub cols: usize,
37 pub rows: usize,
38 pub transform: [f64; 6],
39}
40impl Grid {
41 fn validate(self, n: usize) -> Result<()> {
42 let t = self.transform;
43 if self.cols < 2
44 || self.rows < 2
45 || self.cols.checked_mul(self.rows) != Some(n)
46 || t.iter().any(|v| !v.is_finite())
47 || t[1] <= 0.
48 || t[5] >= 0.
49 || t[2] != 0.
50 || t[4] != 0.
51 {
52 return Err(MapError::Invalid("invalid contour sample grid".into()));
53 }
54 Ok(())
55 }
56 fn point(self, r: usize, c: usize) -> [f64; 2] {
57 [
58 self.transform[0] + (c as f64 + 0.5) * self.transform[1],
59 self.transform[3] + (r as f64 + 0.5) * self.transform[5],
60 ]
61 }
62 fn center(self, r: usize, c: usize) -> [f64; 2] {
63 [
64 self.transform[0] + (c + 1) as f64 * self.transform[1],
65 self.transform[3] + (r + 1) as f64 * self.transform[5],
66 ]
67 }
68}
69#[derive(Clone, Copy)]
70struct Sample {
71 xy: [f64; 2],
72 z: f32,
73}
74#[derive(Clone, Debug)]
75pub struct Unit {
76 pub sequence: u64,
77 paths: Paths64,
78}
79impl Unit {
80 pub fn vertex_count(&self) -> usize {
81 self.paths
82 .iter()
83 .fold(0usize, |n, p| n.saturating_add(p.len()))
84 }
85 pub fn ring_count(&self) -> usize {
86 self.paths.len()
87 }
88}
89struct Fragments {
90 paths: Paths64,
91 vertices: usize,
92 limits: Limits,
93}
94impl Fragments {
95 fn new(limits: Limits) -> Self {
96 Self {
97 paths: vec![],
98 vertices: 0,
99 limits,
100 }
101 }
102 fn push(&mut self, mut p: Path64) -> Result<()> {
103 if p.len() < 3 {
104 return Ok(());
105 }
106 let area = area2_points(&p)?;
109 if area == 0 {
110 return Ok(());
111 }
112 if area < 0 {
113 p.reverse();
114 }
115 let n = self.vertices.checked_add(p.len()).ok_or_else(|| {
116 MapError::Budget("boundary contour planned memory exceeds budget".into())
117 })?;
118 self.limits.admit(n, self.paths.len() + 1)?;
119 self.vertices = n;
120 self.paths.push(p);
121 Ok(())
122 }
123 fn complete(self, sequence: u64) -> Result<Unit> {
124 let paths = union(&self.paths, self.limits)?;
125 Ok(Unit { sequence, paths })
126 }
127}
128fn quantize(p: [f64; 2]) -> Result<Point64> {
129 let q = [
130 (p[0] * PRECISION).round_ties_even(),
131 (p[1] * PRECISION).round_ties_even(),
132 ];
133 if q.iter()
134 .any(|v| !v.is_finite() || v.abs() >= ((1_u64 << 61) as f64))
135 {
136 return Err(MapError::Invalid(
137 "contour coordinate outside integer clipping range".into(),
138 ));
139 }
140 Ok(Point64::new(q[0] as i64, q[1] as i64))
141}
142fn check_field(values: &[Option<f32>]) -> Result<()> {
143 if values.iter().any(|v| v.is_some_and(|v| !v.is_finite())) {
144 Err(MapError::Invalid("nonfinite contour sample".into()))
145 } else {
146 Ok(())
147 }
148}
149fn edge(a: Sample, b: Sample, out: &mut Vec<[f64; 2]>) -> Result<()> {
150 let inside = |z: f32| z >= 0.;
151 if inside(a.z) {
152 out.push(a.xy)
153 }
154 if (a.z >= 0.) != (b.z >= 0.) {
155 let span = b.z - a.z;
156 if !span.is_finite() {
157 return Err(MapError::Invalid("zero-contour depth span overflow".into()));
158 }
159 let t = f64::from((0f32 - a.z) / span);
160 if !t.is_finite() {
161 return Err(MapError::Invalid(
162 "nonfinite zero-contour interpolation".into(),
163 ));
164 }
165 out.push([
166 a.xy[0] + (b.xy[0] - a.xy[0]) * t,
167 a.xy[1] + (b.xy[1] - a.xy[1]) * t,
168 ]);
169 }
170 Ok(())
171}
172fn triangle(v: [Sample; 3], out: &mut Fragments) -> Result<()> {
173 let mut coords = Vec::with_capacity(4);
174 for i in 0..3 {
175 edge(v[i], v[(i + 1) % 3], &mut coords)?
176 }
177 let mut p = coords
178 .into_iter()
179 .map(quantize)
180 .collect::<Result<Path64>>()?;
181 p.dedup();
182 if p.len() > 1 && p.first() == p.last() {
183 p.pop();
184 }
185 out.push(p)
186}
187fn block(v: [Option<Sample>; 4], xy: [f64; 2], out: &mut Fragments) -> Result<()> {
188 let count = v.iter().flatten().count();
189 if count < 3 {
190 return Ok(());
191 }
192 let sum = v.iter().flatten().fold(0f32, |a, b| a + b.z);
193 let z = if count == 4 {
194 (f64::from(sum) * 0.25) as f32
195 } else {
196 sum / 3f32
197 };
198 if !z.is_finite() {
199 return Err(MapError::Invalid("contour center depth overflow".into()));
200 }
201 let mid = Sample { xy, z };
202 for i in 0..4 {
203 let j = (i + 1) % 4;
204 if let (Some(a), Some(b)) = (v[i], v[j]) {
205 triangle([b, a, mid], out)?
206 }
207 }
208 Ok(())
209}
210fn sample(xy: [f64; 2], z: Option<f32>) -> Option<Sample> {
211 z.map(|z| Sample { xy, z })
212}
213pub fn grid(sequence: u64, grid: Grid, depth: &[Option<f32>], limits: Limits) -> Result<Unit> {
217 grid.validate(depth.len())?;
218 quantize(grid.point(0, 0))?;
219 quantize(grid.point(grid.rows - 1, grid.cols - 1))?;
220 limits.admit(0, 0)?;
221 check_field(depth)?;
222 let mut fragments = Fragments::new(limits);
223 for r in 0..grid.rows - 1 {
224 let mut c = 0;
225 while c < grid.cols - 1 {
226 let wet = |c: usize| {
227 let i = r * grid.cols + c;
228 [i, i + 1, i + grid.cols + 1, i + grid.cols]
229 .iter()
230 .all(|&i| depth[i].is_some_and(|v| v >= 0.))
231 };
232 if wet(c) {
233 let start = c;
234 while c + 1 < grid.cols - 1 && wet(c + 1) {
235 c += 1
236 }
237 let mut p = [
238 grid.point(r, start),
239 grid.point(r + 1, start),
240 grid.point(r + 1, c + 1),
241 grid.point(r, c + 1),
242 ]
243 .into_iter()
244 .map(quantize)
245 .collect::<Result<Path64>>()?;
246 p.dedup();
247 fragments.push(p)?;
248 c += 1;
249 continue;
250 }
251 let i = r * grid.cols + c;
252 block(
253 [
254 sample(grid.point(r, c), depth[i]),
255 sample(grid.point(r, c + 1), depth[i + 1]),
256 sample(grid.point(r + 1, c + 1), depth[i + grid.cols + 1]),
257 sample(grid.point(r + 1, c), depth[i + grid.cols]),
258 ],
259 grid.center(r, c),
260 &mut fragments,
261 )?;
262 c += 1;
263 }
264 }
265 fragments.complete(sequence)
266}
267pub fn tin(
270 sequence: u64,
271 xy: &[[f64; 2]],
272 depth: &[Option<f32>],
273 triangles: &[[u32; 3]],
274 limits: Limits,
275) -> Result<Unit> {
276 limits.admit(0, 0)?;
277 if xy.len() != depth.len() {
278 return Err(MapError::Invalid(
279 "stitch contour field length mismatch".into(),
280 ));
281 }
282 check_field(depth)?;
283 for &point in xy {
284 quantize(point)?;
285 }
286 let mut out = Fragments::new(limits);
287 for t in triangles {
288 let mut v = [None; 3];
289 for i in 0..3 {
290 let k = t[i] as usize;
291 if k >= xy.len() {
292 return Err(MapError::Invalid(
293 "stitch contour point out of range".into(),
294 ));
295 }
296 v[i] = sample(xy[k], depth[k]);
297 }
298 if let [Some(a), Some(b), Some(c)] = v {
299 triangle([a, b, c], &mut out)?
300 }
301 }
302 out.complete(sequence)
303}
304
305fn count_vertices(paths: &Paths64) -> Result<usize> {
306 paths.iter().try_fold(0usize, |n, p| {
307 n.checked_add(p.len())
308 .ok_or_else(|| MapError::Budget("contour vertex count overflow".into()))
309 })
310}
311
312fn union(paths: &Paths64, limits: Limits) -> Result<Paths64> {
313 limits.admit(count_vertices(paths)?, paths.len())?;
314 if paths.is_empty() {
315 return Ok(vec![]);
316 }
317 let mut c = Clipper64::new();
318 c.set_preserve_collinear(false);
319 c.add_subject(paths);
320 let mut out = vec![];
321 if !c.execute(ClipType::Union, FillRule::NonZero, &mut out, None) {
322 return Err(MapError::Invalid("contour union failed".into()));
323 }
324 limits.admit(count_vertices(&out)?, out.len())?;
325 Ok(out)
326}
327fn area2_points(p: &Path64) -> Result<i128> {
328 let a = p[0];
329 let mut sum = 0i128;
330 for (p, q) in p.iter().zip(p.iter().cycle().skip(1)).take(p.len()) {
331 let (px, py) = (p.x as i128 - a.x as i128, p.y as i128 - a.y as i128);
332 let (qx, qy) = (q.x as i128 - a.x as i128, q.y as i128 - a.y as i128);
333 let cross = px
334 .checked_mul(qy)
335 .and_then(|a| qx.checked_mul(py).and_then(|b| a.checked_sub(b)))
336 .ok_or_else(|| MapError::Invalid("contour area overflow".into()))?;
337 sum = sum
338 .checked_add(cross)
339 .ok_or_else(|| MapError::Invalid("contour area overflow".into()))?;
340 }
341 Ok(sum)
342}
343
344fn area2(p: &[[i64; 2]]) -> Result<i128> {
345 let a = p[0];
346 let mut sum = 0i128;
347 for (p, q) in p.iter().zip(p.iter().cycle().skip(1)).take(p.len()) {
348 let (px, py) = (p[0] as i128 - a[0] as i128, p[1] as i128 - a[1] as i128);
349 let (qx, qy) = (q[0] as i128 - a[0] as i128, q[1] as i128 - a[1] as i128);
350 sum = sum
351 .checked_add(
352 px.checked_mul(qy)
353 .and_then(|a| qx.checked_mul(py).and_then(|b| a.checked_sub(b)))
354 .ok_or_else(|| MapError::Invalid("contour area overflow".into()))?,
355 )
356 .ok_or_else(|| MapError::Invalid("contour area overflow".into()))?;
357 }
358 Ok(sum)
359}
360fn rotate_min(p: &mut [[i64; 2]]) {
362 let n = p.len();
363 if n < 2 {
364 return;
365 }
366 let (mut i, mut j, mut k) = (0, 1, 0);
367 while i < n && j < n && k < n {
368 let a = p[(i + k) % n];
369 let b = p[(j + k) % n];
370 if a == b {
371 k += 1;
372 continue;
373 }
374 if a > b {
375 i += k + 1;
376 if i == j {
377 i += 1
378 }
379 } else {
380 j += k + 1;
381 if i == j {
382 j += 1
383 }
384 }
385 k = 0;
386 }
387 p.rotate_left(i.min(j) % n);
388}
389fn canonical(path: &Path64, ccw: bool) -> Result<Vec<[i64; 2]>> {
390 let mut p: Vec<_> = path.iter().map(|p| [p.x, p.y]).collect();
391 p.dedup();
392 if p.len() > 1 && p.first() == p.last() {
393 p.pop();
394 }
395 if p.len() < 3 {
396 return Err(MapError::Invalid("degenerate contour ring".into()));
397 }
398 let area = area2(&p)?;
399 if area == 0 {
400 return Err(MapError::Invalid("zero area contour ring".into()));
401 }
402 if (area > 0) != ccw {
403 p.reverse()
404 }
405 rotate_min(&mut p);
406 Ok(p)
407}
408pub type MultiPolygon = Vec<Vec<Vec<[f64; 2]>>>;
409#[derive(Serialize, Debug, PartialEq)]
410pub struct Output {
411 pub method_id: &'static str,
412 pub coordinate_precision: f64,
413 pub coordinates: MultiPolygon,
414}
415fn aoi_paths(aoi: &MultiPolygon, limits: Limits) -> Result<Paths64> {
416 let vertices = aoi.iter().flatten().try_fold(0usize, |n, r| {
417 n.checked_add(r.len())
418 .ok_or_else(|| MapError::Budget("AOI contour vertex count overflow".into()))
419 })?;
420 let rings = aoi.iter().try_fold(0usize, |n, p| {
421 n.checked_add(p.len())
422 .ok_or_else(|| MapError::Budget("AOI contour ring count overflow".into()))
423 })?;
424 limits.admit(vertices, rings)?;
425 let mut paths = vec![];
426 for polygon in aoi {
427 if polygon.is_empty() {
428 return Err(MapError::Invalid("empty AOI polygon".into()));
429 }
430 for (i, ring) in polygon.iter().enumerate() {
431 let path = ring
432 .iter()
433 .copied()
434 .map(quantize)
435 .collect::<Result<Path64>>()?;
436 let p = canonical(&path, i == 0)?;
437 paths.push(p.into_iter().map(|p| Point64::new(p[0], p[1])).collect());
438 }
439 }
440 Ok(paths)
441}
442pub fn finish(units: &[Unit], aoi: Option<&MultiPolygon>, limits: Limits) -> Result<Output> {
445 limits.admit(0, units.len())?;
446 let mut order: Vec<_> = units.iter().collect();
447 order.sort_by_key(|u| u.sequence);
448 if order.windows(2).any(|w| w[0].sequence == w[1].sequence) {
449 return Err(MapError::Invalid("duplicate contour unit sequence".into()));
450 }
451 let vertices = order.iter().try_fold(0usize, |n, u| {
452 n.checked_add(u.vertex_count()).ok_or_else(|| {
453 MapError::Budget("boundary contour planned memory exceeds budget".into())
454 })
455 })?;
456 let count = order.iter().try_fold(0usize, |n, u| {
457 n.checked_add(u.paths.len())
458 .ok_or_else(|| MapError::Budget("contour ring count overflow".into()))
459 })?;
460 limits.admit(vertices, count)?;
461 let paths = order
462 .into_iter()
463 .flat_map(|u| u.paths.iter().cloned())
464 .collect();
465 let paths = union(&paths, limits)?;
466 if paths.is_empty() {
467 if let Some(aoi) = aoi {
468 aoi_paths(aoi, limits)?;
469 }
470 return Ok(Output {
471 method_id: METHOD_ID,
472 coordinate_precision: 1. / PRECISION,
473 coordinates: vec![],
474 });
475 }
476 let mut c = Clipper64::new();
477 c.set_preserve_collinear(false);
478 c.add_subject(&paths);
479 let operation = if let Some(aoi) = aoi {
480 let clips = aoi_paths(aoi, limits)?;
481 let clipped_vertices = vertices
482 .checked_add(count_vertices(&clips)?)
483 .ok_or_else(|| MapError::Budget("AOI contour vertex count overflow".into()))?;
484 let clipped_count = count
485 .checked_add(clips.len())
486 .ok_or_else(|| MapError::Budget("AOI contour ring count overflow".into()))?;
487 limits.admit(clipped_vertices, clipped_count)?;
488 c.add_clip(&clips);
489 ClipType::Intersection
490 } else {
491 ClipType::Union
492 };
493 let mut tree = PolyTree64::new();
494 let mut open = vec![];
495 if !c.execute_tree(operation, FillRule::NonZero, &mut tree, &mut open) {
496 return Err(MapError::Invalid("contour topology assembly failed".into()));
497 }
498 let output_vertices = tree.nodes.iter().try_fold(0usize, |n, p| {
499 n.checked_add(p.polygon().len())
500 .ok_or_else(|| MapError::Budget("contour output vertex count overflow".into()))
501 })?;
502 limits.admit(output_vertices, tree.nodes.len())?;
503 let mut polygons = vec![];
504 for i in 1..tree.nodes.len() {
505 if tree.is_hole(i) {
506 continue;
507 }
508 let node = &tree.nodes[i];
509 let exterior = canonical(node.polygon(), true)?;
510 let mut holes = node
511 .children()
512 .iter()
513 .map(|&j| canonical(tree.nodes[j].polygon(), false))
514 .collect::<Result<Vec<_>>>()?;
515 holes.sort();
516 let mut polygon = vec![exterior];
517 polygon.extend(holes);
518 polygons.push(polygon);
519 }
520 polygons.sort();
521 let coordinates = polygons
522 .into_iter()
523 .map(|p| {
524 p.into_iter()
525 .map(|ring| {
526 let mut r: Vec<_> = ring
527 .into_iter()
528 .map(|p| {
529 [
530 p[0] as f64 * (1. / PRECISION),
531 p[1] as f64 * (1. / PRECISION),
532 ]
533 })
534 .collect();
535 r.push(r[0]);
536 r
537 })
538 .collect()
539 })
540 .collect();
541 Ok(Output {
542 method_id: METHOD_ID,
543 coordinate_precision: 1. / PRECISION,
544 coordinates,
545 })
546}