use rustial_math::GeoCoord;
const EARTH_RADIUS: f64 = 6_378_137.0;
fn destination(from: &GeoCoord, bearing_deg: f64, distance_m: f64) -> GeoCoord {
let lat1 = from.lat.to_radians();
let lon1 = from.lon.to_radians();
let brng = bearing_deg.to_radians();
let d = distance_m / EARTH_RADIUS;
let lat2 = (lat1.sin() * d.cos() + lat1.cos() * d.sin() * brng.cos()).asin();
let lon2 = lon1 + (brng.sin() * d.sin() * lat1.cos()).atan2(d.cos() - lat1.sin() * lat2.sin());
GeoCoord::from_lat_lon(lat2.to_degrees(), lon2.to_degrees())
}
pub fn circle(center: &GeoCoord, radius_m: f64, segments: usize) -> Vec<GeoCoord> {
let n = segments.max(4);
let step = 360.0 / n as f64;
let mut ring = Vec::with_capacity(n + 1);
for i in 0..n {
ring.push(destination(center, i as f64 * step, radius_m));
}
ring.push(ring[0]); ring
}
pub fn arc(
center: &GeoCoord,
radius_m: f64,
start_bearing: f64,
end_bearing: f64,
segments: usize,
) -> Vec<GeoCoord> {
let n = segments.max(2);
let mut sweep = end_bearing - start_bearing;
if sweep <= 0.0 {
sweep += 360.0;
}
let step = sweep / n as f64;
let mut points = Vec::with_capacity(n + 1);
for i in 0..=n {
let bearing = start_bearing + i as f64 * step;
points.push(destination(center, bearing, radius_m));
}
points
}
pub fn sector(
center: &GeoCoord,
radius_m: f64,
start_bearing: f64,
end_bearing: f64,
segments: usize,
) -> Vec<GeoCoord> {
let arc_points = arc(center, radius_m, start_bearing, end_bearing, segments);
let mut ring = Vec::with_capacity(arc_points.len() + 2);
ring.push(*center);
ring.extend(arc_points);
ring.push(*center); ring
}
pub fn ellipse(
center: &GeoCoord,
semi_major_m: f64,
semi_minor_m: f64,
rotation_deg: f64,
segments: usize,
) -> Vec<GeoCoord> {
let n = segments.max(4);
let step = std::f64::consts::TAU / n as f64;
let rot = rotation_deg.to_radians();
let mut ring = Vec::with_capacity(n + 1);
for i in 0..n {
let angle = i as f64 * step;
let lx = semi_major_m * angle.cos();
let ly = semi_minor_m * angle.sin();
let bearing = (lx * rot.sin() + ly * rot.cos()).atan2(lx * rot.cos() - ly * rot.sin());
let dist = (lx * lx + ly * ly).sqrt();
ring.push(destination(center, bearing.to_degrees(), dist));
}
ring.push(ring[0]); ring
}
pub fn rectangle(sw: &GeoCoord, ne: &GeoCoord) -> Vec<GeoCoord> {
vec![
GeoCoord::from_lat_lon(sw.lat, sw.lon),
GeoCoord::from_lat_lon(sw.lat, ne.lon),
GeoCoord::from_lat_lon(ne.lat, ne.lon),
GeoCoord::from_lat_lon(ne.lat, sw.lon),
GeoCoord::from_lat_lon(sw.lat, sw.lon), ]
}
pub fn regular_polygon(
center: &GeoCoord,
radius_m: f64,
sides: usize,
rotation_deg: f64,
) -> Vec<GeoCoord> {
let n = sides.max(3);
let step = 360.0 / n as f64;
let mut ring = Vec::with_capacity(n + 1);
for i in 0..n {
let bearing = rotation_deg + i as f64 * step;
ring.push(destination(center, bearing, radius_m));
}
ring.push(ring[0]); ring
}
pub fn line_along(from: &GeoCoord, to: &GeoCoord, count: usize) -> Vec<GeoCoord> {
let n = count.max(2);
let mut points = Vec::with_capacity(n);
for i in 0..n {
let fraction = i as f64 / (n - 1) as f64;
points.push(crate::geometry_ops::interpolate_great_circle(
from, to, fraction,
));
}
points
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn circle_has_correct_vertex_count() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let ring = circle(¢er, 1000.0, 64);
assert_eq!(ring.len(), 65);
assert!((ring[0].lat - ring[64].lat).abs() < 1e-10);
assert!((ring[0].lon - ring[64].lon).abs() < 1e-10);
}
#[test]
fn circle_radius_is_approximately_correct() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let radius = 10_000.0; let ring = circle(¢er, radius, 64);
for v in &ring[..64] {
let d = crate::geometry_ops::haversine(¢er, v);
assert!((d - radius).abs() < 1.0, "expected ~{radius}m, got {d}m");
}
}
#[test]
fn arc_spans_correct_angles() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let points = arc(¢er, 1000.0, 0.0, 90.0, 4);
assert_eq!(points.len(), 5);
assert!(points[0].lat > center.lat);
assert!(points[4].lon > center.lon);
}
#[test]
fn rectangle_has_five_vertices() {
let sw = GeoCoord::from_lat_lon(0.0, 0.0);
let ne = GeoCoord::from_lat_lon(1.0, 1.0);
let ring = rectangle(&sw, &ne);
assert_eq!(ring.len(), 5);
assert_eq!(ring[0].lat, ring[4].lat);
assert_eq!(ring[0].lon, ring[4].lon);
}
#[test]
fn regular_polygon_triangle() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let ring = regular_polygon(¢er, 1000.0, 3, 0.0);
assert_eq!(ring.len(), 4); }
#[test]
fn sector_includes_center() {
let center = GeoCoord::from_lat_lon(10.0, 20.0);
let ring = sector(¢er, 5000.0, 0.0, 90.0, 8);
assert_eq!(ring[0].lat, center.lat);
assert_eq!(ring[0].lon, center.lon);
let last = ring.last().unwrap();
assert_eq!(last.lat, center.lat);
assert_eq!(last.lon, center.lon);
}
#[test]
fn ellipse_has_correct_vertex_count() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let ring = ellipse(¢er, 2000.0, 1000.0, 45.0, 32);
assert_eq!(ring.len(), 33); }
#[test]
fn line_along_endpoints() {
let a = GeoCoord::from_lat_lon(0.0, 0.0);
let b = GeoCoord::from_lat_lon(10.0, 0.0);
let pts = line_along(&a, &b, 5);
assert_eq!(pts.len(), 5);
assert!((pts[0].lat - a.lat).abs() < 1e-10);
assert!((pts[4].lat - b.lat).abs() < 0.01);
}
#[test]
fn circle_minimum_segments() {
let center = GeoCoord::from_lat_lon(0.0, 0.0);
let ring = circle(¢er, 1000.0, 1); assert_eq!(ring.len(), 5); }
}