Skip to content

Commit

Permalink
Merge pull request #195 from astronomy-commons/sandro/check-polygon-d…
Browse files Browse the repository at this point in the history
…egeneracy

Check for polygon degeneracy
  • Loading branch information
camposandro authored Jan 18, 2024
2 parents d001db3 + f3e559d commit 3358fbc
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/hipscat/catalog/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
SphericalCoordinates,
filter_pixels_by_polygon,
)
from hipscat.pixel_math.validators import validate_declination_values, validate_radius
from hipscat.pixel_math.validators import validate_declination_values, validate_polygon, validate_radius


class Catalog(HealpixDataset):
Expand Down Expand Up @@ -93,6 +93,7 @@ def filter_by_polygon(self, vertices: List[SphericalCoordinates] | List[Cartesia
# Get the coordinates vector on the unit sphere if we were provided
# with polygon spherical coordinates of ra and dec
vertices = hp.ang2vec(ra, dec, lonlat=True)
validate_polygon(vertices)
return self.filter_from_pixel_list(filter_pixels_by_polygon(self.pixel_tree, vertices))

def filter_from_pixel_list(self, pixels: List[HealpixPixel]) -> Catalog:
Expand Down
47 changes: 47 additions & 0 deletions src/hipscat/pixel_math/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class ValidatorsErrors(str, Enum):
"""Error messages for the coordinate validators"""
INVALID_DEC = "declination must be in the -90.0 to 90.0 degree range"
INVALID_RADIUS = "cone radius must be positive"
INVALID_NUM_VERTICES = "polygon must contain a minimum of 3 vertices"
DUPLICATE_VERTICES = "polygon has duplicated vertices"
DEGENERATE_POLYGON = "polygon is degenerate"


def validate_radius(radius: float):
Expand Down Expand Up @@ -38,3 +41,47 @@ def validate_declination_values(dec: float | List[float]):
lower_bound, upper_bound = -90., 90.
if not np.all((dec_values >= lower_bound) & (dec_values <= upper_bound)):
raise ValueError(ValidatorsErrors.INVALID_DEC.value)


def validate_polygon(vertices: np.ndarray):
"""Checks if the polygon contain a minimum of three vertices, that they are
unique and that the polygon does not fall on a great circle.
Arguments:
vertices (np.ndarray): The polygon vertices, in cartesian coordinates
Raises:
ValueError exception if the polygon is invalid.
"""
if len(vertices) < 3:
raise ValueError(ValidatorsErrors.INVALID_NUM_VERTICES.value)
if len(vertices) != len(np.unique(vertices, axis=0)):
raise ValueError(ValidatorsErrors.DUPLICATE_VERTICES.value)
if is_polygon_degenerate(vertices):
raise ValueError(ValidatorsErrors.DEGENERATE_POLYGON.value)


def is_polygon_degenerate(vertices: np.ndarray) -> bool:
"""Checks if all the vertices of the polygon are contained in a same plane.
If the plane intersects the center of the sphere, the polygon is degenerate.
Arguments:
vertices (np.ndarray): The polygon vertices, in cartesian coordinates
Returns:
A boolean, which is True if the polygon is degenerate, i.e. if it falls
on a great circle, False otherwise.
"""
# Calculate the normal vector of the plane using three of the vertices
normal_vector = np.cross(vertices[1] - vertices[0], vertices[2] - vertices[0])

# Check if the other vertices lie on the same plane
for vertex in vertices[3:]:
dot_product = np.dot(normal_vector, vertex - vertices[0])
if not np.isclose(dot_product, 0):
return False

# Check if the plane intersects the sphere's center. If it does,
# the polygon is degenerate and therefore, invalid.
center_distance = np.dot(normal_vector, vertices[0])
return bool(np.isclose(center_distance, 0))
22 changes: 20 additions & 2 deletions tests/hipscat/catalog/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_polygonal_filter(small_sky_order1_catalog):
assert filtered_catalog.catalog_info.total_rows is None


def test_polygon_filter_with_cartesian_coordinates(small_sky_order1_catalog):
def test_polygonal_filter_with_cartesian_coordinates(small_sky_order1_catalog):
sky_vertices = [(282, -58), (282, -55), (272, -55), (272, -58)]
cartesian_vertices = hp.ang2vec(*np.array(sky_vertices).T, lonlat=True)
filtered_catalog_1 = small_sky_order1_catalog.filter_by_polygon(sky_vertices)
Expand Down Expand Up @@ -192,7 +192,7 @@ def test_polygonal_filter_empty(small_sky_order1_catalog):
assert len(filtered_catalog.pixel_tree) == 1


def test_polygonal_filter_invalid_polygon_coordinates(small_sky_order1_catalog):
def test_polygonal_filter_invalid_coordinates(small_sky_order1_catalog):
# Declination is over 90 degrees
polygon_vertices = [(47.1, -100), (64.5, -100), (64.5, 6.27), (47.1, 6.27)]
with pytest.raises(ValueError, match=ValidatorsErrors.INVALID_DEC):
Expand All @@ -202,6 +202,24 @@ def test_polygonal_filter_invalid_polygon_coordinates(small_sky_order1_catalog):
small_sky_order1_catalog.filter_by_polygon(polygon_vertices)


def test_polygonal_filter_invalid_polygon(small_sky_order1_catalog):
# The polygon must have a minimum of 3 vertices
with pytest.raises(ValueError, match=ValidatorsErrors.INVALID_NUM_VERTICES):
vertices = [(100.1, -20.3), (100.1, 40.3)]
small_sky_order1_catalog.filter_by_polygon(vertices[:2])
# The vertices should not have duplicates
with pytest.raises(ValueError, match=ValidatorsErrors.DUPLICATE_VERTICES):
vertices = [(100.1, -20.3), (100.1, -20.3), (280.1, -20.3), (280.1, 40.3)]
small_sky_order1_catalog.filter_by_polygon(vertices)
# The polygons should not be on a great circle
with pytest.raises(ValueError, match=ValidatorsErrors.DEGENERATE_POLYGON):
vertices = [(100.1, 40.3), (100.1, -20.3), (280.1, -20.3), (280.1, 40.3)]
small_sky_order1_catalog.filter_by_polygon(vertices)
with pytest.raises(ValueError, match=ValidatorsErrors.DEGENERATE_POLYGON):
vertices = [(50.1, 0), (100.1, 0), (150.1, 0), (200.1, 0)]
small_sky_order1_catalog.filter_by_polygon(vertices)


def test_empty_directory(tmp_path):
"""Test loading empty or incomplete data"""
## Path doesn't exist
Expand Down

0 comments on commit 3358fbc

Please sign in to comment.