3 Commits

14 changed files with 1963 additions and 180 deletions
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+8
View File
@@ -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>
+8
View File
@@ -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
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+48
View File
@@ -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>
+638
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+313
View File
@@ -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
+142
View File
@@ -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
+48
View File
@@ -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()
+79
View File
@@ -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)
+177
View File
@@ -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>