예제 #1
0
파일: segment.py 프로젝트: jfarrimo/sabx
    def __init__(self, id, road, fromto, comments, 
                 lanes, shoulder, traffic, speed, waypoints):
        """
        Save the passed-in data.

        @param id: id of the C{Segment}
        @type id: C{string}
        @param road: name of road for this C{Segment}
        @type road: C{string}
        @param fromto: SEGMENT_X to SEGMENT_Y
        @type fromto: C{string}
        @param comments: Extra information about this C{Segment}
        @type comments: C{string}
        @param lanes: number of lanes
        @type lanes: C{string}
        @param shoulder: width of shoulder
        @type shoulder: C{string}
        @param traffic: amount of traffic
        @type traffic: C{string}
        @param speed: speed limit
        @type speed: C{string}
        """
        Line.__init__(self, waypoints)
        self.id = id
        self.road = road
        self.fromto = fromto
        self.comments = comments
        self.lanes = lanes
        self.shoulder = shoulder
        self.traffic = traffic
        self.speed = speed
예제 #2
0
 def __init__(self, dir1=(1, 0, 1), dir2=(1, 1, 1), theta_max=None):
     self.horizontal_line = Line(0, 1, 0)
     self.diagonal_line = Line(1, -1, 0)
     if theta_max is None:
         dirn = np.cross(dir1, dir2)
         if dirn[2] < 0:
             dirn = -dirn
         costheta = dirn[2] / np.linalg.norm(dirn)
         phi = np.arctan2(dirn[1], dirn[0])
         r = 2 / costheta
         rc = r * np.sqrt(1 - costheta**2)
         xc, yc = cartesian(rc, phi)
         self.circle = Circle(xc, yc, r)
     else:
         rmax = stereographic_projection(theta_max)
         self.circle = Circle(0, 0, rmax)
예제 #3
0
 def setUp(self):
     self.p1 = Point(0.0, 0.0)
     self.p2 = Point(5.0, 5.0)
     self.p3 = Point(6.0, 4.0)
     self.p4 = Point(6.0, -1.0)
     self.seg1 = Segment(self.p1, self.p2)
     self.seg2 = Segment(self.p3, self.p4)
     self.line1 = Line(self.p1, self.p2)
     self.line2 = Line(self.p3, self.p4)
     self.pointA = Point(3, 3)
     self.pointB = Point(8, 8)
     self.poly = Polygon(
         (Point(-1, -1), Point(6, -1), Point(5, 6), Point(0, 5)))
     self.square = Polygon(
         (Point(0.0, 0.0), Point(6.0, 0.0), Point(6.0,
                                                  5.0), Point(0.0, 5.0)))
예제 #4
0
파일: car.py 프로젝트: paoli7612/formula1
    def move(self, v):
        x,y = v
        p = self.get_next_pos()
        self.last_pos = self.pos.copy()
        self.pos = p.copy()
        self.pos.add(x, y)
        self.current_line = Line(self.last_pos, self.pos)

        if self.pos.isOut() or self.map.intersect(self):
            self.pos = self.inPos.copy()
            self.last_pos = self.inPos.copy()

        self.inPos = self.pos.copy()
