[python-affine] 01/04: Imported Upstream version 2.0.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat May 21 14:07:40 UTC 2016


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

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

commit 8383026748da7a8402a240fba23a2b0953eb60b5
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat May 21 15:49:26 2016 +0200

    Imported Upstream version 2.0.0
---
 .travis.yml                    |  12 ++-
 AUTHORS.txt                    |   4 +-
 CHANGES.txt                    |  21 ++++++
 affine/__init__.py             | 168 ++++++++++++++++++++++-------------------
 affine/tests/test_rotation.py  |  53 +++++++++++++
 affine/tests/test_transform.py | 144 +++++++++++++++++++++++------------
 tox.ini                        |   2 +-
 7 files changed, 272 insertions(+), 132 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a8481aa..e5c6898 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,24 @@
+sudo: false
 language: python
 python:
   - "2.6"
   - "2.7"
   - "3.3"
   - "3.4"
+  - "3.5"
 install:
   - "pip install pytest pytest-cov nose"
   - "pip install coveralls"
   - "pip install -e ."
 script: 
-  - py.test --cov affine --cov-report term-missing
+  - python -m pytest affine/tests --cov affine --cov-report term-missing
 after_success:
   - coveralls
+deploy:
+  on:
+    tags: true
+  provider: pypi
+  distributions: "sdist bdist_wheel"
+  user: seang
+  password:
+    secure: "FlfcoO8a26yNTtxJ9BmzLHCM18r0sDSJvXWNQ2LN/j0qTgKK9XeD9huMeE3s/OptD6DlY36bNgxSW87rsIx/nboyn+eJQA5tvhMP48lhLDZ6rDkRT1jsizxDYskQf4V//nsOoycuV6nTKDnneITGR1tn4AqDRbiwHSKho4GuBaM="
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 34c7ba2..02e814b 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -1,8 +1,8 @@
 Authors
 =======
 
-- Sean Gillies <sean.gillies at gmail.com>
-- Sean Gillies <sean at mapbox.com> (same as ^^)
+- Sean Gillies <sean at mapbox.com>
 - Steven Ring <smr at southsky.com.au>
 - Mike Toews <mwtoews at gmail.com>
 - Kevin Wurster <wursterk at gmail.com>
+- Todd Small <todd_small at icloud.com>
diff --git a/CHANGES.txt b/CHANGES.txt
index f65dafd..8317435 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,27 @@
 CHANGES
 =======
 
+2.0.0 (2016-05-20)
+------------------
+- Final release.
+
+2.0b2 (2016-05-16)
+------------------
+- Bug fix: restore ``Affine __rmul__`` even though it permits dubious 
+  vector * matrix expressions (#27).
+
+2.0b1 (2016-05-16)
+------------------
+- Breaking change: precision used in properties like ``is_conformal`` is no
+  longer a global module attribute, but an Affine class or instance attribute
+  (#19, #20).
+- Breaking change: ``is_degenerate`` property is now exact and not subject to
+  a level of precision (#23).
+- Breaking change: we have reversed our sense of rotation, a positive angle
+  now rotates a point counter-clockwise about the pivot point (#25).
+- Bug fix: a bug in matrix-vector multiplication had been reversing the
+  direction of rotation and is now fixed (#25).
+
 1.3.0 (2016-04-08)
 ------------------
 - is_degenerate predicate is precise, not approximate (#22).
diff --git a/affine/__init__.py b/affine/__init__.py
index 2911ad9..68659e9 100644
--- a/affine/__init__.py
+++ b/affine/__init__.py
@@ -47,26 +47,9 @@ import math
 
 __all__ = ['Affine']
 __author__ = "Sean Gillies"
-__version__ = "1.3.0"
+__version__ = "2.0.0"
 
 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):
@@ -89,7 +72,7 @@ else:  # pragma: no cover
     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__))
+                        % (type(a).__name__, type(b).__name__))
 
 
 def cached_property(func):
@@ -111,9 +94,10 @@ def cached_property(func):
 
 
 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
+    """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:
@@ -128,20 +112,29 @@ def cos_sin_deg(deg):
 
 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.
+    """Two dimensional affine transform for 2D linear mapping.
+
+    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)``.
+    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)``.
+
+    N.B.: multiplication of a transform and an (x, y) vector *always*
+    returns the column vector that is the matrix multiplication product
+    of the transform and (x, y) as a column vector, no matter which is
+    on the left or right side. This is obviously not the case for 
+    matrices and vectors in general, but provides a convenience for
+    users of this class.
 
     :param members: 6 floats for the first two matrix rows.
     :type members: float
     """
+    precision = EPSILON
 
     def __new__(self, *members):
         if len(members) == 6:
@@ -180,7 +173,8 @@ class Affine(
         :type yoff: float
         :rtype: Affine
         """
