edown 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.
Files changed (52) hide show
  1. edown-0.1.0/.github/workflows/build.yml +29 -0
  2. edown-0.1.0/.github/workflows/ci.yml +42 -0
  3. edown-0.1.0/.github/workflows/docs.yml +45 -0
  4. edown-0.1.0/.github/workflows/publish.yml +29 -0
  5. edown-0.1.0/.github/workflows/smoke-live-gee.yml +40 -0
  6. edown-0.1.0/.gitignore +26 -0
  7. edown-0.1.0/CHANGELOG.md +8 -0
  8. edown-0.1.0/CONTRIBUTING.md +22 -0
  9. edown-0.1.0/LICENSE +21 -0
  10. edown-0.1.0/PKG-INFO +217 -0
  11. edown-0.1.0/PLAN.md +114 -0
  12. edown-0.1.0/README.md +149 -0
  13. edown-0.1.0/docs/api.md +19 -0
  14. edown-0.1.0/docs/cli.md +15 -0
  15. edown-0.1.0/docs/examples/aoi.geojson +23 -0
  16. edown-0.1.0/docs/getting-started.md +65 -0
  17. edown-0.1.0/docs/index.md +12 -0
  18. edown-0.1.0/docs/manifest.md +13 -0
  19. edown-0.1.0/examples/s2_find_download_stack.py +124 -0
  20. edown-0.1.0/mkdocs.yml +22 -0
  21. edown-0.1.0/pyproject.toml +101 -0
  22. edown-0.1.0/src/edown/__init__.py +33 -0
  23. edown-0.1.0/src/edown/__main__.py +4 -0
  24. edown-0.1.0/src/edown/aoi.py +82 -0
  25. edown-0.1.0/src/edown/auth.py +107 -0
  26. edown-0.1.0/src/edown/cli.py +325 -0
  27. edown-0.1.0/src/edown/constants.py +25 -0
  28. edown-0.1.0/src/edown/discovery.py +246 -0
  29. edown-0.1.0/src/edown/download.py +431 -0
  30. edown-0.1.0/src/edown/errors.py +22 -0
  31. edown-0.1.0/src/edown/grid.py +235 -0
  32. edown-0.1.0/src/edown/logging_utils.py +13 -0
  33. edown-0.1.0/src/edown/manifest.py +45 -0
  34. edown-0.1.0/src/edown/models.py +175 -0
  35. edown-0.1.0/src/edown/plugins.py +21 -0
  36. edown-0.1.0/src/edown/progress.py +587 -0
  37. edown-0.1.0/src/edown/stack.py +289 -0
  38. edown-0.1.0/src/edown/utils.py +161 -0
  39. edown-0.1.0/tests/__init__.py +1 -0
  40. edown-0.1.0/tests/conftest.py +62 -0
  41. edown-0.1.0/tests/test_aoi.py +13 -0
  42. edown-0.1.0/tests/test_auth.py +85 -0
  43. edown-0.1.0/tests/test_cli.py +100 -0
  44. edown-0.1.0/tests/test_discovery.py +77 -0
  45. edown-0.1.0/tests/test_download.py +54 -0
  46. edown-0.1.0/tests/test_download_runtime.py +234 -0
  47. edown-0.1.0/tests/test_grid.py +32 -0
  48. edown-0.1.0/tests/test_live_s2.py +128 -0
  49. edown-0.1.0/tests/test_manifest.py +30 -0
  50. edown-0.1.0/tests/test_progress.py +283 -0
  51. edown-0.1.0/tests/test_stack.py +146 -0
  52. edown-0.1.0/uv.lock +3125 -0