예제 #5
0
 def build_lines(self, ai, active, pi, point):
     """
     Функция построения линий для текущей конфигурации Boundary
     :param ai: либо индекс линии, которой принадлежит active,
                либо None если active не имеет рёбер
     :param active: активная точка
     :param pi: либо индекс линии, которой принадлежит point,
                либо None если point не имеет рёбер
     :param point: точка, к которой перейдёт активность
     """
     # просто переключаем точку если
     # - хотя бы одна точка не является крайней для своей незамкнутой линии
     #   при том, что линии разные (2 случая)
     predicate = ai is not None and ai != pi and \
                 not self.lines[ai].closed and \
                 not self.lines[ai].is_bound_point(active)
     predicate |= pi is not None and ai != pi and \
                  not self.lines[pi].closed and \
                  not self.lines[pi].is_bound_point(point)
     if predicate:
         return
     # новая линия если
     # - точки не имеют линий
     # - обе точки лежат на замкнутых линиях (могут на одной,
     #   а могут на разных
     predicate = ai is None and pi is None
     predicate |= ai is not None and pi is not None and \
                  self.lines[ai].closed and self.lines[pi].closed
     if predicate:
         self.lines.append(Line(active, point))
         return
     # новая линия с частичным замыканием если
     # - одна точка лежит на замкнутой линии, другая не имеет линии (2 случая)
     predicate = ai is None and pi is not None and self.lines[pi].closed
     predicate |= pi is None and ai is not None and self.lines[ai].closed
     if predicate:
         self.lines.append(Line(active, point))
         if ai is not None:
             self.lines[-1].closed_with[0] = self.lines[ai]
         else:
             self.lines[-1].closed_with[1] = self.lines[pi]
     # продолжение линии если
     # - одна из точек является крайней для своей линии,
     #   а вторая не имеет линии (2 случая)
     predicate = pi is None and ai is not None and \
                 self.lines[ai].is_bound_point(active)
     predicate |= ai is None and pi is not None and \
                  self.lines[pi].is_bound_point(point)
     if predicate:
         if pi is None:
             self.lines[ai].add(point, active)
         else:
             self.lines[pi].add(active, point)
         return
     # продолжение линии с частичным замыканием если
     # - одна из точек является крайней для своей линии,
     #   а вторая лежит на замкнутой (2 случая)
     predicate = pi is not None and ai is not None and \
                 self.lines[pi].closed and \
                 self.lines[ai].is_bound_point(active)
     predicate |= ai is not None and pi is not None and \
                  self.lines[ai].closed and \
                  self.lines[pi].is_bound_point(point)
     if predicate:
         if self.lines[pi].closed:
             self.lines[ai].add(point, active, self.lines[pi])
         else:
             self.lines[pi].add(active, point, self.lines[ai])
         return
     # соединение линий если
     # - обе точки лежат на краях разных линий
     predicate = ai is not None and pi is not None and ai != pi and \
                 self.lines[ai].is_bound_point(active) and \
                 self.lines[pi].is_bound_point(point)
     if predicate:
         self.lines[ai].connect_line(self.lines.pop(pi), active, point)
         return
     # замыкание линии если
     # - обе точки являются краями одной линии
     predicate = ai is not None and pi is not None and ai == pi and \
                 self.lines[ai].is_bound_point(active) and \
                 self.lines[pi].is_bound_point(point)
     if predicate:
         self.lines[ai].close()
         return
     # замыкание линии и создание линии от остатка если
     # - обе точки на одной линии и только одна из них крайняя (2 случая)
     predicate = ai is not None and ai == pi and \
                 self.lines[ai].is_bound_point(active)
     predicate |= pi is not None and pi == ai and \
                  self.lines[pi].is_bound_point(point)
     if predicate:
         line = self.lines[ai].close(active, point)
         self.lines.append(line)
         return
예제 #6
0
class Wedge:
    """
    Wedge defined as described above. The circle is defined upon
    initialization by the stereographic projection of the plane spanned by two
    crystal axes, or by a radius. In the latter case, the center of the
    circle is assumed at the origin.
    """
    def __init__(self, dir1=(1, 0, 1), dir2=(1, 1, 1), theta_max=None):
        self.horizontal_line = Line(0, 1, 0)
        self.diagonal_line = Line(1, -1, 0)
        if theta_max is None:
            dirn = np.cross(dir1, dir2)
            if dirn[2] < 0:
                dirn = -dirn
            costheta = dirn[2] / np.linalg.norm(dirn)
            phi = np.arctan2(dirn[1], dirn[0])
            r = 2 / costheta
            rc = r * np.sqrt(1 - costheta**2)
            xc, yc = cartesian(rc, phi)
            self.circle = Circle(xc, yc, r)
        else:
            rmax = stereographic_projection(theta_max)
            self.circle = Circle(0, 0, rmax)

    def contains(self, point, tol=0.001):
        """
        Determine if wedge contains the point.

        :param point: Point.
        :return: Flag indicating whether wedge contains the point.
        """
        return (self.horizontal_line.isbelow(point, tol)
                and self.diagonal_line.isabove(point, tol)
                and self.circle.contains(point, tol))

    def intersect(self, object, tol=0.001):
        """
        Intersect the wedge with a line or circle.

        :param object: Line or circle to be intersected.
        :param tol: absolute tolerance for containing points or
            removing duplicate points.
        :return [(x, y), ...]: List of intersection points.
        """
        # intersect the lines and the circle defining the wedge with the line
        points = list(intersect(self.horizontal_line, object))
        points += list(intersect(self.diagonal_line, object))
        points += list(intersect(self.circle, object))
        # collect points outside the wedge
        delete_points = set()
        for point in points:
            if not self.contains(point):
                delete_points.add(point)
        # collect duplicate points
        points = list(set(points))
        for point1 in points:
            for point2 in points:
                if point1 is point2:
                    break
                x1, y1 = point1
                x2, y2 = point2
                if abs(x1 - x2) < tol and abs(y1 - y2) < tol:
                    delete_points.add(point1)
        # remove points
        for delete_point in delete_points:
            points.remove(delete_point)
        # order according to increasing distance from origin
        def radius(point):
            return np.hypot(*point)

        points.sort(key=radius)
        return points

    def get_polygon(self, dangle=np.radians(1)):
        """
        Get the wedge as a polygon.

        :param dphi: Approximate phi increment of the circle part (deg)
        :return xy: wedge as a polygon (Nx2 array)
        """
        # end points and angles of arc
        points = intersect(self.horizontal_line, self.circle)
        if self.diagonal_line.isabove(points[0]):
            point1 = points[0]
        else:
            point1 = points[1]
        angle1 = np.arctan2(point1[1] - self.circle.yc,
                            point1[0] - self.circle.xc)
        points = intersect(self.diagonal_line, self.circle)
        if self.horizontal_line.isbelow(points[0]):
            point2 = points[0]
        else:
            point2 = points[1]
        angle2 = np.arctan2(point2[1] - self.circle.yc,
                            point2[0] - self.circle.xc)

        # arc
        xy = self.circle.get_polygon(angle1, angle2, dangle)

        # prepend origin
        origin = intersect(self.horizontal_line, self.diagonal_line)
        xy = np.insert(xy, 0, origin[0], axis=0)

        return xy
