use super::{ Cartesian, Clip, ClipControl, Clipper, PathClipper, RadianPoint, DrawPath, F32_PRECISION, }; fn _clip_longitude_degeneracies(p: RadianPoint) -> RadianPoint { use std::f32::consts::PI; let border = PI.copysign(p.lambda); if (p.lambda - border).abs() < F32_PRECISION { RadianPoint { lambda: p.lambda - (border * F32_PRECISION), phi: p.phi, } } else { p } } // line from a to b crosses antimeridian: calculate at which latitude ("phi") it intersects. fn _clip_antimeridian_intersect(a: RadianPoint, b: RadianPoint) -> f32 { let a = _clip_longitude_degeneracies(a); let b = _clip_longitude_degeneracies(b); let x = Cartesian::from(a); let y = Cartesian::from(b); let result_adjacent = x.b * y.a - y.b * x.a; if result_adjacent.abs() > F32_PRECISION { let result_opposite = x.c * y.b - y.c * x.b; (result_opposite / result_adjacent).atan() } else { (a.phi + b.phi) * 0.5 } } pub struct ClipAntimeridian; impl ClipAntimeridian { #[allow(unused)] pub fn new(sink: S) -> Clip where S: DrawPath, { Clip::new(sink, ClipAntimeridian) } } impl Clipper for ClipAntimeridian { type PathClipper = ClipAntimeridianPathClipper; fn create_clipper(&self) -> Self::PathClipper { ClipAntimeridianPathClipper { first_prev: None } } fn interpolate(&self, control: &mut ClipControl, from_to: Option<(RadianPoint, RadianPoint)>, forward: bool) where S: DrawPath, { use std::f32::consts::{PI, FRAC_PI_2}; let forward_sign = if forward { 1.0 } else { -1.0 }; if let Some((from, to)) = from_to { if (to.lambda - from.lambda).abs() > PI { // crossing antimeridian let lambda = PI.copysign(to.lambda - from.lambda); let phi = forward_sign * lambda / 2.0; control.line(RadianPoint { lambda: -lambda, phi }, false); control.line(RadianPoint { lambda: 0.0, phi }, false); control.line(RadianPoint { lambda: lambda, phi }, false); } else { control.line(to, false); } } else { let phi = forward_sign * FRAC_PI_2; control.line(RadianPoint { lambda: -PI, phi: phi }, false); control.line(RadianPoint { lambda: 0.0, phi: phi }, false); control.line(RadianPoint { lambda: PI, phi: phi }, false); control.line(RadianPoint { lambda: PI, phi: 0.0 }, false); control.line(RadianPoint { lambda: PI, phi: -phi }, false); control.line(RadianPoint { lambda: 0.0, phi: -phi }, false); control.line(RadianPoint { lambda: -PI, phi: -phi }, false); control.line(RadianPoint { lambda: -PI, phi: 0.0 }, false); control.line(RadianPoint { lambda: -PI, phi: phi }, false); } } } pub struct ClipAntimeridianPathClipper { first_prev: Option<((RadianPoint, bool), bool, RadianPoint)>, } impl PathClipper for ClipAntimeridianPathClipper { fn line(&mut self, control: &mut ClipControl, next: RadianPoint, stroke: bool) where S: DrawPath, { use std::f32::consts::{PI, FRAC_PI_2}; let next_positive = next.lambda > 0.0; if let Some(((first, first_stroke), prev_positive, prev)) = &mut self.first_prev { let delta = (next.lambda - prev.lambda).abs(); if (delta - PI).abs() < F32_PRECISION { // line crosses a pole // north or south? let phi_side = if (prev.phi + next.phi) > 0.0 { FRAC_PI_2 } else { -FRAC_PI_2 }; // go to pole on same latitude as prev point control.line(RadianPoint { lambda: prev.lambda, phi: phi_side }, stroke); // then to the "nearer" side (east/west), still at the pole ("same point", but projection might differ) control.line(RadianPoint { lambda: PI.copysign(prev.lambda), phi: phi_side }, false); control.split_path(); // start at side (east/west), but at pole *first = RadianPoint { lambda: PI.copysign(next.lambda), phi: phi_side }; *first_stroke = stroke; control.line(*first, false); // move to pole on same latitude as next point control.line(RadianPoint { lambda: next.lambda, phi: phi_side }, false); } else if *prev_positive != next_positive && delta >= PI { // line crosses antimeridian // get latitude for the intersection let phi_side = _clip_antimeridian_intersect(*prev, next); // move to intersection on prev side control.line(RadianPoint { lambda: PI.copysign(prev.lambda), phi: phi_side }, stroke); control.split_path(); // start at intersection on next side *first = RadianPoint { lambda: PI.copysign(next.lambda), phi: phi_side }; *first_stroke = stroke; control.line(*first, false); } *prev_positive = next_positive; *prev = next; } else { self.first_prev = Some(((next, stroke), next_positive, next)); } control.line(next, stroke); } }