def theta2014(d): """ Convert Julian Day into a rotation angle of the sky about the NCP at midnight, relative to spring equinox. :param d: Julian day :return: Rotation angle, radians """ return (d - calendar.julian_day(year=2014, month=3, day=20, hour=16, minute=55, sec=0)) / 365.25 * unit_rev
def do_rendering(self, settings, context): """ This method is required to actually render this item. :param settings: A dictionary of settings required by the renderer. :param context: A GraphicsContext object to use for drawing :return: None """ is_southern = settings['latitude'] < 0 language = settings['language'] latitude = abs(settings['latitude']) theme = themes[settings['theme']] context.set_font_size(1.2) # Radius of outer edge of star chart r_2 = r_1 - r_gap # Radius of day-of-month ticks from centre of star chart r_3 = r_1 * 0.1 + r_2 * 0.9 # Radius of every fifth day-of-month tick from centre of star chart r_4 = r_1 * 0.2 + r_2 * 0.8 # Radius of lines between months on date scale r_5 = r_1 # Radius for writing numeric labels for days of the month r_6 = r_1 * 0.4 + r_2 * 0.6 # Shade background to month scale shading_inner_radius = r_1 * 0.55 + r_2 * 0.45 context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_1) context.circle(centre_x=0, centre_y=0, radius=shading_inner_radius) context.fill(color=theme['shading']) # Draw the outer edge of planisphere context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_1) context.fill(color=theme['background']) # Draw the central hole in the middle of the planisphere context.begin_sub_path() context.circle(centre_x=0, centre_y=0, radius=central_hole_size) context.stroke(color=theme['edge']) # Combine these two paths to make a clipping path for drawing the star wheel context.clip() # Draw lines of constant declination at 15 degree intervals. for dec in arange(-80, 85, 15): # Convert declination into radius from the centre of the planisphere r = radius(dec=dec, latitude=latitude) if r > r_2: continue context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r) context.stroke(color=theme['grid']) # Draw constellation stick figures for line in open("raw_data/constellation_stick_figures.dat", "rt"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words. # These are the names of the constellations, and the start and end points for each stroke. [name, ra1, dec1, ra2, dec2] = line.split() # If we're making a southern hemisphere planisphere, we flip the sky upside down if is_southern: ra1 = -float(ra1) ra2 = -float(ra2) dec1 = -float(dec1) dec2 = -float(dec2) # Project RA and Dec into radius and azimuth in the planispheric projection r_point_1 = radius(dec=float(dec1), latitude=latitude) if r_point_1 > r_2: continue r_point_2 = radius(dec=float(dec2), latitude=latitude) if r_point_2 > r_2: continue p1 = (-r_point_1 * cos(float(ra1) * unit_deg), -r_point_1 * sin(float(ra1) * unit_deg)) p2 = (-r_point_2 * cos(float(ra2) * unit_deg), -r_point_2 * sin(float(ra2) * unit_deg)) # Impose a maximum length of 4 cm on constellation stick figures; they get quite distorted at the edge if hypot(p2[0] - p1[0], p2[1] - p1[1]) > 4 * unit_cm: continue # Stroke a line context.begin_path() context.move_to(x=p1[0], y=p1[1]) context.line_to(x=p2[0], y=p2[1]) context.stroke(color=theme['stick'], line_width=1, dotted=True) # Draw stars from Yale Bright Star Catalogue for star_descriptor in fetch_bright_star_list()['stars'].values(): [ra, dec, mag] = star_descriptor[:3] # Discard stars fainter than mag 4 if mag == "-" or float(mag) > 4.0: continue ra = float(ra) dec = float(dec) # If we're making a southern hemisphere planisphere, we flip the sky upside down if is_southern: ra *= -1 dec *= -1 r = radius(dec=dec, latitude=latitude) if r > r_2: continue # Represent each star with a small circle context.begin_path() context.circle(centre_x=-r * cos(ra * unit_deg), centre_y=-r * sin(ra * unit_deg), radius=0.18 * unit_mm * (5 - mag)) context.fill(color=theme['star']) # Write constellation names context.set_font_size(0.7) context.set_color(theme['constellation']) # Open a list of the coordinates where we place the names of the constellations for line in open("raw_data/constellation_names.dat"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words [name, ra, dec] = line.split()[:3] # Translate constellation name into the requested language, if required if name in text[language]['constellation_translations']: name = text[language]['constellation_translations'][name] ra = float(ra) * 360. / 24 dec = float(dec) # If we're making a southern hemisphere planisphere, we flip the sky upside down if is_southern: ra = -ra dec = -dec # Render name of constellation, with _s turned into spaces name2 = re.sub("_", " ", name) r = radius(dec=dec, latitude=latitude) if r > r_2: continue p = (-r * cos(ra * unit_deg), -r * sin(ra * unit_deg)) a = atan2(p[0], p[1]) context.text(text=name2, x=p[0], y=p[1], h_align=0, v_align=0, gap=0, rotation=unit_rev / 2 - a) # Calendar ring counts clockwise in northern hemisphere; anticlockwise in southern hemisphere s = -1 if not is_southern else 1 def theta2014(d): """ Convert Julian Day into a rotation angle of the sky about the north celestial pole at midnight, relative to spring equinox. :param d: Julian day :return: Rotation angle, radians """ return (d - calendar.julian_day( year=2014, month=3, day=20, hour=16, minute=55, sec=0)) / 365.25 * unit_rev # Write month names around the date scale context.set_font_size(2.3) context.set_color(theme['date']) for mn, (mlen, name) in enumerate(text[language]['months']): theta = s * theta2014( calendar.julian_day(year=2014, month=mn + 1, day=mlen // 2, hour=12, minute=0, sec=0)) # We supply circular_text with a negative radius here, as a fudge to orientate the text with bottom-inwards context.circular_text(text=name, centre_x=0, centre_y=0, radius=-(r_1 * 0.65 + r_2 * 0.35), azimuth=theta / unit_deg + 180, spacing=1, size=1) # Draw ticks for the days of the month for mn, (mlen, name) in enumerate(text[language]['months']): # Tick marks for each day for d in range(1, mlen + 1): theta = s * theta2014( calendar.julian_day(year=2014, month=mn + 1, day=d, hour=0, minute=0, sec=0)) # Days of the month which are multiples of 5 get longer ticks R = r_3 if (d % 5) else r_4 # The last day of each month is drawn as a dividing line between months if d == mlen: R = r_5 # Draw line context.begin_path() context.move_to(x=r_2 * cos(theta), y=-r_2 * sin(theta)) context.line_to(x=R * cos(theta), y=-R * sin(theta)) context.stroke(line_width=1, dotted=False) # Write numeric labels for the 10th, 20th and last day of each month for d in [10, 20, mlen]: theta = s * theta2014( calendar.julian_day(year=2014, month=mn + 1, day=d, hour=0, minute=0, sec=0)) context.set_font_size(1.2) # First digit theta2 = theta + 0.15 * unit_deg context.text(text="%d" % (d / 10), x=r_6 * cos(theta2), y=-r_6 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta + pi / 2) # Second digit theta2 = theta - 0.15 * unit_deg context.text(text="%d" % (d % 10), x=r_6 * cos(theta2), y=-r_6 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta + pi / 2) # Draw the dividing line between the date scale and the star chart context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_2) context.stroke(color=theme['date'], line_width=1, dotted=False)
def do_rendering(self, settings, context): """ This method is required to actually render this item. :param settings: A dictionary of settings required by the renderer. :param context: A GraphicsContext object to use for drawing :return: None """ language = settings['language'] theme = themes[settings['theme']] context.set_color(color=theme['lines']) # Define the radii of all the concentric circles to be drawn on back of mother # Scale of angles around the rim of the astrolabe r_2 = r_1 - d_12 r_3 = r_2 - d_12 / 2 # Zodiacal constellations r_4 = r_3 - d_12 r_5 = r_4 - d_12 # Calendar for 1394 r_6 = r_5 - d_12 r_7 = r_6 - d_12 # Days of the year r_8 = r_7 - d_12 / 2 # Calendar for 1974 r_9 = r_8 - d_12 r_10 = r_9 - d_12 # Saints' days r_11 = r_10 - d_12 r_12 = r_11 - d_12 # Radius of the central hole r_13 = d_12 * centre_scaling # Draw the handle at the top of the astrolabe ang = 180 * unit_deg - acos(unit_cm / r_1) context.begin_path() context.arc(centre_x=0, centre_y=-r_1, radius=2 * unit_cm, arc_from=-ang - pi / 2, arc_to=ang - pi / 2) context.move_to(x=0, y=-r_1 - 2 * unit_cm) context.line_to(x=0, y=-r_1 + 2 * unit_cm) context.stroke() # Draw circles 1-13 onto back of mother context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_1) context.begin_sub_path() context.circle(centre_x=0, centre_y=0, radius=r_13) context.stroke(line_width=1) context.clip() for radius, line_width in ((r_2, 1), (r_3, 3), (r_4, 1), (r_5, 3), (r_6, 1), (r_7, 1), (r_8, 1), (r_9, 1), (r_10, 3), (r_11, 1), (r_12, 1), (r_13, 1)): context.begin_path() context.circle(centre_x=0, centre_y=0, radius=radius) context.stroke(line_width=line_width) # Label space between circles 1-5 with passage of Sun through zodiacal constellations # Mark every 30 degrees, where Sun enters new zodiacal constellation for theta in arange(0 * unit_deg, 359 * unit_deg, 30 * unit_deg): context.begin_path() context.move_to(x=r_1 * cos(theta), y=-r_1 * sin(theta)) context.line_to(x=r_5 * cos(theta), y=-r_5 * sin(theta)) context.stroke() # Mark 5-degree intervals within each zodiacal constellation for theta in arange(0 * unit_deg, 359 * unit_deg, 5 * unit_deg): context.begin_path() context.move_to(x=r_1 * cos(theta), y=-r_1 * sin(theta)) context.line_to(x=r_4 * cos(theta), y=-r_4 * sin(theta)) context.stroke() # Mark fine scale of 1-degree intervals between circles 2 and 3 for theta in arange(0 * unit_deg, 359.9 * unit_deg, 1 * unit_deg): context.begin_path() context.move_to(x=r_2 * cos(theta), y=-r_2 * sin(theta)) context.line_to(x=r_3 * cos(theta), y=-r_3 * sin(theta)) context.stroke() # Between circles 1 and 2, surround the entire astrolabe with a protractor scale from 0 to 90 degrees # Radius from centre for writing the text of the protractor scale around the rim rt_1 = (r_1 + r_2) / 2 for theta in arange(-180 * unit_deg, 179 * unit_deg, 10 * unit_deg): # Work out angle to display around the rim: counts from 0 to 90 four times, not -180 to 180 degrees! if theta < -179 * unit_deg: theta_disp = theta elif theta < -90 * unit_deg: theta_disp = theta + 180 * unit_deg elif theta < 0 * unit_deg: theta_disp = -theta elif theta < 90 * unit_deg: theta_disp = theta else: theta_disp = -theta + 180 * unit_deg # Display angles around rim as rounded integers theta_disp = floor(theta_disp / unit_deg + 0.01) context.set_font_size(1.2) # Display right-hand zero as a simple one-digit zero if theta_disp == 0: theta2 = theta context.text(text="0", x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Display a cross sign at the left-hand zero elif theta_disp == -180: theta2 = theta context.set_font_size(2.1) context.text(text="\u2720", x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=0, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Display all other angles as two digits, carefully arranged with one digit on either side of the line else: theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp / 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp % 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Between circles 3 and 4, mark 10-, 20-, 30-degree points within each zodiacal constellation # Radius for writing the 30 degree scales within each zodiacal constellation rt_2 = (r_3 + r_4) / 2 for theta in arange(-180 * unit_deg, 179 * unit_deg, 10 * unit_deg): context.set_font_size(1.2) # Work out what angle to display, which is rotation angle modulo 30 degrees theta_disp = floor(theta / unit_deg + 380.01) % 30 + 10 # Write two digits separately, with a slight gap between them for the dividing line they label theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp / 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp % 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Write names of zodiacal constellations between circles 4 and 5 for i, item in enumerate(text[language]["zodiacal_constellations"]): i += 1 name = "{} {}".format(item['name'], item['symbol']) context.circular_text(text=name, centre_x=0, centre_y=0, radius=(r_4 * 0.65 + r_5 * 0.35), azimuth=(-15 + 30 * i), spacing=1, size=1) # Between circles 5 and 10, display calendars for 1394 and 1974 # The Tuckerman tables provide the longitude of the Sun along the ecliptic on any given day of the year. # We produce functions which interpolate the tabulated longitudes, so that we can look up the longitude # of the Sun at any moment in time. x_1394 = [] # List of Julian day numbers of supplied data points y_1394 = [] # List of solar longitude values for each data point x_1974 = [] y_1974 = [] for line in open("raw_data/tuckerman.dat", "rt"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words columns = [float(i) for i in line.split()] x_1394.append( calendar.julian_day(year=1394, month=int(columns[0]), day=int(columns[1]), hour=12, minute=0, sec=0)) x_1974.append( calendar.julian_day(year=1974, month=int(columns[0]), day=int(columns[1]), hour=12, minute=0, sec=0)) y_1394.append(30 * unit_deg * (columns[4] - 1) + columns[5] * unit_deg) y_1974.append(30 * unit_deg * (columns[6] - 1) + columns[7] * unit_deg) # Use scipy to do linear interpolation between the supplied data theta_1394 = scipy.interpolate.interp1d(x=x_1394, y=y_1394, kind='linear') theta_1974 = scipy.interpolate.interp1d(x=x_1974, y=y_1974, kind='linear') # Mark 365 days around calendar using the solar longitude data we have. # Write numbers on the 10th, 20th and last day of each month rt_1 = (r_6 + r_7) / 2 # Radius of text for the 1394 calendar rt_2 = (r_8 + r_9) / 2 # Radius of text for the 1974 calendar prev_theta = 30 * unit_deg * (10 - 1) + 9.4 * unit_deg for line in open("raw_data/tuckerman.dat"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == "#"): continue m, d, interval, last, z1394, a1394, z1974, a1974 = [ float(i) for i in line.split() ] # *** Calendar for 1974 *** # Work out azimuth of given date in 1974 calendar theta = 30 * unit_deg * (z1974 - 1) + a1974 * unit_deg # Interpolate interval into number of days since last data point in table (normally five) if prev_theta > theta: prev_theta = prev_theta - unit_rev for i in arange(0, interval - 0.1): theta_day = prev_theta + (theta - prev_theta) * (i + 1) / interval context.begin_path() context.move_to(x=r_7 * cos(theta_day), y=-r_7 * sin(theta_day)) context.line_to(x=r_8 * cos(theta_day), y=-r_8 * sin(theta_day)) context.stroke() prev_theta = theta # Draw a marker line on calendar. Month ends get longer markers if last: context.begin_path() context.move_to(x=r_8 * cos(theta), y=-r_8 * sin(theta)) context.line_to(x=r_10 * cos(theta), y=-r_10 * sin(theta)) context.stroke() else: context.begin_path() context.move_to(x=r_8 * cos(theta), y=-r_8 * sin(theta)) context.line_to(x=r_9 * cos(theta), y=-r_9 * sin(theta)) context.stroke() # Label 10th and 20th day of month, and last day of month if ((d % 10) == 0) or (d > 26): context.set_font_size(1.0) theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(floor(d / 10)), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(d % 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # *** Calendar for 1394 *** # Work out azimuth of given date in 1394 calendar if settings['astrolabe_type'] == 'full': theta = 30 * unit_deg * (z1394 - 1) + a1394 * unit_deg if last: context.begin_path() context.move_to(x=r_5 * cos(theta), y=-r_5 * sin(theta)) context.line_to(x=r_7 * cos(theta), y=-r_7 * sin(theta)) context.stroke() else: context.begin_path() context.move_to(x=r_6 * cos(theta), y=-r_6 * sin(theta)) context.line_to(x=r_7 * cos(theta), y=-r_7 * sin(theta)) context.stroke() # Label 10th and 20th day of month, and last day of month if ((d % 10) == 0) or (d > 26): context.set_font_size(0.75) theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(d / 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(d % 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Label names of months for mn, (mlen, name) in enumerate(text[language]['months']): theta = theta_1974( calendar.julian_day(year=1974, month=mn + 1, day=mlen // 2, hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_9 * 0.65 + r_10 * 0.35, azimuth=theta / unit_deg, spacing=1, size=0.9) if settings['astrolabe_type'] == 'full': theta = theta_1394( calendar.julian_day(year=1394, month=mn + 1, day=mlen // 2, hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_5 * 0.65 + r_6 * 0.35, azimuth=theta / unit_deg, spacing=1, size=0.75) # Add dates of saints days between circles 10 and 12 context.set_font_size(1.0) for line in open("raw_data/saints_days.dat"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == "#"): continue d, m, name = line.split() day_week = floor( calendar.julian_day(year=1974, month=int(m), day=int(d), hour=12, minute=0, sec=0) - calendar.julian_day( year=1974, month=1, day=1, hour=12, minute=0, sec=0)) % 7 sunday_letter = "abcdefg"[day_week:day_week + 1] theta = theta_1974( calendar.julian_day(year=1974, month=int(m), day=int(d), hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_10 * 0.65 + r_11 * 0.35, azimuth=theta / unit_deg, spacing=1, size=1) context.circular_text(text=sunday_letter, centre_x=0, centre_y=0, radius=r_11 * 0.65 + r_12 * 0.35, azimuth=theta / unit_deg, spacing=1, size=1) # Shadow scale in middle of astrolabe if settings['astrolabe_type'] == 'full': context.begin_path() # Draw horizontal radial line labelled Occidens theta_a = 0 * unit_deg context.move_to(x=r_12 * cos(theta_a), y=-r_12 * sin(theta_a)) context.line_to(x=r_13 * cos(theta_a), y=-r_13 * sin(theta_a)) # Radial line between RECTA and VERSA theta_b = -45 * unit_deg context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_13 * cos(theta_b), y=-r_13 * sin(theta_b)) # Radial line between UMBRA and UMBRA theta_c = -135 * unit_deg context.move_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) context.line_to(x=r_13 * cos(theta_c), y=-r_13 * sin(theta_c)) # Draw horizontal radial line labelled Oriens theta_d = 180 * unit_deg context.move_to(x=r_12 * cos(theta_d), y=-r_12 * sin(theta_d)) context.line_to(x=r_13 * cos(theta_d), y=-r_13 * sin(theta_d)) # Vertical line at right edge of shadow scale context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_12 * cos(theta_b), y=0) # Horizontal line along bottom of shadow scale context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) # Vertical line at left edge of shadow scale context.move_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) context.line_to(x=r_12 * cos(theta_c), y=0) # Central vertical line down middle of shadow scale context.move_to(x=0, y=-r_12 * sin(theta_c)) context.line_to(x=0, y=r_13) context.move_to(x=0, y=-r_12) context.line_to(x=0, y=-r_13) rs1 = r_12 - 0.75 * d_12 / 2 # Radius of corners of fine shadow scale rs2 = rs1 - 0.75 * d_12 # Radius of corners of coarse shadow scale # Draw horizontal and vertical sides of the fine and coarse shadow scales context.move_to(x=rs1 * cos(theta_b), y=-rs1 * sin(theta_b)) context.line_to(x=rs1 * cos(theta_b), y=0) context.move_to(x=rs1 * cos(theta_b), y=-rs1 * sin(theta_b)) context.line_to(x=rs1 * cos(theta_c), y=-rs1 * sin(theta_c)) context.move_to(x=rs1 * cos(theta_c), y=-rs1 * sin(theta_c)) context.line_to(x=rs1 * cos(theta_c), y=0) context.move_to(x=rs2 * cos(theta_b), y=-rs2 * sin(theta_b)) context.line_to(x=rs2 * cos(theta_b), y=0) context.move_to(x=rs2 * cos(theta_b), y=-rs2 * sin(theta_b)) context.line_to(x=rs2 * cos(theta_c), y=-rs2 * sin(theta_c)) context.move_to(x=rs2 * cos(theta_c), y=-rs2 * sin(theta_c)) context.line_to(x=rs2 * cos(theta_c), y=0) context.stroke() # Write the UMBRA and VERSA labels on the shadow scale context.set_font_size(0.64) context.text(text="UMBRA", x=-1 * unit_mm, y=-rs2 * sin(theta_c), h_align=1, v_align=-1, gap=0.7 * unit_mm, rotation=0) context.text(text="UMBRA", x=rs2 * cos(theta_c), y=unit_mm, h_align=-1, v_align=-1, gap=0.7 * unit_mm, rotation=pi / 2) context.text(text="RECTA", x=1 * unit_mm, y=-rs2 * sin(theta_c), h_align=-1, v_align=-1, gap=0.7 * unit_mm, rotation=0) context.text(text="VERSA", x=rs2 * cos(theta_b), y=unit_mm, h_align=1, v_align=-1, gap=0.7 * unit_mm, rotation=-pi / 2) context.text(text="ORIENS", x=-r_12 * 0.95, y=0, h_align=-1, v_align=-1, gap=0.8 * unit_mm, rotation=0) context.text(text="OCCIDENS", x=r_12 * 0.95, y=0, h_align=1, v_align=-1, gap=0.8 * unit_mm, rotation=0) r_label = (rs1 + rs2) / 2 offset = 5 * unit_deg # Divisions of scale on shadow scale q = 90 * unit_deg for i in range(1, 12): # Decide how long to make this tick rs = rs2 if (i % 4 == 0) else rs1 # Draw a tick on the shadow scale (right side) theta = -atan(i / 12) context.begin_path() context.move_to(x=rs * cos(theta_b), y=-rs * cos(theta_b) * tan(theta)) context.line_to(x=r_12 * cos(theta_b), y=-r_12 * cos(theta_b) * tan(theta)) context.stroke() # Label every fourth tick if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * cos(theta_b), y=-r_label * cos(theta_b) * tan(theta - offset), h_align=0, v_align=0, gap=0, rotation=-q - theta) # Draw a tick on the shadow scale (bottom right) theta = -atan(12 / i) context.begin_path() context.move_to(x=rs * sin(theta_b) / tan(theta), y=-rs * sin(theta_b)) context.line_to(x=r_12 * sin(theta_b) / tan(theta), y=-r_12 * sin(theta_b)) context.stroke() # Label every fourth tick if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * sin(theta_b) / tan(theta - offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-q - theta) # Draw a tick on the shadow scale (bottom left) theta = -2 * q - theta context.begin_path() context.move_to(x=rs * sin(theta_b) / tan(theta), y=-rs * sin(theta_b)) context.line_to(x=r_12 * sin(theta_b) / tan(theta), y=-r_12 * sin(theta_b)) context.stroke() # Label every fourth tick if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * sin(theta_b) / tan(theta + offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-q - theta) # Draw a tick on the shadow scale (left side) theta = -2 * q + atan(i / 12) context.begin_path() context.move_to(x=rs * cos(theta_c), y=-rs * cos(theta_c) * tan(theta)) context.line_to(x=r_12 * cos(theta_c), y=-r_12 * cos(theta_c) * tan(theta)) context.stroke() # Label every fourth tick if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * cos(theta_c), y=-r_label * cos(theta_c) * tan(theta + offset), h_align=0, v_align=0, gap=0, rotation=-q - theta) # Add the 12s to the ends of the shadow scale theta = -45 * unit_deg context.text(text="12", x=r_label * sin(theta_b) / tan(theta - offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-pi / 4) theta = -135 * unit_deg context.text(text="12", x=r_label * sin(theta_b) / tan(theta + offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=pi / 4) # Unequal hours scale -- the maths behind this is explained in # http://adsabs.harvard.edu/abs/1975JBAA...86...18E # First draw innermost circle, which touches centre of astrolabe and the top of the unequal hours scale context.begin_path() context.circle(centre_x=0, centre_y=-r_12 / 2, radius=r_12 / 2) context.stroke() # Now draw arcs for the hours 1 to 11 for theta in arange(15 * unit_deg, 75.1 * unit_deg, 15 * unit_deg): # Vertical position of the centre of the arc y_centre = r_12 * cos(theta) / 2 + r_12 * sin(theta) / 2 * tan( theta) # Size of arc arc_end = atan2( r_12 * sin(theta), r_12 * cos(theta) / 2 - r_12 * sin(theta) / 2 * tan(theta)) context.begin_path() context.arc(centre_x=0, centre_y=-y_centre, radius=y_centre, arc_from=arc_end - pi / 2, arc_to=-arc_end - pi / 2) context.stroke() # Finish up context.set_color(color=theme['text']) context.circular_text(text=text[language]['copyright'], centre_x=0, centre_y=0, radius=r_12 - 2 * unit_mm, azimuth=270, spacing=1, size=0.7)
def do_rendering(self, settings, context): """ This method is required to actually render this item. :param settings: A dictionary of settings required by the renderer. :param context: A GraphicsContext object to use for drawing :return: None """ language = settings['language'] theme = themes[settings['theme']] context.set_color(color=theme['lines']) # Radii of circles to be drawn on back of mother r_2 = r_1 - d_12 r_3 = r_2 - d_12 / 2 r_4 = r_3 - d_12 r_5 = r_4 - d_12 r_6 = r_5 - d_12 r_7 = r_6 - d_12 r_8 = r_7 - d_12 / 2 r_9 = r_8 - d_12 r_10 = r_9 - d_12 r_11 = r_10 - d_12 r_12 = r_11 - d_12 r_13 = d_12 * centre_scaling # No need for circle 12 in the Hungarian version if language == "hu": r_12=r_11 # Draw the handle at the top of the astrolabe ang = 180 * unit_deg - acos(unit_cm / r_1) context.begin_path() context.arc(centre_x=0, centre_y=-r_1, radius=2 * unit_cm, arc_from=-ang - pi / 2, arc_to=ang - pi / 2) context.move_to(x=0, y=-r_1 - 2 * unit_cm) context.line_to(x=0, y=-r_1 + 2 * unit_cm) context.stroke() # Draw circles 1-13 onto back of mother context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_1) context.begin_sub_path() context.circle(centre_x=0, centre_y=0, radius=r_13) context.stroke(line_width=1) context.clip() context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_2) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_3) context.stroke(line_width=3) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_4) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_5) context.stroke(line_width=3) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_6) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_7) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_8) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_9) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_10) context.stroke(line_width=3) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_11) context.stroke(line_width=1) if language != "hu": context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_12) context.stroke(line_width=1) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_13) context.stroke(line_width=1) # Label space between circles 1-5 with passage of Sun through zodiacal constellations # Mark every 30 degrees, where Sun enters new zodiacal constellation for theta in arange(0 * unit_deg, 359 * unit_deg, 30 * unit_deg): context.begin_path() context.move_to(x=r_1 * cos(theta), y=-r_1 * sin(theta)) context.line_to(x=r_5 * cos(theta), y=-r_5 * sin(theta)) context.stroke() # Mark five degree intervals within each zodiacal constellation for theta in arange(0 * unit_deg, 359 * unit_deg, 5 * unit_deg): context.begin_path() context.move_to(x=r_1 * cos(theta), y=-r_1 * sin(theta)) context.line_to(x=r_4 * cos(theta), y=-r_4 * sin(theta)) context.stroke() # Mark fine scale of 1-degree intervals between circles 2 and 3 for theta in arange(0 * unit_deg, 359.9 * unit_deg, 1 * unit_deg): context.begin_path() context.move_to(x=r_2 * cos(theta), y=-r_2 * sin(theta)) context.line_to(x=r_3 * cos(theta), y=-r_3 * sin(theta)) context.stroke() # Between circles 3 and 4 mark each constellation 10, 20, 30 degrees # Also, between circles 1 and 2, surround entire astrolabe with 0, 10, .. 90 degrees rt_1 = (r_1 + r_2) / 2 rt_2 = (r_3 + r_4) / 2 for theta in arange(-180 * unit_deg, 179 * unit_deg, 10 * unit_deg): if theta < -179 * unit_deg: theta_disp = theta elif theta < - 90 * unit_deg: theta_disp = theta + 180 * unit_deg elif theta < 0 * unit_deg: theta_disp = -theta elif theta < 90 * unit_deg: theta_disp = theta else: theta_disp = -theta + 180 * unit_deg theta_disp = floor(theta_disp / unit_deg + 0.01) context.set_font_size(1.2) if theta_disp == 0: theta2 = theta context.text(text="0", x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) elif theta_disp == -180: theta2 = theta context.set_font_size(2.1) context.text(text="\u2720", x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=0, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) else: theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp / 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp % 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) context.set_font_size(1.2) theta_disp = floor(theta / unit_deg + 380.01) % 30 + 10 theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp / 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(theta_disp % 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Write names of zodiacal constellations for i, item in enumerate(text[language]["zodiacal_constellations"]): i += 1 name = "{} {}".format(item['name'], item['symbol']) context.circular_text(text=name, centre_x=0, centre_y=0, radius=(r_4 * 0.65 + r_5 * 0.35), azimuth=(-15 + 30 * i), spacing=1, size=1) # Between circles 5 and 10, display calendars for 1394 and 1974 # Produce functions which interpolate the Tuckerman tables x_1394 = [] y_1394 = [] x_1974 = [] y_1974 = [] for line in open("raw_data/tuckerman.dat", "rt"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words columns = [float(i) for i in line.split()] x_1394.append(calendar.julian_day(year=1394, month=int(columns[0]), day=int(columns[1]), hour=12, minute=0, sec=0)) x_1974.append(calendar.julian_day(year=1974, month=int(columns[0]), day=int(columns[1]), hour=12, minute=0, sec=0)) y_1394.append(30 * unit_deg * (columns[4] - 1) + columns[5] * unit_deg) y_1974.append(30 * unit_deg * (columns[6] - 1) + columns[7] * unit_deg) theta_1394 = scipy.interpolate.interp1d(x=x_1394, y=y_1394, kind='linear') theta_1974 = scipy.interpolate.interp1d(x=x_1974, y=y_1974, kind='linear') # Use Tuckerman table to mark 365 days around calendar. # Write numbers on the 10th, 20th and last day of each month rt_1 = (r_6 + r_7) / 2 rt_2 = (r_8 + r_9) / 2 last_theta = 30 * unit_deg * (10 - 1) + 9.4 * unit_deg for line in open("raw_data/tuckerman.dat"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == "#"): continue m, d, interval, last, z1394, a1394, z1974, a1974 = [float(i) for i in line.split()] # Work out azimuth of given date in 1974 calendar theta = 30 * unit_deg * (z1974 - 1) + a1974 * unit_deg # Interpolate interval into number of days since last data point in table (normally five) if last_theta > theta: last_theta = last_theta - unit_rev for i in arange(0, interval - 0.1): theta_day = last_theta + (theta - last_theta) * (i + 1) / interval context.begin_path() context.move_to(x=r_7 * cos(theta_day), y=-r_7 * sin(theta_day)) context.line_to(x=r_8 * cos(theta_day), y=-r_8 * sin(theta_day)) context.stroke() last_theta = theta # Draw a marker line on calendar. Month ends get longer markers if last: context.begin_path() context.move_to(x=r_8 * cos(theta), y=-r_8 * sin(theta)) context.line_to(x=r_10 * cos(theta), y=-r_10 * sin(theta)) context.stroke() else: context.begin_path() context.move_to(x=r_8 * cos(theta), y=-r_8 * sin(theta)) context.line_to(x=r_9 * cos(theta), y=-r_9 * sin(theta)) context.stroke() # Label 10th and 20th day of month, and last day of month if ((d % 10) == 0) or (d > 26): context.set_font_size(1.0) theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(d / 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(d % 10), x=rt_2 * cos(theta2), y=-rt_2 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Work out azimuth of given date in 1394 calendar theta = 30 * unit_deg * (z1394 - 1) + a1394 * unit_deg if last: context.begin_path() context.move_to(x=r_5 * cos(theta), y=-r_5 * sin(theta)) context.line_to(x=r_7 * cos(theta), y=-r_7 * sin(theta)) context.stroke() else: context.begin_path() context.move_to(x=r_6 * cos(theta), y=-r_6 * sin(theta)) context.line_to(x=r_7 * cos(theta), y=-r_7 * sin(theta)) context.stroke() # Label 10th and 20th day of month, and last day of month if ((d % 10) == 0) or (d > 26): context.set_font_size(0.75) theta2 = theta - 0.2 * unit_deg context.text(text="{:.0f}".format(d / 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) theta2 = theta + 0.2 * unit_deg context.text(text="{:.0f}".format(d % 10), x=rt_1 * cos(theta2), y=-rt_1 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta - 90 * unit_deg) # Label names of months for mn, (mlen, name) in enumerate(text[language]['months']): theta = theta_1974(calendar.julian_day(year=1974, month=mn + 1, day=mlen // 2, hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_9 * 0.65 + r_10 * 0.35, azimuth=theta / unit_deg, spacing=1, size=0.9) theta = theta_1394(calendar.julian_day(year=1394, month=mn + 1, day=mlen // 2, hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_5 * 0.65 + r_6 * 0.35, azimuth=theta / unit_deg, spacing=1, size=0.75) # Add significant dates between circles 10 and 12 context.set_font_size(1.0) # In a Hungarian astrolabe we generate the old names of the months instead if language != "hu": data_file = "raw_data/saints_days.dat" else: data_file = "raw_data/old_hungarian_months.dat" for line in open(data_file): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == "#"): continue d, m, name = line.split() day_week = floor(calendar.julian_day(year=1974, month=int(m), day=int(d), hour=12, minute=0, sec=0) - calendar.julian_day(year=1974, month=1, day=1, hour=12, minute=0, sec=0)) % 7 sunday_letter = "abcdefg"[day_week:day_week + 1] theta = theta_1974(calendar.julian_day(year=1974, month=int(m), day=int(d), hour=12, minute=0, sec=0)) context.circular_text(text=name, centre_x=0, centre_y=0, radius=r_10 * 0.65 + r_11 * 0.35, azimuth=theta / unit_deg, spacing=1, size=1) if language != "hu": context.circular_text(text=sunday_letter, centre_x=0, centre_y=0, radius=r_11 * 0.65 + r_12 * 0.35, azimuth=theta / unit_deg, spacing=1, size=1) # Shadow scale in middle of astrolabe context.begin_path() theta_a = 0 * unit_deg context.move_to(x=r_12 * cos(theta_a), y=-r_12 * sin(theta_a)) context.line_to(x=r_13 * cos(theta_a), y=-r_13 * sin(theta_a)) theta_b = - 45 * unit_deg context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_13 * cos(theta_b), y=-r_13 * sin(theta_b)) theta_c = -135 * unit_deg context.move_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) context.line_to(x=r_13 * cos(theta_c), y=-r_13 * sin(theta_c)) theta_d = 180 * unit_deg context.move_to(x=r_12 * cos(theta_d), y=-r_12 * sin(theta_d)) context.line_to(x=r_13 * cos(theta_d), y=-r_13 * sin(theta_d)) context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_12 * cos(theta_b), y=0) context.move_to(x=r_12 * cos(theta_b), y=-r_12 * sin(theta_b)) context.line_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) context.move_to(x=r_12 * cos(theta_c), y=-r_12 * sin(theta_c)) context.line_to(x=r_12 * cos(theta_c), y=0) context.move_to(x=0, y=-r_12 * sin(theta_c)) context.line_to(x=0, y=r_13) context.move_to(x=0, y=-r_12) context.line_to(x=0, y=-r_13) rs1 = r_12 - 0.75 * d_12 / 2 rs2 = rs1 - 0.75 * d_12 context.move_to(x=rs1 * cos(theta_b), y=-rs1 * sin(theta_b)) context.line_to(x=rs1 * cos(theta_b), y=0) context.move_to(x=rs1 * cos(theta_b), y=-rs1 * sin(theta_b)) context.line_to(x=rs1 * cos(theta_c), y=-rs1 * sin(theta_c)) context.move_to(x=rs1 * cos(theta_c), y=-rs1 * sin(theta_c)) context.line_to(x=rs1 * cos(theta_c), y=0) context.move_to(x=rs2 * cos(theta_b), y=-rs2 * sin(theta_b)) context.line_to(x=rs2 * cos(theta_b), y=0) context.move_to(x=rs2 * cos(theta_b), y=-rs2 * sin(theta_b)) context.line_to(x=rs2 * cos(theta_c), y=-rs2 * sin(theta_c)) context.move_to(x=rs2 * cos(theta_c), y=-rs2 * sin(theta_c)) context.line_to(x=rs2 * cos(theta_c), y=0) context.stroke() context.set_font_size(0.64) context.text(text="UMBRA", x=-1 * unit_mm, y=-rs2 * sin(theta_c), h_align=1, v_align=-1, gap=0.7 * unit_mm, rotation=0) context.text(text="UMBRA", x=rs2 * cos(theta_c), y=unit_mm, h_align=-1, v_align=-1, gap=0.7 * unit_mm, rotation=pi / 2) context.text(text="RECTA", x=1 * unit_mm, y=-rs2 * sin(theta_c), h_align=-1, v_align=-1, gap=0.7 * unit_mm, rotation=0) context.text(text="VERSA", x=rs2 * cos(theta_b), y=unit_mm, h_align=1, v_align=-1, gap=0.7 * unit_mm, rotation=-pi / 2) context.text(text="ORIENS", x=-r_12 * 0.95, y=0, h_align=-1, v_align=-1, gap=0.8 * unit_mm, rotation=0) context.text(text="OCCIDENS", x=r_12 * 0.95, y=0, h_align=1, v_align=-1, gap=0.8 * unit_mm, rotation=0) r_label = (rs1 + rs2) / 2 offset = 5 * unit_deg # Divisions of scale q = 90 * unit_deg for i in range(1, 12): rs = rs2 if (i % 4 == 0) else rs1 theta = -atan(i / 12) context.begin_path() context.move_to(x=rs * cos(theta_b), y=-rs * cos(theta_b) * tan(theta)) context.line_to(x=r_12 * cos(theta_b), y=-r_12 * cos(theta_b) * tan(theta)) context.stroke() if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * cos(theta_b), y=-r_label * cos(theta_b) * tan(theta - offset), h_align=0, v_align=0, gap=0, rotation=-q - theta) theta = -atan(12 / i) context.begin_path() context.move_to(x=rs * sin(theta_b) / tan(theta), y=-rs * sin(theta_b)) context.line_to(x=r_12 * sin(theta_b) / tan(theta), y=-r_12 * sin(theta_b)) context.stroke() if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * sin(theta_b) / tan(theta - offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-q - theta) theta = -2 * q - theta context.begin_path() context.move_to(x=rs * sin(theta_b) / tan(theta), y=-rs * sin(theta_b)) context.line_to(x=r_12 * sin(theta_b) / tan(theta), y=-r_12 * sin(theta_b)) context.stroke() if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * sin(theta_b) / tan(theta + offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-q - theta) theta = -2 * q + atan(i / 12) context.begin_path() context.move_to(x=rs * cos(theta_c), y=-rs * cos(theta_c) * tan(theta)) context.line_to(x=r_12 * cos(theta_c), y=-r_12 * cos(theta_c) * tan(theta)) context.stroke() if i % 4 == 0: context.text(text="{:d}".format(i), x=r_label * cos(theta_c), y=-r_label * cos(theta_c) * tan(theta + offset), h_align=0, v_align=0, gap=0, rotation=-q - theta) theta = - 45 * unit_deg context.text(text="12", x=r_label * sin(theta_b) / tan(theta - offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=-pi / 4) theta = -135 * unit_deg context.text(text="12", x=r_label * sin(theta_b) / tan(theta + offset), y=-r_label * sin(theta_b), h_align=0, v_align=0, gap=0, rotation=pi / 4) # Unequal hours scale context.begin_path() context.circle(centre_x=0, centre_y=-r_12 / 2, radius=r_12 / 2) context.stroke() for theta in arange(15 * unit_deg, 75.1 * unit_deg, 15 * unit_deg): y_centre = r_12 * cos(theta) / 2 + r_12 * sin(theta) / 2 * tan(theta) arc_end = atan2(r_12 * sin(theta), r_12 * cos(theta) / 2 - r_12 * sin(theta) / 2 * tan(theta)) context.begin_path() context.arc(centre_x=0, centre_y=-y_centre, radius=y_centre, arc_from=arc_end - pi / 2, arc_to=-arc_end - pi / 2) context.stroke() # Finish up context.set_color(color=theme['text']) context.circular_text(text=text[language]['copyright'], centre_x=0, centre_y=0, radius=r_12 - 2 * unit_mm, azimuth=270, spacing=1, size=0.7)
def do_rendering(self, settings, context): """ This method is required to actually render this item. :param settings: A dictionary of settings required by the renderer. :param context: A GraphicsContext object to use for drawing :return: None """ is_southern = settings['latitude'] < 0 language = settings['language'] latitude = abs(settings['latitude']) theme = themes[settings['theme']] context.set_font_size(1.2) r_2 = r_1 - r_gap r_3 = r_1 * 0.1 + r_2 * 0.9 r_4 = r_1 * 0.2 + r_2 * 0.8 r_5 = r_1 r_6 = r_1 * 0.4 + r_2 * 0.6 context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_1) # Outer edge of planisphere context.fill(color=theme['background']) context.begin_sub_path() context.circle(centre_x=0, centre_y=0, radius=central_hole_size) # White out central hole context.stroke(color=theme['edge']) context.clip() for dec in arange(-80, 85, 15): r = radius(dec=dec, latitude=latitude) if r > r_2: continue context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r) context.stroke(color=theme['grid']) # Draw constellation stick figures for line in open("raw_data/constellation_stick_figures.dat", "rt"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words [name, ra1, dec1, ra2, dec2] = line.split() if is_southern: ra1 = -float(ra1) ra2 = -float(ra2) dec1 = -float(dec1) dec2 = -float(dec2) r_point_1 = radius(dec=float(dec1), latitude=latitude) if r_point_1 > r_2: continue r_point_2 = radius(dec=float(dec2), latitude=latitude) if r_point_2 > r_2: continue p1 = (-r_point_1 * cos(float(ra1) * unit_deg), -r_point_1 * sin(float(ra1) * unit_deg)) p2 = (-r_point_2 * cos(float(ra2) * unit_deg), -r_point_2 * sin(float(ra2) * unit_deg)) # Impose a maximum length of 4 cm on constellation stick figures; they get quite distored at the edge if hypot(p2[0] - p1[0], p2[1] - p1[1]) > 4 * unit_cm: continue context.begin_path() context.move_to(x=p1[0], y=p1[1]) context.line_to(x=p2[0], y=p2[1]) context.stroke(color=theme['stick'], line_width=1, dotted=True) # Draw stars from Yale Bright Star Catalogue for star_descriptor in fetch_bright_star_list()['stars'].values(): [ra, dec, mag] = star_descriptor[:3] # Discard stars fainter than mag 4 if mag == "-" or float(mag) > 4.0: continue ra = float(ra) dec = float(dec) if is_southern: ra *= -1 dec *= -1 r = radius(dec=dec, latitude=latitude) if r > r_2: continue context.begin_path() context.circle(centre_x=-r * cos(ra * unit_deg), centre_y=-r * sin(ra * unit_deg), radius=0.18 * unit_mm * (5 - mag)) context.fill(color=theme['star']) # Write constellation names context.set_font_size(0.7) context.set_color(theme['constellation']) for line in open("raw_data/constellation_names.dat"): line = line.strip() # Ignore blank lines and comment lines if (len(line) == 0) or (line[0] == '#'): continue # Split line into words [name, ra, dec] = line.split()[:3] # Translate constellation name, if required if name in text[language]['constellation_translations']: name = text[language]['constellation_translations'][name] ra = float(ra) * 360. / 24 dec = float(dec) if is_southern: ra = -ra dec = -dec name2 = re.sub("_", " ", name) r = radius(dec=dec, latitude=latitude) if r > r_2: continue p = (-r * cos(ra * unit_deg), -r * sin(ra * unit_deg)) a = atan2(p[0], p[1]) context.text(text=name2, x=p[0], y=p[1], h_align=0, v_align=0, gap=0, rotation=unit_rev / 2 - a) # Calendar ring counts clockwise in northern hemisphere; anticlockwise in southern hemisphere s = -1 if not is_southern else 1 def theta2014(d): """ Convert Julian Day into a rotation angle of the sky about the NCP at midnight, relative to spring equinox. :param d: Julian day :return: Rotation angle, radians """ return (d - calendar.julian_day(year=2014, month=3, day=20, hour=16, minute=55, sec=0)) / 365.25 * unit_rev # Write month names context.set_font_size(1.8) context.set_color(theme['date']) for mn, (mlen, name) in enumerate(text[language]['months']): theta = s * theta2014(calendar.julian_day(year=2014, month=mn+1, day=mlen // 2, hour=12, minute=0, sec=0)) # We supply circular_text with a negative radius here, as a fudge to orientate the text with bottom-inwards context.circular_text(text=name, centre_x=0, centre_y=0, radius=-(r_1 * 0.65 + r_2 * 0.35), azimuth=theta / unit_deg + 180, spacing=1, size=1) # Write day ticks for mn, (mlen, name) in enumerate(text[language]['months']): # Tick marks for d in range(1, mlen + 1): theta = s * theta2014(calendar.julian_day(year=2014, month=mn+1, day=d, hour=0, minute=0, sec=0)) R = r_3 if (d % 5) else r_4 # Multiples of 5 get longer ticks if d == mlen: R = r_5 context.begin_path() context.move_to(x=r_2 * cos(theta), y=-r_2 * sin(theta)) context.line_to(x=R * cos(theta), y=-R * sin(theta)) context.stroke(line_width=1, dotted=False) # Numeric labels for d in [10, 20, mlen]: theta = s * theta2014(calendar.julian_day(year=2014, month=mn+1, day=d, hour=0, minute=0, sec=0)) context.set_font_size(1.0) theta2 = theta + 0.15 * unit_deg context.text(text="%d" % (d / 10), x=r_6 * cos(theta2), y=-r_6 * sin(theta2), h_align=1, v_align=0, gap=0, rotation=-theta + pi/2) theta2 = theta - 0.15 * unit_deg context.text(text="%d" % (d % 10), x=r_6 * cos(theta2), y=-r_6 * sin(theta2), h_align=-1, v_align=0, gap=0, rotation=-theta + pi/2) context.begin_path() context.circle(centre_x=0, centre_y=0, radius=r_2) context.stroke(color=theme['date'], line_width=1, dotted=False)