예제 #7
0

if __name__ == '__main__':
    from math import isclose

    wedge = Wedge()
    print(wedge.circle.xc, wedge.circle.yc, wedge.circle.r)
    print(wedge.get_polygon())

    wedge = Wedge((1, 1, 2), (1, 0, 1))
    print(wedge.circle.xc, wedge.circle.yc, wedge.circle.r)
    print(wedge.get_polygon())

    exit()

    line1 = Line(1, 4, 6)
    line2 = Line(-5, 2, -8)
    circle1 = Circle(-1, 2, 5)
    circle2 = Circle(-2, 0, 4)

    solutions = intersect(line1, line2)
    for solution in solutions:
        x, y = solution
        print('x={}, y={}'.format(x, y))
        assert isclose(x, 2)
        assert isclose(y, 1)

    solutions = intersect(circle1, line1)
    for solution in solutions:
        x, y = solution
        print('x={}, y={}'.format(x, y))
예제 #8
0
class TestGeom(unittest.TestCase):
    def setUp(self):
        self.p1 = Point(0.0, 0.0)
        self.p2 = Point(5.0, 5.0)
        self.p3 = Point(6.0, 4.0)
        self.p4 = Point(6.0, -1.0)
        self.seg1 = Segment(self.p1, self.p2)
        self.seg2 = Segment(self.p3, self.p4)
        self.line1 = Line(self.p1, self.p2)
        self.line2 = Line(self.p3, self.p4)
        self.pointA = Point(3, 3)
        self.pointB = Point(8, 8)
        self.poly = Polygon(
            (Point(-1, -1), Point(6, -1), Point(5, 6), Point(0, 5)))
        self.square = Polygon(
            (Point(0.0, 0.0), Point(6.0, 0.0), Point(6.0,
                                                     5.0), Point(0.0, 5.0)))

    def test_repr(self):
        self.assertEqual(self.p2.__repr__(), "Point(5.0, 5.0)")
        self.assertEqual(self.line1.__repr__(), "[(0.0, 0.0)..(5.0, 5.0)]")

    def test_get_tuple(self):
        self.assertTrue(isinstance(self.p1.get_tuple(), tuple))

    def test_point_introspection(self):
        self.assertAlmostEqual(self.p1.x, self.p1[0], places=6)
        self.assertAlmostEqual(self.p4.y, self.p4[1], places=6)
        self.assertRaises(IndexError, self.p1.__getitem__, 2)

    def test_point_copy(self):
        p = self.p1.copy((2.4, -1.22))
        self.assertAlmostEqual(p.x, 2.4, places=6)
        self.assertAlmostEqual(p.y, -1.22, places=6)

    def test_line_introspection(self):
        self.assertAlmostEqual(self.line2.x1, 6.0, places=6)
        self.assertAlmostEqual(self.line2.y1, 4.0, places=6)
        self.assertAlmostEqual(self.line2.x2, 6.0, places=6)
        self.assertAlmostEqual(self.line2.y2, -1.0, places=6)
        midpt = self.line2.midpoint()
        self.assertAlmostEqual(midpt.x, 6.0, places=6)
        self.assertAlmostEqual(midpt.y, 1.5, places=6)
        self.assertFalse(self.line1.is_vertical())
        self.assertTrue(self.line2.is_vertical())
        self.assertTrue(self.line2.slope() is None)
        self.assertAlmostEqual(self.line1.slope(), 1.0, places=6)
        self.assertAlmostEqual(self.line1.yintercept(), 0.0, places=6)
        self.assertTrue(self.line2.yintercept() is None)

    def test_point_and_dist(self):
        self.assertEqual("%s" % self.p1, "(0.0, 0.0)")
        self.assertEqual("%s" % self.p2, "(5.0, 5.0)")
        self.assertFalse(self.p1 == self.p2)
        self.assertTrue(self.p1 == self.p1)
        # Length should be sqrt(5^2 + 5^2) = 7.071
        self.assertAlmostEqual(dist(self.p1, self.p2), 7.071, places=3)
        self.assertAlmostEqual(self.p1.dist(self.p2), 7.071, places=3)
        # Test distance between a point and a line (perpendicular dist)
        # Correct answer determined using AutoCAD software
        self.assertAlmostEqual(self.p3.dist(self.line1), 1.4142, places=4)
        self.assertTrue(self.p3.dist("Joe") is None)

    def test_point_move(self):
        p = Point(1.0, 3.0)
        p.move(0.75, -2.3)
        self.assertAlmostEqual(p.x, 1.75, places=3)
        self.assertAlmostEqual(p.y, 0.70, places=3)

    def test_point_rotate(self):
        # Rotate point about origin
        p = Point(1.0, 3.0)
        p.rotate(angle=0.5)
        # Correct answer determined using AutoCAD software
        self.assertAlmostEqual(p.x, -0.56069405, places=3)
        self.assertAlmostEqual(p.y, 3.11217322, places=3)

    def test_segment(self):
        self.assertEqual("%s" % self.seg1, "(0.0, 0.0)-(5.0, 5.0)")
        self.assertEqual(self.seg1.intersection(self.seg2), None)
        self.assertEqual(self.seg1.intersection(self.seg1), None)

    def test_line(self):
        # Length should be sqrt(5^2 + 5^2) = 7.071
        self.assertAlmostEqual(self.line1.length(), 7.071, places=3)
        self.assertEqual(self.line1.intersection(self.line2), Point(6.0, 6.0))
        self.assertEqual(self.line1.intersection(self.line1), None)

    def test_polygon(self):
        self.assertTrue(self.poly.point_within(self.pointA))
        self.assertFalse(self.poly.point_within(self.pointB))
        # Area of square = 6*5 = 30
        self.assertAlmostEqual(self.square.area(), 30.0, places=2)

    def test_point_constructors(self):
        pt1 = (4.5, -5.4)
        pt2 = Point.from_tuple(pt1)
        pt3 = Point(x=4.5, y=-5.4)
        pt4 = Point.from_point(pt3)
        self.assertAlmostEqual(pt1[0], pt2.x, places=6)
        self.assertAlmostEqual(pt1[1], pt2.y, places=6)
        self.assertAlmostEqual(pt1[0], pt3.x, places=6)
        self.assertAlmostEqual(pt1[1], pt3.y, places=6)
        self.assertAlmostEqual(pt1[0], pt4.x, places=6)
        self.assertAlmostEqual(pt1[1], pt4.y, places=6)


