PyGeoFetch 2.6.2__tar.gz → 2.6.2.1__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.
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/CHANGELOG.md +5 -5
- {pygeofetch-2.6.2/pygeofetch.egg-info → pygeofetch-2.6.2.1}/PKG-INFO +25 -28
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/README.md +23 -27
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/__init__.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/auth_commands.py +6 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/main.py +10 -6
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/monitor_commands.py +49 -14
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/search_commands.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/authenticator.py +5 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/downloader.py +31 -13
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/engine.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/monitor.py +16 -5
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/state.py +20 -6
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/__init__.py +96 -71
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/analysis.py +62 -40
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/annotation.py +116 -70
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/atmosphere.py +168 -70
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/coregister.py +126 -58
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/deburst.py +39 -12
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/esd.py +65 -28
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/extraction.py +126 -67
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/flatearth.py +45 -18
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/geolocation.py +79 -29
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/gpu.py +3 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/interferogram.py +502 -193
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/ionosphere.py +118 -45
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/offset_geometry.py +47 -43
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/offset_tracking.py +45 -20
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/preflight.py +288 -169
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/provenance.py +2 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/ps_selection.py +21 -9
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/splitspectrum_diagnostic.py +12 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/stack_selection.py +179 -77
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/synthetic.py +53 -17
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/timeseries.py +268 -152
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/unwrap.py +408 -156
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/validate.py +36 -13
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/visualize.py +15 -5
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/insar/workflow.py +102 -29
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/meta.yaml +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/models/satellite_data.py +5 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/indices.py +5 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/preprocessor.py +55 -25
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/sar.py +13 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/indices.py +4 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/stacker.py +4 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/timeseries.py +41 -13
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/airbus_oneatlas.py +17 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/alaska_satellite_facility.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/aws_earth.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/copernicus.py +19 -8
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/copernicus_nodes.py +73 -23
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/digitalglobe.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/earth_explorer_additional.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/element84.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/eodag_provider.py +9 -5
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/esa_scihub.py +17 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/geoserver_generic.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/google_earth_engine.py +17 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/inpe_cbers.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/isro_bhuvan.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/jaxa_earth.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/maxar_gbdx.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/nasa_earthdata.py +6 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/nasa_earthdata_cloud.py +2 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/noaa_big_data.py +5 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/opentopography.py +6 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/planet.py +4 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/planetary_computer.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/sentinel_hub.py +11 -7
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/terrabotics.py +4 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/usgs.py +20 -5
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/__init__.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/_native.py +1 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/extraction.py +8 -4
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/gcp_recovery.py +1 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/viz/map.py +82 -33
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/viz/plot.py +91 -38
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1/pygeofetch.egg-info}/PKG-INFO +25 -28
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/SOURCES.txt +0 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/requires.txt +1 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pyproject.toml +9 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_atmospheric_circular_regression.py +21 -6
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_build_sbas_network.py +68 -38
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_burst_metadata.py +20 -6
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_burst_processing_integration.py +110 -31
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_burst_sync_large_gap.py +49 -35
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_burst_sync_prescreening.py +68 -43
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_burst_synchronization.py +56 -36
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_copernicus_nodes.py +63 -36
- pygeofetch-2.6.2.1/tests/test_coregister_integration.py +353 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_coregister_upgrades.py +110 -47
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_era5_atmospheric_correction.py +17 -6
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_extraction.py +46 -12
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_flat_earth_phase.py +72 -24
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_gcp_reprojection.py +28 -8
- pygeofetch-2.6.2.1/tests/test_insar.py +691 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_ionosphere.py +102 -33
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_map_preview_consolidation.py +41 -24
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_models.py +10 -3
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_monitor.py +51 -30
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_offset_tracking.py +73 -31
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_pgv_integration_contract.py +42 -42
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_preflight_gate.py +398 -107
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_provider_geometry_audit.py +26 -12
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_provider_scene_disambiguation.py +31 -12
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_providers.py +1 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_ps_selection.py +122 -54
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_raster_collocation_integration.py +75 -36
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_reference_pixel_and_despike.py +64 -38
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_risk_mapping.py +99 -43
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_screen_stack_orchestration.py +68 -41
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_search_download.py +3 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_sign_convention.py +71 -37
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_splitspectrum_diagnostic.py +7 -2
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_stack_selection_consolidation.py +104 -45
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_state.py +33 -24
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_topo_r2_bug.py +42 -21
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_utils.py +2 -1
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_wls_inversion.py +124 -54
- pygeofetch-2.6.2/tests/test_coregister_integration.py +0 -354
- pygeofetch-2.6.2/tests/test_insar.py +0 -578
- pygeofetch-2.6.2/tests/test_preflight_gate (1).py +0 -319
- pygeofetch-2.6.2/tests/test_provider_geometry_audit (1).py +0 -200
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/LICENSE +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/MANIFEST.in +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/config_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/download_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/index_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/pipeline_process_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/postprocess_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/preprocess_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/cli/sar_commands.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/config/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/config/defaults.yaml +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/config/settings.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/cache_manager.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/logging.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/orbits.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/scheduler.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/core/searcher.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/models/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/models/download_task.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/models/search_query.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/models/user_auth.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/base.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/batch.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/pipeline.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processing/postprocessor.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/landsat.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/processor/loader.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/providers/base.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/_ost.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/_sarxarray.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/sar/processor.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/file_utils.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/geo_utils.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/logging_setup.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/retry_handler.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/utils/validators.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch/viz/__init__.py +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/dependency_links.txt +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/entry_points.txt +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/not-zip-safe +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/pygeofetch.egg-info/top_level.txt +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/setup.cfg +0 -0
- {pygeofetch-2.6.2 → pygeofetch-2.6.2.1}/tests/test_viz_plot.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to PyGeoFetch are documented here.
|
|
4
|
-
Format follows [Keep a Changelog](https://keepachangelog.com/en/2.6.2/).
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/2.6.2.1/).
|
|
5
5
|
|
|
6
|
-
## [2.6.2] — 2026-07-29
|
|
6
|
+
## [2.6.2.1] — 2026-07-29
|
|
7
7
|
|
|
8
8
|
### Added
|
|
9
9
|
- Major SNAPHU unwrapping improvements for urban InSAR applications
|
|
@@ -72,7 +72,7 @@ unwrapped = unwrapper.unwrap(
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
## [2.6.2] — 2026-07-12
|
|
75
|
+
## [2.6.2.1] — 2026-07-12
|
|
76
76
|
|
|
77
77
|
### Added
|
|
78
78
|
- Federated search across 22+ satellite data providers
|
|
@@ -188,8 +188,8 @@ unwrapped = unwrapper.unwrap(
|
|
|
188
188
|
|
|
189
189
|
| Version | Date | Key Changes |
|
|
190
190
|
|---------|------------|-------------|
|
|
191
|
-
| **2.6.2** | 2026-07-29 | SNAPHU unwrapping fixes, 87.1% reliable pixels |
|
|
192
|
-
| **2.6.2** | 2026-07-12 | 22 providers, 17 indices, Sentinel-1C/D |
|
|
191
|
+
| **2.6.2.1** | 2026-07-29 | SNAPHU unwrapping fixes, 87.1% reliable pixels |
|
|
192
|
+
| **2.6.2.1** | 2026-07-12 | 22 providers, 17 indices, Sentinel-1C/D |
|
|
193
193
|
| **1.9.2** | 2026-06-15 | Batch processing, parallel downloads |
|
|
194
194
|
| **1.9.1** | 2026-05-20 | 3D terrain, SNAPHU integration, time series |
|
|
195
195
|
| **1.9.0** | 2026-04-25 | Initial public release |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygeofetch
|
|
3
|
-
Version: 2.6.2
|
|
3
|
+
Version: 2.6.2.1
|
|
4
4
|
Summary: Universal satellite data pipeline + geospatial processing platform
|
|
5
5
|
Author-email: Samuel Appiah Kubi <appiahkubis14@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -121,6 +121,7 @@ Requires-Dist: pytest>=8.0; extra == "dev"
|
|
|
121
121
|
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
122
122
|
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
123
123
|
Requires-Dist: ruff>=0.3; extra == "dev"
|
|
124
|
+
Requires-Dist: black<25.0,>=24.0; extra == "dev"
|
|
124
125
|
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
125
126
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
126
127
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
@@ -135,11 +136,10 @@ Dynamic: license-file
|
|
|
135
136
|
|
|
136
137
|
<div align="center">
|
|
137
138
|
|
|
138
|
-
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/main/icon/pygeofetch_logo.png" alt="PyGeoFetch Logo" width="350">
|
|
139
|
-
|
|
140
139
|
[](https://pypi.org/project/pygeofetch/)
|
|
141
140
|
[](https://pypi.org/project/pygeofetch/)
|
|
142
141
|
[](https://opensource.org/licenses/MIT)
|
|
142
|
+
[](https://doi.org/10.5281/zenodo.22087230)
|
|
143
143
|
[](https://github.com/appiahkubis14/pygeofetch/actions)
|
|
144
144
|
[](https://codecov.io/gh/appiahkubis14/pygeofetch)
|
|
145
145
|
|
|
@@ -147,6 +147,10 @@ Dynamic: license-file
|
|
|
147
147
|
|
|
148
148
|
[Quick Start](#-quick-start) · [Mexico City Case Study](#-case-study-mapping-mexico-city-land-subsidence-with-insar) · [Case Study Repo ↗](https://github.com/EOCoreINT/mexico-subsidence-project) · [Documentation](https://appiahkubis14.github.io/pygeofetch-docs/) · [Notebooks](#-notebooks)
|
|
149
149
|
|
|
150
|
+
<br>
|
|
151
|
+
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/main/icon/pygeofetch_logo.png" alt="PyGeoFetch Logo" width="350">
|
|
152
|
+
<br>
|
|
153
|
+
|
|
150
154
|
</div>
|
|
151
155
|
|
|
152
156
|
---
|
|
@@ -213,7 +217,7 @@ Every provider has been directly verified to return at minimum a correct boundin
|
|
|
213
217
|
pip install pygeofetch # core — free providers work immediately
|
|
214
218
|
pip install "pygeofetch[geo]" # + rasterio, geopandas, shapely
|
|
215
219
|
pip install "pygeofetch[insar]" # + native SBAS inversion, phase unwrapping
|
|
216
|
-
pip install "pygeofetch[insar-full]" # +
|
|
220
|
+
pip install "pygeofetch[insar-full]" # + For Linux Users
|
|
217
221
|
pip install "pygeofetch[all]" # everything
|
|
218
222
|
```
|
|
219
223
|
|
|
@@ -365,29 +369,22 @@ Run `pygeofetch --help` or any subcommand with `--help` for the complete, curren
|
|
|
365
369
|
|
|
366
370
|
---
|
|
367
371
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
| `provider_search_footprint_test.ipynb` | Search and footprint-map validation across all 24 providers |
|
|
385
|
-
|
|
386
|
-
```bash
|
|
387
|
-
cd notebooks/ && jupyter lab
|
|
388
|
-
``` -->
|
|
389
|
-
|
|
390
|
-
---
|
|
372
|
+
## 📚 Citation
|
|
373
|
+
|
|
374
|
+
If you use PyGeoFetch in your research or operational work, please cite it using the following:
|
|
375
|
+
|
|
376
|
+
### Software Citation
|
|
377
|
+
```bibtex
|
|
378
|
+
@software{pygeofetch_2026,
|
|
379
|
+
author = {Appiah Kubi, Samuel},
|
|
380
|
+
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
381
|
+
year = {2026},
|
|
382
|
+
publisher = {Zenodo},
|
|
383
|
+
version = {2.6.2.1},
|
|
384
|
+
doi = {10.5281/zenodo.22087230},
|
|
385
|
+
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
391
388
|
|
|
392
389
|
## Documentation
|
|
393
390
|
|
|
@@ -400,7 +397,7 @@ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Good first
|
|
|
400
397
|
```bash
|
|
401
398
|
git clone git@github.com:EOCoreINT/pygeofetch.git && cd pygeofetch
|
|
402
399
|
pip install -e ".[dev,all]" && pytest tests/unit/ -v
|
|
403
|
-
|
|
400
|
+
|
|
404
401
|
|
|
405
402
|
## License
|
|
406
403
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/main/icon/pygeofetch_logo.png" alt="PyGeoFetch Logo" width="350">
|
|
4
|
-
|
|
5
3
|
[](https://pypi.org/project/pygeofetch/)
|
|
6
4
|
[](https://pypi.org/project/pygeofetch/)
|
|
7
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://doi.org/10.5281/zenodo.22087230)
|
|
8
7
|
[](https://github.com/appiahkubis14/pygeofetch/actions)
|
|
9
8
|
[](https://codecov.io/gh/appiahkubis14/pygeofetch)
|
|
10
9
|
|
|
@@ -12,6 +11,10 @@
|
|
|
12
11
|
|
|
13
12
|
[Quick Start](#-quick-start) · [Mexico City Case Study](#-case-study-mapping-mexico-city-land-subsidence-with-insar) · [Case Study Repo ↗](https://github.com/EOCoreINT/mexico-subsidence-project) · [Documentation](https://appiahkubis14.github.io/pygeofetch-docs/) · [Notebooks](#-notebooks)
|
|
14
13
|
|
|
14
|
+
<br>
|
|
15
|
+
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/main/icon/pygeofetch_logo.png" alt="PyGeoFetch Logo" width="350">
|
|
16
|
+
<br>
|
|
17
|
+
|
|
15
18
|
</div>
|
|
16
19
|
|
|
17
20
|
---
|
|
@@ -78,7 +81,7 @@ Every provider has been directly verified to return at minimum a correct boundin
|
|
|
78
81
|
pip install pygeofetch # core — free providers work immediately
|
|
79
82
|
pip install "pygeofetch[geo]" # + rasterio, geopandas, shapely
|
|
80
83
|
pip install "pygeofetch[insar]" # + native SBAS inversion, phase unwrapping
|
|
81
|
-
pip install "pygeofetch[insar-full]" # +
|
|
84
|
+
pip install "pygeofetch[insar-full]" # + For Linux Users
|
|
82
85
|
pip install "pygeofetch[all]" # everything
|
|
83
86
|
```
|
|
84
87
|
|
|
@@ -230,29 +233,22 @@ Run `pygeofetch --help` or any subcommand with `--help` for the complete, curren
|
|
|
230
233
|
|
|
231
234
|
---
|
|
232
235
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| `provider_search_footprint_test.ipynb` | Search and footprint-map validation across all 24 providers |
|
|
250
|
-
|
|
251
|
-
```bash
|
|
252
|
-
cd notebooks/ && jupyter lab
|
|
253
|
-
``` -->
|
|
254
|
-
|
|
255
|
-
---
|
|
236
|
+
## 📚 Citation
|
|
237
|
+
|
|
238
|
+
If you use PyGeoFetch in your research or operational work, please cite it using the following:
|
|
239
|
+
|
|
240
|
+
### Software Citation
|
|
241
|
+
```bibtex
|
|
242
|
+
@software{pygeofetch_2026,
|
|
243
|
+
author = {Appiah Kubi, Samuel},
|
|
244
|
+
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
245
|
+
year = {2026},
|
|
246
|
+
publisher = {Zenodo},
|
|
247
|
+
version = {2.6.2.1},
|
|
248
|
+
doi = {10.5281/zenodo.22087230},
|
|
249
|
+
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
256
252
|
|
|
257
253
|
## Documentation
|
|
258
254
|
|
|
@@ -265,7 +261,7 @@ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Good first
|
|
|
265
261
|
```bash
|
|
266
262
|
git clone git@github.com:EOCoreINT/pygeofetch.git && cd pygeofetch
|
|
267
263
|
pip install -e ".[dev,all]" && pytest tests/unit/ -v
|
|
268
|
-
|
|
264
|
+
|
|
269
265
|
|
|
270
266
|
## License
|
|
271
267
|
|
|
@@ -221,8 +221,11 @@ def auth_remove(provider: str, yes: bool) -> None:
|
|
|
221
221
|
click.confirm(f"Remove credentials for {provider!r}?", abort=True)
|
|
222
222
|
|
|
223
223
|
mgr = AuthManager()
|
|
224
|
-
mgr.
|
|
225
|
-
|
|
224
|
+
removed = mgr.remove(provider)
|
|
225
|
+
if removed:
|
|
226
|
+
console.print(f"[green]Credentials for [bold]{provider}[/] removed.[/]")
|
|
227
|
+
else:
|
|
228
|
+
console.print(f"[yellow]No stored credentials found for {provider!r}.[/]")
|
|
226
229
|
|
|
227
230
|
|
|
228
231
|
@auth.command(name="export")
|
|
@@ -250,7 +253,7 @@ def auth_export(provider: str | None, output: str) -> None:
|
|
|
250
253
|
from pygeofetch.core.authenticator import AuthManager
|
|
251
254
|
|
|
252
255
|
mgr = AuthManager()
|
|
253
|
-
data = mgr.export_credentials(
|
|
256
|
+
data = mgr.export_credentials(providers=[provider] if provider else None)
|
|
254
257
|
|
|
255
258
|
with open(output, "w") as f:
|
|
256
259
|
json.dump(data, f, indent=2, default=str)
|
|
@@ -484,9 +484,11 @@ def providers_info(provider_id: str, as_json: bool) -> None:
|
|
|
484
484
|
table.add_row("Auth type", caps.auth_type)
|
|
485
485
|
table.add_row(
|
|
486
486
|
"Auth status",
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
487
|
+
(
|
|
488
|
+
f"[green]{auth_status}[/]"
|
|
489
|
+
if "✓" in auth_status or "🌐" in auth_status
|
|
490
|
+
else f"[red]{auth_status}[/]"
|
|
491
|
+
),
|
|
490
492
|
)
|
|
491
493
|
table.add_row("Satellites", ", ".join(caps.satellites) if caps.satellites else "—")
|
|
492
494
|
table.add_row("SAR support", "[green]Yes[/]" if caps.supports_sar else "No")
|
|
@@ -495,9 +497,11 @@ def providers_info(provider_id: str, as_json: bool) -> None:
|
|
|
495
497
|
table.add_row("CQL2 filter", "[green]Yes[/]" if caps.supports_cql2 else "No")
|
|
496
498
|
table.add_row(
|
|
497
499
|
"Resolution",
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
(
|
|
501
|
+
f"{caps.resolution_min_m}m – {caps.resolution_max_m}m"
|
|
502
|
+
if caps.resolution_min_m
|
|
503
|
+
else "Varies"
|
|
504
|
+
),
|
|
501
505
|
)
|
|
502
506
|
table.add_row("Regions", ", ".join(caps.regions) if caps.regions else "global")
|
|
503
507
|
table.add_row("Endpoint", caps.endpoint_url or "—")
|
|
@@ -33,22 +33,51 @@ console = Console()
|
|
|
33
33
|
logger = logging.getLogger("pygeofetch.cli.monitor")
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
@click.group(
|
|
36
|
+
@click.group(
|
|
37
|
+
name="monitor", help="Automated, incremental InSAR monitoring (Feature 4)."
|
|
38
|
+
)
|
|
37
39
|
def monitor():
|
|
38
40
|
pass
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
@monitor.command(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@click.option(
|
|
46
|
-
|
|
43
|
+
@monitor.command(
|
|
44
|
+
name="run",
|
|
45
|
+
help="Run one real monitoring cycle: search for new scenes, plan incremental processing.",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--state-db",
|
|
49
|
+
required=True,
|
|
50
|
+
type=click.Path(),
|
|
51
|
+
help="Path to this project's real state database.",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--bbox",
|
|
55
|
+
required=True,
|
|
56
|
+
type=str,
|
|
57
|
+
help="Real AOI bounding box, 'minlon,minlat,maxlon,maxlat'.",
|
|
58
|
+
)
|
|
59
|
+
@click.option(
|
|
60
|
+
"--satellites",
|
|
61
|
+
default="Sentinel-1A,Sentinel-1B",
|
|
62
|
+
help="Comma-separated real satellite names.",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--n-neighbors",
|
|
66
|
+
default=3,
|
|
67
|
+
type=int,
|
|
68
|
+
help="Real neighbors each new date connects to (spec default: 3).",
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--cron-daily",
|
|
72
|
+
is_flag=True,
|
|
73
|
+
help="Marker flag for scheduled invocation (real cron job); does not change behavior, documents intent in logs.",
|
|
74
|
+
)
|
|
47
75
|
@click.pass_context
|
|
48
76
|
def monitor_run(ctx, state_db, bbox, satellites, n_neighbors, cron_daily):
|
|
49
77
|
client = ctx.obj.get("client") if ctx.obj else None
|
|
50
78
|
if client is None:
|
|
51
79
|
from pygeofetch import PyGeoFetch
|
|
80
|
+
|
|
52
81
|
client = PyGeoFetch()
|
|
53
82
|
|
|
54
83
|
state = ProjectState(state_db)
|
|
@@ -66,7 +95,9 @@ def monitor_run(ctx, state_db, bbox, satellites, n_neighbors, cron_daily):
|
|
|
66
95
|
if state.last_download_timestamp:
|
|
67
96
|
query.start_date = state.last_download_timestamp
|
|
68
97
|
|
|
69
|
-
console.print(
|
|
98
|
+
console.print(
|
|
99
|
+
f"[bold]pygeofetch monitor[/bold] — {'scheduled' if cron_daily else 'manual'} run"
|
|
100
|
+
)
|
|
70
101
|
console.print(f"State: {state_db}")
|
|
71
102
|
|
|
72
103
|
try:
|
|
@@ -76,7 +107,9 @@ def monitor_run(ctx, state_db, bbox, satellites, n_neighbors, cron_daily):
|
|
|
76
107
|
console.print(f"[red]Search failed:[/red] {exc}")
|
|
77
108
|
raise
|
|
78
109
|
|
|
79
|
-
available_dates = sorted(
|
|
110
|
+
available_dates = sorted(
|
|
111
|
+
{str(r.datetime)[:10] for r in results if getattr(r, "datetime", None)}
|
|
112
|
+
)
|
|
80
113
|
plan = plan_monitoring_run(state, available_dates, n_neighbors=n_neighbors)
|
|
81
114
|
|
|
82
115
|
console.print(plan.message)
|
|
@@ -84,11 +117,13 @@ def monitor_run(ctx, state_db, bbox, satellites, n_neighbors, cron_daily):
|
|
|
84
117
|
console.print(f" New dates: {plan.new_dates}")
|
|
85
118
|
console.print(f" New pairs needed: {len(plan.new_pairs)}")
|
|
86
119
|
|
|
87
|
-
state.record_run(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
state.record_run(
|
|
121
|
+
RunSummary(
|
|
122
|
+
status="success" if plan.network_changed else "no_new_scenes",
|
|
123
|
+
n_new_scenes=len(plan.new_dates),
|
|
124
|
+
detail=plan.message,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
92
127
|
|
|
93
128
|
return plan
|
|
94
129
|
|
|
@@ -67,7 +67,7 @@ def search() -> None:
|
|
|
67
67
|
@click.option(
|
|
68
68
|
"--polarisation",
|
|
69
69
|
default=None,
|
|
70
|
-
help=
|
|
70
|
+
help="SAR polarisation e.g. VV, VH, HH, HV. Same Copernicus-only caveat as --product-type.",
|
|
71
71
|
)
|
|
72
72
|
@click.option("--max-results", "-n", default=100, show_default=True)
|
|
73
73
|
@click.option(
|
|
@@ -447,9 +447,11 @@ class AuthManager:
|
|
|
447
447
|
{
|
|
448
448
|
"provider": provider,
|
|
449
449
|
"has_session": session is not None and session.is_valid,
|
|
450
|
-
"session_expires":
|
|
451
|
-
|
|
452
|
-
|
|
450
|
+
"session_expires": (
|
|
451
|
+
session.expires_at.isoformat()
|
|
452
|
+
if session and session.expires_at
|
|
453
|
+
else None
|
|
454
|
+
),
|
|
453
455
|
}
|
|
454
456
|
)
|
|
455
457
|
return result
|
|
@@ -25,7 +25,7 @@ import hashlib
|
|
|
25
25
|
import time
|
|
26
26
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict
|
|
28
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, cast
|
|
29
29
|
|
|
30
30
|
from pygeofetch.core.logging import DownloadProgress, get_logger
|
|
31
31
|
from pygeofetch.models.download_task import (
|
|
@@ -365,16 +365,19 @@ class AdaptiveDownloader:
|
|
|
365
365
|
sum(1 for r in results if r and r.success)
|
|
366
366
|
sum(1 for r in results if r and not r.success)
|
|
367
367
|
dp.__exit__(None, None, None)
|
|
368
|
-
assert len(results) == len(
|
|
369
|
-
|
|
370
|
-
)
|
|
368
|
+
assert len(results) == len(
|
|
369
|
+
data_list
|
|
370
|
+
), f"BUG: result count {len(results)} != input count {len(data_list)}"
|
|
371
371
|
sum(
|
|
372
372
|
r.bytes_downloaded / (1024 * 1024)
|
|
373
373
|
for r in results
|
|
374
374
|
if r and r.success and r.bytes_downloaded
|
|
375
375
|
)
|
|
376
376
|
# summary printed by DownloadProgress footer
|
|
377
|
-
|
|
377
|
+
# The loop above has already replaced every None slot with a
|
|
378
|
+
# real DownloadResult (failure placeholder), so this is
|
|
379
|
+
# genuinely list[DownloadResult] at this point.
|
|
380
|
+
return cast(List[DownloadResult], results)
|
|
378
381
|
|
|
379
382
|
def _get_provider(self, provider_id: str) -> Any:
|
|
380
383
|
"""Get a provider instance with a fresh authenticated session."""
|
|
@@ -889,7 +892,7 @@ class AdaptiveDownloader:
|
|
|
889
892
|
from pygeofetch.processor import SpectralIndex
|
|
890
893
|
except ImportError as exc:
|
|
891
894
|
raise ImportError(
|
|
892
|
-
f
|
|
895
|
+
f"{index_name} requires the processor extra: "
|
|
893
896
|
f'pip install "pygeofetch[processor]"'
|
|
894
897
|
) from exc
|
|
895
898
|
|
|
@@ -904,7 +907,10 @@ class AdaptiveDownloader:
|
|
|
904
907
|
(
|
|
905
908
|
p
|
|
906
909
|
for p in result.output_paths
|
|
907
|
-
if any(
|
|
910
|
+
if any(
|
|
911
|
+
pat in p.name or pat.lower() in p.name.lower()
|
|
912
|
+
for pat in patterns
|
|
913
|
+
)
|
|
908
914
|
),
|
|
909
915
|
None,
|
|
910
916
|
)
|
|
@@ -914,8 +920,15 @@ class AdaptiveDownloader:
|
|
|
914
920
|
f"{band_name} (looked for {patterns} in filenames)"
|
|
915
921
|
)
|
|
916
922
|
with rasterio.open(path) as src:
|
|
917
|
-
arr =
|
|
918
|
-
|
|
923
|
+
arr = (
|
|
924
|
+
src.read(
|
|
925
|
+
1,
|
|
926
|
+
out_shape=ref_shape,
|
|
927
|
+
resampling=rasterio.enums.Resampling.bilinear,
|
|
928
|
+
)
|
|
929
|
+
if ref_shape
|
|
930
|
+
else src.read(1)
|
|
931
|
+
)
|
|
919
932
|
if ref_shape is None:
|
|
920
933
|
ref_shape = arr.shape
|
|
921
934
|
profile = src.profile.copy()
|
|
@@ -929,6 +942,12 @@ class AdaptiveDownloader:
|
|
|
929
942
|
arr = np.where(arr == src.nodata, np.nan, arr)
|
|
930
943
|
band_arrays[band_name] = arr
|
|
931
944
|
|
|
945
|
+
assert ref_shape is not None and profile is not None, (
|
|
946
|
+
f"{index_name}: required_bands was empty or the loop never "
|
|
947
|
+
f"ran -- ref_shape/profile must be set by the first band "
|
|
948
|
+
f"processed above."
|
|
949
|
+
)
|
|
950
|
+
|
|
932
951
|
si = SpectralIndex(prefer_spyndex=False)
|
|
933
952
|
index_arr = np.asarray(si.compute(index_name, **band_arrays), dtype=np.float32)
|
|
934
953
|
# Any band being NaN at a pixel must make the index NaN there too,
|
|
@@ -950,7 +969,6 @@ class AdaptiveDownloader:
|
|
|
950
969
|
logger.info(f"{index_name} computed → {out_path}")
|
|
951
970
|
return result
|
|
952
971
|
|
|
953
|
-
|
|
954
972
|
def _find_existing_download(
|
|
955
973
|
self, data: "SatelliteData", destination: Path
|
|
956
974
|
) -> Path | None:
|
|
@@ -1230,7 +1248,8 @@ class AdaptiveDownloader:
|
|
|
1230
1248
|
"%s has no direct CRS/transform, using its %d "
|
|
1231
1249
|
"ground control points for reprojection instead "
|
|
1232
1250
|
"(common for raw SAR/GRD delivery).",
|
|
1233
|
-
source_path.name,
|
|
1251
|
+
source_path.name,
|
|
1252
|
+
len(src_gcps),
|
|
1234
1253
|
)
|
|
1235
1254
|
elif src_crs is None:
|
|
1236
1255
|
raise RuntimeError(
|
|
@@ -1301,7 +1320,6 @@ class AdaptiveDownloader:
|
|
|
1301
1320
|
)
|
|
1302
1321
|
raise RuntimeError(msg)
|
|
1303
1322
|
|
|
1304
|
-
|
|
1305
1323
|
def _backoff(self, attempt: int, options: DownloadOptions) -> float:
|
|
1306
1324
|
"""Calculate retry delay based on strategy."""
|
|
1307
1325
|
import random
|
|
@@ -1334,4 +1352,4 @@ class AdaptiveDownloader:
|
|
|
1334
1352
|
@property
|
|
1335
1353
|
def _result_total_size_mb(self) -> float:
|
|
1336
1354
|
"""Property used by result objects."""
|
|
1337
|
-
return 0.0
|
|
1355
|
+
return 0.0
|
|
@@ -484,4 +484,4 @@ class PyGeoFetch:
|
|
|
484
484
|
|
|
485
485
|
def __repr__(self) -> str:
|
|
486
486
|
authed = [item["provider"] for item in self.auth.list()]
|
|
487
|
-
return f"PyGeoFetch(authenticated={authed}, cache_entries={len(self.searcher.cache._cache)})"
|
|
487
|
+
return f"PyGeoFetch(authenticated={authed}, cache_entries={len(self.searcher.cache._cache)})"
|
|
@@ -81,7 +81,8 @@ def generate_incremental_pairs(
|
|
|
81
81
|
)
|
|
82
82
|
neighbors = candidates_by_distance[:n_neighbors]
|
|
83
83
|
for neighbor in neighbors:
|
|
84
|
-
|
|
84
|
+
a, b = sorted([new_date, neighbor])
|
|
85
|
+
pair: Tuple[str, str] = (a, b)
|
|
85
86
|
if pair not in seen:
|
|
86
87
|
seen.add(pair)
|
|
87
88
|
pairs.append(pair)
|
|
@@ -136,18 +137,28 @@ def plan_monitoring_run(
|
|
|
136
137
|
|
|
137
138
|
if not new_dates:
|
|
138
139
|
return MonitoringRunResult(
|
|
139
|
-
new_dates=[],
|
|
140
|
+
new_dates=[],
|
|
141
|
+
new_pairs=[],
|
|
142
|
+
network_changed=False,
|
|
143
|
+
ran_inversion=False,
|
|
140
144
|
message="No new dates since last run -- nothing to do.",
|
|
141
145
|
)
|
|
142
146
|
|
|
143
|
-
new_pairs = generate_incremental_pairs(
|
|
147
|
+
new_pairs = generate_incremental_pairs(
|
|
148
|
+
new_dates, sorted(already_processed), n_neighbors
|
|
149
|
+
)
|
|
144
150
|
|
|
145
151
|
logger.info(
|
|
146
152
|
"Monitoring run: %d new date(s), %d new pair(s) needed (%d neighbors each).",
|
|
147
|
-
len(new_dates),
|
|
153
|
+
len(new_dates),
|
|
154
|
+
len(new_pairs),
|
|
155
|
+
n_neighbors,
|
|
148
156
|
)
|
|
149
157
|
|
|
150
158
|
return MonitoringRunResult(
|
|
151
|
-
new_dates=new_dates,
|
|
159
|
+
new_dates=new_dates,
|
|
160
|
+
new_pairs=new_pairs,
|
|
161
|
+
network_changed=True,
|
|
162
|
+
ran_inversion=False,
|
|
152
163
|
message=f"{len(new_dates)} new date(s), {len(new_pairs)} new pair(s) planned.",
|
|
153
164
|
)
|
|
@@ -127,7 +127,9 @@ class ProjectState:
|
|
|
127
127
|
|
|
128
128
|
# ── processed dates ─────────────────────────────────────────────
|
|
129
129
|
|
|
130
|
-
def mark_dates_processed(
|
|
130
|
+
def mark_dates_processed(
|
|
131
|
+
self, dates: List[str], scene_ids: Optional[List[Optional[str]]] = None
|
|
132
|
+
):
|
|
131
133
|
"""Real, idempotent insert -- re-marking an already-processed date is a no-op, not an error."""
|
|
132
134
|
now = datetime.now(timezone.utc).isoformat()
|
|
133
135
|
scene_ids = scene_ids or [None] * len(dates)
|
|
@@ -139,12 +141,16 @@ class ProjectState:
|
|
|
139
141
|
|
|
140
142
|
def processed_dates(self) -> List[str]:
|
|
141
143
|
with self._connect() as conn:
|
|
142
|
-
rows = conn.execute(
|
|
144
|
+
rows = conn.execute(
|
|
145
|
+
"SELECT date FROM processed_dates ORDER BY date"
|
|
146
|
+
).fetchall()
|
|
143
147
|
return [r[0] for r in rows]
|
|
144
148
|
|
|
145
149
|
def is_date_processed(self, date: str) -> bool:
|
|
146
150
|
with self._connect() as conn:
|
|
147
|
-
row = conn.execute(
|
|
151
|
+
row = conn.execute(
|
|
152
|
+
"SELECT 1 FROM processed_dates WHERE date = ?", (date,)
|
|
153
|
+
).fetchone()
|
|
148
154
|
return row is not None
|
|
149
155
|
|
|
150
156
|
# ── project metadata (last_download_timestamp, network hash, reference pixel) ──
|
|
@@ -159,7 +165,9 @@ class ProjectState:
|
|
|
159
165
|
|
|
160
166
|
def _get_meta(self, key: str) -> Optional[str]:
|
|
161
167
|
with self._connect() as conn:
|
|
162
|
-
row = conn.execute(
|
|
168
|
+
row = conn.execute(
|
|
169
|
+
"SELECT value FROM project_meta WHERE key = ?", (key,)
|
|
170
|
+
).fetchone()
|
|
163
171
|
return row[0] if row else None
|
|
164
172
|
|
|
165
173
|
@property
|
|
@@ -226,7 +234,13 @@ class ProjectState:
|
|
|
226
234
|
(limit,),
|
|
227
235
|
).fetchall()
|
|
228
236
|
return [
|
|
229
|
-
{
|
|
230
|
-
|
|
237
|
+
{
|
|
238
|
+
"run_id": r[0],
|
|
239
|
+
"started_at": r[1],
|
|
240
|
+
"finished_at": r[2],
|
|
241
|
+
"status": r[3],
|
|
242
|
+
"n_new_scenes": r[4],
|
|
243
|
+
"detail": r[5],
|
|
244
|
+
}
|
|
231
245
|
for r in rows
|
|
232
246
|
]
|