Source

geoalg / geoalg / vector2d.py

#!/usr/bin/env python
#coding:utf-8
# Author:  mozman
# Purpose: 2d vector math
# module belongs to package geoalg
# Created: 16.03.2010
# Copyright (C) 2010, Manfred Moitzi
# License: GPLv3

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import math

__all__= ['Vec2']

X, Y = (0, 1)

class _Vec2Factory(object):
    def __call__(self, vector):
        """ Vector creation, returns the NULLVEC for vector == (0, 0).
        """
        v = _Vec2(vector)
        return v if not v.isNULL else NULLVEC

    @staticmethod
    def from_floats(x, y):
        """Return vector <x>,<y> (without type checking)."""
        return Vec2((x, y))

    @staticmethod
    def from_points(p1, p2):
        """Return vector from <p1> to <p2>.

        :param p1: 2-tuple or Vec2
        :param p2: 2-tuple or Vec2
        """
        x1, y1 = p1
        x2, y2 = p2
        return Vec2((x2-x1, y2-y1))

    @staticmethod
    def from_angle(angle, length=1.):
        """Return vector from <angle> and <length>."""
        x = math.cos(angle) * length
        y = math.sin(angle) * length
        return Vec2((x, y))

    @staticmethod
    def distance(point1, point2):
        """ calc distance between two 2d points """
        return math.hypot(point1[0]-point2[0], point1[1]-point2[1])

    @staticmethod
    def midpoint(point1, point2):
        """ calc midpoint between point1 and point2 """
        return (point1[0]+point2[0])*.5, (point1[1]+point2[1])*.5

    @staticmethod
    def line_angle(v1, v2):
        """calc angle between v1 -> v2 and the x-axis
        returns: angle in radians
        """
        r = v2 - v1
        return r.angle()

# Vec2 is a factory used like a class
# like: v = Vec2((1, 2))
Vec2 = _Vec2Factory()

class _Vec2(object):
    __slots__ = ['x', 'y']

    def __init__(self, vector):
        """Constructor

        :param vector: 2-tuple
        """
        self.x = float(vector[X])
        self.y = float(vector[Y])

    def __len__(self):
        return 2

    def copy(self):
        return Vec2((self.x, self.y))
    __copy__ = copy

    def __getitem__(self, key):
        """Access vector elements by index.
        """
        # this is important to use a Vec2 object as replacement for a tuple and
        # vice verse.
        return self.__getattribute__(('x', 'y')[key])

    def __setitem__(self, key, value):
        """Set vector elements by index.
        """
        self.__setattr__(('x', 'y')[key], float(value))

    def __iter__(self):
        return iter((self.x, self.y))

    def __hash__(self):
        return hash((self.x, self.y))

    def __abs__(self):
        """Magnitude"""
        return self.length2()**0.5
    length = __abs__

    def length2(self):
        """Squared length for fast distance compare."""
        return self.x*self.x + self.y*self.y

    @property
    def tuple(self):
        return self.x, self.y

    @property
    def isNULL(self):
        return not any(self)

    def normalized(self):
        """Return unit vector."""
        m = self.__abs__()
        return Vec2((self.x/m, self.y/m))

    def perpendicular(self):
        """Return perpendicular vactor."""
        return Vec2.from_floats(-self.y, self.x)

    def __nonzero__(self):
        """Check for zero vector."""
        return bool(self.x or self.y)

    def __neg__(self):
        return Vec2((-self.x, -self.y))

    def __eq__(self, other):
        return self.x == other.x and \
               self.y == other.y

    def __lt__(self, other):
        if self.x == other.x:
            return self.y < other.y
        else:
            return self.x < other.x

    def __add__(self, other):
        if isinstance(other, _Vec2):
            return Vec2((self.x+other.x, self.y+other.y))
        else:
            scalar = float(other)
            return Vec2((self.x+scalar, self.y+scalar))

    def __radd__(self, other):
        return self.__add__(other)

    def __sub__(self, other):
        if isinstance(other, _Vec2):
            return Vec2((self.x-other.x, self.y-other.y))
        else:
            scalar = float(other)
            return Vec2((self.x-scalar, self.y-scalar))

    def __rsub__(self, other):
        if isinstance(other, _Vec2):
            return Vec2((other.x-self.x, other.y-self.y))
        else:
            scalar = float(other)
            return Vec2((scalar-self.x, scalar-self.y))

    def __mul__(self, other):
        scalar = float(other)
        if scalar:
            return Vec2((self.x*scalar, self.y*scalar))
        else:
            return NULLVEC

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        scalar = float(other)
        return Vec2((self.x/scalar, self.y/scalar))
    __div__ = __truediv__

    def __rtruediv__(self, other):
        scalar = float(other)
        return Vec2((scalar/self.x, scalar/self.y))
    __rdiv__ = __rtruediv__

    def dot(self, other):
        """Scalar product"""
        return self.x*other[X] + self.y*other[Y]

    def angle(self):
        """Return angle of vector <self> in radians."""
        return math.atan2(self.y, self.x)

    def rotate(self, angle):
        """Rotate vector <self> about <angle>. (angle in radians)"""
        cos_a = math.cos(angle)
        sin_a = math.sin(angle)
        return Vec2((self.x * cos_a - self.y * sin_a, self.x * sin_a + self.y * cos_a))

    def distance(self, other):
        """Distance to <other> vector."""
        return self.distance2(other) ** 0.5

    def distance2(self, other):
        """Squared distance to <other> vector."""
        dx = self.x - other[X]
        dy = self.y - other[Y]
        return dx*dx + dy*dy

    def mid(self, other):
        """Midpoint between <self> and <other>."""
        return Vec2(((self.x+other[X])*0.5, (self.y+other[Y])*0.5))

class _NullVec(_Vec2):
    def angle(self):
        raise ValueError("for NULLVEC not defined")

    def normalized(self):
        return NULLVEC

    def perpendicular(self):
        return NULLVEC

    def __mul__(self, other):
        return NULLVEC

    def __truediv__(self, other):
        return NULLVEC
    __div__ = __truediv__

    def __rtruediv__(self, other):
        raise ZeroDivisionError("division by NULLVEC")
    __rdiv__ = __rtruediv__

NULLVEC = _NullVec((0, 0))

def distance(point1, point2):
    """ calc distance between two 2d points """
    return math.hypot(point1[X]-point2[X], point1[Y]-point2[Y])

def midpoint(point1, point2):
    """ calc midpoint between point1 and point2 """
    return (point1[X]+point2[X])*.5, (point1[Y]+point2[Y])*.5
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.