[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 @@
[](https://github.com/gadomski/antimeridian/blob/main/LICENSE)
[](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