def gnu(self, visible=False): """Return a string for Gnuplot.""" L = [] if visible: pt3 = Point(self.pt1.x, self.pt2.y) # top left pt4 = Point(self.pt2.x, self.pt1.y) # bottom right L.append(self.pt1.gnu()) L.append(self.pt2.gnu()) L.append(pt3.gnu()) L.append(pt4.gnu()) L.append('set object rectangle from {},{} to {},{} fs empty\n'.format( float(self.pt1.x), float(self.pt1.y), float(self.pt2.x), float(self.pt2.y))) return "".join(L)
class Triangle: """The class defining a triangle.""" def __init__(self, *arguments): """Make a triangle in the plane.""" if len(arguments) == 0: self.pt1 = Point(0, 0) self.pt2 = Point(1, 0) self.pt3 = Point(0, 1) elif len(arguments) == 3: if not all(isinstance(pt, Point) for pt in arguments): raise ValueError("arguments are not points") self.pt1, self.pt2, self.pt3 = arguments elif len(arguments) == 6: x1, y1, x2, y2, x3, y3 = arguments self.pt1 = Point(x1, y1) self.pt2 = Point(x2, y2) self.pt3 = Point(x3, y3) else: raise ValueError("bad number of arguments") if (self.pt2 - self.pt1).cross(self.pt3 - self.pt1) == 0: raise ValueError("collinear points") def __repr__(self): """String representation of a triangle.""" return "Triangle({0!r}, {1!r}, {2!r}, {3!r}, {4!r}, {5!r})".format( self.pt1.x, self.pt1.y, self.pt2.x, self.pt2.y, self.pt3.x, self.pt3.y) def copy(self): """Return a copy of a triangle.""" return Triangle(self.pt1, self.pt2, self.pt3) def area(self): """Return the triangle area.""" a = self.pt2 - self.pt1 # powstaja wektory b = self.pt3 - self.pt1 return Fraction(1, 2) * abs(a.cross(b)) # iloczyn wektorowy def center(self): """Return the center of a triangle.""" # Jakby szukanie srodka masy. return (self.pt1 + self.pt2 + self.pt3) * Fraction(1, 3) def make3(self): """Return three smaller triangles (division).""" pt4 = self.center() t1 = Triangle(self.pt1, self.pt2, pt4) t2 = Triangle(self.pt2, self.pt3, pt4) t3 = Triangle(self.pt3, self.pt1, pt4) return (t1, t2, t3) def make4(self): """Return four smaller triangles (division).""" pt4 = Fraction(1, 2) * (self.pt1 + self.pt2) pt5 = Fraction(1, 2) * (self.pt3 + self.pt2) pt6 = Fraction(1, 2) * (self.pt1 + self.pt3) t1 = Triangle(self.pt1, pt4, pt6) t2 = Triangle(self.pt2, pt5, pt4) t3 = Triangle(self.pt3, pt6, pt5) t4 = Triangle(pt4, pt5, pt6) return (t1, t2, t3, t4) def move(self, *arguments): # przesuniecie o (x, y) """Return a new moved triangle.""" if len(arguments) == 1 and isinstance(arguments[0], Point): pt1 = arguments[0] return Triangle(*((pt1 + pt2) for pt2 in (self.pt1, self.pt2, self.pt3))) elif len(arguments) == 2: pt1 = Point(*arguments) return Triangle(*((pt1 + pt2) for pt2 in (self.pt1, self.pt2, self.pt3))) else: raise ValueError("bad arguments") def __eq__(self, other): """Comparison of triangles (t1 == t2).""" return set([self.pt1, self.pt2, self.pt3]) == set([other.pt1, other.pt2, other.pt3]) #return frozenset([self.pt1, self.pt2, self.pt3]) == frozenset([other.pt1, other.pt2, other.pt3]) def __ne__(self, other): """Comparison of triangles (t1 != t2).""" return not self == other def __hash__(self): """Hashable triangles.""" return hash(frozenset([self.pt1, self.pt2, self.pt3])) def __contains__(self, other): """Test if a point is in a triangle.""" if isinstance(other, Point): # Chyba wystarczy sprawdzic, czy punkt jest po tej samej # stronie boku, co przeciwlegly wierzcholek. # Trojkat jest domkniety, zawiera swoj brzeg. # Tu mozna tez uzyc oriented_area(), bo chodzi o znak. a12 = orientation(self.pt1, self.pt2, self.pt3) b12 = orientation(self.pt1, self.pt2, other) a23 = orientation(self.pt2, self.pt3, self.pt1) b23 = orientation(self.pt2, self.pt3, other) a31 = orientation(self.pt3, self.pt1, self.pt2) b31 = orientation(self.pt3, self.pt1, other) return (a12 * b12 >= 0) and (a23 * b23 >= 0) and (a31 * b31 >= 0) elif isinstance(other, Segment): return other.pt1 in self and other.pt2 in self else: raise ValueError() def orientation(self): """Triangle orientation.""" return orientation(self.pt1, self.pt2, self.pt3) def gnu(self, visible=False): """Return a string for Gnuplot.""" L = [] if visible: L.append(self.pt1.gnu()) L.append(self.pt2.gnu()) L.append(self.pt3.gnu()) L.append(Segment(self.pt1, self.pt2).gnu()) L.append(Segment(self.pt1, self.pt3).gnu()) L.append(Segment(self.pt2, self.pt3).gnu()) return "".join(L) def common_segment(self, other): """Find the common segment of two triangles.""" set1 = set([self.pt1, self.pt2, self.pt3]) set2 = set([other.pt1, other.pt2, other.pt3]) set3 = set1 & set2 assert len(set3) == 2 return Segment(set3.pop(), set3.pop()) def third_node(self, pt1, pt2): """Find a third node of a triangle.""" node_set = set([self.pt1, self.pt2, self.pt3]) node_set.remove(pt1) node_set.remove(pt2) assert len(node_set) == 1 return node_set.pop() def in_circumcircle(self, point): """Check if point is inside triangle circumcircle. Formula is taken from https://en.wikipedia.org/wiki/Delaunay_triangulation#Algorithms """ # Preparing parameters for calculating det for 3x3 matrix. a = self.pt1 - point b = self.pt2 - point c = self.pt3 - point det = (a*a) * b.cross(c) - (b*b) * a.cross(c) + (c*c) * a.cross(b) if orientation(self.pt1, self.pt2, self.pt3) > 0: return det > 0 else: return det < 0 def circumcenter(self): """Return the circumcenter for the triangle. https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcircle_equations """ a, b, c = self.pt1, self.pt2, self.pt3 d = 2 * ( a.cross(b) - a.cross(c) + b.cross(c) ) x = ((a*a)*(b.y - c.y) + (b*b)*(c.y - a.y) + (c*c)*(a.y - b.y)) y = ((a*a)*(c.x - b.x) + (b*b)*(a.x - c.x) + (c*c)*(b.x - a.x)) if isinstance((x+y+d), float): return Point(x / float(d), y / float(d)) else: return Point(Fraction(x, d), Fraction(y, d)) def iterpoints(self): """Generate all points on demand (counterclockwise).""" if orientation(self.pt1, self.pt2, self.pt3) > 0: yield self.pt1 yield self.pt2 yield self.pt3 else: yield self.pt1 yield self.pt3 yield self.pt2 def itersegments(self): """Generate all segments on demand (segment.pt1 < segment.pt2).""" if self.pt1 < self.pt2: yield Segment(self.pt1, self.pt2) else: yield Segment(self.pt2, self.pt1) if self.pt1 < self.pt3: yield Segment(self.pt1, self.pt3) else: yield Segment(self.pt3, self.pt1) if self.pt2 < self.pt3: yield Segment(self.pt2, self.pt3) else: yield Segment(self.pt3, self.pt2) def itersegments_oriented(self): """Generate oriented segments (the face is on the right).""" if orientation(self.pt1, self.pt2, self.pt3) > 0: yield Segment(self.pt1, self.pt3) yield Segment(self.pt3, self.pt2) yield Segment(self.pt2, self.pt1) else: yield Segment(self.pt1, self.pt2) yield Segment(self.pt2, self.pt3) yield Segment(self.pt3, self.pt1)
class TestPoint(unittest.TestCase): def setUp(self): self.p1 = Point(3.4, 5.6) self.p2 = Point(4.5, 2.1) self.p3 = Point(Fraction(1, 2), Fraction(2, 3)) def test_print(self): self.assertEqual(repr(Point()),"Point(0, 0)") self.assertEqual(repr(self.p2),"Point(4.5, 2.1)") self.assertEqual(repr(self.p1),"Point(3.4, 5.6)") self.assertEqual(repr(self.p3),"Point(Fraction(1, 2), Fraction(2, 3))") def test_add(self): self.assertAlmostEqual(self.p1 + self.p2, Point(7.9, 7.7)) def test_sub(self): self.assertAlmostEqual(self.p1 - self.p2, Point(-1.1, 3.5)) def test_mul(self): self.assertAlmostEqual(self.p1 * self.p2, 27.06) self.assertAlmostEqual(self.p1 * 2, Point(6.8, 11.2)) self.assertAlmostEqual(2 * self.p1, Point(6.8, 11.2)) self.assertAlmostEqual(Point(2, 3) * 1.5, Point(3.0, 4.5)) self.assertAlmostEqual(1.5 * Point(2, 3), Point(3.0, 4.5)) self.assertAlmostEqual(self.p1.cross(self.p2), -18.06) self.assertAlmostEqual(Point(1, 0).cross(Point(0, 1)), 1) self.assertAlmostEqual(Point(0, 1).cross(Point(1, 0)), -1) def test_copy(self): p3 = self.p1.copy() self.assertEqual(p3, self.p1) self.assertNotEqual(id(p3), id(self.p1)) # inny obiekt def test_length(self): self.assertAlmostEqual(Point(3, 4).length(), 5.0) self.p1 = Point(3.4, 5.6) self.p2 = Point(4.5, 2.1) self.p3 = Point(Fraction(1, 2), Fraction(2, 3)) def test_cmp(self): self.assertTrue(self.p1 == Point(3.4, 5.6)) self.assertFalse(self.p1 == self.p2) self.assertTrue(self.p1 != self.p2) self.assertFalse(self.p1 != self.p1) self.assertTrue(self.p1 < self.p2) self.assertFalse(self.p1 < self.p3) self.assertTrue(self.p1 <= self.p2) self.assertFalse(self.p1 <= self.p3) self.assertTrue(self.p2 > self.p1) self.assertFalse(self.p3 > self.p1) self.assertTrue(self.p2 >= self.p1) self.assertFalse(self.p3 >= self.p1) def test_alpha(self): self.assertEqual(Point(5, 5).alpha(), Fraction(1, 2)) self.assertEqual(Point(-3, 2).alpha(), Fraction(8, 5)) self.assertEqual(Point(-5, -1).alpha(), Fraction(13, 6)) self.assertEqual(Point(1, -3).alpha(), Fraction(13, 4)) self.assertEqual(self.p3.alpha(), Fraction(4, 7)) def test_hash(self): aset = set() aset.add(self.p1) aset.add(self.p1) # ignored self.assertEqual(len(aset), 1) aset.add(self.p2) self.assertEqual(len(aset), 2) aset.add(Point(1, 2)) aset.add(Point(1.0, 2.0)) # ignored, float self.assertEqual(len(aset), 3) def test_gnu(self): self.assertEqual(self.p1.gnu(), 'set label "" at 3.4,5.6 point pt 7 ps 0.5\n') self.assertEqual(self.p2.gnu(), 'set label "" at 4.5,2.1 point pt 7 ps 0.5\n') def tearDown(self): pass
class Rectangle: """The class defining a rectangle.""" def __init__(self, *arguments): """Make a rectangle in the plane.""" if len(arguments) == 0: self.pt1 = Point(0, 0) self.pt2 = Point(1, 1) elif len(arguments) == 2: if not all(isinstance(pt, Point) for pt in arguments): raise ValueError("arguments are not points") self.pt1, self.pt2 = arguments elif len(arguments) == 4: x1, y1, x2, y2 = arguments self.pt1 = Point(x1, y1) self.pt2 = Point(x2, y2) else: raise ValueError("bad number of arguments") # Nie chcemy prostokatow o polu zerowym. if self.pt1.x >= self.pt2.x: raise ValueError("x1 >= x2") if self.pt1.y >= self.pt2.y: raise ValueError("y1 >= y2") def __repr__(self): """String representation of a rectangle.""" return "Rectangle({0!r}, {1!r}, {2!r}, {3!r})".format( self.pt1.x, self.pt1.y, self.pt2.x, self.pt2.y) def copy(self): # zwraca nowa instancje """Return a copy of a rectangle.""" return Rectangle(self.pt1, self.pt2) def center(self): """Return the center of a rectangle.""" return (self.pt1 + self.pt2) * Fraction(1, 2) def area(self): """Return the rectangle area.""" return abs((self.pt2.y - self.pt1.y) * (self.pt2.x - self.pt1.x)) def make4(self): """Return four smaller rectangles (division).""" pt3 = self.center() tl = Rectangle(self.pt1.x, pt3.y, pt3.x, self.pt2.y) # top left tr = Rectangle(pt3.x, pt3.y, self.pt2.x, self.pt2.y) # top right bl = Rectangle(self.pt1.x, self.pt1.y, pt3.x, pt3.y) # bottom left br = Rectangle(pt3.x, self.pt1.y, self.pt2.x, pt3.y) # bottom right # Kolejnosc dopasowana do QuadTree. return (tl, tr, bl, br) def cover(self, other): """Return the smallest rectangle covering given rectangles.""" x1 = min(self.pt1.x, other.pt1.x) y1 = min(self.pt1.y, other.pt1.y) x2 = max(self.pt2.x, other.pt2.x) y2 = max(self.pt2.y, other.pt2.y) return Rectangle(x1, y1, x2, y2) def intersection(self, other): """Return the intersection of given rectangles.""" x1 = max(self.pt1.x, other.pt1.x) y1 = max(self.pt1.y, other.pt1.y) x2 = min(self.pt2.x, other.pt2.x) y2 = min(self.pt2.y, other.pt2.y) return Rectangle(x1, y1, x2, y2) def move(self, *arguments): # przesuniecie o (x, y) """Return a new moved rectangle.""" if len(arguments) == 1 and isinstance(arguments[0], Point): pt = arguments[0] x1 = self.pt1.x + pt.x y1 = self.pt1.y + pt.y x2 = self.pt2.x + pt.x y2 = self.pt2.y + pt.y return Rectangle(x1, y1, x2, y2) elif len(arguments) == 2: x, y = arguments x1 = self.pt1.x + x y1 = self.pt1.y + y x2 = self.pt2.x + x y2 = self.pt2.y + y return Rectangle(x1, y1, x2, y2) else: raise ValueError("bad arguments") def __eq__(self, other): """Comparison of rectangles (r1 == r2).""" return other.pt1 == self.pt1 and other.pt2 == self.pt2 def __ne__(self, other): """Comparison of rectangles (r1 != r2).""" return not self == other def __lt__(self, other): """Comparison of rectangless (r1 < r2).""" return (self.pt1.x, self.pt1.y, self.pt2.x, self.pt2.y) < ( other.pt1.x, other.pt1.y, other.pt2.x, other.pt2.y) def __hash__(self): """Hashable rectangles.""" #return hash((self.pt1, self.pt2)) return hash((self.pt1.x, self.pt1.y, self.pt2.x, self.pt2.y)) def __contains__(self, other): """Test if a point is in a rectangle.""" if isinstance(other, Point): in_x = self.pt1.x <= other.x <= self.pt2.x in_y = self.pt1.y <= other.y <= self.pt2.y return in_x and in_y elif isinstance(other, Segment): return other.pt1 in self and other.pt2 in self else: raise ValueError() def is_square(self): """Test if a rectangle is a square.""" return (self.pt2.x - self.pt1.x) == (self.pt2.y - self.pt1.y) def iterpoints(self): """Generate all points on demand (counterclockwise).""" yield self.pt1 yield Point(self.pt2.x, self.pt1.y) yield self.pt2 yield Point(self.pt1.x, self.pt2.y) def itersegments(self): """Generate all segments on demand (segment.pt1 < segment.pt2).""" yield Segment(self.pt1.x, self.pt1.y, self.pt2.x, self.pt1.y) # bottom yield Segment(self.pt1.x, self.pt1.y, self.pt1.x, self.pt2.y) # left yield Segment(self.pt1.x, self.pt2.y, self.pt2.x, self.pt2.y) # top yield Segment(self.pt2.x, self.pt1.y, self.pt2.x, self.pt2.y) # right def itersegments_oriented(self): """Generate oriented segments (the face is on the right).""" yield Segment(self.pt1.x, self.pt1.y, self.pt1.x, self.pt2.y) # left yield Segment(self.pt1.x, self.pt2.y, self.pt2.x, self.pt2.y) # top yield Segment(self.pt2.x, self.pt2.y, self.pt2.x, self.pt1.y) # right yield Segment(self.pt2.x, self.pt1.y, self.pt1.x, self.pt1.y) # bottom def gnu(self, visible=False): """Return a string for Gnuplot.""" L = [] if visible: pt3 = Point(self.pt1.x, self.pt2.y) # top left pt4 = Point(self.pt2.x, self.pt1.y) # bottom right L.append(self.pt1.gnu()) L.append(self.pt2.gnu()) L.append(pt3.gnu()) L.append(pt4.gnu()) L.append('set object rectangle from {},{} to {},{} fs empty\n'.format( float(self.pt1.x), float(self.pt1.y), float(self.pt2.x), float(self.pt2.y))) return "".join(L)
# Python Gnuplot wrapper # pip install PyGnuplot # Python 2.7 and 3.6 # # https://pypi.org/project/gnuplotlib/ # Gnuplot-based plotting for numpy # pip install gnuplotlib import random import Gnuplot # Python 2 only from planegeometry.structures.points import Point gnu = Gnuplot.Gnuplot(persist=1) for i in range(100): point = Point(random.random(), random.random()) gnu(point.gnu()) # Wyswietlenie grafu. gnu('set terminal pdf enhanced') gnu('set output "random_points.pdf"') gnu('set grid') gnu('unset key') gnu('set size square') #gnu('unset border') #gnu('unset tics') gnu('set xlabel "x"') gnu('set ylabel "y"') gnu('set title "Random points"') gnu('set xrange [{}:{}]'.format(0, 1)) gnu('set yrange [{}:{}]'.format(0, 1)) gnu.plot('NaN title ""')
class Circle: """The class defining a circle.""" def __init__(self, *arguments): """Make a circle in the plane.""" if len(arguments) == 0: self.pt = Point(0, 0) self.radius = 1 elif len(arguments) == 2: self.pt, self.radius = arguments if not isinstance(self.pt, Point): raise ValueError("the first argument is not a point") elif len(arguments) == 3: x, y, self.radius = arguments self.pt = Point(x, y) else: raise ValueError("bad number of arguments") if self.radius < 0: raise ValueError("radius negative") def __repr__(self): """String representation of a circle.""" return "Circle({0!r}, {1!r}, {2!r})".format( self.pt.x, self.pt.y, self.radius) def copy(self): """Return a copy of a circle.""" return Circle(self.pt, self.radius) def center(self): """Return the center of a circle.""" return self.pt def area(self): """Return the circle area.""" return math.pi * self.radius * self.radius def move(self, *arguments): # przesuniecie o (x, y) """Return a new moved circle.""" if len(arguments) == 1 and isinstance(arguments[0], Point): pt = arguments[0] return Circle(self.pt.x + pt.x, self.pt.y + pt.y, self.radius) elif len(arguments) == 2: x, y = arguments return Circle(self.pt.x + x, self.pt.y + y, self.radius) else: raise ValueError("bad arguments") def __eq__(self, other): """Comparison of circles (c1 == c2).""" return self.pt == other.pt and self.radius == other.radius def __ne__(self, other): """Comparison of circles (c1 != c2).""" return not self == other def __lt__(self, other): """Comparison of circles (c1 < c2).""" return (self.pt.x, self.pt.y, self.radius) < ( other.pt.x, other.pt.y, other.radius) def __hash__(self): """Hashable circles.""" #return hash((self.pt, self.radius)) return hash((self.pt.x, self.pt.y, self.radius)) def cover(self, other): """Return the smallest circle covering given circles.""" # Trzeba znalezc srodek i promien. if self.pt == other.pt: radius = max(self.radius, other.radius) return Circle(self.pt, radius) # return self if self.radius > other.radius else other if self.radius <= other.radius: # c1 to mniejszy okrag c1, c2 = self, other else: c1, c2 = other, self # Teraz c1 to mniejszy okrag. distance = (c1.pt - c2.pt).length() if distance + c1.radius <= c2.radius: # c1 in c2 return c2.copy() radius = 0.5 * (distance + c1.radius + c2.radius) # f(t) = c1.pt + (c2.pt - c1.pt) * t t = (radius - c1.radius) / distance #t = 0.5 + (c2.radius - c1.radius)/(2.0 * distance) pt = c1.pt + (c2.pt - c1.pt) * t return Circle(pt, radius) def __contains__(self, other): """Test if a point is in a circle.""" if isinstance(other, Point): #distance = (self.pt - other).length() #return distance <= self.radius # Chce unikac pierwiastkowania i liczb float. vector = self.pt - other return vector * vector <= self.radius * self.radius else: raise ValueError() def gnu(self, visible=False): """Return a string for Gnuplot.""" L = [] if visible: L.append(self.pt.gnu()) L.append('set object circle at {},{} size {} fc rgb "black" fs empty\n'.format( float(self.pt.x), float(self.pt.y), float(self.radius))) return "".join(L)
#!/usr/bin/python import random import subprocess from planegeometry.structures.points import Point proc = subprocess.Popen( ['gnuplot', '--persist'], shell=False, stdin=subprocess.PIPE, ) for i in range(100): point = Point(random.random(), random.random()) #proc.stdin.write(bytearray(point.gnu(), encoqding='utf-8')) proc.stdin.write(bytearray(point.gnu(), encoding='ascii')) proc.stdin.write(b'set terminal pdf enhanced \n') proc.stdin.write(b'set output "random_points3.pdf" \n') proc.stdin.write(b'set grid \n') proc.stdin.write(b'unset key \n') proc.stdin.write(b'set size square \n') proc.stdin.write(b'set xlabel "x" \n') proc.stdin.write(b'set xlabel "y" \n') proc.stdin.write(b'set title "Random points" \n') proc.stdin.write(b'set xrange [0:1] \n') proc.stdin.write(b'set yrange [0:1] \n') proc.stdin.write(b'plot NaN title "" \n') proc.stdin.write(b'unset output \n') proc.stdin.write(b'quit \n')