class TestAzimuthalEquidistantProjection(unittest.TestCase): def setUp(self): self.origin = Point(0, 0) self.p = AzimuthalEquidistantProjection(reference_longitude=0, reference_scale=40) def test_pole(self): self.assertEqual(self.p.project(SphericalPoint(0, 90)), self.origin) def test_latitude(self): self.assertEqual(self.p.project(SphericalPoint(0, 50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(90, 50)), Point(0, 1)) self.assertEqual(self.p.project(SphericalPoint(180, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(270, 50)), Point(0, -1)) self.p.celestial = True self.assertEqual(self.p.project(SphericalPoint(0, 50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(90, 50)), Point(0, -1)) self.assertEqual(self.p.project(SphericalPoint(180, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(270, 50)), Point(0, 1)) def test_inverse_project(self): self.assertEqual(self.p.inverse_project(Point(1, 0)), SphericalPoint(0, 50)) self.assertEqual(self.p.inverse_project(Point(0, 1)), SphericalPoint(90, 50)) self.assertEqual(self.p.inverse_project(Point(-1, 0)), SphericalPoint(180, 50)) self.assertEqual(self.p.inverse_project(Point(0, -1)), SphericalPoint(270, 50)) p = SphericalPoint(225, 76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.inverse_project(Point(1, 0)), SphericalPoint(0, 50)) self.assertEqual(self.p.inverse_project(Point(0, 1)), SphericalPoint(270, 50)) self.assertEqual(self.p.inverse_project(Point(-1, 0)), SphericalPoint(180, 50)) self.assertEqual(self.p.inverse_project(Point(0, -1)), SphericalPoint(90, 50)) p = SphericalPoint(225, 76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) def test_reference_longitude(self): self.p.reference_longitude = 40 self.assertEqual(self.origin.distance(self.p.project(SphericalPoint(0, 50))), 1.0) self.assertEqual(self.p.project(SphericalPoint(40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(130, 50)), Point(0, 1)) self.assertEqual(self.p.project(SphericalPoint(220, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(310, 50)), Point(0, -1)) p = SphericalPoint(225, 76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.project(SphericalPoint(40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(130, 50)), Point(0, -1)) self.assertEqual(self.p.project(SphericalPoint(220, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(310, 50)), Point(0, 1)) p = SphericalPoint(225, 76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) self.p.celestial = False self.p.reference_longitude = -40 self.assertEqual(self.p.project(SphericalPoint(-40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(50, 50)), Point(0, 1)) self.assertEqual(self.p.project(SphericalPoint(140, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(230, 50)), Point(0, -1)) def test_south_pole(self): self.p = AzimuthalEquidistantProjection(north=False, reference_longitude=0, reference_scale=40) # Pole self.assertEqual(self.p.project(SphericalPoint(0, -90)), self.origin) # Project self.assertEqual(self.p.project(SphericalPoint(0, -50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(90, -50)), Point(0, -1)) self.assertEqual(self.p.project(SphericalPoint(180, -50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(270, -50)), Point(0, 1)) self.p.celestial = True self.assertEqual(self.p.project(SphericalPoint(0, -50)), Point(1, 0)) self.assertEqual(self.p.project(SphericalPoint(90, -50)), Point(0, 1)) self.assertEqual(self.p.project(SphericalPoint(180, -50)), Point(-1, 0)) self.assertEqual(self.p.project(SphericalPoint(270, -50)), Point(0, -1)) # Inverse project self.p.celestial = False self.assertEqual(self.p.inverse_project(Point(1, 0)), SphericalPoint(0, -50)) self.assertEqual(self.p.inverse_project(Point(0, -1)), SphericalPoint(90, -50)) self.assertEqual(self.p.inverse_project(Point(-1, 0)), SphericalPoint(180, -50)) self.assertEqual(self.p.inverse_project(Point(0, 1)), SphericalPoint(270, -50)) p = SphericalPoint(225, -76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.inverse_project(Point(1, 0)), SphericalPoint(0, -50)) self.assertEqual(self.p.inverse_project(Point(0, 1)), SphericalPoint(90, -50)) self.assertEqual(self.p.inverse_project(Point(-1, 0)), SphericalPoint(180, -50)) self.assertEqual(self.p.inverse_project(Point(0, -1)), SphericalPoint(270, -50)) p = SphericalPoint(225, -76) pp = self.p.project(p) ppp = self.p.inverse_project(pp) self.assertEqual(p, ppp) def test_other_scale(self): self.p = AzimuthalEquidistantProjection(reference_longitude=0, reference_scale=80.0/190.0) self.assertEqual(self.p.project(SphericalPoint(0, 50)), Point(95, 0))
class AzimuthalEquidistantProjection(Projection): def __init__( self, center_longitude=0, center_latitude=90, standard_parallel1=None, standard_parallel2=None, reference_scale=45, horizontal_stretch=1.0, celestial=False, ): """Azimuthal equidistant map projection. Center of projection is the pole, which gets projected to the point (0, 0). Args: center_longitude: the longitude that points to the right center_latitude: the latitude at the center of the map (+ or - 90 degrees) standard_parallel1: not used standard_parallel2: not used reference_scale: degrees of latitude per unit distance horizontal_stretch: factor with which to expand the horizontal axis celestial: longitude increases clockwise around north pole """ Projection.__init__( self, center_longitude, center_latitude, standard_parallel1, standard_parallel2, reference_scale, horizontal_stretch, celestial, ) self.north = center_latitude > 0 self.origin = Point(0, 0) if self.north: if self.reference_scale >= 90: raise ProjectionError( f"Invalid reference scale {self.reference_scale} for north pole" ) else: self.reference_scale *= -1 if self.reference_scale <= -90: raise ProjectionError( f"Invalid reference scale {self.reference_scale} for south pole" ) @property def reverse_polar_direction(self): """Whether increasing longitude is clockwise or anticlockwise.""" return self.north == self.celestial def project(self, skycoord): longitude = self.reduce_longitude(skycoord.ra.degree) latitude = skycoord.dec.degree rho = (self.center_latitude - latitude) / self.reference_scale theta = math.radians(longitude - self.center_longitude) if self.reverse_polar_direction: theta *= -1 return Point(self.horizontal_stretch * rho * math.cos(theta), rho * math.sin(theta)) def backproject(self, point): rho = self.origin.distance(point) theta = ensure_angle_range(math.degrees(math.atan2(point.y, point.x))) if self.reverse_polar_direction: theta *= -1 longitude = ensure_angle_range(theta + self.center_longitude) latitude = (-rho * self.reference_scale / self.horizontal_stretch + self.center_latitude) return SkyCoordDeg(longitude, latitude) def parallel(self, latitude, min_longitude, max_longitude): p = self.project(SkyCoordDeg(0, latitude)) return Circle(self.origin, self.origin.distance(p))