Skip to content

Commit d95c1a9

Browse files
committed
Bugfix polygon vertices ordering
Fixes an issue where the edge vertices ordering can be incorrectly flipped. Change also removes unnecessary arguments from `discretize_all_edge_boundaries`, and propagates this change to methods that call this function.
1 parent 54fd444 commit d95c1a9

File tree

4 files changed

+86
-28
lines changed

4 files changed

+86
-28
lines changed

regions/_utils/spherical_helpers.py

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -316,22 +316,38 @@ def _add_tan_pts_if_in_pa_range(
316316
return coord_list
317317

318318

319-
def _validate_vertices_ordering(verts, gc, centroid):
320-
pas_verts = gc.center.position_angle(verts).to(u.deg)
319+
def _check_edge_lt_pi(pas_verts, wrap_ang):
320+
pas_verts_wrap = pas_verts.wrap_at(wrap_ang)
321+
322+
is_valid_arc_length = ((pas_verts_wrap[1] - pas_verts_wrap[0]).to(u.deg) <= 180 * u.deg)
323+
324+
return pas_verts_wrap, is_valid_arc_length
325+
326+
327+
def _validate_vertices_ordering(verts, gc, gc_center=None):
328+
if gc is not None:
329+
gc_center = gc.center
330+
pas_verts = gc_center.position_angle(verts).to(u.deg)
321331

322-
# Check ordering of vertices pas:
323-
pa_centroid = gc.center.position_angle(centroid).to(u.deg)
324332
wrap_ang = pas_verts[0]
325-
pas_verts_wrap = pas_verts.wrap_at(wrap_ang)
326-
pa_centroid_wrap = pa_centroid.wrap_at(wrap_ang)
327-
in_range_lon = (pa_centroid_wrap >= pas_verts_wrap[0]) & (
328-
pa_centroid_wrap <= pas_verts_wrap[1]
329-
)
330-
if not in_range_lon:
331-
# If not in range, invert the vertices PA ordering:
332-
pas_verts = pas_verts[::-1]
333-
wrap_ang = pas_verts[0]
334-
pas_verts_wrap = pas_verts.wrap_at(wrap_ang)
333+
334+
# Check ordering of vertices pas:
335+
336+
# Principle: ALL EDGES must be <= 180 deg of length
337+
# So difference of pa[1] - pa[0] <= 180 deg
338+
# If not, swap the order.
339+
340+
pas_verts_wrap, is_valid_arc_length = _check_edge_lt_pi(pas_verts, wrap_ang)
341+
342+
if not is_valid_arc_length:
343+
wrap_ang_opp = pas_verts[-1]
344+
pas_verts_wrap_opp, is_valid_arc_length_opp = _check_edge_lt_pi(
345+
pas_verts[::-1], wrap_ang_opp
346+
)
347+
if is_valid_arc_length_opp:
348+
return pas_verts_wrap_opp, wrap_ang_opp
349+
350+
raise ValueError('Invalid arc') # should never occur
335351

336352
return pas_verts_wrap, wrap_ang
337353

@@ -400,7 +416,7 @@ def get_edge_raw_lonlat_bounds_circ_edges(vertices, centroid, gcs):
400416
# PAs from gc center to vertices:
401417
verts = SkyCoord(np.concatenate([[vertices[i - 1]], [vertices[i]]]))
402418

403-
pas_verts_wrap, wrap_ang = _validate_vertices_ordering(verts, gc, centroid)
419+
pas_verts_wrap, wrap_ang = _validate_vertices_ordering(verts, gc)
404420

405421
# --------------------------------------------------------
406422
# Latitude tangent points from bound circle as len 2 SkyCoord:
@@ -409,7 +425,8 @@ def get_edge_raw_lonlat_bounds_circ_edges(vertices, centroid, gcs):
409425
tan_lat_pts = _get_circle_latitude_tangent_points(gc.center, gc.radius)
410426

411427
lats_list = _add_tan_pts_if_in_pa_range(
412-
lats_list, tan_lat_pts, gc, wrap_ang, pas_verts_wrap, coord='lat'
428+
lats_list, tan_lat_pts, gc, wrap_ang, pas_verts_wrap,
429+
coord='lat'
413430
)
414431

415432
# --------------------------------------------------------
@@ -419,7 +436,8 @@ def get_edge_raw_lonlat_bounds_circ_edges(vertices, centroid, gcs):
419436
tan_lon_pts = _get_circle_longitude_tangent_points(gc.center, gc.radius)
420437
if tan_lon_pts is not None:
421438
lons_list = _add_tan_pts_if_in_pa_range(
422-
lons_list, tan_lon_pts, gc, wrap_ang, pas_verts_wrap, coord='lon'
439+
lons_list, tan_lon_pts, gc, wrap_ang, pas_verts_wrap,
440+
coord='lon'
423441
)
424442

425443
lons_arr = [lons_list.min(), lons_list.max()]
@@ -432,7 +450,13 @@ def get_edge_raw_lonlat_bounds_circ_edges(vertices, centroid, gcs):
432450
return Longitude(lons_arr).to(u.deg), Latitude(lats_arr).to(u.deg)
433451

434452

435-
def _discretize_edge_boundary(vertices, circ, centroid, n_points):
453+
def _discretize_edge_boundary(vertices, circ, n_points,
454+
circ_center=None, circ_radius=None):
455+
456+
if circ is not None:
457+
circ_center = circ.center
458+
circ_radius = circ.radius
459+
436460
# Discretize an edge boundary defined by a circle, geodetic or not:
437461
# either great circle arc, or a span of a non-great circle
438462
# (e.g., constant lat edges of RangeSphericalSkyRegion)
@@ -441,7 +465,9 @@ def _discretize_edge_boundary(vertices, circ, centroid, n_points):
441465
# connecting circle center to the two vertices bounding that edge:
442466
pas_verts = circ.center.position_angle(vertices).to(u.deg)
443467

444-
pas_verts_wrap, wrap_ang = _validate_vertices_ordering(vertices, circ, centroid)
468+
pas_verts_wrap, wrap_ang = _validate_vertices_ordering(
469+
vertices, circ, gc_center=circ_center,
470+
)
445471

446472
# Need to wrap angles to calculate span: wrap at lower value:
447473
pas_verts_wrap = pas_verts.wrap_at(wrap_ang)
@@ -453,12 +479,47 @@ def _discretize_edge_boundary(vertices, circ, centroid, n_points):
453479

454480
# Calculate directional offsets to get boundary discretization,
455481
# with vertices as SkyCoords
456-
bound_verts = circ.center.directional_offset_by(theta, circ.radius)
482+
bound_verts = circ.center.directional_offset_by(theta, circ_radius)
457483

458484
return bound_verts
459485

460486

461-
def discretize_all_edge_boundaries(vertices, circs, centroid, n_points):
487+
def discretize_line_boundary(coords, n_points):
488+
"""
489+
Discretize all edge boundaries for spherical sky regions.
490+
491+
Parameters
492+
----------
493+
coords : `~astropy.coordinates.SkyCoord`
494+
The start, end of the line as a SkyCoord.
495+
496+
n_points : int
497+
The number of points for discretization along each edge.
498+
499+
Returns
500+
-------
501+
edge_bound_verts : `~astropy.coordinates.SkyCoord`
502+
The discretized boundary edge vertices.
503+
"""
504+
# Iterate over full set of vertices & boundary circles for
505+
# a region (polygon or range)
506+
507+
gc_center = cross_product_skycoord2skycoord(coords[0], coords[1])
508+
gc_radius = 90 * u.deg
509+
510+
bound_verts = _discretize_edge_boundary(coords, None, n_points,
511+
circ_center=gc_center, circ_radius=gc_radius)
512+
513+
return SkyCoord(
514+
SkyCoord(
515+
bound_verts,
516+
representation_type=UnitSphericalRepresentation,
517+
),
518+
representation_type=SphericalRepresentation,
519+
)
520+
521+
522+
def discretize_all_edge_boundaries(vertices, circs, n_points):
462523
"""
463524
Discretize all edge boundaries for spherical sky regions.
464525
@@ -470,9 +531,6 @@ def discretize_all_edge_boundaries(vertices, circs, centroid, n_points):
470531
circs : `~regions.CircleSphericalSkyRegion`
471532
The circle boundaries as CircleSphericalSkyRegion instances.
472533
473-
centroid : `~astropy.coordinates.SkyCoord`
474-
The region centroid as a SkyCoord.
475-
476534
n_points : int
477535
The number of points for discretization along each edge.
478536
@@ -489,7 +547,7 @@ def discretize_all_edge_boundaries(vertices, circs, centroid, n_points):
489547
# PAs from gc center to vertices:
490548
verts = SkyCoord(np.concatenate([[vertices[i - 1]], [vertices[i]]]))
491549

492-
bound_verts = _discretize_edge_boundary(verts, circ, centroid, n_points)
550+
bound_verts = _discretize_edge_boundary(verts, circ, n_points)
493551

494552
if all_edge_bound_verts is None:
495553
all_edge_bound_verts = bound_verts

regions/shapes/lune.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def transform_to(self, frame, merge_attributes=True):
184184

185185
def discretize_boundary(self, n_points=100):
186186
bound_verts = discretize_all_edge_boundaries(
187-
self.vertices, self._edge_circs, self.centroid, n_points
187+
self.vertices, self._edge_circs, n_points
188188
)
189189
return PolygonSphericalSkyRegion(bound_verts)
190190

regions/shapes/polygon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ def transform_to(self, frame, merge_attributes=True):
597597

598598
def discretize_boundary(self, n_points=10):
599599
bound_verts = discretize_all_edge_boundaries(
600-
self.vertices, self._edge_circs, self.centroid, n_points
600+
self.vertices, self._edge_circs, n_points
601601
)
602602
return PolygonSphericalSkyRegion(bound_verts)
603603

regions/shapes/range.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ def discretize_boundary(self, n_points=10):
825825
return self.longitude_bounds.discretize_boundary(n_points=n_points)
826826
elif (bound_nverts == 4) | (bound_nverts == 3):
827827
bound_verts = discretize_all_edge_boundaries(
828-
self.vertices, self._edge_circs, self.centroid, n_points
828+
self.vertices, self._edge_circs, n_points
829829
)
830830
return PolygonSphericalSkyRegion(bound_verts)
831831
elif bound_nverts == 6:

0 commit comments

Comments
 (0)