PyGeoFetch 2.6.2.9__tar.gz → 2.6.3.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 (238) hide show
  1. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/CHANGELOG.md +7 -7
  2. {pygeofetch-2.6.2.9/pygeofetch.egg-info → pygeofetch-2.6.3.0}/PKG-INFO +2 -2
  3. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/README.md +1 -1
  4. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/__init__.py +1 -1
  5. pygeofetch-2.6.3.0/pygeofetch/cli/classify_commands.py +112 -0
  6. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/index_commands.py +64 -22
  7. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/main.py +95 -1
  8. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/postprocess_commands.py +48 -0
  9. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/engine.py +2 -0
  10. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/searcher.py +32 -87
  11. pygeofetch-2.6.3.0/pygeofetch/insar/feasibility.py +306 -0
  12. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/meta.yaml +1 -1
  13. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/base.py +104 -1
  14. pygeofetch-2.6.3.0/pygeofetch/processing/classification.py +367 -0
  15. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/indices.py +101 -15
  16. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/postprocessor.py +169 -0
  17. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/aws_earth.py +10 -1
  18. pygeofetch-2.6.3.0/pygeofetch/utils/geocoding.py +132 -0
  19. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0/pygeofetch.egg-info}/PKG-INFO +2 -2
  20. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/SOURCES.txt +4 -0
  21. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pyproject.toml +1 -1
  22. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/LICENSE +0 -0
  23. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/MANIFEST.in +0 -0
  24. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/__init__.py +0 -0
  25. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/auth_commands.py +0 -0
  26. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/config_commands.py +0 -0
  27. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/download_commands.py +0 -0
  28. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/monitor_commands.py +0 -0
  29. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/multisensor_commands.py +0 -0
  30. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/optical_commands.py +0 -0
  31. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  32. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/preprocess_commands.py +0 -0
  33. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/sar_commands.py +0 -0
  34. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/cli/search_commands.py +0 -0
  35. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/config/__init__.py +0 -0
  36. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/config/defaults.yaml +0 -0
  37. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/config/settings.py +0 -0
  38. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/__init__.py +0 -0
  39. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/authenticator.py +0 -0
  40. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/cache_manager.py +0 -0
  41. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/data_organizer.py +0 -0
  42. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/downloader.py +0 -0
  43. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/logging.py +0 -0
  44. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/monitor.py +0 -0
  45. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/orbits.py +0 -0
  46. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/scheduler.py +0 -0
  47. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/core/state.py +0 -0
  48. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/geotech/__init__.py +0 -0
  49. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/geotech/risk_mapper.py +0 -0
  50. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/__init__.py +0 -0
  51. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/advanced_safeguards.py +0 -0
  52. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/analysis.py +0 -0
  53. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/annotation.py +0 -0
  54. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/atmosphere.py +0 -0
  55. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/auto_fallback.py +0 -0
  56. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/coregister.py +0 -0
  57. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/deburst.py +0 -0
  58. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/esd.py +0 -0
  59. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/extraction.py +0 -0
  60. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/flatearth.py +0 -0
  61. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/geolocation.py +0 -0
  62. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/gpu.py +0 -0
  63. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/interferogram.py +0 -0
  64. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/ionosphere.py +0 -0
  65. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/offset_geometry.py +0 -0
  66. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/offset_tracking.py +0 -0
  67. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/preflight.py +0 -0
  68. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/provenance.py +0 -0
  69. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/ps_selection.py +0 -0
  70. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
  71. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/stack_selection.py +0 -0
  72. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/synthetic.py +0 -0
  73. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/timeseries.py +0 -0
  74. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/unwrap.py +0 -0
  75. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/validate.py +0 -0
  76. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/visualize.py +0 -0
  77. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/insar/workflow.py +0 -0
  78. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/models/__init__.py +0 -0
  79. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/models/download_task.py +0 -0
  80. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/models/satellite_data.py +0 -0
  81. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/models/search_query.py +0 -0
  82. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/models/user_auth.py +0 -0
  83. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/_init.py +0 -0
  84. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/dem_differencing.py +0 -0
  85. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/flood_mapping.py +0 -0
  86. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
  87. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
  88. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
  89. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/optical/__init__.py +0 -0
  90. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/optical/offset_tracking.py +0 -0
  91. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/__init__.py +0 -0
  92. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/batch.py +0 -0
  93. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/pipeline.py +0 -0
  94. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/preprocessor.py +0 -0
  95. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/sar.py +0 -0
  96. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processing/uq_exporter.py +0 -0
  97. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/__init__.py +0 -0
  98. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/indices.py +0 -0
  99. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/landsat.py +0 -0
  100. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/loader.py +0 -0
  101. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/stacker.py +0 -0
  102. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/processor/timeseries.py +0 -0
  103. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/__init__.py +0 -0
  104. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  105. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  106. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/base.py +0 -0
  107. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/copernicus.py +0 -0
  108. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/copernicus_nodes.py +0 -0
  109. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/digitalglobe.py +0 -0
  110. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  111. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/element84.py +0 -0
  112. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/eodag_provider.py +0 -0
  113. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/esa_scihub.py +0 -0
  114. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/geoserver_generic.py +0 -0
  115. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/google_earth_engine.py +0 -0
  116. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/inpe_cbers.py +0 -0
  117. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/isro_bhuvan.py +0 -0
  118. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/jaxa_earth.py +0 -0
  119. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/maxar_gbdx.py +0 -0
  120. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/nasa_earthdata.py +0 -0
  121. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  122. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/noaa_big_data.py +0 -0
  123. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/opentopography.py +0 -0
  124. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/planet.py +0 -0
  125. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/planetary_computer.py +0 -0
  126. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/sentinel_hub.py +0 -0
  127. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/terrabotics.py +0 -0
  128. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/providers/usgs.py +0 -0
  129. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/__init__.py +0 -0
  130. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/_native.py +0 -0
  131. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/_ost.py +0 -0
  132. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/_sarxarray.py +0 -0
  133. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/extraction.py +0 -0
  134. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/math.py +0 -0
  135. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/pipelines.py +0 -0
  136. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/sar/processor.py +0 -0
  137. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/stac_compute/__init__.py +0 -0
  138. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
  139. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/__init__.py +0 -0
  140. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/file_utils.py +0 -0
  141. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/gcp_recovery.py +0 -0
  142. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/geo_utils.py +0 -0
  143. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/logging_setup.py +0 -0
  144. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/retry_handler.py +0 -0
  145. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/sar_math.py +0 -0
  146. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/utils/validators.py +0 -0
  147. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/validation/__init__.py +0 -0
  148. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/validation/optical_validator.py +0 -0
  149. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/viz/__init__.py +0 -0
  150. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/viz/map.py +0 -0
  151. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch/viz/plot.py +0 -0
  152. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/dependency_links.txt +0 -0
  153. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/entry_points.txt +0 -0
  154. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/not-zip-safe +0 -0
  155. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/requires.txt +0 -0
  156. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/top_level.txt +0 -0
  157. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/setup.cfg +0 -0
  158. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_advanced_safeguards.py +0 -0
  159. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_airbus_oneatlas.py +0 -0
  160. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_alaska_satellite_facility.py +0 -0
  161. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_atmospheric_circular_regression.py +0 -0
  162. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_auto_fallback.py +0 -0
  163. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_auto_fallback_fixed.py +0 -0
  164. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_build_sbas_network-new.py +0 -0
  165. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_build_sbas_network.py +0 -0
  166. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_burst_metadata.py +0 -0
  167. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_burst_processing_integration.py +0 -0
  168. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_burst_sync_large_gap.py +0 -0
  169. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_burst_sync_prescreening.py +0 -0
  170. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_burst_synchronization.py +0 -0
  171. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_circuit_breaker_wiring.py +0 -0
  172. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_cli_optical_validation.py +0 -0
  173. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_copernicus_nodes.py +0 -0
  174. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_coregister_integration.py +0 -0
  175. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_coregister_upgrades (1).py +0 -0
  176. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_coregister_upgrades.py +0 -0
  177. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_credential_encryption.py +0 -0
  178. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_data_organizer.py +0 -0
  179. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_dem_differencing_pipeline.py +0 -0
  180. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_digitalglobe.py +0 -0
  181. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_earth_explorer_additional.py +0 -0
  182. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_era5_atmospheric_correction.py +0 -0
  183. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_esa_scihub.py +0 -0
  184. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_extraction.py +0 -0
  185. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_flat_earth_phase.py +0 -0
  186. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_flood_mapping_pipeline.py +0 -0
  187. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_gcp_reprojection.py +0 -0
  188. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_geology_indices_and_rvi_fix.py +0 -0
  189. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_geoserver_generic.py +0 -0
  190. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_google_earth_engine.py +0 -0
  191. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_inpe_cbers.py +0 -0
  192. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_insar.py +0 -0
  193. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_insar_optical_fusion_pipeline.py +0 -0
  194. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_ionosphere.py +0 -0
  195. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_isro_bhuvan.py +0 -0
  196. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_jaxa_earth.py +0 -0
  197. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_lightweight_prefetch.py +0 -0
  198. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_map_preview_consolidation.py +0 -0
  199. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_models.py +0 -0
  200. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_monitor.py +0 -0
  201. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
  202. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_nasa_earthdata_public_search.py +0 -0
  203. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_noaa_big_data.py +0 -0
  204. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_offset_tracking.py +0 -0
  205. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_opentopography_pointcloud_usgs.py +0 -0
  206. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_optical_offset_tracking.py +0 -0
  207. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_optical_validation_wiring.py +0 -0
  208. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_optical_validator.py +0 -0
  209. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_optical_validator_stac.py +0 -0
  210. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_pgv_integration_contract.py +0 -0
  211. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_pipeline_process_export.py +0 -0
  212. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_planet_asset_type_fix.py +0 -0
  213. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_preflight_gate.py +0 -0
  214. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_preflight_spatial_baseline.py +0 -0
  215. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_provider_geometry_audit.py +0 -0
  216. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_provider_scene_disambiguation.py +0 -0
  217. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_providers.py +0 -0
  218. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_ps_selection.py +0 -0
  219. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_raster_collocation_integration.py +0 -0
  220. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_reference_pixel_and_despike.py +0 -0
  221. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_risk_mapper.py +0 -0
  222. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_risk_mapping.py +0 -0
  223. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_sar_pipelines.py +0 -0
  224. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_screen_stack_orchestration.py +0 -0
  225. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_search_download.py +0 -0
  226. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_sentinel_hub_catalog_fix.py +0 -0
  227. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_sign_convention.py +0 -0
  228. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_splitspectrum_diagnostic.py +0 -0
  229. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_stack_selection_consolidation.py +0 -0
  230. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_state.py +0 -0
  231. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_strain_and_fusion.py +0 -0
  232. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_terrain_corrected_change_pipeline.py +0 -0
  233. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_topo_r2_bug.py +0 -0
  234. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_uq_exporter.py +0 -0
  235. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_utils.py +0 -0
  236. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_vegetation_disturbance_pipeline.py +0 -0
  237. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.0}/tests/test_viz_plot.py +0 -0
  238. {pygeofetch-2.6.2.9 → pygeofetch-2.6.3.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.2.9.4
23
+ ## [Unreleased] — v2.6.3.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.2.9.4] — 2026-08-25 (latest)
133
+ ## [2.6.3.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.2.9] — 2026-08-24
152
+ ## [2.6.3.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.2.9.4 — 2026-07-12"** describing 22+ providers, Sentinel-1C/1D
264
+ **"2.6.3.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.2.9.4 was
266
+ management, 17 spectral indices, and various bug fixes. **2.6.3.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.2.9.4`**, dated
410
- 2026-07-29 and 2026-07-12. **2.6.2.9.4 was only ever released once**,
409
+ 1. **Two different entries were both labeled `2.6.3.0.4`**, dated
410
+ 2026-07-29 and 2026-07-12. **2.6.3.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.2.9
3
+ Version: 2.6.3.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
@@ -369,7 +369,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
369
369
  title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
370
370
  year = {2026},
371
371
  publisher = {Zenodo},
372
- version = {2.6.2.9.1},
372
+ version = {2.6.3.0.1},
373
373
  doi = {10.5281/zenodo.22087230},
374
374
  url = {https://doi.org/10.5281/zenodo.22087230}
375
375
  }
@@ -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.2.9.1},
232
+ version = {2.6.3.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.2.9.4"
33
+ __version__ = "2.6.3.0.4"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -0,0 +1,112 @@
1
+ """CLI commands for pixel classification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import click
8
+ from rich.console import Console
9
+
10
+ # from pygeofetch.cli.index_commands import clip_options, _parse_bbox
11
+
12
+ console = Console()
13
+
14
+
15
+ def _engine():
16
+ from pygeofetch.core.engine import PyGeoFetch
17
+
18
+ return PyGeoFetch(log_level="WARNING")
19
+
20
+
21
+ def _pr(result, name):
22
+ if result.success:
23
+ size_mb = (
24
+ result.output_path.stat().st_size / (1024 * 1024)
25
+ if result.output_path and result.output_path.exists()
26
+ else 0
27
+ )
28
+ console.print(
29
+ f"[green]✓[/] {name} → {result.output_path}"
30
+ f" ({size_mb:.1f} MB, {result.duration_seconds:.2f}s)"
31
+ )
32
+ meta = result.metadata or {}
33
+ for k, v in meta.items():
34
+ if k != "classification_report":
35
+ console.print(f" {k}: {v}")
36
+ else:
37
+ console.print(f"[red]✗[/] {name} failed: {result.error}")
38
+ sys.exit(1)
39
+
40
+
41
+ @click.group()
42
+ def classify() -> None:
43
+ """Pixel classification — unsupervised K-means and supervised Random Forest/SVM."""
44
+
45
+
46
+ @classify.command("kmeans")
47
+ @click.argument("inputs", nargs=-1, type=click.Path(exists=True))
48
+ @click.option("--clusters", "-k", default=5, show_default=True, type=int, help="Number of clusters.")
49
+ @click.option("--output", "-o", default=None)
50
+ @click.option("--random-state", default=42, show_default=True, type=int)
51
+ # @clip_options
52
+ def kmeans_cmd(inputs, clusters, output, random_state, bbox, geometry, geometry_crs):
53
+ """Unsupervised K-means clustering. No training data needed.
54
+
55
+ Example: pygeofetch classify kmeans B02.tif B03.tif B04.tif B08.tif -k 5
56
+ """
57
+ if len(inputs) < 1:
58
+ console.print("[red]Provide at least 1 input band raster[/]")
59
+ sys.exit(1)
60
+ e = _engine()
61
+ r = e.classify.kmeans(
62
+ inputs=list(inputs), n_clusters=clusters, output=output, random_state=random_state,
63
+ bbox=bbox, geometry=geometry, geometry_crs=geometry_crs,
64
+ )
65
+ _pr(r, f"K-means ({clusters} clusters)")
66
+
67
+
68
+ @classify.command("train")
69
+ @click.argument("inputs", nargs=-1, type=click.Path(exists=True))
70
+ @click.option("--training-data", required=True, type=click.Path(exists=True), help="Vector file (GeoJSON/Shapefile) with labeled training geometries.")
71
+ @click.option("--class-field", required=True, help="Attribute column holding the real class label.")
72
+ @click.option("--output-model", "-o", required=True, help="Where to save the trained model.")
73
+ @click.option("--model-type", default="random_forest", type=click.Choice(["random_forest", "svm"]), show_default=True)
74
+ @click.option("--n-estimators", default=100, show_default=True, type=int, help="Random Forest tree count.")
75
+ @click.option("--test-size", default=0.2, show_default=True, type=float, help="Fraction of samples held out for real accuracy reporting.")
76
+ def train_cmd(inputs, training_data, class_field, output_model, model_type, n_estimators, test_size):
77
+ """Train a supervised classifier from labeled vector training samples.
78
+
79
+ Example: pygeofetch classify train B02.tif B03.tif B04.tif B08.tif
80
+ --training-data samples.geojson --class-field land_cover -o model.joblib
81
+ """
82
+ if len(inputs) < 1:
83
+ console.print("[red]Provide at least 1 input band raster[/]")
84
+ sys.exit(1)
85
+ e = _engine()
86
+ r = e.classify.train(
87
+ inputs=list(inputs), training_data=training_data, class_field=class_field,
88
+ output_model=output_model, model_type=model_type, n_estimators=n_estimators,
89
+ test_size=test_size,
90
+ )
91
+ _pr(r, f"train ({model_type})")
92
+
93
+
94
+ @classify.command("predict")
95
+ @click.argument("inputs", nargs=-1, type=click.Path(exists=True))
96
+ @click.option("--model", "-m", required=True, type=click.Path(exists=True), help="A model trained via 'classify train'.")
97
+ @click.option("--output", "-o", default=None)
98
+ # @clip_options
99
+ def predict_cmd(inputs, model, output, bbox, geometry, geometry_crs):
100
+ """Apply a trained model to a real raster stack, producing a classification map.
101
+
102
+ Example: pygeofetch classify predict B02.tif B03.tif B04.tif B08.tif -m model.joblib
103
+ """
104
+ if len(inputs) < 1:
105
+ console.print("[red]Provide at least 1 input band raster[/]")
106
+ sys.exit(1)
107
+ e = _engine()
108
+ r = e.classify.predict(
109
+ inputs=list(inputs), model=model, output=output,
110
+ bbox=bbox, geometry=geometry, geometry_crs=geometry_crs,
111
+ )
112
+ _pr(r, "predict")
@@ -16,6 +16,37 @@ def _engine():
16
16
  return PyGeoFetch(log_level="WARNING")
17
17
 
18
18
 
19
+ def _parse_bbox(ctx, param, value):
20
+ if value is None:
21
+ return None
22
+ try:
23
+ parts = [float(x.strip()) for x in value.split(",")]
24
+ except ValueError:
25
+ raise click.BadParameter("must be 4 comma-separated numbers: minx,miny,maxx,maxy") from None
26
+ if len(parts) != 4:
27
+ raise click.BadParameter("must be exactly 4 comma-separated numbers: minx,miny,maxx,maxy")
28
+ return tuple(parts)
29
+
30
+
31
+ def clip_options(f):
32
+ """Shared --bbox/--geometry/--geometry-crs options, added to every
33
+ index command whose underlying method supports post-computation
34
+ clipping."""
35
+ f = click.option(
36
+ "--bbox", callback=_parse_bbox, default=None,
37
+ help="Clip the result to minx,miny,maxx,maxy (comma-separated).",
38
+ )(f)
39
+ f = click.option(
40
+ "--geometry", default=None, type=click.Path(exists=True),
41
+ help="Clip the result to a GeoJSON file's geometry instead of a bbox.",
42
+ )(f)
43
+ f = click.option(
44
+ "--geometry-crs", default="EPSG:4326", show_default=True,
45
+ help="CRS of --bbox/--geometry coordinates.",
46
+ )(f)
47
+ return f
48
+
49
+
19
50
  def _pr(result, name):
20
51
  if result.success:
21
52
  size_mb = (
@@ -55,7 +86,8 @@ def index() -> None:
55
86
  help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
56
87
  )
57
88
  @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
58
- def ndvi_cmd(red, nir, output, chunked, tile_size):
89
+ @clip_options
90
+ def ndvi_cmd(red, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
59
91
  """NDVI — Normalized Difference Vegetation Index. Range: -1 to +1. Vegetation > 0.3.
60
92
 
61
93
  Pass the SAME path to --red and --nir to auto-detect both bands from
@@ -63,7 +95,7 @@ def ndvi_cmd(red, nir, output, chunked, tile_size):
63
95
  real, embedded band descriptions.
64
96
  """
65
97
  e = _engine()
66
- r = e.indices.ndvi(red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
98
+ r = e.indices.ndvi(red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
67
99
  _pr(r, "NDVI")
68
100
 
69
101
 
@@ -80,7 +112,8 @@ def ndvi_cmd(red, nir, output, chunked, tile_size):
80
112
  help="Process in memory-safe tiles for large rasters that exceed available RAM or the 4GB GeoTIFF limit.",
81
113
  )
82
114
  @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
83
- def ndre_cmd(rededge, nir, output, chunked, tile_size):
115
+ @clip_options
116
+ def ndre_cmd(rededge, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
84
117
  """NDRE — Normalized Difference Red Edge Index. Range: -1 to +1.
85
118
 
86
119
  Less prone to saturation than NDVI in dense canopy -- useful for
@@ -89,7 +122,7 @@ def ndre_cmd(rededge, nir, output, chunked, tile_size):
89
122
  both bands from one merged multi-band image.
90
123
  """
91
124
  e = _engine()
92
- r = e.indices.ndre(rededge=rededge, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
125
+ r = e.indices.ndre(rededge=rededge, nir=nir, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
93
126
  _pr(r, "NDRE")
94
127
 
95
128
 
@@ -100,14 +133,15 @@ def ndre_cmd(rededge, nir, output, chunked, tile_size):
100
133
  @click.option("--output", "-o", default=None)
101
134
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
102
135
  @click.option("--tile-size", default=1024, show_default=True, type=int)
103
- def evi_cmd(blue, red, nir, output, chunked, tile_size):
136
+ @clip_options
137
+ def evi_cmd(blue, red, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
104
138
  """EVI — Enhanced Vegetation Index. Better than NDVI in high-biomass areas.
105
139
 
106
140
  Pass the SAME path to --blue, --red, and --nir to auto-detect all
107
141
  three bands from one merged multi-band image.
108
142
  """
109
143
  e = _engine()
110
- r = e.indices.evi(blue=blue, red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
144
+ r = e.indices.evi(blue=blue, red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
111
145
  _pr(r, "EVI")
112
146
 
113
147
 
@@ -118,14 +152,15 @@ def evi_cmd(blue, red, nir, output, chunked, tile_size):
118
152
  @click.option("--output", "-o", default=None)
119
153
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
120
154
  @click.option("--tile-size", default=1024, show_default=True, type=int)
121
- def savi_cmd(red, nir, soil_l, output, chunked, tile_size):
155
+ @clip_options
156
+ def savi_cmd(red, nir, soil_l, output, chunked, tile_size, bbox, geometry, geometry_crs):
122
157
  """SAVI — Soil Adjusted Vegetation Index. Reduces soil brightness influence.
123
158
 
124
159
  Pass the SAME path to --red and --nir to auto-detect both bands
125
160
  from one merged multi-band image.
126
161
  """
127
162
  e = _engine()
128
- r = e.indices.savi(red=red, nir=nir, L=soil_l, output=output, chunked=chunked, tile_size=tile_size)
163
+ r = e.indices.savi(red=red, nir=nir, L=soil_l, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
129
164
  _pr(r, "SAVI")
130
165
 
131
166
 
@@ -135,14 +170,15 @@ def savi_cmd(red, nir, soil_l, output, chunked, tile_size):
135
170
  @click.option("--output", "-o", default=None)
136
171
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
137
172
  @click.option("--tile-size", default=1024, show_default=True, type=int)
138
- def ndwi_cmd(green, nir, output, chunked, tile_size):
173
+ @clip_options
174
+ def ndwi_cmd(green, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
139
175
  """NDWI — Normalized Difference Water Index. Water > 0.
140
176
 
141
177
  Pass the SAME path to --green and --nir to auto-detect both bands
142
178
  from one merged multi-band image.
143
179
  """
144
180
  e = _engine()
145
- r = e.indices.ndwi(green=green, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
181
+ r = e.indices.ndwi(green=green, nir=nir, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
146
182
  _pr(r, "NDWI")
147
183
 
148
184
 
@@ -152,14 +188,15 @@ def ndwi_cmd(green, nir, output, chunked, tile_size):
152
188
  @click.option("--output", "-o", default=None)
153
189
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
154
190
  @click.option("--tile-size", default=1024, show_default=True, type=int)
155
- def mndwi_cmd(green, swir1, output, chunked, tile_size):
191
+ @clip_options
192
+ def mndwi_cmd(green, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
156
193
  """MNDWI — Modified NDWI. Better separation of water from built-up areas.
157
194
 
158
195
  Pass the SAME path to --green and --swir1 to auto-detect both
159
196
  bands from one merged multi-band image.
160
197
  """
161
198
  e = _engine()
162
- r = e.indices.mndwi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
199
+ r = e.indices.mndwi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
163
200
  _pr(r, "MNDWI")
164
201
 
165
202
 
@@ -169,14 +206,15 @@ def mndwi_cmd(green, swir1, output, chunked, tile_size):
169
206
  @click.option("--output", "-o", default=None)
170
207
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
171
208
  @click.option("--tile-size", default=1024, show_default=True, type=int)
172
- def ndbi_cmd(nir, swir1, output, chunked, tile_size):
209
+ @clip_options
210
+ def ndbi_cmd(nir, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
173
211
  """NDBI — Normalized Difference Built-up Index. Urban > 0.
174
212
 
175
213
  Pass the SAME path to --nir and --swir1 to auto-detect both bands
176
214
  from one merged multi-band image.
177
215
  """
178
216
  e = _engine()
179
- r = e.indices.ndbi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
217
+ r = e.indices.ndbi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
180
218
  _pr(r, "NDBI")
181
219
 
182
220
 
@@ -186,14 +224,15 @@ def ndbi_cmd(nir, swir1, output, chunked, tile_size):
186
224
  @click.option("--output", "-o", default=None)
187
225
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
188
226
  @click.option("--tile-size", default=1024, show_default=True, type=int)
189
- def ndsi_cmd(green, swir1, output, chunked, tile_size):
227
+ @clip_options
228
+ def ndsi_cmd(green, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
190
229
  """NDSI — Snow Index. Snow > 0.4.
191
230
 
192
231
  Pass the SAME path to --green and --swir1 to auto-detect both
193
232
  bands from one merged multi-band image.
194
233
  """
195
234
  e = _engine()
196
- r = e.indices.ndsi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
235
+ r = e.indices.ndsi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
197
236
  _pr(r, "NDSI")
198
237
 
199
238
 
@@ -203,14 +242,15 @@ def ndsi_cmd(green, swir1, output, chunked, tile_size):
203
242
  @click.option("--output", "-o", default=None)
204
243
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
205
244
  @click.option("--tile-size", default=1024, show_default=True, type=int)
206
- def ndmi_cmd(nir, swir1, output, chunked, tile_size):
245
+ @clip_options
246
+ def ndmi_cmd(nir, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
207
247
  """NDMI — Moisture Index. Sensitive to canopy water content.
208
248
 
209
249
  Pass the SAME path to --nir and --swir1 to auto-detect both bands
210
250
  from one merged multi-band image.
211
251
  """
212
252
  e = _engine()
213
- r = e.indices.ndmi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
253
+ r = e.indices.ndmi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
214
254
  _pr(r, "NDMI")
215
255
 
216
256
 
@@ -220,14 +260,15 @@ def ndmi_cmd(nir, swir1, output, chunked, tile_size):
220
260
  @click.option("--output", "-o", default=None)
221
261
  @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
222
262
  @click.option("--tile-size", default=1024, show_default=True, type=int)
223
- def nbr_cmd(nir, swir2, output, chunked, tile_size):
263
+ @clip_options
264
+ def nbr_cmd(nir, swir2, output, chunked, tile_size, bbox, geometry, geometry_crs):
224
265
  """NBR — Normalized Burn Ratio. Use dNBR for burn severity.
225
266
 
226
267
  Pass the SAME path to --nir and --swir2 to auto-detect both bands
227
268
  from one merged multi-band image.
228
269
  """
229
270
  e = _engine()
230
- r = e.indices.nbr(nir=nir, swir2=swir2, output=output, chunked=chunked, tile_size=tile_size)
271
+ r = e.indices.nbr(nir=nir, swir2=swir2, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
231
272
  _pr(r, "NBR")
232
273
 
233
274
 
@@ -351,10 +392,11 @@ def albedo_cmd(inputs, sensor, output):
351
392
  help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
352
393
  )
353
394
  @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
354
- def band_math_cmd(inputs, expr, output, chunked, tile_size):
395
+ @clip_options
396
+ def band_math_cmd(inputs, expr, output, chunked, tile_size, bbox, geometry, geometry_crs):
355
397
  """Arbitrary band arithmetic. Example: --expr '(B[1]-B[0])/(B[1]+B[0])'."""
356
398
  e = _engine()
357
- r = e.indices.band_math(inputs=list(inputs), expression=expr, output=output, chunked=chunked, tile_size=tile_size)
399
+ r = e.indices.band_math(inputs=list(inputs), expression=expr, output=output, chunked=chunked, tile_size=tile_size, bbox=bbox, geometry=geometry, geometry_crs=geometry_crs)
358
400
  _pr(r, f"band-math ({expr[:40]})")
359
401
 
360
402
 
@@ -27,6 +27,7 @@ from pygeofetch.cli.config_commands import config
27
27
  from pygeofetch.cli.download_commands import download
28
28
  from pygeofetch.cli.index_commands import index
29
29
  from pygeofetch.cli.multisensor_commands import multisensor
30
+ from pygeofetch.cli.classify_commands import classify
30
31
  from pygeofetch.cli.optical_commands import optical
31
32
  from pygeofetch.cli.pipeline_process_commands import proc_pipeline
32
33
  from pygeofetch.cli.postprocess_commands import post
@@ -107,6 +108,7 @@ cli.add_command(sar)
107
108
  cli.add_command(proc_pipeline)
108
109
  cli.add_command(optical)
109
110
  cli.add_command(multisensor)
111
+ cli.add_command(classify)
110
112
 
111
113
 
112
114
  # ---------------------------------------------------------------------------
@@ -212,6 +214,98 @@ def version(as_json: bool) -> None:
212
214
  )
213
215
 
214
216
 
217
+ # ---------------------------------------------------------------------------
218
+ # GEOCODE
219
+ # ---------------------------------------------------------------------------
220
+
221
+
222
+ @cli.command()
223
+ @click.argument("query")
224
+ @click.option("--limit", default=1, show_default=True, type=int, help="Max results to return.")
225
+ def geocode(query: str, limit: int) -> None:
226
+ """Look up a place name and get its coordinates and bounding box.
227
+
228
+ Example: pygeofetch geocode "Nairobi, Kenya"
229
+
230
+ The printed bbox is ready to paste directly into a SearchQuery.
231
+ """
232
+ from pygeofetch.utils.geocoding import geocode as _geocode
233
+
234
+ try:
235
+ results = _geocode(query, limit=limit)
236
+ except Exception as exc:
237
+ console.print(f"[red]✗[/] geocode failed: {exc}")
238
+ sys.exit(1)
239
+
240
+ if not results:
241
+ console.print(f"[yellow]No results found for[/] {query!r}")
242
+ return
243
+
244
+ for r in results:
245
+ console.print(f"[green]{r['display_name']}[/]")
246
+ console.print(f" lat/lon: {r['lat']}, {r['lon']}")
247
+ console.print(f" bbox (min_lon, min_lat, max_lon, max_lat): {r['bbox']}")
248
+
249
+
250
+ @cli.command(name="reverse-geocode")
251
+ @click.argument("lat", type=float)
252
+ @click.argument("lon", type=float)
253
+ def reverse_geocode_cmd(lat: float, lon: float) -> None:
254
+ """Look up the real place name at a coordinate.
255
+
256
+ Example: pygeofetch reverse-geocode -1.286389 36.817223
257
+ """
258
+ from pygeofetch.utils.geocoding import reverse_geocode as _reverse_geocode
259
+
260
+ try:
261
+ place = _reverse_geocode(lat, lon)
262
+ except Exception as exc:
263
+ console.print(f"[red]✗[/] reverse-geocode failed: {exc}")
264
+ sys.exit(1)
265
+
266
+ if place is None:
267
+ console.print(f"[yellow]No real place found at[/] ({lat}, {lon})")
268
+ return
269
+ console.print(f"[green]{place['display_name']}[/]")
270
+
271
+
272
+ # ---------------------------------------------------------------------------
273
+ # INSAR FEASIBILITY
274
+ # ---------------------------------------------------------------------------
275
+
276
+
277
+ @cli.command(name="assess-feasibility")
278
+ @click.option("--bbox", required=True, help="min_lon,min_lat,max_lon,max_lat")
279
+ @click.option("--start-date", required=True)
280
+ @click.option("--end-date", required=True)
281
+ @click.option("--max-temporal-baseline", default=72, show_default=True, type=int)
282
+ @click.option("--deep-check", is_flag=True, help="Also run real, live burst-sync screening on the best track (costs real network calls).")
283
+ def assess_feasibility_cmd(bbox, start_date, end_date, max_temporal_baseline, deep_check) -> None:
284
+ """Assess InSAR feasibility for an AOI before downloading anything.
285
+
286
+ Example: pygeofetch assess-feasibility --bbox 18.85,50.15,19.15,50.35 \\
287
+ --start-date 2016-01-01 --end-date 2021-06-30
288
+
289
+ Reports the real, largest temporally-connected component per track
290
+ -- an honest upper bound, not a guarantee InSAR will work.
291
+ """
292
+ from pygeofetch.core.engine import PyGeoFetch
293
+ from pygeofetch.insar.feasibility import assess_insar_feasibility
294
+
295
+ try:
296
+ bbox_tuple = tuple(float(x) for x in bbox.split(","))
297
+ except ValueError:
298
+ console.print("[red]--bbox must be 4 comma-separated numbers[/]")
299
+ sys.exit(1)
300
+
301
+ client = PyGeoFetch(log_level="WARNING")
302
+ report = assess_insar_feasibility(
303
+ client, bbox_tuple, start_date, end_date,
304
+ max_temporal_baseline_days=max_temporal_baseline, deep_check=deep_check,
305
+ )
306
+ report.print_summary()
307
+
308
+
215
309
  # ---------------------------------------------------------------------------
216
310
  # DOCTOR
217
311
  # ---------------------------------------------------------------------------
@@ -923,4 +1017,4 @@ def main() -> None:
923
1017
 
924
1018
 
925
1019
  if __name__ == "__main__":
926
- main()
1020
+ main()
@@ -183,3 +183,51 @@ def cog_cmd(input, compress, blocksize, output):
183
183
  e = _engine()
184
184
  r = e.post.cog(input, compress=compress, blocksize=blocksize, output=output)
185
185
  _pr(r, f"COG ({compress}, {blocksize}px)")
186
+
187
+
188
+ @post.command("overlay")
189
+ @click.argument("left", type=click.Path(exists=True))
190
+ @click.argument("right", type=click.Path(exists=True))
191
+ @click.option("--output", "-o", required=True)
192
+ @click.option("--how", default="intersection", show_default=True,
193
+ type=click.Choice(["intersection", "union", "difference", "symmetric_difference", "identity"]))
194
+ def overlay_cmd(left, right, output, how):
195
+ """Real polygon overlay between two vector layers.
196
+
197
+ Example: pygeofetch post overlay flood.geojson parcels.geojson -o flooded_parcels.geojson --how intersection
198
+ """
199
+ e = _engine()
200
+ r = e.post.overlay(left, right, output=output, how=how)
201
+ _pr(r, f"overlay ({how})")
202
+
203
+
204
+ @post.command("dissolve")
205
+ @click.argument("input", type=click.Path(exists=True))
206
+ @click.option("--output", "-o", required=True)
207
+ @click.option("--by", default=None, help="Attribute column to group by before dissolving.")
208
+ @click.option("--aggfunc", default="first", show_default=True, help="How to aggregate other columns within each group.")
209
+ def dissolve_cmd(input, output, by, aggfunc):
210
+ """Merge polygons sharing the same attribute value into single geometries.
211
+
212
+ Example: pygeofetch post dissolve parcels.geojson -o zones.geojson --by land_use
213
+ """
214
+ e = _engine()
215
+ r = e.post.dissolve(input, output=output, by=by, aggfunc=aggfunc)
216
+ _pr(r, f"dissolve (by={by})")
217
+
218
+
219
+ @post.command("spatial-join")
220
+ @click.argument("left", type=click.Path(exists=True))
221
+ @click.argument("right", type=click.Path(exists=True))
222
+ @click.option("--output", "-o", required=True)
223
+ @click.option("--how", default="inner", show_default=True, type=click.Choice(["inner", "left", "right"]))
224
+ @click.option("--predicate", default="intersects", show_default=True,
225
+ type=click.Choice(["intersects", "contains", "within", "touches", "crosses", "overlaps"]))
226
+ def spatial_join_cmd(left, right, output, how, predicate):
227
+ """Attach attributes from one vector layer to another by real spatial relationship.
228
+
229
+ Example: pygeofetch post spatial-join points.geojson zones.geojson -o joined.geojson --predicate within
230
+ """
231
+ e = _engine()
232
+ r = e.post.spatial_join(left, right, output=output, how=how, predicate=predicate)
233
+ _pr(r, f"spatial-join ({predicate})")
@@ -40,6 +40,7 @@ from pygeofetch.models.satellite_data import SatelliteData
40
40
  from pygeofetch.processing.batch import BatchProcessor
41
41
  from pygeofetch.processing.indices import SpectralIndices
42
42
  from pygeofetch.processing.postprocessor import PostProcessor
43
+ from pygeofetch.processing.classification import Classifier
43
44
  from pygeofetch.processing.preprocessor import Preprocessor
44
45
  from pygeofetch.processing.sar import SARProcessor
45
46
  from pygeofetch.utils.geo_utils import _normalise_satellite_name
@@ -156,6 +157,7 @@ class PyGeoFetch:
156
157
  self.preprocess = Preprocessor()
157
158
  self.indices = SpectralIndices()
158
159
  self.post = PostProcessor()
160
+ self.classify = Classifier()
159
161
  self.sar = SARProcessor()
160
162
  self.batch = BatchProcessor(engine=self)
161
163