use super::*;
use crate::{Resolution, math::FloatAdder};
use approx::assert_relative_eq;
use geo::{CoordsIter as _, ToRadians as _};
macro_rules! world_test {
($name:ident, $resolution: literal) => {
#[test]
fn $name() {
let resolution =
Resolution::try_from($resolution).expect("resolution");
for base in CellIndex::base_cells() {
for cell in base.children(resolution) {
assert_cell(cell);
}
}
}
};
}
world_test!(entire_world_res0, 0);
world_test!(entire_world_res1, 1);
world_test!(entire_world_res2, 2);
#[test]
fn resolution15() {
assert_cell(CellIndex::try_from(0x8f754e64992d6d8).unwrap());
}
#[test]
fn all_pentagons() {
for res in Resolution::range(Resolution::Zero, Resolution::Fifteen) {
for cell in res.pentagons() {
assert_cell(cell);
}
}
}
#[test]
fn three_polygons() {
#[rustfmt::skip]
let cells = [
0x8027fffffffffff, 0x802bfffffffffff, 0x804dfffffffffff,
0x8067fffffffffff, 0x806dfffffffffff, 0x8049fffffffffff,
0x805ffffffffffff, 0x8057fffffffffff, 0x807dfffffffffff,
0x80a5fffffffffff, 0x80a9fffffffffff, 0x808bfffffffffff,
0x801bfffffffffff, 0x8035fffffffffff, 0x803ffffffffffff,
0x8053fffffffffff, 0x8043fffffffffff, 0x8021fffffffffff,
0x8011fffffffffff, 0x801ffffffffffff, 0x8097fffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
let expected_area = 2.2440497074541694;
assert_eq!(mpoly.0.len(), 3, "3 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 3, "with 3 holes");
assert_eq!(mpoly[1].num_interior_rings(), 1, "with 1 holes");
assert_eq!(mpoly[2].num_interior_rings(), 0, "with 0 holes");
let area = multipolygon_area(&mpoly);
assert_relative_eq!(area, expected_area, epsilon = 1e-15);
}
#[test]
fn h3_issue_1049() {
#[rustfmt::skip]
let cells = [
0x827487fffffffff, 0x82748ffffffffff, 0x827497fffffffff,
0x82749ffffffffff, 0x8274affffffffff, 0x8274c7fffffffff,
0x8274cffffffffff, 0x8274d7fffffffff, 0x8274e7fffffffff,
0x8274effffffffff, 0x8274f7fffffffff, 0x82754ffffffffff,
0x827c07fffffffff, 0x827c27fffffffff, 0x827c2ffffffffff,
0x827c37fffffffff, 0x827d87fffffffff, 0x827d8ffffffffff,
0x827d97fffffffff, 0x827d9ffffffffff, 0x827da7fffffffff,
0x827daffffffffff, 0x82801ffffffffff, 0x8280a7fffffffff,
0x8280affffffffff, 0x8280b7fffffffff, 0x828197fffffffff,
0x82819ffffffffff, 0x8281a7fffffffff, 0x8281b7fffffffff,
0x828207fffffffff, 0x82820ffffffffff, 0x828227fffffffff,
0x82822ffffffffff, 0x8282e7fffffffff, 0x828307fffffffff,
0x82830ffffffffff, 0x82831ffffffffff, 0x82832ffffffffff,
0x828347fffffffff, 0x82834ffffffffff, 0x828357fffffffff,
0x82835ffffffffff, 0x828367fffffffff, 0x828377fffffffff,
0x82a447fffffffff, 0x82a457fffffffff, 0x82a45ffffffffff,
0x82a467fffffffff, 0x82a46ffffffffff, 0x82a477fffffffff,
0x82a4c7fffffffff, 0x82a4cffffffffff, 0x82a4d7fffffffff,
0x82a4e7fffffffff, 0x82a4effffffffff, 0x82a4f7fffffffff,
0x82a547fffffffff, 0x82a54ffffffffff, 0x82a557fffffffff,
0x82a55ffffffffff, 0x82a567fffffffff, 0x82a577fffffffff,
0x82a837fffffffff, 0x82a897fffffffff, 0x82a8a7fffffffff,
0x82a8b7fffffffff, 0x82a917fffffffff, 0x82a927fffffffff,
0x82a937fffffffff, 0x82a987fffffffff, 0x82a98ffffffffff,
0x82a997fffffffff, 0x82a99ffffffffff, 0x82a9a7fffffffff,
0x82a9affffffffff, 0x82ac47fffffffff, 0x82ac57fffffffff,
0x82ac5ffffffffff, 0x82ac67fffffffff, 0x82ac6ffffffffff,
0x82ac77fffffffff, 0x82ad47fffffffff, 0x82ad4ffffffffff,
0x82ad57fffffffff, 0x82ad5ffffffffff, 0x82ad67fffffffff,
0x82ad77fffffffff, 0x82c207fffffffff, 0x82c217fffffffff,
0x82c227fffffffff, 0x82c237fffffffff, 0x82c287fffffffff,
0x82c28ffffffffff, 0x82c29ffffffffff, 0x82c2a7fffffffff,
0x82c2affffffffff, 0x82c2b7fffffffff, 0x82c307fffffffff,
0x82c317fffffffff, 0x82c31ffffffffff, 0x82c337fffffffff,
0x82cfb7fffffffff, 0x82d0c7fffffffff, 0x82d0d7fffffffff,
0x82d0dffffffffff, 0x82d0e7fffffffff, 0x82d0f7fffffffff,
0x82d147fffffffff, 0x82d157fffffffff, 0x82d15ffffffffff,
0x82d167fffffffff, 0x82d177fffffffff, 0x82d187fffffffff,
0x82d18ffffffffff, 0x82d197fffffffff, 0x82d19ffffffffff,
0x82d1a7fffffffff, 0x82d1affffffffff, 0x82dc47fffffffff,
0x82dc57fffffffff, 0x82dc5ffffffffff, 0x82dc67fffffffff,
0x82dc6ffffffffff, 0x82dc77fffffffff, 0x82dcc7fffffffff,
0x82dccffffffffff, 0x82dcd7fffffffff, 0x82dce7fffffffff,
0x82dceffffffffff, 0x82dcf7fffffffff, 0x82dd1ffffffffff,
0x82dd47fffffffff, 0x82dd4ffffffffff, 0x82dd57fffffffff,
0x82dd5ffffffffff, 0x82dd6ffffffffff, 0x82dd87fffffffff,
0x82dd8ffffffffff, 0x82dd97fffffffff, 0x82dd9ffffffffff,
0x82ddaffffffffff, 0x82ddb7fffffffff, 0x82dec7fffffffff,
0x82decffffffffff, 0x82ded7fffffffff, 0x82dee7fffffffff,
0x82deeffffffffff, 0x82def7fffffffff, 0x82df0ffffffffff,
0x82df1ffffffffff, 0x82df47fffffffff, 0x82df4ffffffffff,
0x82df57fffffffff, 0x82df5ffffffffff, 0x82df77fffffffff,
0x82df8ffffffffff, 0x82df9ffffffffff, 0x82e6c7fffffffff,
0x82e6cffffffffff, 0x82e6d7fffffffff, 0x82e6dffffffffff,
0x82e6effffffffff, 0x82e6f7fffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 12, "12 polygons");
assert!(
mpoly
.iter()
.all(|polygon| polygon.num_interior_rings() == 0),
"with no holes"
);
}
#[test]
fn equator_cells() {
#[rustfmt::skip]
let cells = [
0x81807ffffffffff, 0x817efffffffffff, 0x81723ffffffffff,
0x817ebffffffffff, 0x817c3ffffffffff, 0x817e3ffffffffff,
0x817a3ffffffffff, 0x8166fffffffffff, 0x8172bffffffffff,
0x816afffffffffff, 0x81933ffffffffff, 0x8168fffffffffff,
0x8188fffffffffff, 0x81853ffffffffff, 0x817f7ffffffffff,
0x8180bffffffffff, 0x81783ffffffffff, 0x81743ffffffffff,
0x8170bffffffffff, 0x8173bffffffffff, 0x8179bffffffffff,
0x817cbffffffffff, 0x8188bffffffffff, 0x81857ffffffffff,
0x816f7ffffffffff, 0x8177bffffffffff, 0x81617ffffffffff,
0x816f3ffffffffff, 0x8174bffffffffff, 0x8180fffffffffff,
0x817a7ffffffffff, 0x81767ffffffffff, 0x81757ffffffffff,
0x81957ffffffffff, 0x81787ffffffffff, 0x81847ffffffffff,
0x81653ffffffffff, 0x817bbffffffffff, 0x816cfffffffffff,
0x816abffffffffff, 0x815f3ffffffffff, 0x817c7ffffffffff,
0x8168bffffffffff, 0x818cbffffffffff, 0x818cfffffffffff,
0x818afffffffffff, 0x8174fffffffffff, 0x8172fffffffffff,
0x8170fffffffffff, 0x816fbffffffffff, 0x81657ffffffffff,
0x816c7ffffffffff, 0x8186bffffffffff, 0x81763ffffffffff,
0x818a7ffffffffff, 0x8186fffffffffff, 0x81707ffffffffff,
0x8182bffffffffff, 0x818f3ffffffffff, 0x8182fffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 1, "1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 1, "with 1 holes");
}
#[test]
fn prime_meridian() {
#[rustfmt::skip]
let cells = [
0x81efbffffffffff, 0x81c07ffffffffff, 0x81d1bffffffffff,
0x81097ffffffffff, 0x8109bffffffffff, 0x81d0bffffffffff,
0x81987ffffffffff, 0x81017ffffffffff, 0x81e67ffffffffff,
0x81ddbffffffffff, 0x81ac7ffffffffff, 0x8158bffffffffff,
0x81397ffffffffff, 0x81593ffffffffff, 0x81c17ffffffffff,
0x81827ffffffffff, 0x81197ffffffffff, 0x81eebffffffffff,
0x81383ffffffffff, 0x81dcbffffffffff, 0x81757ffffffffff,
0x81093ffffffffff, 0x81073ffffffffff, 0x8159bffffffffff,
0x81f17ffffffffff, 0x81187ffffffffff, 0x81007ffffffffff,
0x81997ffffffffff, 0x81753ffffffffff, 0x81033ffffffffff,
0x81f2bffffffffff, 0x8138bffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 1, "1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 0, "with 0 holes");
}
#[test]
fn anti_meridian() {
#[rustfmt::skip]
let cells = [
0x817ebffffffffff, 0x8133bffffffffff, 0x81047ffffffffff,
0x81f3bffffffffff, 0x81dbbffffffffff, 0x8132bffffffffff,
0x810cbffffffffff, 0x81bb3ffffffffff, 0x81db3ffffffffff,
0x81bafffffffffff, 0x81177ffffffffff, 0x817fbffffffffff,
0x81ba3ffffffffff, 0x815abffffffffff, 0x815bbffffffffff,
0x81eafffffffffff, 0x81ed7ffffffffff, 0x81057ffffffffff,
0x819a7ffffffffff, 0x81eabffffffffff, 0x819b7ffffffffff,
0x81167ffffffffff, 0x81227ffffffffff, 0x8171bffffffffff,
0x81237ffffffffff, 0x810dbffffffffff, 0x81033ffffffffff,
0x81f2bffffffffff, 0x8147bffffffffff, 0x81f33ffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 1, "1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 0, "with 0 holes");
}
#[test]
fn both_meridians() {
#[rustfmt::skip]
let cells = [
0x81efbffffffffff, 0x81c07ffffffffff, 0x81d1bffffffffff,
0x81097ffffffffff, 0x817ebffffffffff, 0x8133bffffffffff,
0x81047ffffffffff, 0x8109bffffffffff, 0x81f3bffffffffff,
0x81d0bffffffffff, 0x81987ffffffffff, 0x81dbbffffffffff,
0x81017ffffffffff, 0x81e67ffffffffff, 0x81ddbffffffffff,
0x8132bffffffffff, 0x810cbffffffffff, 0x81bb3ffffffffff,
0x81ac7ffffffffff, 0x81db3ffffffffff, 0x8158bffffffffff,
0x81397ffffffffff, 0x81593ffffffffff, 0x81bafffffffffff,
0x81177ffffffffff, 0x817fbffffffffff, 0x81ba3ffffffffff,
0x81c17ffffffffff, 0x815abffffffffff, 0x81827ffffffffff,
0x815bbffffffffff, 0x81eafffffffffff, 0x81197ffffffffff,
0x81ed7ffffffffff, 0x81eebffffffffff, 0x81383ffffffffff,
0x81057ffffffffff, 0x819a7ffffffffff, 0x81dcbffffffffff,
0x81757ffffffffff, 0x81eabffffffffff, 0x81093ffffffffff,
0x819b7ffffffffff, 0x81073ffffffffff, 0x8159bffffffffff,
0x8147bffffffffff, 0x81167ffffffffff, 0x81f17ffffffffff,
0x8171bffffffffff, 0x81227ffffffffff, 0x81187ffffffffff,
0x81237ffffffffff, 0x81007ffffffffff, 0x810dbffffffffff,
0x81997ffffffffff, 0x81753ffffffffff, 0x81033ffffffffff,
0x81f2bffffffffff, 0x8138bffffffffff, 0x81f33ffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 1, "1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 1, "with 1 holes");
}
#[test]
fn meridians_and_equator() {
#[rustfmt::skip]
let cells = [
0x817c3ffffffffff, 0x81047ffffffffff, 0x8188fffffffffff,
0x817f7ffffffffff, 0x8180bffffffffff, 0x81177ffffffffff,
0x817fbffffffffff, 0x8188bffffffffff, 0x815bbffffffffff,
0x81eafffffffffff, 0x816f3ffffffffff, 0x817a7ffffffffff,
0x819a7ffffffffff, 0x81757ffffffffff, 0x817bbffffffffff,
0x816cfffffffffff, 0x8168bffffffffff, 0x81237ffffffffff,
0x818afffffffffff, 0x8172fffffffffff, 0x816fbffffffffff,
0x81657ffffffffff, 0x81763ffffffffff, 0x818a7ffffffffff,
0x81eabffffffffff, 0x8138bffffffffff, 0x8182fffffffffff,
0x81c07ffffffffff, 0x8109bffffffffff, 0x8166fffffffffff,
0x81987ffffffffff, 0x8172bffffffffff, 0x8168fffffffffff,
0x81853ffffffffff, 0x810cbffffffffff, 0x81bb3ffffffffff,
0x81db3ffffffffff, 0x81743ffffffffff, 0x81bafffffffffff,
0x8179bffffffffff, 0x818f3ffffffffff, 0x81857ffffffffff,
0x816f7ffffffffff, 0x8177bffffffffff, 0x8174bffffffffff,
0x81eebffffffffff, 0x81383ffffffffff, 0x81767ffffffffff,
0x81787ffffffffff, 0x819b7ffffffffff, 0x8159bffffffffff,
0x8171bffffffffff, 0x818cbffffffffff, 0x818cfffffffffff,
0x8170fffffffffff, 0x81707ffffffffff, 0x8147bffffffffff,
0x81167ffffffffff, 0x81f33ffffffffff, 0x817efffffffffff,
0x81f3bffffffffff, 0x81017ffffffffff, 0x816afffffffffff,
0x81e67ffffffffff, 0x81ddbffffffffff, 0x8132bffffffffff,
0x8170bffffffffff, 0x81ba3ffffffffff, 0x81c17ffffffffff,
0x815abffffffffff, 0x81617ffffffffff, 0x8180fffffffffff,
0x81dcbffffffffff, 0x81957ffffffffff, 0x81093ffffffffff,
0x81847ffffffffff, 0x81653ffffffffff, 0x81073ffffffffff,
0x8174fffffffffff, 0x810dbffffffffff, 0x81997ffffffffff,
0x816c7ffffffffff, 0x81033ffffffffff, 0x8186bffffffffff,
0x81f2bffffffffff, 0x81efbffffffffff, 0x81807ffffffffff,
0x81d1bffffffffff, 0x81097ffffffffff, 0x817ebffffffffff,
0x81723ffffffffff, 0x8133bffffffffff, 0x817e3ffffffffff,
0x817a3ffffffffff, 0x81d0bffffffffff, 0x81dbbffffffffff,
0x81933ffffffffff, 0x81783ffffffffff, 0x81ac7ffffffffff,
0x8158bffffffffff, 0x81397ffffffffff, 0x81593ffffffffff,
0x8173bffffffffff, 0x817cbffffffffff, 0x81827ffffffffff,
0x81197ffffffffff, 0x81ed7ffffffffff, 0x81057ffffffffff,
0x816abffffffffff, 0x815f3ffffffffff, 0x81f17ffffffffff,
0x81227ffffffffff, 0x817c7ffffffffff, 0x81007ffffffffff,
0x81753ffffffffff, 0x8186fffffffffff, 0x8182bffffffffff,
0x81187ffffffffff,
]
.into_iter()
.map(|value| CellIndex::try_from(value).unwrap())
.collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 1, "1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 3, "with 3 holes");
}
#[test]
fn empty() {
let cells = Vec::new();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 0, "no geometry");
}
#[test]
#[expect(clippy::float_cmp, reason = "on purpose")]
fn world() {
let cells = CellIndex::base_cells().collect::<Vec<_>>();
let mpoly = dissolve(cells);
assert_eq!(mpoly.0.len(), 8, "8 triangles");
for polygon in &mpoly {
assert_eq!(polygon.num_interior_rings(), 0, "no holes");
assert_eq!(polygon.exterior().coords_count(), 4, "triangle");
}
let area = multipolygon_area(&mpoly);
assert_eq!(area, 4. * PI, "exact area expected");
}
#[test]
fn duplicate_cells() {
let cells = [
CellIndex::try_from(0x81efbffffffffff).unwrap(),
CellIndex::try_from(0x81efbffffffffff).unwrap(),
CellIndex::try_from(0x81efbffffffffff).unwrap(),
];
let result = ArcSet::new(cells, true).unwrap_err();
assert_eq!(result, DissolutionError::DuplicateInput);
}
#[test]
fn heterogenous_resolution() {
let cells = [
CellIndex::try_from(0x8027fffffffffff).unwrap(),
CellIndex::try_from(0x81efbffffffffff).unwrap(),
];
let result = ArcSet::new(cells, true).unwrap_err();
assert_eq!(result, DissolutionError::UnsupportedResolution);
}
fn linestring_area(ring: &LineString) -> f64 {
let area = linear_ring_area(&ring.0);
assert!(area >= 0., "ring area should be positive");
assert!(area < 4. * PI, "ring area should be smaller than the world");
assert!(ring.coords_count() >= 4, "ring should have 3+ coords");
area
}
fn polygon_area(polygon: &Polygon) -> f64 {
let mut adder = FloatAdder::default();
adder += linestring_area(polygon.exterior());
for hole in polygon.interiors() {
adder += linestring_area(hole);
adder += -4. * PI;
}
adder.into()
}
fn multipolygon_area(mpoly: &MultiPolygon) -> f64 {
mpoly
.iter()
.fold(FloatAdder::default(), |mut adder, polygon| {
adder += polygon_area(polygon);
adder
})
.into()
}
fn dissolve(cells: Vec<CellIndex>) -> MultiPolygon {
let expected_area = cells
.iter()
.fold(FloatAdder::default(), |mut adder, cell| {
adder += cell.area_rads2();
adder
})
.into();
let mut mpoly = MultiPolygon::from(ArcSet::new(cells, true).unwrap());
mpoly.to_radians_in_place();
let area = multipolygon_area(&mpoly);
assert!(
mpoly.iter().is_sorted_by_key(|polygon| core::cmp::Reverse(
OrderedFloat(linestring_area(polygon.exterior()))
)),
"polygons should be ordered by area enclosed by outer loop, decreasing"
);
for polygon in &mpoly {
assert_polygon(polygon);
}
assert_relative_eq!(area, expected_area, epsilon = 1e-8);
mpoly
}
fn assert_polygon(polygon: &Polygon) {
let area = polygon_area(polygon);
let outer_area = linestring_area(polygon.exterior());
assert!(area >= 0., "polygon area should be positive");
assert!(area < 4. * PI, "polygon should be smaller than the world");
assert!(area <= outer_area, "total area should be <= the outer loop");
if let Some(hole) = polygon.interiors().first() {
assert!(
outer_area <= linestring_area(hole),
"outer loop should have 'less' area than first hole."
);
}
assert!(
polygon
.interiors()
.is_sorted_by_key(|hole| OrderedFloat(linestring_area(hole))),
"polygon holes should be ordered by area, increasing"
);
}
fn assert_cell(cell: CellIndex) {
let mpoly = dissolve(vec![cell]);
assert_eq!(mpoly.0.len(), 1, "exactly 1 polygon");
assert_eq!(mpoly[0].num_interior_rings(), 0, "cell has zero holes");
let exterior = mpoly[0].exterior();
assert!(exterior.coords_count() >= 6, "at least 5 vertices");
assert!(exterior.coords_count() <= 11, "at most 10 vertices");
}