Example #1
0
        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
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)