[python-affine] 01/05: Imported Upstream version 1.0.1

Johan Van de Wauw johanvdw-guest at moszumanska.debian.org
Mon Oct 27 21:36:29 UTC 2014


This is an automated email from the git hooks/post-receive script.

johanvdw-guest pushed a commit to branch master
in repository python-affine.

commit e4d3a85f5f44e4ec47b21ccfac857389eee21813
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date:   Sun Oct 26 20:23:13 2014 +0100

    Imported Upstream version 1.0.1
---
 .gitignore                     |  54 ++++++
 AUTHORS.txt                    |   6 +
 CHANGES.txt                    |  13 ++
 LICENSE.txt                    |  26 +++
 README.rst                     |  90 +++++++++
 affine/__init__.py             | 428 +++++++++++++++++++++++++++++++++++++++++
 affine/tests/__init__.py       |   0
 affine/tests/test_transform.py | 367 +++++++++++++++++++++++++++++++++++
 setup.py                       |  35 ++++
 9 files changed, 1019 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..51cbe85
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,54 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Django stuff:
+*.log
+*.pot
+
+# Sphinx documentation
+docs/_build/
+
diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644
index 0000000..3c9e19e
--- /dev/null
+++ b/AUTHORS.txt
@@ -0,0 +1,6 @@
+Authors
+=======
+
+- Sean Gillies <sean.gillies at gmail.com>
+- Sean Gillies <sean at mapbox.com> (same as ^^)
+- Steven Ring <smr at southsky.com.au>
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..d4a4667
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,13 @@
+CHANGES
+=======
+
+1.0.1 (2014-10-20)
+------------------
+- set_epsilon() now actually sets module EPSILON (#4).
+- add AUTHORS.txt.
+
+1.0 (2014-05-27)
+----------------
+- Code ported from Casey Duncan's Planar package.
+- from_gdal() class method added.
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..1fa75b5
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,26 @@
+Copyright (c) 2014, Sean C. Gillies
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Sean C. Gillies nor the names of
+      its contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..5f1a1cf
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,90 @@
+Affine
+======
+
+Matrices describing affine transformation of the plane
+
+The Affine package is derived from Casey Duncan's Planar package. Please see
+the copyright statement in `affine/__init__.py] <affine/__init__.py>`__.
+
+Usage
+-----
+
+The 3x3 augmented affine transformation matrix for transformations in two
+dimensions is illustrated below.
+
+.. ::
+
+  | x' |   | a  b  c | | x |
+  | y' | = | d  e  f | | y |
+  | 1  |   | 0  0  1 | | 1 |
+
+Matrices can be created by passing the values ``a, b, c, d, e, f`` to the
+``affine.Affine`` constructor or by using its ``identity()``,
+``translation()``, ``scale()``, ``shear()``, and ``rotation()`` class methods.
+
+.. code-block:: pycon
+
+  >>> from affine import Affine
+  >>> Affine.identity()
+  Affine(1.0, 0.0, 0.0,
+         0.0, 1.0, 0.0)
+  >>> Affine.translation(1.0, 5.0)
+  Affine(1.0, 0.0, 1.0,
+         0.0, 1.0, 5.0)
+  >>> Affine.scale(2.0)
+  Affine(2.0, 0.0, 0.0,
+         0.0, 2.0, 0.0)
+  >>> Affine.shear(45.0, 45.0)  # decimal degrees
+  Affine(1.0, 0.9999999999999999, 0.0,
+         0.9999999999999999, 1.0, 0.0)
+  >>> Affine.rotation(45.0)     # decimal degrees
+  Affine(0.7071067811865476, 0.7071067811865475, 0.0,
+         -0.7071067811865475, 0.7071067811865476, 0.0)
+
+These matrices can be applied to `(x, y)` tuples to obtain transformed
+coordinates `(x', y')`.
+
+.. code-block:: pycon
+
+  >>> Affine.translation(1.0, 5.0) * (1.0, 1.0)
+  (2.0, 6.0)
+  >>> Affine.rotation(45.0) * (1.0, 1.0)
+  (1.1102230246251565e-16, 1.414213562373095)
+
+They may also be multiplied together to combine transformations.
+
+.. code-block:: pycon
+
+  >>> Affine.translation(1.0, 5.0) * Affine.rotation(45.0)
+  Affine(0.7071067811865476, 0.7071067811865475, 1.0,
+         -0.7071067811865475, 0.7071067811865476, 5.0)
+
+Usage with GIS data packages
+----------------------------
+
+Georeferenced raster datasets use affine transformations to map from image
+coordinates to world coordinates. The ``affine.Affine.from_gdal()`` class
+method helps convert `GDAL geotransforms
+<http://www.gdal.org/classGDALDataset.html#af9593cc241e7d140f5f3c4798a43a668>`__, 
+sequences of 6 numbers in which the first and fourth are the x and y offsets
+and the second and sixth are the x and y pixel sizes.
+
+Using a GDAL dataset transformation matrix, the world coordinates ``x, y`` 
+corresponding to the top left corner of the pixel 100 rows down from the 
+origin can be easily computed.
+
+.. code-block:: pycon
+
+  >>> fwd = Affine.from_gdal(-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
+  >>> col, row = 0, 100
+  >>> fwd * (col, row)
+  (-237481.5, 195036.4)
+
+The reverse transformation is obained using the `~` operator.
+
+.. code-block:: pycon
+
+  >>> rev = ~fwd
+  >>> rev * fwd * (col, row)
+  (0.0, 99.99999999999999)
+
diff --git a/affine/__init__.py b/affine/__init__.py
new file mode 100644
index 0000000..30f1247
--- /dev/null
+++ b/affine/__init__.py
@@ -0,0 +1,428 @@
+"""Affine transformation matrices
+
+The 3x3 augmented affine transformation matrix for transformations in two
+dimensions is illustrated below.
+
+  | x' |   | a  b  c | | x |
+  | y' | = | d  e  f | | y |
+  | 1  |   | 0  0  1 | | 1 |
+
+The Affine package is derived from Casey Duncan's Planar package. See the
+copyright statement below.
+"""
+
+#############################################################################
+# Copyright (c) 2010 by Casey Duncan
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * Neither the name(s) of the copyright holders nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AS IS AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#############################################################################
+
+from __future__ import division
+
+from collections import namedtuple
+import math
+
+
+__all__ = ['Affine']
+__author__ = "Sean Gillies"
+__version__ = "1.0.1"
+
+EPSILON=1e-5
+EPSILON2=EPSILON**2
+
+def set_epsilon(epsilon):
+    """Set the global absolute error value and rounding limit for approximate
+    floating point comparison operations. This value is accessible via the
+    :attr:`planar.EPSILON` global variable.
+
+    The default value of ``0.00001`` is suitable for values
+    that are in the "countable range". You may need a larger
+    epsilon when using large absolute values, and a smaller value
+    for very small values close to zero. Otherwise approximate
+    comparison operations will not behave as expected.
+    """
+    global EPSILON, EPSILON2
+    EPSILON = float(epsilon)
+    EPSILON2 = EPSILON**2
+
+
+class TransformNotInvertibleError(Exception):
+    """The transform could not be inverted"""
+
+# Define assert_unorderable() depending on the language
+# implicit ordering rules. This keeps things consistent
+# across major Python versions
+try:
+    3 > ""
+except TypeError: # pragma: no cover
+    # No implicit ordering (newer Python)
+    def assert_unorderable(a, b):
+        """Assert that a and b are unorderable"""
+        return NotImplemented
+else: # pragma: no cover
+    # Implicit ordering by default (older Python)
+    # We must raise an exception ourselves
+    # To prevent nonsensical ordering
+    def assert_unorderable(a, b):
+        """Assert that a and b are unorderable"""
+        raise TypeError("unorderable types: %s and %s"
+            % (type(a).__name__, type(b).__name__))
+
+def cached_property(func):
+    """Special property decorator that caches the computed
+    property value in the object's instance dict the first
+    time it is accessed.
+    """
+    name = func.__name__
+    doc = func.__doc__
+    def getter(self, name=name):
+        try:
+            return self.__dict__[name]
+        except KeyError:
+            self.__dict__[name] = value = func(self)
+            return value
+    getter.func_name = name
+    return property(getter, doc=doc)
+
+def cos_sin_deg(deg):
+    """Return the cosine and sin for the given angle
+    in degrees, with special-case handling of multiples
+    of 90 for perfect right angles
+    """
+    deg = deg % 360.0
+    if deg == 90.0:
+        return 0.0, 1.0
+    elif deg == 180.0:
+        return -1.0, 0
+    elif deg == 270.0:
+        return 0, -1.0
+    rad = math.radians(deg)
+    return math.cos(rad), math.sin(rad)
+
+
+class Affine(
+        namedtuple('Affine', ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'))):
+    """Two dimensional affine transform for linear mapping from 2D coordinates
+    to other 2D coordinates. Parallel lines are preserved by these
+    transforms. Affine transforms can perform any combination of translations,
+    scales/flips, shears, and rotations.  Class methods are provided to
+    conveniently compose transforms from these operations.
+
+    Internally the transform is stored as a 3x3 transformation matrix.  The
+    transform may be constructed directly by specifying the first two rows of
+    matrix values as 6 floats. Since the matrix is an affine transform, the
+    last row is always ``(0, 0, 1)``.
+
+    :param members: 6 floats for the first two matrix rows.
+    :type members: float
+    """
+
+    def __new__(self, *members):
+        if len(members) == 6:
+            mat3x3 = [x * 1.0 for x in members] + [0.0, 0.0, 1.0]
+            return tuple.__new__(Affine, mat3x3)
+        else:
+            raise TypeError(
+                "Expected 6 number args, got %s" % len(members))
+
+    @classmethod
+    def from_gdal(cls, c, a, b, f, d, e):
+        members = [a, b, c, d, e, f]
+        mat3x3 = [x * 1.0 for x in members] + [0.0, 0.0, 1.0]
+        return tuple.__new__(Affine, mat3x3)
+
+    @classmethod
+    def identity(cls):
+        """Return the identity transform.
+
+        :rtype: Affine
+        """
+        return identity
+
+    @classmethod
+    def translation(cls, xoff, yoff):
+        """Create a translation transform from an offset vector.
+
+        :param xoff: Translation x offset.
+        :type xoff: float
+        :param yoff: Translation y offset.
+        :type yoff: float
+        :rtype: Affine
+        """
+        return tuple.__new__(cls,
+            (1.0, 0.0, xoff,
+             0.0, 1.0, yoff,
+             0.0, 0.0, 1.0))
+
+    @classmethod
+    def scale(cls, *scaling):
+        """Create a scaling transform from a scalar or vector.
+
+        :param scaling: The scaling factor. A scalar value will
+            scale in both dimensions equally. A vector scaling
+            value scales the dimensions independently.
+        :type scaling: float or sequence
+        :rtype: Affine
+        """
+        if len(scaling) == 1:
+            sx = sy = float(scaling[0])
+        else:
+            sx, sy = scaling
+        return tuple.__new__(cls,
+            (sx, 0.0, 0.0,
+             0.0, sy, 0.0,
+             0.0, 0.0, 1.0))
+
+    @classmethod
+    def shear(cls, x_angle=0, y_angle=0):
+        """Create a shear transform along one or both axes.
+
+        :param x_angle: Angle in degrees to shear along the x-axis.
+        :type x_angle: float
+        :param y_angle: Angle in degrees to shear along the y-axis.
+        :type y_angle: float
+        :rtype: Affine
+        """
+        sx = math.tan(math.radians(x_angle))
+        sy = math.tan(math.radians(y_angle))
+        return tuple.__new__(cls,
+            (1.0, sy, 0.0,
+             sx, 1.0, 0.0,
+             0.0, 0.0, 1.0))
+
+    @classmethod
+    def rotation(cls, angle, pivot=None):
+        """Create a rotation transform at the specified angle,
+        optionally about the specified pivot point.
+
+        :param angle: Rotation angle in degrees
+        :type angle: float
+        :param pivot: Point to rotate about, if omitted the
+            rotation is about the origin.
+        :type pivot: sequence
+        :rtype: Affine
+        """
+        ca, sa = cos_sin_deg(angle)
+        if pivot is None:
+            return tuple.__new__(cls,
+                (ca, sa, 0.0,
+                -sa, ca, 0.0,
+                 0.0, 0.0, 1.0))
+        else:
+            px, py = pivot
+            return tuple.__new__(cls,
+                (ca, sa, px - px*ca + py*sa,
+                -sa, ca, py - px*sa - py*ca,
+                 0.0, 0.0, 1.0))
+
+    def __str__(self):
+        """Concise string representation."""
+        return ("|% .2f,% .2f,% .2f|\n"
+                "|% .2f,% .2f,% .2f|\n"
+                "|% .2f,% .2f,% .2f|") % self
+
+    def __repr__(self):
+        """Precise string representation."""
+        return ("Affine(%r, %r, %r,\n"
+                "       %r, %r, %r)") % self[:6]
+
+    def to_gdal(self):
+        return (self.c, self.a, self.b, self.f, self.d, self.e)
+
+    @property
+    def xoff(self):
+        return self.c
+
+    @property
+    def yoff(self):
+        return self.f
+
+    @cached_property
+    def determinant(self):
+        """The determinant of the transform matrix. This value
+        is equal to the area scaling factor when the transform
+        is applied to a shape.
+        """
+        a, b, c, d, e, f, g, h, i = self
+        return a*e - b*d
+
+    @cached_property
+    def is_identity(self):
+        """True if this transform equals the identity matrix,
+        within rounding limits.
+        """
+        return self is identity or self.almost_equals(identity)
+
+    @cached_property
+    def is_rectilinear(self):
+        """True if the transform is rectilinear, i.e., whether a shape would
+        remain axis-aligned, within rounding limits, after applying the
+        transform.
+        """
+        a, b, c, d, e, f, g, h, i = self
+        return ((abs(a) < EPSILON and abs(e) < EPSILON)
+            or (abs(d) < EPSILON and abs(b) < EPSILON))
+
+    @cached_property
+    def is_conformal(self):
+        """True if the transform is conformal, i.e., if angles between points
+        are preserved after applying the transform, within rounding limits.
+        This implies that the transform has no effective shear.
+        """
+        a, b, c, d, e, f, g, h, i = self
+        return abs(a*b + d*e) < EPSILON
+
+    @cached_property
+    def is_orthonormal(self):
+        """True if the transform is orthonormal, which means that the
+        transform represents a rigid motion, which has no effective scaling or
+        shear. Mathematically, this means that the axis vectors of the
+        transform matrix are perpendicular and unit-length.  Applying an
+        orthonormal transform to a shape always results in a congruent shape.
+        """
+        a, b, c, d, e, f, g, h, i = self
+        return (self.is_conformal
+            and abs(1.0 - (a*a + d*d)) < EPSILON
+            and abs(1.0 - (b*b + e*e)) < EPSILON)
+
+    @cached_property
+    def is_degenerate(self):
+        """True if this transform is degenerate, which means that it will
+        collapse a shape to an effective area of zero. Degenerate transforms
+        cannot be inverted.
+        """
+        return abs(self.determinant) < EPSILON
+
+    @property
+    def column_vectors(self):
+        """The values of the transform as three 2D column vectors"""
+        a, b, c, d, e, f, _, _, _ = self
+        return (a, d), (b, e), (c, f)
+
+    def almost_equals(self, other):
+        """Compare transforms for approximate equality.
+
+        :param other: Transform being compared.
+        :type other: Affine
+        :return: True if absolute difference between each element
+            of each respective tranform matrix < ``EPSILON``.
+        """
+        for i in (0, 1, 2, 3, 4, 5):
+            if abs(self[i] - other[i]) >= EPSILON:
+                return False
+        return True
+
+    def __gt__(self, other):
+        return assert_unorderable(self, other)
+
+    __ge__ = __lt__ = __le__ = __gt__
+
+    # Override from base class. We do not support entrywise
+    # addition, subtraction or scalar multiplication because
+    # the result is not an affine transform
+
+    def __add__(self, other):
+        raise TypeError("Operation not supported")
+
+    __iadd__ = __add__
+
+    def __mul__(self, other):
+        """Apply the transform using matrix multiplication, creating a
+        resulting object of the same type.  A transform may be applied to
+        another transform, a vector, vector array, or shape.
+
+        :param other: The object to transform.
+        :type other: Affine, :class:`~planar.Vec2`,
+            :class:`~planar.Vec2Array`, :class:`~planar.Shape`
+        :rtype: Same as ``other``
+        """
+        sa, sb, sc, sd, se, sf, _, _, _ = self
+        if isinstance(other, Affine):
+            oa, ob, oc, od, oe, of, _, _, _ = other
+            return tuple.__new__(Affine,
+                (sa*oa + sb*od, sa*ob + sb*oe, sa*oc + sb*of + sc,
+                 sd*oa + se*od, sd*ob + se*oe, sd*oc + se*of + sf,
+                 0.0, 0.0, 1.0))
+        else:
+            try:
+                vx, vy = other
+            except Exception:
+                return NotImplemented
+            return (vx*sa + vy*sd + sc, vx*sb + vy*se + sf)
+
+    def __rmul__(self, other):
+        # We should not be called if other is an affine instance
+        # This is just a guarantee, since we would potentially
+        # return the wrong answer in that case
+        assert not isinstance(other, Affine)
+        return self.__mul__(other)
+
+    def __imul__(self, other):
+        if isinstance(other, Affine) or isinstance(other, tuple):
+            return self.__mul__(other)
+        else:
+            return NotImplemented
+
+    def itransform(self, seq):
+        """Transform a sequence of points or vectors in place.
+
+        :param seq: Mutable sequence of :class:`~planar.Vec2` to be
+            transformed.
+        :returns: None, the input sequence is mutated in place.
+        """
+        if self is not identity and self != identity:
+            sa, sb, sc, sd, se, sf, _, _, _ = self
+            for i, (x, y) in enumerate(seq):
+                seq[i] = (x*sa + y*sd + sc, x*sb + y*se + sf)
+
+    def __invert__(self):
+        """Return the inverse transform.
+
+        :raises: :except:`TransformNotInvertible` if the transform
+            is degenerate.
+        """
+        if self.is_degenerate:
+            raise TransformNotInvertibleError(
+                "Cannot invert degenerate transform")
+        idet = 1.0 / self.determinant
+        sa, sb, sc, sd, se, sf, _, _, _ = self
+        ra = se * idet
+        rb = -sb * idet
+        rd = -sd * idet
+        re = sa * idet
+        return tuple.__new__(Affine,
+            (ra, rb, -sc*ra - sf*rb,
+             rd, re, -sc*rd - sf*re,
+             0.0, 0.0, 1.0))
+
+    __hash__ = tuple.__hash__ # hash is not inherited in Py 3
+
+
+identity = Affine(1, 0, 0, 0, 1, 0)
+"""The identity transform"""
+
+
+# vim: ai ts=4 sts=4 et sw=4 tw=78
+
diff --git a/affine/tests/__init__.py b/affine/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/affine/tests/test_transform.py b/affine/tests/test_transform.py
new file mode 100644
index 0000000..e731e02
--- /dev/null
+++ b/affine/tests/test_transform.py
@@ -0,0 +1,367 @@
+#############################################################################
+# Planar is Copyright (c) 2010 by Casey Duncan
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, 
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * Neither the name(s) of the copyright holders nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AS IS AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#############################################################################
+
+"""Transform unit tests"""
+
+from __future__ import division
+import sys
+import math
+import unittest
+from nose.tools import assert_equal, assert_almost_equal, raises
+
+from affine import Affine
+
+
+def seq_almost_equal(t1, t2, error=0.00001):
+    assert len(t1) == len(t2), "%r != %r" % (t1, t2)
+    for m1, m2 in zip(t1, t2):
+        assert abs(m1 - m2) <= error, "%r != %r" % (t1, t2)
+
+
+class PyAffineTestCase(unittest.TestCase):
+
+    @raises(TypeError)
+    def test_zero_args(self):
+        Affine()
+
+    @raises(TypeError)
+    def test_wrong_arg_type(self):
+        Affine(None)
+
+    @raises(TypeError)
+    def test_args_too_few(self):
+        Affine(1, 2)
+
+    @raises(TypeError)
+    def test_args_too_many(self):
+        Affine(*range(10))
+
+    @raises(TypeError)
+    def test_args_members_wrong_type(self):
+        Affine(0, 2, 3, None, None, "")
+
+    def test_len(self):
+        t = Affine(1, 2, 3, 4, 5, 6)
+        assert_equal(len(t), 9)
+
+    def test_slice_last_row(self):
+        t = Affine(1, 2, 3, 4, 5, 6)
+        assert_equal(t[-3:], (0, 0, 1))
+
+    def test_members_are_floats(self):
+        t = Affine(1, 2, 3, 4, 5, 6)
+        for m in t:
+            assert isinstance(m, float), repr(m)
+
+    def test_getitem(self):
+        t = Affine(1, 2, 3, 4, 5, 6)
+        assert_equal(t[0], 1)
+        assert_equal(t[1], 2)
+        assert_equal(t[2], 3)
+        assert_equal(t[3], 4)
+        assert_equal(t[4], 5)
+        assert_equal(t[5], 6)
+        assert_equal(t[6], 0)
+        assert_equal(t[7], 0)
+        assert_equal(t[8], 1)
+        assert_equal(t[-1], 1)
+
+    @raises(TypeError)
+    def test_getitem_wrong_type(self):
+        t = Affine(1, 2, 3, 4, 5, 6)
+        t['foobar']
+
+    def test_str(self):
+        assert_equal(
+            str(Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)), 
+            "| 1.11, 2.22, 3.33|\n|-4.44,-5.55, 6.67|\n| 0.00, 0.00, 1.00|")
+
+    def test_repr(self):
+        assert_equal(
+            repr(Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)), 
+            ("Affine(1.111, 2.222, 3.456,\n"
+             "       4.444, 5.5, 6.25)"))
+
+    def test_identity_constructor(self):
+        ident = Affine.identity()
+        assert isinstance(ident, Affine)
+        assert_equal(tuple(ident), (1,0,0, 0,1,0, 0,0,1))
+        assert ident.is_identity
+
+    def test_translation_constructor(self):
+        trans = Affine.translation(2, -5)
+        assert isinstance(trans, Affine)
+        assert_equal(tuple(trans), (1,0,2, 0,1,-5, 0,0,1))
+
+    def test_scale_constructor(self):
+        scale = Affine.scale(5)
+        assert isinstance(scale, Affine)
+        assert_equal(tuple(scale), (5,0,0, 0,5,0, 0,0,1))
+        scale = Affine.scale(-1, 2)
+        assert_equal(tuple(scale), (-1,0,0, 0,2,0, 0,0,1))
+        assert_equal(tuple(Affine.scale(1)), 
+            tuple(Affine.identity()))
+
+    def test_shear_constructor(self):
+        shear = Affine.shear(30)
+        assert isinstance(shear, Affine)
+        sx = math.tan(math.radians(30))
+        seq_almost_equal(tuple(shear), (1,0,0, sx,1,0, 0,0,1))
+        shear = Affine.shear(-15, 60)
+        sx = math.tan(math.radians(-15))
+        sy = math.tan(math.radians(60))
+        seq_almost_equal(tuple(shear), (1,sy,0, sx,1,0, 0,0,1))
+        shear = Affine.shear(y_angle=45)
+        seq_almost_equal(tuple(shear), (1,1,0, 0,1,0, 0,0,1))
+
+    def test_rotation_constructor(self):
+        rot = Affine.rotation(60)
+        assert isinstance(rot, Affine)
+        r = math.radians(60)
+        s, c = math.sin(r), math.cos(r)
+        assert_equal(tuple(rot), (c,s,0, -s,c,0, 0,0,1))
+        rot = Affine.rotation(337)
+        r = math.radians(337)
+        s, c = math.sin(r), math.cos(r)
+        seq_almost_equal(tuple(rot), (c,s,0, -s,c,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(0)), 
+            tuple(Affine.identity()))
+
+    def test_rotation_constructor_quadrants(self):
+        assert_equal(tuple(Affine.rotation(0)), (1,0,0, 0,1,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(90)), (0,1,0, -1,0,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(180)), (-1,0,0, 0,-1,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(-180)), (-1,0,0, 0,-1,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(270)), (0,-1,0, 1,0,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(-90)), (0,-1,0, 1,0,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(360)), (1,0,0, 0,1,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(450)), (0,1,0, -1,0,0, 0,0,1))
+        assert_equal(tuple(Affine.rotation(-450)), (0,-1,0, 1,0,0, 0,0,1))
+
+    def test_rotation_constructor_with_pivot(self):
+        assert_equal(tuple(Affine.rotation(60)),
+            tuple(Affine.rotation(60, pivot=(0,0))))
+        rot = Affine.rotation(27, pivot=(2,-4))
+        r = math.radians(27)
+        s, c = math.sin(r), math.cos(r)
+        assert_equal(tuple(rot), 
+            (c,s,2 - 2*c - 4*s, -s,c,-4 - 2*s + 4*c, 0,0,1))
+        assert_equal(tuple(Affine.rotation(0, (-3, 2))), 
+            tuple(Affine.identity()))
+
+    @raises(TypeError)
+    def test_rotation_contructor_wrong_arg_types(self):
+        Affine.rotation(1,1)
+
+    def test_determinant(self):
+        assert_equal(Affine.identity().determinant, 1)
+        assert_equal(Affine.scale(2).determinant, 4)
+        assert_equal(Affine.scale(0).determinant, 0)
+        assert_equal(Affine.scale(5,1).determinant, 5)
+        assert_equal(Affine.scale(-1,1).determinant, -1)
+        assert_equal(Affine.scale(-1,0).determinant, 0)
+        assert_almost_equal(Affine.rotation(77).determinant, 1)
+        assert_almost_equal(Affine.translation(32, -47).determinant, 1)
+
+    def test_is_rectilinear(self):
+        assert Affine.identity().is_rectilinear
+        assert Affine.scale(2.5, 6.1).is_rectilinear
+        assert Affine.translation(4, -1).is_rectilinear
+        assert Affine.rotation(90).is_rectilinear
+        assert not Affine.shear(4, -1).is_rectilinear
+        assert not Affine.rotation(-26).is_rectilinear
+
+    def test_is_conformal(self):
+        assert Affine.identity().is_conformal
+        assert Affine.scale(2.5, 6.1).is_conformal
+        assert Affine.translation(4, -1).is_conformal
+        assert Affine.rotation(90).is_conformal
+        assert Affine.rotation(-26).is_conformal
+        assert not Affine.shear(4, -1).is_conformal
+
+    def test_is_orthonormal(self):
+        assert Affine.identity().is_orthonormal
+        assert Affine.translation(4, -1).is_orthonormal
+        assert Affine.rotation(90).is_orthonormal
+        assert Affine.rotation(-26).is_orthonormal
+        assert not Affine.scale(2.5, 6.1).is_orthonormal
+        assert not Affine.scale(.5, 2).is_orthonormal
+        assert not Affine.shear(4, -1).is_orthonormal
+
+    def test_is_degenerate(self):
+        from affine import EPSILON
+        assert not Affine.identity().is_degenerate
+        assert not Affine.translation(2, -1).is_degenerate
+        assert not Affine.shear(0, -22.5).is_degenerate
+        assert not Affine.rotation(88.7).is_degenerate
+        assert not Affine.scale(0.5).is_degenerate
+        assert Affine.scale(0).is_degenerate
+        assert Affine.scale(-10, 0).is_degenerate
+        assert Affine.scale(0, 300).is_degenerate
+        assert Affine.scale(0).is_degenerate
+        assert Affine.scale(0).is_degenerate
+        assert Affine.scale(EPSILON).is_degenerate
+
+    def test_column_vectors(self):
+        import affine
+        a, b, c = Affine(2, 3, 4, 5, 6, 7).column_vectors
+        assert isinstance(a, tuple)
+        assert isinstance(b, tuple)
+        assert isinstance(c, tuple)
+        assert_equal(a, (2, 5))
+        assert_equal(b, (3, 6))
+        assert_equal(c, (4, 7))
+
+    def test_almost_equals(self):
+        from affine import EPSILON
+        assert EPSILON != 0, EPSILON
+        E = EPSILON * 0.5
+        t = Affine(1.0, E, 0, -E, 1.0+E, E)
+        assert t.almost_equals(Affine.identity())
+        assert Affine.identity().almost_equals(t)
+        assert t.almost_equals(t)
+        t = Affine(1.0, 0, 0, -EPSILON, 1.0, 0)
+        assert not t.almost_equals(Affine.identity())
+        assert not Affine.identity().almost_equals(t)
+        assert t.almost_equals(t)
+
+    def test_equality(self):
+        t1 = Affine(1, 2, 3, 4, 5, 6)
+        t2 = Affine(6, 5, 4, 3, 2, 1)
+        t3 = Affine(1, 2, 3, 4, 5, 6)
+        assert t1 == t3
+        assert not t1 == t2
+        assert t2 == t2
+        assert not t1 != t3
+        assert not t2 != t2
+        assert t1 != t2
+        assert not t1 == 1
+        assert t1 != 1
+
+    @raises(TypeError)
+    def test_gt(self):
+        Affine(1,2,3,4,5,6) > Affine(6,5,4,3,2,1)
+
+    @raises(TypeError)
+    def test_lt(self):
+        Affine(1,2,3,4,5,6) < Affine(6,5,4,3,2,1)
+
+    @raises(TypeError)
+    def test_add(self):
+        Affine(1,2,3,4,5,6) + Affine(6,5,4,3,2,1)
+        
+    @raises(TypeError)
+    def test_sub(self):
+        Affine(1,2,3,4,5,6) - Affine(6,5,4,3,2,1)
+
+    def test_mul_by_identity(self):
+        t = Affine(1,2,3,4,5,6)
+        assert_equal(tuple(t * Affine.identity()), tuple(t))
+
+    def test_mul_transform(self):
+        t = Affine.rotation(5) * Affine.rotation(29)
+        assert isinstance(t, Affine)
+        seq_almost_equal(t, Affine.rotation(34))
+        t = Affine.scale(3, 5) * Affine.scale(2)
+        seq_almost_equal(t, Affine.scale(6, 10))
+
+    def test_itransform(self):
+        pts = [(4,1), (-1,0), (3,2)]
+        r = Affine.scale(-2).itransform(pts)
+        assert r is None, r
+        assert_equal(pts, [(-8, -2), (2,0), (-6,-4)])
+
+    @raises(TypeError)
+    def test_mul_wrong_type(self):
+        Affine(1,2,3,4,5,6) * None
+
+    @raises(TypeError)
+    def test_mul_sequence_wrong_member_types(self):
+        class NotPtSeq:
+            @classmethod
+            def from_points(cls, points):
+                list(points)
+            def __iter__(self):
+                yield 0
+        Affine(1,2,3,4,5,6) * NotPtSeq()
+        
+    def test_imul_transform(self):
+        t = Affine.translation(3, 5)
+        t *= Affine.translation(-2, 3.5)
+        assert isinstance(t, Affine)
+        seq_almost_equal(t, Affine.translation(1, 8.5))
+
+    def test_inverse(self):
+        seq_almost_equal(~Affine.identity(), Affine.identity())
+        seq_almost_equal(
+            ~Affine.translation(2, -3), Affine.translation(-2, 3))
+        seq_almost_equal(
+            ~Affine.rotation(-33.3), Affine.rotation(33.3))
+        t = Affine(1,2,3,4,5,6)
+        seq_almost_equal(~t * t,  Affine.identity())
+    
+    def test_cant_invert_degenerate(self):
+        from affine import TransformNotInvertibleError
+        t = Affine.scale(0)
+        self.assertRaises(TransformNotInvertibleError, lambda: ~t)
+
+    def test_set_epsilon(self):
+        import affine
+
+        old_epsilon = affine.EPSILON
+        old_epsilon2 = affine.EPSILON2
+
+        try:
+            affine.set_epsilon(123)
+            assert_equal(123, affine.EPSILON)
+            assert_equal(123*123, affine.EPSILON2)
+        finally:
+            affine.set_epsilon(old_epsilon)
+            
+
+        
+
+
+def test_gdal():
+    t = Affine.from_gdal(-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
+    assert t.c == t.xoff == -237481.5
+    assert t.a == 425.0
+    assert t.b == 0.0
+    assert t.f == t.yoff ==  237536.4
+    assert t.d == 0.0
+    assert t.e == -425.0
+    assert tuple(t) == (425.0, 0.0, -237481.5, 0.0, -425.0, 237536.4, 0, 0, 1.0)
+    assert t.to_gdal() == (-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vim: ai ts=4 sts=4 et sw=4 tw=78
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..4915923
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,35 @@
+from setuptools import setup, find_packages
+import sys, os
+
+# Parse the version from the fiona module.
+with open('affine/__init__.py') as f:
+    for line in f:
+        if line.find("__version__") >= 0:
+            version = line.split("=")[1].strip()
+            version = version.strip('"')
+            version = version.strip("'")
+            continue
+
+readme = open('README.rst').read()
+
+setup(name='affine',
+      version=version,
+      description="Matrices describing affine transformation of the plane",
+      long_description=readme,
+      classifiers=[],
+      keywords='',
+      author='Sean Gillies',
+      author_email='sean at mapbox.com',
+      url='https://github.com/sgillies/affine',
+      license='BSD',
+      package_dir={'': '.'},
+      packages=['affine'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-affine.git



More information about the Pkg-grass-devel mailing list