def equatorial_meridian(self, tick_interval, frame): if tick_interval is not None: for i in range(int(360 / int(tick_interval))): l = i * tick_interval sp = SkyCoordDeg(l, 0, frame=frame).icrs if self.clip_at_border: p = self.projection.project(sp) if not self.clipper.point_inside(p): continue else: if not self.inside_coordinate_range(sp): continue p = self.projection.project(sp) v = self.projection.project( SkyCoordDeg(l + 1, 0, frame=frame).icrs) - p v = v.rotate(90) / v.norm tp1 = p + 0.5 * v tp2 = p - 0.5 * v lp = p + 0.4 * v tick = Line(tp1, tp2) label = Label( lp, text=f"\\textit{{{l}\\textdegree}}", fontsize="miniscule", angle=tick.angle - 90, position="below", fill="white", ) yield EquatorialMeridian(l, tick, label)
def _calculate_parallel_circle_center(self): """Calculates the center of the parallel circles.""" s0 = SkyCoordDeg(self.center_longitude, 0) s3 = SkyCoordDeg(self.center_longitude, numpy.sign(self.center_latitude) * 90) p0 = self.project(s0) p1 = self.project( longitude=self.center_longitude, latitude=math.fabs(math.degrees(self.G)) - 90, ) p3 = self.project(s3) delta = numpy.sign(self.center_latitude) * (p1.y - p0.y) self.parallel_circle_center = Point(0, p3.y + delta)
def ecliptic_to_equatorial(p, epoch=REFERENCE_EPOCH): """Returns the equatorial coordinates for the given point in ecliptic coordinates. Equations taken from: Practical Astronomy with your Calculator or Spreadsheet Peter Duffett-Smith and Jonathan Zwart Cambridge University Press, 2011 Paragraph 27 (page 51) Only used to draw the ecliptic. """ eps = math.radians(obliquity_of_the_ecliptic(REFERENCE_EPOCH)) ceps = math.cos(eps) seps = math.sin(eps) l = math.radians(p.ra.degree) b = math.radians(p.dec.degree) longitude = math.degrees( math.atan2(math.sin(l) * ceps - math.tan(b) * seps, math.cos(l))) latitude = math.degrees( math.asin(math.sin(b) * ceps + math.cos(b) * seps * math.sin(l))) # Precess to the current epoch pc = PrecessionCalculator(REFERENCE_EPOCH, epoch) longitude, latitude = pc.precess(longitude, latitude) return SkyCoordDeg(ensure_angle_range(longitude), latitude)
def constellations_in_area( min_longitude, max_longitude, min_latitude, max_latitude, nsamples=1000 ): """Generates a list of all constellations that overlap with the given area. Uses a simple Monte Carlo strategy. """ constellations = {} sum_weight = 0 for i in range(nsamples): # Generate a random longitude and latitude pair inside the bounding box longitude = min_longitude + (max_longitude - min_longitude) * random.random() latitude = min_latitude + (max_latitude - min_latitude) * random.random() # Retrieve the short name of the constellation for that coordinate constellation = get_constellation( SkyCoordDeg(longitude, latitude), short_name=True ) # Use the cosine of the latitude as weight, so higher latitude areas are not dominating weight = math.cos(math.radians(latitude)) # Update the constellation dict and sum_weight if constellation not in constellations: constellations[constellation] = weight else: constellations[constellation] += weight sum_weight += weight # Sort the constellation dict by value and return the constellations of decreasing area res = sorted([(v / sum_weight, k) for k, v in constellations.items()], reverse=True) return [x[1] for x in res]
def test_latitude(self): self.assertEqual(self.p.project(SkyCoordDeg(0, 50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(90, 50)), Point(0, 1)) self.assertEqual(self.p.project(SkyCoordDeg(180, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(270, 50)), Point(0, -1)) self.p.celestial = True self.assertEqual(self.p.project(SkyCoordDeg(0, 50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(90, 50)), Point(0, -1)) self.assertEqual(self.p.project(SkyCoordDeg(180, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(270, 50)), Point(0, 1))
def pole_markers(self, frame): for latitude in [-90, 90]: sp = SkyCoordDeg(0, latitude, frame=frame).icrs p = self.projection.project(sp) if self.config.rotate_poles: delta1 = (self.projection.project( SkyCoordDeg(sp.ra.degree + 1, sp.dec.degree)) - p) delta2 = (self.projection.project( SkyCoordDeg(sp.ra.degree, sp.dec.degree + 1)) - p) else: delta1 = Point(1, 0) delta2 = Point(0, 1) delta1 *= self.config.pole_marker_size / delta1.norm delta2 *= self.config.pole_marker_size / delta2.norm if self.clipper.point_inside(p): yield Line(p + delta1, p - delta1) yield Line(p + delta2, p - delta2)
def _generate_equator_points(self): data = [ SkyCoordDeg(longitude, 0, frame=self.frame).icrs for longitude in numpy.arange(0, 360, 1) ] longitudes = [x.ra.degree for x in data] latitudes = [x.dec.degree for x in data] i = longitudes.index(min(longitudes)) self.LONGITUDES[self.frame] = longitudes[i:] + longitudes[:i] self.LATITUDES[self.frame] = latitudes[i:] + latitudes[:i]
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 backproject(self, point): if self.celestial: x = -point.x else: x = point.x longitude = (self.center_longitude + self.reference_scale * x / self.horizontal_stretch) latitude = point.y * self.reference_scale return SkyCoordDeg(longitude, latitude)
def polar_tick(self): if not self.config.polar_tick: return None latitude = 90 delta = Point(0, 1) if self.config.center_latitude < 0: delta = Point(0, -1) latitude *= -1 p1 = self.projection.project(SkyCoordDeg(0, latitude)) p2 = p1 + self.config.marked_ticksize * delta return Line(p1, p2)
def get_constellation_boundaries_for_area( min_longitude, max_longitude, min_latitude, max_latitude, epoch="J2000.0" ): # Convert longitude to 0-360 values # TODO: sometimes boundaries cross the map but have no vertices within the map area + margin and are not plotted min_longitude = ensure_angle_range(min_longitude) max_longitude = ensure_angle_range(max_longitude) if max_longitude == min_longitude: max_longitude += 360 db = SkyMapDatabase() q = "SELECT * FROM skymap_constellation_boundaries WHERE" if min_longitude < max_longitude: q += " ((ra1>={0} AND ra1<={1}".format(min_longitude, max_longitude) else: q += " (((ra1>={0} OR ra1<={1})".format(min_longitude, max_longitude) q += " AND dec1>={0} AND dec1<={1}) OR".format(min_latitude, max_latitude) if min_longitude < max_longitude: q += " (ra2>={0} AND ra2<={1}".format(min_longitude, max_longitude) else: q += " ((ra2>={0} OR ra2<={1})".format(min_longitude, max_longitude) q += " AND dec2>={0} AND dec2<={1}))".format(min_latitude, max_latitude) res = db.query(q) result = [] for row in res: p1 = SkyCoordDeg(row["ra1"], row["dec1"]) p2 = SkyCoordDeg(row["ra2"], row["dec2"]) e = ConstellationBoundaryEdge(p1, p2) e.precess() result.append(e) db.close() return result
def backproject(self, point): sign_n = numpy.sign(self.n) rho = (math.radians(self.reference_scale) * sign_n * math.sqrt(point.x**2 + (self.rho_0 - point.y)**2)) theta = math.degrees( math.atan2(sign_n * point.x, sign_n * (self.rho_0 - point.y))) if self.celestial: theta *= -1 longitude = self.center_longitude + theta / self.n latitude = math.degrees(self.G - rho) return SkyCoordDeg(longitude, latitude)
def test_inverse_project(self): self.assertEqual(self.p.backproject(Point(1, 0)), SkyCoordDeg(0, 50)) self.assertEqual(self.p.backproject(Point(0, 1)), SkyCoordDeg(90, 50)) self.assertEqual(self.p.backproject(Point(-1, 0)), SkyCoordDeg(180, 50)) self.assertEqual(self.p.backproject(Point(0, -1)), SkyCoordDeg(270, 50)) p = SkyCoordDeg(225, 76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.backproject(Point(1, 0)), SkyCoordDeg(0, 50)) self.assertEqual(self.p.backproject(Point(0, 1)), SkyCoordDeg(270, 50)) self.assertEqual(self.p.backproject(Point(-1, 0)), SkyCoordDeg(180, 50)) self.assertEqual(self.p.backproject(Point(0, -1)), SkyCoordDeg(90, 50)) p = SkyCoordDeg(225, 76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp)
def equator(self, frame): e = Equator(frame) points = e.points_inside_area(self.min_longitude, self.max_longitude, self.min_latitude, self.max_latitude) points = [ self.projection.project(SkyCoordDeg(p[0], p[1])) for p in points ] if not points: return None polygon = Polygon(points, closed=False) if self.clip_at_border: polys, borders = self.clipper.clip(polygon) if not polys: return None polygon = polys[0] return polygon
def galactic_to_equatorial(p, epoch=REFERENCE_EPOCH): """Returns the equatorial coordinates for the given point in galactic coordinates. Equations taken from: Practical Astronomy with your Calculator or Spreadsheet Peter Duffett-Smith and Jonathan Zwart Cambridge University Press, 2011 Paragraph 30 (page 58) Only used to draw the galactic equator. """ l = math.radians(p.ra.degree) b = math.radians(p.dec.degree) # Get the longitude and latitude of the north galactic pole ngp = galactic_pole(GALACTIC_NORTH_POLE_EPOCH) pl = math.radians(ngp.ra.degree) pb = math.radians(ngp.dec.degree) # Get the galactic longitude offset l0 = math.radians(GALACTIC_LONGITUDE_NORTH_CELESTIAL_POLE - 90.0) # Determine the equatorial longitude = math.degrees( math.atan2( math.cos(b) * math.cos(l - l0), (math.sin(b) * math.cos(pb) - math.cos(b) * math.sin(pb) * math.sin(l - l0)), ) + pl) latitude = math.degrees( math.asin( math.cos(b) * math.cos(pb) * math.sin(l - l0) + math.sin(b) * math.sin(pb))) # Precess to the current epoch pc = PrecessionCalculator(GALACTIC_NORTH_POLE_EPOCH, epoch) longitude, latitude = pc.precess(longitude, latitude) return SkyCoordDeg(ensure_angle_range(longitude), latitude)
def polar_label(self): if not self.config.polar_tick: return None latitude = 90 delta = Point(0, 1) pos = "above" text = "+90\\textdegree" if self.config.center_latitude < 0: delta = Point(0, -1) latitude *= -1 pos = "below" text = "--90\\textdegree" p1 = self.projection.project(SkyCoordDeg(0, latitude)) p2 = p1 + self.config.label_distance * delta return Label( p2, text=text, fontsize=self.config.parallel_config.fontsize, angle=0, position=pos, fill="white", )
def parallel(self, latitude, min_longitude, max_longitude): p = self.project(SkyCoordDeg(0, latitude)) return Circle(self.origin, self.origin.distance(p))
def galactic_pole(epoch=REFERENCE_EPOCH): """Returns the location of the galactic north pole at the given epoch.""" p = PrecessionCalculator(GALACTIC_NORTH_POLE_EPOCH, epoch) return SkyCoordDeg(*p.precess(GALACTIC_NORTH_POLE.ra.degree, GALACTIC_NORTH_POLE.dec.degree))
import datetime import math from astropy.coordinates import Longitude from skymap.geometry import SkyCoordDeg, ensure_angle_range from skymap.coordinates import PrecessionCalculator, REFERENCE_EPOCH GALACTIC_NORTH_POLE = SkyCoordDeg(Longitude("12h49m").degree, 27.4) GALACTIC_LONGITUDE_NORTH_CELESTIAL_POLE = 123 GALACTIC_NORTH_POLE_EPOCH = datetime.datetime(1950, 1, 1).date() # Ecliptic coordinate system def obliquity_of_the_ecliptic(epoch=REFERENCE_EPOCH): """Returns the obliquity of the ecliptic for the given epoch, in degrees. The calculation is taken from the Astronomical Almanac 2010, as given on https://en.wikipedia.org/wiki/Axial_tilt#Short_term. """ a = 23.0 + 26 / 60.0 + 21.406 / 3600.0 b = -46.836769 / 3600.0 c = -0.0001831 / 3600.0 d = 0.00200340 / 3600.0 e = -0.576e-6 / 3600.0 f = -0.0434e-6 / 3600.0 t = (epoch - REFERENCE_EPOCH).days / 36525.0 return a + b * t + c * t**2 + d * t**3 + e * t**4 + f * t**5 def ecliptic_to_equatorial(p, epoch=REFERENCE_EPOCH): """Returns the equatorial coordinates for the given point in ecliptic coordinates.
def inverse_project(self, point): longitude = (self.center_longitude + self.reference_scale * point.x / self.horizontal_stretch) latitude = self.reference_scale * point.y return SkyCoordDeg(longitude, latitude)
def parallel(self, latitude, min_longitude, max_longitude): p1 = self.project(SkyCoordDeg(min_longitude - 0.1, latitude)) p2 = self.project(SkyCoordDeg(max_longitude + 0.1, latitude)) return Line(p1, p2)
def meridian(self, longitude, min_latitude, max_latitude): p1 = self.project(SkyCoordDeg(longitude, min_latitude)) p2 = self.project(SkyCoordDeg(longitude, max_latitude)) return Line(p1, p2)
def parallel(self, latitude, min_longitude, max_longitude): p = self.project(SkyCoordDeg(self.center_longitude, latitude)) radius = p.distance(self.parallel_circle_center) return Circle(self.parallel_circle_center, radius)
def test_reference_longitude(self): self.p.center_longitude = 40 self.assertAlmostEqual( self.origin.distance(self.p.project(SkyCoordDeg(0, 50))), 1.0) self.assertEqual(self.p.project(SkyCoordDeg(40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(130, 50)), Point(0, 1)) self.assertEqual(self.p.project(SkyCoordDeg(220, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(310, 50)), Point(0, -1)) p = SkyCoordDeg(225, 76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.project(SkyCoordDeg(40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(130, 50)), Point(0, -1)) self.assertEqual(self.p.project(SkyCoordDeg(220, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(310, 50)), Point(0, 1)) p = SkyCoordDeg(225, 76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp) self.p.celestial = False self.p.center_longitude = -40 self.assertEqual(self.p.project(SkyCoordDeg(-40, 50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(50, 50)), Point(0, 1)) self.assertEqual(self.p.project(SkyCoordDeg(140, 50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(230, 50)), Point(0, -1))
def test_inverse_projection(self): self.assertEqual(self.p.backproject(Point(0, 0)), SkyCoordDeg(0, 45)) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(15, 45))), SkyCoordDeg(15, 45)) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(-15, 45))), SkyCoordDeg(-15, 45), ) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(29, 32))), SkyCoordDeg(29, 32)) self.p.celestial = True self.assertEqual(self.p.backproject(Point(0, 0)), SkyCoordDeg(0, 45)) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(15, 45))), SkyCoordDeg(15, 45)) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(-15, 45))), SkyCoordDeg(-15, 45), ) self.assertEqual( self.p.backproject(self.p.project(SkyCoordDeg(29, 32))), SkyCoordDeg(29, 32))
def parallels(self): config = self.config.parallel_config interval = config.tick_interval current_latitude = math.ceil(self.min_latitude / interval) * interval # Internal ticks and labels internal_longitude = self.config.center_longitude if self.config.internal_label_longitude is not None: internal_longitude = self.config.internal_label_longitude internal_meridian = self.projection.meridian(internal_longitude, self.min_latitude, self.max_latitude) internal_tickangle = internal_meridian.angle - 90 if internal_tickangle < 0: internal_tickangle += 180 while current_latitude <= self.max_latitude: # print(f"Parallel at {current_latitude}") parallel = self.projection.parallel(current_latitude, self.min_longitude, self.max_longitude) internal_point = self.projection.project( SkyCoordDeg(internal_longitude, current_latitude)) if not self.clipper.point_inside(internal_point): internal_point = None if self.clip_at_border: parallel_parts, borders = self.clipper.clip(parallel) for p, b in zip(parallel_parts, borders): yield Parallel( current_latitude, p, b, internal_point, internal_tickangle, config, ) else: if isinstance(parallel, Circle) and ensure_angle_range( self.min_longitude) != ensure_angle_range( self.max_longitude): # Construct the arc from the endpoints p1 = (self.projection.project( SkyCoordDeg(self.min_longitude, current_latitude)) - parallel.center) p2 = (self.projection.project( SkyCoordDeg(self.max_longitude, current_latitude)) - parallel.center) parallel = Arc(parallel.center, parallel.radius, p1.angle, p2.angle) yield Parallel( current_latitude, parallel, ["right", "left"], internal_point, internal_tickangle, config, ) current_latitude += interval
"bottom", "top" ] mc.coordinate_grid_config.parallel_tick_borders = [ "left", "right" ] mc.coordinate_grid_config.parallel_internal_labels = False mc.coordinate_grid_config.meridian_labeltextfunc = meridian_label mc.coordinate_grid_config.flip_meridian_labels = False print("Latitude:", mc.min_latitude, mc.max_latitude) print("Longitude:", mc.min_longitude, mc.max_longitude) voffset = cc["offset"] if mc.center_latitude < 0: # Adjust offset p1 = mc.projection.project( SkyCoordDeg(mc.max_longitude, mc.min_latitude)) p2 = mc.projection.project( SkyCoordDeg(mc.min_longitude, mc.min_latitude)) voffset += abs(p1.y - p2.y) if left: mc.llcorner = p.llcorner + Point(0, LEGEND_HEIGHT + voffset) mc.origin = mc.llcorner + Point( LEGEND_WIDTH, 0.5 * mc.latitude_range * MM_PER_DEGREE) else: mc.llcorner = p.llcorner + Point(0, LEGEND_HEIGHT + voffset) mc.origin = mc.llcorner + Point( 0, 0.5 * mc.latitude_range * MM_PER_DEGREE)
def test_south_pole(self): self.p = AzimuthalEquidistantProjection(center_longitude=0, center_latitude=-90, reference_scale=40) # Pole self.assertEqual(self.p.project(SkyCoordDeg(0, -90)), self.origin) # Project self.assertEqual(self.p.project(SkyCoordDeg(0, -50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(90, -50)), Point(0, -1)) self.assertEqual(self.p.project(SkyCoordDeg(180, -50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(270, -50)), Point(0, 1)) self.p.celestial = True self.assertEqual(self.p.project(SkyCoordDeg(0, -50)), Point(1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(90, -50)), Point(0, 1)) self.assertEqual(self.p.project(SkyCoordDeg(180, -50)), Point(-1, 0)) self.assertEqual(self.p.project(SkyCoordDeg(270, -50)), Point(0, -1)) # Inverse project self.p.celestial = False self.assertEqual(self.p.backproject(Point(1, 0)), SkyCoordDeg(0, -50)) self.assertEqual(self.p.backproject(Point(0, -1)), SkyCoordDeg(90, -50)) self.assertEqual(self.p.backproject(Point(-1, 0)), SkyCoordDeg(180, -50)) self.assertEqual(self.p.backproject(Point(0, 1)), SkyCoordDeg(270, -50)) p = SkyCoordDeg(225, -76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp) self.p.celestial = True self.assertEqual(self.p.backproject(Point(1, 0)), SkyCoordDeg(0, -50)) self.assertEqual(self.p.backproject(Point(0, 1)), SkyCoordDeg(90, -50)) self.assertEqual(self.p.backproject(Point(-1, 0)), SkyCoordDeg(180, -50)) self.assertEqual(self.p.backproject(Point(0, -1)), SkyCoordDeg(270, -50)) p = SkyCoordDeg(225, -76) pp = self.p.project(p) ppp = self.p.backproject(pp) self.assertEqual(p, ppp)
def interpolate_points(self, step=1.0): if self.epoch != CONST_BOUND_EPOCH: raise ValueError( "Interpolations is only allowed for epoch 1875.0 boundaries!" ) if self.direction == "parallel": # dec is changing fixed_value = self.coord1.ra.degree v1 = self.coord1.dec.degree v2 = self.coord2.dec.degree elif self.direction == "meridian": # ra is changing fixed_value = self.coord1.dec.degree v1 = self.coord1.ra.degree v2 = self.coord2.ra.degree else: raise ValueError("Only one coordinate can change in an 1875 edge!") d = v2 - v1 if abs(d) < 180: # No zero crossing (assuming edges never exceed 180) if d > 0: # Interpolate from v1 to v2 (forward) new_values = [v1] new_val = step * math.ceil(v1 / step) while new_val < v2: new_values.append(new_val) new_val += step new_values.append(v2) else: # Interpolate from v2 to v1 (backward) new_values = [v1] new_val = step * math.floor(v1 / step) while new_val > v2: new_values.append(new_val) new_val -= step new_values.append(v2) else: # Zero crossing if d < 0: # Interpolate from v1 to v2 (forward), with zero crossing new_values = [v1] new_val = step * math.ceil(v1 / step) while new_val < v2 + 360: new_values.append(new_val) new_val += step new_values.append(v2) else: # Interpolate from v2 to v2 (backward), with zero crossing new_values = [v1] new_val = step * math.floor(v1 / step) while new_val + 360 >= v2: new_values.append(new_val) new_val -= step new_values.append(v2) if self.direction == "parallel": self.interpolated_points = [SkyCoordDeg(v, fixed_value) for v in new_values] else: self.interpolated_points = [SkyCoordDeg(fixed_value, v) for v in new_values] return self.interpolated_points
def test_projection(self): self.assertEqual(self.p.cone_angle, 45) self.assertEqual(self.p.project(SkyCoordDeg(0, 45)), Point(0, 0)) self.assertEqual(self.p.project(SkyCoordDeg(0, 50)), Point(0, 0.5)) self.assertEqual(self.p.project(SkyCoordDeg(0, 35)), Point(0, -1))