-        return tuple.__new__(cls,
+        return tuple.__new__(
+            cls,
             (1.0, 0.0, xoff,
              0.0, 1.0, yoff,
              0.0, 0.0, 1.0))
@@ -199,7 +193,8 @@ class Affine(
             sx = sy = float(scaling[0])
         else:
             sx, sy = scaling
-        return tuple.__new__(cls,
+        return tuple.__new__(
+            cls,
             (sx, 0.0, 0.0,
              0.0, sy, 0.0,
              0.0, 0.0, 1.0))
@@ -216,34 +211,40 @@ class Affine(
         """
         mx = math.tan(math.radians(x_angle))
         my = math.tan(math.radians(y_angle))
-        return tuple.__new__(cls,
+        return tuple.__new__(
+            cls,
             (1.0, mx, 0.0,
              my, 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.
+        """Create a rotation transform at the specified angle.
+
+        A pivot point other than the coordinate system origin may be
+        optionally specified.
 
-        :param angle: Rotation angle in degrees
+        :param angle: Rotation angle in degrees, counter-clockwise
+            about the pivot point.
         :type angle: float
-        :param pivot: Point to rotate about, if omitted the
-            rotation is about the origin.
+        :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,
+            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,
+            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):
@@ -276,57 +277,64 @@ class Affine(
 
     @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.
+        """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
+    @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)
+        return self is identity or self.almost_equals(identity, self.precision)
 
-    @cached_property
+    @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.
+        """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))
+        return ((abs(a) < self.precision and abs(e) < self.precision)
+                or (abs(d) < self.precision and abs(b) < self.precision))
 
-    @cached_property
+    @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.
+        """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
+        return abs(a * b + d * e) < self.precision
 
-    @cached_property
+    @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.
+        """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)
+                and abs(1.0 - (a * a + d * d)) < self.precision
+                and abs(1.0 - (b * b + e * e)) < self.precision)
 
     @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.
+        """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 self.determinant == 0.0
 
@@ -336,16 +344,16 @@ class Affine(
         a, b, c, d, e, f, _, _, _ = self
         return (a, d), (b, e), (c, f)
 
-    def almost_equals(self, other):
+    def almost_equals(self, other, precision=EPSILON):
         """Compare transforms for approximate equality.
 
         :param other: Transform being compared.
         :type other: Affine
         :return: True if absolute difference between each element
