diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 636a494..8f21a06 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: extras: - "dev" - "cli,dev" - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" - name: Insall uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Install with development dependencies diff --git a/.github/workflows/joss.yaml b/.github/workflows/joss.yaml new file mode 100644 index 0000000..cfa70a5 --- /dev/null +++ b/.github/workflows/joss.yaml @@ -0,0 +1,26 @@ +name: JOSS paper + +on: + pull_request: + paths: + - docs/paper.* + push: + branches: + - main + paths: + - docs/paper.* + +jobs: + paper: + runs-on: ubuntu-latest + name: JOSS paper + steps: + - uses: actions/checkout@v4 + - uses: openjournals/openjournals-draft-action@master + with: + journal: joss + paper-path: docs/paper.md + - uses: actions/upload-artifact@v4 + with: + name: paper + path: docs/paper.pdf diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0f21403..40aa10f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install build run: pip install build - name: Build diff --git a/docs/conf.py b/docs/conf.py index d1bc9ec..33294f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ ] templates_path = ["_templates"] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "paper.*"] html_theme = "pydata_sphinx_theme" html_static_path = ["_static"] diff --git a/docs/img/antimeridian.jpg b/docs/img/antimeridian.jpg new file mode 100644 index 0000000..33c8ccc Binary files /dev/null and b/docs/img/antimeridian.jpg differ diff --git a/docs/paper.bib b/docs/paper.bib new file mode 100644 index 0000000..e8a4759 --- /dev/null +++ b/docs/paper.bib @@ -0,0 +1,41 @@ +@rfc{10.17487/RFC7946, +author = {Butler, H. and Daly, M. and Doyle, A. and Gillies, S. and Hagen, S. and Schaub, T.}, +title = {RFC 7946: The GeoJSON Format}, +year = {2016}, +publisher = {RFC Editor}, +address = {USA}, +abstract = {GeoJSON is a geospatial data interchange format based on JavaScript Object Notation (JSON). It defines several types of JSON objects and the manner in which they are combined to represent data about geographic features, their properties, and their spatial extents. GeoJSON uses a geographic coordinate reference system, World Geodetic System 1984, and units of decimal degrees.} +} + +@proceedings{alma992356353405961, +author = {Various}, +title = {International Conference Held at Washington for the Purpose of Fixing a Prime Meridian and a Universal Day. October, 1884. Protocols of the Proceedings}, +year = {1884}, +url = {https://www.gutenberg.org/files/17759/17759-h/17759-h.htm} +} + +@misc{STAC_Contributors_SpatioTemporal_Asset_Catalog_2024, +author = {{STAC Contributors}}, +title = {{SpatioTemporal Asset Catalog (STAC) specification}}, +url = {https://stacspec.org}, +year = {2024} +} + +@software{Gillies_Shapely_2024, +author = {Gillies, Sean and van der Wel, Casper and Van den Bossche, Joris and Taves, Mike W. and Arnott, Joshua and Ward, Brendan C. and {others}}, +doi = {10.5281/zenodo.5597138}, +license = {BSD-3-Clause}, +month = aug, +title = {{Shapely}}, +url = {https://github.com/shapely/shapely}, +version = {2.0.6}, +year = {2024} +} + +@manual{Cartopy, +author = {{Met Office}}, +title = {Cartopy: a cartographic python library with a Matplotlib interface}, +year = {2010 - 2015}, +address = {Exeter, Devon }, +url = {https://scitools.org.uk/cartopy} +} diff --git a/docs/paper.md b/docs/paper.md new file mode 100644 index 0000000..cba7fc5 --- /dev/null +++ b/docs/paper.md @@ -0,0 +1,63 @@ +--- +title: "antimeridian: A Python package for fixing geometries that cross the 180th meridian" +tags: + - Python + - geospatial + - antimeridian +authors: + - name: Peter Gadomski + orcid: 0000-0003-4877-7217 + corresponding: true + affiliation: 1 + - name: Preston Hartzell + orcid: 0000-0002-8293-3706 + affiliation: 2 +affiliations: + - name: Development Seed, USA + index: 1 + - name: Element 84, Inc., USA + index: 2 +date: 10 October 2024 +bibliography: paper.bib +--- + +## Summary + +Locations on and around planet Earth can be represented in a geodetic coordinate system by a latitude, a longitude, and a height. +Longitude, the "horizontal" dimension, covers the domain from -180° to 180° or 0° and 360°. +Where the two domain bounds meet is known as the _180th meridian_ or the _antimeridian_. + +![Earth map centered on the Pacific ocean, with the 180th meridian highlighted.](./img/antimeridian.jpg) + +The GeoJSON specification [@10.17487/RFC7946] describes how antimeridian-crossing shapes should be represented. +For a variety of reasons, real-world geometries often do not comply with the specification, leading to confusing and unrepresentable geometries. +Our **antimeridan** package provides Python functions for correcting improper geometries, as well as other related utilities. + +## Statement of need + +Due to a variety of factors, including the relative lack of populated settlements on the other side of the world, the Prime Meridian (0°) runs through Greenwich, England [@alma992356353405961]. +Before the advent of satellite imagery, relatively few geospatial products crossed the 180th meridian, and so the problem of antimeridian-crossing geometries was usually avoidable. +The proliferation of satellite remote sensing products, including Earth Observation (EO) imagery, coupled with the ubiquity of interactive online maps, the antimeridian has become a feature that can appear on almost anyone's tablet, web portal, or desktop Geographic Information System (GIS) software. +There is a a need to create and fix antimeridian-crossing geometries at scale, e.g. for large SpatioTemporal Asset Catalog (STAC) [@STAC_Contributors_SpatioTemporal_Asset_Catalog_2024] catalogs that are used to search and discover petabytes of geospatial data. +When creating these catalogs, improper antimeridian-crossing geometries need to be corrected before ingesting to a data store to ensure that queries do not break and visualizations do not go haywire. +This is the problem for which **antimeridian** was designed. + +To the best of our knowledge, the [algorithm](https://antimeridian.readthedocs.io/en/stable/the-algorithm.html) underlying **antimeridian** is a novel one. +Briefly, it breaks each polygon into segments and finds where those segments might cross the antimeridian. +It then breaks that segment at that crossing point and closes that segment along the antimeridian to create a new polygon. +This results in a multi polygon split on the antimeridian, as the GeoJSON specification requires. + +![A complex shape split at the antimeridian](./img/complex-split.png) + +The library also includes utilities for calculating centriods from antimeridian-crossing geometries and generating valid GeoJSON antimeridian-crossing bounding boxes. +To our knowledge, it has been ported to at least one other language in the [go-geospatial/antimeridian](https://pkg.go.dev/github.com/go-geospatial/antimeridian). + +## Key references + +- The **antimeridian** package relies on Shapely [@Gillies_Shapely_2024] for geometry validation, conversions, and other operations. +- We use Cartopy [@Cartopy] to generate visualizations for our documentation. + +# Acknowledgements + +We acknowledge the financial and program support of the Planetary Computer team at Microsoft, particularly Rob Emanuele, Tom Augspurger, and Matt McFarland. +We would also like to acknowledge our employers, Development Seed and Element 84, who support open source software through direct funding and developer contribution. diff --git a/pyproject.toml b/pyproject.toml index c7147e8..ae4db94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,11 @@ version = "0.3.8" authors = [{ name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }] description = "Fix GeoJSON geometries that cross the antimeridian" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = ["geojson", "antimeridian", "shapely"] license = { text = "Apache-2.0" } classifiers = [ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12",