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
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 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 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()
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
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
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))
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)
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)
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)