[Git][debian-gis-team/python-affine][upstream] New upstream version 2.4.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Fri Jan 20 05:39:13 GMT 2023
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-affine
Commits:
2c3486d7 by Bas Couwenberg at 2023-01-20T06:21:14+01:00
New upstream version 2.4.0
- - - - -
16 changed files:
- − .coveragerc
- + .github/codecov.yml
- + .github/workflows/ci.yml
- .gitignore
- − .travis.yml
- CHANGES.txt
- − MANIFEST.in
- README.rst
- affine/__init__.py
- affine/tests/test_rotation.py
- + affine/tests/test_serialize.py
- affine/tests/test_transform.py
- + pyproject.toml
- setup.cfg
- − setup.py
- tox.ini
Changes:
=====================================
.coveragerc deleted
=====================================
@@ -1,2 +0,0 @@
-[run]
-omit = affine/tests/*
=====================================
.github/codecov.yml
=====================================
@@ -0,0 +1,8 @@
+comment: off
+
+coverage:
+ status:
+ project:
+ default:
+ target: auto
+ threshold: 5
=====================================
.github/workflows/ci.yml
=====================================
@@ -0,0 +1,82 @@
+name: CI
+
+# On every pull request, but only on push to master
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - '*'
+ pull_request:
+env:
+ LATEST_PY_VERSION: '3.11'
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
+
+ steps:
+ - uses: actions/checkout at v3
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python at v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install affine
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install .["test"]
+
+ - name: Run tests
+ run: |
+ python -m pytest --cov=affine --cov-report=term-missing --cov-report=xml
+
+ - name: Upload Coverage Results
+ if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
+ uses: codecov/codecov-action at v3
+ with:
+ files: ./coverage.xml
+ flags: unittests
+ fail_ci_if_error: false
+
+ lint:
+ needs: tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout at v3
+ - name: Set up Python
+ uses: actions/setup-python at v4
+ with:
+ python-version: ${{ env.LATEST_PY_VERSION }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install flake8 pydocstyle
+
+ - name: Run lint
+ run: |
+ flake8 --ignore=E501,W503
+ python -m pydocstyle affine
+
+ typing:
+ needs: tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout at v3
+ - name: Set up Python
+ uses: actions/setup-python at v4
+ with:
+ python-version: ${{ env.LATEST_PY_VERSION }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install mypy pytest
+
+ - name: Run typing test
+ run: |
+ mypy affine
=====================================
.gitignore
=====================================
@@ -57,3 +57,5 @@ docs/_build/
__pycache__
*.swp
+
+.coverage.*
=====================================
.travis.yml deleted
=====================================
@@ -1,23 +0,0 @@
-dist: xenial
-language: python
-cache: pip
-python:
- - 2.7
- - 3.6
- - 3.7
- - pypy3
-install:
- - pip install flake8 pydocstyle
- - pip install .[test]
-script:
- - flake8
- - python -m pydocstyle affine
- - python -m pytest --cov affine --cov-report term-missing
-after_success:
- - coveralls
-deploy:
- on:
- tags: true
- provider: pypi
- distributions: "sdist bdist_wheel"
- user: __token__
=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,22 @@
CHANGES
=======
+2.4.0 (2023-01-19)
+------------------
+
+- Package is marked as Python 3 only, two instances of "%" string formatting
+ are replaced by f-strings (#96).
+
+2.4b1 (2023-01-18)
+------------------
+
+- Elimination of Python 2/3 compatibility code in __gt__ (#94).
+- Addition of optional keyword arguments for __new__, solving an issue with
+ Dask (#92).
+- Addition of some type hints for float arguments and return types (#87)..
+- Python version support is now 3.7-3.11 (#82).
+- Faster __new__ and from_gdal methods (#78).
+
2.3.1 (2022-03-24)
------------------
=====================================
MANIFEST.in deleted
=====================================
@@ -1,4 +0,0 @@
-exclude *.rst *.txt *.py
-include CHANGES.txt AUTHORS.txt LICENSE.txt VERSION.txt README.rst setup.py setup.cfg
-recursive-include affine/tests *.py
-exclude MANIFEST.in
=====================================
README.rst
=====================================
@@ -1,13 +1,13 @@
Affine
======
-Matrices describing affine transformation of the plane.
+Matrices describing 2D affine transformation of the plane.
-.. image:: https://travis-ci.org/sgillies/affine.svg?branch=master
- :target: https://travis-ci.org/sgillies/affine
+.. image:: https://github.com/rasterio/affine/actions/workflows/ci.yml/badge.svg?branch=main
+ :target: https://github.com/rasterio/affine/actions/workflows/ci.yml
-.. image:: https://coveralls.io/repos/sgillies/affine/badge.svg
- :target: https://coveralls.io/r/sgillies/affine
+.. image:: https://codecov.io/gh/rasterio/affine/branch/main/graph/badge.svg
+ :target: https://codecov.io/gh/rasterio/affine
The Affine package is derived from Casey Duncan's Planar package. Please see
the copyright statement in `affine/__init__.py <affine/__init__.py>`__.
=====================================
affine/__init__.py
=====================================
@@ -32,18 +32,16 @@ copyright statement below.
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#############################################################################
-from __future__ import division
-
from collections import namedtuple
import math
import warnings
-__all__ = ['Affine']
+__all__ = ["Affine"]
__author__ = "Sean Gillies"
-__version__ = "2.3.1"
+__version__ = "2.4.0"
-EPSILON = 1e-5
+EPSILON: float = 1e-5
class AffineError(Exception):
@@ -58,26 +56,6 @@ class UndefinedRotationError(AffineError):
"""The rotation angle could not be computed for this transform"""
-# 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
@@ -92,11 +70,12 @@ def cached_property(func):
except KeyError:
self.__dict__[name] = value = func(self)
return value
+
getter.func_name = name
return property(getter, doc=doc)
-def cos_sin_deg(deg):
+def cos_sin_deg(deg: float):
"""Return the cosine and sin for the given angle in degrees.
With special-case handling of multiples of 90 for perfect right
@@ -113,8 +92,7 @@ def cos_sin_deg(deg):
return math.cos(rad), math.sin(rad)
-class Affine(
- namedtuple('Affine', ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'))):
+class Affine(namedtuple("Affine", ("a", "b", "c", "d", "e", "f", "g", "h", "i"))):
"""Two dimensional affine transform for 2D linear mapping.
Parameters
@@ -132,7 +110,7 @@ class Affine(
Attributes
----------
a, b, c, d, e, f, g, h, i : float
- The coefficients of the 3x3 augumented affine transformation
+ The coefficients of the 3x3 augmented affine transformation
matrix
| x' | | a b c | | x |
@@ -161,9 +139,21 @@ class Affine(
users of this class.
"""
+
precision = EPSILON
- def __new__(cls, a, b, c, d, e, f):
+ def __new__(
+ cls,
+ a: float,
+ b: float,
+ c: float,
+ d: float,
+ e: float,
+ f: float,
+ g: float = 0.0,
+ h: float = 0.0,
+ i: float = 1.0,
+ ):
"""Create a new object
Parameters
@@ -171,19 +161,29 @@ class Affine(
a, b, c, d, e, f : float
Elements of an augmented affine transformation matrix.
"""
- mat3x3 = [x * 1.0 for x in [a, b, c, d, e, f]] + [0.0, 0.0, 1.0]
- return tuple.__new__(cls, mat3x3)
+ return tuple.__new__(
+ cls,
+ (
+ a * 1.0,
+ b * 1.0,
+ c * 1.0,
+ d * 1.0,
+ e * 1.0,
+ f * 1.0,
+ g * 1.0,
+ h * 1.0,
+ i * 1.0,
+ ),
+ )
@classmethod
- def from_gdal(cls, c, a, b, f, d, e):
+ def from_gdal(cls, c: float, a: float, b: float, f: float, d: float, e: float):
"""Use same coefficient order as GDAL's GetGeoTransform().
:param c, a, b, f, d, e: 6 floats ordered by GDAL.
:rtype: Affine
"""
- members = [a, b, c, d, e, f]
- mat3x3 = [x * 1.0 for x in members] + [0.0, 0.0, 1.0]
- return tuple.__new__(cls, mat3x3)
+ return cls.__new__(cls, a, b, c, d, e, f)
@classmethod
def identity(cls):
@@ -194,7 +194,7 @@ class Affine(
return identity
@classmethod
- def translation(cls, xoff, yoff):
+ def translation(cls, xoff: float, yoff: float):
"""Create a translation transform from an offset vector.
:param xoff: Translation x offset.
@@ -203,11 +203,7 @@ class Affine(
: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))
+ 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):
@@ -223,14 +219,10 @@ class Affine(
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))
+ 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):
+ def shear(cls, x_angle: float = 0, y_angle: float = 0):
"""Create a shear transform along one or both axes.
:param x_angle: Shear angle in degrees parallel to the x-axis.
@@ -241,14 +233,10 @@ class Affine(
"""
mx = math.tan(math.radians(x_angle))
my = math.tan(math.radians(y_angle))
- return tuple.__new__(
- cls,
- (1.0, mx, 0.0,
- my, 1.0, 0.0,
- 0.0, 0.0, 1.0))
+ 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):
+ def rotation(cls, angle: float, pivot=None):
"""Create a rotation transform at the specified angle.
A pivot point other than the coordinate system origin may be
@@ -264,18 +252,23 @@ class 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))
+ 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))
+ (
+ ca,
+ -sa,
+ px - px * ca + py * sa,
+ sa,
+ ca,
+ py - px * sa - py * ca,
+ 0.0,
+ 0.0,
+ 1.0,
+ ),
+ )
@classmethod
def permutation(cls, *scaling):
@@ -287,22 +280,17 @@ class Affine(
:rtype: Affine
"""
- return tuple.__new__(
- cls,
- (0.0, 1.0, 0.0,
- 1.0, 0.0, 0.0,
- 0.0, 0.0, 1.0))
+ return tuple.__new__(cls, (0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0))
- def __str__(self):
+ def __str__(self) -> str:
"""Concise string representation."""
- return ("|% .2f,% .2f,% .2f|\n"
- "|% .2f,% .2f,% .2f|\n"
- "|% .2f,% .2f,% .2f|") % self
+ return (
+ "|% .2f,% .2f,% .2f|\n" "|% .2f,% .2f,% .2f|\n" "|% .2f,% .2f,% .2f|"
+ ) % self
- def __repr__(self):
+ def __repr__(self) -> str:
"""Precise string representation."""
- return ("Affine(%r, %r, %r,\n"
- " %r, %r, %r)") % self[:6]
+ return ("Affine(%r, %r, %r,\n" " %r, %r, %r)") % self[:6]
def to_gdal(self):
"""Return same coefficient order as GDAL's SetGeoTransform().
@@ -322,17 +310,17 @@ class Affine(
return (self.a, self.b, self.d, self.e, self.xoff, self.yoff)
@property
- def xoff(self):
+ def xoff(self) -> float:
"""Alias for 'c'"""
return self.c
@property
- def yoff(self):
+ def yoff(self) -> float:
"""Alias for 'f'"""
return self.f
@cached_property
- def determinant(self):
+ def determinant(self) -> float:
"""The determinant of the transform matrix.
This value is equal to the area scaling factor when the
@@ -353,10 +341,10 @@ class Affine(
# The singular values are the square root of the eigenvalues
# of the matrix times its transpose, M M*
# Computing trace and determinant of M M*
- trace = a**2 + b**2 + d**2 + e**2
- det = (a * e - b * d)**2
+ trace = a ** 2 + b ** 2 + d ** 2 + e ** 2
+ det = (a * e - b * d) ** 2
- delta = trace**2 / 4 - det
+ delta = trace ** 2 / 4 - det
if delta < 1e-12:
delta = 0
@@ -365,7 +353,7 @@ class Affine(
return l1, l2
@property
- def eccentricity(self):
+ def eccentricity(self) -> float:
"""The eccentricity of the affine transformation.
This value represents the eccentricity of an ellipse under
@@ -377,14 +365,15 @@ class Affine(
return math.sqrt(l1 ** 2 - l2 ** 2) / l1
@property
- def rotation_angle(self):
+ def rotation_angle(self) -> float:
"""The rotation angle in degrees of the affine transformation.
This is the rotation angle in degrees of the affine transformation,
assuming it is in the form M = R S, where R is a rotation and S is a
scaling.
- Raises UndefinedRotationError for improper and degenerate transformations.
+ Raises UndefinedRotationError for improper and degenerate
+ transformations.
"""
a, b, _, c, d, _, _, _, _ = self
if self.is_proper or self.is_degenerate:
@@ -395,25 +384,26 @@ class Affine(
raise UndefinedRotationError
@property
- def is_identity(self):
+ def is_identity(self) -> bool:
"""True if this transform equals the identity matrix,
within rounding limits.
"""
return self is identity or self.almost_equals(identity, self.precision)
@property
- def is_rectilinear(self):
+ def is_rectilinear(self) -> bool:
"""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) < self.precision and abs(e) < self.precision) or
- (abs(d) < self.precision and abs(b) < self.precision))
+ return (abs(a) < self.precision and abs(e) < self.precision) or (
+ abs(d) < self.precision and abs(b) < self.precision
+ )
@property
- def is_conformal(self):
+ def is_conformal(self) -> bool:
"""True if the transform is conformal.
i.e., if angles between points are preserved after applying the
@@ -424,7 +414,7 @@ class Affine(
return abs(a * b + d * e) < self.precision
@property
- def is_orthonormal(self):
+ def is_orthonormal(self) -> bool:
"""True if the transform is orthonormal.
Which means that the transform represents a rigid motion, which
@@ -434,12 +424,14 @@ class Affine(
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)) < self.precision and
- abs(1.0 - (b * b + e * e)) < self.precision)
+ return (
+ self.is_conformal
+ 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):
+ def is_degenerate(self) -> bool:
"""True if this transform is degenerate.
Which means that it will collapse a shape to an effective area
@@ -448,7 +440,7 @@ class Affine(
return self.determinant == 0.0
@cached_property
- def is_proper(self):
+ def is_proper(self) -> bool:
"""True if this transform is proper.
Which means that it does not include reflection.
@@ -461,7 +453,7 @@ class Affine(
a, b, c, d, e, f, _, _, _ = self
return (a, d), (b, e), (c, f)
- def almost_equals(self, other, precision=EPSILON):
+ def almost_equals(self, other, precision: float = EPSILON) -> bool:
"""Compare transforms for approximate equality.
:param other: Transform being compared.
@@ -474,8 +466,8 @@ class Affine(
return False
return True
- def __gt__(self, other):
- return assert_unorderable(self, other)
+ def __gt__(self, other) -> bool:
+ return NotImplemented
__ge__ = __lt__ = __le__ = __gt__
@@ -505,9 +497,18 @@ class Affine(
oa, ob, oc, od, oe, of, _, _, _ = other
return tuple.__new__(
self.__class__,
- (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))
+ (
+ 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
@@ -528,8 +529,11 @@ class Affine(
just a guarantee, since we would potentially return the wrong
answer in that case.
"""
- warnings.warn("Right multiplication will be prohibited in version 3.0",
- DeprecationWarning, stacklevel=2)
+ warnings.warn(
+ "Right multiplication will be prohibited in version 3.0",
+ DeprecationWarning,
+ stacklevel=2,
+ )
assert not isinstance(other, Affine)
return self.__mul__(other)
@@ -539,7 +543,7 @@ class Affine(
else:
return NotImplemented
- def itransform(self, seq):
+ def itransform(self, seq) -> None:
"""Transform a sequence of points or vectors in place.
:param seq: Mutable sequence of :class:`~planar.Vec2` to be
@@ -558,8 +562,7 @@ class Affine(
is degenerate.
"""
if self.is_degenerate:
- raise TransformNotInvertibleError(
- "Cannot invert degenerate transform")
+ raise TransformNotInvertibleError("Cannot invert degenerate transform")
idet = 1.0 / self.determinant
sa, sb, sc, sd, se, sf, _, _, _ = self
ra = se * idet
@@ -568,9 +571,8 @@ class Affine(
re = sa * idet
return tuple.__new__(
self.__class__,
- (ra, rb, -sc * ra - sf * rb,
- rd, re, -sc * rd - sf * re,
- 0.0, 0.0, 1.0))
+ (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
@@ -592,32 +594,32 @@ identity = Affine(1, 0, 0, 0, 1, 0)
# Miscellaneous utilities
-def loadsw(s):
+def loadsw(s: str):
"""Returns Affine from the contents of a world file string.
- This method also translates the coefficients from from center- to
+ This method also translates the coefficients from center- to
corner-based coordinates.
:param s: str with 6 floats ordered in a world file.
:rtype: Affine
"""
- if not hasattr(s, 'split'):
+ if not hasattr(s, "split"):
raise TypeError("Cannot split input string")
coeffs = s.split()
if len(coeffs) != 6:
raise ValueError("Expected 6 coefficients, found %d" % len(coeffs))
- a, d, b, e, c, f = [float(x) for x in coeffs]
+ a, d, b, e, c, f = (float(x) for x in coeffs)
center = tuple.__new__(Affine, [a, b, c, d, e, f, 0.0, 0.0, 1.0])
return center * Affine.translation(-0.5, -0.5)
-def dumpsw(obj):
+def dumpsw(obj) -> str:
"""Return string for a world file.
- This method also translates the coefficients from from corner- to
+ This method also translates the coefficients from corner- to
center-based coordinates.
:rtype: str
"""
center = obj * Affine.translation(0.5, 0.5)
- return '\n'.join(repr(getattr(center, x)) for x in list('adbecf')) + '\n'
+ return "\n".join(repr(getattr(center, x)) for x in list("adbecf")) + "\n"
=====================================
affine/tests/test_rotation.py
=====================================
@@ -35,19 +35,21 @@ def test_rotation_matrix():
"""
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 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 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))
+ 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)
=====================================
affine/tests/test_serialize.py
=====================================
@@ -0,0 +1,8 @@
+"""Test unpacking and repacking affine matrices."""
+
+from affine import Affine
+
+
+def test_issue79():
+ """An affine matrix can be created from an unpacked matrix."""
+ Affine(*Affine.identity())
=====================================
affine/tests/test_transform.py
=====================================
@@ -28,8 +28,6 @@
"""Transform unit tests"""
-from __future__ import division
-
import math
import unittest
from textwrap import dedent
@@ -41,13 +39,12 @@ from affine import Affine, EPSILON
def seq_almost_equal(t1, t2, error=0.00001):
- assert len(t1) == len(t2), "%r != %r" % (t1, t2)
+ assert len(t1) == len(t2), f"{t1!r} != {t2!r}"
for m1, m2 in zip(t1, t2):
- assert abs(m1 - m2) <= error, "%r != %r" % (t1, t2)
+ assert abs(m1 - m2) <= error, f"{t1!r} != {t2!r}"
class PyAffineTestCase(unittest.TestCase):
-
def test_zero_args(self):
with pytest.raises(TypeError):
Affine()
@@ -97,168 +94,101 @@ class PyAffineTestCase(unittest.TestCase):
def test_getitem_wrong_type(self):
t = Affine(1, 2, 3, 4, 5, 6)
with pytest.raises(TypeError):
- t['foobar']
+ t["foobar"]
def test_str(self):
t = Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)
- assert str(t) == dedent("""\
+ assert str(t) == dedent(
+ """\
| 1.11, 2.22, 3.33|
|-4.44,-5.55, 6.67|
- | 0.00, 0.00, 1.00|""")
+ | 0.00, 0.00, 1.00|"""
+ )
def test_repr(self):
t = Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)
- assert repr(t) == dedent("""\
+ assert repr(t) == dedent(
+ """\
Affine(1.111, 2.222, 3.456,
- 4.444, 5.5, 6.25)""")
+ 4.444, 5.5, 6.25)"""
+ )
def test_identity_constructor(self):
ident = Affine.identity()
assert isinstance(ident, Affine)
- assert \
- tuple(ident) == \
- (1, 0, 0,
- 0, 1, 0,
- 0, 0, 1)
+ assert tuple(ident) == (1, 0, 0, 0, 1, 0, 0, 0, 1)
assert ident.is_identity
def test_permutation_constructor(self):
perm = Affine.permutation()
assert isinstance(perm, Affine)
- assert \
- tuple(perm) == \
- (0, 1, 0,
- 1, 0, 0,
- 0, 0, 1)
- assert (perm*perm).is_identity
+ assert tuple(perm) == (0, 1, 0, 1, 0, 0, 0, 0, 1)
+ assert (perm * perm).is_identity
def test_translation_constructor(self):
trans = Affine.translation(2, -5)
assert isinstance(trans, Affine)
- assert \
- tuple(trans) == \
- (1, 0, 2,
- 0, 1, -5,
- 0, 0, 1)
+ assert 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 \
- tuple(scale) == \
- (5, 0, 0,
- 0, 5, 0,
- 0, 0, 1)
+ assert tuple(scale) == (5, 0, 0, 0, 5, 0, 0, 0, 1)
scale = Affine.scale(-1, 2)
- assert \
- tuple(scale) == \
- (-1, 0, 0,
- 0, 2, 0,
- 0, 0, 1)
+ assert tuple(scale) == (-1, 0, 0, 0, 2, 0, 0, 0, 1)
assert tuple(Affine.scale(1)) == tuple(Affine.identity())
def test_shear_constructor(self):
shear = Affine.shear(30)
assert isinstance(shear, Affine)
mx = math.tan(math.radians(30))
- seq_almost_equal(
- tuple(shear),
- (1, mx, 0,
- 0, 1, 0,
- 0, 0, 1))
+ seq_almost_equal(tuple(shear), (1, mx, 0, 0, 1, 0, 0, 0, 1))
shear = Affine.shear(-15, 60)
mx = math.tan(math.radians(-15))
my = math.tan(math.radians(60))
- seq_almost_equal(
- tuple(shear),
- (1, mx, 0,
- my, 1, 0,
- 0, 0, 1))
+ seq_almost_equal(tuple(shear), (1, mx, 0, my, 1, 0, 0, 0, 1))
shear = Affine.shear(y_angle=45)
- seq_almost_equal(
- tuple(shear),
- (1, 0, 0,
- 1, 1, 0,
- 0, 0, 1))
+ seq_almost_equal(tuple(shear), (1, 0, 0, 1, 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 \
- tuple(rot) == \
- (c, -s, 0,
- s, c, 0,
- 0, 0, 1)
+ assert 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))
+ seq_almost_equal(tuple(rot), (c, -s, 0, s, c, 0, 0, 0, 1))
assert tuple(Affine.rotation(0)) == tuple(Affine.identity())
def test_rotation_constructor_quadrants(self):
- assert \
- tuple(Affine.rotation(0)) == \
- (1, 0, 0,
- 0, 1, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(90)) == \
- (0, -1, 0,
- 1, 0, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(180)) == \
- (-1, 0, 0,
- 0, -1, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(-180)) == \
- (-1, 0, 0,
- 0, -1, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(270)) == \
- (0, 1, 0,
- -1, 0, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(-90)) == \
- (0, 1, 0,
- -1, 0, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(360)) == \
- (1, 0, 0,
- 0, 1, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(450)) == \
- (0, -1, 0,
- 1, 0, 0,
- 0, 0, 1)
- assert \
- tuple(Affine.rotation(-450)) == \
- (0, 1, 0,
- -1, 0, 0,
- 0, 0, 1)
+ assert tuple(Affine.rotation(0)) == (1, 0, 0, 0, 1, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(90)) == (0, -1, 0, 1, 0, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(180)) == (-1, 0, 0, 0, -1, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(-180)) == (-1, 0, 0, 0, -1, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(270)) == (0, 1, 0, -1, 0, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(-90)) == (0, 1, 0, -1, 0, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(360)) == (1, 0, 0, 0, 1, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(450)) == (0, -1, 0, 1, 0, 0, 0, 0, 1)
+ assert tuple(Affine.rotation(-450)) == (0, 1, 0, -1, 0, 0, 0, 0, 1)
def test_rotation_constructor_with_pivot(self):
- assert tuple(Affine.rotation(60)) == \
- tuple(Affine.rotation(60, pivot=(0, 0)))
+ assert 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 \
- tuple(rot) == \
- (c, -s, 2 - 2 * c - 4 * s,
- s, c, -4 - 2 * s + 4 * c,
- 0, 0, 1)
+ assert tuple(rot) == (
+ c,
+ -s,
+ 2 - 2 * c - 4 * s,
+ s,
+ c,
+ -4 - 2 * s + 4 * c,
+ 0,
+ 0,
+ 1,
+ )
assert tuple(Affine.rotation(0, (-3, 2))) == tuple(Affine.identity())
def test_rotation_contructor_wrong_arg_types(self):
@@ -297,7 +227,7 @@ class PyAffineTestCase(unittest.TestCase):
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.scale(0.5, 2).is_orthonormal
assert not Affine.shear(4, -1).is_orthonormal
def test_is_degenerate(self):
@@ -393,7 +323,7 @@ class PyAffineTestCase(unittest.TestCase):
A = Affine.rotation(33)
pts = [(4, 1), (-1, 0), (3, 2)]
- pts_expect = [A*pt for pt in pts]
+ pts_expect = [A * pt for pt in pts]
r = A.itransform(pts)
assert r is None
assert pts == pts_expect
@@ -422,10 +352,8 @@ class PyAffineTestCase(unittest.TestCase):
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))
+ 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())
@@ -437,36 +365,40 @@ class PyAffineTestCase(unittest.TestCase):
def test_bad_type_world(self):
"""wrong type, i.e don't use readlines()"""
with pytest.raises(TypeError):
- affine.loadsw(['1.0', '0.0', '0.0', '1.0', '0.0', '0.0'])
+ affine.loadsw(["1.0", "0.0", "0.0", "1.0", "0.0", "0.0"])
def test_bad_value_world(self):
"""Wrong number of parameters."""
with pytest.raises(ValueError):
- affine.loadsw('1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0')
+ affine.loadsw("1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0")
def test_simple_world(self):
- s = '1.0\n0.0\n0.0\n-1.0\n100.5\n199.5\n'
+ s = "1.0\n0.0\n0.0\n-1.0\n100.5\n199.5\n"
a = affine.loadsw(s)
- assert \
- a == \
- Affine(
- 1.0, 0.0, 100.0,
- 0.0, -1., 200.0)
+ assert a == Affine(1.0, 0.0, 100.0, 0.0, -1.0, 200.0)
assert affine.dumpsw(a) == s
def test_real_world(self):
- s = dedent('''\
+ s = dedent(
+ """\
39.9317755024
30.0907511581
30.0907511576
-39.9317755019
2658137.2266720217
- 5990821.7039887439''') # no EOL
+ 5990821.7039887439"""
+ ) # no EOL
a1 = affine.loadsw(s)
assert a1.almost_equals(
Affine(
- 39.931775502364644, 30.090751157602412, 2658102.2154086917,
- 30.090751157602412, -39.931775502364644, 5990826.624500916))
+ 39.931775502364644,
+ 30.090751157602412,
+ 2658102.2154086917,
+ 30.090751157602412,
+ -39.931775502364644,
+ 5990826.624500916,
+ )
+ )
a1out = affine.dumpsw(a1)
assert isinstance(a1out, str)
a2 = affine.loadsw(a1out)
@@ -476,6 +408,7 @@ class PyAffineTestCase(unittest.TestCase):
# 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
@@ -522,18 +455,18 @@ def test_transform_precision():
def test_associative():
point = (12, 5)
- trans = Affine.translation(-10., -5.)
- rot90 = Affine.rotation(90.)
+ trans = Affine.translation(-10.0, -5.0)
+ rot90 = Affine.rotation(90.0)
result1 = rot90 * (trans * point)
result2 = (rot90 * trans) * point
- seq_almost_equal(result1, (0., 2.))
+ seq_almost_equal(result1, (0.0, 2.0))
seq_almost_equal(result1, result2)
def test_roundtrip():
point = (12, 5)
trans = Affine.translation(3, 4)
- rot37 = Affine.rotation(37.)
+ rot37 = Affine.rotation(37.0)
point_prime = (trans * rot37) * point
roundtrip_point = ~(trans * rot37) * point_prime
seq_almost_equal(point, roundtrip_point)
@@ -552,17 +485,15 @@ def test_eccentricity():
def test_eccentricity_complex():
- assert \
- (Affine.scale(2, 3) * Affine.rotation(77)).eccentricity == \
- pytest.approx(math.sqrt(5) / 3)
- assert \
- (Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \
- pytest.approx(math.sqrt(5) / 3)
- assert \
- (Affine.translation(32, -47) *
- Affine.rotation(77) *
- Affine.scale(2, 3)).eccentricity == \
- pytest.approx(math.sqrt(5) / 3)
+ assert (Affine.scale(2, 3) * Affine.rotation(77)).eccentricity == pytest.approx(
+ math.sqrt(5) / 3
+ )
+ assert (Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == pytest.approx(
+ math.sqrt(5) / 3
+ )
+ assert (
+ Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)
+ ).eccentricity == pytest.approx(math.sqrt(5) / 3)
def test_rotation_angle():
@@ -585,6 +516,7 @@ def test_mul_fallback_unpack():
class TextPoint:
"""Not iterable, will trigger ValueError in Affine.__mul__."""
+
def __rmul__(self, other):
return other * (1, 2)
@@ -597,6 +529,7 @@ def test_mul_fallback_type_error():
class TextPoint:
"""Iterable, but values trigger TypeError in Affine.__mul__."""
+
def __iter__(self):
return ("1", "2")
@@ -606,5 +539,5 @@ def test_mul_fallback_type_error():
assert Affine.identity() * TextPoint() == (1, 2)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
=====================================
pyproject.toml
=====================================
@@ -0,0 +1,44 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "affine"
+description = "Matrices describing affine transformation of the plane"
+dynamic = ["version"]
+authors = [
+ {name = "Sean Gillies", email = "sean.gillies at gmail.com"},
+]
+readme = "README.rst"
+keywords = ["affine", "transformation", "matrix"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3 :: Only",
+ "Topic :: Multimedia :: Graphics :: Graphics Conversion",
+ "Topic :: Scientific/Engineering :: GIS",
+]
+license = {text = "BSD-3-Clause"}
+requires-python = ">=3.7"
+
+[project.optional-dependencies]
+test = [
+ "pytest >=4.6",
+ "pytest-cov",
+]
+dev = [
+ "pydocstyle",
+ "flake8",
+ "coveralls",
+]
+
+[project.urls]
+Source = "https://github.com/rasterio/affine"
+
+[tool.flit.sdist]
+include = [
+ "AUTHORS.txt",
+ "CHANGES.txt",
+ "LICENSE.txt",
+]
=====================================
setup.cfg
=====================================
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal: 1
-
[tool:pytest]
testpaths: affine/tests
=====================================
setup.py deleted
=====================================
@@ -1,35 +0,0 @@
-import codecs
-import os
-
-from setuptools import find_packages, setup
-
-
-# Parse the version from the affine module.
-with codecs.open(os.path.join("affine", "__init__.py")) as f:
- for line in f:
- if "__version__" in line:
- version = line.split("=")[1].strip()
- version = version.strip('"').strip("'")
- break
-
-with codecs.open("README.rst", encoding="utf-8") as f:
- readme = f.read()
-
-
-setup(
- name="affine",
- version=version,
- description="Matrices describing affine transformation of the plane.",
- long_description=readme,
- classifiers=[],
- keywords="affine transformation matrix",
- author="Sean Gillies",
- author_email="sean at mapbox.com",
- url="https://github.com/sgillies/affine",
- license="BSD",
- packages=find_packages(exclude=["ez_setup", "examples", "tests"]),
- include_package_data=True,
- zip_safe=True,
- extras_require={"test": ["pytest>=4.6", "pytest-cov", "pydocstyle",
- "flake8", "coveralls"]},
-)
=====================================
tox.ini
=====================================
@@ -1,11 +1,11 @@
[tox]
-envlist =
- py27,py36,py37
+envlist =
+ py37,py38,py39,py310,py311
[testenv]
usedevelop = true
deps =
pytest-cov
responses
-commands =
+commands =
python -m pytest affine/tests --cov affine --cov-report term-missing
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-affine/-/commit/2c3486d7ae58fa5918f7d6d56e7bdc9404df3835
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-affine/-/commit/2c3486d7ae58fa5918f7d6d56e7bdc9404df3835
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230120/98af819c/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list