[Git][debian-gis-team/antimeridian][upstream] New upstream version 0.3.3

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Wed Aug 30 07:33:38 BST 2023



Antonio Valentino pushed to branch upstream at Debian GIS Project / antimeridian


Commits:
799e2113 by Antonio Valentino at 2023-08-30T06:23:08+00:00
New upstream version 0.3.3
- - - - -


9 changed files:

- .pre-commit-config.yaml
- CHANGELOG.md
- README.md
- RELEASING.md
- pyproject.toml
- scripts/install-min-dependencies
- src/antimeridian/__init__.py
- src/antimeridian/_implementation.py
- tests/test_polygon.py


Changes:

=====================================
.pre-commit-config.yaml
=====================================
@@ -7,23 +7,23 @@ repos:
       - id: check-yaml
       - id: check-added-large-files
   - repo: https://github.com/psf/black
-    rev: 23.3.0
+    rev: 23.7.0
     hooks:
       - id: black
   - repo: https://github.com/adamchainz/blacken-docs
-    rev: "1.15.0"
+    rev: "1.16.0"
     hooks:
       - id: blacken-docs
         additional_dependencies:
           - black~=23.3
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.4.1
+    rev: v1.5.1
     hooks:
       - id: mypy
         additional_dependencies:
-          - click~=8.1,!=8.1.4 # https://github.com/pallets/click/issues/2558
+          - click~=8.1.6
           - pytest~=7.3
   - repo: https://github.com/charliermarsh/ruff-pre-commit
-    rev: v0.0.277
+    rev: v0.0.285
     hooks:
       - id: ruff


=====================================
CHANGELOG.md
=====================================
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 ## [Unreleased]
 
