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.
Files changed (256) hide show
  1. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/CHANGELOG.md +7 -7
  2. {pygeofetch-2.6.4.0/pygeofetch.egg-info → pygeofetch-2.6.5.0}/PKG-INFO +3 -3
  3. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/README.md +1 -1
  4. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/__init__.py +1 -1
  5. pygeofetch-2.6.5.0/pygeofetch/insar/network_thinning.py +285 -0
  6. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/preflight.py +130 -1
  7. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/meta.yaml +1 -1
  8. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0/pygeofetch.egg-info}/PKG-INFO +3 -3
  9. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/SOURCES.txt +2 -0
  10. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pyproject.toml +2 -2
  11. pygeofetch-2.6.5.0/tests/test_network_thinning.py +144 -0
  12. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/LICENSE +0 -0
  13. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/MANIFEST.in +0 -0
  14. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/__init__.py +0 -0
  15. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/auth_commands.py +0 -0
  16. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/classify_commands.py +0 -0
  17. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/config_commands.py +0 -0
  18. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/download_commands.py +0 -0
  19. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/index_commands.py +0 -0
  20. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/main.py +0 -0
  21. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/monitor_commands.py +0 -0
  22. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/multisensor_commands.py +0 -0
  23. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/optical_commands.py +0 -0
  24. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  25. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/postprocess_commands.py +0 -0
  26. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/preprocess_commands.py +0 -0
  27. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/sar_commands.py +0 -0
  28. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/scale_commands.py +0 -0
  29. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/cli/search_commands.py +0 -0
  30. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/__init__.py +0 -0
  31. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/defaults.yaml +0 -0
  32. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/config/settings.py +0 -0
  33. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/__init__.py +0 -0
  34. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/authenticator.py +0 -0
  35. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/cache_manager.py +0 -0
  36. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/chunked_processor.py +0 -0
  37. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/__init__.py +0 -0
  38. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/geoparquet_io.py +0 -0
  39. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_formats/zarr_io.py +0 -0
  40. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/data_organizer.py +0 -0
  41. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/downloader.py +0 -0
  42. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/engine.py +0 -0
  43. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/logging.py +0 -0
  44. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/monitor.py +0 -0
  45. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/orbits.py +0 -0
  46. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/progress.py +0 -0
  47. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/scheduler.py +0 -0
  48. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/searcher.py +0 -0
  49. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/core/state.py +0 -0
  50. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/geotech/__init__.py +0 -0
  51. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/geotech/risk_mapper.py +0 -0
  52. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/__init__.py +0 -0
  53. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/advanced_safeguards.py +0 -0
  54. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/analysis.py +0 -0
  55. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/annotation.py +0 -0
  56. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/atmosphere.py +0 -0
  57. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/auto_fallback.py +0 -0
  58. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/coregister.py +0 -0
  59. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/deburst.py +0 -0
  60. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/esd.py +0 -0
  61. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/extraction.py +0 -0
  62. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/feasibility.py +0 -0
  63. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/flatearth.py +0 -0
  64. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/geolocation.py +0 -0
  65. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/gpu.py +0 -0
  66. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/interferogram.py +0 -0
  67. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/ionosphere.py +0 -0
  68. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/offset_geometry.py +0 -0
  69. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/offset_tracking.py +0 -0
  70. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/provenance.py +0 -0
  71. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/ps_selection.py +0 -0
  72. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
  73. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/stack_selection.py +0 -0
  74. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/synthetic.py +0 -0
  75. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/timeseries.py +0 -0
  76. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/unwrap.py +0 -0
  77. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/validate.py +0 -0
  78. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/visualize.py +0 -0
  79. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/insar/workflow.py +0 -0
  80. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/__init__.py +0 -0
  81. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/download_task.py +0 -0
  82. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/satellite_data.py +0 -0
  83. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/search_query.py +0 -0
  84. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/stream_options.py +0 -0
  85. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/models/user_auth.py +0 -0
  86. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/__init__.py +0 -0
  87. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/_init.py +0 -0
  88. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/dem_differencing.py +0 -0
  89. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/flood_mapping.py +0 -0
  90. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
  91. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
  92. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
  93. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/optical/__init__.py +0 -0
  94. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/optical/offset_tracking.py +0 -0
  95. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/__init__.py +0 -0
  96. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/base.py +0 -0
  97. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/batch.py +0 -0
  98. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/classification.py +0 -0
  99. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/indices.py +0 -0
  100. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/pipeline.py +0 -0
  101. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/postprocessor.py +0 -0
  102. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/preprocessor.py +0 -0
  103. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/sar.py +0 -0
  104. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processing/uq_exporter.py +0 -0
  105. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/__init__.py +0 -0
  106. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/indices.py +0 -0
  107. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/landsat.py +0 -0
  108. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/loader.py +0 -0
  109. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/stacker.py +0 -0
  110. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/processor/timeseries.py +0 -0
  111. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/__init__.py +0 -0
  112. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  113. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  114. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/aws_earth.py +0 -0
  115. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/base.py +0 -0
  116. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/copernicus.py +0 -0
  117. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/copernicus_nodes.py +0 -0
  118. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/digitalglobe.py +0 -0
  119. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  120. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/element84.py +0 -0
  121. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/eodag_provider.py +0 -0
  122. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/esa_scihub.py +0 -0
  123. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/geoserver_generic.py +0 -0
  124. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/google_earth_engine.py +0 -0
  125. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/inpe_cbers.py +0 -0
  126. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/isro_bhuvan.py +0 -0
  127. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/jaxa_earth.py +0 -0
  128. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/maxar_gbdx.py +0 -0
  129. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/nasa_earthdata.py +0 -0
  130. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  131. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/noaa_big_data.py +0 -0
  132. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/opentopography.py +0 -0
  133. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/planet.py +0 -0
  134. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/planetary_computer.py +0 -0
  135. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/sentinel_hub.py +0 -0
  136. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/terrabotics.py +0 -0
  137. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/providers/usgs.py +0 -0
  138. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/__init__.py +0 -0
  139. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_native.py +0 -0
  140. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_ost.py +0 -0
  141. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/_sarxarray.py +0 -0
  142. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/extraction.py +0 -0
  143. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/math.py +0 -0
  144. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/pipelines.py +0 -0
  145. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/sar/processor.py +0 -0
  146. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/__init__.py +0 -0
  147. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/burst_streaming.py +0 -0
  148. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/cog_streamer.py +0 -0
  149. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
  150. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/stac_compute/window_calculator.py +0 -0
  151. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/__init__.py +0 -0
  152. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/scene.py +0 -0
  153. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/streaming/windowed.py +0 -0
  154. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/__init__.py +0 -0
  155. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/file_utils.py +0 -0
  156. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/gcp_recovery.py +0 -0
  157. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/geo_utils.py +0 -0
  158. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/geocoding.py +0 -0
  159. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/logging_setup.py +0 -0
  160. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/retry_handler.py +0 -0
  161. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/sar_math.py +0 -0
  162. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/utils/validators.py +0 -0
  163. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/validation/__init__.py +0 -0
  164. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/validation/optical_validator.py +0 -0
  165. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/__init__.py +0 -0
  166. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/map.py +0 -0
  167. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch/viz/plot.py +0 -0
  168. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/dependency_links.txt +0 -0
  169. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/entry_points.txt +0 -0
  170. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/not-zip-safe +0 -0
  171. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/requires.txt +0 -0
  172. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/pygeofetch.egg-info/top_level.txt +0 -0
  173. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/setup.cfg +0 -0
  174. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_advanced_safeguards.py +0 -0
  175. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_airbus_oneatlas.py +0 -0
  176. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_alaska_satellite_facility.py +0 -0
  177. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_atmospheric_circular_regression.py +0 -0
  178. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_auto_fallback.py +0 -0
  179. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_auto_fallback_fixed.py +0 -0
  180. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_build_sbas_network-new.py +0 -0
  181. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_build_sbas_network.py +0 -0
  182. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_metadata.py +0 -0
  183. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_processing_integration.py +0 -0
  184. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_sync_large_gap.py +0 -0
  185. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_sync_prescreening.py +0 -0
  186. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_burst_synchronization.py +0 -0
  187. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_circuit_breaker_wiring.py +0 -0
  188. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_cli_optical_validation.py +0 -0
  189. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_copernicus_nodes.py +0 -0
  190. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_integration.py +0 -0
  191. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_upgrades (1).py +0 -0
  192. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_coregister_upgrades.py +0 -0
  193. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_credential_encryption.py +0 -0
  194. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_data_organizer.py +0 -0
  195. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_dem_differencing_pipeline.py +0 -0
  196. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_digitalglobe.py +0 -0
  197. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_earth_explorer_additional.py +0 -0
  198. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_era5_atmospheric_correction.py +0 -0
  199. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_esa_scihub.py +0 -0
  200. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_extraction.py +0 -0
  201. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_flat_earth_phase.py +0 -0
  202. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_flood_mapping_pipeline.py +0 -0
  203. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_gcp_reprojection.py +0 -0
  204. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_geology_indices_and_rvi_fix.py +0 -0
  205. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_geoserver_generic.py +0 -0
  206. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_google_earth_engine.py +0 -0
  207. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_inpe_cbers.py +0 -0
  208. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_insar.py +0 -0
  209. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_insar_optical_fusion_pipeline.py +0 -0
  210. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_ionosphere.py +0 -0
  211. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_isro_bhuvan.py +0 -0
  212. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_jaxa_earth.py +0 -0
  213. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_lightweight_prefetch.py +0 -0
  214. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_map_preview_consolidation.py +0 -0
  215. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_models.py +0 -0
  216. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_monitor.py +0 -0
  217. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
  218. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_nasa_earthdata_public_search.py +0 -0
  219. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_noaa_big_data.py +0 -0
  220. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_offset_tracking.py +0 -0
  221. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_opentopography_pointcloud_usgs.py +0 -0
  222. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_offset_tracking.py +0 -0
  223. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validation_wiring.py +0 -0
  224. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validator.py +0 -0
  225. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_optical_validator_stac.py +0 -0
  226. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_pgv_integration_contract.py +0 -0
  227. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_pipeline_process_export.py +0 -0
  228. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_planet_asset_type_fix.py +0 -0
  229. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_gate.py +0 -0
  230. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_quiet.py +0 -0
  231. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_preflight_spatial_baseline.py +0 -0
  232. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_provider_geometry_audit.py +0 -0
  233. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_provider_scene_disambiguation.py +0 -0
  234. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_providers.py +0 -0
  235. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_ps_selection.py +0 -0
  236. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_raster_collocation_integration.py +0 -0
  237. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_reference_pixel_and_despike.py +0 -0
  238. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_risk_mapper.py +0 -0
  239. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_risk_mapping.py +0 -0
  240. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sar_pipelines.py +0 -0
  241. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_screen_stack_orchestration.py +0 -0
  242. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_search_download.py +0 -0
  243. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sentinel_hub_catalog_fix.py +0 -0
  244. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_sign_convention.py +0 -0
  245. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_splitspectrum_diagnostic.py +0 -0
  246. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_stack_selection_consolidation.py +0 -0
  247. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_state.py +0 -0
  248. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_strain_and_fusion.py +0 -0
  249. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_streaming.py +0 -0
  250. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_terrain_corrected_change_pipeline.py +0 -0
  251. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_topo_r2_bug.py +0 -0
  252. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_uq_exporter.py +0 -0
  253. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_utils.py +0 -0
  254. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_vegetation_disturbance_pipeline.py +0 -0
  255. {pygeofetch-2.6.4.0 → pygeofetch-2.6.5.0}/tests/test_viz_plot.py +0 -0
  256. {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.4.0.4
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.4.0.4] — 2026-08-25 (latest)
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.4.0] — 2026-08-24
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.4.0.4 — 2026-07-12"** describing 22+ providers, Sentinel-1C/1D
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.4.0.4 was
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.4.0.4`**, dated
410
- 2026-07-29 and 2026-07-12. **2.6.4.0.4 was only ever released once**,
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.4.0
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://appiahkubis14.github.io/pygeofetch-docs/
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.4.0.1},
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.4.0.1},
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
  }
@@ -30,7 +30,7 @@ CLI::
30
30
  pygeofetch doctor
31
31
  """
32
32
 
33
- __version__ = "2.6.4.0.4"
33
+ __version__ = "2.6.5.0.4"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -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 = 7.5 # Sentinel-1 IW SLC average size
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,4 +1,4 @@
1
- {% set version = "2.6.4.0.4" %}
1
+ {% set version = "2.6.5.0.4" %}
2
2
 
3
3
  package:
4
4
  name: pygeofetch
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygeofetch
3
- Version: 2.6.4.0
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://appiahkubis14.github.io/pygeofetch-docs/
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.4.0.1},
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.4.0"
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://appiahkubis14.github.io/pygeofetch-docs/"
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