'''Vector ====== The :class:`Vector` represents a 2D vector (x, y). Our implementation is built on top of a Python list. An example of constructing a Vector:: >>> # Construct a point at 82,34 >>> v = Vector(82, 34) >>> v[0] 82 >>> v.x 82 >>> v[1] 34 >>> v.y 34 >>> # Construct by giving a list of 2 values >>> pos = (93, 45) >>> v = Vector(pos) >>> v[0] 93 >>> v.x 93 >>> v[1] 45 >>> v.y 45 Optimized usage --------------- Most of the time, you can use a list for arguments instead of using a Vector. For example, if you want to calculate the distance between 2 points:: a = (10, 10) b = (87, 34) # optimized method print('distance between a and b:', Vector(a).distance(b)) # non-optimized method va = Vector(a) vb = Vector(b) print('distance between a and b:', va.distance(vb)) Vector operators ---------------- The :class:`Vector` supports some numeric operators such as +, -, /:: >>> Vector(1, 1) + Vector(9, 5) [10, 6] >>> Vector(9, 5) - Vector(5, 5) [4, 0] >>> Vector(10, 10) / Vector(2., 4.) [5.0, 2.5] >>> Vector(10, 10) / 5. [2.0, 2.0] You can also use in-place operators:: >>> v = Vector(1, 1) >>> v += 2 >>> v [3, 3] >>> v *= 5 [15, 15] >>> v /= 2. [7.5, 7.5] ''' __all__ = ('Vector', ) import math class Vector(list): '''Vector class. See module documentation for more information. ''' def __init__(self, *largs): if len(largs) == 1: super(Vector, self).__init__(largs[0]) elif len(largs) == 2: super(Vector, self).__init__(largs) else: raise Exception('Invalid vector') def _get_x(self): return self[0] def _set_x(self, x): self[0] = x x = property(_get_x, _set_x) ''':attr:`x` represents the first element in the list. >>> v = Vector(12, 23) >>> v[0] 12 >>> v.x 12 ''' def _get_y(self): return self[1] def _set_y(self, y): self[1] = y y = property(_get_y, _set_y) ''':attr:`y` represents the second element in the list. >>> v = Vector(12, 23) >>> v[1] 23 >>> v.y 23 ''' def __getslice__(self, i, j): try: # use the list __getslice__ method and convert # result to vector return Vector(super(Vector, self).__getslice__(i, j)) except Exception: raise TypeError('vector::FAILURE in __getslice__') def __add__(self, val): return Vector(list(map(lambda x, y: x + y, self, val))) def __iadd__(self, val): if type(val) in (int, float): self.x += val self.y += val else: self.x += val.x self.y += val.y return self def __neg__(self): return Vector([-x for x in self]) def __sub__(self, val): return Vector(list(map(lambda x, y: x - y, self, val))) def __isub__(self, val): if type(val) in (int, float): self.x -= val self.y -= val else: self.x -= val.x self.y -= val.y return self def __mul__(self, val): try: return Vector(list(map(lambda x, y: x * y, self, val))) except Exception: return Vector([x * val for x in self]) def __imul__(self, val): if type(val) in (int, float): self.x *= val self.y *= val else: self.x *= val.x self.y *= val.y return self def __rmul__(self, val): return (self * val) def __truediv__(self, val): try: return Vector(list(map(lambda x, y: x / y, self, val))) except Exception: return Vector([x / val for x in self]) def __div__(self, val): try: return Vector(list(map(lambda x, y: x / y, self, val))) except Exception: return Vector([x / val for x in self]) def __rtruediv__(self, val): try: return Vector(*val) / self except Exception: return Vector(val, val) / self def __rdiv__(self, val): try: return Vector(*val) / self except Exception: return Vector(val, val) / self def __idiv__(self, val): if type(val) in (int, float): self.x /= val self.y /= val else: self.x /= val.x self.y /= val.y return self def length(self): '''Returns the length of a vector. >>> Vector(10, 10).length() 14.142135623730951 >>> pos = (10, 10) >>> Vector(pos).length() 14.142135623730951 ''' return math.sqrt(self[0] ** 2 + self[1] ** 2) def length2(self): '''Returns the length of a vector squared. >>> Vector(10, 10).length2() 200 >>> pos = (10, 10) >>> Vector(pos).length2() 200 ''' return self[0] ** 2 + self[1] ** 2 def distance(self, to): '''Returns the distance between two points. >>> Vector(10, 10).distance((5, 10)) 5. >>> a = (90, 33) >>> b = (76, 34) >>> Vector(a).distance(b) 14.035668847618199 ''' return math.sqrt((self[0] - to[0]) ** 2 + (self[1] - to[1]) ** 2) def distance2(self, to): '''Returns the distance between two points squared. >>> Vector(10, 10).distance2((5, 10)) 25 ''' return (self[0] - to[0]) ** 2 + (self[1] - to[1]) ** 2 def normalize(self): '''Returns a new vector that has the same direction as vec, but has a length of one. >>> v = Vector(88, 33).normalize() >>> v [0.93632917756904444, 0.3511234415883917] >>> v.length() 1.0 ''' if self[0] == 0. and self[1] == 0.: return Vector(0., 0.) return self / self.length() def dot(self, a): '''Computes the dot product of a and b. >>> Vector(2, 4).dot((2, 2)) 12 ''' return self[0] * a[0] + self[1] * a[1] def angle(self, a): '''Computes the angle between a and b, and returns the angle in degrees. >>> Vector(100, 0).angle((0, 100)) -90.0 >>> Vector(87, 23).angle((-77, 10)) -157.7920283010705 ''' angle = -(180 / math.pi) * math.atan2( self[0] * a[1] - self[1] * a[0], self[0] * a[0] + self[1] * a[1]) return angle def rotate(self, angle): '''Rotate the vector with an angle in degrees. >>> v = Vector(100, 0) >>> v.rotate(45) [70.71067811865476, 70.71067811865474] ''' angle = math.radians(angle) return Vector( (self[0] * math.cos(angle)) - (self[1] * math.sin(angle)), (self[1] * math.cos(angle)) + (self[0] * math.sin(angle))) @staticmethod def line_intersection(v1, v2, v3, v4): ''' Finds the intersection point between the lines (1)v1->v2 and (2)v3->v4 and returns it as a vector object. >>> a = (98, 28) >>> b = (72, 33) >>> c = (10, -5) >>> d = (20, 88) >>> Vector.line_intersection(a, b, c, d) [15.25931928687196, 43.911669367909241] .. warning:: This is a line intersection method, not a segment intersection. For math see: http://en.wikipedia.org/wiki/Line-line_intersection ''' # linear algebar sucks...seriously!! x1, x2, x3, x4 = float(v1[0]), float(v2[0]), float(v3[0]), float(v4[0]) y1, y2, y3, y4 = float(v1[1]), float(v2[1]), float(v3[1]), float(v4[1]) u = (x1 * y2 - y1 * x2) v = (x3 * y4 - y3 * x4) denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) if denom == 0: return None px = (u * (x3 - x4) - (x1 - x2) * v) / denom py = (u * (y3 - y4) - (y1 - y2) * v) / denom return Vector(px, py) @staticmethod def segment_intersection(v1, v2, v3, v4): ''' Finds the intersection point between segments (1)v1->v2 and (2)v3->v4 and returns it as a vector object. >>> a = (98, 28) >>> b = (72, 33) >>> c = (10, -5) >>> d = (20, 88) >>> Vector.segment_intersection(a, b, c, d) None >>> a = (0, 0) >>> b = (10, 10) >>> c = (0, 10) >>> d = (10, 0) >>> Vector.segment_intersection(a, b, c, d) [5, 5] ''' # Yaaay! I love linear algebra applied within the realms of geometry. x1, x2, x3, x4 = float(v1[0]), float(v2[0]), float(v3[0]), float(v4[0]) y1, y2, y3, y4 = float(v1[1]), float(v2[1]), float(v3[1]), float(v4[1]) # This is mostly the same as the line_intersection u = (x1 * y2 - y1 * x2) v = (x3 * y4 - y3 * x4) denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) if denom == 0: return None px = (u * (x3 - x4) - (x1 - x2) * v) / denom py = (u * (y3 - y4) - (y1 - y2) * v) / denom # Here are the new bits c1 = (x1 <= px <= x2) or (x2 <= px <= x1) or (x1 == x2) c2 = (y1 <= py <= y2) or (y2 <= py <= y1) or (y1 == y2) c3 = (x3 <= px <= x4) or (x4 <= px <= x3) or (x3 == x4) c4 = (y3 <= py <= y4) or (y4 <= py <= y3) or (y3 == y4) if (c1 and c2) and (c3 and c4): return Vector(px, py) else: return None @staticmethod def in_bbox(point, a, b): '''Return True if `point` is in the bounding box defined by `a` and `b`. >>> bmin = (0, 0) >>> bmax = (100, 100) >>> Vector.in_bbox((50, 50), bmin, bmax) True >>> Vector.in_bbox((647, -10), bmin, bmax) False ''' return ((point[0] <= a[0] and point[0] >= b[0] or point[0] <= b[0] and point[0] >= a[0]) and (point[1] <= a[1] and point[1] >= b[1] or point[1] <= b[1] and point[1] >= a[1]))