use geo_types::{
Coord, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point as GeoPoint,
Polygon as GeoPoly, Rect as GeoRect, Triangle,
};
use crate::s2::point_vector::PointVector;
use crate::s2::polyline::Polyline;
use crate::s2::{LatLng, Loop, Point, Polygon, Rect};
fn point_to_coord(p: Point) -> Coord<f64> {
let ll = LatLng::from_point(p);
Coord {
x: ll.lng.degrees(),
y: ll.lat.degrees(),
}
}
fn coord_to_point(c: Coord<f64>) -> Point {
LatLng::from_degrees(c.y, c.x).to_point()
}
fn loop_to_ring(lp: &Loop) -> LineString<f64> {
let mut coords: Vec<Coord<f64>> = lp.vertices().iter().map(|&p| point_to_coord(p)).collect();
if let Some(&first) = coords.first() {
coords.push(first);
}
LineString(coords)
}
fn ring_to_loop(ls: LineString<f64>) -> Loop {
let mut coords = ls.into_inner();
if coords.len() > 1 && coords.first() == coords.last() {
coords.pop();
}
let vertices: Vec<Point> = coords.into_iter().map(coord_to_point).collect();
Loop::new(vertices)
}
impl From<Coord<f64>> for LatLng {
fn from(c: Coord<f64>) -> Self {
LatLng::from_degrees(c.y, c.x)
}
}
impl From<LatLng> for Coord<f64> {
fn from(ll: LatLng) -> Self {
Coord {
x: ll.lng.degrees(),
y: ll.lat.degrees(),
}
}
}
impl From<GeoPoint<f64>> for LatLng {
fn from(p: GeoPoint<f64>) -> Self {
LatLng::from_degrees(p.y(), p.x())
}
}
impl From<LatLng> for GeoPoint<f64> {
fn from(ll: LatLng) -> Self {
GeoPoint::new(ll.lng.degrees(), ll.lat.degrees())
}
}
impl From<GeoPoint<f64>> for Point {
fn from(p: GeoPoint<f64>) -> Self {
LatLng::from_degrees(p.y(), p.x()).to_point()
}
}
impl From<Point> for GeoPoint<f64> {
fn from(p: Point) -> Self {
let ll = LatLng::from_point(p);
GeoPoint::new(ll.lng.degrees(), ll.lat.degrees())
}
}
impl From<Line<f64>> for Polyline {
fn from(line: Line<f64>) -> Self {
Polyline::new(vec![coord_to_point(line.start), coord_to_point(line.end)])
}
}
impl From<Polyline> for Line<f64> {
fn from(pl: Polyline) -> Self {
let v = pl.vertices_vec();
assert!(
v.len() >= 2,
"Polyline must have at least 2 vertices to convert to geo_types::Line"
);
Line {
start: point_to_coord(v[0]),
end: point_to_coord(v[1]),
}
}
}
impl From<LineString<f64>> for Polyline {
fn from(ls: LineString<f64>) -> Self {
let vertices: Vec<Point> = ls.into_inner().into_iter().map(coord_to_point).collect();
Polyline::new(vertices)
}
}
impl From<Polyline> for LineString<f64> {
fn from(pl: Polyline) -> Self {
LineString(
pl.vertices_vec()
.iter()
.map(|&p| point_to_coord(p))
.collect(),
)
}
}
pub fn multi_linestring_to_polylines(mls: MultiLineString<f64>) -> Vec<Polyline> {
mls.into_iter().map(Polyline::from).collect()
}
pub fn polylines_to_multi_linestring(polylines: Vec<Polyline>) -> MultiLineString<f64> {
MultiLineString(polylines.into_iter().map(LineString::from).collect())
}
impl From<MultiPoint<f64>> for PointVector {
fn from(mp: MultiPoint<f64>) -> Self {
PointVector::new(mp.into_iter().map(Point::from).collect())
}
}
impl From<PointVector> for MultiPoint<f64> {
fn from(pv: PointVector) -> Self {
MultiPoint(pv.points().iter().map(|&p| GeoPoint::from(p)).collect())
}
}
impl From<Triangle<f64>> for Loop {
fn from(t: Triangle<f64>) -> Self {
Loop::new(vec![
coord_to_point(t.v1()),
coord_to_point(t.v2()),
coord_to_point(t.v3()),
])
}
}
impl From<Loop> for Triangle<f64> {
fn from(lp: Loop) -> Self {
let v = lp.vertices();
assert_eq!(
v.len(),
3,
"Loop must have exactly 3 vertices to convert to geo_types::Triangle"
);
Triangle(
point_to_coord(v[0]),
point_to_coord(v[1]),
point_to_coord(v[2]),
)
}
}
impl From<GeoRect<f64>> for Rect {
fn from(r: GeoRect<f64>) -> Self {
let min = r.min();
let max = r.max();
let lo = LatLng::from_degrees(min.y, min.x);
let hi = LatLng::from_degrees(max.y, max.x);
Rect::from_point_pair(lo, hi)
}
}
impl From<Rect> for GeoRect<f64> {
fn from(r: Rect) -> Self {
let lo = r.lo();
let hi = r.hi();
GeoRect::new(
Coord {
x: lo.lng.degrees(),
y: lo.lat.degrees(),
},
Coord {
x: hi.lng.degrees(),
y: hi.lat.degrees(),
},
)
}
}
impl From<GeoPoly<f64>> for Polygon {
fn from(poly: GeoPoly<f64>) -> Self {
let (exterior, interiors) = poly.into_inner();
let mut loops: Vec<Loop> = Vec::with_capacity(1 + interiors.len());
loops.push(ring_to_loop(exterior));
for ring in interiors {
loops.push(ring_to_loop(ring));
}
Polygon::from_loops(loops)
}
}
pub fn multi_polygon_to_polygons(mp: MultiPolygon<f64>) -> Vec<Polygon> {
mp.into_iter().map(Polygon::from).collect()
}
impl From<Polygon> for MultiPolygon<f64> {
fn from(polygon: Polygon) -> Self {
struct ShellBuilder {
exterior: LineString<f64>,
holes: Vec<LineString<f64>>,
depth: i32,
}
let mut result: Vec<GeoPoly<f64>> = Vec::new();
let mut stack: Vec<ShellBuilder> = Vec::new();
fn flush_until(
stack: &mut Vec<ShellBuilder>,
result: &mut Vec<GeoPoly<f64>>,
min_depth: i32,
) {
while stack.last().is_some_and(|s| s.depth >= min_depth) {
if let Some(sb) = stack.pop() {
result.push(GeoPoly::new(sb.exterior, sb.holes));
}
}
}
for lp in polygon.loops() {
let depth = lp.depth();
let ring = loop_to_ring(lp);
if depth % 2 == 0 {
flush_until(&mut stack, &mut result, depth);
stack.push(ShellBuilder {
exterior: ring,
holes: Vec::new(),
depth,
});
} else {
flush_until(&mut stack, &mut result, depth);
if let Some(top) = stack.last_mut() {
top.holes.push(ring);
}
}
}
while let Some(sb) = stack.pop() {
result.push(GeoPoly::new(sb.exterior, sb.holes));
}
MultiPolygon(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::s2::point_vector::PointVector;
use crate::s2::polyline::Polyline;
use crate::s2::{LatLng, Loop, Polygon, Rect, Region};
use geo_types::{
Coord, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point as GeoPoint,
Polygon as GeoPoly, Rect as GeoRect, Triangle,
};
use quickcheck_macros::quickcheck;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-10
}
fn ll(lat: f64, lng: f64) -> LatLng {
LatLng::from_degrees(lat, lng)
}
fn clamp_finite(v: f64) -> f64 {
if v.is_finite() {
v.clamp(-1e10, 1e10)
} else {
0.0
}
}
fn squash(v: f64, max: f64) -> f64 {
let v = clamp_finite(v);
max * v / (1.0 + v.abs())
}
fn safe_coord(lat: f64, lng: f64) -> Coord<f64> {
Coord {
x: squash(lng, 179.0),
y: squash(lat, 89.0),
}
}
#[test]
fn coord_latlng_roundtrip() {
let c = Coord {
x: 2.3522_f64,
y: 48.8566_f64,
}; let ll = LatLng::from(c);
assert!(approx_eq(ll.lat.degrees(), 48.8566));
assert!(approx_eq(ll.lng.degrees(), 2.3522));
let back = Coord::from(ll);
assert!(approx_eq(back.x, c.x));
assert!(approx_eq(back.y, c.y));
}
#[test]
fn geopoint_latlng_roundtrip() {
let p = GeoPoint::new(-0.1278_f64, 51.5074_f64); let ll = LatLng::from(p);
assert!(approx_eq(ll.lat.degrees(), 51.5074));
assert!(approx_eq(ll.lng.degrees(), -0.1278));
let back = GeoPoint::from(ll);
assert!(approx_eq(back.x(), p.x()));
assert!(approx_eq(back.y(), p.y()));
}
#[test]
fn geopoint_s2point_roundtrip() {
let p = GeoPoint::new(139.6917_f64, 35.6895_f64); let s2p = Point::from(p);
let back = GeoPoint::from(s2p);
assert!((back.x() - p.x()).abs() < 1e-13);
assert!((back.y() - p.y()).abs() < 1e-13);
}
#[test]
fn line_to_polyline() {
let line = Line {
start: Coord { x: 0.0, y: 0.0 },
end: Coord { x: 90.0, y: 0.0 },
};
let pl = Polyline::from(line);
assert_eq!(pl.vertices_vec().len(), 2);
}
#[test]
fn linestring_to_polyline_vertex_count() {
let ls = LineString::new(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 90.0, y: 0.0 },
Coord { x: 0.0, y: 90.0 },
]);
let pl = Polyline::from(ls);
assert_eq!(pl.vertices_vec().len(), 3);
}
#[test]
fn polyline_to_linestring_vertex_count() {
let pl = Polyline::new(vec![
ll(0.0, 0.0).to_point(),
ll(0.0, 90.0).to_point(),
ll(90.0, 0.0).to_point(),
]);
let ls = LineString::from(pl);
assert_eq!(ls.0.len(), 3);
}
#[test]
fn multi_linestring_roundtrip_count() {
let mls = MultiLineString(vec![
LineString::new(vec![Coord { x: 0.0, y: 0.0 }, Coord { x: 1.0, y: 0.0 }]),
LineString::new(vec![Coord { x: 2.0, y: 0.0 }, Coord { x: 3.0, y: 0.0 }]),
]);
let pls = multi_linestring_to_polylines(mls);
assert_eq!(pls.len(), 2);
let back = polylines_to_multi_linestring(pls);
assert_eq!(back.0.len(), 2);
}
#[test]
fn multipoint_pointvector_roundtrip() {
let mp = MultiPoint(vec![GeoPoint::new(0.0, 0.0), GeoPoint::new(90.0, 45.0)]);
let pv = PointVector::from(mp);
assert_eq!(pv.points().len(), 2);
let back = MultiPoint::from(pv);
assert_eq!(back.0.len(), 2);
assert!(approx_eq(back.0[1].x(), 90.0));
assert!(approx_eq(back.0[1].y(), 45.0));
}
#[test]
fn triangle_to_loop_vertex_count() {
let t = Triangle(
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 5.0, y: 10.0 },
);
let lp = Loop::from(t);
assert_eq!(lp.vertices().len(), 3);
}
#[test]
fn rect_roundtrip() {
let geo_rect = GeoRect::new(Coord { x: -10.0, y: -20.0 }, Coord { x: 10.0, y: 20.0 });
let s2_rect = Rect::from(geo_rect);
assert!(approx_eq(s2_rect.lo().lat.degrees(), -20.0));
assert!(approx_eq(s2_rect.lo().lng.degrees(), -10.0));
assert!(approx_eq(s2_rect.hi().lat.degrees(), 20.0));
assert!(approx_eq(s2_rect.hi().lng.degrees(), 10.0));
let back = GeoRect::from(s2_rect);
assert!(approx_eq(back.min().x, -10.0));
assert!(approx_eq(back.min().y, -20.0));
assert!(approx_eq(back.max().x, 10.0));
assert!(approx_eq(back.max().y, 20.0));
}
fn square_ring(size: f64) -> LineString<f64> {
let s = size;
LineString::new(vec![
Coord { x: -s, y: -s },
Coord { x: s, y: -s },
Coord { x: s, y: s },
Coord { x: -s, y: s },
Coord { x: -s, y: -s }, ])
}
#[test]
fn geo_polygon_to_s2_polygon_contains_center() {
let geo_poly = GeoPoly::new(square_ring(10.0), vec![]);
let s2_poly = Polygon::from(geo_poly);
let center = ll(0.0, 0.0).to_point();
assert!(s2_poly.contains_point(¢er));
let outside = ll(20.0, 0.0).to_point();
assert!(!s2_poly.contains_point(&outside));
}
#[test]
fn geo_polygon_with_hole() {
let outer = square_ring(10.0);
let inner = square_ring(2.0);
let geo_poly = GeoPoly::new(outer, vec![inner]);
let s2_poly = Polygon::from(geo_poly);
assert!(s2_poly.contains_point(&ll(5.0, 5.0).to_point()));
assert!(!s2_poly.contains_point(&ll(0.0, 0.0).to_point()));
}
#[test]
fn s2_polygon_to_multi_polygon_single_shell() {
let shell = Loop::new(vec![
ll(-10.0, -10.0).to_point(),
ll(-10.0, 10.0).to_point(),
ll(10.0, 10.0).to_point(),
ll(10.0, -10.0).to_point(),
]);
let s2_poly = Polygon::from_loops(vec![shell]);
let multi = MultiPolygon::from(s2_poly);
assert_eq!(multi.0.len(), 1);
assert_eq!(multi.0[0].exterior().0.len(), 5);
}
#[test]
fn s2_polygon_to_multi_polygon_with_hole() {
let outer = Loop::new(vec![
ll(-10.0, -10.0).to_point(),
ll(-10.0, 10.0).to_point(),
ll(10.0, 10.0).to_point(),
ll(10.0, -10.0).to_point(),
]);
let inner = Loop::new(vec![
ll(-2.0, -2.0).to_point(),
ll(-2.0, 2.0).to_point(),
ll(2.0, 2.0).to_point(),
ll(2.0, -2.0).to_point(),
]);
let s2_poly = Polygon::from_loops(vec![outer, inner]);
let multi = MultiPolygon::from(s2_poly);
assert_eq!(multi.0.len(), 1);
assert_eq!(multi.0[0].interiors().len(), 1);
}
#[test]
fn s2_polygon_to_multi_polygon_two_shells() {
let west = Loop::new(vec![
ll(-10.0, -40.0).to_point(),
ll(-10.0, -20.0).to_point(),
ll(10.0, -20.0).to_point(),
ll(10.0, -40.0).to_point(),
]);
let east = Loop::new(vec![
ll(-10.0, 20.0).to_point(),
ll(-10.0, 40.0).to_point(),
ll(10.0, 40.0).to_point(),
ll(10.0, 20.0).to_point(),
]);
let s2_poly = Polygon::from_loops(vec![west, east]);
let multi = MultiPolygon::from(s2_poly);
assert_eq!(multi.0.len(), 2);
assert!(multi.0.iter().all(|p| p.interiors().is_empty()));
}
#[test]
fn polyline_to_line_roundtrip() {
let line = Line {
start: Coord { x: 10.0, y: 20.0 },
end: Coord { x: 30.0, y: 40.0 },
};
let pl = Polyline::from(line);
let back = Line::from(pl);
assert!(approx_eq(back.start.x, 10.0));
assert!(approx_eq(back.start.y, 20.0));
assert!(approx_eq(back.end.x, 30.0));
assert!(approx_eq(back.end.y, 40.0));
}
#[test]
fn linestring_polyline_coordinate_roundtrip() {
let coords = vec![
Coord {
x: -73.9857,
y: 40.7484,
}, Coord {
x: 2.2945,
y: 48.8584,
}, Coord {
x: 139.6917,
y: 35.6895,
}, ];
let ls = LineString::new(coords.clone());
let pl = Polyline::from(ls);
let back = LineString::from(pl);
assert_eq!(back.0.len(), coords.len());
for (got, want) in back.0.iter().zip(&coords) {
assert!(approx_eq(got.x, want.x));
assert!(approx_eq(got.y, want.y));
}
}
#[test]
fn loop_triangle_roundtrip() {
let t = Triangle(
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 5.0, y: 10.0 },
);
let lp = Loop::from(t);
let back = Triangle::from(lp);
assert!(approx_eq(back.v1().x, 0.0));
assert!(approx_eq(back.v1().y, 0.0));
assert!(approx_eq(back.v2().x, 10.0));
assert!(approx_eq(back.v2().y, 0.0));
assert!(approx_eq(back.v3().x, 5.0));
assert!(approx_eq(back.v3().y, 10.0));
}
#[quickcheck]
fn prop_triangle_loop_roundtrip(
lat1: f64,
lng1: f64,
lat2: f64,
lng2: f64,
lat3: f64,
lng3: f64,
) -> bool {
let c = [
safe_coord(lat1, lng1),
safe_coord(lat2, lng2),
safe_coord(lat3, lng3),
];
let t = Triangle(c[0], c[1], c[2]);
let back = Triangle::from(Loop::from(t));
let close =
|a: Coord<f64>, b: Coord<f64>| (a.x - b.x).abs() < 1e-9 && (a.y - b.y).abs() < 1e-9;
close(back.v1(), c[0]) && close(back.v2(), c[1]) && close(back.v3(), c[2])
}
#[test]
fn multi_polygon_to_vec_s2_polygon() {
let mp = MultiPolygon(vec![
GeoPoly::new(square_ring(5.0), vec![]),
GeoPoly::new(square_ring(3.0), vec![]),
]);
let polys = multi_polygon_to_polygons(mp);
assert_eq!(polys.len(), 2);
}
}