+## [0.3.3] - 2023-08-21
+
+### Fixed
+
+- Wrapping of centroid points ([#69](https://github.com/gadomski/antimeridian/pull/69))
+
+## [0.3.2] - 2023-08-21
+
+### Added
+
+- `centroid` ([#67](https://github.com/gadomski/antimeridian/pull/67))
+
 ## [0.3.1] - 2023-07-10
 
 ### Added
@@ -104,8 +116,10 @@ This v0.1.0 release is to indicate that we think that this package is ready to u
 
 Initial release.
 
-[unreleased]: https://github.com/gadomski/antimeridian/compare/v0.3.0...HEAD
-[0.3.1]: https://github.com/gadomsk/antimeridian/compare/v0.2.6...v0.3.0
+[unreleased]: https://github.com/gadomski/antimeridian/compare/v0.3.3...HEAD
+[0.3.3]: https://github.com/gadomsk/antimeridian/compare/v0.3.2...v0.3.3
+[0.3.2]: https://github.com/gadomsk/antimeridian/compare/v0.3.1...v0.3.2
+[0.3.1]: https://github.com/gadomsk/antimeridian/compare/v0.3.0...v0.3.1
 [0.3.0]: https://github.com/gadomsk/antimeridian/compare/v0.2.6...v0.3.0
 [0.2.6]: https://github.com/gadomsk/antimeridian/compare/v0.2.5...v0.2.6
 [0.2.5]: https://github.com/gadomsk/antimeridian/compare/v0.2.4...v0.2.5


=====================================
README.md
=====================================
@@ -7,6 +7,8 @@
 [![GitHub](https://img.shields.io/github/license/gadomski/antimeridian?style=for-the-badge)](https://github.com/gadomski/antimeridian/blob/main/LICENSE)
 [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=for-the-badge)](https://github.com/gadomski/antimeridian/blob/main/CODE_OF_CONDUCT)
 
+<img src="docs/img/complex-split.png" style="width: 600px;" alt="Demonstration image" />
+
 Fix shapes that cross the antimeridian.
 See [the documentation](https://antimeridian.readthedocs.io) for information about the underlying algorithm.
 Depends on [shapely](https://shapely.readthedocs.io) and [numpy](https://numpy.org/).
@@ -31,6 +33,9 @@ import antimeridian
 fixed = antimeridian.fix_geojson(geojson)
 ```
 
+We also have some utilities to create [bounding boxes](https://antimeridian.readthedocs.io/en/latest/api.html#antimeridian.bbox) and [centroids](https://antimeridian.readthedocs.io/en/latest/api.html#antimeridian.centroid) from antimeridian-crossing polygons and multipolygons.
+See [the documentation](https://antimeridian.readthedocs.io/) for a complete API reference.
+
 ### Command line interface
 
 Use the `cli` optional dependency to install the `antimeridian` CLI:


=====================================
RELEASING.md
=====================================
@@ -6,7 +6,9 @@
 3. Update:
    - The version in [pyproject.toml](./pyproject.toml)
    - The [CHANGELOG](./CHANGELOG.md)
-   - The **pre-commit** hooks: `pre-commit autoupdate`
+   - The **pre-commit** hooks:
+      - `pre-commit autoupdate`
+      - Update any `additional_dependencies` fields to match `pyproject.toml`
 4. Open a PR with the changes
 5. When the PR is merged, created a tag on `main` with that version with a `v` prefix, e.g. `vX.Y.Z`.
 6. Push the tag to Github, which will fire off the release workflow.


=====================================
pyproject.toml
=====================================
@@ -1,32 +1,25 @@
 [project]
 name = "antimeridian"
-version = "0.3.1"
-authors = [
-    {name = "Pete Gadomski", email = "pete.gadomski at gmail.com"}
-]
+version = "0.3.3"
+authors = [{ name = "Pete Gadomski", email = "pete.gadomski at gmail.com" }]
 description = "Fix GeoJSON geometries that cross the antimeridian"
 readme = "README.md"
 requires-python = ">=3.8"
 keywords = ["geojson", "antimeridian", "shapely"]
-license = {text = "Apache-2.0"}
+license = { text = "Apache-2.0" }
 classifiers = [
     "Programming Language :: Python :: 3",
     "Development Status :: 4 - Beta",
 ]
-dependencies = [
-    "numpy>=1.17.4",
-    "shapely>=2.0",
-]
+dependencies = ["numpy>=1.17.4", "shapely>=2.0"]
 
 [project.urls]
-documentation = "https://antimeridian.readthedocs.io"
-repository = "https://github.com/gadomski/antimeridan"
-changelog = "https://github.com/gadomski/antimeridian/blob/main/CHANGELOG.md"
+Documentation = "https://antimeridian.readthedocs.io"
+Github = "https://github.com/gadomski/antimeridan"
+Changelog = "https://github.com/gadomski/antimeridian/blob/main/CHANGELOG.md"
 
 [project.optional-dependencies]
-cli = [
-    "click~=8.1"
-]
+cli = ["click~=8.1.6"]
 dev = [
     "black~=23.3",
     "blacken-docs~=1.13",
@@ -35,7 +28,7 @@ dev = [
     "pre-commit~=3.2",
     "pytest~=7.3",
     "pytest-console-scripts~=1.3",
-    "ruff==0.0.275",
+    "ruff==0.0.285",
     "tomli~=2.0; python_version<'3.11'",
     "typing_extensions; python_version<'3.10'",
 ]
@@ -45,9 +38,9 @@ docs = [
     "jupytext~=1.14",
     "nbsphinx~=0.9",
     "pydata-sphinx-theme~=0.13",
-    "scipy~=1.10.0",  # need to stay below 1.11 due to https://github.com/SciTools/cartopy/issues/2199
+    "scipy~=1.10.0",             # need to stay below 1.11 due to https://github.com/SciTools/cartopy/issues/2199
     "sphinx~=7.0",
-    "sphinx-click~=4.4",
+    "sphinx-click~=5.0",
 ]
 
 [project.scripts]
@@ -57,7 +50,12 @@ antimeridian = "antimeridian._cli:cli"
 strict = true
 
 [[tool.mypy.overrides]]
-module = ["shapely", "shapely.geometry"]
+module = [
+    "shapely",
+    "shapely.geometry",
+    "shapely.affinity",
+    "shapely.validation",
+]
 ignore_missing_imports = true
 
 [tool.pytest.ini_options]


=====================================
scripts/install-min-dependencies
=====================================
@@ -30,7 +30,7 @@ for install_requires in filter(
 ):
     requirement = Requirement(install_requires)
     assert len(requirement.specifier) == 1
-    specifier = list(requirement.specifier)[0]
+    specifier = next(iter(requirement.specifier))
     assert specifier.operator == ">="
     install_requires = install_requires.replace(">=", "==")
     requirements.append(install_requires)


=====================================
src/antimeridian/__init__.py
=====================================
@@ -4,6 +4,7 @@ from ._implementation import (
     FixWindingWarning,
     GeoInterface,
     bbox,
+    centroid,
     fix_geojson,
     fix_line_string,
     fix_multi_line_string,
@@ -18,6 +19,7 @@ __all__ = [
     "FixWindingWarning",
     "GeoInterface",
     "bbox",
+    "centroid",
     "fix_geojson",
     "fix_line_string",
     "fix_multi_line_string",


=====================================
src/antimeridian/_implementation.py
=====================================
@@ -14,16 +14,19 @@ from typing import Any, Dict, List, Optional, Protocol, Tuple, Union, cast
 
 import numpy
 import shapely
+import shapely.affinity
 import shapely.geometry
+import shapely.validation
 from shapely.geometry import (
     LinearRing,
     LineString,
     MultiLineString,
     MultiPolygon,
+    Point,
     Polygon,
 )
 
-Point = Tuple[float, float]
+XY = Tuple[float, float]
 
 
 class AntimeridianWarning(UserWarning):
@@ -217,7 +220,7 @@ def fix_shape(
         raise ValueError(f"unsupported geom_type: {geom.geom_type}")
 
 
-def segment_shape(shape: Dict[str, Any] | GeoInterface) -> List[List[Point]]:
+def segment_shape(shape: Dict[str, Any] | GeoInterface) -> List[List[XY]]:
     geom = shapely.geometry.shape(shape)
     if geom.geom_type == "Polygon":
         return segment_polygon(geom)
@@ -359,7 +362,7 @@ def fix_multi_line_string(multi_line_string: MultiLineString) -> MultiLineString
     return MultiLineString(line_strings)
 
 
-def segment_polygon(polygon: Polygon) -> List[List[Point]]:
+def segment_polygon(polygon: Polygon) -> List[List[XY]]:
     segments = segment(list(polygon.exterior.coords))
     if not segments:
         segments = [list(polygon.exterior.coords)]
@@ -423,7 +426,7 @@ def fix_polygon_to_list(
     return polygons
 
 
-def segment(coords: List[Point]) -> List[List[Point]]:
+def segment(coords: List[XY]) -> List[List[XY]]:
     segment = []
     segments = []
     for i, point in enumerate(coords):
@@ -459,7 +462,7 @@ def segment(coords: List[Point]) -> List[List[Point]]:
     return segments
 
 
-def crossing_latitude(start: Point, end: Point) -> float:
+def crossing_latitude(start: XY, end: XY) -> float:
     if abs(start[0]) == 180:
         return start[1]
     elif abs(end[0]) == 180:
@@ -480,12 +483,12 @@ def crossing_latitude(start: Point, end: Point) -> float:
 
 
 def extend_over_poles(
-    segments: List[List[Point]],
+    segments: List[List[XY]],
     *,
     force_north_pole: bool,
     force_south_pole: bool,
     fix_winding: bool,
-) -> List[List[Point]]:
+) -> List[List[XY]]:
     left_starts = list()
     right_starts = list()
     left_ends = list()
@@ -537,7 +540,7 @@ def extend_over_poles(
 
 
 def build_polygons(
-    segments: List[List[Point]],
+    segments: List[List[XY]],
 ) -> List[Polygon]:
     if not segments:
         return []
@@ -592,7 +595,7 @@ def build_polygons(
         return polygons
 
 
-def is_self_closing(segment: List[Point]) -> bool:
+def is_self_closing(segment: List[XY]) -> bool:
     is_right = segment[-1][0] == 180
     return segment[0][0] == segment[-1][0] and (
         (is_right and segment[0][1] > segment[-1][1])
@@ -644,6 +647,43 @@ def bbox(shape: Dict[str, Any] | GeoInterface) -> List[float]:
         )
 
 
+def centroid(shape: Dict[str, Any] | GeoInterface) -> Point:
+    """Calculates the centroid for a polygon or multipolygon.
+
+    Polygons are easy, we just use :py:func:`shapely.centroid`. For
+    multi-polygons, the antimeridian is taken into account by calculating the
+    centroid from an identical multi-polygon with coordinates in [0, 360).
+
+    Args:
+        shape: The polygon or multipolygon for which to calculate the centroid.
+
+    Returns:
+        Point: The centroid.
+    """
+    # Inspired by
+    # https://github.com/stactools-packages/sentinel2/blob/f90f5fa006459e9bb59bfd327d9199e5259ec4a7/src/stactools/sentinel2/stac.py#L192-L208
+    geom = shapely.geometry.shape(shape)
+    if geom.geom_type == "Polygon":
+        return cast(Point, geom.centroid)
+    elif geom.geom_type == "MultiPolygon":
+        geoms = list()
+        for component in geom.geoms:
+            if any(c[0] < 0 for c in component.exterior.coords):
+                geoms.append(shapely.affinity.translate(component, xoff=+360))
+            else:
+                geoms.append(component)
+        centroid = cast(
+            Point, shapely.validation.make_valid(MultiPolygon(geoms)).centroid
+        )
+        if centroid.x > 180:
+            centroid = Point(centroid.x - 360, centroid.y)
+        return centroid
+    else:
+        raise ValueError(
+            f"unsupported geom_type for centroid calculation: {geom.geom_type}"
+        )
+
+
 def is_coincident_to_antimeridian(polygon: Polygon) -> bool:
     for start, end in zip(polygon.exterior.coords, polygon.exterior.coords[1:]):
         if abs(start[0]) == 180 and start[0] == end[0]:


=====================================
tests/test_polygon.py
=====================================
@@ -1,8 +1,11 @@
+from typing import cast
+
 import antimeridian
 import pytest
+import shapely.affinity
 import shapely.geometry
 from antimeridian import FixWindingWarning
-from shapely.geometry import MultiPolygon, Polygon
+from shapely.geometry import MultiPolygon, Point, Polygon
 
 from .conftest import Reader
 
@@ -130,3 +133,26 @@ def test_fix_winding_interior_segments(read_input: Reader, read_output: Reader)
     with pytest.warns(FixWindingWarning):
         fixed = antimeridian.fix_polygon(input)
     assert fixed.normalize() == output.normalize()
+
+
+def test_centroid_simple(read_input: Reader) -> None:
+    input = read_input("simple")
+    centroid = cast(Point, antimeridian.centroid(input))
+    assert centroid.x == 95
+    assert centroid.y == 45
+
+
+def test_centroid_split(read_output: Reader) -> None:
+    input = read_output("split")
+    centroid = cast(Point, antimeridian.centroid(input))
+    assert centroid.x == 180
+    assert centroid.y == 45
+
+
+def test_centroid_split_with_shift(read_input: Reader) -> None:
+    input = read_input("split")
+    input = shapely.affinity.translate(input, xoff=+1)
+    input = antimeridian.fix_polygon(input)
+    centroid = cast(Point, antimeridian.centroid(input))
+    assert centroid.x == -179
+    assert centroid.y == 45



View it on GitLab: https://salsa.debian.org/debian-gis-team/antimeridian/-/commit/799e21131db037555839ae0f4c26dcb2dbe78bf2

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/antimeridian/-/commit/799e21131db037555839ae0f4c26dcb2dbe78bf2
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/20230830/c9fb2bca/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list