@@ -0,0 +1,29 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ - uses: actions/setup-python@v6
15
+ with:
16
+ python-version: "3.12"
17
+ - name: Install build tooling
18
+ run: |
19
+ python -m pip install --upgrade pip
20
+ python -m pip install build twine
21
+ - name: Build artifacts
22
+ run: python -m build
23
+ - name: Validate artifacts
24
+ run: twine check dist/*
25
+ - name: Upload dist
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: python-dist
29
+ path: dist/
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ quality:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ - uses: actions/setup-python@v6
14
+ with:
15
+ python-version: "3.9"
16
+ - name: Install lint and type-check tooling
17
+ run: |
18
+ python -m pip install --upgrade pip
19
+ python -m pip install -e ".[dev]"
20
+ - name: Ruff
21
+ run: ruff check .
22
+ - name: Mypy
23
+ run: mypy src
24
+
25
+ test:
26
+ needs: quality
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
32
+ steps:
33
+ - uses: actions/checkout@v6
34
+ - uses: actions/setup-python@v6
35
+ with:
36
+ python-version: ${{ matrix.python-version }}
37
+ - name: Install package and tooling
38
+ run: |
39
+ python -m pip install --upgrade pip
40
+ python -m pip install -e ".[dev,stack,dask]"
41
+ - name: Pytest
42
+ run: pytest
@@ -0,0 +1,45 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - uses: actions/setup-python@v6
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Install docs dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ python -m pip install -e ".[docs]"
25
+ - name: Build docs
26
+ run: mkdocs build --strict
27
+ - name: Setup Pages
28
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
29
+ uses: actions/configure-pages@v6
30
+ - name: Upload Pages artifact
31
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
32
+ uses: actions/upload-pages-artifact@v4
33
+ with:
34
+ path: site/
35
+
36
+ deploy:
37
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment:
41
+ name: github-pages
42
+ url: ${{ steps.deployment.outputs.page_url }}
43
+ steps:
44
+ - id: deployment
45
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,29 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ environment: pypi
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+ - uses: actions/setup-python@v6
20
+ with:
21
+ python-version: "3.12"
22
+ - name: Install build tooling
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ python -m pip install build
26
+ - name: Build artifacts
27
+ run: python -m build
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,40 @@
1
+ name: Live GEE Smoke Test
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ smoke:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v6
11
+ - uses: actions/setup-python@v6
12
+ with:
13
+ python-version: "3.12"
14
+ - name: Install package
15
+ run: |
16
+ python -m pip install --upgrade pip
17
+ python -m pip install -e ".[dev,stack]"
18
+ - name: Materialize GEE service-account key
19
+ env:
20
+ GEE_SERVICE_ACCOUNT_SECRET: ${{ secrets.GEE_SERVICE_ACCOUNT }}
21
+ GEE_SERVICE_ACCOUNT_KEY_JSON: ${{ secrets.GEE_SERVICE_ACCOUNT_KEY }}
22
+ run: |
23
+ if [ -z "$GEE_SERVICE_ACCOUNT_SECRET" ]; then
24
+ echo "GEE_SERVICE_ACCOUNT secret is not set" >&2
25
+ exit 1
26
+ fi
27
+ if [ -z "$GEE_SERVICE_ACCOUNT_KEY_JSON" ]; then
28
+ echo "GEE_SERVICE_ACCOUNT_KEY secret is not set" >&2
29
+ exit 1
30
+ fi
31
+ key_path="$RUNNER_TEMP/gee-service-account.json"
32
+ printf '%s' "$GEE_SERVICE_ACCOUNT_KEY_JSON" > "$key_path"
33
+ chmod 600 "$key_path"
34
+ echo "GEE_SERVICE_ACCOUNT_KEY=$key_path" >> "$GITHUB_ENV"
35
+ - name: Run live smoke test
36
+ env:
37
+ GEE_SERVICE_ACCOUNT: ${{ secrets.GEE_SERVICE_ACCOUNT }}
38
+ EDOWN_RUN_LIVE_TESTS: "1"
39
+ run: |
40
+ python -m pytest -s tests/test_live_s2.py
edown-0.1.0/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ .DS_Store
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ .mypy_cache/
5
+ .coverage
6
+ coverage.xml
7
+ dist/
8
+ build/
9
+ site/
10
+ htmlcov/
11
+ .venv/
12
+ venv/
13
+ __pycache__/
14
+ *.pyc
15
+ *.pyo
16
+ *.egg-info/
17
+
18
+ # Legacy local reference scripts; intentionally not tracked.
19
+ gee_downloader.py
20
+ access_GEE_generic.py
21
+
22
+ # Runtime outputs
23
+ data/
24
+ manifests/
25
+ images/
26
+ stacks/
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial package scaffold for `edown`.
6
+ - Native-grid Google Earth Engine GeoTIFF downloader with run manifest generation.
7
+ - Optional Zarr stacking for grid-compatible image groups.
8
+ - CLI, tests, documentation, and GitHub Actions release pipeline.
@@ -0,0 +1,22 @@
1
+ # Contributing
2
+
3
+ ## Local Setup
4
+
5
+ ```bash
6
+ python -m pip install -e ".[dev,stack,dask]"
7
+ ```
8
+
9
+ ## Checks
10
+
11
+ ```bash
12
+ ruff check .
13
+ mypy src
14
+ pytest
15
+ python -m build
16
+ ```
17
+
18
+ ## Notes
19
+
20
+ - Keep the legacy `gee_downloader.py` and `access_GEE_generic.py` scripts ignored; they are local references only.
21
+ - Favor pure functions in the discovery and grid-planning layers so they remain easy to test without live Earth Engine access.
22
+ - Live Earth Engine checks belong in the optional `smoke-live-gee.yml` workflow, not in the default unit test suite.
edown-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Yin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
edown-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: edown
3
+ Version: 0.1.0
4
+ Summary: Native-grid Google Earth Engine downloader with optional alignment-aware Zarr stacking.
5
+ Project-URL: Homepage, https://github.com/MarcYin/edown
6
+ Project-URL: Documentation, https://marcyin.github.io/edown/
7
+ Project-URL: Issues, https://github.com/MarcYin/edown/issues
8
+ Author: Marc Yin
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Marc Yin
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: download,earth-engine,gee,geospatial,raster
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.9
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Scientific/Engineering :: GIS
41
+ Requires-Python: <3.13,>=3.9
42
+ Requires-Dist: click>=8.1
43
+ Requires-Dist: earthengine-api>=0.1.397
44
+ Requires-Dist: numpy>=1.24
45
+ Requires-Dist: pyproj>=3.5
46
+ Requires-Dist: rasterio>=1.3
47
+ Requires-Dist: shapely>=2.0
48
+ Requires-Dist: tenacity>=8.2
49
+ Provides-Extra: dask
50
+ Requires-Dist: dask-jobqueue>=0.8.5; extra == 'dask'
51
+ Requires-Dist: distributed>=2024.1.0; extra == 'dask'
52
+ Requires-Dist: tqdm>=4.66; extra == 'dask'
53
+ Provides-Extra: dev
54
+ Requires-Dist: build>=1.2; extra == 'dev'
55
+ Requires-Dist: mypy>=1.8; extra == 'dev'
56
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
57
+ Requires-Dist: pytest>=8.0; extra == 'dev'
58
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
59
+ Requires-Dist: twine>=5.0; extra == 'dev'
60
+ Provides-Extra: docs
61
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
62
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
63
+ Provides-Extra: stack
64
+ Requires-Dist: dask[array]>=2024.1.0; extra == 'stack'
65
+ Requires-Dist: xarray>=2024.1.0; extra == 'stack'
66
+ Requires-Dist: zarr>=2.17; extra == 'stack'
67
+ Description-Content-Type: text/markdown
68
+
69
+ # edown
70
+
71
+ `edown` is a Google Earth Engine downloader that discovers images for a location and time range, downloads each image in its native grid as GeoTIFF, and can optionally build Zarr stacks for grid-compatible groups.
72
+
73
+ ## What It Does
74
+
75
+ - Searches an ImageCollection by date range and AOI.
76
+ - Preserves each image's native CRS and transform instead of forcing a common projection.
77
+ - Downloads intersecting chunks in parallel across multiple images.
78
+ - Writes a run manifest with discovery, download, and stack metadata.
79
+ - Builds Zarr outputs only when images share an alignment signature.
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ python -m pip install edown
85
+ ```
86
+
87
+ For local development:
88
+
89
+ ```bash
90
+ python -m pip install -e ".[dev,stack,dask]"
91
+ ```
92
+
93
+ ## Authentication
94
+
95
+ `edown` prefers Earth Engine service-account credentials when both of these environment variables are set:
96
+
97
+ - `GEE_SERVICE_ACCOUNT`
98
+ - `GEE_SERVICE_ACCOUNT_KEY`
99
+
100
+ Otherwise it tries, in order:
101
+
102
+ - persistent Earth Engine user credentials
103
+ - Google application default credentials
104
+
105
+ If both user-auth and ADC refresh tokens are stale, reauthenticate before running downloads.
106
+
107
+ ## CLI
108
+
109
+ Search only:
110
+
111
+ ```bash
112
+ edown search \
113
+ --collection-id COPERNICUS/S2_SR_HARMONIZED \
114
+ --start-date 2024-06-01 \
115
+ --end-date 2024-06-07 \
116
+ --bbox -0.15 51.48 0.02 51.56 \
117
+ --band B4 \
118
+ --band B8 \
119
+ --manifest-path manifests/search.json
120
+ ```
121
+
122
+ Download native-grid GeoTIFFs:
123
+
124
+ ```bash
125
+ edown download \
126
+ --collection-id COPERNICUS/S2_SR_HARMONIZED \
127
+ --start-date 2024-06-01 \
128
+ --end-date 2024-06-07 \
129
+ --geojson docs/examples/aoi.geojson \
130
+ --band B4 \
131
+ --band B8 \
132
+ --output-root ./data
133
+ ```
134
+
135
+ Build Zarr stacks from compatible groups:
136
+
137
+ ```bash
138
+ edown stack \
139
+ --manifest-path manifests/run.json \
140
+ --output-root ./data
141
+ ```
142
+
143
+ Run the bundled end-to-end Sentinel-2 example:
144
+
145
+ ```bash
146
+ python examples/s2_find_download_stack.py --output-root ./data/live-s2
147
+ ```
148
+
149
+ ## Python API
150
+
151
+ ```python
152
+ from pathlib import Path
153
+
154
+ from edown import AOI, DownloadConfig, download_images
155
+
156
+ config = DownloadConfig(
157
+ collection_id="COPERNICUS/S2_SR_HARMONIZED",
158
+ start_date="2024-06-01",
159
+ end_date="2024-06-07",
160
+ aoi=AOI.from_bbox((-0.15, 51.48, 0.02, 51.56)),
161
+ bands=("B4", "B8"),
162
+ output_root=Path("data"),
163
+ )
164
+
165
+ summary = download_images(config)
166
+ print(summary.manifest_path)
167
+ ```
168
+
169
+ ## Live Integration Test
170
+
171
+ The default test suite is mocked and offline. For a real end-to-end Sentinel-2 smoke test, install the stack extras:
172
+
173
+ ```bash
174
+ python -m pip install -e ".[dev,stack]"
175
+ ```
176
+
177
+ Then run:
178
+
179
+ ```bash
180
+ export EDOWN_RUN_LIVE_TESTS=1
181
+ python -m pytest -s tests/test_live_s2.py
182
+ ```
183
+
184
+ Default live settings:
185
+
186
+ - collection: `COPERNICUS/S2_SR_HARMONIZED`
187
+ - dates: `2024-06-01` through `2024-06-03`
188
+ - bbox: `-0.1278,51.5072,-0.1270,51.5078`
189
+ - bands: `B4,B8`
190
+
191
+ You can override them with:
192
+
193
+ - `EDOWN_LIVE_COLLECTION_ID`
194
+ - `EDOWN_LIVE_START_DATE`
195
+ - `EDOWN_LIVE_END_DATE`
196
+ - `EDOWN_LIVE_BBOX`
197
+ - `EDOWN_LIVE_BANDS`
198
+
199
+ This requires valid Earth Engine authentication, via either:
200
+
201
+ - `GEE_SERVICE_ACCOUNT` and `GEE_SERVICE_ACCOUNT_KEY` (path to a service-account JSON key file)
202
+ - existing local Earth Engine credentials
203
+ - valid Google application default credentials
204
+
205
+ ## Notes
206
+
207
+ - `--chunk-size` is only used as an exact size when `--chunk-size-mode fixed` is set.
208
+ - In the default `auto` mode, `edown` estimates chunk sizes per image from the AOI window and request byte limits.
209
+
210
+ ## Development
211
+
212
+ - `python -m pytest`
213
+ - `ruff check .`
214
+ - `mypy src`
215
+ - `python -m build`
216
+
217
+ Docs are built with MkDocs Material and published to GitHub Pages from GitHub Actions.
edown-0.1.0/PLAN.md ADDED
@@ -0,0 +1,114 @@
1
+ # Edown Bootstrap And Release Plan
2
+
3
+ ## Summary
4
+ - Bootstrap `/Users/fengyin/Documents/edown` into a git repo, add `origin` as `git@github.com:MarcYin/edown.git`, use `main` as the default branch, and push the initial scaffold to the empty GitHub repository.
5
+ - Build `edown` as a `src`-layout Python package that combines the useful logic from the two current scripts, but does not commit those scripts to the repository.
6
+ - Make GeoTIFF the primary output: one file per discovered image, clipped to the AOI in that image’s native CRS/grid.
7
+ - Add an optional stacking stage that creates Zarr outputs only for grid-compatible images; mixed-CRS or mixed-transform collections are split into separate alignment groups instead of being forced into one CRS.
8
+ - First implementation artifact: add `PLAN.md` at repo root containing this plan, then scaffold `src/edown/`, tests, docs, and `.github/workflows/`.
9
+
10
+ ## Repository And Packaging
11
+ - Initialize local git in `/Users/fengyin/Documents/edown`, set remote `origin` to `git@github.com:MarcYin/edown.git`, create branch `main`, and use that as the default branch.
12
+ - Add `.gitignore` before the first commit and explicitly ignore:
13
+ - `gee_downloader.py`
14
+ - `access_GEE_generic.py`
15
+ - Treat those two scripts as local reference material only: read from them during implementation, extract/rewrite the needed logic into package modules, but do not track or publish the original files.
16
+ - Use `src` layout with distribution/import name `edown`.
17
+ - Python support: `>=3.9,<3.13`.
18
+ - Build backend: `hatchling`.
19
+ - Standard project files: `README.md`, `LICENSE` (MIT), `CHANGELOG.md`, `pyproject.toml`, `.gitignore`, docs config, test config, and contributor docs.
20
+
21
+ ## Public APIs And CLI
22
+ - Public Python API:
23
+ - `search_images(config: SearchConfig) -> SearchResult`
24
+ - `download_images(config: DownloadConfig) -> DownloadSummary`
25
+ - `stack_images(config: StackConfig) -> list[StackResult]`
26
+ - Public types:
27
+ - `AOI` with `bbox` or `geojson_path`
28
+ - `SearchConfig` for collection, time range, AOI, band selection, rename/scale, and auth/server settings
29
+ - `DownloadConfig` for search config plus output root, worker counts, retry policy, chunk size mode, resume/overwrite, and manifest path
30
+ - `StackConfig` for manifest/input source, backend (`threads`, `dask-local`, `dask-slurm`), and output root
31
+ - `ImageRecord`, `DownloadResult`, `DownloadSummary`, `AlignmentGroup`, and `StackResult`
32
+ - CLI commands:
33
+ - `edown search ...` discovers images and writes a manifest without downloading
34
+ - `edown download ...` performs search plus native-grid GeoTIFF download
35
+ - `edown stack ...` builds one Zarr store per compatible alignment group from a manifest or download directory
36
+ - AOI input:
37
+ - support `--bbox xmin ymin xmax ymax`
38
+ - support `--geojson path`
39
+ - GeoJSON is used for AOI geometry/bounds in discovery and clipping logic; v1 does not do polygon-mask rasterization
40
+ - Authentication:
41
+ - auto mode uses `GEE_SERVICE_ACCOUNT` and `GEE_SERVICE_ACCOUNT_KEY` when both exist
42
+ - otherwise fall back to normal Earth Engine user/application-default auth
43
+ - default endpoint is the high-volume Earth Engine URL, with override support
44
+ - Keep band rename/scale behavior, but replace raw `mask_eval` with a safer plugin hook: Python callable API and CLI `--transform-plugin module:function`
45
+
46
+ ## Implementation Changes
47
+ - Split the current script logic into focused modules for auth, config/models, discovery, projection/grid math, chunk planning, downloader, manifest I/O, stacking, CLI, and errors.
48
+ - Reuse the generic script’s discovery features:
49
+ - temporal chunking around the 5000-image limit
50
+ - band selection and include/exclude regex filters
51
+ - rename/scale mapping
52
+ - request-size-based chunk estimation
53
+ - Reuse the downloader script’s native-grid strategy:
54
+ - compute AOI intersection in each image’s own CRS
55
+ - plan chunks only for intersecting windows
56
+ - fetch chunks in parallel across many images
57
+ - Replace the current “submit everything at once” behavior with a bounded global task queue so large runs stay memory-safe.
58
+ - Write GeoTIFFs from the coordinator thread; worker threads only fetch chunk data.
59
+ - Record each image’s native CRS, affine transform, dimensions, selected bands, and alignment signature in the manifest.
60
+ - Define stack compatibility as matching CRS, affine transform, AOI window shape, output band order, and dtype.
61
+ - Build one Zarr store per alignment group; incompatible groups are skipped with explicit manifest/report entries.
62
+ - Keep Dask local and SLURM as optional extras and optional backends, not core requirements.
63
+ - Standardize output layout:
64
+ - `manifests/run-<timestamp>.json`
65
+ - `images/<collection>/<safe-image-id>.tif`
66
+ - `images/<collection>/<safe-image-id>.tif.metadata.json`
67
+ - `stacks/<collection>/<alignment-group>.zarr`
68
+
69
+ ## CI, Docs, And Release
70
+ - Dependencies:
71
+ - base: `earthengine-api`, `numpy`, `rasterio`, `shapely`, `pyproj`, `click`, `tenacity`
72
+ - stack extra: `xarray`, `zarr`, `dask[array]`
73
+ - dask extra: `distributed`, `dask_jobqueue`, `tqdm`
74
+ - dev/docs: `pytest`, `pytest-mock`, `ruff`, `mypy`, `mkdocs-material`, `mkdocstrings[python]`
75
+ - GitHub Actions workflows:
76
+ - `ci.yml` for Ruff, mypy, unit tests, and mocked integration tests on Python 3.9, 3.10, 3.11, and 3.12
77
+ - `build.yml` for wheel/sdist build plus `twine check`
78
+ - `docs.yml` for MkDocs build on PRs and deploy to GitHub Pages on `main`
79
+ - `publish.yml` for PyPI release on tags like `v0.1.0`
80
+ - `smoke-live-gee.yml` as an optional `workflow_dispatch` live smoke test using repo secrets
81
+ - Docs stack:
82
+ - MkDocs Material with `mkdocstrings`
83
+ - publish to GitHub Pages at `https://marcyin.github.io/edown/`
84
+ - include install, auth, quickstart, CLI reference, Python API reference, manifest format, and mixed-grid stacking behavior
85
+ - Publishing:
86
+ - configure PyPI Trusted Publishing from GitHub Actions with `id-token: write`
87
+ - publish on version tags from `main`
88
+
89
+ ## Test Plan
90
+ - Unit tests:
91
+ - AOI parsing for bbox and GeoJSON
92
+ - collection discovery chunking around the 5000-image threshold
93
+ - band selection, rename, scale, and missing-band handling
94
+ - native-grid AOI intersection and chunk window planning
95
+ - chunk-size estimation against request-size limits
96
+ - alignment-signature grouping for stack compatibility
97
+ - manifest serialization plus resume/overwrite behavior
98
+ - Mocked integration tests:
99
+ - threaded multi-image download writes valid GeoTIFFs and metadata sidecars
100
+ - mixed-grid collections download successfully and split into separate alignment groups
101
+ - stack creation succeeds for compatible groups and skips incompatible ones cleanly
102
+ - retry and partial-failure cases are surfaced correctly in the manifest and CLI exit code
103
+ - transform plugin modifies server-side image preparation without unsafe eval
104
+ - Release validation:
105
+ - wheel and sdist build cleanly
106
+ - docs build without broken references
107
+ - optional live smoke workflow downloads a tiny public AOI and verifies outputs
108
+
109
+ ## Assumptions And Defaults
110
+ - `edown` remains the package/distribution name unless PyPI availability changes before first release.
111
+ - GeoTIFF is the main artifact; Zarr is derived and only built for compatible native-grid groups.
112
+ - No automatic reprojection or “single common CRS” behavior is allowed in the default workflow.
113
+ - `click` remains the CLI framework.
114
+ - The two current scripts are intentionally excluded from git via `.gitignore` and are not part of the published package.