#![allow(clippy::pedantic)]
#[cfg(not(tarpaulin_include))]
#[cfg(test)]
mod voronoi {
extern crate pretty_assertions;
use geo::algorithm::cyclic_match::CyclicMatch;
use geo::coords_iter::CoordsIter;
use geo::line_string;
use geo::Geometry;
use geo::LineString;
use geo::MultiPoint;
use geo::Point;
use geo_types::Coord;
use pretty_assertions::assert_eq;
use d3_geo_rs::clip::circle::ClipCircleC;
use d3_geo_rs::data_object::FeatureCollection;
use d3_geo_rs::data_object::FeatureProperty;
use d3_geo_rs::projection::builder::template::ResampleNoPCNC;
use d3_geo_rs::stream::DrainStub;
use d3_geo_voronoi_rs::voronoi::Voronoi;
#[test]
fn two_hemispheres() {
println!("two points leads to two hemispheres.");
let sites = MultiPoint(vec![Point::new(-20f64, -20f64), Point::new(20f64, 20f64)]);
let mut gv: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(Some(Geometry::MultiPoint(sites))) {
Ok(ok) => gv = ok,
Err(_) => {
panic!("could not proceed");
}
}
match gv.polygons(None) {
None => {
unreachable!();
}
Some(FeatureCollection(mut features)) => {
let last_cell = line_string![
Coord {
x: -90.0,
y: 43.21917889371418
},
Coord { x: 180.0, y: -0. },
Coord {
x: 90.0,
y: -43.21917889371418
},
Coord { x: 0., y: 0. },
Coord {
x: -90.,
y: 43.21917889371418
}
];
let g: Geometry<f64> = features.pop().unwrap().geometry.pop().unwrap();
match g {
Geometry::Polygon(p) => {
let ls = p.exterior();
assert_eq!(last_cell, *ls);
}
_ => {
panic!("expecting a polygon");
}
};
let first_cell = line_string![
Coord {
x: 0.0_f64,
y: 0.0_f64
},
Coord {
x: 90.0_f64,
y: -43.21917889371418_f64
},
Coord {
x: 180.0_f64,
y: -0.0_f64
},
Coord {
x: -90_f64,
y: 43.21917889371418_f64
},
Coord { x: 0_f64, y: 0_f64 },
];
let g: Geometry<f64> = features.pop().unwrap().geometry.pop().unwrap();
match g {
Geometry::Polygon(p) => {
let ls = p.exterior();
assert_eq!(first_cell, *ls);
}
_ => {
panic!("expecting a polygon");
}
};
}
}
}
#[test]
pub fn voronoi_polygons_returns_polygons() {
println!("geoVoronoi.polygons(sites) returns polygons.");
let sites = MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
]);
let mut gv: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(Some(Geometry::MultiPoint(sites))) {
Ok(ok) => gv = ok,
Err(_) => {
panic!("could not proceed");
}
};
match gv.polygons(None) {
None => {
panic!("Must return a FeatureCollection<T>.");
}
Some(FeatureCollection(features)) => {
println!("Found a Features Collection.");
let g = &features[0].geometry[0];
match g {
Geometry::Polygon(polygon) => {
let ls = polygon.exterior();
let u = ls.points().next().unwrap();
let v = Point::new(-175f64, -4.981069f64);
assert!((u.x() - v.x()).abs() < 1e-6f64);
assert!((u.y() - v.y()).abs() < 1e-6f64);
}
_ => {
panic!("Expected a polygon object.");
}
}
}
}
}
#[test]
fn polygon_tollerates_nan() {
println!("geoVoronoi.polygons(sites) tolerates NaN.");
let sites = MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(2f64, 1f64),
Point::new(f64::NAN, -1f64),
Point::new(4f64, f64::NAN),
Point::new(5f64, 10f64),
]);
let g = Geometry::MultiPoint(sites);
let mut gv: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(Some(g)) {
Ok(ok) => gv = ok,
Err(_) => {
panic!("could not proceed");
}
};
let _u = gv.polygons(None);
}
#[test]
fn computes_the_hull() {
let sites = MultiPoint(vec![
Point::new(10f64, 0f64),
Point::new(10f64, 10f64),
Point::new(3f64, 5f64),
Point::new(-2f64, 5f64),
Point::new(0f64, 0f64),
]);
let gv = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(None)
{
Ok(gv) => gv,
Err(_) => {
panic!("could not proceed");
}
};
let hull = gv.hull(Some(Geometry::MultiPoint(sites)));
match hull {
Some(polygon) => {
let actual_ls = polygon.exterior();
let expected_ls = LineString::from(vec![
Point::new(10f64, 10f64),
Point::new(10f64, 0f64),
Point::new(0f64, 0f64),
Point::new(-2f64, 5f64),
Point::new(10f64, 10f64),
]);
assert!(actual_ls.is_cyclic_match(expected_ls));
}
None => {
panic!("expecting a polygon");
}
}
}
#[test]
fn computes_the_delaunay_mesh() {
let sites = MultiPoint(vec![
Point::new(10f64, 0f64),
Point::new(10f64, 10f64),
Point::new(3f64, 5f64),
Point::new(-2f64, 5f64),
Point::new(0f64, 0f64),
]);
let gv = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(Some(Geometry::MultiPoint(sites)))
{
Ok(gv) => gv,
Err(_) => {
panic!("could not proceed");
}
};
let mesh = gv.mesh(None);
let golden: Vec<LineString<f64>> = vec![
vec![[3., 5.], [-2., 5.]].into(),
vec![[3., 5.], [0., 0.]].into(),
vec![[-2., 5.], [0., 0.]].into(),
vec![[10., 10.], [-2., 5.]].into(),
vec![[10., 10.], [3., 5.]].into(),
vec![[10., 0.], [3., 5.]].into(),
vec![[10., 0.], [0., 0.]].into(),
vec![[10., 0.], [10., 10.]].into(),
];
match mesh {
Some(mls) => {
assert!(mls.0.len() == golden.len());
for ls in mls {
assert!(golden.contains(&ls), "Linestring not found in golden list.");
}
}
None => {
panic!("Expected the mesh as a MultiLineString.");
}
}
}
#[test]
fn computes_the_polygons_mesh() {
let sites = MultiPoint(vec![
Point::new(10f64, 0f64),
Point::new(10f64, 10f64),
Point::new(3f64, 5f64),
Point::new(-2f64, 5f64),
Point::new(0f64, 0f64),
]);
let ls_string_golden: Vec<String> = vec![
"-175 -5/-175 -5".into(),
"-175 -5/0 3".into(),
"-175 -5/1 15".into(),
"-175 -5/5 0".into(),
"-175 -5/8 5".into(),
"0 3/1 15".into(),
"0 3/5 0".into(),
"1 15/8 5".into(),
"5 0/8 5".into(),
];
let gv = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(Some(Geometry::MultiPoint(sites)))
{
Ok(gv) => gv,
Err(_) => {
panic!("could not proceed");
}
};
let cell_mesh_maybe = gv.cell_mesh(None);
match cell_mesh_maybe {
Some(cell_mesh) => {
let c_string = cell_mesh.iter().map(|ls| {
let d = ls.coords_iter();
let mut e: Vec<String> = d
.map(|p| format!("{} {}", p.x.round(), p.y.round()))
.collect::<Vec<String>>();
e.sort();
e
});
let mut ls_string: Vec<String> = c_string.map(|ls| ls.join("/")).collect();
ls_string.sort();
assert_eq!(ls_string, ls_string_golden);
}
None => {
panic!("expecting a MultiLineString");
}
}
}
#[test]
fn voronoi_finds_p() {
let sites = MultiPoint(vec![
Point::new(10f64, 0f64),
Point::new(10f64, 10f64),
Point::new(3f64, 5f64),
Point::new(-2f64, 5f64),
Point::new(0f64, 0f64),
]);
let mut voro: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(Some(Geometry::MultiPoint(sites.clone()))) {
Ok(ok) => voro = ok,
Err(_) => {
panic!("cannot proceed");
}
};
assert_eq!(
voro.find(
&Coord {
x: 1.0_f64,
y: 1.0_f64
},
None
),
Some(4)
);
let mut voro2: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(Some(Geometry::MultiPoint(sites))) {
Ok(ok) => voro2 = ok,
Err(_) => {
panic!("cannot proceed");
}
};
assert_eq!(
voro2.find(
&Coord {
x: 1.0_f64,
y: 1.0_f64
},
Some(4.0)
),
Some(4)
);
}
#[test]
fn voronoi_link() {
let sites = Geometry::MultiPoint(MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
]));
let mut gv: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(None) {
Ok(ok) => gv = ok,
Err(_) => {
panic!("could not proceed");
}
};
match gv.links(Some(sites)) {
Some(FeatureCollection(features)) => {
let mut out: Vec<f64> = features
.iter()
.map(|d| match d.properties[0] {
FeatureProperty::Source(p) => p.x,
_ => {
panic!("Did not find a source property.");
}
})
.collect();
out.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(out, vec![0.0, 0.0, 10.0])
}
None => {
panic!("Was expecting a feature collection.")
}
}
}
#[test]
fn voronoi_triangles_returns_geojson() {
let sites = Geometry::MultiPoint(MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
]));
let gv = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(None)
{
Ok(gv) => gv,
Err(_) => {
panic!("cannot proceed");
}
};
match gv.triangles(Some(sites)) {
Some(FeatureCollection(features)) => {
assert_eq!(features.len(), 1);
}
None => {
panic!("Was expecting a feature collection.")
}
}
}
#[test]
fn voronoi_links_returns_urquhart_graph() {
let sites = Geometry::MultiPoint(MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
]));
let mut gv: Voronoi<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>;
match Voronoi::new(None) {
Ok(ok) => gv = ok,
Err(_) => {
panic!("could not proceed");
}
};
match gv.links(Some(sites)) {
Some(FeatureCollection(features)) => {
let mut results: Vec<bool> = Vec::new();
for fs in features {
let fs_u: Vec<bool> = fs
.properties
.iter()
.filter_map(|fs| match fs {
FeatureProperty::Urquhart(u) => Some(*u),
_ => None,
})
.collect();
assert_eq!(fs_u.len(), 1);
results.push(fs_u[0]);
}
results.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(results, [false, true, true]);
}
None => {
panic!("Was expecting a feature collection.")
}
}
}
#[test]
fn voronoi_links_returns_circumcenters() {
let sites = Geometry::MultiPoint(MultiPoint(vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
]));
let gv = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(None)
{
Ok(gv) => gv,
Err(_) => {
panic!("could not proceed");
}
};
match gv.triangles(Some(sites)) {
Some(FeatureCollection(features)) => {
println!("features {features:?}");
match &features[0].properties[0] {
FeatureProperty::Circumecenter(u) => {
println!("c {u:?}");
let v = Coord {
x: 5.0_f64,
y: 4.981069_f64,
};
let w = Coord {
x: -180.0_f64 + v.x,
y: -1.0_f64 * v.y,
};
#[allow(clippy::assertions_on_constants)]
if ((u.x - v.x).abs() < 1e-6 && (u.y - v.y).abs() < 1e-6)
|| ((u.x - w.x).abs() < 1e-6 && (u.y - w.y).abs() < 1e-6)
{
debug_assert!(true);
} else {
debug_assert!(false);
}
}
_ => {
panic!("was expecting a circumcenter");
}
}
}
None => {
panic!("Was expecting a feature collection.")
}
}
}
#[test]
fn does_not_list_fake_points() {
println!("geoVoronoi’s delaunay does not list fake points in its triangles");
let sites = vec![
Point::new(0f64, 0f64),
Point::new(10f64, 0f64),
Point::new(0f64, 10f64),
];
let u = match Voronoi::<
ClipCircleC<ResampleNoPCNC<DrainStub<_>, _, _>, _>,
_,
_,
_,
_,
_,
_,
_,
>::new(Some(Geometry::MultiPoint(MultiPoint(sites.clone()))))
{
Ok(u) => u,
Err(_) => {
panic!("could not proceed");
}
};
assert_eq!(
u.delaunay.unwrap().delaunay.triangles.iter().max(),
Some(&(sites.len() - 1usize))
);
}
}