Compare commits
3 Commits
25969cb0f5
...
a4ad11742a
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ad11742a | |||
| f2c11079e4 | |||
| b5b600023e |
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/remote-sensing.iml" filepath="$PROJECT_DIR$/.idea/remote-sensing.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+48
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="8512e5bb-2b73-4d09-a37b-d0b357b3fbe9" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"associatedIndex": 1
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="39nRCQRaBb6bqtrPoIoBjpMe8Bs" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "dev__01",
|
||||
"last_opened_file_path": "//wsl.localhost/Ubuntu-22.04/home/x79/remote-sensing"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-python-sdk-4762d8aabb82-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.30387.173" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="8512e5bb-2b73-4d09-a37b-d0b357b3fbe9" name="Changes" comment="" />
|
||||
<created>1771329720024</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1771329720024</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,638 @@
|
||||
# GEMINI PROJECT CONTEXT - Land Classification & Remote Sensing System
|
||||
|
||||
**Last Updated**: March 26, 2026
|
||||
**Project Location**: `/home/x79/remote-sensing`
|
||||
**Purpose**: Complete land classification and environmental monitoring system using satellite remote sensing for Vietnam
|
||||
|
||||
---
|
||||
|
||||
## 📋 PROJECT OVERVIEW
|
||||
|
||||
### High-Level Purpose & Problem Domain
|
||||
- **Core Task**: Classify land use/land cover (8 land classes) in Vietnam using multispectral Sentinel-2 and radar Sentinel-1 data from Microsoft Planetary Computer
|
||||
- **Geographic Focus**: Vietnam provinces/regions with bounding-box (bbox) based Area-of-Interest (AOI) selection
|
||||
- **Key Capabilities**:
|
||||
- Dynamic training with user-selected regions and time periods
|
||||
- Pixel-wise inference (prediction) on new regions
|
||||
- Cloud removal using 7 different strategies
|
||||
- NDVI time-series forecasting and change detection workflows
|
||||
- Auto-generated HTML reports with visualizations
|
||||
- Batch processing of multiple regions
|
||||
- Model lifecycle management (save, load, validate, delete)
|
||||
|
||||
### Data Pipeline
|
||||
```
|
||||
Sentinel-2 (optical) + Sentinel-1 (SAR)
|
||||
↓
|
||||
[Feature Extraction: 4 modes - simple (3) / temporal (39) / extended (15) / odc (8)]
|
||||
↓
|
||||
[Model Training: XGBoost, RF, SVM, CNN, Swin-UNet, MobileNet-LRASPP]
|
||||
↓
|
||||
[Prediction: Pixel-wise classification]
|
||||
↓
|
||||
[Output: GeoTIFF + PNG preview + HTML report + JSON metadata]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ SYSTEM ARCHITECTURE
|
||||
|
||||
### Core Technology Stack
|
||||
- **Backend**: FastAPI (~4200 lines in `api_server.py`)
|
||||
- **ML Training**: scikit-learn (XGBoost, RF, SVM, DT) + PyTorch (CNN, Swin-UNet, MobileNet)
|
||||
- **Geospatial**: rasterio, rioxarray, geopandas, xarray, odc.stac
|
||||
- **Data Access**: Microsoft Planetary Computer STAC API (Sentinel-2 L2A, Sentinel-1 RTC)
|
||||
- **Frontend**: HTML + Leaflet.js (map drawing) + Fetch API + Chart.js
|
||||
- **GPU Support**: PyTorch with CUDA 12.x (optional fallback to CPU)
|
||||
|
||||
### Folder Structure
|
||||
```
|
||||
remote-sensing/
|
||||
├── Core Backend
|
||||
│ ├── api_server.py # FastAPI app (~4200 LOC, 70+ endpoints)
|
||||
│ ├── train_module.py # Training pipeline engine
|
||||
│ ├── feature_extractor.py # Unified feature extraction (4 modes)
|
||||
│ ├── model_manager.py # Model lifecycle management
|
||||
│ ├── cloud_removal.py # 7 cloud removal strategies
|
||||
│ ├── report_generator.py # Auto HTML/PNG report generation
|
||||
│ ├── generate_previews.py # GeoTIFF → PNG conversion
|
||||
│ │
|
||||
├── Utilities & Lookup
|
||||
│ ├── vietnam_provinces.py # Province bboxes & metadata
|
||||
│ ├── vietnam_provinces_merged.py # 32-province variant
|
||||
│ ├── utils.py # Geospatial helper functions
|
||||
│ ├── create_odc_metadata.py # Metadata generator utility
|
||||
│ │
|
||||
├── Frontend Pages (HTML)
|
||||
│ ├── index.html # Main dashboard hub
|
||||
│ ├── training_interface.html # Training UI
|
||||
│ ├── prediction_interface.html # Prediction UI
|
||||
│ ├── batch_interface.html # Batch processing UI
|
||||
│ ├── ndvi_interface.html # NDVI time-series UI
|
||||
│ ├── dashboard.html # Analytics dashboard
|
||||
│ ├── reports_interface.html # Reports management
|
||||
│ ├── change_detection_interface.html # Change detection UI
|
||||
│ ├── cloud_training_interface.html # Cloud removal training UI
|
||||
│ │
|
||||
├── Tests & Notebooks
|
||||
│ ├── test_*.py # Unit & integration tests
|
||||
│ ├── 01.train_ODC*.ipynb # Training notebooks
|
||||
│ ├── 02.predict_ODC.ipynb # Prediction notebooks
|
||||
│ ├── cloud_removal_train.ipynb # Cloud removal training
|
||||
│ │
|
||||
├── Model Storage & Caches
|
||||
│ ├── model_train/ # Trained models (*.joblib, *.pth)
|
||||
│ │ ├── model_odc.joblib # Legacy GridSearchCV model
|
||||
│ │ ├── model_*_info.json # Metadata sidecar files
|
||||
│ ├── cloud_removal_model/ # Cloud removal U-Net models (.pth)
|
||||
│ ├── predictions/ # Prediction output (GeoTIFF + PNG)
|
||||
│ ├── reports/ # Generated HTML reports
|
||||
│ ├── dataset_cache/ # Cached Sentinel data (optional)
|
||||
│ │
|
||||
├── Config & Documentation
|
||||
│ ├── requirement.txt # Python dependencies
|
||||
│ ├── requirements_api.txt # API-specific deps
|
||||
│ ├── IMPLEMENTATION_SUMMARY.md # Model manager summary
|
||||
│ ├── MODEL_MANAGER_GUIDE.md # Full model management guide
|
||||
│ ├── NDVI_FORECAST_METHODOLOGY.md # NDVI algorithm docs
|
||||
│ ├── CLOUD_TRAINING_GUIDE.md # Cloud removal training guide
|
||||
│ └── [Other guides & docs]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 MAIN MODULES & RESPONSIBILITIES
|
||||
|
||||
| **Module** | **File(s)** | **Key Responsibility** |
|
||||
|---|---|---|
|
||||
| **API Server** | `api_server.py` | FastAPI app with 70+ endpoints; routes all training, prediction, batch, cloud removal, dashboard, reports, model management tasks |
|
||||
| **Training Engine** | `train_module.py` | Complete training pipeline: fetch data → feature extraction → train/test split → model training → evaluation → save with metadata |
|
||||
| **Feature Extraction** | `feature_extractor.py` | Standardized feature extraction with 4 modes: simple, temporal, extended, odc; used by both training and prediction |
|
||||
| **Model Manager** | `model_manager.py` | Lifecycle management: list, load, save, validate, delete models; handles metadata JSON; auto-detects CNN/PyTorch models |
|
||||
| **Cloud Removal** | `cloud_removal.py` | 7 cloud removal strategies: classic (3-step), temporal_only, median_composite, none, speckle filter, ML inpainting, deep learning U-Net |
|
||||
| **Report Generator** | `report_generator.py` | Auto-generates HTML/PNG reports with confusion matrices, class distributions, accuracy trends |
|
||||
| **Preview Generator** | `generate_previews.py` | Converts GeoTIFF outputs to PNG previews (NDVI or classification rasters) |
|
||||
| **Province Lookup** | `vietnam_provinces*.py` | Lookup tables for 32+ Vietnamese provinces with bboxes and region grouping |
|
||||
| **Utilities** | `utils.py` | Geospatial helper functions (load GeoDataFrames, etc.) |
|
||||
|
||||
---
|
||||
|
||||
## 📊 END-TO-END WORKFLOWS
|
||||
|
||||
### 1. TRAINING WORKFLOW
|
||||
```
|
||||
User Input → Training Configuration
|
||||
↓
|
||||
API Endpoint: POST /api/training/start
|
||||
↓
|
||||
train_module.py: train_model()
|
||||
1. Fetch Sentinel-2 & Sentinel-1 from Planetary Computer STAC
|
||||
2. Apply cloud mask (SCL band: clouds, shadows, cirrus masked)
|
||||
3. Extract features via FeatureExtractor (mode: simple/temporal/extended/odc)
|
||||
4. Train/test split (default 0.2)
|
||||
5. Train selected model type (XGBoost, RF, CNN, Swin-UNet, MobileNet)
|
||||
6. Evaluate: accuracy, precision, recall, F1, confusion matrix
|
||||
↓
|
||||
model_manager.py: Save model + JSON metadata
|
||||
↓
|
||||
report_generator.py: Auto-generate HTML training report
|
||||
↓
|
||||
Return: {model_filename, accuracy_metrics, training_time}
|
||||
```
|
||||
|
||||
**Key Metadata Saved**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-03-26T14:30:00",
|
||||
"model_type": "xgboost",
|
||||
"feature_mode": "temporal",
|
||||
"n_features": 39,
|
||||
"n_classes": 8,
|
||||
"features": ["NDVI_t1", "NDVI_t2", ..., "NDWI_t1", ...],
|
||||
"test_accuracy": 0.85,
|
||||
"train_accuracy": 0.92,
|
||||
"bbox": [105.6, 9.3, 106.2, 9.8],
|
||||
"time_range": "2023-03-01/2023-05-31",
|
||||
"resolution": 20,
|
||||
"data_source": "Microsoft Planetary Computer STAC"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. PREDICTION WORKFLOW
|
||||
```
|
||||
User Input → Prediction Configuration (model_filename, bbox, time_range, cloud_strategy)
|
||||
↓
|
||||
API Endpoint: POST /api/predict or POST /api/predict/with-ndvi
|
||||
↓
|
||||
run_prediction() function:
|
||||
1. Load model via model_manager.py (retrieves metadata, feature requirements)
|
||||
2. Fetch Sentinel-2 & Sentinel-1 for new region
|
||||
3. Apply chosen cloud_removal_method (classic/temporal_only/median_composite/none/deep_learning)
|
||||
4. Extract features matching model's metadata requirements
|
||||
5. Auto-adjust if feature count mismatch (pad/trim)
|
||||
6. Predict land class for each pixel
|
||||
7. (Optional) Calculate NDVI: (NIR - Red) / (NIR + Red)
|
||||
8. Save outputs: GeoTIFF + PNG preview
|
||||
↓
|
||||
generate_previews.py: Create PNG from GeoTIFF
|
||||
↓
|
||||
report_generator.py: Generate prediction report
|
||||
↓
|
||||
Return: {prediction_file, ndvi_file, class_distribution, statistics}
|
||||
```
|
||||
|
||||
### 3. BATCH PROCESSING WORKFLOW
|
||||
```
|
||||
User uploads CSV with multiple regions:
|
||||
(name, min_lon, min_lat, max_lon, max_lat, start_date, end_date, max_scenes, cloud_cover, resolution)
|
||||
↓
|
||||
API Endpoint: POST /api/batch/start
|
||||
↓
|
||||
Enqueue all regions; process sequentially
|
||||
↓
|
||||
For each region: Run same prediction workflow
|
||||
↓
|
||||
Track status per region: Queued → Running → Completed/Failed
|
||||
↓
|
||||
UI shows progress bar, auto-retry on failure (max 3 retries)
|
||||
↓
|
||||
Return: Bulk results with per-region status & output files
|
||||
```
|
||||
|
||||
### 4. CLOUD REMOVAL WORKFLOW
|
||||
```
|
||||
User selects cloud_removal_method in prediction config:
|
||||
↓
|
||||
cloud_removal.py: process_cloud_removal()
|
||||
↓
|
||||
Strategy Selection:
|
||||
• 'classic': temporal interpolation → median composite → spatial interpolation (3-step)
|
||||
• 'temporal_only': ffill + bfill across time dimension (fast, good for many scenes)
|
||||
• 'median_composite': Prioritize median across scenes (best for noise reduction)
|
||||
• 'none': Keep original, just fill NaN with 0
|
||||
• 'deep': Use trained U-Net model (S2 cloudy + S1 → clean S2)
|
||||
• 'ml_inpainting': KNN or Random Forest based inpainting
|
||||
• 'speckle_filter': Reduce radar noise
|
||||
↓
|
||||
Return cleaned Sentinel-2 data for subsequent feature extraction
|
||||
```
|
||||
|
||||
### 5. NDVI TIME-SERIES WORKFLOW
|
||||
```
|
||||
User requests NDVI calculation (bbox + time_range + aggregation)
|
||||
↓
|
||||
API Endpoint: POST /api/ndvi/timeseries or /api/ndvi/predict-timeseries
|
||||
↓
|
||||
Load Sentinel-2 (B04 Red, B08 NIR)
|
||||
↓
|
||||
Calculate NDVI = (NIR - Red) / (NIR + Red + 0.00001)
|
||||
↓
|
||||
Resample to monthly or user-defined aggregation
|
||||
↓
|
||||
Export as GeoTIFF + PNG visualization
|
||||
↓
|
||||
Show time-series graph & statistics (mean, min, max, std, trend)
|
||||
```
|
||||
|
||||
### 6. CHANGE DETECTION WORKFLOW
|
||||
```
|
||||
User selects: model + current_period + prediction_period
|
||||
↓
|
||||
API Endpoint: POST /api/change-detection/compare-periods
|
||||
↓
|
||||
Run prediction for both time periods
|
||||
↓
|
||||
Compute difference map (current - prediction)
|
||||
↓
|
||||
Classify changes: increased vegetation, decreased vegetation, stable
|
||||
↓
|
||||
Generate change map GeoTIFF + report with statistics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 API ENDPOINTS SUMMARY (70+ endpoints)
|
||||
|
||||
### Model Management
|
||||
- `GET /api/models/list` - List all trained models with metadata
|
||||
- `GET /api/models/{filename}/info` - Get model details
|
||||
- `GET /api/models/{filename}/validate` - Validate model integrity
|
||||
- `DELETE /api/models/{filename}` - Delete model file
|
||||
|
||||
### Training APIs
|
||||
- `POST /api/training/start` - Start land classification training
|
||||
- `GET /api/training/status` - Get training progress
|
||||
- `POST /api/training/stop` - Cancel ongoing training
|
||||
- `POST /api/cloud-removal/train` - Train cloud removal U-Net
|
||||
|
||||
### Prediction APIs
|
||||
- `POST /api/predict` - Standard prediction (classification only)
|
||||
- `POST /api/predict/with-ndvi` - Prediction with NDVI export
|
||||
- `POST /api/change-detection/compare-periods` - Change detection
|
||||
- `GET /api/prediction/status` - Check prediction progress
|
||||
- `GET /api/predictions/list` - List prediction outputs
|
||||
- `GET /api/predictions/download/{filename}` - Download prediction file
|
||||
- `GET /api/predictions/preview/{filename}` - View PNG preview
|
||||
|
||||
### Batch Processing
|
||||
- `POST /api/batch/start` - Enqueue multiple predictions from CSV
|
||||
- `GET /api/batch/status` - Check batch queue
|
||||
- `GET /api/batch/results/{batch_id}` - Retrieve batch results
|
||||
- `POST /api/batch/cancel/{batch_id}` - Cancel batch job
|
||||
|
||||
### Cloud Removal
|
||||
- `GET /api/cloud-removal/methods` - List available strategies
|
||||
- `GET /api/cloud-removal/models` - List trained .pth models
|
||||
- `POST /api/cloud-removal/upload` - Upload .pth cloud removal model
|
||||
- `DELETE /api/cloud-removal/models/{filename}` - Delete cloud removal model
|
||||
|
||||
### Dashboard & Reports
|
||||
- `GET /api/dashboard/statistics` - Overall system stats
|
||||
- `GET /api/dashboard/accuracy-trends` - Accuracy over time
|
||||
- `GET /api/dashboard/class-distribution/{model_filename}` - Class distribution
|
||||
- `GET /api/reports/list` - List generated reports
|
||||
- `GET /api/reports/view/{filename}` - View HTML report
|
||||
- `GET /api/reports/download/{filename}` - Download report
|
||||
- `DELETE /api/reports/delete/{filename}` - Delete report
|
||||
|
||||
### Provinces & Utilities
|
||||
- `GET /api/provinces/list` - List all Vietnamese provinces
|
||||
- `GET /api/provinces/by-region` - Group provinces by region
|
||||
- `GET /api/provinces/{province_name}/bbox` - Get province bbox
|
||||
- `GET /api/provinces/search/{query}` - Search province by name
|
||||
- `GET /api/provinces-32/*` - Alternative 32-province variant
|
||||
- `GET /api/network/check` - Check connectivity to Planetary Computer
|
||||
- `GET /api/cache/info` - Show cache statistics
|
||||
- `POST /api/cache/clear` - Clear local cache
|
||||
|
||||
### NDVI & Time-Series
|
||||
- `POST /api/ndvi/timeseries` - Calculate NDVI time-series
|
||||
- `POST /api/ndvi/predict-timeseries` - NDVI prediction/forecast
|
||||
- `POST /api/ndvi/forecast` - NDVI forecasting
|
||||
|
||||
### File Management
|
||||
- `GET /api/training/files` - List training files
|
||||
- `GET /api/overlay/shapefiles` - List available shapefiles
|
||||
- `GET /api/training/shapefile/{filename}/labels` - Get shapefile labels
|
||||
- `POST /api/land-classification/upload` - Upload custom model
|
||||
- `POST /api/cloud-removal/upload` - Upload cloud removal model
|
||||
|
||||
### Frontend Routes (Serve HTML)
|
||||
- `GET /` - Main dashboard
|
||||
- `GET /training` - Training interface
|
||||
- `GET /prediction` - Prediction interface
|
||||
- `GET /dashboard` - Analytics dashboard
|
||||
- `GET /batch` - Batch processing UI
|
||||
- `GET /ndvi` - NDVI time-series UI
|
||||
- `GET /reports` - Reports management
|
||||
- `GET /cloud-training` - Cloud removal training
|
||||
- `GET /change-detection` - Change detection UI
|
||||
|
||||
---
|
||||
|
||||
## 💾 DATA INPUTS / OUTPUTS & FOLDER CONVENTIONS
|
||||
|
||||
### Input Data Sources
|
||||
- **Sentinel-2 L2A** from Microsoft Planetary Computer STAC API
|
||||
- Bands: B02 (blue), B03 (green), B04 (red), B08 (NIR), B11 (SWIR), SCL (cloud mask)
|
||||
- Resolution: 10m or 20m (user selectable)
|
||||
- Collection: `sentinel-2-l2a`
|
||||
|
||||
- **Sentinel-1 RTC** from Planetary Computer
|
||||
- Bands: VH, VV (radar polarizations)
|
||||
- Converted to dB scale: `10 * log10(intensity)`
|
||||
- Collection: `sentinel-1-rtc`
|
||||
|
||||
- **Training Labels**: User-provided shapefiles with pixel-level class labels
|
||||
|
||||
### Output File Structure
|
||||
```
|
||||
predictions/
|
||||
├── prediction_YYYYMMDD_HHMMSS.tif # Classification GeoTIFF
|
||||
├── prediction_YYYYMMDD_HHMMSS.png # PNG preview
|
||||
├── ndvi_YYYYMMDD_HHMMSS.tif # NDVI raster
|
||||
├── ndvi_YYYYMMDD_HHMMSS.png # NDVI preview
|
||||
|
||||
reports/
|
||||
├── training_report_*.html # Auto training reports
|
||||
├── prediction_report_*.html # Auto prediction reports
|
||||
|
||||
model_train/
|
||||
├── model_odc.joblib # Legacy model
|
||||
├── model_odc_info.json # Metadata
|
||||
├── model_xgboost_*.joblib # XGBoost models
|
||||
├── model_xgboost_*_info.json # Metadata
|
||||
├── model_cnn_*.joblib # CNN models
|
||||
├── model_cnn_*_info.json # Metadata
|
||||
|
||||
cloud_removal_model/
|
||||
├── cloud_removal_unet_best.pth # Trained U-Net
|
||||
├── *.pth # Custom models
|
||||
├── *.json # Model metadata
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 EXTERNAL DEPENDENCIES & PLATFORMS
|
||||
|
||||
### Critical External Services
|
||||
- **Microsoft Planetary Computer** (STAC API)
|
||||
- Hosts Sentinel-2 L2A and Sentinel-1 RTC archives
|
||||
- URL: `https://planetarycomputer.microsoft.com/api/stac/v1`
|
||||
- Auto-signed access tokens via `planetary_computer.sign_inplace`
|
||||
- Network connectivity check: `GET /api/network/check`
|
||||
|
||||
### Key Python Libraries
|
||||
- **Geospatial**: rasterio, rioxarray, geopandas, shapely, Cartopy, folium, ipyleaflet
|
||||
- **Data Processing**: numpy, pandas, xarray, dask
|
||||
- **ML**: scikit-learn, xgboost
|
||||
- **Deep Learning**: torch, torchvision
|
||||
- **Web**: fastapi, uvicorn, pydantic
|
||||
- **Visualization**: matplotlib, Pillow (PIL)
|
||||
- **Document Gen**: markdown, Pillow
|
||||
|
||||
### GPU Support
|
||||
- PyTorch with CUDA 12.x (optional; falls back to CPU)
|
||||
- Benefits Swin-UNet and CNN models (10-100x speedup)
|
||||
- CPU training for XGBoost/RF typically <1 hour; deep models need GPU for reasonable speed
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ FEATURE EXTRACTION MODES (CRITICAL)
|
||||
|
||||
Train and prediction **MUST** use same feature mode and dimension; metadata auto-detects this.
|
||||
|
||||
| Mode | # Features | Description | Best For | Training Time |
|
||||
|---|---|---|---|---|
|
||||
| **simple** | 3 | NDVI_mean, VH_db_mean, VV_db_mean | Fast iteration, baseline | ~5-10 min |
|
||||
| **temporal** | 39 | NDVI/NDWI/NDBI across 13 months + radar stats | High accuracy (~85%+) | ~30-60 min |
|
||||
| **extended** | 15 | NDVI/NDWI/NDBI stats (mean/std/min/max) + radar | Balanced speed/accuracy | ~15-30 min |
|
||||
| **odc** | 8 | NDVI stats + NDWI/NDBI/EVI mean (legacy ODC mode) | Legacy compatibility | ~10-20 min |
|
||||
|
||||
**Critical**: If feature mode = "temporal" (39 features) at training, prediction MUST extract 39 features. System auto-detects from metadata but will fail if mismatched.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 OPERATIONAL NOTES & CONSTRAINTS
|
||||
|
||||
### Performance Limits
|
||||
1. **Planetary Computer Timeout Issues**
|
||||
- Large bbox (>10km × 10km) + long time range (>1 month) + high max_scenes → timeouts
|
||||
- **Solution**: Progressive loading (subdivide bbox), reduce time window, reduce max_scenes
|
||||
- **Safe Settings**: bbox ≤ 10km × 10km, time ≤ 1 month, max_scenes ≤ 12
|
||||
|
||||
2. **Memory Usage**
|
||||
- Temporal mode (39 features) requires ~2-3x RAM vs simple mode
|
||||
- Large regions: reduce resolution (10m → 20m) or split into sub-tiles
|
||||
- Batch processing: sequential (one region at a time due to API limits)
|
||||
|
||||
3. **GPU Training**
|
||||
- Swin-UNet: ~15-60 min on GPU vs ~2-4 hours on CPU
|
||||
- CNN: ~10-30 min on GPU vs ~1-2 hours on CPU
|
||||
- XGBoost/RF: CPU-bound; GPU not beneficial
|
||||
|
||||
### Data Quality Issues
|
||||
1. **Cloud Cover**
|
||||
- SCL band values: 3=cloud shadow, 8=cloud medium, 9=cloud high, 10=cirrus
|
||||
- Recommend multiple scenes (≥5) for temporal aggregation
|
||||
- Cloud removal strategy critical—test different approaches
|
||||
|
||||
2. **Radar Data (Sentinel-1)**
|
||||
- Not always available for all regions/dates
|
||||
- System gracefully falls back to zeros if unavailable
|
||||
- Safe for "extended" & "odc" modes that have radar fallback
|
||||
|
||||
3. **Feature Mode Mismatch**
|
||||
- Model trained with "temporal" (39 features) needs 39-dim input
|
||||
- System auto-adjusts (pads/trims) from metadata but may degrade accuracy
|
||||
- **Best Practice**: Align feature mode explicitly; don't mix
|
||||
|
||||
### Known Caveats
|
||||
1. **Legacy Model (model_odc.joblib)**: Hardcoded 39 temporal features; auto-detected via `model_odc_info.json`
|
||||
2. **Metadata Consistency**: Old models may lack `.json` sidecar; system generates default (may be incorrect)
|
||||
3. **Batch Processing**: Sequential only; large batches (100+ regions) take hours
|
||||
4. **Change Detection**: Simple differencing approach; requires same model & feature mode for both periods
|
||||
5. **Rate Limiting**: Planetary Computer may rate-limit if too many concurrent requests
|
||||
|
||||
### Recommended Best Practices
|
||||
- Test model on small bbox first (2km × 2km, 1 week, 3 scenes)
|
||||
- Use "simple" mode for fast iteration, "temporal" for best accuracy (85%+)
|
||||
- Store metadata JSON alongside model file (sidecar pattern)
|
||||
- Version control: record feature_mode & n_features in every training
|
||||
- Monitor training accuracy; retrain if <70% accuracy
|
||||
- Cache Sentinel data locally to avoid repeated downloads
|
||||
- Use "median_composite" cloud strategy if >5 scenes; "temporal_only" if 3-4 scenes
|
||||
|
||||
---
|
||||
|
||||
## 🔍 TEST COVERAGE MAP
|
||||
|
||||
| Test File | Coverage | Status |
|
||||
|---|---|---|
|
||||
| `test_model_manager.py` | ModelManager lifecycle (list, load, validate) | ✅ Well-tested |
|
||||
| `test_feature_extractor.py` | All 4 feature extraction modes | ✅ Well-tested |
|
||||
| `test_training_api.py` | Training API endpoints | ✅ Partial |
|
||||
| `test_cloud_removal.py` | 7 cloud removal strategies | ✅ Well-tested |
|
||||
| `test_cloud_training.py` | U-Net cloud removal training | ✅ Partial |
|
||||
| `test_shapefile_api.py` | Shapefile overlay feature | ✅ Partial |
|
||||
| `test_planetary_computer.py` | Planetary Computer STAC access | ✅ Well-tested |
|
||||
| `test_new_features.py` | Recent feature releases | ✅ Partial |
|
||||
| Jupyter Notebooks | Training & prediction workflows | ✅ Mix of unit/integration/notebooks |
|
||||
|
||||
**Coverage Notes**: Model management, feature extraction, and cloud removal well-tested; Dashboard UI, change detection, NDVI time-series mostly tested via notebooks.
|
||||
|
||||
---
|
||||
|
||||
## 📚 FILE REFERENCE MAP
|
||||
|
||||
### Core Execution
|
||||
- `api_server.py` — Main FastAPI application (~4200 LOC)
|
||||
- `train_module.py` — Training logic (data fetch → feature extraction → training)
|
||||
- `run_prediction_new.py` — Prediction execution function
|
||||
- `feature_extractor.py` — Unified feature extraction (4 modes)
|
||||
- `model_manager.py` — Model lifecycle (load/save/validate/list)
|
||||
- `cloud_removal.py` — Cloud removal strategies (7 methods)
|
||||
- `report_generator.py` — HTML/PNG report auto-generation
|
||||
- `generate_previews.py` — GeoTIFF → PNG conversion
|
||||
|
||||
### Data & Config
|
||||
- `vietnam_provinces.py` — 32+ province lookup tables & bboxes
|
||||
- `vietnam_provinces_merged.py` — Alternative 32-province variant
|
||||
- `utils.py` — Geospatial utility functions
|
||||
- `create_odc_metadata.py` — Legacy metadata generator
|
||||
|
||||
### Frontend
|
||||
- `index.html` — Main dashboard hub (tab navigation)
|
||||
- `training_interface.html` — Training configuration UI
|
||||
- `prediction_interface.html` — Prediction configuration UI
|
||||
- `batch_interface.html` — Batch processing (CSV upload)
|
||||
- `ndvi_interface.html` — NDVI time-series visualization
|
||||
- `dashboard.html` — Analytics & model performance dashboard
|
||||
- `reports_interface.html` — Report management & viewing
|
||||
- `change_detection_interface.html` — Change detection visualization
|
||||
- `cloud_training_interface.html` — Cloud removal U-Net training
|
||||
|
||||
### Documentation
|
||||
- `IMPLEMENTATION_SUMMARY.md` — Model manager & system overview
|
||||
- `MODEL_MANAGER_GUIDE.md` — Complete model management guide
|
||||
- `NDVI_FORECAST_METHODOLOGY.md` — NDVI algorithm documentation
|
||||
- `CLOUD_TRAINING_GUIDE.md` — Cloud removal training guide
|
||||
- `NDVI_PREDICTION_GUIDE.md` — NDVI prediction workflow
|
||||
- `CLOUD_PROCESSING.md` — Cloud processing notes
|
||||
- `UPDATE_SUMMARY.md` — Recent updates & features
|
||||
|
||||
---
|
||||
|
||||
## 🚀 BOOTSTRAP PROMPT FOR GEMINI
|
||||
|
||||
### System Context (Copy & Paste for Gemini)
|
||||
|
||||
```
|
||||
You are assisting a remote-sensing land-classification project for Vietnam.
|
||||
|
||||
## ARCHITECTURE SNAPSHOT
|
||||
- **Backend**: FastAPI (~4200 LOC, 70+ endpoints) for orchestrating training, prediction, batch, cloud removal, reporting
|
||||
- **Data Source**: Microsoft Planetary Computer STAC API (Sentinel-2 L2A + Sentinel-1 RTC)
|
||||
- **Training**: scikit-learn (XGBoost/RF/SVM/DT) + PyTorch (CNN/Swin-UNet/MobileNet)
|
||||
- **Feature Extraction**: 4 modes (simple 3-feat / temporal 39-feat / extended 15-feat / odc 8-feat)
|
||||
- **Cloud Removal**: 7 strategies (classic, temporal_only, median_composite, none, ML inpainting, deep U-Net)
|
||||
- **Output**: GeoTIFF + PNG + HTML report + JSON metadata
|
||||
|
||||
## CORE FILES TO UNDERSTAND (Priority Order)
|
||||
1. api_server.py — Main API server (training, prediction, batch, models, reports)
|
||||
2. train_module.py — Training pipeline (data fetch → feature extraction → train → save)
|
||||
3. feature_extractor.py — Unified feature extraction with auto mode detection
|
||||
4. model_manager.py — Model lifecycle (load/save/validate/list)
|
||||
5. cloud_removal.py — Cloud removal strategies (7 methods)
|
||||
6. report_generator.py — Auto-generate HTML reports
|
||||
7. run_prediction_new.py — Prediction execution
|
||||
8. vietnam_provinces.py — Province lookup & bbox tables
|
||||
|
||||
## CRITICAL CONSTRAINTS & GOTCHAS
|
||||
1. **Feature Mode Consistency**: Training & prediction MUST use same mode (simple/temporal/extended/odc)
|
||||
→ Auto-detected from metadata JSON
|
||||
→ Mismatch causes dimension error or accuracy degradation
|
||||
|
||||
2. **Planetary Computer Limits**:
|
||||
→ Timeout if bbox >10km×10km OR time range >1 month OR max_scenes >12
|
||||
→ Solution: subdivide bbox, reduce time window, limit scenes
|
||||
|
||||
3. **Cloud Strategy Selection**:
|
||||
→ ≥5 scenes → use "median_composite" (best noise reduction)
|
||||
→ 3-4 scenes → use "temporal_only" (fast temporal interp)
|
||||
→ <3 scenes → use "none" (skip cloud removal)
|
||||
|
||||
4. **Radar Data Fallback**:
|
||||
→ Sentinel-1 may be unavailable for some regions
|
||||
→ System gracefully falls back to zeros (safe for all modes)
|
||||
|
||||
5. **Model Metadata**:
|
||||
→ Always stored as `model_name_info.json` sidecar file
|
||||
→ Contains: n_features, feature_mode, features list, accuracy, bbox, time_range
|
||||
→ Missing metadata → system uses defaults (may be incorrect)
|
||||
|
||||
6. **Legacy Model (model_odc.joblib)**:
|
||||
→ Hardcoded 39 temporal features
|
||||
→ Metadata in model_odc_info.json
|
||||
|
||||
## REASONING CHECKLIST (before answering)
|
||||
□ Is feature_mode consistent between train and prediction?
|
||||
□ Is metadata.json present and correct?
|
||||
□ Does bbox exceed 10km×10km? (Planetary Computer timeout risk)
|
||||
□ Is cloud_removal_strategy appropriate for # of scenes?
|
||||
□ Is Sentinel-1 data available for this region/date?
|
||||
□ Is model a joblib (scikit-learn) or .pth (PyTorch) file?
|
||||
□ Is GPU available for deep models (CNN, Swin-UNet)?
|
||||
□ Does memory allow temporal feature extraction (39-feat)?
|
||||
|
||||
## RESPONSE FORMAT
|
||||
- Always cite api_server.py endpoint, function name, or module being discussed
|
||||
- Verify feature_mode & n_features from metadata JSON
|
||||
- Suggest cloud_removal_strategy based on # of scenes available
|
||||
- For unknown issues: offer alternative approaches (reduce bbox, cache results, use simpler model)
|
||||
- Explain reasoning using checklist above
|
||||
|
||||
## DATA FLOW SUMMARY
|
||||
Sentinel-2/S1 → [Cloud Remove] → [Feature Extract] → [Train/Predict] → [GeoTIFF + PNG + Report]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 QUICK REFERENCE CHECKLIST
|
||||
|
||||
### Before Troubleshooting Any Issue
|
||||
- [ ] Check feature_mode consistency (metadata JSON)
|
||||
- [ ] Verify metadata.json exists for the model
|
||||
- [ ] Check Planetary Computer connectivity (`GET /api/network/check`)
|
||||
- [ ] Review cloud_removal_method choice (≥5 scenes = median_composite)
|
||||
- [ ] Confirm Sentinel-1 availability (or fallback to zeros if missing)
|
||||
- [ ] Validate bbox size (≤10km×10km for safety)
|
||||
- [ ] Check memory usage for temporal feature mode
|
||||
- [ ] Verify GPU if using CNN/Swin-UNet models
|
||||
|
||||
### Common Issues & Solutions
|
||||
| Issue | Likely Cause | Solution |
|
||||
|---|---|---|
|
||||
| Training timeout | Large bbox / long time / many scenes | Subdivide bbox, reduce time window, max_scenes ≤ 12 |
|
||||
| Feature dimension mismatch | Different feature_mode between train & predict | Check metadata.json, ensure same mode |
|
||||
| Low prediction accuracy | Cloud cover, poor training data, feature mode too simple | Use "temporal" mode, increase training data, try cloud removal |
|
||||
| Out of memory | Temporal features + large region | Reduce resolution (20m), split into sub-tiles, increase RAM |
|
||||
| Model not found | Wrong filename or model_train/ path issue | `GET /api/models/list` to verify, check file path |
|
||||
| Planetary Computer error | Network issue or API rate limit | Check DNS, retry later, reduce concurrent requests |
|
||||
| Cloud removal failing | Strategy not suitable for scene count | Try "none" or "median_composite" depending on scenes |
|
||||
|
||||
---
|
||||
|
||||
## 🎓 LEARNING RESOURCES IN REPO
|
||||
|
||||
- **Notebooks**: `01.train_ODC.ipynb`, `02.predict_ODC.ipynb`, `cloud_removal_train.ipynb`
|
||||
- **Tests**: `test_*.py` files for unit test patterns
|
||||
- **Docs**: All `*.md` files for detailed guides and methodology
|
||||
- **Code Comments**: API server and modules heavily commented
|
||||
|
||||
---
|
||||
|
||||
**Generated**: March 26, 2026
|
||||
**For Use By**: Gemini, Claude, GPT, or any AI system needing project context
|
||||
**Maintainer**: Remote-Sensing Project Team
|
||||
|
||||
+291
-1
@@ -224,6 +224,9 @@ class PredictionConfig(BaseModel):
|
||||
# Cloud removal strategy
|
||||
cloud_removal_method: str = "classic"
|
||||
cloud_removal_model: Optional[str] = None # Optional: .pth model filename for deep learning cloud removal
|
||||
|
||||
# Shapefile overlay for visualization
|
||||
shapefile_overlay: Optional[str] = None # Path to shapefile for overlaying boundaries
|
||||
|
||||
|
||||
class TrainingStatus(BaseModel):
|
||||
@@ -286,6 +289,7 @@ class PredictionWithNDVIConfig(BaseModel):
|
||||
export_classification: bool = True # Export classification raster
|
||||
cloud_removal_method: str = "classic"
|
||||
cloud_removal_model: Optional[str] = None # Optional: .pth model filename for deep learning cloud removal
|
||||
shapefile_overlay: Optional[str] = None # Path to shapefile for overlaying boundaries
|
||||
|
||||
|
||||
class CloudRemovalTrainingConfig(BaseModel):
|
||||
@@ -1228,6 +1232,87 @@ async def list_training_files():
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/overlay/shapefiles")
|
||||
async def list_overlay_shapefiles():
|
||||
"""Liệt kê các shapefile có sẵn cho overlay trên prediction"""
|
||||
overlay_dirs = ["region", "ChauThanh", "ThuanHoa"]
|
||||
shapefiles = []
|
||||
|
||||
for overlay_dir in overlay_dirs:
|
||||
dir_path = Path(overlay_dir)
|
||||
if not dir_path.exists():
|
||||
continue
|
||||
|
||||
# Find all .shp files in this directory and subdirectories
|
||||
for shp_file in dir_path.rglob("*.shp"):
|
||||
try:
|
||||
file_size = shp_file.stat().st_size
|
||||
file_modified = datetime.fromtimestamp(shp_file.stat().st_mtime).isoformat()
|
||||
|
||||
# Try to read shapefile to get feature count and bbox
|
||||
import geopandas as gpd
|
||||
gdf = gpd.read_file(str(shp_file))
|
||||
feature_count = len(gdf)
|
||||
|
||||
# Calculate bbox (always in EPSG:4326 for consistency)
|
||||
bbox = None
|
||||
if not gdf.empty and gdf.crs:
|
||||
try:
|
||||
# Reproject to EPSG:4326 if needed
|
||||
if gdf.crs != "EPSG:4326":
|
||||
gdf_4326 = gdf.to_crs("EPSG:4326")
|
||||
else:
|
||||
gdf_4326 = gdf
|
||||
|
||||
# Get total bounds [minx, miny, maxx, maxy]
|
||||
bounds = gdf_4326.total_bounds
|
||||
if len(bounds) == 4:
|
||||
bbox = [
|
||||
float(bounds[0]), # min_lon
|
||||
float(bounds[1]), # min_lat
|
||||
float(bounds[2]), # max_lon
|
||||
float(bounds[3]) # max_lat
|
||||
]
|
||||
except Exception as bbox_error:
|
||||
print(f"[WARNING] Cannot calculate bbox for {shp_file}: {bbox_error}")
|
||||
|
||||
# Get relative path from workspace root
|
||||
relative_path = str(shp_file)
|
||||
|
||||
shapefiles.append({
|
||||
"filename": shp_file.name,
|
||||
"path": relative_path,
|
||||
"directory": overlay_dir,
|
||||
"size_bytes": file_size,
|
||||
"size_mb": round(file_size / 1024 / 1024, 2),
|
||||
"modified": file_modified,
|
||||
"feature_count": feature_count,
|
||||
"crs": str(gdf.crs) if gdf.crs else "Unknown",
|
||||
"bbox": bbox # [min_lon, min_lat, max_lon, max_lat] in EPSG:4326
|
||||
})
|
||||
except Exception as e:
|
||||
# If cannot read shapefile, just add basic info
|
||||
file_size = shp_file.stat().st_size
|
||||
file_modified = datetime.fromtimestamp(shp_file.stat().st_mtime).isoformat()
|
||||
relative_path = str(shp_file)
|
||||
|
||||
shapefiles.append({
|
||||
"filename": shp_file.name,
|
||||
"path": relative_path,
|
||||
"directory": overlay_dir,
|
||||
"size_bytes": file_size,
|
||||
"size_mb": round(file_size / 1024 / 1024, 2),
|
||||
"modified": file_modified,
|
||||
"error": f"Cannot read shapefile: {str(e)}"
|
||||
})
|
||||
|
||||
return {
|
||||
"shapefiles": shapefiles,
|
||||
"count": len(shapefiles),
|
||||
"directories": overlay_dirs
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/training/shapefile/{filename}/labels")
|
||||
async def get_shapefile_labels(filename: str):
|
||||
"""Lấy các label từ một shapefile cụ thể"""
|
||||
@@ -1729,6 +1814,85 @@ def update_progress(message: str):
|
||||
print(f"[PROGRESS] {message}")
|
||||
|
||||
|
||||
def rasterize_shapefile_overlay(shapefile_path, reference_raster, boundary_value=255):
|
||||
"""
|
||||
Rasterize shapefile boundaries to overlay on prediction result.
|
||||
|
||||
Args:
|
||||
shapefile_path: Path to shapefile
|
||||
reference_raster: xarray DataArray to match dimensions and CRS
|
||||
boundary_value: Value to use for boundaries (default 255 for white)
|
||||
|
||||
Returns:
|
||||
numpy array with boundaries, same shape as reference_raster
|
||||
"""
|
||||
try:
|
||||
import geopandas as gpd
|
||||
from rasterio.features import rasterize
|
||||
import numpy as np
|
||||
|
||||
# Read shapefile
|
||||
gdf = gpd.read_file(shapefile_path)
|
||||
print(f"[OVERLAY] Loaded shapefile with {len(gdf)} features, CRS: {gdf.crs}")
|
||||
|
||||
# Ensure CRS matches
|
||||
target_crs = reference_raster.rio.crs
|
||||
if gdf.crs != target_crs:
|
||||
print(f"[OVERLAY] Reprojecting from {gdf.crs} to {target_crs}")
|
||||
gdf = gdf.to_crs(target_crs)
|
||||
|
||||
# Get raster dimensions and transform
|
||||
height, width = reference_raster.shape
|
||||
transform = reference_raster.rio.transform()
|
||||
print(f"[OVERLAY] Raster dimensions: {height}x{width}")
|
||||
print(f"[OVERLAY] Transform: {transform}")
|
||||
|
||||
# Calculate appropriate buffer size based on pixel resolution
|
||||
# Get pixel size from transform (transform[0] is x resolution)
|
||||
pixel_size = abs(transform[0]) # in CRS units
|
||||
# Very thin boundary - only 0.2 pixels wide for 1px line
|
||||
buffer_distance = pixel_size * 0.2
|
||||
print(f"[OVERLAY] Pixel size: {pixel_size}, Buffer distance: {buffer_distance} (thin 1px line)")
|
||||
|
||||
# Create boundary geometries with minimal buffering
|
||||
boundary_geoms = []
|
||||
for idx, geom in enumerate(gdf.geometry):
|
||||
if geom is not None and geom.is_valid:
|
||||
# Get boundary of each polygon
|
||||
boundary = geom.boundary
|
||||
if boundary is not None:
|
||||
# Minimal buffer for 1-pixel thin line
|
||||
buffered = boundary.buffer(buffer_distance)
|
||||
boundary_geoms.append((buffered, boundary_value))
|
||||
|
||||
if not boundary_geoms:
|
||||
print(f"[WARNING] No valid boundary geometries found in {shapefile_path}")
|
||||
return np.zeros((height, width), dtype=np.uint8)
|
||||
|
||||
print(f"[OVERLAY] Rasterizing {len(boundary_geoms)} boundaries...")
|
||||
|
||||
# Rasterize boundaries
|
||||
boundary_mask = rasterize(
|
||||
shapes=boundary_geoms,
|
||||
out_shape=(height, width),
|
||||
transform=transform,
|
||||
fill=0, # Background
|
||||
dtype=np.uint8
|
||||
)
|
||||
|
||||
boundary_count = np.count_nonzero(boundary_mask)
|
||||
print(f"[OVERLAY] Boundary pixels: {boundary_count} / {height*width} ({boundary_count/(height*width)*100:.2f}%)")
|
||||
|
||||
if boundary_count == 0:
|
||||
print(f"[OVERLAY WARNING] No boundary pixels were rasterized! Check CRS and geometry overlap.")
|
||||
|
||||
return boundary_mask
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to rasterize shapefile {shapefile_path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def update_prediction_progress(message: str):
|
||||
"""Cập nhật prediction progress message"""
|
||||
global prediction_status
|
||||
@@ -2083,6 +2247,33 @@ async def run_prediction(config: PredictionConfig):
|
||||
|
||||
prediction_da.rio.to_raster(str(output_file), driver="GTiff")
|
||||
|
||||
# ============ SHAPEFILE OVERLAY ============
|
||||
overlay_mask = None
|
||||
if config.shapefile_overlay:
|
||||
prediction_status["progress"] = f"Đang overlay shapefile: {config.shapefile_overlay}..."
|
||||
print(f"[OVERLAY] Shapefile overlay requested: {config.shapefile_overlay}")
|
||||
|
||||
# Validate shapefile path exists
|
||||
shapefile_path = Path(config.shapefile_overlay)
|
||||
if not shapefile_path.exists():
|
||||
print(f"[OVERLAY ERROR] Shapefile not found: {shapefile_path}")
|
||||
print(f"[OVERLAY ERROR] Absolute path: {shapefile_path.absolute()}")
|
||||
print(f"[OVERLAY ERROR] Current working directory: {Path.cwd()}")
|
||||
else:
|
||||
print(f"[OVERLAY] Shapefile exists: {shapefile_path.absolute()}")
|
||||
try:
|
||||
overlay_mask = rasterize_shapefile_overlay(str(shapefile_path), prediction_da)
|
||||
if overlay_mask is not None and np.count_nonzero(overlay_mask) > 0:
|
||||
print(f"[OVERLAY] Successfully rasterized shapefile boundaries ({np.count_nonzero(overlay_mask)} pixels)")
|
||||
else:
|
||||
print(f"[OVERLAY WARNING] Shapefile rasterized but no boundary pixels found")
|
||||
except Exception as overlay_error:
|
||||
print(f"[OVERLAY ERROR] Exception: {overlay_error}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print(f"[OVERLAY] No shapefile overlay requested")
|
||||
|
||||
# Generate PNG preview for web display
|
||||
prediction_status["progress"] = "Đang tạo PNG preview..."
|
||||
png_file = output_dir / f"prediction_{timestamp}.png"
|
||||
@@ -2097,6 +2288,42 @@ async def run_prediction(config: PredictionConfig):
|
||||
|
||||
# Plot prediction with colormap
|
||||
im = ax.imshow(predictions_2d, cmap='tab20', interpolation='nearest')
|
||||
|
||||
# Overlay shapefile boundaries if available
|
||||
if overlay_mask is not None and np.count_nonzero(overlay_mask) > 0:
|
||||
print(f"[PNG OVERLAY] Overlaying {np.count_nonzero(overlay_mask)} boundary pixels")
|
||||
|
||||
# Create a mask for boundaries (where overlay_mask > 0)
|
||||
boundary_mask = overlay_mask > 0
|
||||
|
||||
# Method: Direct overlay with high-contrast colors
|
||||
# Create RGBA overlay image
|
||||
overlay_rgba = np.zeros((*predictions_2d.shape, 4))
|
||||
overlay_rgba[boundary_mask, 0] = 1.0 # Red = 1.0 (white)
|
||||
overlay_rgba[boundary_mask, 1] = 1.0 # Green = 1.0 (white)
|
||||
overlay_rgba[boundary_mask, 2] = 1.0 # Blue = 1.0 (white)
|
||||
overlay_rgba[boundary_mask, 3] = 1.0 # Alpha = 1.0 (fully opaque)
|
||||
|
||||
# Overlay on top of prediction
|
||||
ax.imshow(overlay_rgba, interpolation='nearest')
|
||||
|
||||
# Also add a black outline for better contrast
|
||||
from scipy import ndimage
|
||||
boundary_dilated = ndimage.binary_dilation(boundary_mask, iterations=1)
|
||||
boundary_outline = boundary_dilated & ~boundary_mask
|
||||
|
||||
outline_rgba = np.zeros((*predictions_2d.shape, 4))
|
||||
outline_rgba[boundary_outline, 0] = 0.0 # Black outline
|
||||
outline_rgba[boundary_outline, 1] = 0.0
|
||||
outline_rgba[boundary_outline, 2] = 0.0
|
||||
outline_rgba[boundary_outline, 3] = 0.8
|
||||
|
||||
ax.imshow(outline_rgba, interpolation='nearest')
|
||||
|
||||
print(f"[PNG OVERLAY] Added shapefile boundaries to visualization (direct overlay method)")
|
||||
else:
|
||||
print(f"[PNG OVERLAY] No overlay mask or empty mask (pixels: {np.count_nonzero(overlay_mask) if overlay_mask is not None else 0})")
|
||||
|
||||
ax.set_title(f'Land Classification - {timestamp}', fontsize=16, fontweight='bold', pad=20)
|
||||
ax.set_xlabel('X (pixels)', fontsize=11)
|
||||
ax.set_ylabel('Y (pixels)', fontsize=11)
|
||||
@@ -2176,7 +2403,9 @@ async def run_prediction(config: PredictionConfig):
|
||||
"n_features": features.shape[1],
|
||||
"feature_mode": feature_mode,
|
||||
"used_radar": use_radar,
|
||||
"model_used": config.model_filename
|
||||
"model_used": config.model_filename,
|
||||
"shapefile_overlay": config.shapefile_overlay,
|
||||
"overlay_applied": overlay_mask is not None
|
||||
}
|
||||
|
||||
# Auto generate prediction report
|
||||
@@ -4355,8 +4584,69 @@ async def predict_with_ndvi(config: PredictionWithNDVIConfig, background_tasks:
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Patch
|
||||
|
||||
# ============ SHAPEFILE OVERLAY ============
|
||||
overlay_mask = None
|
||||
if config.shapefile_overlay:
|
||||
print(f"[OVERLAY] Shapefile overlay requested: {config.shapefile_overlay}")
|
||||
|
||||
# Validate shapefile path exists
|
||||
shapefile_path = Path(config.shapefile_overlay)
|
||||
if not shapefile_path.exists():
|
||||
print(f"[OVERLAY ERROR] Shapefile not found: {shapefile_path}")
|
||||
print(f"[OVERLAY ERROR] Absolute path: {shapefile_path.absolute()}")
|
||||
else:
|
||||
print(f"[OVERLAY] Shapefile exists: {shapefile_path.absolute()}")
|
||||
try:
|
||||
# Import rioxarray for rio accessor
|
||||
import rioxarray
|
||||
|
||||
# Create temporary DataArray for rasterization
|
||||
temp_da = xr.DataArray(
|
||||
prediction_raster,
|
||||
coords={
|
||||
"y": np.linspace(bbox[3], bbox[1], height),
|
||||
"x": np.linspace(bbox[0], bbox[2], width)
|
||||
},
|
||||
dims=["y", "x"]
|
||||
)
|
||||
temp_da.rio.write_crs("EPSG:4326", inplace=True)
|
||||
temp_da.rio.write_transform(transform, inplace=True)
|
||||
|
||||
overlay_mask = rasterize_shapefile_overlay(str(shapefile_path), temp_da)
|
||||
if overlay_mask is not None and np.count_nonzero(overlay_mask) > 0:
|
||||
print(f"[OVERLAY] Successfully rasterized shapefile boundaries ({np.count_nonzero(overlay_mask)} pixels)")
|
||||
else:
|
||||
print(f"[OVERLAY WARNING] Shapefile rasterized but no boundary pixels found")
|
||||
except Exception as overlay_error:
|
||||
print(f"[OVERLAY ERROR] Exception: {overlay_error}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print(f"[OVERLAY] No shapefile overlay requested")
|
||||
|
||||
fig, ax = plt.subplots(figsize=(14, 10), dpi=150)
|
||||
im = ax.imshow(prediction_raster, cmap='tab20', interpolation='nearest')
|
||||
|
||||
# Overlay shapefile boundaries if available
|
||||
if overlay_mask is not None and np.count_nonzero(overlay_mask) > 0:
|
||||
print(f"[PNG OVERLAY] Overlaying {np.count_nonzero(overlay_mask)} boundary pixels (1px thin line)")
|
||||
|
||||
# Create a mask for boundaries
|
||||
boundary_mask = overlay_mask > 0
|
||||
|
||||
# Create RGBA overlay image - thin 1px white line only
|
||||
overlay_rgba = np.zeros((*prediction_raster.shape, 4))
|
||||
overlay_rgba[boundary_mask, 0] = 1.0 # White (R=1)
|
||||
overlay_rgba[boundary_mask, 1] = 1.0 # White (G=1)
|
||||
overlay_rgba[boundary_mask, 2] = 1.0 # White (B=1)
|
||||
overlay_rgba[boundary_mask, 3] = 1.0 # Fully opaque
|
||||
|
||||
ax.imshow(overlay_rgba, interpolation='nearest')
|
||||
|
||||
print(f"[PNG OVERLAY] Added thin 1px shapefile boundaries to visualization")
|
||||
else:
|
||||
print(f"[PNG OVERLAY] No overlay mask or empty mask")
|
||||
|
||||
ax.set_title(f'Land Classification - {timestamp}', fontsize=16, fontweight='bold', pad=20)
|
||||
ax.set_xlabel('X (pixels)', fontsize=11)
|
||||
ax.set_ylabel('Y (pixels)', fontsize=11)
|
||||
|
||||
+190
-1
@@ -774,6 +774,23 @@
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Shapefile Overlay Option -->
|
||||
<div class="form-group" style="margin-bottom: 15px;">
|
||||
<label style="font-weight: 600; color: #c2410c; margin-bottom: 8px; display: block;">
|
||||
🗺️ Overlay Shapefile (Hiển thị ranh giới lô đất)
|
||||
</label>
|
||||
<select id="shapefileOverlay" onchange="onShapefileSelected(event)" style="width: 100%; padding: 10px; border: 2px solid #fdba74; border-radius: 8px; font-size: 14px; background: white;">
|
||||
<option value="">-- Không overlay --</option>
|
||||
<!-- Shapefiles will be loaded here -->
|
||||
</select>
|
||||
<div style="font-size: 0.85em; color: #9a3412; margin-top: 4px; line-height: 1.4;">
|
||||
<b>🎯 Tự động cập nhật vùng prediction:</b><br>
|
||||
✅ Khi chọn shapefile → <b>Bbox trên bản đồ tự động thay đổi</b> theo vùng shapefile<br>
|
||||
✅ <b>CRS sẽ tự động chuyển đổi</b> - không cần lo về EPSG:4326/9209/32648<br>
|
||||
💡 Không chọn shapefile → Dùng bbox tùy chỉnh do bạn vẽ trên bản đồ
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="startPrediction()" id="predictBtn" style="margin-top: 5px; width: 100%; font-size: 1.1em; padding: 16px;">
|
||||
@@ -1651,6 +1668,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Get shapefile overlay option
|
||||
const shapefileOverlay = document.getElementById('shapefileOverlay').value;
|
||||
|
||||
// Warn if no shapefile selected (optional but recommended)
|
||||
if (!shapefileOverlay) {
|
||||
const confirmWithoutShapefile = confirm(
|
||||
'⚠️ CẢNH BÁO: Bạn chưa chọn shapefile!\n\n' +
|
||||
'❌ Kết quả sẽ KHÔNG có đường ranh giới lô đất.\n\n' +
|
||||
'💡 Để có đường phân lô trên ảnh kết quả:\n' +
|
||||
' - Hủy bỏ\n' +
|
||||
' - Chọn shapefile trong dropdown "Overlay Shapefile"\n' +
|
||||
' - Chạy lại prediction\n\n' +
|
||||
'Bạn có muốn tiếp tục KHÔNG CÓ ranh giới lô đất không?'
|
||||
);
|
||||
|
||||
if (!confirmWithoutShapefile) {
|
||||
console.log('[PREDICTION] User cancelled to select shapefile');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log(`[PREDICTION] Shapefile selected: ${shapefileOverlay}`);
|
||||
}
|
||||
|
||||
const config = {
|
||||
model_filename: modelFilename,
|
||||
min_lon: selectedBbox.min_lon,
|
||||
@@ -1666,7 +1706,8 @@
|
||||
export_ndvi: exportNDVI,
|
||||
export_classification: true,
|
||||
cloud_removal_method: cloudRemovalConfig.method,
|
||||
cloud_removal_model: cloudRemovalConfig.model_filename || null
|
||||
cloud_removal_model: cloudRemovalConfig.model_filename || null,
|
||||
shapefile_overlay: shapefileOverlay || null
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -2598,6 +2639,7 @@
|
||||
loadPredProvinces(); // Load provinces list
|
||||
loadNDVIProvinces(); // Load NDVI provinces list
|
||||
loadNDVIModels(); // Load models for NDVI
|
||||
loadOverlayShapefiles(); // Load shapefiles for overlay
|
||||
|
||||
// Add event listener for model selection
|
||||
document.getElementById('modelSelect').addEventListener('change', updateModelInfo);
|
||||
@@ -2993,6 +3035,153 @@
|
||||
|
||||
alert(`✅ Đã áp dụng preset: ${config.name}\n\nBbox: [${config.bbox.join(', ')}]\nThời gian: ${config.start_date} → ${config.end_date}\nSample points: ${config.sample_points}`);
|
||||
}
|
||||
|
||||
// Load overlay shapefiles
|
||||
async function loadOverlayShapefiles() {
|
||||
try {
|
||||
const response = await fetch('/api/overlay/shapefiles');
|
||||
const data = await response.json();
|
||||
|
||||
const select = document.getElementById('shapefileOverlay');
|
||||
select.innerHTML = '<option value="">-- Không overlay --</option>';
|
||||
|
||||
if (data.shapefiles && data.shapefiles.length > 0) {
|
||||
data.shapefiles.forEach(shp => {
|
||||
const option = document.createElement('option');
|
||||
option.value = shp.path;
|
||||
|
||||
// Build detailed label with CRS and bbox info
|
||||
let label = `${shp.filename} - ${shp.feature_count} features`;
|
||||
|
||||
// Add CRS info (important for matching!)
|
||||
if (shp.crs) {
|
||||
const crsCode = shp.crs.split(':').pop(); // Extract code from "EPSG:4326"
|
||||
label += ` | CRS: ${crsCode}`;
|
||||
}
|
||||
|
||||
// Add bbox info for easy matching with prediction area
|
||||
if (shp.bbox && shp.bbox.length === 4) {
|
||||
const [minLon, minLat, maxLon, maxLat] = shp.bbox;
|
||||
label += ` | Vùng: [${minLon.toFixed(2)}, ${minLat.toFixed(2)}, ${maxLon.toFixed(2)}, ${maxLat.toFixed(2)}]`;
|
||||
}
|
||||
|
||||
option.textContent = label;
|
||||
|
||||
// Store full shapefile info as data attributes for later use
|
||||
option.dataset.crs = shp.crs || '';
|
||||
option.dataset.bbox = JSON.stringify(shp.bbox || []);
|
||||
option.dataset.featureCount = shp.feature_count;
|
||||
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
console.log(`[Overlay Shapefiles] Loaded ${data.shapefiles.length} shapefiles`);
|
||||
} else {
|
||||
console.log('[Overlay Shapefiles] No shapefiles found');
|
||||
}
|
||||
|
||||
// Add event listener for shapefile selection change (OUTSIDE the if block)
|
||||
// Remove old listener first to prevent duplicates
|
||||
select.removeEventListener('change', onShapefileSelected);
|
||||
select.addEventListener('change', onShapefileSelected);
|
||||
console.log('[Overlay Shapefiles] Event listener attached');
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Overlay Shapefiles] Error loading shapefiles:', error);
|
||||
const select = document.getElementById('shapefileOverlay');
|
||||
select.innerHTML = '<option value="">Error loading shapefiles</option>';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle shapefile selection - auto update bbox on map
|
||||
function onShapefileSelected(event) {
|
||||
console.log('[Shapefile Select] Event triggered');
|
||||
console.log('[Shapefile Select] map exists:', typeof map !== 'undefined');
|
||||
console.log('[Shapefile Select] drawnItems exists:', typeof drawnItems !== 'undefined');
|
||||
|
||||
const selectedOption = event.target.selectedOptions[0];
|
||||
|
||||
// If no shapefile selected (empty value), keep current bbox
|
||||
if (!selectedOption || !selectedOption.value) {
|
||||
console.log('[Shapefile Select] No shapefile selected, keeping current bbox');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Shapefile Select] Selected shapefile:', selectedOption.value);
|
||||
|
||||
// Get bbox from data attribute
|
||||
const bboxData = selectedOption.dataset.bbox;
|
||||
console.log('[Shapefile Select] Bbox data:', bboxData);
|
||||
|
||||
if (!bboxData || bboxData === '[]') {
|
||||
console.warn('[Shapefile Select] Selected shapefile has no bbox data');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const bbox = JSON.parse(bboxData);
|
||||
console.log('[Shapefile Select] Parsed bbox:', bbox);
|
||||
|
||||
if (bbox.length !== 4) {
|
||||
console.warn('[Shapefile Select] Invalid bbox format:', bbox);
|
||||
return;
|
||||
}
|
||||
|
||||
const [minLon, minLat, maxLon, maxLat] = bbox;
|
||||
|
||||
// Validate bbox
|
||||
if (minLon < -180 || maxLon > 180 || minLat < -90 || maxLat > 90) {
|
||||
alert('❌ Bbox của shapefile không hợp lệ!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Shapefile Select] Creating rectangle with bounds:', [[minLat, minLon], [maxLat, maxLon]]);
|
||||
|
||||
// Update prediction map bbox
|
||||
const bounds = [
|
||||
[minLat, minLon],
|
||||
[maxLat, maxLon]
|
||||
];
|
||||
|
||||
const rectangle = L.rectangle(bounds, {
|
||||
color: '#667eea',
|
||||
weight: 3,
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
|
||||
// Clear old bbox and add new one
|
||||
console.log('[Shapefile Select] Clearing old layers...');
|
||||
drawnItems.clearLayers();
|
||||
console.log('[Shapefile Select] Adding new rectangle...');
|
||||
drawnItems.addLayer(rectangle);
|
||||
console.log('[Shapefile Select] Fitting map to bounds...');
|
||||
map.fitBounds(bounds, { padding: [50, 50] });
|
||||
|
||||
// Update selected bbox variable
|
||||
selectedBbox = {
|
||||
min_lon: minLon,
|
||||
min_lat: minLat,
|
||||
max_lon: maxLon,
|
||||
max_lat: maxLat
|
||||
};
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('prediction_bbox', JSON.stringify(selectedBbox));
|
||||
|
||||
console.log(`[Shapefile Select] Auto-updated bbox from shapefile:`, selectedBbox);
|
||||
|
||||
// Show notification
|
||||
const crs = selectedOption.dataset.crs || 'Unknown';
|
||||
alert(`✅ Đã tự động cập nhật vùng prediction theo shapefile!\n\n` +
|
||||
`📍 Bbox: [${minLon.toFixed(4)}, ${minLat.toFixed(4)}, ${maxLon.toFixed(4)}, ${maxLat.toFixed(4)}]\n` +
|
||||
`🗺️ CRS: ${crs}\n\n` +
|
||||
`💡 Bạn có thể điều chỉnh lại bằng cách vẽ lại trên bản đồ nếu muốn.`);
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Shapefile Select] Error parsing bbox:', e);
|
||||
alert(`❌ Lỗi khi xử lý bbox: ${e.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+9
-178
@@ -1,4 +1,3 @@
|
||||
affine @ file:///home/conda/feedstock_root/build_artifacts/affine_1733762038348/work
|
||||
aiobotocore==2.25.0
|
||||
aiohappyeyeballs==2.6.1
|
||||
aiohttp==3.12.15
|
||||
@@ -7,162 +6,63 @@ aiosignal==1.4.0
|
||||
alembic==1.16.5
|
||||
annotated-doc==0.0.4
|
||||
annotated-types==0.7.0
|
||||
antimeridian @ file:///home/conda/feedstock_root/build_artifacts/antimeridian_1753706324394/work
|
||||
anyio @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_anyio_1758634638/work
|
||||
argon2-cffi @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi_1749017159514/work
|
||||
argon2-cffi-bindings @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi-bindings_1649500328244/work
|
||||
arrow @ file:///home/conda/feedstock_root/build_artifacts/arrow_1733584251875/work
|
||||
asciitree==0.3.3
|
||||
asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1733250440834/work
|
||||
async-lru @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_async-lru_1742153708/work
|
||||
async-timeout==3.0.1
|
||||
attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1741918516150/work
|
||||
babel @ file:///home/conda/feedstock_root/build_artifacts/babel_1738490167835/work
|
||||
beautifulsoup4 @ file:///home/conda/feedstock_root/build_artifacts/beautifulsoup4_1759146011391/work
|
||||
bleach @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_bleach_1737382993/work
|
||||
blinker==1.9.0
|
||||
bokeh==3.7.3
|
||||
boto3==1.40.18
|
||||
botocore==1.40.49
|
||||
Bottleneck @ file:///croot/bottleneck_1731058641041/work
|
||||
branca @ file:///croot/branca_1675157607453/work
|
||||
Brotli @ file:///croot/brotli-split_1736182456865/work
|
||||
brotlicffi @ file:///croot/brotlicffi_1736182461069/work
|
||||
cached-property @ file:///home/conda/feedstock_root/build_artifacts/cached_property_1615209429212/work
|
||||
cachetools==6.2.0
|
||||
Cartopy==0.25.0
|
||||
certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1759648874697/work/certifi
|
||||
cffi @ file:///croot/cffi_1736182485317/work
|
||||
cftime @ file:///home/conda/feedstock_root/build_artifacts/cftime_1649636873066/work
|
||||
chardet @ file:///home/conda/feedstock_root/build_artifacts/chardet_1649184137891/work
|
||||
charset-normalizer @ file:///croot/charset-normalizer_1721748349566/work
|
||||
ciso8601==2.3.3
|
||||
click @ file:///home/conda/feedstock_root/build_artifacts/click_1747811314515/work
|
||||
click-plugins @ file:///home/conda/feedstock_root/build_artifacts/click-plugins_1750848229740/work
|
||||
cligj @ file:///home/conda/feedstock_root/build_artifacts/cligj_1733749956636/work
|
||||
cloudpickle @ file:///home/conda/feedstock_root/build_artifacts/cloudpickle_1736947526808/work
|
||||
colorama==0.4.6
|
||||
colorcet==3.1.0
|
||||
comm @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_comm_1753453984/work
|
||||
contourpy @ file:///croot/contourpy_1732540045555/work
|
||||
cycler @ file:///tmp/build/80754af9/cycler_1637851556182/work
|
||||
cytoolz==0.11.2
|
||||
dask @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_dask-core_1760473436/work
|
||||
dask-gateway @ file:///Users/runner/miniforge3/conda-bld/bld/rattler-build_dask-gateway_1744370153/work/dask-gateway
|
||||
dask-glm @ file:///home/conda/feedstock_root/build_artifacts/dask-glm_1701346265909/work
|
||||
dask-image==2024.5.3
|
||||
dask-ml @ file:///home/conda/feedstock_root/build_artifacts/dask-ml_1679705292494/work
|
||||
datacube==1.8.15
|
||||
datacube==1.9.4
|
||||
datacube_ows==1.9.4
|
||||
datashader==0.18.2
|
||||
dea-tools==0.3.0
|
||||
debugpy @ file:///home/task_175706711740264/conda-bld/debugpy_1757067131873/work
|
||||
decorator @ file:///home/conda/feedstock_root/build_artifacts/decorator_1740384970518/work
|
||||
deepdiff==8.6.1
|
||||
defusedxml @ file:///home/conda/feedstock_root/build_artifacts/defusedxml_1615232257335/work
|
||||
deprecat @ file:///home/conda/feedstock_root/build_artifacts/deprecat_1734684036993/work
|
||||
distributed @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_distributed_1760476147/work
|
||||
eo-tides==0.8.2
|
||||
exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1746947292760/work
|
||||
executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1756729339227/work
|
||||
fastapi==0.124.3
|
||||
fasteners @ file:///home/conda/feedstock_root/build_artifacts/fasteners_1734943108928/work
|
||||
fastjsonschema @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_python-fastjsonschema_1755304154/work/dist
|
||||
filelock==3.19.1
|
||||
fiona==1.10.1
|
||||
Flask==3.1.2
|
||||
flask-babel==4.0.0
|
||||
flatbuffers==25.2.10
|
||||
folium==0.20.0
|
||||
fonttools @ file:///croot/fonttools_1737039080035/work
|
||||
fqdn @ file:///home/conda/feedstock_root/build_artifacts/fqdn_1733327382592/work/dist
|
||||
frozenlist==1.7.0
|
||||
fsspec @ file:///home/conda/feedstock_root/build_artifacts/fsspec_1756908513222/work
|
||||
GDAL @ file:///croot/gdal-split_1734448174900/work/build/swig/python
|
||||
GeoAlchemy2 @ file:///home/conda/feedstock_root/build_artifacts/geoalchemy2_1753372953474/work
|
||||
geographiclib==2.1
|
||||
geojson==3.2.0
|
||||
geomad==1.0.0
|
||||
geopandas @ file:///croot/geopandas-split_1755761494241/work
|
||||
geopy==2.4.1
|
||||
greenlet @ file:///home/conda/feedstock_root/build_artifacts/greenlet_1648882383677/work
|
||||
h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1745526374115/work
|
||||
h2 @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_h2_1756364871/work
|
||||
git-filter-repo==2.47.0
|
||||
h3==4.3.1
|
||||
hdstats==0.2.1
|
||||
holoviews==1.21.0
|
||||
hpack @ file:///home/conda/feedstock_root/build_artifacts/hpack_1737618293087/work
|
||||
httpcore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_httpcore_1745602916/work
|
||||
httpx @ file:///home/conda/feedstock_root/build_artifacts/httpx_1733663348460/work
|
||||
hvplot==0.12.1
|
||||
hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1737618333194/work
|
||||
idna==3.10
|
||||
imagecodecs==2025.3.30
|
||||
imageio==2.37.0
|
||||
importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_importlib-metadata_1747934053/work
|
||||
ipykernel @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipykernel_1760459840/work
|
||||
ipyleaflet==0.20.0
|
||||
ipython @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipython_1748711175/work
|
||||
ipywidgets==8.1.7
|
||||
iso8601==2.1.0
|
||||
isoduration @ file:///home/conda/feedstock_root/build_artifacts/isoduration_1733493628631/work/dist
|
||||
itsdangerous==2.2.0
|
||||
jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1733300866624/work
|
||||
Jinja2 @ file:///croot/jinja2_1741710844255/work
|
||||
jmespath @ file:///home/conda/feedstock_root/build_artifacts/jmespath_1733229141657/work
|
||||
joblib @ file:///home/conda/feedstock_root/build_artifacts/joblib_1756321760188/work
|
||||
json5 @ file:///home/conda/feedstock_root/build_artifacts/json5_1755034879854/work
|
||||
jsonpointer @ file:///home/conda/feedstock_root/build_artifacts/jsonpointer_1756754132747/work
|
||||
jsonschema @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jsonschema_1755595646/work
|
||||
jsonschema-specifications==2025.4.1
|
||||
jupyter-events @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter_events_1738765986/work
|
||||
jupyter-leaflet==0.20.0
|
||||
jupyter-lsp @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter-lsp_1756388269/work/jupyter-lsp
|
||||
jupyter-ui-poll==1.0.0
|
||||
jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1733440914442/work
|
||||
jupyter_core @ file:///home/conda/feedstock_root/build_artifacts/jupyter_core_1748333051527/work
|
||||
jupyter_server @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter_server_1755870522/work
|
||||
jupyter_server_terminals @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_terminals_1733427956852/work
|
||||
jupyterlab @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_1758913905644/work
|
||||
jupyterlab_pygments @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_pygments_1733328101776/work
|
||||
jupyterlab_server @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_server_1733599573484/work
|
||||
jupyterlab_widgets==3.0.15
|
||||
kiwisolver @ file:///croot/kiwisolver_1737039087198/work
|
||||
lark==1.2.2
|
||||
lark-parser==0.12.0
|
||||
lazy_loader==0.4
|
||||
linkify-it-py==2.0.3
|
||||
llvmlite @ file:///croot/llvmlite_1741209858218/work
|
||||
locket @ file:///home/conda/feedstock_root/build_artifacts/locket_1650660393415/work
|
||||
lxml==5.4.0
|
||||
lz4 @ file:///croot/lz4_1736366683208/work
|
||||
Mako @ file:///home/conda/feedstock_root/build_artifacts/mako_1744317760971/work
|
||||
mapclassify @ file:///croot/mapclassify_1675157730177/work
|
||||
Markdown==3.9
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe @ file:///croot/markupsafe_1738584038848/work
|
||||
matplotlib==3.10.5
|
||||
matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1733416936468/work
|
||||
mdit-py-plugins==0.5.0
|
||||
mdurl==0.1.2
|
||||
mistune @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_mistune_1756495311/work
|
||||
mpmath==1.3.0
|
||||
msgpack @ file:///home/conda/feedstock_root/build_artifacts/msgpack-python_1648745999384/work
|
||||
multidict @ file:///home/conda/feedstock_root/build_artifacts/multidict_1648882415384/work
|
||||
multipledispatch @ file:///home/conda/feedstock_root/build_artifacts/multipledispatch_1721907546485/work
|
||||
narwhals==2.3.0
|
||||
nbclient @ file:///home/conda/feedstock_root/build_artifacts/nbclient_1734628800805/work
|
||||
nbconvert @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_nbconvert-core_1738067871/work
|
||||
nbformat @ file:///home/conda/feedstock_root/build_artifacts/nbformat_1733402752141/work
|
||||
nest_asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1733325553580/work
|
||||
netCDF4 @ file:///croot/netcdf4_1743512888672/work
|
||||
networkx @ file:///croot/networkx_1737039604450/work
|
||||
notebook @ file:///home/conda/feedstock_root/build_artifacts/notebook_1759152069573/work
|
||||
notebook_shim @ file:///home/conda/feedstock_root/build_artifacts/notebook-shim_1733408315203/work
|
||||
numba @ file:///croot/numba_1750798165355/work
|
||||
numcodecs @ file:///croot/numcodecs_1707513121886/work
|
||||
numexpr @ file:///croot/numexpr_1755766469354/work
|
||||
numpy @ file:///croot/numpy_and_numpy_base_1755590845055/work/dist/numpy-1.26.4-cp310-cp310-linux_x86_64.whl#sha256=1096d33ad9a9757a1b4b46634d809e894263fc8b78780bff36801684b6e8cc88
|
||||
nvidia-cublas-cu12==12.8.4.1
|
||||
nvidia-cuda-cupti-cu12==12.8.90
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
@@ -174,136 +74,67 @@ nvidia-curand-cu12==10.3.9.90
|
||||
nvidia-cusolver-cu12==11.7.3.90
|
||||
nvidia-cusparse-cu12==12.5.8.93
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-nccl-cu12==2.27.3
|
||||
nvidia-nccl-cu12==2.27.5
|
||||
nvidia-nvjitlink-cu12==12.8.93
|
||||
nvidia-nvshmem-cu12==3.3.20
|
||||
nvidia-nvtx-cu12==12.8.90
|
||||
odc-algo==0.2.3
|
||||
odc-geo==0.4.10
|
||||
odc-io==0.2.2
|
||||
odc-loader @ file:///home/conda/feedstock_root/build_artifacts/odc-loader_1743656085024/work
|
||||
odc-stac @ file:///home/conda/feedstock_root/build_artifacts/odc-stac_1746136311934/work
|
||||
odc-ui==0.2.1
|
||||
orderly-set==5.5.0
|
||||
overrides @ file:///home/conda/feedstock_root/build_artifacts/overrides_1734587627321/work
|
||||
OWSLib==0.34.1
|
||||
packaging @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_packaging_1745345660/work
|
||||
pandas @ file:///home/task_175982153789305/conda-bld/pandas_1759822248912/work/dist/pandas-2.3.3-cp310-cp310-linux_x86_64.whl#sha256=0de7c83109c411cc2a74419a396c92f65e3d1e457fb4d835e5f100cfb04393a7
|
||||
pandocfilters @ file:///home/conda/feedstock_root/build_artifacts/pandocfilters_1631603243851/work
|
||||
panel==1.7.5
|
||||
param==2.2.1
|
||||
parso @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_parso_1755974222/work
|
||||
partd @ file:///home/conda/feedstock_root/build_artifacts/partd_1715026491486/work
|
||||
pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1733301927746/work
|
||||
pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1733327343728/work
|
||||
pillow @ file:///croot/pillow_1738010226202/work
|
||||
PIMS==0.7
|
||||
planetary-computer==1.0.0
|
||||
platformdirs @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_platformdirs_1756227402/work
|
||||
prometheus_client==0.22.1
|
||||
prometheus_flask_exporter==0.23.2
|
||||
prompt_toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1756321756983/work
|
||||
propcache==0.3.2
|
||||
psutil @ file:///home/conda/feedstock_root/build_artifacts/psutil_1653089181607/work
|
||||
psycopg2 @ file:///croot/psycopg2_1744919787325/work
|
||||
ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1733302279685/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=92c32ff62b5fd8cf325bec5ab90d7be3d2a8ca8c8a3813ff487a8d2002630d1f
|
||||
pure_eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1733569405015/work
|
||||
pyarrow @ file:///home/task_175983338836370/conda-bld/pyarrow_1759833584228/work/python
|
||||
pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work
|
||||
pyct==0.5.0
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1750615794071/work
|
||||
pyogrio @ file:///croot/pyogrio_1741107161422/work
|
||||
pyows==0.3.1
|
||||
pyparsing @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_pyparsing_1753873557/work
|
||||
pyproj @ file:///croot/pyproj_1739284761968/work
|
||||
PyQt6==6.7.1
|
||||
PyQt6_sip @ file:///croot/pyqt-split_1753427276959/work/pyqt_sip
|
||||
pyshp==2.3.1
|
||||
PySocks @ file:///home/builder/ci_310/pysocks_1640793678128/work
|
||||
pystac @ file:///home/conda/feedstock_root/build_artifacts/pystac_1758218055393/work
|
||||
pystac-client==0.9.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
python-json-logger @ file:///home/conda/feedstock_root/build_artifacts/python-json-logger_1677079630776/work
|
||||
python-multipart==0.0.21
|
||||
python-slugify==8.0.4
|
||||
pyTMD==2.2.8
|
||||
pytz @ file:///home/conda/feedstock_root/build_artifacts/pytz_1742920838005/work
|
||||
pyviz_comms==3.0.6
|
||||
PyYAML==6.0.2
|
||||
pyzmq @ file:///croot/pyzmq_1734687138743/work
|
||||
rasterio @ file:///croot/rasterio_1740069178893/work
|
||||
rasterstats==0.20.0
|
||||
referencing==0.36.2
|
||||
regex==2025.9.1
|
||||
requests @ file:///croot/requests_1756709366904/work
|
||||
rfc3339_validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3339-validator_1733599910982/work
|
||||
rfc3986-validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3986-validator_1598024191506/work
|
||||
rfc3987==1.3.8
|
||||
rfc3987-syntax @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_rfc3987-syntax_1752876729/work
|
||||
rioxarray @ file:///home/conda/feedstock_root/build_artifacts/rioxarray_1737140588464/work
|
||||
rpds-py @ file:///croot/rpds-py_1736541261634/work
|
||||
ruamel.yaml @ file:///home/conda/feedstock_root/build_artifacts/ruamel.yaml_1649033201098/work
|
||||
ruamel.yaml.clib==0.2.12
|
||||
s3fs==2025.9.0
|
||||
s3transfer==0.13.1
|
||||
scikit-image==0.25.2
|
||||
scikit-learn==1.7.1
|
||||
scipy @ file:///croot/scipy_1747238027288/work/dist/scipy-1.15.3-cp310-cp310-linux_x86_64.whl#sha256=2a791554880ad4f358fcc4cd2a982ffe1e9d472e9241011216b2be797457f1f9
|
||||
seaborn==0.13.2
|
||||
Send2Trash @ file:///home/conda/feedstock_root/build_artifacts/send2trash_1733322040660/work
|
||||
setuptools-scm==9.2.0
|
||||
shapely @ file:///croot/shapely_1754380812723/work
|
||||
simplejson==3.20.1
|
||||
sip @ file:///croot/sip_1738856193618/work
|
||||
six==1.17.0
|
||||
slicerator==1.1.0
|
||||
sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1733244044561/work
|
||||
snuggs @ file:///home/conda/feedstock_root/build_artifacts/snuggs_1733818638588/work
|
||||
sortedcontainers @ file:///home/conda/feedstock_root/build_artifacts/sortedcontainers_1738440353519/work
|
||||
soupsieve @ file:///home/conda/feedstock_root/build_artifacts/soupsieve_1756330469801/work
|
||||
sparse @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_sparse_1747799051/work
|
||||
SQLAlchemy==1.4.54
|
||||
stack_data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1733569443808/work
|
||||
SQLAlchemy==2.0.0
|
||||
starlette==0.50.0
|
||||
sympy==1.14.0
|
||||
tblib @ file:///home/conda/feedstock_root/build_artifacts/tblib_1743515515538/work
|
||||
terminado @ file:///home/conda/feedstock_root/build_artifacts/terminado_1710262609923/work
|
||||
text-unidecode==1.3
|
||||
threadpoolctl @ file:///home/conda/feedstock_root/build_artifacts/threadpoolctl_1741878222898/work
|
||||
tifffile==2025.5.10
|
||||
timescale==0.0.9
|
||||
timezonefinder==8.0.0
|
||||
tinycss2 @ file:///home/conda/feedstock_root/build_artifacts/tinycss2_1729802851396/work
|
||||
tomli @ file:///croot/tomli_1753774587605/work
|
||||
toolz @ file:///home/conda/feedstock_root/build_artifacts/toolz_1733736030883/work
|
||||
torch==2.8.0
|
||||
tornado @ file:///croot/tornado_1748956929273/work
|
||||
torch==2.9.1
|
||||
torchvision==0.24.1
|
||||
tqdm==4.67.1
|
||||
traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1733367359838/work
|
||||
traittypes==0.2.1
|
||||
triton==3.4.0
|
||||
types-python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/types-python-dateutil_1759899809376/work
|
||||
triton==3.5.1
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_typing_extensions_1756220668/work
|
||||
typing_utils @ file:///home/conda/feedstock_root/build_artifacts/typing_utils_1733331286120/work
|
||||
tzdata @ file:///croot/python-tzdata_1746123641790/work
|
||||
uc-micro-py==1.0.3
|
||||
unicodedata2 @ file:///croot/unicodedata2_1736541023050/work
|
||||
uri-template @ file:///home/conda/feedstock_root/build_artifacts/uri-template_1733323593477/work/dist
|
||||
urllib3 @ file:///croot/urllib3_1750775463400/work
|
||||
uvicorn==0.38.0
|
||||
wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1733231326287/work
|
||||
webcolors @ file:///home/conda/feedstock_root/build_artifacts/webcolors_1733359735138/work
|
||||
webencodings @ file:///home/conda/feedstock_root/build_artifacts/webencodings_1733236011802/work
|
||||
websocket-client @ file:///home/conda/feedstock_root/build_artifacts/websocket-client_1759928050786/work
|
||||
Werkzeug==3.1.3
|
||||
widgetsnbextension==4.0.14
|
||||
wrapt @ file:///home/conda/feedstock_root/build_artifacts/wrapt_1651495243689/work
|
||||
xarray @ file:///home/conda/feedstock_root/build_artifacts/xarray_1749743207754/work
|
||||
xgboost==3.1.2
|
||||
xyzservices @ file:///croot/xyzservices_1675159059961/work
|
||||
yarl==1.20.1
|
||||
zarr @ file:///home/conda/feedstock_root/build_artifacts/zarr_1733237197728/work
|
||||
zict @ file:///home/conda/feedstock_root/build_artifacts/zict_1733261551178/work
|
||||
zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1749421620841/work
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
affine @ file:///home/conda/feedstock_root/build_artifacts/affine_1733762038348/work
|
||||
aiobotocore==2.25.0
|
||||
aiohappyeyeballs==2.6.1
|
||||
aiohttp==3.12.15
|
||||
aioitertools==0.12.0
|
||||
aiosignal==1.4.0
|
||||
alembic==1.16.5
|
||||
annotated-doc==0.0.4
|
||||
annotated-types==0.7.0
|
||||
antimeridian @ file:///home/conda/feedstock_root/build_artifacts/antimeridian_1753706324394/work
|
||||
anyio @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_anyio_1758634638/work
|
||||
argon2-cffi @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi_1749017159514/work
|
||||
argon2-cffi-bindings @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi-bindings_1649500328244/work
|
||||
arrow @ file:///home/conda/feedstock_root/build_artifacts/arrow_1733584251875/work
|
||||
asciitree==0.3.3
|
||||
asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1733250440834/work
|
||||
async-lru @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_async-lru_1742153708/work
|
||||
async-timeout==3.0.1
|
||||
attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1741918516150/work
|
||||
babel @ file:///home/conda/feedstock_root/build_artifacts/babel_1738490167835/work
|
||||
beautifulsoup4 @ file:///home/conda/feedstock_root/build_artifacts/beautifulsoup4_1759146011391/work
|
||||
bleach @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_bleach_1737382993/work
|
||||
blinker==1.9.0
|
||||
bokeh==3.7.3
|
||||
boto3==1.40.18
|
||||
botocore==1.40.49
|
||||
Bottleneck @ file:///croot/bottleneck_1731058641041/work
|
||||
branca @ file:///croot/branca_1675157607453/work
|
||||
Brotli @ file:///croot/brotli-split_1736182456865/work
|
||||
brotlicffi @ file:///croot/brotlicffi_1736182461069/work
|
||||
cached-property @ file:///home/conda/feedstock_root/build_artifacts/cached_property_1615209429212/work
|
||||
cachetools==6.2.0
|
||||
Cartopy==0.25.0
|
||||
certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1762976168352/work/certifi
|
||||
cffi @ file:///croot/cffi_1736182485317/work
|
||||
cftime @ file:///home/conda/feedstock_root/build_artifacts/cftime_1649636873066/work
|
||||
chardet @ file:///home/conda/feedstock_root/build_artifacts/chardet_1649184137891/work
|
||||
charset-normalizer @ file:///croot/charset-normalizer_1721748349566/work
|
||||
ciso8601==2.3.3
|
||||
click @ file:///home/conda/feedstock_root/build_artifacts/click_1747811314515/work
|
||||
click-plugins @ file:///home/conda/feedstock_root/build_artifacts/click-plugins_1750848229740/work
|
||||
cligj @ file:///home/conda/feedstock_root/build_artifacts/cligj_1733749956636/work
|
||||
cloudpickle @ file:///home/conda/feedstock_root/build_artifacts/cloudpickle_1736947526808/work
|
||||
colorama==0.4.6
|
||||
colorcet==3.1.0
|
||||
comm @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_comm_1753453984/work
|
||||
contourpy @ file:///croot/contourpy_1732540045555/work
|
||||
cycler @ file:///tmp/build/80754af9/cycler_1637851556182/work
|
||||
cytoolz==0.11.2
|
||||
dask @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_dask-core_1760473436/work
|
||||
dask-gateway @ file:///Users/runner/miniforge3/conda-bld/bld/rattler-build_dask-gateway_1744370153/work/dask-gateway
|
||||
dask-glm @ file:///home/conda/feedstock_root/build_artifacts/dask-glm_1701346265909/work
|
||||
dask-image==2024.5.3
|
||||
dask-ml @ file:///home/conda/feedstock_root/build_artifacts/dask-ml_1679705292494/work
|
||||
datacube==1.8.15
|
||||
datacube_ows==1.9.4
|
||||
datashader==0.18.2
|
||||
dea-tools==0.3.0
|
||||
debugpy @ file:///home/task_175706711740264/conda-bld/debugpy_1757067131873/work
|
||||
decorator @ file:///home/conda/feedstock_root/build_artifacts/decorator_1740384970518/work
|
||||
deepdiff==8.6.1
|
||||
defusedxml @ file:///home/conda/feedstock_root/build_artifacts/defusedxml_1615232257335/work
|
||||
deprecat @ file:///home/conda/feedstock_root/build_artifacts/deprecat_1734684036993/work
|
||||
distributed @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_distributed_1760476147/work
|
||||
eo-tides==0.8.2
|
||||
exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1746947292760/work
|
||||
executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1756729339227/work
|
||||
fastapi==0.124.3
|
||||
fasteners @ file:///home/conda/feedstock_root/build_artifacts/fasteners_1734943108928/work
|
||||
fastjsonschema @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_python-fastjsonschema_1755304154/work/dist
|
||||
filelock==3.19.1
|
||||
fiona==1.10.1
|
||||
Flask==3.1.2
|
||||
flask-babel==4.0.0
|
||||
flatbuffers==25.2.10
|
||||
folium==0.20.0
|
||||
fonttools @ file:///croot/fonttools_1737039080035/work
|
||||
fqdn @ file:///home/conda/feedstock_root/build_artifacts/fqdn_1733327382592/work/dist
|
||||
frozenlist==1.7.0
|
||||
fsspec @ file:///home/conda/feedstock_root/build_artifacts/fsspec_1756908513222/work
|
||||
GDAL @ file:///croot/gdal-split_1734448174900/work/build/swig/python
|
||||
GeoAlchemy2 @ file:///home/conda/feedstock_root/build_artifacts/geoalchemy2_1753372953474/work
|
||||
geographiclib==2.1
|
||||
geojson==3.2.0
|
||||
geomad==1.0.0
|
||||
geopandas @ file:///croot/geopandas-split_1755761494241/work
|
||||
geopy==2.4.1
|
||||
git-filter-repo==2.47.0
|
||||
greenlet @ file:///home/conda/feedstock_root/build_artifacts/greenlet_1648882383677/work
|
||||
h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1745526374115/work
|
||||
h2 @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_h2_1756364871/work
|
||||
h3==4.3.1
|
||||
hdstats==0.2.1
|
||||
holoviews==1.21.0
|
||||
hpack @ file:///home/conda/feedstock_root/build_artifacts/hpack_1737618293087/work
|
||||
httpcore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_httpcore_1745602916/work
|
||||
httpx @ file:///home/conda/feedstock_root/build_artifacts/httpx_1733663348460/work
|
||||
hvplot==0.12.1
|
||||
hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1737618333194/work
|
||||
idna==3.10
|
||||
imagecodecs==2025.3.30
|
||||
imageio==2.37.0
|
||||
importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_importlib-metadata_1747934053/work
|
||||
ipykernel @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipykernel_1760459840/work
|
||||
ipyleaflet==0.20.0
|
||||
ipython @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipython_1748711175/work
|
||||
ipywidgets==8.1.7
|
||||
iso8601==2.1.0
|
||||
isoduration @ file:///home/conda/feedstock_root/build_artifacts/isoduration_1733493628631/work/dist
|
||||
itsdangerous==2.2.0
|
||||
jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1733300866624/work
|
||||
Jinja2 @ file:///croot/jinja2_1741710844255/work
|
||||
jmespath @ file:///home/conda/feedstock_root/build_artifacts/jmespath_1733229141657/work
|
||||
joblib @ file:///home/conda/feedstock_root/build_artifacts/joblib_1756321760188/work
|
||||
json5 @ file:///home/conda/feedstock_root/build_artifacts/json5_1755034879854/work
|
||||
jsonpointer @ file:///home/conda/feedstock_root/build_artifacts/jsonpointer_1756754132747/work
|
||||
jsonschema @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jsonschema_1755595646/work
|
||||
jsonschema-specifications==2025.4.1
|
||||
jupyter-events @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter_events_1738765986/work
|
||||
jupyter-leaflet==0.20.0
|
||||
jupyter-lsp @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter-lsp_1756388269/work/jupyter-lsp
|
||||
jupyter-ui-poll==1.0.0
|
||||
jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1733440914442/work
|
||||
jupyter_core @ file:///home/conda/feedstock_root/build_artifacts/jupyter_core_1748333051527/work
|
||||
jupyter_server @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_jupyter_server_1755870522/work
|
||||
jupyter_server_terminals @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_terminals_1733427956852/work
|
||||
jupyterlab @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_1758913905644/work
|
||||
jupyterlab_pygments @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_pygments_1733328101776/work
|
||||
jupyterlab_server @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_server_1733599573484/work
|
||||
jupyterlab_widgets==3.0.15
|
||||
kiwisolver @ file:///croot/kiwisolver_1737039087198/work
|
||||
lark==1.2.2
|
||||
lark-parser==0.12.0
|
||||
lazy_loader==0.4
|
||||
linkify-it-py==2.0.3
|
||||
llvmlite @ file:///croot/llvmlite_1741209858218/work
|
||||
locket @ file:///home/conda/feedstock_root/build_artifacts/locket_1650660393415/work
|
||||
lxml==5.4.0
|
||||
lz4 @ file:///croot/lz4_1736366683208/work
|
||||
Mako @ file:///home/conda/feedstock_root/build_artifacts/mako_1744317760971/work
|
||||
mapclassify @ file:///croot/mapclassify_1675157730177/work
|
||||
Markdown==3.9
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe @ file:///croot/markupsafe_1738584038848/work
|
||||
matplotlib==3.10.5
|
||||
matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1733416936468/work
|
||||
mdit-py-plugins==0.5.0
|
||||
mdurl==0.1.2
|
||||
mistune @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_mistune_1756495311/work
|
||||
mpmath==1.3.0
|
||||
msgpack @ file:///home/conda/feedstock_root/build_artifacts/msgpack-python_1648745999384/work
|
||||
multidict @ file:///home/conda/feedstock_root/build_artifacts/multidict_1648882415384/work
|
||||
multipledispatch @ file:///home/conda/feedstock_root/build_artifacts/multipledispatch_1721907546485/work
|
||||
narwhals==2.3.0
|
||||
nbclient @ file:///home/conda/feedstock_root/build_artifacts/nbclient_1734628800805/work
|
||||
nbconvert @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_nbconvert-core_1738067871/work
|
||||
nbformat @ file:///home/conda/feedstock_root/build_artifacts/nbformat_1733402752141/work
|
||||
nest_asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1733325553580/work
|
||||
netCDF4 @ file:///croot/netcdf4_1743512888672/work
|
||||
networkx @ file:///croot/networkx_1737039604450/work
|
||||
notebook @ file:///home/conda/feedstock_root/build_artifacts/notebook_1759152069573/work
|
||||
notebook_shim @ file:///home/conda/feedstock_root/build_artifacts/notebook-shim_1733408315203/work
|
||||
numba @ file:///croot/numba_1750798165355/work
|
||||
numcodecs @ file:///croot/numcodecs_1707513121886/work
|
||||
numexpr @ file:///croot/numexpr_1755766469354/work
|
||||
numpy @ file:///croot/numpy_and_numpy_base_1755590845055/work/dist/numpy-1.26.4-cp310-cp310-linux_x86_64.whl#sha256=1096d33ad9a9757a1b4b46634d809e894263fc8b78780bff36801684b6e8cc88
|
||||
nvidia-cublas-cu12==12.8.4.1
|
||||
nvidia-cuda-cupti-cu12==12.8.90
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
nvidia-cuda-runtime-cu12==12.8.90
|
||||
nvidia-cudnn-cu12==9.10.2.21
|
||||
nvidia-cufft-cu12==11.3.3.83
|
||||
nvidia-cufile-cu12==1.13.1.3
|
||||
nvidia-curand-cu12==10.3.9.90
|
||||
nvidia-cusolver-cu12==11.7.3.90
|
||||
nvidia-cusparse-cu12==12.5.8.93
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-nccl-cu12==2.27.5
|
||||
nvidia-nvjitlink-cu12==12.8.93
|
||||
nvidia-nvshmem-cu12==3.3.20
|
||||
nvidia-nvtx-cu12==12.8.90
|
||||
odc-algo==0.2.3
|
||||
odc-geo==0.4.10
|
||||
odc-io==0.2.2
|
||||
odc-loader @ file:///home/conda/feedstock_root/build_artifacts/odc-loader_1743656085024/work
|
||||
odc-stac @ file:///home/conda/feedstock_root/build_artifacts/odc-stac_1746136311934/work
|
||||
odc-ui==0.2.1
|
||||
orderly-set==5.5.0
|
||||
overrides @ file:///home/conda/feedstock_root/build_artifacts/overrides_1734587627321/work
|
||||
OWSLib==0.34.1
|
||||
packaging @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_packaging_1745345660/work
|
||||
pandas @ file:///home/task_175982153789305/conda-bld/pandas_1759822248912/work/dist/pandas-2.3.3-cp310-cp310-linux_x86_64.whl#sha256=0de7c83109c411cc2a74419a396c92f65e3d1e457fb4d835e5f100cfb04393a7
|
||||
pandocfilters @ file:///home/conda/feedstock_root/build_artifacts/pandocfilters_1631603243851/work
|
||||
panel==1.7.5
|
||||
param==2.2.1
|
||||
parso @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_parso_1755974222/work
|
||||
partd @ file:///home/conda/feedstock_root/build_artifacts/partd_1715026491486/work
|
||||
pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1733301927746/work
|
||||
pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1733327343728/work
|
||||
pillow @ file:///croot/pillow_1738010226202/work
|
||||
PIMS==0.7
|
||||
planetary-computer==1.0.0
|
||||
platformdirs @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_platformdirs_1756227402/work
|
||||
prometheus_client==0.22.1
|
||||
prometheus_flask_exporter==0.23.2
|
||||
prompt_toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1756321756983/work
|
||||
propcache==0.3.2
|
||||
psutil @ file:///home/conda/feedstock_root/build_artifacts/psutil_1653089181607/work
|
||||
psycopg2 @ file:///croot/psycopg2_1744919787325/work
|
||||
ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1733302279685/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=92c32ff62b5fd8cf325bec5ab90d7be3d2a8ca8c8a3813ff487a8d2002630d1f
|
||||
pure_eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1733569405015/work
|
||||
pyarrow @ file:///home/task_175983338836370/conda-bld/pyarrow_1759833584228/work/python
|
||||
pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work
|
||||
pyct==0.5.0
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1750615794071/work
|
||||
pyogrio @ file:///croot/pyogrio_1741107161422/work
|
||||
pyows==0.3.1
|
||||
pyparsing @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_pyparsing_1753873557/work
|
||||
pyproj @ file:///croot/pyproj_1739284761968/work
|
||||
PyQt6==6.7.1
|
||||
PyQt6_sip @ file:///croot/pyqt-split_1753427276959/work/pyqt_sip
|
||||
pyshp==2.3.1
|
||||
PySocks @ file:///home/builder/ci_310/pysocks_1640793678128/work
|
||||
pystac @ file:///home/conda/feedstock_root/build_artifacts/pystac_1758218055393/work
|
||||
pystac-client==0.9.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
python-json-logger @ file:///home/conda/feedstock_root/build_artifacts/python-json-logger_1677079630776/work
|
||||
python-multipart==0.0.21
|
||||
python-slugify==8.0.4
|
||||
pyTMD==2.2.8
|
||||
pytz @ file:///home/conda/feedstock_root/build_artifacts/pytz_1742920838005/work
|
||||
pyviz_comms==3.0.6
|
||||
PyYAML==6.0.2
|
||||
pyzmq @ file:///croot/pyzmq_1734687138743/work
|
||||
rasterio @ file:///croot/rasterio_1740069178893/work
|
||||
rasterstats==0.20.0
|
||||
referencing==0.36.2
|
||||
regex==2025.9.1
|
||||
requests @ file:///croot/requests_1756709366904/work
|
||||
rfc3339_validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3339-validator_1733599910982/work
|
||||
rfc3986-validator @ file:///home/conda/feedstock_root/build_artifacts/rfc3986-validator_1598024191506/work
|
||||
rfc3987==1.3.8
|
||||
rfc3987-syntax @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_rfc3987-syntax_1752876729/work
|
||||
rioxarray @ file:///home/conda/feedstock_root/build_artifacts/rioxarray_1737140588464/work
|
||||
rpds-py @ file:///croot/rpds-py_1736541261634/work
|
||||
ruamel.yaml @ file:///home/conda/feedstock_root/build_artifacts/ruamel.yaml_1649033201098/work
|
||||
ruamel.yaml.clib==0.2.12
|
||||
s3fs==2025.9.0
|
||||
s3transfer==0.13.1
|
||||
scikit-image==0.25.2
|
||||
scikit-learn==1.7.1
|
||||
scipy @ file:///croot/scipy_1747238027288/work/dist/scipy-1.15.3-cp310-cp310-linux_x86_64.whl#sha256=2a791554880ad4f358fcc4cd2a982ffe1e9d472e9241011216b2be797457f1f9
|
||||
seaborn==0.13.2
|
||||
Send2Trash @ file:///home/conda/feedstock_root/build_artifacts/send2trash_1733322040660/work
|
||||
setuptools-scm==9.2.0
|
||||
shapely @ file:///croot/shapely_1754380812723/work
|
||||
simplejson==3.20.1
|
||||
sip @ file:///croot/sip_1738856193618/work
|
||||
six==1.17.0
|
||||
slicerator==1.1.0
|
||||
sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1733244044561/work
|
||||
snuggs @ file:///home/conda/feedstock_root/build_artifacts/snuggs_1733818638588/work
|
||||
sortedcontainers @ file:///home/conda/feedstock_root/build_artifacts/sortedcontainers_1738440353519/work
|
||||
soupsieve @ file:///home/conda/feedstock_root/build_artifacts/soupsieve_1756330469801/work
|
||||
sparse @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_sparse_1747799051/work
|
||||
SQLAlchemy==1.4.54
|
||||
stack_data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1733569443808/work
|
||||
starlette==0.50.0
|
||||
sympy==1.14.0
|
||||
tblib @ file:///home/conda/feedstock_root/build_artifacts/tblib_1743515515538/work
|
||||
terminado @ file:///home/conda/feedstock_root/build_artifacts/terminado_1710262609923/work
|
||||
text-unidecode==1.3
|
||||
threadpoolctl @ file:///home/conda/feedstock_root/build_artifacts/threadpoolctl_1741878222898/work
|
||||
tifffile==2025.5.10
|
||||
timescale==0.0.9
|
||||
timezonefinder==8.0.0
|
||||
tinycss2 @ file:///home/conda/feedstock_root/build_artifacts/tinycss2_1729802851396/work
|
||||
tomli @ file:///croot/tomli_1753774587605/work
|
||||
toolz @ file:///home/conda/feedstock_root/build_artifacts/toolz_1733736030883/work
|
||||
torch==2.9.1
|
||||
torchvision==0.24.1
|
||||
tornado @ file:///croot/tornado_1748956929273/work
|
||||
tqdm==4.67.1
|
||||
traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1733367359838/work
|
||||
traittypes==0.2.1
|
||||
triton==3.5.1
|
||||
types-python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/types-python-dateutil_1759899809376/work
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_typing_extensions_1756220668/work
|
||||
typing_utils @ file:///home/conda/feedstock_root/build_artifacts/typing_utils_1733331286120/work
|
||||
tzdata @ file:///croot/python-tzdata_1746123641790/work
|
||||
uc-micro-py==1.0.3
|
||||
unicodedata2 @ file:///croot/unicodedata2_1736541023050/work
|
||||
uri-template @ file:///home/conda/feedstock_root/build_artifacts/uri-template_1733323593477/work/dist
|
||||
urllib3 @ file:///croot/urllib3_1750775463400/work
|
||||
uvicorn==0.38.0
|
||||
wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1733231326287/work
|
||||
webcolors @ file:///home/conda/feedstock_root/build_artifacts/webcolors_1733359735138/work
|
||||
webencodings @ file:///home/conda/feedstock_root/build_artifacts/webencodings_1733236011802/work
|
||||
websocket-client @ file:///home/conda/feedstock_root/build_artifacts/websocket-client_1759928050786/work
|
||||
Werkzeug==3.1.3
|
||||
widgetsnbextension==4.0.14
|
||||
wrapt @ file:///home/conda/feedstock_root/build_artifacts/wrapt_1651495243689/work
|
||||
xarray @ file:///home/conda/feedstock_root/build_artifacts/xarray_1749743207754/work
|
||||
xgboost==3.1.2
|
||||
xyzservices @ file:///croot/xyzservices_1675159059961/work
|
||||
yarl==1.20.1
|
||||
zarr @ file:///home/conda/feedstock_root/build_artifacts/zarr_1733237197728/work
|
||||
zict @ file:///home/conda/feedstock_root/build_artifacts/zict_1733261551178/work
|
||||
zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1749421620841/work
|
||||
@@ -0,0 +1,142 @@
|
||||
aiobotocore==2.25.0
|
||||
aiohappyeyeballs==2.6.1
|
||||
aiohttp==3.12.15
|
||||
aioitertools==0.12.0
|
||||
aiosignal==1.4.0
|
||||
alembic==1.16.5
|
||||
annotated-doc==0.0.4
|
||||
annotated-types==0.7.0
|
||||
asciitree==0.3.3
|
||||
async-timeout==3.0.1
|
||||
blinker==1.9.0
|
||||
bokeh==3.7.3
|
||||
boto3==1.40.18
|
||||
botocore==1.40.49
|
||||
cachetools==6.2.0
|
||||
Cartopy==0.25.0
|
||||
ciso8601==2.3.3
|
||||
colorama==0.4.6
|
||||
colorcet==3.1.0
|
||||
cytoolz==0.11.2
|
||||
dask-image==2024.5.3
|
||||
datacube==1.9.4
|
||||
datacube_ows==1.9.4
|
||||
datashader==0.18.2
|
||||
dea-tools==0.3.0
|
||||
deepdiff==8.6.1
|
||||
eo-tides==0.8.2
|
||||
fastapi==0.124.3
|
||||
filelock==3.19.1
|
||||
fiona==1.10.1
|
||||
Flask==3.1.2
|
||||
flask-babel==4.0.0
|
||||
flatbuffers==25.2.10
|
||||
folium==0.20.0
|
||||
frozenlist==1.7.0
|
||||
geographiclib==2.1
|
||||
geojson==3.2.0
|
||||
geomad==1.0.0
|
||||
geopy==2.4.1
|
||||
git-filter-repo==2.47.0
|
||||
h3==4.3.1
|
||||
hdstats==0.2.1
|
||||
holoviews==1.21.0
|
||||
hvplot==0.12.1
|
||||
idna==3.10
|
||||
imagecodecs==2025.3.30
|
||||
imageio==2.37.0
|
||||
ipyleaflet==0.20.0
|
||||
ipywidgets==8.1.7
|
||||
iso8601==2.1.0
|
||||
itsdangerous==2.2.0
|
||||
jsonschema-specifications==2025.4.1
|
||||
jupyter-leaflet==0.20.0
|
||||
jupyter-ui-poll==1.0.0
|
||||
jupyterlab_widgets==3.0.15
|
||||
lark==1.2.2
|
||||
lark-parser==0.12.0
|
||||
lazy_loader==0.4
|
||||
linkify-it-py==2.0.3
|
||||
lxml==5.4.0
|
||||
Markdown==3.9
|
||||
markdown-it-py==4.0.0
|
||||
matplotlib==3.10.5
|
||||
mdit-py-plugins==0.5.0
|
||||
mdurl==0.1.2
|
||||
mpmath==1.3.0
|
||||
narwhals==2.3.0
|
||||
nvidia-cublas-cu12==12.8.4.1
|
||||
nvidia-cuda-cupti-cu12==12.8.90
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
nvidia-cuda-runtime-cu12==12.8.90
|
||||
nvidia-cudnn-cu12==9.10.2.21
|
||||
nvidia-cufft-cu12==11.3.3.83
|
||||
nvidia-cufile-cu12==1.13.1.3
|
||||
nvidia-curand-cu12==10.3.9.90
|
||||
nvidia-cusolver-cu12==11.7.3.90
|
||||
nvidia-cusparse-cu12==12.5.8.93
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-nccl-cu12==2.27.5
|
||||
nvidia-nvjitlink-cu12==12.8.93
|
||||
nvidia-nvshmem-cu12==3.3.20
|
||||
nvidia-nvtx-cu12==12.8.90
|
||||
odc-algo==0.2.3
|
||||
odc-geo==0.4.10
|
||||
odc-io==0.2.2
|
||||
odc-ui==0.2.1
|
||||
orderly-set==5.5.0
|
||||
OWSLib==0.34.1
|
||||
panel==1.7.5
|
||||
param==2.2.1
|
||||
PIMS==0.7
|
||||
planetary-computer==1.0.0
|
||||
prometheus_client==0.22.1
|
||||
prometheus_flask_exporter==0.23.2
|
||||
propcache==0.3.2
|
||||
pyct==0.5.0
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
pyows==0.3.1
|
||||
PyQt6==6.7.1
|
||||
pyshp==2.3.1
|
||||
pystac-client==0.9.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
python-multipart==0.0.21
|
||||
python-slugify==8.0.4
|
||||
pyTMD==2.2.8
|
||||
pyviz_comms==3.0.6
|
||||
PyYAML==6.0.2
|
||||
rasterstats==0.20.0
|
||||
referencing==0.36.2
|
||||
regex==2025.9.1
|
||||
rfc3987==1.3.8
|
||||
ruamel.yaml.clib==0.2.12
|
||||
s3fs==2025.9.0
|
||||
s3transfer==0.13.1
|
||||
scikit-image==0.25.2
|
||||
scikit-learn==1.7.1
|
||||
seaborn==0.13.2
|
||||
setuptools-scm==9.2.0
|
||||
simplejson==3.20.1
|
||||
six==1.17.0
|
||||
slicerator==1.1.0
|
||||
SQLAlchemy==2.0.0
|
||||
starlette==0.50.0
|
||||
sympy==1.14.0
|
||||
text-unidecode==1.3
|
||||
tifffile==2025.5.10
|
||||
timescale==0.0.9
|
||||
timezonefinder==8.0.0
|
||||
torch==2.9.1
|
||||
torchvision==0.24.1
|
||||
tqdm==4.67.1
|
||||
traittypes==0.2.1
|
||||
triton==3.5.1
|
||||
typing-inspection==0.4.1
|
||||
uc-micro-py==1.0.3
|
||||
uvicorn==0.38.0
|
||||
Werkzeug==3.1.3
|
||||
widgetsnbextension==4.0.14
|
||||
xgboost==3.1.2
|
||||
yarl==1.20.1
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify shapefile overlay API returns correct bbox data
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_shapefile_api():
|
||||
"""Test /api/overlay/shapefiles endpoint"""
|
||||
print("Testing /api/overlay/shapefiles endpoint...")
|
||||
|
||||
try:
|
||||
response = requests.get('http://localhost:8000/api/overlay/shapefiles')
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"\n✅ API Response successful")
|
||||
print(f"Total shapefiles: {data.get('count', 0)}")
|
||||
|
||||
if data.get('shapefiles'):
|
||||
print("\n📋 Shapefile details:")
|
||||
for idx, shp in enumerate(data['shapefiles'], 1):
|
||||
print(f"\n{idx}. {shp.get('filename')}")
|
||||
print(f" Path: {shp.get('path')}")
|
||||
print(f" CRS: {shp.get('crs')}")
|
||||
print(f" Features: {shp.get('feature_count')}")
|
||||
print(f" Bbox: {shp.get('bbox')}")
|
||||
|
||||
# Verify bbox format
|
||||
bbox = shp.get('bbox')
|
||||
if bbox and len(bbox) == 4:
|
||||
print(f" ✅ Bbox format valid: [minLon, minLat, maxLon, maxLat]")
|
||||
else:
|
||||
print(f" ❌ Bbox format invalid or missing!")
|
||||
else:
|
||||
print("\n⚠️ No shapefiles found")
|
||||
else:
|
||||
print(f"\n❌ API returned status code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("\n❌ Cannot connect to API server. Is it running on localhost:8000?")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_shapefile_api()
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script to verify shapefile overlay functionality
|
||||
"""
|
||||
import geopandas as gpd
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
# Test shapefile path
|
||||
shapefile_path = "ChauThanh/HienTrang/ChauThanh_kiemke.shp"
|
||||
|
||||
print("=" * 70)
|
||||
print("TESTING SHAPEFILE OVERLAY")
|
||||
print("=" * 70)
|
||||
|
||||
# Check if file exists
|
||||
shp = Path(shapefile_path)
|
||||
print(f"\n1. Checking file existence:")
|
||||
print(f" Path: {shp}")
|
||||
print(f" Exists: {shp.exists()}")
|
||||
print(f" Absolute: {shp.absolute()}")
|
||||
|
||||
if shp.exists():
|
||||
# Read shapefile
|
||||
print(f"\n2. Reading shapefile...")
|
||||
gdf = gpd.read_file(str(shp))
|
||||
print(f" Features: {len(gdf)}")
|
||||
print(f" CRS: {gdf.crs}")
|
||||
print(f" Bounds: {gdf.total_bounds}")
|
||||
print(f" Columns: {list(gdf.columns)}")
|
||||
|
||||
# Check geometries
|
||||
print(f"\n3. Checking geometries...")
|
||||
valid_count = sum(1 for geom in gdf.geometry if geom is not None and geom.is_valid)
|
||||
print(f" Valid geometries: {valid_count} / {len(gdf)}")
|
||||
|
||||
# Sample geometry bounds
|
||||
if len(gdf) > 0:
|
||||
sample_geom = gdf.geometry.iloc[0]
|
||||
print(f" Sample geometry type: {sample_geom.geom_type}")
|
||||
print(f" Sample geometry bounds: {sample_geom.bounds}")
|
||||
|
||||
# Test reprojection to EPSG:4326
|
||||
print(f"\n4. Testing reprojection to EPSG:4326...")
|
||||
try:
|
||||
gdf_4326 = gdf.to_crs("EPSG:4326")
|
||||
print(f" Success!")
|
||||
print(f" New bounds: {gdf_4326.total_bounds}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
|
||||
# Test boundary extraction
|
||||
print(f"\n5. Testing boundary extraction...")
|
||||
boundaries = []
|
||||
for geom in gdf.geometry:
|
||||
if geom is not None and geom.is_valid:
|
||||
boundary = geom.boundary
|
||||
if boundary is not None:
|
||||
boundaries.append(boundary)
|
||||
print(f" Extracted boundaries: {len(boundaries)}")
|
||||
|
||||
# Test buffering
|
||||
print(f"\n6. Testing buffer...")
|
||||
buffer_size = 0.001 # degrees or meters depending on CRS
|
||||
buffered = []
|
||||
for boundary in boundaries[:10]: # Test first 10
|
||||
try:
|
||||
buf = boundary.buffer(buffer_size)
|
||||
buffered.append(buf)
|
||||
except Exception as e:
|
||||
print(f" Buffer error: {e}")
|
||||
print(f" Successfully buffered: {len(buffered)} / 10")
|
||||
|
||||
else:
|
||||
print(" ERROR: Shapefile not found!")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("TEST COMPLETE")
|
||||
print("=" * 70)
|
||||
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test Shapefile Selection</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||
#map { height: 400px; border: 2px solid #ccc; margin: 20px 0; }
|
||||
.info-box { background: #f0f0f0; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧪 Test Shapefile Auto-Select Bbox</h1>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>Chọn Shapefile:</h3>
|
||||
<select id="shapefileOverlay" onchange="onShapefileSelected(event)" style="width: 100%; padding: 10px; font-size: 14px;">
|
||||
<option value="">-- Chọn shapefile --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>Current Bbox:</h3>
|
||||
<pre id="bboxInfo">Chưa chọn shapefile</pre>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>Console Logs:</h3>
|
||||
<pre id="console" style="max-height: 200px; overflow-y: auto; background: #000; color: #0f0; padding: 10px;"></pre>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// Global variables
|
||||
let map, drawnItems, selectedBbox = null;
|
||||
|
||||
// Custom console.log to display in page
|
||||
const originalLog = console.log;
|
||||
console.log = function(...args) {
|
||||
originalLog.apply(console, args);
|
||||
const consoleEl = document.getElementById('console');
|
||||
consoleEl.textContent += args.join(' ') + '\n';
|
||||
consoleEl.scrollTop = consoleEl.scrollHeight;
|
||||
};
|
||||
|
||||
// Initialize map
|
||||
function initMap() {
|
||||
map = L.map('map').setView([10.0, 105.8], 10);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
drawnItems = new L.FeatureGroup();
|
||||
map.addLayer(drawnItems);
|
||||
|
||||
console.log('✅ Map initialized');
|
||||
}
|
||||
|
||||
// Load shapefiles from API
|
||||
async function loadShapefiles() {
|
||||
try {
|
||||
console.log('📡 Fetching shapefiles from API...');
|
||||
const response = await fetch('http://localhost:8000/api/overlay/shapefiles');
|
||||
const data = await response.json();
|
||||
|
||||
const select = document.getElementById('shapefileOverlay');
|
||||
select.innerHTML = '<option value="">-- Chọn shapefile --</option>';
|
||||
|
||||
if (data.shapefiles && data.shapefiles.length > 0) {
|
||||
data.shapefiles.forEach(shp => {
|
||||
const option = document.createElement('option');
|
||||
option.value = shp.path;
|
||||
|
||||
let label = `${shp.filename} - ${shp.feature_count} features`;
|
||||
if (shp.crs) {
|
||||
const crsCode = shp.crs.split(':').pop();
|
||||
label += ` | CRS: ${crsCode}`;
|
||||
}
|
||||
if (shp.bbox && shp.bbox.length === 4) {
|
||||
const [minLon, minLat, maxLon, maxLat] = shp.bbox;
|
||||
label += ` | [${minLon.toFixed(2)}, ${minLat.toFixed(2)}, ${maxLon.toFixed(2)}, ${maxLat.toFixed(2)}]`;
|
||||
}
|
||||
|
||||
option.textContent = label;
|
||||
option.dataset.crs = shp.crs || '';
|
||||
option.dataset.bbox = JSON.stringify(shp.bbox || []);
|
||||
option.dataset.featureCount = shp.feature_count;
|
||||
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
console.log(`✅ Loaded ${data.shapefiles.length} shapefiles`);
|
||||
} else {
|
||||
console.log('⚠️ No shapefiles found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading shapefiles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle shapefile selection
|
||||
function onShapefileSelected(event) {
|
||||
console.log('🔔 Shapefile selection changed');
|
||||
|
||||
const selectedOption = event.target.selectedOptions[0];
|
||||
|
||||
if (!selectedOption || !selectedOption.value) {
|
||||
console.log('ℹ️ No shapefile selected');
|
||||
document.getElementById('bboxInfo').textContent = 'Chưa chọn shapefile';
|
||||
return;
|
||||
}
|
||||
|
||||
const bboxData = selectedOption.dataset.bbox;
|
||||
console.log('📦 Bbox data from option:', bboxData);
|
||||
|
||||
if (!bboxData || bboxData === '[]') {
|
||||
console.log('⚠️ No bbox data in selected option');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const bbox = JSON.parse(bboxData);
|
||||
console.log('📊 Parsed bbox:', bbox);
|
||||
|
||||
if (bbox.length !== 4) {
|
||||
console.log('❌ Invalid bbox length:', bbox.length);
|
||||
return;
|
||||
}
|
||||
|
||||
const [minLon, minLat, maxLon, maxLat] = bbox;
|
||||
|
||||
// Validate bbox
|
||||
if (minLon < -180 || maxLon > 180 || minLat < -90 || maxLat > 90) {
|
||||
console.log('❌ Bbox out of valid range');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Valid bbox:', {minLon, minLat, maxLon, maxLat});
|
||||
|
||||
// Update map
|
||||
const bounds = [[minLat, minLon], [maxLat, maxLon]];
|
||||
const rectangle = L.rectangle(bounds, {
|
||||
color: '#667eea',
|
||||
weight: 3,
|
||||
fillOpacity: 0.2
|
||||
});
|
||||
|
||||
drawnItems.clearLayers();
|
||||
drawnItems.addLayer(rectangle);
|
||||
map.fitBounds(bounds, { padding: [50, 50] });
|
||||
|
||||
selectedBbox = {min_lon: minLon, min_lat: minLat, max_lon: maxLon, max_lat: maxLat};
|
||||
|
||||
console.log('🗺️ Map updated with new bbox');
|
||||
|
||||
// Update bbox info display
|
||||
document.getElementById('bboxInfo').textContent = JSON.stringify(selectedBbox, null, 2);
|
||||
|
||||
alert(`✅ Bbox updated!\n\nmin_lon: ${minLon.toFixed(4)}\nmin_lat: ${minLat.toFixed(4)}\nmax_lon: ${maxLon.toFixed(4)}\nmax_lat: ${maxLat.toFixed(4)}`);
|
||||
|
||||
} catch (e) {
|
||||
console.error('❌ Error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
window.onload = function() {
|
||||
console.log('🚀 Page loaded, initializing...');
|
||||
initMap();
|
||||
loadShapefiles();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user