Copilot commented on code in PR #60:
URL:
https://github.com/apache/sedona-spatialbench/pull/60#discussion_r2508555416
##########
spatialbench/tests/geometry_tests.rs:
##########
@@ -0,0 +1,136 @@
+//! Geometry validation tests for generated spatial data.
+//!
+//! This test suite validates that all generated geometries meet quality and
correctness standards:
+//! - **Coordinate validity**: Longitude [-180°, 180°], latitude [-90°, 90°]
+//! - **OGC compliance**: Valid topology, counter-clockwise winding for
exterior rings
+//! - **Precision constraints**: Coordinates limited to 9 decimal places
+//! - **Antimeridian handling**: No dateline crossings in generated polygons
+//!
+//! These tests ensure spatial data quality and compatibility with spatial
databases and GIS tools.
+
+#[cfg(test)]
+mod geometry_tests {
+ use geo::{Validation, Winding};
+ use spatialbench::generators::{BuildingGenerator, TripGenerator};
+ use spatialbench::spatial::geometry::GEOMETRY_PRECISION;
+ use spatialbench::spatial::utils::crosses_dateline;
+
+ #[test]
+ fn test_trip_coordinates_are_valid() {
+ let generator = TripGenerator::new(0.1, 1, 1);
+ let trips: Vec<_> = generator.iter().collect();
+
+ for trip in trips {
+ // Check pickup coordinates are valid
+ assert!(
+ trip.t_pickuploc.x() >= -180.0 && trip.t_pickuploc.x() <=
180.0,
+ "Pickup longitude out of range: {}",
+ trip.t_pickuploc.x()
+ );
+ assert!(
+ trip.t_pickuploc.y() >= -90.0 && trip.t_pickuploc.y() <= 90.0,
+ "Pickup latitude out of range: {}",
+ trip.t_pickuploc.y()
+ );
+
+ // Check dropoff coordinates are valid
+ assert!(
+ trip.t_dropoffloc.x() >= -180.0 && trip.t_dropoffloc.x() <=
180.0,
+ "Dropoff longitude out of range: {}",
+ trip.t_dropoffloc.x()
+ );
+ assert!(
+ trip.t_dropoffloc.y() >= -90.0 && trip.t_dropoffloc.y() <=
90.0,
+ "Dropoff latitude out of range: {}",
+ trip.t_dropoffloc.y()
+ );
+
+ // Check coordinates have proper precision (9 decimal places)
+ let pickup_x_precision =
+ (trip.t_pickuploc.x() * GEOMETRY_PRECISION).round() /
GEOMETRY_PRECISION;
+ assert_eq!(trip.t_pickuploc.x(), pickup_x_precision);
+
+ let dropoff_x_precision =
+ (trip.t_dropoffloc.x() * GEOMETRY_PRECISION).round() /
GEOMETRY_PRECISION;
+ assert_eq!(trip.t_dropoffloc.x(), dropoff_x_precision);
+ }
+ }
+
+ #[test]
+ fn test_building_polygons_are_valid() {
+ let generator = BuildingGenerator::new(10.0, 1, 1);
+ let buildings: Vec<_> = generator.iter().collect();
+
+ for building in buildings {
+ let polygon = &building.b_boundary;
+
+ assert!(
+ polygon.is_valid(),
+ "Building {} has invalid polygon",
+ building.b_buildingkey
+ );
+ }
+ }
+
+ #[test]
+ fn test_building_polygon_winding() {
+ // Create a generator with a small scale factor
+ let generator = BuildingGenerator::new(1.0, 1, 1);
+ let buildings: Vec<_> = generator.iter().collect();
+
+ // Check that all building polygons have counter-clockwise winding
+ for building in buildings.iter() {
+ let exterior = building.b_boundary.exterior();
+ assert!(
+ exterior.is_ccw(),
+ "Building {} polygon should have counter-clockwise winding",
+ building.b_buildingkey
+ );
+ }
+ }
+
+ #[test]
+ fn test_coordinate_precision() {
+ let generator = TripGenerator::new(0.01, 1, 1);
+ let trips: Vec<_> = generator.iter().collect();
+
+ for trip in trips {
+ // Check 8 decimal place precision
+ let pickup_x_str = format!("{:.9}", trip.t_pickuploc.x());
+ let pickup_y_str = format!("{:.9}", trip.t_pickuploc.y());
+ let dropoff_x_str = format!("{:.9}", trip.t_dropoffloc.x());
+ let dropoff_y_str = format!("{:.9}", trip.t_dropoffloc.y());
+
+ // Verify no extra precision beyond 8 decimals
Review Comment:
Comment mentions '8 decimals' but the validation is for 9 decimal places
based on the format string and GEOMETRY_PRECISION constant.
```suggestion
// Check 9 decimal place precision
let pickup_x_str = format!("{:.9}", trip.t_pickuploc.x());
let pickup_y_str = format!("{:.9}", trip.t_pickuploc.y());
let dropoff_x_str = format!("{:.9}", trip.t_dropoffloc.x());
let dropoff_y_str = format!("{:.9}", trip.t_dropoffloc.y());
// Verify no extra precision beyond 9 decimals
```
##########
spatialbench/src/spatial/utils/antimeridian.rs:
##########
@@ -0,0 +1,258 @@
+use geo::{Centroid, LineString, Polygon};
+
+/// Wraps a longitude value to ensure it stays within the valid range of
[-180, 180] degrees.
+///
+/// Longitude is a circular coordinate:
+/// - If longitude exceeds 180°, it wraps around from the eastern hemisphere
back to the western hemisphere.
+/// - If longitude is below -180°, it wraps around from the western hemisphere
back to the eastern hemisphere.
+pub fn wrap_around_longitude(mut lon: f64) -> f64 {
+ while lon > 180.0 {
+ lon -= 360.0;
+ }
+ while lon < -180.0 {
+ lon += 360.0;
+ }
+ lon
+}
+
+/// Checks if a polygon crosses the dateline (antimeridian at ±180°)
+pub fn crosses_dateline(polygon: &Polygon) -> bool {
+ let coords = polygon.exterior().coords();
+ let mut has_east = false;
+ let mut has_west = false;
+
+ for coord in coords {
+ if (coord.x > 90.0 && coord.x <= 180.0) || coord.x < -180.0 {
Review Comment:
Condition `coord.x < -180.0` should not be possible if coordinates are
properly wrapped to [-180, 180] range. This suggests either the function
expects unwrapped input or there's a logical issue with the dateline crossing
detection.
```suggestion
if coord.x > 90.0 && coord.x <= 180.0 {
```
##########
spatialbench/src/spatial/utils/antimeridian.rs:
##########
@@ -0,0 +1,258 @@
+use geo::{Centroid, LineString, Polygon};
+
+/// Wraps a longitude value to ensure it stays within the valid range of
[-180, 180] degrees.
+///
+/// Longitude is a circular coordinate:
+/// - If longitude exceeds 180°, it wraps around from the eastern hemisphere
back to the western hemisphere.
+/// - If longitude is below -180°, it wraps around from the western hemisphere
back to the eastern hemisphere.
+pub fn wrap_around_longitude(mut lon: f64) -> f64 {
+ while lon > 180.0 {
+ lon -= 360.0;
+ }
+ while lon < -180.0 {
+ lon += 360.0;
+ }
+ lon
+}
+
+/// Checks if a polygon crosses the dateline (antimeridian at ±180°)
+pub fn crosses_dateline(polygon: &Polygon) -> bool {
+ let coords = polygon.exterior().coords();
+ let mut has_east = false;
+ let mut has_west = false;
+
+ for coord in coords {
+ if (coord.x > 90.0 && coord.x <= 180.0) || coord.x < -180.0 {
+ has_east = true;
+ }
+ if coord.x > 180.0 || (coord.x >= -180.0 && coord.x < -90.0) {
+ has_west = true;
+ }
+ if has_east && has_west {
Review Comment:
Condition `coord.x > 180.0` should not be possible if coordinates are
properly wrapped to [-180, 180] range. The logic for detecting dateline
crossing appears inconsistent with the function's documented purpose.
```suggestion
let coords: Vec<_> = polygon.exterior().coords().collect();
if coords.len() < 2 {
return false;
}
for i in 0..coords.len() - 1 {
let lon1 = coords[i].x;
let lon2 = coords[i + 1].x;
if (lon1 - lon2).abs() > 180.0 {
```
##########
spatialbench/tests/geometry_tests.rs:
##########
@@ -0,0 +1,136 @@
+//! Geometry validation tests for generated spatial data.
+//!
+//! This test suite validates that all generated geometries meet quality and
correctness standards:
+//! - **Coordinate validity**: Longitude [-180°, 180°], latitude [-90°, 90°]
+//! - **OGC compliance**: Valid topology, counter-clockwise winding for
exterior rings
+//! - **Precision constraints**: Coordinates limited to 9 decimal places
+//! - **Antimeridian handling**: No dateline crossings in generated polygons
+//!
+//! These tests ensure spatial data quality and compatibility with spatial
databases and GIS tools.
+
+#[cfg(test)]
+mod geometry_tests {
+ use geo::{Validation, Winding};
+ use spatialbench::generators::{BuildingGenerator, TripGenerator};
+ use spatialbench::spatial::geometry::GEOMETRY_PRECISION;
+ use spatialbench::spatial::utils::crosses_dateline;
+
+ #[test]
+ fn test_trip_coordinates_are_valid() {
+ let generator = TripGenerator::new(0.1, 1, 1);
+ let trips: Vec<_> = generator.iter().collect();
+
+ for trip in trips {
+ // Check pickup coordinates are valid
+ assert!(
+ trip.t_pickuploc.x() >= -180.0 && trip.t_pickuploc.x() <=
180.0,
+ "Pickup longitude out of range: {}",
+ trip.t_pickuploc.x()
+ );
+ assert!(
+ trip.t_pickuploc.y() >= -90.0 && trip.t_pickuploc.y() <= 90.0,
+ "Pickup latitude out of range: {}",
+ trip.t_pickuploc.y()
+ );
+
+ // Check dropoff coordinates are valid
+ assert!(
+ trip.t_dropoffloc.x() >= -180.0 && trip.t_dropoffloc.x() <=
180.0,
+ "Dropoff longitude out of range: {}",
+ trip.t_dropoffloc.x()
+ );
+ assert!(
+ trip.t_dropoffloc.y() >= -90.0 && trip.t_dropoffloc.y() <=
90.0,
+ "Dropoff latitude out of range: {}",
+ trip.t_dropoffloc.y()
+ );
+
+ // Check coordinates have proper precision (9 decimal places)
+ let pickup_x_precision =
+ (trip.t_pickuploc.x() * GEOMETRY_PRECISION).round() /
GEOMETRY_PRECISION;
+ assert_eq!(trip.t_pickuploc.x(), pickup_x_precision);
+
+ let dropoff_x_precision =
+ (trip.t_dropoffloc.x() * GEOMETRY_PRECISION).round() /
GEOMETRY_PRECISION;
+ assert_eq!(trip.t_dropoffloc.x(), dropoff_x_precision);
+ }
+ }
+
+ #[test]
+ fn test_building_polygons_are_valid() {
+ let generator = BuildingGenerator::new(10.0, 1, 1);
+ let buildings: Vec<_> = generator.iter().collect();
+
+ for building in buildings {
+ let polygon = &building.b_boundary;
+
+ assert!(
+ polygon.is_valid(),
+ "Building {} has invalid polygon",
+ building.b_buildingkey
+ );
+ }
+ }
+
+ #[test]
+ fn test_building_polygon_winding() {
+ // Create a generator with a small scale factor
+ let generator = BuildingGenerator::new(1.0, 1, 1);
+ let buildings: Vec<_> = generator.iter().collect();
+
+ // Check that all building polygons have counter-clockwise winding
+ for building in buildings.iter() {
+ let exterior = building.b_boundary.exterior();
+ assert!(
+ exterior.is_ccw(),
+ "Building {} polygon should have counter-clockwise winding",
+ building.b_buildingkey
+ );
+ }
+ }
+
+ #[test]
+ fn test_coordinate_precision() {
+ let generator = TripGenerator::new(0.01, 1, 1);
+ let trips: Vec<_> = generator.iter().collect();
+
+ for trip in trips {
+ // Check 8 decimal place precision
Review Comment:
Comment mentions '8 decimal place precision' but the actual precision being
validated is 9 decimal places (format!(\"{:.9}\") and GEOMETRY_PRECISION =
1_000_000_000.0).
```suggestion
// Check 9 decimal place precision
```
##########
spatialbench/src/spatial/utils/antimeridian.rs:
##########
@@ -0,0 +1,258 @@
+use geo::{Centroid, LineString, Polygon};
+
+/// Wraps a longitude value to ensure it stays within the valid range of
[-180, 180] degrees.
+///
+/// Longitude is a circular coordinate:
+/// - If longitude exceeds 180°, it wraps around from the eastern hemisphere
back to the western hemisphere.
+/// - If longitude is below -180°, it wraps around from the western hemisphere
back to the eastern hemisphere.
+pub fn wrap_around_longitude(mut lon: f64) -> f64 {
+ while lon > 180.0 {
+ lon -= 360.0;
+ }
+ while lon < -180.0 {
+ lon += 360.0;
+ }
+ lon
+}
+
+/// Checks if a polygon crosses the dateline (antimeridian at ±180°)
+pub fn crosses_dateline(polygon: &Polygon) -> bool {
+ let coords = polygon.exterior().coords();
+ let mut has_east = false;
+ let mut has_west = false;
+
+ for coord in coords {
+ if (coord.x > 90.0 && coord.x <= 180.0) || coord.x < -180.0 {
+ has_east = true;
+ }
+ if coord.x > 180.0 || (coord.x >= -180.0 && coord.x < -90.0) {
+ has_west = true;
+ }
+ if has_east && has_west {
+ return true;
+ }
+ }
+ false
+}
+
+/// Clamps a polygon's longitude coordinates to one side of the antimeridian
(±180°).
+///
+/// When a polygon crosses the dateline, this function constrains its
coordinates to remain
+/// on either the eastern (0° to 180°) or western (-180° to 0°) hemisphere
based on where
+/// the polygon's centroid is located.
+///
+/// # Behavior
+/// - If the centroid is in the eastern hemisphere (≥ 0°), coordinates are
clamped to [0°, 180°]
+/// or kept at their original values if already within [-180°, 0°]
+/// - If the centroid is in the western hemisphere (< 0°), coordinates are
clamped to [-180°, 0°]
+/// or kept at their original values if already within [0°, 180°]
+/// - Latitude values (y-coordinates) remain unchanged
+///
+pub fn clamp_polygon_to_dateline(polygon: &Polygon) -> Polygon {
+ let centroid = polygon.centroid().expect("Polygon should have centroid");
+ let east_bound = centroid.x() >= 0.0;
+ let keep_east = (centroid.x() >= 0.0 && centroid.x() <= 180.0) ||
(centroid.x() < -180.0);
Review Comment:
Condition `centroid.x() < -180.0` should never be true if coordinates are
normalized to [-180, 180] range. This suggests the function may not handle all
edge cases correctly.
```suggestion
let centroid_lon = wrap_around_longitude(centroid.x());
let east_bound = centroid_lon >= 0.0;
let keep_east = centroid_lon >= 0.0 && centroid_lon <= 180.0;
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]