-            of each respective transform matrix < ``EPSILON``.
+            of each respective transform matrix < ``self.precision``.
         """
         for i in (0, 1, 2, 3, 4, 5):
-            if abs(self[i] - other[i]) >= EPSILON:
+            if abs(self[i] - other[i]) >= precision:
                 return False
         return True
 
@@ -376,7 +384,8 @@ class Affine(
         sa, sb, sc, sd, se, sf, _, _, _ = self
         if isinstance(other, Affine):
             oa, ob, oc, od, oe, of, _, _, _ = other
-            return tuple.__new__(Affine,
+            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))
@@ -385,12 +394,12 @@ class Affine(
                 vx, vy = other
             except Exception:
                 return NotImplemented
-            return (vx * sa + vy * sd + sc, vx * sb + vy * se + sf)
+            return (vx * sa + vy * sb + sc, vx * sd + 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
+        # return the wrong answer in that case.
         assert not isinstance(other, Affine)
         return self.__mul__(other)
 
@@ -427,7 +436,8 @@ class Affine(
         rb = -sb * idet
         rd = -sd * idet
         re = sa * idet
-        return tuple.__new__(Affine,
+        return tuple.__new__(
+            Affine,
             (ra, rb, -sc * ra - sf * rb,
              rd, re, -sc * rd - sf * re,
              0.0, 0.0, 1.0))
diff --git a/affine/tests/test_rotation.py b/affine/tests/test_rotation.py
new file mode 100644
index 0000000..fe783e8
--- /dev/null
+++ b/affine/tests/test_rotation.py
@@ -0,0 +1,53 @@
+import math
+
+from affine import Affine
+
+
+def test_rotation_angle():
+    """A positive angle rotates a vector counter clockwise
+
+    (1.0, 0.0):
+
+        |
+        |
+        |
+        |
+        0---------*
+
+    Affine.rotation(45.0) * (1.0, 0.0) == (0.707..., 0.707...)
+
+        |
+        |      *
+        |
+        |
+        0----------
+    """
+    x, y = Affine.rotation(45.0) * (1.0, 0.0)
+    assert round(x, 14) == round(math.sqrt(2.0) / 2.0, 14)
+    assert round(y, 14) == round(math.sqrt(2.0) / 2.0, 14)
+
+
+def test_rotation_matrix():
+    """A rotation matrix has expected elements
+
+    | cos(a) -sin(a) |
+    | sin(a)  cos(a) |
+
+    """
+    rot = Affine.rotation(90.0)
+    assert round(rot.a, 15) == round(math.cos(math.pi/2.0), 15)
+    assert round(rot.b, 15) == round(-math.sin(math.pi/2.0), 15)
+    assert rot.c == 0.0
+    assert round(rot.d, 15) == round(math.sin(math.pi/2.0), 15)
+    assert round(rot.e, 15) == round(math.cos(math.pi/2.0), 15)
+    assert rot.f == 0.0
+
+
+def test_rotation_matrix_pivot():
+    """A rotation matrix with pivot has expected elements"""
+    rot = Affine.rotation(90.0, pivot=(1.0, 1.0))
+    exp = (Affine.translation(1.0, 1.0)
+           * Affine.rotation(90.0)
+           * Affine.translation(-1.0, -1.0))
+    for r, e in zip(rot, exp):
+        assert round(r, 15) == round(e, 15)
diff --git a/affine/tests/test_transform.py b/affine/tests/test_transform.py
index 7afcccb..0a21976 100644
--- a/affine/tests/test_transform.py
+++ b/affine/tests/test_transform.py
@@ -35,7 +35,7 @@ import unittest
 from textwrap import dedent
 from nose.tools import assert_equal, assert_almost_equal, raises
 
-from affine import Affine
+from affine import Affine, EPSILON
 
 
 def seq_almost_equal(t1, t2, error=0.00001):
@@ -123,9 +123,9 @@ class PyAffineTestCase(unittest.TestCase):
         assert isinstance(trans, Affine)
         assert_equal(
             tuple(trans),
-            (1, 0,  2,
+            (1, 0, 2,
              0, 1, -5,
-             0, 0,  1))
+             0, 0, 1))
 
     def test_scale_constructor(self):
         scale = Affine.scale(5)
@@ -139,8 +139,8 @@ class PyAffineTestCase(unittest.TestCase):
         assert_equal(
             tuple(scale),
             (-1, 0, 0,
-              0, 2, 0,
-              0, 0, 1))
+             0, 2, 0,
+             0, 0, 1))
         assert_equal(tuple(Affine.scale(1)), tuple(Affine.identity()))
 
     def test_shear_constructor(self):
@@ -158,8 +158,8 @@ class PyAffineTestCase(unittest.TestCase):
         seq_almost_equal(
             tuple(shear),
             (1, mx, 0,
-            my,  1, 0,
-             0,  0, 1))
+             my, 1, 0,
+             0, 0, 1))
         shear = Affine.shear(y_angle=45)
         seq_almost_equal(
             tuple(shear),
@@ -174,16 +174,16 @@ class PyAffineTestCase(unittest.TestCase):
         s, c = math.sin(r), math.cos(r)
         assert_equal(
             tuple(rot),
-            (c, s, 0,
-            -s, c, 0,
+            (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,
+            (c, -s, 0,
+             s, c, 0,
              0, 0, 1))
         assert_equal(tuple(Affine.rotation(0)), tuple(Affine.identity()))
 
@@ -195,29 +195,29 @@ class PyAffineTestCase(unittest.TestCase):
              0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(90)),
-            (0, 1, 0,
-            -1, 0, 0,
+            (0, -1, 0,
+             1, 0, 0,
              0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(180)),
-            (-1,  0, 0,
-              0, -1, 0,
-              0,  0, 1))
+            (-1, 0, 0,
+             0, -1, 0,
+             0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(-180)),
-            (-1,  0, 0,
-              0, -1, 0,
-              0,  0, 1))
+            (-1, 0, 0,
+             0, -1, 0,
+             0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(270)),
-            (0, -1, 0,
-             1,  0, 0,
-             0,  0, 1))
+            (0, 1, 0,
+             -1, 0, 0,
+             0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(-90)),
-            (0, -1, 0,
-             1,  0, 0,
-             0,  0, 1))
+            (0, 1, 0,
+             -1, 0, 0,
+             0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(360)),
             (1, 0, 0,
@@ -225,25 +225,25 @@ class PyAffineTestCase(unittest.TestCase):
              0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(450)),
-            (0, 1, 0,
-            -1, 0, 0,
+            (0, -1, 0,
+             1, 0, 0,
              0, 0, 1))
         assert_equal(
             tuple(Affine.rotation(-450)),
-            (0, -1, 0,
-             1,  0, 0,
-             0,  0, 1))
+            (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))))
+                     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,
+            (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()))
@@ -288,7 +288,6 @@ class PyAffineTestCase(unittest.TestCase):
         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
@@ -310,8 +309,7 @@ class PyAffineTestCase(unittest.TestCase):
         assert_equal(c, (4, 7))
 
     def test_almost_equals(self):
-        from affine import EPSILON
-        assert EPSILON != 0, EPSILON
+        EPSILON = 1e-5
         E = EPSILON * 0.5
         t = Affine(1.0, E, 0, -E, 1.0 + E, E)
         assert t.almost_equals(Affine.identity())
@@ -322,6 +320,18 @@ class PyAffineTestCase(unittest.TestCase):
         assert not Affine.identity().almost_equals(t)
         assert t.almost_equals(t)
 
+    def test_almost_equals_2(self):
+        EPSILON = 1e-10
+        E = EPSILON * 0.5
+        t = Affine(1.0, E, 0, -E, 1.0 + E, E)
+        assert t.almost_equals(Affine.identity(), precision=EPSILON)
+        assert Affine.identity().almost_equals(t, precision=EPSILON)
+        assert t.almost_equals(t, precision=EPSILON)
+        t = Affine(1.0, 0, 0, -EPSILON, 1.0, 0)
+        assert not t.almost_equals(Affine.identity(), precision=EPSILON)
+        assert not Affine.identity().almost_equals(t, precision=EPSILON)
+        assert t.almost_equals(t, precision=EPSILON)
+
     def test_equality(self):
         t1 = Affine(1, 2, 3, 4, 5, 6)
         t2 = Affine(6, 5, 4, 3, 2, 1)
@@ -397,25 +407,13 @@ class PyAffineTestCase(unittest.TestCase):
         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())
+        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
-
-        try:
-            affine.set_epsilon(123)
-            assert_equal(123, affine.EPSILON)
-            assert_equal(123 * 123, affine.EPSILON2)
-        finally:
-            affine.set_epsilon(old_epsilon)
-
     @raises(TypeError)
     def test_bad_type_world(self):
         from affine import loadsw
@@ -459,6 +457,9 @@ class PyAffineTestCase(unittest.TestCase):
         self.assertTrue(a1.almost_equals(a2))
 
 
+# We're using pytest for tests added after 1.0 and don't need unittest
+# test case classes.
+
 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
@@ -471,6 +472,51 @@ def test_gdal():
     assert t.to_gdal() == (-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
 
 
+def test_imul_number():
+    t = Affine(1, 2, 3, 4, 5, 6)
+    try:
+        t *= 2.0
+    except TypeError:
+        assert True
+
+
+def test_mul_tuple():
+    t = Affine(1, 2, 3, 4, 5, 6)
+    t * (2.0, 2.0)
+
+
+def test_rmul_tuple():
+    t = Affine(1, 2, 3, 4, 5, 6)
+    (2.0, 2.0) * t
+
+
+def test_transform_precision():
+    t = Affine.rotation(45.0)
+    assert t.precision == EPSILON
+    t.precision = 1e-10
+    assert t.precision == 1e-10
+    assert Affine.rotation(0.0).precision == EPSILON
+
+
+def test_associative():
+    point = (12, 5)
+    trans = Affine.translation(-10., -5.)
+    rot90 = Affine.rotation(90.)
+    result1 = rot90 * (trans * point)
+    result2 = (rot90 * trans) * point
+    seq_almost_equal(result1, (0., 2.))
+    seq_almost_equal(result1, result2)
+
+
+def test_roundtrip():
+    point = (12, 5)
+    trans = Affine.translation(3, 4)
+    rot37 = Affine.rotation(37.)
+    point_prime = (trans * rot37) * point
+    roundtrip_point = ~(trans * rot37) * point_prime
+    seq_almost_equal(point, roundtrip_point)
+
+
 if __name__ == '__main__':
     unittest.main()
 
diff --git a/tox.ini b/tox.ini
index df8aec2..407da79 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,4 +9,4 @@ deps =
     pytest-cov
     responses
 commands = 
-    python -m pytest --cov affine --cov-report term-missing
+    python -m pytest affine/tests --cov affine --cov-report term-missing

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