#    # Test point rotation about another point
#    p5 = Point(8.0, 7.0)                # Original point
#    p6 = Point(8.0, 7.0)                # Original point to be rotated
#    p6.rotate(math.radians(30), p2)     # Rotate about point p2

#    # Test polygon offset
#    poly2 = Polygon((Point(0, 6), Point(4, 2), Point(10,0), Point(9, 9), Point(5, 11)))
#    poly2.offset(1, True)
#    for p in poly2.points:
#        print p
#    poly2.get_segments()
##    poly2.plot()

#    print line1.dir_vector(), line2.dir_vector()

#    print "Test distance from pt1 to pt2: %s" % (p1.dist(p2))
#    print "Test distance from %s to %s: %s" % (p1, seg2, seg2.dist_to_pt(p1))
#    seg3 = Segment(Point(1.0, 2.0), Point(3.0, 3.0))
#    print "Test distance from %s to %s: %s" % (p1, seg3, seg3.dist_to_pt(p1))

    def test_vector(self):
        v1 = Vector(7, 4)
        v2 = Vector(-6, 3)
        self.assertEqual("%s" % v1, "Vector(7.0, 4.0)")
        self.assertEqual("%s" % v2, "Vector(-6.0, 3.0)")
        # length should be sqrt(7**2 + 4**2) = 8.062
        self.assertAlmostEqual(v1.norm(), 8.062, places=3)
        # Create a unit vector and test its length
        v1u = v1.unit_vector()
        self.assertAlmostEqual(v1u.norm(), 1.00, places=3)
        v3 = v1 * 6.4
        self.assertTrue(isinstance(v3, Vector))
        # v3 length = 8.062(6.4) = 51.597
        self.assertAlmostEqual(v3.norm(), 51.597, places=2)
        # v1 + v2 = (1, 7); length = 7.071
        v4 = v1 + v2
        self.assertAlmostEqual(v4.norm(), 7.071, places=3)
        # Dot product of v1, v2 = (7)(-6) + (4)(3) = -30
        self.assertAlmostEqual(v1.dot(v2), -30.0, places=3)
        self.assertAlmostEqual(v2.dot(v1), -30.0, places=3)
        # Cross product of v1 x v2 = (7)(3) - (-6)(4) = 45
        self.assertAlmostEqual(v1.cross(v2), 45.0, places=3)
        # Cross product of v2 x v1 = (-6)(4) - (7)(3) = -45
        self.assertAlmostEqual(v2.cross(v1), -45.0, places=3)
        v5 = v1.perp()
        self.assertAlmostEqual(v5[0], -4.0, places=3)
        self.assertAlmostEqual(v5[1], 7.0, places=3)
