qdgc-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qdgc_py-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: qdgc-py
3
+ Version: 0.1.0
4
+ Summary: Extended Quarter Degree Grid Cell (QDGC) encoding utilities
5
+ Author: Ragnvald Larsen
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/ragnvald/qdgc
8
+ Project-URL: Repository, https://github.com/ragnvald/qdgc
9
+ Keywords: qdgc,geocode,gis,biodiversity
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: GIS
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == "dev"
19
+
20
+ # qdgc-py
21
+
22
+ `qdgc-py` is a lightweight Python package for Extended Quarter Degree Grid Cell
23
+ (QDGC) codes.
24
+
25
+ It modernizes the legacy scripts in this repository into a reusable API that can
26
+ be imported by other projects.
27
+
28
+ QDGC is a hierarchical lon/lat square grid in EPSG:4326:
29
+
30
+ - level 0 cells are 1 deg x 1 deg
31
+ - each level splits each cell into 4 quadrants
32
+ - side length in degrees is `1.0 / (2 ** level)`
33
+
34
+ ## Install
35
+
36
+ Once published to PyPI:
37
+
38
+ ```bash
39
+ pip install qdgc-py
40
+ ```
41
+
42
+ The distribution name is `qdgc-py`; the import name is `qdgc_py`:
43
+
44
+ ```python
45
+ import qdgc_py
46
+ ```
47
+
48
+ ## Quickstart (from source)
49
+
50
+ From `qdgc/qdgc_py`:
51
+
52
+ ```bash
53
+ pip install -e .
54
+ pytest -q
55
+ ```
56
+
57
+ ## API
58
+
59
+ ```python
60
+ from qdgc_py import (
61
+ encode,
62
+ decode_bounds,
63
+ decode_centroid,
64
+ cell_to_boundary,
65
+ cell_to_polygon,
66
+ polygon_to_cells,
67
+ bbox_to_cells,
68
+ cell_to_parent,
69
+ cell_to_children,
70
+ average_cell_area,
71
+ estimate_cell_count,
72
+ )
73
+
74
+ code = encode(38.98754324, -9.87548764, level=5)
75
+ bounds = decode_bounds(code)
76
+ center = decode_centroid(code)
77
+
78
+ ring_latlon = cell_to_boundary(code) # (lat, lon)
79
+ ring_lonlat = cell_to_polygon(code) # (lon, lat)
80
+
81
+ exterior = [
82
+ (10.0, 20.0),
83
+ (12.0, 20.0),
84
+ (12.0, 22.0),
85
+ (10.0, 22.0),
86
+ (10.0, 20.0),
87
+ ]
88
+ cells = polygon_to_cells(exterior, level=4, predicate="intersects")
89
+ bbox_cells = bbox_to_cells(10.0, 20.0, 12.0, 22.0, level=4)
90
+
91
+ parent = cell_to_parent(code)
92
+ children = cell_to_children(parent)
93
+
94
+ area_km2 = average_cell_area(4, lat=45.0)
95
+ estimate = estimate_cell_count(exterior, 4)
96
+ ```
97
+
98
+ ## API summary
99
+
100
+ - `encode(lon, lat, level)` -> `str`
101
+ - `encode_many(points, level)` -> `list[str]`
102
+ - `decode_bounds(code)` -> `QDGCCell`
103
+ - `decode_centroid(code)` -> `(lon, lat)`
104
+ - `cell_to_boundary(code)` -> `list[(lat, lon)]`
105
+ - `cell_to_polygon(code)` -> `list[(lon, lat)]`
106
+ - `bbox_to_cells(min_lon, min_lat, max_lon, max_lat, level)` -> `list[str]`
107
+ - `polygon_to_cells(exterior, level, holes=None, predicate="intersects")` -> `list[str]`
108
+ - `cell_to_parent(code, parent_level=None)` -> `str`
109
+ - `cell_to_children(code, child_level=None)` -> `list[str]`
110
+ - `level_degrees(level)` -> `float`
111
+ - `average_cell_area(level, lat=None, unit="km^2")` -> `float`
112
+ - `estimate_cell_count(exterior, level, bbox=None)` -> `int`
113
+ - `is_valid_cell(code)` -> `bool`
114
+
115
+ H3-style convenience aliases are also available:
116
+
117
+ - `latlng_to_cell(lat, lng, res)`
118
+ - `cell_to_latlng(cell)`
119
+ - `average_hexagon_area(res, unit="km^2")`
120
+
121
+ ## Level table
122
+
123
+ Approximate side lengths and equatorial cell areas:
124
+
125
+ | level | side (deg) | side at equator (km) | area at equator (km^2) |
126
+ |---|---:|---:|---:|
127
+ | 0 | 1.0 | 111.32 | 12364.35 |
128
+ | 1 | 0.5 | 55.66 | 3091.09 |
129
+ | 2 | 0.25 | 27.83 | 772.77 |
130
+ | 3 | 0.125 | 13.92 | 193.19 |
131
+ | 4 | 0.0625 | 6.96 | 48.30 |
132
+ | 5 | 0.03125 | 3.48 | 12.07 |
133
+ | 6 | 0.015625 | 1.74 | 3.02 |
134
+
135
+ Values are approximate because metric size varies with latitude.
136
+
137
+ Boundary behavior at origin is deterministic and legacy-compatible:
138
+
139
+ ```python
140
+ encode(0.0, 0.0, 1) # E000N00C
141
+ encode(0.0, 0.0, 2) # E000N00CC
142
+ ```
143
+
144
+ ## Current scope
145
+
146
+ - Deterministic `encode()` implementation compatible with legacy `qdgc_lib.py`
147
+ - Decode helpers for bounds and centroid
148
+ - Cell geometry, hierarchy, validation, and area estimation helpers
149
+ - Polygon/bbox to cells fill helpers in pure stdlib Python (no shapely dependency)
150
+ - Compatibility tests against legacy implementation plus AOI fill edge cases
151
+
152
+ ## Legacy compatibility and boundary behavior
153
+
154
+ This package intentionally preserves legacy QDGC behavior from `qdgc_lib.py`.
155
+
156
+ - Level 0 uses hemisphere-prefixed degree cells (`E/W` + 3-digit longitude,
157
+ `N/S` + 2-digit latitude), consistent with the extended QDGC description.
158
+ - Subdivision letters follow the original QDGC orientation:
159
+ `A=upper-left`, `B=upper-right`, `C=lower-left`, `D=lower-right`.
160
+ - Boundary points are deterministic. At `(lon=0, lat=0)`, encoding resolves to
161
+ east/north and then to the lower-left subcell at each level, e.g.:
162
+ - level 1: `E000N00C`
163
+ - level 2: `E000N00CC`
164
+
165
+ The boundary choice at exact split lines is a legacy convention and is kept for
166
+ backward compatibility.
167
+
168
+ ## Reference
169
+
170
+ Larsen, R., Holmern, T., Prager, S. D., Maliti, H., and Røskaft, E. (2009).
171
+ Using the extended quarter degree grid cell system to unify mapping and
172
+ sharing of biodiversity data. African Journal of Ecology.
173
+ https://doi.org/10.1111/j.1365-2028.2008.00997.x
174
+
175
+ ## Versioning and releases
176
+
177
+ - Current package version is defined in `pyproject.toml` (`project.version`).
178
+ - Bump the version when behavior or public API changes.
179
+ - Tag releases in git using the same version (for example `v0.1.1`).
180
+
181
+ ### Publishing to PyPI
182
+
183
+ Releases publish automatically via GitHub Actions using PyPI Trusted Publishing
184
+ (OIDC) - no API tokens or stored secrets. See
185
+ `.github/workflows/qdgc_py-release.yml`.
186
+
187
+ One-time setup on [pypi.org](https://pypi.org) (project -> Publishing -> add a
188
+ trusted publisher):
189
+
190
+ | Field | Value |
191
+ |-----------------|-----------------------|
192
+ | Owner | `ragnvald` |
193
+ | Repository | `qdgc` |
194
+ | Workflow name | `qdgc_py-release.yml` |
195
+ | Environment | `pypi` |
196
+
197
+ To cut a release:
198
+
199
+ ```bash
200
+ # 1. bump project.version in qdgc_py/pyproject.toml (e.g. 0.1.1)
201
+ # 2. tag and push
202
+ git tag v0.1.1
203
+ git push origin v0.1.1
204
+ ```
205
+
206
+ The workflow runs the tests, builds the sdist and wheel, validates metadata with
207
+ `twine check`, and uploads to PyPI.
208
+
209
+ Manual fallback (from `qdgc_py/`, requires a PyPI API token):
210
+
211
+ ```bash
212
+ pip install build twine
213
+ python -m build
214
+ twine check dist/*
215
+ twine upload dist/*
216
+ ```
217
+
218
+ ## Next steps
219
+
220
+ - Performance tuning for very large AOIs and high levels
221
+ - Publish tagged releases to PyPI when API is stable
@@ -0,0 +1,202 @@
1
+ # qdgc-py
2
+
3
+ `qdgc-py` is a lightweight Python package for Extended Quarter Degree Grid Cell
4
+ (QDGC) codes.
5
+
6
+ It modernizes the legacy scripts in this repository into a reusable API that can
7
+ be imported by other projects.
8
+
9
+ QDGC is a hierarchical lon/lat square grid in EPSG:4326:
10
+
11
+ - level 0 cells are 1 deg x 1 deg
12
+ - each level splits each cell into 4 quadrants
13
+ - side length in degrees is `1.0 / (2 ** level)`
14
+
15
+ ## Install
16
+
17
+ Once published to PyPI:
18
+
19
+ ```bash
20
+ pip install qdgc-py
21
+ ```
22
+
23
+ The distribution name is `qdgc-py`; the import name is `qdgc_py`:
24
+
25
+ ```python
26
+ import qdgc_py
27
+ ```
28
+
29
+ ## Quickstart (from source)
30
+
31
+ From `qdgc/qdgc_py`:
32
+
33
+ ```bash
34
+ pip install -e .
35
+ pytest -q
36
+ ```
37
+
38
+ ## API
39
+
40
+ ```python
41
+ from qdgc_py import (
42
+ encode,
43
+ decode_bounds,
44
+ decode_centroid,
45
+ cell_to_boundary,
46
+ cell_to_polygon,
47
+ polygon_to_cells,
48
+ bbox_to_cells,
49
+ cell_to_parent,
50
+ cell_to_children,
51
+ average_cell_area,
52
+ estimate_cell_count,
53
+ )
54
+
55
+ code = encode(38.98754324, -9.87548764, level=5)
56
+ bounds = decode_bounds(code)
57
+ center = decode_centroid(code)
58
+
59
+ ring_latlon = cell_to_boundary(code) # (lat, lon)
60
+ ring_lonlat = cell_to_polygon(code) # (lon, lat)
61
+
62
+ exterior = [
63
+ (10.0, 20.0),
64
+ (12.0, 20.0),
65
+ (12.0, 22.0),
66
+ (10.0, 22.0),
67
+ (10.0, 20.0),
68
+ ]
69
+ cells = polygon_to_cells(exterior, level=4, predicate="intersects")
70
+ bbox_cells = bbox_to_cells(10.0, 20.0, 12.0, 22.0, level=4)
71
+
72
+ parent = cell_to_parent(code)
73
+ children = cell_to_children(parent)
74
+
75
+ area_km2 = average_cell_area(4, lat=45.0)
76
+ estimate = estimate_cell_count(exterior, 4)
77
+ ```
78
+
79
+ ## API summary
80
+
81
+ - `encode(lon, lat, level)` -> `str`
82
+ - `encode_many(points, level)` -> `list[str]`
83
+ - `decode_bounds(code)` -> `QDGCCell`
84
+ - `decode_centroid(code)` -> `(lon, lat)`
85
+ - `cell_to_boundary(code)` -> `list[(lat, lon)]`
86
+ - `cell_to_polygon(code)` -> `list[(lon, lat)]`
87
+ - `bbox_to_cells(min_lon, min_lat, max_lon, max_lat, level)` -> `list[str]`
88
+ - `polygon_to_cells(exterior, level, holes=None, predicate="intersects")` -> `list[str]`
89
+ - `cell_to_parent(code, parent_level=None)` -> `str`
90
+ - `cell_to_children(code, child_level=None)` -> `list[str]`
91
+ - `level_degrees(level)` -> `float`
92
+ - `average_cell_area(level, lat=None, unit="km^2")` -> `float`
93
+ - `estimate_cell_count(exterior, level, bbox=None)` -> `int`
94
+ - `is_valid_cell(code)` -> `bool`
95
+
96
+ H3-style convenience aliases are also available:
97
+
98
+ - `latlng_to_cell(lat, lng, res)`
99
+ - `cell_to_latlng(cell)`
100
+ - `average_hexagon_area(res, unit="km^2")`
101
+
102
+ ## Level table
103
+
104
+ Approximate side lengths and equatorial cell areas:
105
+
106
+ | level | side (deg) | side at equator (km) | area at equator (km^2) |
107
+ |---|---:|---:|---:|
108
+ | 0 | 1.0 | 111.32 | 12364.35 |
109
+ | 1 | 0.5 | 55.66 | 3091.09 |
110
+ | 2 | 0.25 | 27.83 | 772.77 |
111
+ | 3 | 0.125 | 13.92 | 193.19 |
112
+ | 4 | 0.0625 | 6.96 | 48.30 |
113
+ | 5 | 0.03125 | 3.48 | 12.07 |
114
+ | 6 | 0.015625 | 1.74 | 3.02 |
115
+
116
+ Values are approximate because metric size varies with latitude.
117
+
118
+ Boundary behavior at origin is deterministic and legacy-compatible:
119
+
120
+ ```python
121
+ encode(0.0, 0.0, 1) # E000N00C
122
+ encode(0.0, 0.0, 2) # E000N00CC
123
+ ```
124
+
125
+ ## Current scope
126
+
127
+ - Deterministic `encode()` implementation compatible with legacy `qdgc_lib.py`
128
+ - Decode helpers for bounds and centroid
129
+ - Cell geometry, hierarchy, validation, and area estimation helpers
130
+ - Polygon/bbox to cells fill helpers in pure stdlib Python (no shapely dependency)
131
+ - Compatibility tests against legacy implementation plus AOI fill edge cases
132
+
133
+ ## Legacy compatibility and boundary behavior
134
+
135
+ This package intentionally preserves legacy QDGC behavior from `qdgc_lib.py`.
136
+
137
+ - Level 0 uses hemisphere-prefixed degree cells (`E/W` + 3-digit longitude,
138
+ `N/S` + 2-digit latitude), consistent with the extended QDGC description.
139
+ - Subdivision letters follow the original QDGC orientation:
140
+ `A=upper-left`, `B=upper-right`, `C=lower-left`, `D=lower-right`.
141
+ - Boundary points are deterministic. At `(lon=0, lat=0)`, encoding resolves to
142
+ east/north and then to the lower-left subcell at each level, e.g.:
143
+ - level 1: `E000N00C`
144
+ - level 2: `E000N00CC`
145
+
146
+ The boundary choice at exact split lines is a legacy convention and is kept for
147
+ backward compatibility.
148
+
149
+ ## Reference
150
+
151
+ Larsen, R., Holmern, T., Prager, S. D., Maliti, H., and Røskaft, E. (2009).
152
+ Using the extended quarter degree grid cell system to unify mapping and
153
+ sharing of biodiversity data. African Journal of Ecology.
154
+ https://doi.org/10.1111/j.1365-2028.2008.00997.x
155
+
156
+ ## Versioning and releases
157
+
158
+ - Current package version is defined in `pyproject.toml` (`project.version`).
159
+ - Bump the version when behavior or public API changes.
160
+ - Tag releases in git using the same version (for example `v0.1.1`).
161
+
162
+ ### Publishing to PyPI
163
+
164
+ Releases publish automatically via GitHub Actions using PyPI Trusted Publishing
165
+ (OIDC) - no API tokens or stored secrets. See
166
+ `.github/workflows/qdgc_py-release.yml`.
167
+
168
+ One-time setup on [pypi.org](https://pypi.org) (project -> Publishing -> add a
169
+ trusted publisher):
170
+
171
+ | Field | Value |
172
+ |-----------------|-----------------------|
173
+ | Owner | `ragnvald` |
174
+ | Repository | `qdgc` |
175
+ | Workflow name | `qdgc_py-release.yml` |
176
+ | Environment | `pypi` |
177
+
178
+ To cut a release:
179
+
180
+ ```bash
181
+ # 1. bump project.version in qdgc_py/pyproject.toml (e.g. 0.1.1)
182
+ # 2. tag and push
183
+ git tag v0.1.1
184
+ git push origin v0.1.1
185
+ ```
186
+
187
+ The workflow runs the tests, builds the sdist and wheel, validates metadata with
188
+ `twine check`, and uploads to PyPI.
189
+
190
+ Manual fallback (from `qdgc_py/`, requires a PyPI API token):
191
+
192
+ ```bash
193
+ pip install build twine
194
+ python -m build
195
+ twine check dist/*
196
+ twine upload dist/*
197
+ ```
198
+
199
+ ## Next steps
200
+
201
+ - Performance tuning for very large AOIs and high levels
202
+ - Publish tagged releases to PyPI when API is stable
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "qdgc-py"
7
+ version = "0.1.0"
8
+ description = "Extended Quarter Degree Grid Cell (QDGC) encoding utilities"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [
13
+ { name = "Ragnvald Larsen" }
14
+ ]
15
+ keywords = ["qdgc", "geocode", "gis", "biodiversity"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Intended Audience :: Science/Research",
21
+ "Topic :: Scientific/Engineering :: GIS"
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/ragnvald/qdgc"
26
+ Repository = "https://github.com/ragnvald/qdgc"
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=8.0"
31
+ ]
32
+
33
+ [tool.setuptools]
34
+ package-dir = {"" = "src"}
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,45 @@
1
+ """Public API for qdgc_py."""
2
+
3
+ from .core import (
4
+ QDGCCell,
5
+ average_cell_area,
6
+ average_hexagon_area,
7
+ bbox_to_cells,
8
+ cell_to_boundary,
9
+ cell_to_children,
10
+ cell_to_latlng,
11
+ cell_to_parent,
12
+ cell_to_polygon,
13
+ decode_bounds,
14
+ decode_centroid,
15
+ encode,
16
+ encode_many,
17
+ estimate_cell_count,
18
+ is_valid_cell,
19
+ latlng_to_cell,
20
+ level_degrees,
21
+ polygon_to_cells,
22
+ )
23
+
24
+ __all__ = [
25
+ "QDGCCell",
26
+ "encode",
27
+ "encode_many",
28
+ "decode_bounds",
29
+ "decode_centroid",
30
+ "cell_to_boundary",
31
+ "cell_to_polygon",
32
+ "bbox_to_cells",
33
+ "polygon_to_cells",
34
+ "cell_to_parent",
35
+ "cell_to_children",
36
+ "level_degrees",
37
+ "average_cell_area",
38
+ "estimate_cell_count",
39
+ "is_valid_cell",
40
+ "latlng_to_cell",
41
+ "cell_to_latlng",
42
+ "average_hexagon_area",
43
+ ]
44
+
45
+ __version__ = "0.1.0"