PyGeoFetch 2.6.2.8__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.
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/CHANGELOG.md +7 -7
- {pygeofetch-2.6.2.8/pygeofetch.egg-info → pygeofetch-2.6.3.0}/PKG-INFO +2 -2
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/README.md +1 -1
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/__init__.py +1 -1
- pygeofetch-2.6.3.0/pygeofetch/cli/classify_commands.py +112 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/index_commands.py +161 -30
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/main.py +95 -1
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/postprocess_commands.py +48 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/engine.py +2 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/searcher.py +32 -87
- pygeofetch-2.6.3.0/pygeofetch/insar/feasibility.py +306 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/meta.yaml +1 -1
- pygeofetch-2.6.3.0/pygeofetch/processing/base.py +651 -0
- pygeofetch-2.6.3.0/pygeofetch/processing/classification.py +367 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/indices.py +444 -61
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/postprocessor.py +169 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/aws_earth.py +10 -1
- pygeofetch-2.6.3.0/pygeofetch/utils/geocoding.py +132 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0/pygeofetch.egg-info}/PKG-INFO +2 -2
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/SOURCES.txt +4 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pyproject.toml +1 -1
- pygeofetch-2.6.2.8/pygeofetch/processing/base.py +0 -338
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/LICENSE +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/MANIFEST.in +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/auth_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/config_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/download_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/monitor_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/multisensor_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/optical_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/pipeline_process_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/preprocess_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/sar_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/cli/search_commands.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/config/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/config/defaults.yaml +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/config/settings.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/authenticator.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/cache_manager.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/data_organizer.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/downloader.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/logging.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/monitor.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/orbits.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/scheduler.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/core/state.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/geotech/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/geotech/risk_mapper.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/advanced_safeguards.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/analysis.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/annotation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/atmosphere.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/auto_fallback.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/coregister.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/deburst.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/esd.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/extraction.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/flatearth.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/geolocation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/gpu.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/interferogram.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/ionosphere.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/offset_geometry.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/offset_tracking.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/preflight.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/provenance.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/ps_selection.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/stack_selection.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/synthetic.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/timeseries.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/unwrap.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/validate.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/visualize.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/insar/workflow.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/models/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/models/download_task.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/models/satellite_data.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/models/search_query.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/models/user_auth.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/_init.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/dem_differencing.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/flood_mapping.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/optical/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/optical/offset_tracking.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/batch.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/preprocessor.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/sar.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processing/uq_exporter.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/indices.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/landsat.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/loader.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/stacker.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/processor/timeseries.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/airbus_oneatlas.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/base.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/copernicus.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/copernicus_nodes.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/digitalglobe.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/earth_explorer_additional.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/element84.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/eodag_provider.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/esa_scihub.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/geoserver_generic.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/google_earth_engine.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/inpe_cbers.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/isro_bhuvan.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/jaxa_earth.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/maxar_gbdx.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/nasa_earthdata.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/noaa_big_data.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/opentopography.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/planet.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/planetary_computer.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/sentinel_hub.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/terrabotics.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/providers/usgs.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/_native.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/_ost.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/_sarxarray.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/extraction.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/math.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/pipelines.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/sar/processor.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/stac_compute/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/file_utils.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/gcp_recovery.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/geo_utils.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/logging_setup.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/retry_handler.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/sar_math.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/utils/validators.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/validation/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/validation/optical_validator.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/viz/__init__.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/viz/map.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch/viz/plot.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/dependency_links.txt +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/entry_points.txt +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/not-zip-safe +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/requires.txt +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/pygeofetch.egg-info/top_level.txt +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/setup.cfg +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_advanced_safeguards.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_airbus_oneatlas.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_alaska_satellite_facility.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_atmospheric_circular_regression.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_auto_fallback.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_auto_fallback_fixed.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_build_sbas_network-new.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_build_sbas_network.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_burst_metadata.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_burst_processing_integration.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_burst_sync_large_gap.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_burst_sync_prescreening.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_burst_synchronization.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_circuit_breaker_wiring.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_cli_optical_validation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_copernicus_nodes.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_coregister_integration.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_coregister_upgrades (1).py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_coregister_upgrades.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_credential_encryption.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_data_organizer.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_dem_differencing_pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_digitalglobe.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_earth_explorer_additional.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_era5_atmospheric_correction.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_esa_scihub.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_extraction.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_flat_earth_phase.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_flood_mapping_pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_gcp_reprojection.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_geology_indices_and_rvi_fix.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_geoserver_generic.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_google_earth_engine.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_inpe_cbers.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_insar.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_insar_optical_fusion_pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_ionosphere.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_isro_bhuvan.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_jaxa_earth.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_lightweight_prefetch.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_map_preview_consolidation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_models.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_monitor.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_nasa_earthdata_public_search.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_noaa_big_data.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_offset_tracking.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_opentopography_pointcloud_usgs.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_optical_offset_tracking.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_optical_validation_wiring.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_optical_validator.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_optical_validator_stac.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_pgv_integration_contract.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_pipeline_process_export.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_planet_asset_type_fix.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_preflight_gate.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_preflight_spatial_baseline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_provider_geometry_audit.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_provider_scene_disambiguation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_providers.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_ps_selection.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_raster_collocation_integration.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_reference_pixel_and_despike.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_risk_mapper.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_risk_mapping.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_sar_pipelines.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_screen_stack_orchestration.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_search_download.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_sentinel_hub_catalog_fix.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_sign_convention.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_stack_selection_consolidation.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_state.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_strain_and_fusion.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_terrain_corrected_change_pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_topo_r2_bug.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_uq_exporter.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_utils.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_vegetation_disturbance_pipeline.py +0 -0
- {pygeofetch-2.6.2.8 → pygeofetch-2.6.3.0}/tests/test_viz_plot.py +0 -0
- {pygeofetch-2.6.2.8 → 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
410
|
-
2026-07-29 and 2026-07-12. **2.6.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -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 = (
|
|
@@ -50,22 +81,67 @@ def index() -> None:
|
|
|
50
81
|
"--nir", required=True, type=click.Path(exists=True), help="NIR band (e.g. B08.tif)"
|
|
51
82
|
)
|
|
52
83
|
@click.option("--output", "-o", default=None)
|
|
53
|
-
|
|
54
|
-
""
|
|
84
|
+
@click.option(
|
|
85
|
+
"--chunked", is_flag=True,
|
|
86
|
+
help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
|
|
87
|
+
)
|
|
88
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
|
|
89
|
+
@clip_options
|
|
90
|
+
def ndvi_cmd(red, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
91
|
+
"""NDVI — Normalized Difference Vegetation Index. Range: -1 to +1. Vegetation > 0.3.
|
|
92
|
+
|
|
93
|
+
Pass the SAME path to --red and --nir to auto-detect both bands from
|
|
94
|
+
one merged multi-band image (e.g. a drone orthomosaic), using its
|
|
95
|
+
real, embedded band descriptions.
|
|
96
|
+
"""
|
|
55
97
|
e = _engine()
|
|
56
|
-
r = e.indices.ndvi(red=red, nir=nir, output=output)
|
|
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)
|
|
57
99
|
_pr(r, "NDVI")
|
|
58
100
|
|
|
59
101
|
|
|
102
|
+
@index.command("ndre")
|
|
103
|
+
@click.option(
|
|
104
|
+
"--rededge", required=True, type=click.Path(exists=True), help="Red edge band"
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--nir", required=True, type=click.Path(exists=True), help="NIR band"
|
|
108
|
+
)
|
|
109
|
+
@click.option("--output", "-o", default=None)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--chunked", is_flag=True,
|
|
112
|
+
help="Process in memory-safe tiles for large rasters that exceed available RAM or the 4GB GeoTIFF limit.",
|
|
113
|
+
)
|
|
114
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
|
|
115
|
+
@clip_options
|
|
116
|
+
def ndre_cmd(rededge, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
117
|
+
"""NDRE — Normalized Difference Red Edge Index. Range: -1 to +1.
|
|
118
|
+
|
|
119
|
+
Less prone to saturation than NDVI in dense canopy -- useful for
|
|
120
|
+
crop stress and chlorophyll monitoring from drone multispectral
|
|
121
|
+
sensors. Pass the SAME path to --rededge and --nir to auto-detect
|
|
122
|
+
both bands from one merged multi-band image.
|
|
123
|
+
"""
|
|
124
|
+
e = _engine()
|
|
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)
|
|
126
|
+
_pr(r, "NDRE")
|
|
127
|
+
|
|
128
|
+
|
|
60
129
|
@index.command("evi")
|
|
61
130
|
@click.option("--blue", required=True, type=click.Path(exists=True))
|
|
62
131
|
@click.option("--red", required=True, type=click.Path(exists=True))
|
|
63
132
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
64
133
|
@click.option("--output", "-o", default=None)
|
|
65
|
-
|
|
66
|
-
|
|
134
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
135
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
136
|
+
@clip_options
|
|
137
|
+
def evi_cmd(blue, red, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
138
|
+
"""EVI — Enhanced Vegetation Index. Better than NDVI in high-biomass areas.
|
|
139
|
+
|
|
140
|
+
Pass the SAME path to --blue, --red, and --nir to auto-detect all
|
|
141
|
+
three bands from one merged multi-band image.
|
|
142
|
+
"""
|
|
67
143
|
e = _engine()
|
|
68
|
-
r = e.indices.evi(blue=blue, red=red, nir=nir, output=output)
|
|
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)
|
|
69
145
|
_pr(r, "EVI")
|
|
70
146
|
|
|
71
147
|
|
|
@@ -74,10 +150,17 @@ def evi_cmd(blue, red, nir, output):
|
|
|
74
150
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
75
151
|
@click.option("--soil-l", default=0.5, show_default=True, type=float)
|
|
76
152
|
@click.option("--output", "-o", default=None)
|
|
77
|
-
|
|
78
|
-
|
|
153
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
154
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
155
|
+
@clip_options
|
|
156
|
+
def savi_cmd(red, nir, soil_l, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
157
|
+
"""SAVI — Soil Adjusted Vegetation Index. Reduces soil brightness influence.
|
|
158
|
+
|
|
159
|
+
Pass the SAME path to --red and --nir to auto-detect both bands
|
|
160
|
+
from one merged multi-band image.
|
|
161
|
+
"""
|
|
79
162
|
e = _engine()
|
|
80
|
-
r = e.indices.savi(red=red, nir=nir, L=soil_l, output=output)
|
|
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)
|
|
81
164
|
_pr(r, "SAVI")
|
|
82
165
|
|
|
83
166
|
|
|
@@ -85,10 +168,17 @@ def savi_cmd(red, nir, soil_l, output):
|
|
|
85
168
|
@click.option("--green", required=True, type=click.Path(exists=True))
|
|
86
169
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
87
170
|
@click.option("--output", "-o", default=None)
|
|
88
|
-
|
|
89
|
-
|
|
171
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
172
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
173
|
+
@clip_options
|
|
174
|
+
def ndwi_cmd(green, nir, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
175
|
+
"""NDWI — Normalized Difference Water Index. Water > 0.
|
|
176
|
+
|
|
177
|
+
Pass the SAME path to --green and --nir to auto-detect both bands
|
|
178
|
+
from one merged multi-band image.
|
|
179
|
+
"""
|
|
90
180
|
e = _engine()
|
|
91
|
-
r = e.indices.ndwi(green=green, nir=nir, output=output)
|
|
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)
|
|
92
182
|
_pr(r, "NDWI")
|
|
93
183
|
|
|
94
184
|
|
|
@@ -96,10 +186,17 @@ def ndwi_cmd(green, nir, output):
|
|
|
96
186
|
@click.option("--green", required=True, type=click.Path(exists=True))
|
|
97
187
|
@click.option("--swir1", required=True, type=click.Path(exists=True))
|
|
98
188
|
@click.option("--output", "-o", default=None)
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
190
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
191
|
+
@clip_options
|
|
192
|
+
def mndwi_cmd(green, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
193
|
+
"""MNDWI — Modified NDWI. Better separation of water from built-up areas.
|
|
194
|
+
|
|
195
|
+
Pass the SAME path to --green and --swir1 to auto-detect both
|
|
196
|
+
bands from one merged multi-band image.
|
|
197
|
+
"""
|
|
101
198
|
e = _engine()
|
|
102
|
-
r = e.indices.mndwi(green=green, swir1=swir1, output=output)
|
|
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)
|
|
103
200
|
_pr(r, "MNDWI")
|
|
104
201
|
|
|
105
202
|
|
|
@@ -107,10 +204,17 @@ def mndwi_cmd(green, swir1, output):
|
|
|
107
204
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
108
205
|
@click.option("--swir1", required=True, type=click.Path(exists=True))
|
|
109
206
|
@click.option("--output", "-o", default=None)
|
|
110
|
-
|
|
111
|
-
|
|
207
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
208
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
209
|
+
@clip_options
|
|
210
|
+
def ndbi_cmd(nir, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
211
|
+
"""NDBI — Normalized Difference Built-up Index. Urban > 0.
|
|
212
|
+
|
|
213
|
+
Pass the SAME path to --nir and --swir1 to auto-detect both bands
|
|
214
|
+
from one merged multi-band image.
|
|
215
|
+
"""
|
|
112
216
|
e = _engine()
|
|
113
|
-
r = e.indices.ndbi(nir=nir, swir1=swir1, output=output)
|
|
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)
|
|
114
218
|
_pr(r, "NDBI")
|
|
115
219
|
|
|
116
220
|
|
|
@@ -118,10 +222,17 @@ def ndbi_cmd(nir, swir1, output):
|
|
|
118
222
|
@click.option("--green", required=True, type=click.Path(exists=True))
|
|
119
223
|
@click.option("--swir1", required=True, type=click.Path(exists=True))
|
|
120
224
|
@click.option("--output", "-o", default=None)
|
|
121
|
-
|
|
122
|
-
|
|
225
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
226
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
227
|
+
@clip_options
|
|
228
|
+
def ndsi_cmd(green, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
229
|
+
"""NDSI — Snow Index. Snow > 0.4.
|
|
230
|
+
|
|
231
|
+
Pass the SAME path to --green and --swir1 to auto-detect both
|
|
232
|
+
bands from one merged multi-band image.
|
|
233
|
+
"""
|
|
123
234
|
e = _engine()
|
|
124
|
-
r = e.indices.ndsi(green=green, swir1=swir1, output=output)
|
|
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)
|
|
125
236
|
_pr(r, "NDSI")
|
|
126
237
|
|
|
127
238
|
|
|
@@ -129,10 +240,17 @@ def ndsi_cmd(green, swir1, output):
|
|
|
129
240
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
130
241
|
@click.option("--swir1", required=True, type=click.Path(exists=True))
|
|
131
242
|
@click.option("--output", "-o", default=None)
|
|
132
|
-
|
|
133
|
-
|
|
243
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
244
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
245
|
+
@clip_options
|
|
246
|
+
def ndmi_cmd(nir, swir1, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
247
|
+
"""NDMI — Moisture Index. Sensitive to canopy water content.
|
|
248
|
+
|
|
249
|
+
Pass the SAME path to --nir and --swir1 to auto-detect both bands
|
|
250
|
+
from one merged multi-band image.
|
|
251
|
+
"""
|
|
134
252
|
e = _engine()
|
|
135
|
-
r = e.indices.ndmi(nir=nir, swir1=swir1, output=output)
|
|
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)
|
|
136
254
|
_pr(r, "NDMI")
|
|
137
255
|
|
|
138
256
|
|
|
@@ -140,10 +258,17 @@ def ndmi_cmd(nir, swir1, output):
|
|
|
140
258
|
@click.option("--nir", required=True, type=click.Path(exists=True))
|
|
141
259
|
@click.option("--swir2", required=True, type=click.Path(exists=True))
|
|
142
260
|
@click.option("--output", "-o", default=None)
|
|
143
|
-
|
|
144
|
-
|
|
261
|
+
@click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
|
|
262
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int)
|
|
263
|
+
@clip_options
|
|
264
|
+
def nbr_cmd(nir, swir2, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
265
|
+
"""NBR — Normalized Burn Ratio. Use dNBR for burn severity.
|
|
266
|
+
|
|
267
|
+
Pass the SAME path to --nir and --swir2 to auto-detect both bands
|
|
268
|
+
from one merged multi-band image.
|
|
269
|
+
"""
|
|
145
270
|
e = _engine()
|
|
146
|
-
r = e.indices.nbr(nir=nir, swir2=swir2, output=output)
|
|
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)
|
|
147
272
|
_pr(r, "NBR")
|
|
148
273
|
|
|
149
274
|
|
|
@@ -262,10 +387,16 @@ def albedo_cmd(inputs, sensor, output):
|
|
|
262
387
|
"--expr", "-e", required=True, help="Python expression. Bands as B[0], B[1], ..."
|
|
263
388
|
)
|
|
264
389
|
@click.option("--output", "-o", default=None)
|
|
265
|
-
|
|
390
|
+
@click.option(
|
|
391
|
+
"--chunked", is_flag=True,
|
|
392
|
+
help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
|
|
393
|
+
)
|
|
394
|
+
@click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
|
|
395
|
+
@clip_options
|
|
396
|
+
def band_math_cmd(inputs, expr, output, chunked, tile_size, bbox, geometry, geometry_crs):
|
|
266
397
|
"""Arbitrary band arithmetic. Example: --expr '(B[1]-B[0])/(B[1]+B[0])'."""
|
|
267
398
|
e = _engine()
|
|
268
|
-
r = e.indices.band_math(inputs=list(inputs), expression=expr, output=output)
|
|
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)
|
|
269
400
|
_pr(r, f"band-math ({expr[:40]})")
|
|
270
401
|
|
|
271
402
|
|
|
@@ -276,4 +407,4 @@ def stack_cmd(inputs, output):
|
|
|
276
407
|
"""Stack single-band rasters into a multi-band GeoTIFF."""
|
|
277
408
|
e = _engine()
|
|
278
409
|
r = e.indices.stack(inputs=list(inputs), output=output)
|
|
279
|
-
_pr(r, f"stack ({len(inputs)} bands)")
|
|
410
|
+
_pr(r, f"stack ({len(inputs)} bands)")
|
|
@@ -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})")
|