예제 #9
0
def plot_grid(frame, dtheta=None, dphi=None, linestyle=':', fig=None, ax=None):
    """
    Plot a grid of theta=const and phi=const lines.

    :param frame: Frame of plotting area (Wedge).
    :param dtheta: Increment between theta=const lines (deg).
        dtheta=None means automatic, dtheta=0 means no grid.
    :param dphi: Increment between phi=const lines (deg).
        dphi=None means automatic, dphi=0 means no grid.
    :param fig: Figure to be plotted to
    :param ax: Axes to be plotted to.
    """
    if fig is None:
        fig = plt.gcf()
    if ax is None:
        ax = plt.gca()

    frame_polygon = frame.get_polygon()

    if dtheta != 0:
        x = frame_polygon[:, 0]
        xmin = np.max(x)
        xmax = np.max(x)
        r = np.hypot(frame_polygon[:, 0], frame_polygon[:, 1])
        theta = np.degrees(inverse_stereographic_projection(r))
        theta_max = np.max(theta)

        if dtheta is None:
            if theta_max > 50:
                dtheta = 10.
            elif theta_max > 20:
                dtheta = 5.
            elif theta_max > 10:
                dtheta = 2.
            elif theta_max > 5:
                dtheta = 1.
            elif theta_max > 2:
                dtheta = 0.5
            elif theta_max > 1:
                dtheta = 0.2
            else:
                dtheta = 0.1

        thetas = np.arange(0., theta_max, dtheta)

        for theta in thetas:
            r = stereographic_projection(np.radians(theta))
            circle = Circle(0., 0., r)
            if theta == 0: print('theta=0')
            points = frame.intersect(circle)
            if len(points) != 2:
                print(points)
                print('plot_grid: skipping theta=' + str(theta))
                continue
            angle1 = np.arctan2(points[0][1], points[0][0])
            angle2 = np.arctan2(points[1][1], points[1][0])
            # assume difference betwenn angles < 180 deg
            dangle = angle2 - angle1
            if -np.pi < dangle < 0 or dangle > np.pi:
                angle1, angle2 = angle2, angle1
            circle_polygon = circle.get_polygon(angle1, angle2)
            circle_patch = mpl.patches.Polygon(circle_polygon,
                                               closed=False,
                                               fill=False,
                                               linestyle=linestyle)
            ax.add_patch(circle_patch)

            if dtheta < 1:
                label = str(theta)
            else:
                label = str(int(theta))
            label += r'$^\circ$'
            position = circle_polygon[0, :]
            if np.hypot(*position) < xmax:
                plot_text(label, position, ha='center', va='top', offset=0.5)

    if dphi != 0:
        phi = np.arctan2(frame_polygon[:, 1], frame_polygon[:, 0])
        phi_min = np.degrees(np.min(phi))
        phi_max = np.degrees(np.max(phi))

        if dphi is None:
            delta_phi = phi_max - phi_min
            if delta_phi > 30:
                dphi = 15.
            elif delta_phi > 20:
                dphi = 10.
            elif delta_phi > 10:
                dphi = 5.
            else:
                dphi = 1.

        imin = round(phi_min / dphi + 1)
        phi_min = imin * dphi
        imax = -round(-phi_max / dphi + 1)
        phi_max = imax * dphi
        phis = np.linspace(phi_min, phi_max, imax - imin + 1)

        for phi in phis:
            line = Line(point1=(0, 0),
                        point2=(np.cos(np.radians(phi)),
                                np.sin(np.radians(phi))))
            points = frame.intersect(line)
            if len(points) != 2:
                print(points)
                print('plot_grid: skipping phi=' + str(phi))
                continue
            x = (points[0][0], points[1][0])
            y = (points[0][1], points[1][1])
            ax.plot(x, y, linestyle=linestyle, color='k')

            label = str(int(phi)) + r'$^\circ$'
            position = points[1]
            plot_text(label, position, ha='left', offset=0.5)
