PyGeoFetch 2.6.4.0__tar.gz → 2.6.5.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.
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/CHANGELOG.md +7 -7
- {pygeofetch-2.6.4.0/pygeofetch.egg-info → pygeofetch-2.6.5.0}/PKG-INFO +3 -3
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/README.md +1 -1
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/__init__.py +1 -1
- pygeofetch-2.6.5.0/pygeofetch/insar/network_thinning.py +285 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/preflight.py +130 -1
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/meta.yaml +1 -1
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0/pygeofetch.egg-info}/PKG-INFO +3 -3
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/SOURCES.txt +2 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pyproject.toml +2 -2
- pygeofetch-2.6.5.0/tests/test_network_thinning.py +144 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/LICENSE +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/MANIFEST.in +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/auth_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/classify_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/config_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/download_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/index_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/main.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/monitor_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/multisensor_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/optical_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/pipeline_process_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/postprocess_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/preprocess_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/sar_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/scale_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/search_commands.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/defaults.yaml +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/settings.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/authenticator.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/cache_manager.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/chunked_processor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/geoparquet_io.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/zarr_io.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_organizer.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/downloader.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/engine.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/logging.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/monitor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/orbits.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/progress.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/scheduler.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/searcher.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/state.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/geotech/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/geotech/risk_mapper.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/advanced_safeguards.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/analysis.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/annotation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/atmosphere.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/auto_fallback.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/coregister.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/deburst.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/esd.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/extraction.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/feasibility.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/flatearth.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/geolocation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/gpu.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/interferogram.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/ionosphere.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/offset_geometry.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/offset_tracking.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/provenance.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/ps_selection.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/stack_selection.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/synthetic.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/timeseries.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/unwrap.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/validate.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/visualize.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/workflow.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/download_task.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/satellite_data.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/search_query.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/stream_options.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/user_auth.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/_init.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/dem_differencing.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/flood_mapping.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/optical/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/optical/offset_tracking.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/base.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/batch.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/classification.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/indices.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/postprocessor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/preprocessor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/sar.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/uq_exporter.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/indices.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/landsat.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/loader.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/stacker.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/timeseries.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/airbus_oneatlas.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/aws_earth.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/base.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/copernicus.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/copernicus_nodes.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/digitalglobe.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/earth_explorer_additional.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/element84.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/eodag_provider.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/esa_scihub.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/geoserver_generic.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/google_earth_engine.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/inpe_cbers.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/isro_bhuvan.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/jaxa_earth.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/maxar_gbdx.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/nasa_earthdata.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/noaa_big_data.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/opentopography.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/planet.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/planetary_computer.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/sentinel_hub.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/terrabotics.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/usgs.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_native.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_ost.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_sarxarray.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/extraction.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/math.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/pipelines.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/processor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/burst_streaming.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/cog_streamer.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/window_calculator.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/scene.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/windowed.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/file_utils.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/gcp_recovery.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/geo_utils.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/geocoding.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/logging_setup.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/retry_handler.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/sar_math.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/validators.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/validation/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/validation/optical_validator.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/__init__.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/map.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/plot.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/dependency_links.txt +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/entry_points.txt +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/not-zip-safe +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/requires.txt +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/top_level.txt +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/setup.cfg +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_advanced_safeguards.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_airbus_oneatlas.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_alaska_satellite_facility.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_atmospheric_circular_regression.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_auto_fallback.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_auto_fallback_fixed.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_build_sbas_network-new.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_build_sbas_network.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_metadata.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_processing_integration.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_sync_large_gap.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_sync_prescreening.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_synchronization.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_circuit_breaker_wiring.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_cli_optical_validation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_copernicus_nodes.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_integration.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_upgrades (1).py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_upgrades.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_credential_encryption.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_data_organizer.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_dem_differencing_pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_digitalglobe.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_earth_explorer_additional.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_era5_atmospheric_correction.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_esa_scihub.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_extraction.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_flat_earth_phase.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_flood_mapping_pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_gcp_reprojection.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_geology_indices_and_rvi_fix.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_geoserver_generic.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_google_earth_engine.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_inpe_cbers.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_insar.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_insar_optical_fusion_pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_ionosphere.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_isro_bhuvan.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_jaxa_earth.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_lightweight_prefetch.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_map_preview_consolidation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_models.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_monitor.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_nasa_earthdata_public_search.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_noaa_big_data.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_offset_tracking.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_opentopography_pointcloud_usgs.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_offset_tracking.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validation_wiring.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validator.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validator_stac.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_pgv_integration_contract.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_pipeline_process_export.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_planet_asset_type_fix.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_gate.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_quiet.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_spatial_baseline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_provider_geometry_audit.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_provider_scene_disambiguation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_providers.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_ps_selection.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_raster_collocation_integration.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_reference_pixel_and_despike.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_risk_mapper.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_risk_mapping.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sar_pipelines.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_screen_stack_orchestration.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_search_download.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sentinel_hub_catalog_fix.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sign_convention.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_stack_selection_consolidation.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_state.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_strain_and_fusion.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_streaming.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_terrain_corrected_change_pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_topo_r2_bug.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_uq_exporter.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_utils.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_vegetation_disturbance_pipeline.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_viz_plot.py +0 -0
- {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_wls_inversion.py +0 -0
|
@@ -20,7 +20,7 @@ date, and marked accordingly rather than filled in with invented
|
|
|
20
20
|
bullet points.
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
## [Unreleased] — v2.6.
|
|
23
|
+
## [Unreleased] — v2.6.5.0.4
|
|
24
24
|
|
|
25
25
|
```{note}
|
|
26
26
|
Not yet published to PyPI. This entry documents work completed and
|
|
@@ -130,7 +130,7 @@ audit and remain open for a follow-up pass.
|
|
|
130
130
|
|
|
131
131
|
---
|
|
132
132
|
|
|
133
|
-
## [2.6.
|
|
133
|
+
## [2.6.5.0.4] — 2026-08-25 (latest)
|
|
134
134
|
|
|
135
135
|
Confirmed via direct fetch of the current PyPI listing.
|
|
136
136
|
|
|
@@ -149,7 +149,7 @@ Confirmed via direct fetch of the current PyPI listing.
|
|
|
149
149
|
- `SearchQuery.sort_ascending: bool` — earlier examples showed a `sort_order: "asc"/"desc"` string field that doesn't exist on the model (the CLI's `--sort-order` flag does convert to this internally, so CLI docs were unaffected)
|
|
150
150
|
- `DownloadOptions.post_process` requires a list of `PostProcessAction` objects — passing plain strings raises a real `pydantic.ValidationError`
|
|
151
151
|
|
|
152
|
-
## [2.6.
|
|
152
|
+
## [2.6.5.0] — 2026-08-24
|
|
153
153
|
Real, verified release. Detailed notes not independently recovered from PyPI's release-history page (no distinct per-version notes exposed for this release specifically).
|
|
154
154
|
|
|
155
155
|
## [2.6.1] — 2026-08-22
|
|
@@ -261,9 +261,9 @@ recovered.
|
|
|
261
261
|
|
|
262
262
|
```{note}
|
|
263
263
|
The previous version of this changelog had an entry labeled
|
|
264
|
-
**"2.6.
|
|
264
|
+
**"2.6.5.0.4 — 2026-07-12"** describing 22+ providers, Sentinel-1C/1D
|
|
265
265
|
support, SLC search with provider routing, precise orbit file
|
|
266
|
-
management, 17 spectral indices, and various bug fixes. **2.6.
|
|
266
|
+
management, 17 spectral indices, and various bug fixes. **2.6.5.0.4 was
|
|
267
267
|
only ever released once, on 2026-08-25** — it was never released on
|
|
268
268
|
2026-07-12. The date matches this real release, **1.0.8**, exactly.
|
|
269
269
|
That content is reproduced below under the version it actually
|
|
@@ -406,8 +406,8 @@ The version of this file uploaded for this update had **5 entries
|
|
|
406
406
|
covering 5 dates**. PyPI's real release history has **54 releases**.
|
|
407
407
|
Specific issues found and fixed:
|
|
408
408
|
|
|
409
|
-
1. **Two different entries were both labeled `2.6.
|
|
410
|
-
2026-07-29 and 2026-07-12. **2.6.
|
|
409
|
+
1. **Two different entries were both labeled `2.6.5.0.4`**, dated
|
|
410
|
+
2026-07-29 and 2026-07-12. **2.6.5.0.4 was only ever released once**,
|
|
411
411
|
on 2026-08-25 (confirmed directly from the live PyPI listing).
|
|
412
412
|
2. **The 2026-07-12 entry's date exactly matches a real release**
|
|
413
413
|
— version **1.0.8** — and its content (22+ providers, Sentinel-1C/D,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygeofetch
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.5.0
|
|
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
|
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
28
|
Project-URL: Homepage, https://www.eocoreint.com/
|
|
29
|
-
Project-URL: Documentation, https://
|
|
29
|
+
Project-URL: Documentation, https://pygeofetch.readthedocs.io/en/latest/
|
|
30
30
|
Project-URL: Repository, https://github.com/EOCoreINT/pygeofetch
|
|
31
31
|
Project-URL: Bug Tracker, https://github.com/EOCoreINT/pygeofetch/issues
|
|
32
32
|
Keywords: satellite,remote-sensing,GIS,earth-observation,geospatial,SAR,Sentinel,Landsat
|
|
@@ -371,7 +371,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
|
|
|
371
371
|
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
372
372
|
year = {2026},
|
|
373
373
|
publisher = {Zenodo},
|
|
374
|
-
version = {2.6.
|
|
374
|
+
version = {2.6.5.0.1},
|
|
375
375
|
doi = {10.5281/zenodo.22087230},
|
|
376
376
|
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
377
377
|
}
|
|
@@ -229,7 +229,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
|
|
|
229
229
|
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
230
230
|
year = {2026},
|
|
231
231
|
publisher = {Zenodo},
|
|
232
|
-
version = {2.6.
|
|
232
|
+
version = {2.6.5.0.1},
|
|
233
233
|
doi = {10.5281/zenodo.22087230},
|
|
234
234
|
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
235
235
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Download-budget thinning that never breaks the interferometric network.
|
|
3
|
+
|
|
4
|
+
A full Sentinel-1 SLC is ~7.5 GB, so a 4-year, 6-day-repeat stack is >1.6 TB.
|
|
5
|
+
Most time-series work does not need every acquisition, so this module picks an
|
|
6
|
+
evenly spaced subset of acquisition *dates* -- and guarantees the result is
|
|
7
|
+
still a connected SBAS network:
|
|
8
|
+
|
|
9
|
+
* every kept date has at least one partner within ``max_temporal_baseline_days``
|
|
10
|
+
(and, where possible, ``min_redundancy`` partners);
|
|
11
|
+
* the number of disconnected components is never larger than in the full
|
|
12
|
+
stack (data gaps in the archive cannot be repaired, but thinning will not
|
|
13
|
+
create new ones);
|
|
14
|
+
* if a download budget cannot be met without disconnecting the network, the
|
|
15
|
+
network wins and the report says by how much the budget was exceeded.
|
|
16
|
+
|
|
17
|
+
Pure functions, no network, no I/O.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
from collections.abc import Sequence
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from datetime import date
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _d(iso: str) -> date:
|
|
29
|
+
return date.fromisoformat(iso[:10])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _days(a: str, b: str) -> int:
|
|
33
|
+
return (_d(b) - _d(a)).days
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class NetworkStats:
|
|
38
|
+
"""Connectivity of the temporal-baseline-limited pair graph."""
|
|
39
|
+
|
|
40
|
+
n_dates: int
|
|
41
|
+
n_pairs: int
|
|
42
|
+
n_components: int
|
|
43
|
+
min_redundancy: int # smallest number of partners any date has
|
|
44
|
+
weak_dates: list[str] = field(default_factory=list) # dates below target
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ThinningResult:
|
|
49
|
+
kept: list[str]
|
|
50
|
+
dropped: list[str]
|
|
51
|
+
requested_days: int | None
|
|
52
|
+
effective_days: int | None
|
|
53
|
+
gb_per_scene: float
|
|
54
|
+
gb_before: float
|
|
55
|
+
gb_after: float
|
|
56
|
+
max_gb: float | None
|
|
57
|
+
budget_met: bool
|
|
58
|
+
stats_before: NetworkStats
|
|
59
|
+
stats_after: NetworkStats
|
|
60
|
+
notes: list[str] = field(default_factory=list)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def changed(self) -> bool:
|
|
64
|
+
return bool(self.dropped)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def network_stats(
|
|
68
|
+
dates: Sequence[str],
|
|
69
|
+
max_temporal_baseline_days: int,
|
|
70
|
+
min_redundancy: int = 2,
|
|
71
|
+
) -> NetworkStats:
|
|
72
|
+
"""Union-find over pairs closer than ``max_temporal_baseline_days``."""
|
|
73
|
+
ds = sorted(set(dates))
|
|
74
|
+
n = len(ds)
|
|
75
|
+
parent = list(range(n))
|
|
76
|
+
|
|
77
|
+
def find(i: int) -> int:
|
|
78
|
+
while parent[i] != i:
|
|
79
|
+
parent[i] = parent[parent[i]]
|
|
80
|
+
i = parent[i]
|
|
81
|
+
return i
|
|
82
|
+
|
|
83
|
+
degree = [0] * n
|
|
84
|
+
pairs = 0
|
|
85
|
+
ords = [_d(x).toordinal() for x in ds]
|
|
86
|
+
for i in range(n):
|
|
87
|
+
j = i + 1
|
|
88
|
+
while j < n and ords[j] - ords[i] <= max_temporal_baseline_days:
|
|
89
|
+
pairs += 1
|
|
90
|
+
degree[i] += 1
|
|
91
|
+
degree[j] += 1
|
|
92
|
+
ri, rj = find(i), find(j)
|
|
93
|
+
if ri != rj:
|
|
94
|
+
parent[ri] = rj
|
|
95
|
+
j += 1
|
|
96
|
+
comps = len({find(i) for i in range(n)}) if n else 0
|
|
97
|
+
weak = [ds[i] for i in range(n) if degree[i] < min_redundancy]
|
|
98
|
+
return NetworkStats(
|
|
99
|
+
n_dates=n,
|
|
100
|
+
n_pairs=pairs,
|
|
101
|
+
n_components=comps,
|
|
102
|
+
min_redundancy=min(degree) if n else 0,
|
|
103
|
+
weak_dates=weak,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _cadence_pick(dates: list[str], every_days: int) -> list[str]:
|
|
108
|
+
"""Evenly spaced subset: after each kept date take the acquisition closest
|
|
109
|
+
to ``last + every_days`` (never nearer than half a step)."""
|
|
110
|
+
if not dates or every_days <= 1:
|
|
111
|
+
return list(dates)
|
|
112
|
+
ords = [_d(x).toordinal() for x in dates]
|
|
113
|
+
kept_idx = [0]
|
|
114
|
+
while True:
|
|
115
|
+
last = ords[kept_idx[-1]]
|
|
116
|
+
target = last + every_days
|
|
117
|
+
floor = last + max(1, every_days // 2)
|
|
118
|
+
best: int | None = None
|
|
119
|
+
for j in range(kept_idx[-1] + 1, len(dates)):
|
|
120
|
+
if ords[j] < floor:
|
|
121
|
+
continue
|
|
122
|
+
if best is None or abs(ords[j] - target) < abs(ords[best] - target):
|
|
123
|
+
best = j
|
|
124
|
+
if ords[j] > target:
|
|
125
|
+
break
|
|
126
|
+
if best is None:
|
|
127
|
+
break
|
|
128
|
+
kept_idx.append(best)
|
|
129
|
+
kept = [dates[i] for i in kept_idx]
|
|
130
|
+
# keep the final acquisition so the time span is not silently shortened
|
|
131
|
+
if kept[-1] != dates[-1] and _days(kept[-1], dates[-1]) >= every_days // 2:
|
|
132
|
+
kept.append(dates[-1])
|
|
133
|
+
return kept
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _repair_connectivity(
|
|
137
|
+
kept: list[str],
|
|
138
|
+
all_dates: list[str],
|
|
139
|
+
max_tb: int,
|
|
140
|
+
target_components: int,
|
|
141
|
+
) -> tuple[list[str], list[str]]:
|
|
142
|
+
"""Add back the dropped dates that reconnect the network."""
|
|
143
|
+
kept_set = set(kept)
|
|
144
|
+
added: list[str] = []
|
|
145
|
+
while network_stats(sorted(kept_set), max_tb).n_components > target_components:
|
|
146
|
+
cur = network_stats(sorted(kept_set), max_tb).n_components
|
|
147
|
+
best, best_c = None, cur
|
|
148
|
+
for d in all_dates:
|
|
149
|
+
if d in kept_set:
|
|
150
|
+
continue
|
|
151
|
+
c = network_stats(sorted(kept_set | {d}), max_tb).n_components
|
|
152
|
+
if c < best_c:
|
|
153
|
+
best, best_c = d, c
|
|
154
|
+
if best is None:
|
|
155
|
+
# no single date helps (gap wider than 2x baseline): bridge the
|
|
156
|
+
# widest remaining gap with its midpoint-most dropped date
|
|
157
|
+
ks = sorted(kept_set)
|
|
158
|
+
gaps = [
|
|
159
|
+
(_days(a, b), a, b) for a, b in zip(ks, ks[1:]) if _days(a, b) > max_tb
|
|
160
|
+
]
|
|
161
|
+
if not gaps:
|
|
162
|
+
break
|
|
163
|
+
_, a, b = max(gaps)
|
|
164
|
+
mid = (_d(a).toordinal() + _d(b).toordinal()) / 2
|
|
165
|
+
cand = [d for d in all_dates if a < d < b and d not in kept_set]
|
|
166
|
+
if not cand:
|
|
167
|
+
break
|
|
168
|
+
best = min(cand, key=lambda d: abs(_d(d).toordinal() - mid))
|
|
169
|
+
kept_set.add(best)
|
|
170
|
+
added.append(best)
|
|
171
|
+
return sorted(kept_set), added
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def thin_dates(
|
|
175
|
+
dates: Sequence[str],
|
|
176
|
+
*,
|
|
177
|
+
max_temporal_baseline_days: int,
|
|
178
|
+
thin_to_days: int | None = None,
|
|
179
|
+
max_download_gb: float | None = None,
|
|
180
|
+
gb_per_scene: float = 7.5,
|
|
181
|
+
min_redundancy: int = 2,
|
|
182
|
+
) -> ThinningResult:
|
|
183
|
+
"""Choose a connected, evenly spaced subset of ``dates``."""
|
|
184
|
+
all_dates = sorted(set(dates))
|
|
185
|
+
before = network_stats(all_dates, max_temporal_baseline_days, min_redundancy)
|
|
186
|
+
gb_before = len(all_dates) * gb_per_scene
|
|
187
|
+
notes: list[str] = []
|
|
188
|
+
|
|
189
|
+
def finish(kept: list[str], eff: int | None, met: bool) -> ThinningResult:
|
|
190
|
+
kept = sorted(kept)
|
|
191
|
+
after = network_stats(kept, max_temporal_baseline_days, min_redundancy)
|
|
192
|
+
return ThinningResult(
|
|
193
|
+
kept=kept,
|
|
194
|
+
dropped=[d for d in all_dates if d not in set(kept)],
|
|
195
|
+
requested_days=thin_to_days,
|
|
196
|
+
effective_days=eff,
|
|
197
|
+
gb_per_scene=gb_per_scene,
|
|
198
|
+
gb_before=gb_before,
|
|
199
|
+
gb_after=len(kept) * gb_per_scene,
|
|
200
|
+
max_gb=max_download_gb,
|
|
201
|
+
budget_met=met,
|
|
202
|
+
stats_before=before,
|
|
203
|
+
stats_after=after,
|
|
204
|
+
notes=notes,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if len(all_dates) < 3 or (thin_to_days is None and max_download_gb is None):
|
|
208
|
+
return finish(all_dates, None, True)
|
|
209
|
+
|
|
210
|
+
span = _days(all_dates[0], all_dates[-1])
|
|
211
|
+
# Largest step that still gives every date `min_redundancy` partners.
|
|
212
|
+
step_cap = max(1, max_temporal_baseline_days // max(1, min_redundancy))
|
|
213
|
+
|
|
214
|
+
step = thin_to_days or 1
|
|
215
|
+
if thin_to_days and thin_to_days > step_cap:
|
|
216
|
+
notes.append(
|
|
217
|
+
f"thin_to_days={thin_to_days} lowered to {step_cap}: with a "
|
|
218
|
+
f"{max_temporal_baseline_days}-day temporal baseline a coarser "
|
|
219
|
+
f"cadence leaves dates with fewer than {min_redundancy} partners."
|
|
220
|
+
)
|
|
221
|
+
step = step_cap
|
|
222
|
+
|
|
223
|
+
budget_n: int | None = None
|
|
224
|
+
if max_download_gb is not None:
|
|
225
|
+
budget_n = max(2, int(max_download_gb // gb_per_scene))
|
|
226
|
+
needed = math.ceil(span / max(1, step)) + 1
|
|
227
|
+
if needed > budget_n:
|
|
228
|
+
step = max(step, math.ceil(span / max(1, budget_n - 1)))
|
|
229
|
+
|
|
230
|
+
# The network must stay connected: step can never exceed the baseline.
|
|
231
|
+
hard_cap = max(1, max_temporal_baseline_days)
|
|
232
|
+
if step > hard_cap:
|
|
233
|
+
notes.append(
|
|
234
|
+
f"A {step}-day cadence would disconnect the network (temporal "
|
|
235
|
+
f"baseline {max_temporal_baseline_days} d); using {hard_cap} d."
|
|
236
|
+
)
|
|
237
|
+
step = hard_cap
|
|
238
|
+
|
|
239
|
+
kept = _cadence_pick(all_dates, step)
|
|
240
|
+
kept, added = _repair_connectivity(
|
|
241
|
+
kept, all_dates, max_temporal_baseline_days, before.n_components
|
|
242
|
+
)
|
|
243
|
+
if added:
|
|
244
|
+
notes.append(
|
|
245
|
+
f"{len(added)} date(s) added back to keep the network connected: "
|
|
246
|
+
f"{', '.join(added[:5])}{' ...' if len(added) > 5 else ''}."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Trim only if over budget AND removal cannot disconnect anything.
|
|
250
|
+
if budget_n is not None and len(kept) > budget_n:
|
|
251
|
+
base = network_stats(kept, max_temporal_baseline_days).n_components
|
|
252
|
+
while len(kept) > budget_n and len(kept) > 2:
|
|
253
|
+
order = sorted(
|
|
254
|
+
range(1, len(kept) - 1),
|
|
255
|
+
key=lambda i: _days(kept[i - 1], kept[i + 1]),
|
|
256
|
+
)
|
|
257
|
+
for i in order:
|
|
258
|
+
trial = kept[:i] + kept[i + 1 :]
|
|
259
|
+
if (
|
|
260
|
+
network_stats(trial, max_temporal_baseline_days).n_components
|
|
261
|
+
<= base
|
|
262
|
+
):
|
|
263
|
+
kept = trial
|
|
264
|
+
break
|
|
265
|
+
else:
|
|
266
|
+
break
|
|
267
|
+
|
|
268
|
+
met = budget_n is None or len(kept) <= budget_n
|
|
269
|
+
if not met:
|
|
270
|
+
notes.append(
|
|
271
|
+
f"Budget of {max_download_gb:g} GB would need <= {budget_n} scenes, "
|
|
272
|
+
f"but keeping the network connected needs {len(kept)} "
|
|
273
|
+
f"({len(kept) * gb_per_scene:.0f} GB). Raise the budget or the "
|
|
274
|
+
f"temporal baseline (max_temporal_baseline_days)."
|
|
275
|
+
)
|
|
276
|
+
return finish(kept, step, met)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def date_groups(scenes: Sequence[object]) -> dict[str, list[object]]:
|
|
280
|
+
"""Group scene objects by acquisition date (``scene.datetime``)."""
|
|
281
|
+
out: dict[str, list[object]] = {}
|
|
282
|
+
for s in scenes:
|
|
283
|
+
key = str(getattr(s, "datetime", ""))[:10]
|
|
284
|
+
out.setdefault(key, []).append(s)
|
|
285
|
+
return out
|
|
@@ -212,6 +212,10 @@ class PreflightGate:
|
|
|
212
212
|
max_runtime_s: Optional[float] = 900.0,
|
|
213
213
|
log_file: Optional[Any] = None,
|
|
214
214
|
print_summary: bool = True,
|
|
215
|
+
# Download budget:
|
|
216
|
+
thin_to_days: Optional[int] = None,
|
|
217
|
+
max_download_gb: Optional[float] = None,
|
|
218
|
+
gb_per_scene: float = 7.5,
|
|
215
219
|
) -> None:
|
|
216
220
|
"""
|
|
217
221
|
Notebook-friendly options (all optional):
|
|
@@ -231,7 +235,24 @@ class PreflightGate:
|
|
|
231
235
|
per-day file in the system temp folder; the path is
|
|
232
236
|
shown in the summary and on ``report.log_file``).
|
|
233
237
|
print_summary: print ``report.summary()`` when ``run()`` finishes.
|
|
238
|
+
|
|
239
|
+
Download budget (both off by default; nothing changes unless set):
|
|
240
|
+
|
|
241
|
+
thin_to_days: keep roughly one acquisition per this many days
|
|
242
|
+
(e.g. 12 or 24) instead of every one.
|
|
243
|
+
max_download_gb: upper limit for the estimated download; the cadence
|
|
244
|
+
is coarsened until the estimate fits.
|
|
245
|
+
gb_per_scene: size used for that estimate (Sentinel-1 SLC ~7.5 GB).
|
|
246
|
+
|
|
247
|
+
Thinning always keeps the interferometric network connected (see
|
|
248
|
+
:mod:`pygeofetch.insar.network_thinning`). If a budget cannot be met
|
|
249
|
+
without disconnecting it, the network wins and the report says so.
|
|
250
|
+
It runs BEFORE the burst/orbit check, so fewer scenes also means far
|
|
251
|
+
fewer network requests.
|
|
234
252
|
"""
|
|
253
|
+
self.thin_to_days = thin_to_days
|
|
254
|
+
self.max_download_gb = max_download_gb
|
|
255
|
+
self.gb_per_scene = float(gb_per_scene)
|
|
235
256
|
self.verbose = verbose
|
|
236
257
|
self.show_progress = show_progress
|
|
237
258
|
self.max_workers = max(1, int(max_workers))
|
|
@@ -340,6 +361,10 @@ class PreflightGate:
|
|
|
340
361
|
selected, cov_issues = self._check_coverage(selected)
|
|
341
362
|
issues.extend(cov_issues)
|
|
342
363
|
|
|
364
|
+
# Check 2b: optional download budget (thin dates, keep network connected)
|
|
365
|
+
selected, thin_issues, thinning = self._apply_download_budget(selected)
|
|
366
|
+
issues.extend(thin_issues)
|
|
367
|
+
|
|
343
368
|
# Check 3: temporal sampling / predicted SBAS connectivity
|
|
344
369
|
issues.extend(self._check_temporal_network(selected))
|
|
345
370
|
|
|
@@ -348,8 +373,21 @@ class PreflightGate:
|
|
|
348
373
|
selected, burst_issues = self._screen_burst_families(selected, geometry_report)
|
|
349
374
|
issues.extend(burst_issues)
|
|
350
375
|
|
|
376
|
+
issues.extend(self._recheck_network_after_screening(selected, thinning))
|
|
377
|
+
|
|
351
378
|
# Build the provenance manifest from everything we now know
|
|
352
379
|
manifest = self._build_manifest(selected, search_report, issues)
|
|
380
|
+
if thinning is not None:
|
|
381
|
+
manifest["thinning"] = {
|
|
382
|
+
"requested_days": thinning.requested_days,
|
|
383
|
+
"effective_days": thinning.effective_days,
|
|
384
|
+
"max_download_gb": thinning.max_gb,
|
|
385
|
+
"gb_before": thinning.gb_before,
|
|
386
|
+
"gb_after": thinning.gb_after,
|
|
387
|
+
"budget_met": thinning.budget_met,
|
|
388
|
+
"dropped_dates": thinning.dropped,
|
|
389
|
+
"notes": thinning.notes,
|
|
390
|
+
}
|
|
353
391
|
|
|
354
392
|
# Decide go/no-go
|
|
355
393
|
severity = self._overall_severity(issues)
|
|
@@ -479,6 +517,97 @@ class PreflightGate:
|
|
|
479
517
|
)
|
|
480
518
|
return kept, issues
|
|
481
519
|
|
|
520
|
+
# ── Check 2b: download budget ─────────────────────────────────────────
|
|
521
|
+
def _apply_download_budget(
|
|
522
|
+
self, selected: List[Any]
|
|
523
|
+
) -> Tuple[List[Any], List[PreflightIssue], Optional[Any]]:
|
|
524
|
+
if self.thin_to_days is None and self.max_download_gb is None:
|
|
525
|
+
return selected, [], None
|
|
526
|
+
from pygeofetch.insar.network_thinning import thin_dates
|
|
527
|
+
|
|
528
|
+
dates = [str(s.datetime)[:10] for s in selected]
|
|
529
|
+
res = thin_dates(
|
|
530
|
+
dates,
|
|
531
|
+
max_temporal_baseline_days=self.max_temporal_baseline_days,
|
|
532
|
+
thin_to_days=self.thin_to_days,
|
|
533
|
+
max_download_gb=self.max_download_gb,
|
|
534
|
+
gb_per_scene=self.gb_per_scene,
|
|
535
|
+
min_redundancy=self.min_network_redundancy,
|
|
536
|
+
)
|
|
537
|
+
if not res.changed and not res.notes:
|
|
538
|
+
return selected, [], res
|
|
539
|
+
keep = set(res.kept)
|
|
540
|
+
new_sel = [s for s in selected if str(s.datetime)[:10] in keep]
|
|
541
|
+
a, b = res.stats_after, res.stats_before
|
|
542
|
+
detail = (
|
|
543
|
+
f"{len(res.kept)} of {len(set(dates))} dates kept "
|
|
544
|
+
f"(~{res.effective_days} d cadence), "
|
|
545
|
+
f"{res.gb_before:.0f} -> {res.gb_after:.0f} GB; network: "
|
|
546
|
+
f"{a.n_components} component(s) (was {b.n_components}), every "
|
|
547
|
+
f"date has >= {a.min_redundancy} partner(s) within "
|
|
548
|
+
f"{self.max_temporal_baseline_days} d."
|
|
549
|
+
)
|
|
550
|
+
issues = [
|
|
551
|
+
PreflightIssue(
|
|
552
|
+
code="DOWNLOAD_BUDGET_APPLIED",
|
|
553
|
+
severity=SEVERITY_GREEN,
|
|
554
|
+
message="Download size reduced by thinning acquisition dates.",
|
|
555
|
+
auto_fixed=True,
|
|
556
|
+
fix_detail=detail,
|
|
557
|
+
)
|
|
558
|
+
]
|
|
559
|
+
if not res.budget_met or res.notes:
|
|
560
|
+
issues.append(
|
|
561
|
+
PreflightIssue(
|
|
562
|
+
code="DOWNLOAD_BUDGET_NOTE",
|
|
563
|
+
severity=SEVERITY_YELLOW if not res.budget_met else SEVERITY_GREEN,
|
|
564
|
+
message=" ".join(res.notes),
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
if a.weak_dates and a.n_components <= b.n_components:
|
|
568
|
+
issues.append(
|
|
569
|
+
PreflightIssue(
|
|
570
|
+
code="THINNED_NETWORK_REDUNDANCY",
|
|
571
|
+
severity=SEVERITY_YELLOW,
|
|
572
|
+
message=(
|
|
573
|
+
f"{len(a.weak_dates)} date(s) have fewer than "
|
|
574
|
+
f"{self.min_network_redundancy} partners after thinning. "
|
|
575
|
+
f"The network is connected but thin; raise "
|
|
576
|
+
f"max_temporal_baseline_days or max_download_gb for "
|
|
577
|
+
f"more redundancy."
|
|
578
|
+
),
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
return new_sel, issues, res
|
|
582
|
+
|
|
583
|
+
def _recheck_network_after_screening(
|
|
584
|
+
self, selected: List[Any], thinning: Optional[Any]
|
|
585
|
+
) -> List[PreflightIssue]:
|
|
586
|
+
"""The burst check can drop dates after thinning; confirm the final
|
|
587
|
+
set is still as connected as the full archive was."""
|
|
588
|
+
if thinning is None:
|
|
589
|
+
return []
|
|
590
|
+
from pygeofetch.insar.network_thinning import network_stats
|
|
591
|
+
|
|
592
|
+
dates = sorted({str(s.datetime)[:10] for s in selected})
|
|
593
|
+
st = network_stats(
|
|
594
|
+
dates, self.max_temporal_baseline_days, self.min_network_redundancy
|
|
595
|
+
)
|
|
596
|
+
if st.n_components > thinning.stats_before.n_components:
|
|
597
|
+
return [
|
|
598
|
+
PreflightIssue(
|
|
599
|
+
code="NETWORK_DISCONNECTED_AFTER_SCREEN",
|
|
600
|
+
severity=SEVERITY_RED,
|
|
601
|
+
message=(
|
|
602
|
+
f"After burst/orbit screening the thinned network has "
|
|
603
|
+
f"{st.n_components} disconnected parts (full archive: "
|
|
604
|
+
f"{thinning.stats_before.n_components}). Use a finer "
|
|
605
|
+
f"thin_to_days or a larger max_download_gb."
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
]
|
|
609
|
+
return []
|
|
610
|
+
|
|
482
611
|
# ── Check 3: temporal network prediction ──────────────────────────────
|
|
483
612
|
def _check_temporal_network(self, selected: List[Any]) -> List[PreflightIssue]:
|
|
484
613
|
"""
|
|
@@ -1210,7 +1339,7 @@ class PreflightGate:
|
|
|
1210
1339
|
# ── FIX: BANDWIDTH & DOWNLOAD MANIFEST ──
|
|
1211
1340
|
# Calculate estimated bandwidth and build a concrete manifest of
|
|
1212
1341
|
# what will actually be downloaded, preventing surprise disk fills.
|
|
1213
|
-
ESTIMATED_GB_PER_SLC_SCENE =
|
|
1342
|
+
ESTIMATED_GB_PER_SLC_SCENE = self.gb_per_scene # Sentinel-1 IW SLC ~7.5
|
|
1214
1343
|
estimated_bandwidth_gb = len(selected) * ESTIMATED_GB_PER_SLC_SCENE
|
|
1215
1344
|
|
|
1216
1345
|
download_manifest = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygeofetch
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.5.0
|
|
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
|
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
28
|
Project-URL: Homepage, https://www.eocoreint.com/
|
|
29
|
-
Project-URL: Documentation, https://
|
|
29
|
+
Project-URL: Documentation, https://pygeofetch.readthedocs.io/en/latest/
|
|
30
30
|
Project-URL: Repository, https://github.com/EOCoreINT/pygeofetch
|
|
31
31
|
Project-URL: Bug Tracker, https://github.com/EOCoreINT/pygeofetch/issues
|
|
32
32
|
Keywords: satellite,remote-sensing,GIS,earth-observation,geospatial,SAR,Sentinel,Landsat
|
|
@@ -371,7 +371,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
|
|
|
371
371
|
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
372
372
|
year = {2026},
|
|
373
373
|
publisher = {Zenodo},
|
|
374
|
-
version = {2.6.
|
|
374
|
+
version = {2.6.5.0.1},
|
|
375
375
|
doi = {10.5281/zenodo.22087230},
|
|
376
376
|
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
377
377
|
}
|
|
@@ -67,6 +67,7 @@ pygeofetch/insar/geolocation.py
|
|
|
67
67
|
pygeofetch/insar/gpu.py
|
|
68
68
|
pygeofetch/insar/interferogram.py
|
|
69
69
|
pygeofetch/insar/ionosphere.py
|
|
70
|
+
pygeofetch/insar/network_thinning.py
|
|
70
71
|
pygeofetch/insar/offset_geometry.py
|
|
71
72
|
pygeofetch/insar/offset_tracking.py
|
|
72
73
|
pygeofetch/insar/preflight.py
|
|
@@ -213,6 +214,7 @@ tests/test_models.py
|
|
|
213
214
|
tests/test_monitor.py
|
|
214
215
|
tests/test_nasa_earthdata_cloud_s3_fix.py
|
|
215
216
|
tests/test_nasa_earthdata_public_search.py
|
|
217
|
+
tests/test_network_thinning.py
|
|
216
218
|
tests/test_noaa_big_data.py
|
|
217
219
|
tests/test_offset_tracking.py
|
|
218
220
|
tests/test_opentopography_pointcloud_usgs.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pygeofetch"
|
|
7
|
-
version = "2.6.
|
|
7
|
+
version = "2.6.5.0"
|
|
8
8
|
description = "Universal satellite data pipeline + geospatial processing platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -235,7 +235,7 @@ pygeofetch = "pygeofetch.cli.main:cli"
|
|
|
235
235
|
|
|
236
236
|
[project.urls]
|
|
237
237
|
Homepage = "https://www.eocoreint.com/"
|
|
238
|
-
Documentation = "https://
|
|
238
|
+
Documentation = "https://pygeofetch.readthedocs.io/en/latest/ "
|
|
239
239
|
Repository = "https://github.com/EOCoreINT/pygeofetch"
|
|
240
240
|
"Bug Tracker" = "https://github.com/EOCoreINT/pygeofetch/issues"
|
|
241
241
|
|