@@ -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,90 @@ 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 get_line_edge_raw_lonlat_bounds_circ_edges (coords , center ):
454+ """
455+ Get the raw longitude / latitude bounds from the circle edges of
456+ spherical sky region.
457+
458+ Parameters
459+ ----------
460+ coords : `~astropy.coordinates.SkyCoord`
461+ The start, end of the line as a SkyCoord.
462+
463+ center : `~astropy.coordinates.SkyCoord`
464+ The line center as a SkyCoord.
465+
466+ Returns
467+ -------
468+ longitude_limits, latitude_limits: `~astropy.coordinates.Longitude`
469+ Length two |Longitude| and |Latitude| with the computed
470+ longitude/latitude bounds from the polygon edges.
471+ """
472+ # Consider lon/lat of vertices: may produce min/max bounds:
473+ vrepr = coords .represent_as ('spherical' )
474+
475+ # Special handling:
476+ # Exclude vertices from longitude bounds if any is on a pole
477+ lons_list = []
478+ lats_list = []
479+ for v in vrepr :
480+ if np .abs (v .lat .to (u .deg ).deg ) < 90 :
481+ lons_list .append (v .lon )
482+ lats_list .append (v .lat )
483+ lons_list = Longitude (lons_list , unit = u .radian )
484+ lats_list = Latitude (lats_list , unit = u .radian )
485+
486+ # Need to also check for "out bulging" from edges,
487+ # as far as latitude/lon bounds:
488+ # eg, 2 vertices at ~60deg: the gc arc goes ~closer to the pole;
489+ # a circle centered close to the pole but not extending over it:
490+ # WIDE lon bounds
491+
492+ gc_center = cross_product_skycoord2skycoord (coords [0 ], coords [1 ])
493+ gc_radius = 90 * u .deg
494+
495+ pas_verts_wrap , wrap_ang = _validate_vertices_ordering (
496+ coords , None , gc_center = gc_center
497+ )
498+
499+ # --------------------------------------------------------
500+ # Latitude tangent points from bound circle as len 2 SkyCoord:
501+ # Only add to the list if the tangent point is located along this edge
502+ tan_lat_pts = _get_circle_latitude_tangent_points (gc_center , gc_radius )
503+
504+ lats_list = _add_tan_pts_if_in_pa_range (
505+ lats_list , tan_lat_pts , None , wrap_ang , pas_verts_wrap , coord = 'lat' ,
506+ gc_center = gc_center
507+ )
508+
509+ # --------------------------------------------------------
510+ # Longitude tangent points from bound circle as len 2 SkyCoord:
511+ # Only add to the list if the tangent point is located along this edge
512+
513+ tan_lon_pts = _get_circle_longitude_tangent_points (gc_center , gc_radius )
514+ if tan_lon_pts is not None :
515+ lons_list = _add_tan_pts_if_in_pa_range (
516+ lons_list , tan_lon_pts , None , wrap_ang , pas_verts_wrap , coord = 'lon' ,
517+ gc_center = gc_center
518+ )
519+
520+ lons_arr = [lons_list .min (), lons_list .max ()]
521+ lats_arr = [lats_list .min (), lats_list .max ()]
522+
523+ # --------------------------------------------------------
524+ # Invert longitude order if centroid is outside of range:
525+ lons_arr = _validate_lon_bounds_ordering (lons_arr , center )
526+
527+ return Longitude (lons_arr ).to (u .deg ), Latitude (lats_arr ).to (u .deg )
528+
529+
530+ def _discretize_edge_boundary (vertices , circ , n_points ,
531+ circ_center = None , circ_radius = None ):
532+
533+ if circ is not None :
534+ circ_center = circ .center
535+ circ_radius = circ .radius
536+
436537 # Discretize an edge boundary defined by a circle, geodetic or not:
437538 # either great circle arc, or a span of a non-great circle
438539 # (e.g., constant lat edges of RangeSphericalSkyRegion)
@@ -441,7 +542,9 @@ def _discretize_edge_boundary(vertices, circ, centroid, n_points):
441542 # connecting circle center to the two vertices bounding that edge:
442543 pas_verts = circ .center .position_angle (vertices ).to (u .deg )
443544
444- pas_verts_wrap , wrap_ang = _validate_vertices_ordering (vertices , circ , centroid )
545+ pas_verts_wrap , wrap_ang = _validate_vertices_ordering (
546+ vertices , circ , gc_center = circ_center ,
547+ )
445548
446549 # Need to wrap angles to calculate span: wrap at lower value:
447550 pas_verts_wrap = pas_verts .wrap_at (wrap_ang )
@@ -453,12 +556,47 @@ def _discretize_edge_boundary(vertices, circ, centroid, n_points):
453556
454557 # Calculate directional offsets to get boundary discretization,
455558 # with vertices as SkyCoords
456- bound_verts = circ .center .directional_offset_by (theta , circ . radius )
559+ bound_verts = circ .center .directional_offset_by (theta , circ_radius )
457560
458561 return bound_verts
459562
460563
461- def discretize_all_edge_boundaries (vertices , circs , centroid , n_points ):
564+ def discretize_line_boundary (coords , n_points ):
565+ """
566+ Discretize all edge boundaries for spherical sky regions.
567+
568+ Parameters
569+ ----------
570+ coords : `~astropy.coordinates.SkyCoord`
571+ The start, end of the line as a SkyCoord.
572+
573+ n_points : int
574+ The number of points for discretization along each edge.
575+
576+ Returns
577+ -------
578+ edge_bound_verts : `~astropy.coordinates.SkyCoord`
579+ The discretized boundary edge vertices.
580+ """
581+ # Iterate over full set of vertices & boundary circles for
582+ # a region (polygon or range)
583+
584+ gc_center = cross_product_skycoord2skycoord (coords [0 ], coords [1 ])
585+ gc_radius = 90 * u .deg
586+
587+ bound_verts = _discretize_edge_boundary (coords , None , n_points ,
588+ circ_center = gc_center , circ_radius = gc_radius )
589+
590+ return SkyCoord (
591+ SkyCoord (
592+ bound_verts ,
593+ representation_type = UnitSphericalRepresentation ,
594+ ),
595+ representation_type = SphericalRepresentation ,
596+ )
597+
598+
599+ def discretize_all_edge_boundaries (vertices , circs , n_points ):
462600 """
463601 Discretize all edge boundaries for spherical sky regions.
464602
@@ -470,9 +608,6 @@ def discretize_all_edge_boundaries(vertices, circs, centroid, n_points):
470608 circs : `~regions.CircleSphericalSkyRegion`
471609 The circle boundaries as CircleSphericalSkyRegion instances.
472610
473- centroid : `~astropy.coordinates.SkyCoord`
474- The region centroid as a SkyCoord.
475-
476611 n_points : int
477612 The number of points for discretization along each edge.
478613
@@ -489,7 +624,7 @@ def discretize_all_edge_boundaries(vertices, circs, centroid, n_points):
489624 # PAs from gc center to vertices:
490625 verts = SkyCoord (np .concatenate ([[vertices [i - 1 ]], [vertices [i ]]]))
491626
492- bound_verts = _discretize_edge_boundary (verts , circ , centroid , n_points )
627+ bound_verts = _discretize_edge_boundary (verts , circ , n_points )
493628
494629 if all_edge_bound_verts is None :
495630 all_edge_bound_verts = bound_verts
0 commit comments