예제 #10
0
def plot_plane(miller,
               frame,
               linestyle='-',
               loc='center',
               offset=0,
               fig=None,
               ax=None):
    """
    Show the plane by drawing a line.

    :param miller: 3-tuple containing the Miller indices.
    :param frame: Frame object to which the line representing the plane is to
        be clipped (Wedge).
    :param linestyle: Line style (if empty or None, no line is drawn)
    :param loc: Position of annotation. Valid choices are 'top',
        'center', and 'bottom'
    :param offset: Offset of the annotation in units of the font size.
        Not applied if pos='center' (if empty or None, no annotation is
        written).
    :param fig: Figure to be plotted to
    :param ax: Axes to be plotted to.
   """
    if fig is None:
        fig = plt.gcf()
    if ax is None:
        ax = plt.gca()

    normal = np.asarray(miller, dtype=float)
    normal /= np.linalg.norm(normal)
    straight = (normal[2] == 0)

    # parameters describing the plane
    if straight:
        phi = -np.arctan2(normal[0], normal[1])
        plane = Line(point1=(0, 0), point2=(np.cos(phi), np.sin(phi)))
    else:
        r = -2 / normal[2]
        phic = np.arctan2(normal[1], normal[0])
        rc = -r * np.hypot(normal[0], normal[1])
        xc, yc = cartesian(rc, phic)
        plane = Circle(xc, yc, r)

    # intersections with frame
    points = frame.intersect(plane)
    if len(points) != 2:
        print(points)
        print('plot_plane: skipping ' + str(miller))
        return

    # text position and rotation
    if straight:
        xy = np.array(points)

        dirt = xy[1] - xy[0]
        dirt /= np.linalg.norm(dirt)
        dirn = np.array((-dirt[1], dirt[0]))
        text_position = 0.5 * (xy[0] + xy[1])
        text_rotation = np.arctan2(dirt[1], dirt[0])
    else:
        arc = Arc(plane.xc, plane.yc, plane.r, points[0], points[1])
        xy = arc.get_polygon()

        anglen = 0.5 * (arc.angle1 + arc.angle2)
        dirn = np.array((np.cos(anglen), np.sin(anglen)))
        text_position = np.array(
            (plane.xc + plane.r * dirn[0], plane.yc + plane.r * dirn[1]))
        text_rotation = anglen - np.pi / 2

    if linestyle:
        path = mpl.patches.Polygon(xy,
                                   closed=False,
                                   fill=False,
                                   linestyle=linestyle)
        ax.add_patch(path)

    if loc:
        # text
        text = '($'
        for m in miller:
            if m >= 0:
                text += str(m)
            else:
                text += r'\overline{' + str(-m) + r'}'
        text += '$)'

        va, ha = loc2align(loc)
        ha = 'center'  # ignore ha
        text_rotation = np.degrees(text_rotation)

        plot_text(text, text_position, ha, va, offset, text_rotation)