Beispiel #1
0
def path1_is_contained_in_path2(path1, path2, crosses=False):
    if path2.length() == 0:
        return False
    if path1.start != path1.end:
        if path2.start != path2.end:
            return False
        if path2.intersect(path1):
            return False

    # find a point that's definitely outside path2
    xmin, xmax, ymin, ymax = path2.bbox()
    B = (xmin + 1) + 1j*(ymax + 1)

    A = path1.start  # pick an arbitrary point in path1
    AB_line = Path(Line(A, B))
    number_of_intersections = len(AB_line.intersect(path2))
    if number_of_intersections % 2 and not crosses:  # if number of intersections is odd
        return True
    elif crosses and number_of_intersections > 0:
        return False
    else:
        return False
class Astrolabe:
    """A proper traveling astrolabe would have a set of plates for each
    clime, with maybe extra plates for special places.

    Morrison:
    > The earliest astrolabes, which were deeply influenced by Greek tradition,
    included plates for the latitudes of the *climates.* The climates of the world 
    were defined by Ptolemy to be the latitudes where the length of the longest 
    day of the year varied by one-half hour. Ptolemy calculated the latitude
    corresponding to a 15-minute difference in the length of the longest day
    (using a value of 23 degrees 51 minutes 20 seconds for the obliquity of
    the ecliptic) for 39 latitudes, which covered the Earth from the equator
    to the North Pole. The ones called the classic *climata* were for the
    half-hour differences in the longest day covering the then populated world."""

    climata = {
        "Meroe": 16.45,
        "Soene": 23.85,
        "Lower Egypt": 30.37,
        "Rhodes": 36.00,
        "Hellespont": 40.93,
        "Mid-Pontus": 45.02,
        "Mouth of Borysthinia": 48.53,
    }

    def __init__(self,
                 obliquity=23.4443291,
                 radius_capricorn=100,
                 plate_parameters=None):
        self.obliquity = obliquity
        self._obliquityRadians = math.radians(obliquity)
        self._obliquityRadiansArgument = math.radians(
            (90 - self.obliquity) / 2)
        self.RadiusCapricorn = radius_capricorn
        self.plate_parameters = self.climata
        self.plate_altitudes = list(range(0, 90, 10))
        self.plate_azimuths = list(range(10, 90, 10))

        self.tropic_arcs()
        self.plates(plate_parameters=plate_parameters)

        # Parameters for layout of graduated limb.
        self.short_tick_angles = list(range(0, 361, 1))
        self.long_tick_angles = list(range(0, 375, 15))

        self.short_tick = 5
        self.long_tick = 15
        self.ticks = {
            "inner_radius": self.RadiusCapricorn,
            "center_radius": self.RadiusCapricorn + self.short_tick,
            "outer_radius": self.RadiusCapricorn + self.long_tick,
            "short_tick_angles": self.short_tick_angles,
            "long_tick_angles": self.long_tick_angles,
        }

        # Ecliptic
        self.RadiusEcliptic = (self.RadiusCapricorn + self.RadiusCancer) / 2.0
        self.yEclipticCenter = (self.RadiusCapricorn - self.RadiusCancer) / 2.0
        self.xEclipticCenter = 0.0

        self.ecliptic_pole = self.RadiusEquator * math.tan(
            self._obliquityRadiansArgument / 2.0)
        self.ecliptic_center = self.RadiusEquator * math.tan(
            self._obliquityRadiansArgument)

        self.ecliptic = {
            "cx": self.xEclipticCenter,
            "cy": self.yEclipticCenter,
            "r": self.RadiusEcliptic,
            "width": 5,
        }

        self.ecliptic_path = Path(
            Arc(
                start=complex(0, self.ecliptic["cy"] + self.ecliptic["r"]),
                radius=(complex(self.ecliptic["r"], self.ecliptic["r"])),
                rotation=0.0,
                large_arc=True,
                sweep=False,
                end=complex(0, self.ecliptic["cy"] - self.ecliptic["r"]),
            ),
            Arc(
                start=complex(0, self.ecliptic["cy"] - self.ecliptic["r"]),
                radius=(complex(self.ecliptic["r"], self.ecliptic["r"])),
                rotation=0.0,
                large_arc=True,
                sweep=False,
                end=complex(0, self.ecliptic["cy"] + self.ecliptic["r"]),
            ),
        )

    def plates(self, plate_parameters=None):
        if plate_parameters is not None:
            self.plate_parameters.update(plate_parameters)

        self.plates = {}
        for location, latitude in self.plate_parameters.items():
            if location not in self.plates:
                self.plates[location] = {
                    "location": location,
                    "latitude": latitude
                }
            # Note: if different locations with same name, will overwrite.
            self.plate(location=location)

    def plate(self, location=None):
        """Compute parts for one plate"""
        plate = self.plates[location]
        plate_latitude = plate["latitude"]

        almucantars = self.almucantar_arcs(altitudes=self.plate_altitudes,
                                           latitude=plate_latitude)
        plate["almucantars"] = almucantars

        almucantar_center = self.almucantar_arc(altitude=80,
                                                latitude=plate_latitude)
        plate["almucantar_center"] = almucantar_center

        plate["horizon"] = self.horizon(latitude=plate_latitude)

        prime_vertical = self.azimuth_arc(azimuth=90,
                                          latitude=plate_latitude,
                                          prime=True)
        plate["prime_vertical"] = prime_vertical[0]  # TODO: clean this up

        azimuth_arcs = self.azimuth_arcs(latitude=plate_latitude)
        plate["azimuths"] = azimuth_arcs

    def horizon(self, latitude=None):
        radiansLatitude = math.radians(latitude)
        rHorizon = self.RadiusEquator / math.sin(radiansLatitude)
        yHorizon = self.RadiusEquator / math.tan(radiansLatitude)
        xHorizon = 0.0
        return {"cx": xHorizon, "cy": yHorizon, "r": rHorizon}

    def tropic_arcs(self):
        r""" The size of an astrolabe is contolled by the radius of the
        tropic of Capricorn. Recall that the tropics represent the
        extreme positions of the sun on its path (the ecliptic) through 
        the course of a year. Summer solstice occurs when the sun reaches
        the tropic of Cancer and winter solstice when the sun reaches the
        tropic of Capricorn. The stereographic projection, from the south
        pole onto the plane of the equator, is visualized as lines from the
        south pole tracing out new curves on the plane of the equator. The
        projection sees the tropics as three concentric circles. From the 
        view of the south pole, the tropic of Capricorn is the outer circle
        and the tropic of Cancer is the inner circle.

        Plate grid equation 1, the tropics.
        R_{Equator} = R_{Capricorn} \tan(\frac{90 - \epsilon}{2}),
        R_{Cancer} = R_{Equator} \tan(\frac{90 - \epsilon}{2})
        """

        self.RadiusEquator = self.RadiusCapricorn * math.tan(
            self._obliquityRadiansArgument)
        self.RadiusCancer = self.RadiusEquator * math.tan(
            self._obliquityRadiansArgument)

    def almucantar_arc(self, altitude, latitude):
        r"""Generate circle of constant altitude.

        Plate grid equation 2, circles of equal altitude (almucantars).
        y_{center} &= R_{Equator}(\frac{\cos\phi}{\sin\phi + \sin a}), & 
        r_{a} &= R_{Equator} (\frac{\cos a}{\sin\phi + \sin a}) \\
        r_{U} &= R_{Equator} \cot(\frac{\phi +  a}{2}), &
        r_{L} &= -R_{Equator} \tan(\frac{\phi -  a}{2})
        """
        radiansAltitude = math.radians(altitude)
        radiansLatitude = math.radians(latitude)

        almucantarCenter = self.RadiusEquator * (
            math.cos(radiansLatitude) /
            (math.sin(radiansLatitude) + math.sin(radiansAltitude)))
        almucantarRadius = self.RadiusEquator * (
            math.cos(radiansAltitude) /
            (math.sin(radiansLatitude) + math.sin(radiansAltitude)))
        return {
            "alt": altitude,
            "cx": 0,
            "cy": almucantarCenter,
            "r": almucantarRadius
        }

    def almucantar_arcs(self, altitudes=None, latitude=None):
        almucantar_coords = []

        for altitude in altitudes:
            almucantar_coords.append(
                self.almucantar_arc(altitude=altitude, latitude=latitude))
        return almucantar_coords

    def azimuth_arc(self, azimuth=None, latitude=None, prime=False):
        # Plate grid equation 3, circles of azimuth.
        radiansMinus = math.radians((90 - latitude) / 2.0)
        radiansPlus = math.radians((90 + latitude) / 2.0)

        yZenith = self.RadiusEquator * math.tan(radiansMinus)
        yNadir = -self.RadiusEquator * math.tan(radiansPlus)
        yCenter = (yZenith + yNadir) / 2.0
        yAzimuth = (yZenith - yNadir) / 2.0

        radiansAzimuth = math.radians(azimuth)
        xAzimuth = yAzimuth * math.tan(radiansAzimuth)
        radiusAzimuth = yAzimuth / math.cos(radiansAzimuth)

        if prime is True:
            coord_left = {"az": 90, "cx": 0, "cy": yCenter, "r": yAzimuth}
            coord_right = {"az": 90, "cx": 0, "cy": yCenter, "r": yAzimuth}
        else:
            coord_left = {
                "az": azimuth,
                "cx": xAzimuth,
                "cy": yCenter,
                "r": radiusAzimuth,
            }
            coord_right = {
                "az": azimuth,
                "cx": -xAzimuth,
                "cy": yCenter,
                "r": radiusAzimuth,
            }

        return [coord_left, coord_right]

    def azimuth_arcs(self, latitude=None):

        azimuth_coords = []
        for azimuth in self.plate_azimuths:
            coords = self.azimuth_arc(azimuth=azimuth, latitude=latitude)
            azimuth_coords.extend(coords)
        return azimuth_coords

    def ecliptic_division(self, constructionAngle=None):

        # The procedure for dividing the ecliptic is (Figure 6-7 with the following steps numbered):
        # 1. Locate the ecliptic pole on the meridian at $R_{eq} \tan(\epsilon / 2)$ from the center.
        # 2. Divide the equator into equal segments of longitude: 12 divisions of 30 for the entry
        #    into each zodiac sign: more divisions depending on the resolution desired.
        # 3. Draw a line from each equator division to the ecliptic pole. The corresponding longitude
        #    point on the ecliptic is where this line intersects the ecliptic circle.
        # 4. A tic mark on the ecliptic is drawn toward the center of the instrument.

        # Endpoint of line on the Equator, through origin with slope give by angle.
        x0 = self.RadiusEquator * math.cos(math.radians(constructionAngle))
        y0 = self.RadiusEquator * math.sin(math.radians(constructionAngle))

        # Endpoint of a line from the ecliptic pole to mark on equator makes
        # an angle theta2.
        theta2 = math.atan2((y0 - self.ecliptic_pole), x0)
        if x0 > 0.0:
            slope = (y0 - self.ecliptic_pole) / x0
            # Draw line through equator division and ecliptic pole.
            # Since the ecliptic is inside the tropic of capricorn circle, overshoot
            # by picking x2 = rcap
            x2 = self.RadiusCapricorn
            y2 = slope * x2 + self.ecliptic_pole
            constructionLine = Line(complex(0, self.ecliptic_pole),
                                    complex(x2, y2))
        elif x0 < 0.0:
            slope = (y0 - self.ecliptic_pole) / x0
            # Draw line through equator division and ecliptic pole.
            # Since the ecliptic is inside the tropic of capricorn circle, overshoot
            # by picking x2 = rcap
            x2 = -self.RadiusCapricorn
            y2 = slope * x2 + self.ecliptic_pole
            constructionLine = Line(complex(0, self.ecliptic_pole),
                                    complex(x2, y2))
        else:
            constructionLine = Line(complex(0, self.ecliptic_pole),
                                    complex(0, self.RadiusCapricorn))

        intersections = []
        for (T1, seg1,
             t1), (T2, seg2,
                   t2) in self.ecliptic_path.intersect(constructionLine):
            p = self.ecliptic_path.point(T1)
            intersections.append(p)

        intersections = [i for i in intersections if i is not None]
        intersections = list(set([(p.real, p.imag) for p in intersections]))

        match_list = []
        while len(intersections) > 0:
            p = intersections.pop()
            l = [p]
            for m, q in enumerate(intersections):
                if math.isclose(p[0], q[0], abs_tol=0.01) and math.isclose(
                        p[1], q[1], abs_tol=0.01):
                    l.append(q)
                    intersections.pop(m)
            match_list.append(l)

        match_list = [{
            "angle": constructionAngle,
            "x2": m[0][0],
            "y2": m[0][1]
        } for m in match_list]
        return match_list[0]