rust-galmon-web/src/ui/main/world_geo/clip_antimeridian.rs

147 lines
4.6 KiB
Rust

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<S>(sink: S) -> Clip<S, ClipAntimeridian>
where
S: DrawPath<Point = RadianPoint>,
{
Clip::new(sink, ClipAntimeridian)
}
}
impl Clipper for ClipAntimeridian {
type PathClipper = ClipAntimeridianPathClipper;
fn create_clipper(&self) -> Self::PathClipper {
ClipAntimeridianPathClipper {
first_prev: None
}
}
fn interpolate<S>(&self, control: &mut ClipControl<S>, from_to: Option<(RadianPoint, RadianPoint)>, forward: bool)
where
S: DrawPath<Point = RadianPoint>,
{
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<S>(&mut self, control: &mut ClipControl<S>, next: RadianPoint, stroke: bool)
where
S: DrawPath<Point = RadianPoint>,
{
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);
}
}