[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