Files

973 lines
22 KiB
Markdown
Raw Permalink Normal View History

2026-05-11 03:27:21 +03:30
# مستند کامل اپ `location_data`
این سند، وضعیت فعلی اپ `location_data` را به صورت کامل توضیح می‌دهد:
- مدل‌های داده
- منطق business
- جریان ساخت location و block
- subdivision و خوشه‌بندی
- تولید analysis grid
- سنجش‌ازدور با openEO
- تسک‌های Celery
- APIهای فعلی
- ساختار responseها
- محدودیت‌ها و فرضیات فعلی
این فایل بر اساس کد فعلی پروژه نوشته شده است و هدفش این است که یک مرجع فنی برای توسعه‌دهنده‌های بعدی باشد.
---
## 1) هدف اپ `location_data`
اپ `location_data` در وضعیت فعلی چند نقش اصلی دارد:
1. نگه‌داری موقعیت جغرافیایی زمین با `lat/lon`
2. نگه‌داری مرز زمین یا مرز blockها
3. ساخت ساختار blockهای مزرعه
4. اجرای subdivision برای blockها
5. تولید grid analysis با ابعاد 30x30 متر
6. نگه‌داری نتایج سنجش‌ازدور روی هر grid cell
7. نگه‌داری داده‌های خاک و NDVI سنتی
8. فراهم کردن API برای:
- location data
- subdivision
- remote sensing trigger/result
- NDVI health
به صورت خلاصه، `location_data` الان فقط یک جدول مختصات نیست؛ بلکه هاب مکانی پروژه است.
---
## 2) مفاهیم اصلی دامنه
### 2.1) SoilLocation
`SoilLocation` نماینده یک location اصلی برای یک مزرعه یا مرکز زمین است.
این مدل:
- مختصات `latitude` و `longitude` را نگه می‌دارد
- `farm_boundary` را ذخیره می‌کند
- تعداد blockهای اولیه را نگه می‌دارد
- `block_layout` را نگه می‌دارد
- مبنای ارتباط با:
- `SoilDepthData`
- `BlockSubdivision`
- `AnalysisGridCell`
- `RemoteSensingRun`
- `NdviObservation`
---
### 2.2) BlockSubdivision
`BlockSubdivision` نتیجه خردسازی یک block است.
این مدل نگه می‌دارد:
- block code
- مرز همان block
- chunk size برای subdivision
- grid points اولیه
- centroid points نهایی
- elbow plot
- metadata الگوریتم
این مدل برای مرحله‌ای است که یک block را به بخش‌های کوچک‌تر تقسیم می‌کنیم.
---
### 2.3) AnalysisGridCell
`AnalysisGridCell` سلول‌های 30x30 متری تحلیل سنجش‌ازدور را نگه می‌دارد.
هر cell:
- به یک `SoilLocation` وصل است
- در صورت نیاز به یک `BlockSubdivision` وصل است
- یک `cell_code` یکتا دارد
- geometry خودش را به صورت Polygon نگه می‌دارد
- centroid خودش را نگه می‌دارد
این مدل واحد اصلی تحلیل remote sensing است.
---
### 2.4) AnalysisGridObservation
`AnalysisGridObservation` داده زمانی هر سلول را نگه می‌دارد.
برای هر cell و بازه زمانی:
- `ndvi`
- `ndwi`
- `lst_c`
- `soil_vv`
- `soil_vv_db`
- `dem_m`
- `slope_deg`
ذخیره می‌شود.
این مدل cache دیتابیسی اصلی برای نتایج openEO است.
---
### 2.5) RemoteSensingRun
`RemoteSensingRun` وضعیت یک اجرای async سنجش‌ازدور را نگه می‌دارد.
این مدل:
- به `SoilLocation` وصل است
- optionally به `BlockSubdivision` وصل است
- `block_code` و بازه زمانی را نگه می‌دارد
- status execution را نگه می‌دارد
- metadata مربوط به task/backend/result summary را نگه می‌دارد
این مدل برای tracking jobها در Celery استفاده می‌شود.
---
### 2.6) SoilDepthData
این مدل داده‌های خاک را در عمق‌های مختلف نگه می‌دارد:
- `0-5cm`
- `5-15cm`
- `15-30cm`
---
### 2.7) NdviObservation
این مدل نگه‌دارنده NDVI سنتی است که جدا از workflow جدید openEO هم هنوز وجود دارد.
---
## 3) ساختار فایل‌های مهم اپ
```text
location_data/
├── admin.py
├── apps.py
├── block_subdivision.py
├── grid_analysis.py
├── models.py
├── ndvi.py
├── openeo_service.py
├── remote_sensing.py
├── serializers.py
├── soil_adapters.py
├── tasks.py
├── urls.py
├── views.py
├── migrations/
└── tests...
```
### نقش فایل‌ها
- `models.py`: مدل‌های اصلی
- `serializers.py`: serializerهای API
- `views.py`: endpointهای DRF
- `urls.py`: routeها
- `tasks.py`: تسک‌های Celery
- `block_subdivision.py`: subdivision و elbow/kmeans
- `grid_analysis.py`: ساخت analysis grid cells
- `openeo_service.py`: لایه سرویس openEO
- `remote_sensing.py`: منطق قدیمی‌تر سنجش‌ازدور/NDVI ساده
- `soil_adapters.py`: adapterهای داده خاک
---
## 4) تنظیمات مهم
### `SUBDIVISION_CHUNK_SQM`
در `config/settings.py`:
```python
SUBDIVISION_CHUNK_SQM = int(os.environ.get("SUBDIVISION_CHUNK_SQM", "900"))
```
مقدار پیش‌فرض فعلی:
- `900`
معنا:
- grid analysis با سلول‌های `30m x 30m`
چون:
```text
step_m = sqrt(900) = 30m
```
---
## 5) مدل‌های دیتابیس و منطق آن‌ها
## 5.1) SoilLocation
فیلدهای مهم:
- `latitude`
- `longitude`
- `task_id`
- `farm_boundary`
- `input_block_count`
- `block_layout`
- `created_at`
- `updated_at`
### قید مهم
- `latitude + longitude` یکتا هستند
### block_layout
`block_layout` JSON summary کلی blockها را نگه می‌دارد.
نمونه:
```json
{
"input_block_count": 1,
"default_full_farm": true,
"algorithm_status": "completed",
"blocks": [
{
"block_code": "block-1",
"order": 1,
"source": "default",
"needs_subdivision": true,
"sub_blocks": [
{
"sub_block_code": "sub-block-1",
"centroid_lat": 35.689123,
"centroid_lon": 51.389456
}
],
"subdivision_summary": {
"chunk_size_sqm": 900,
"grid_point_count": 12,
"centroid_count": 3,
"optimal_k": 3
},
"analysis_grid_summary": {
"chunk_size_sqm": 900,
"cell_count": 18
}
}
]
}
```
`block_layout` canonical source نیست؛ بیشتر یک summary سریع برای API است.
---
## 5.2) BlockSubdivision
فیلدهای مهم:
- `soil_location`
- `block_code`
- `source_boundary`
- `chunk_size_sqm`
- `grid_points`
- `centroid_points`
- `grid_point_count`
- `centroid_count`
- `elbow_plot`
- `status`
- `metadata`
### نقش
برای هر `block_code` در هر location، نتیجه subdivision در این مدل ذخیره می‌شود.
### metadata
شامل مواردی مثل:
- `estimated_area_sqm`
- `optimal_k`
- `inertia_curve`
- `analysis_grid`
---
## 5.3) RemoteSensingRun
فیلدهای مهم:
- `soil_location`
- `block_subdivision`
- `block_code`
- `provider`
- `chunk_size_sqm`
- `temporal_start`
- `temporal_end`
- `status`
- `metadata`
- `error_message`
- `started_at`
- `finished_at`
### statusها
- `pending`
- `running`
- `success`
- `failure`
### نقش
این جدول وضعیت اجرای async را نگه می‌دارد.
---
## 5.4) AnalysisGridCell
فیلدهای مهم:
- `soil_location`
- `block_subdivision`
- `block_code`
- `cell_code`
- `chunk_size_sqm`
- `geometry`
- `centroid_lat`
- `centroid_lon`
### نقش
واحد spatial اصلی برای تحلیل remote sensing است.
### idempotency
سطح سرویس با این شرط enforce می‌شود:
- اگر برای یک `SoilLocation + block_code + chunk_size_sqm` cellها قبلاً ساخته شده باشند، دوباره ساخته نمی‌شوند.
### geometry
به صورت GeoJSON-like polygon ذخیره می‌شود.
---
## 5.5) AnalysisGridObservation
فیلدهای مهم:
- `cell`
- `run`
- `temporal_start`
- `temporal_end`
- `ndvi`
- `ndwi`
- `lst_c`
- `soil_vv`
- `soil_vv_db`
- `dem_m`
- `slope_deg`
- `metadata`
### uniqueness
برای جلوگیری از duplicate:
- روی `cell + temporal_start + temporal_end` constraint داریم.
این باعث می‌شود cache دیتابیسی پایدار باشد.
---
## 5.6) SoilDepthData
این مدل داده‌های خاک را در عمق‌های مختلف نگه می‌دارد.
هنوز به صورت مستقیم برای هر analysis grid cell جداگانه استفاده نشده است.
---
## 5.7) NdviObservation
این مدل legacy / parallel NDVI store است.
workflow جدید openEO جایگزین آن نشده، بلکه کنار آن وجود دارد.
---
## 6) منطق subdivision در `block_subdivision.py`
این فایل مسئول خردسازی blockها است.
### کارهایی که انجام می‌دهد
- استخراج polygon از boundary
- تبدیل مختصات جغرافیایی به مختصات محلی متری
- تولید grid points اولیه
- اجرای KMeans برای `K=1..10`
- محاسبه SSE/Inertia
- پیدا کردن elbow point
- انتخاب centroidها
- رسم elbow plot با matplotlib
- ذخیره plot در `ImageField`
- sync کردن نتیجه با `block_layout`
### input
ممکن است boundary به شکل‌های زیر برسد:
- GeoJSON Polygon
- corners
- list مستقیم از points
### خروجی
- centroidهای نهایی block
- metadata الگوریتم
- elbow plot
---
## 7) منطق ساخت analysis grid در `grid_analysis.py`
این فایل مسئول تولید سلول‌های 30x30 متری برای تحلیل remote sensing است.
### تابع اصلی
- `create_or_get_analysis_grid_cells(...)`
### ورودی‌ها
- `location`
- optional `boundary`
- optional `block_code`
- optional `block_subdivision`
- optional `chunk_size_sqm`
### رفتار
1. chunk size را تعیین می‌کند
2. boundary را resolve می‌کند
3. polygon را extract می‌کند
4. اگر قبلاً برای همان `location + block_code + chunk_size` cell ساخته شده باشد، خروجی existing برمی‌گرداند
5. اگر نه، grid cellها ساخته می‌شوند و `AnalysisGridCell` ذخیره می‌شود
### نحوه ساخت شبکه
- polygon به دستگاه محلی متری تبدیل می‌شود
- `step_m = sqrt(chunk_size_sqm)` محاسبه می‌شود
- یک grid مستطیلی روی bounding box ساخته می‌شود
- هر cell که با polygon intersect داشته باشد نگه داشته می‌شود
### cell_code
فرمت فعلی deterministic است:
```text
loc-{location_id}__block-{block_code}__chunk-{chunk_size_sqm}__rXXXXcYYYY
```
### metadata summary
پس از ساخت grid:
- روی `BlockSubdivision.metadata["analysis_grid"]`
- و روی `SoilLocation.block_layout`
summary ذخیره می‌شود.
---
## 8) منطق openEO در `openeo_service.py`
این فایل لایه service اصلی برای تحلیل openEO است.
### backend
```text
https://openeofed.dataspace.copernicus.eu
```
### هدف
گرفتن batch metricها برای مجموعه‌ای از `AnalysisGridCell`ها.
### جریان کلی
1. اتصال و auth به openEO
2. ساخت `FeatureCollection` از cellها
3. ساخت `spatial_extent`
4. اجرای یک job per metric روی همه cellها
5. parse کردن نتیجه aggregate_spatial
6. merge کردن metricها روی map keyed by `cell_code`
### metricهای فعلی
- `ndvi` از `SENTINEL2_L2A`
- `ndwi` از `SENTINEL2_L2A`
- `lst_c` از `SENTINEL3_SLSTR_L2_LST`
- `soil_vv` از `SENTINEL1_GRD`
- `soil_vv_db` در Python از `soil_vv`
- `dem_m` از `COPERNICUS_30`
- `slope_deg` از DEM اگر backend پشتیبانی کند
### cloud mask Sentinel-2
کلاس‌های معتبر SCL:
- `4`
- `5`
- `6`
نکته مهم:
- از `isin()` استفاده نمی‌شود
- فقط logical comparison استفاده می‌شود
### aggregate_spatial
فقط از:
```python
aggregate_spatial(geometries=feature_collection, reducer="mean")
```
استفاده می‌شود.
### slope support
اگر backend `slope()` را پشتیبانی نکند:
- `slope_deg = null`
- و metadata می‌گوید `slope_supported=False`
### normalized output
خروجی نهایی به این شکل است:
```python
{
"results": {
"cell-1": {
"ndvi": ...,
"ndwi": ...,
"lst_c": ...,
"soil_vv": ...,
"soil_vv_db": ...,
"dem_m": ...,
"slope_deg": ...,
}
},
"metadata": {
"backend": "openeo",
"collections_used": [...],
"slope_supported": True,
"job_refs": {},
"failed_metrics": []
}
}
```
---
## 9) Celery workflow در `tasks.py`
### تسک قدیمی
- `fetch_soil_data_task`
برای خاک legacy است.
### workflow جدید remote sensing
تابع/تسک‌های اصلی:
- `run_remote_sensing_analysis(...)`
- `run_remote_sensing_analysis_task.delay(...)`
### ورودی task
- `soil_location_id`
- optional `block_code`
- `temporal_start`
- `temporal_end`
- optional `force_refresh`
- optional `run_id`
### رفتار task
1. `SoilLocation` را پیدا می‌کند
2. `BlockSubdivision` را اگر لازم باشد resolve می‌کند
3. `RemoteSensingRun` را create/update می‌کند
4. `AnalysisGridCell`ها را ensure می‌کند
5. اگر observation برای همان range قبلاً باشد و `force_refresh=False`، دوباره process نمی‌کند
6. در غیر این صورت، `compute_remote_sensing_metrics()` را صدا می‌زند
7. `AnalysisGridObservation`ها را upsert می‌کند
8. status run را success/failure می‌کند
### idempotency
اگر observation قبلاً برای همان:
- cell
- temporal_start
- temporal_end
وجود داشته باشد، duplicate ساخته نمی‌شود.
### retry behavior
task روی خطاهای transient مثل:
- `OpenEOExecutionError`
- `OpenEOServiceError`
- request-level failures
retry می‌کند.
روی auth failure retry نمی‌کند.
---
## 10) APIهای فعلی `location_data`
## 10.1) `GET /api/soil-data/`
کاربرد:
- فقط اطلاعات ذخیره‌شده location را برمی‌گرداند
- subdivision را rerun نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
خروجی:
- location data
- block layout
- block subdivisions
- depths
---
## 10.2) `POST /api/soil-data/`
کاربرد:
- `SoilLocation` را create/get می‌کند
- در صورت نیاز `BlockSubdivision` می‌سازد
ورودی:
- `lat`
- `lon`
- `block_count`
- `block_code`
- `farm_boundary`
خروجی:
- location کامل
- `source` = `created` یا `database`
---
## 10.3) `GET /api/soil-data/tasks/<task_id>/status/`
کاربرد:
- status task قدیمی fetch خاک
---
## 10.4) `POST /api/soil-data/ndvi-health/`
کاربرد:
- NDVI health مستقل برای farm
---
## 10.5) `POST /api/soil-data/remote-sensing/`
کاربرد:
- remote sensing analysis را queue می‌کند
- heavy work را sync اجرا نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
- `start_date`
- `end_date`
- optional `force_refresh`
رفتار:
1. location را پیدا می‌کند
2. run می‌سازد
3. Celery task را enqueue می‌کند
4. `202 Accepted` برمی‌گرداند
خروجی شامل:
- `status=processing`
- `source=processing`
- `location`
- `block_code`
- `chunk_size_sqm`
- `temporal_extent`
- `summary` خالی
- `cells=[]`
- `run`
- `task_id`
---
## 10.6) `GET /api/soil-data/remote-sensing/`
کاربرد:
- فقط cache دیتابیسی remote sensing را می‌خواند
- هیچ openEO یا subdivision sync اجرا نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
- `start_date`
- `end_date`
خروجی حالت‌ها:
### حالت 1: result موجود است
- `status=success`
- `source=database`
- summary metrics
- cells list
- run info
### حالت 2: result هنوز نیست ولی job در حال اجراست
- `status=processing`
- `source=processing`
- summary خالی
- cells empty
- run info
### حالت 3: location نیست
- `404`
---
## 11) serializerهای مهم
### `SoilDataRequestSerializer`
برای endpoint اصلی location.
### `SoilLocationResponseSerializer`
برای بازگشت location + blocks + depths.
### `BlockSubdivisionSerializer`
برای بازگشت subdivision data.
### `RemoteSensingTriggerSerializer`
برای trigger API remote sensing.
### `RemoteSensingCellObservationSerializer`
برای بازگشت per-cell remote sensing metrics.
### `RemoteSensingSummarySerializer`
برای بازگشت summary statisticها.
### `RemoteSensingRunSerializer`
برای بازگشت status run.
### `RemoteSensingResponseSerializer`
برای payload کامل remote sensing GET.
---
## 12) منطق summary statistics در remote sensing GET
در response مربوط به `GET /remote-sensing/` این فیلدها برمی‌گردند:
- `cell_count`
- `ndvi_mean`
- `ndwi_mean`
- `lst_c_mean`
- `soil_vv_db_mean`
- `dem_m_mean`
- `slope_deg_mean`
این‌ها از روی observationهای موجود در DB محاسبه می‌شوند، نه از openEO live.
---
## 13) admin
در `admin.py` الان موارد زیر رجیستر شده‌اند:
- `SoilLocation`
- `SoilDepthData`
- `BlockSubdivision`
- `RemoteSensingRun`
- `AnalysisGridCell`
- `AnalysisGridObservation`
این باعث می‌شود debugging و inspection از طریق admin ممکن باشد.
---
## 14) تست‌های فعلی
### `test_soil_api.py`
- ساخت location
- ساخت subdivision
- رفتار GET/POST location
### `test_block_subdivision.py`
- elbow detection
- payload subdivision
### `test_grid_analysis.py`
- ساخت analysis grid 30x30
- idempotency grid cells
- استفاده از boundary location
### `test_openeo_service.py`
- parse نتیجه aggregate_spatial
- merge metricها
- conversion به dB
### `test_remote_sensing_api.py`
- queue شدن remote sensing task
- processing response
- cache read response
- not found behavior
### `test_ndvi_health_api.py`
- NDVI health API
---
## 15) وابستگی‌های مهم
در `requirements.txt` dependencyهای مهم این بخش‌ها شامل این‌ها هستند:
- `scikit-learn`
- `matplotlib`
- `Pillow`
- `numpy`
- `openeo`
### نقش آن‌ها
- `scikit-learn`: KMeans
- `matplotlib`: elbow plot
- `Pillow`: ImageField support
- `numpy`: وابستگی عددی
- `openeo`: ارتباط با backend سنجش‌ازدور
---
## 16) migrationهای مهم
- `0008_soillocation_block_layout.py`
- `0009_blocksubdivision.py`
- `0010_blocksubdivision_elbow_plot.py`
- `0011_remote_sensing_models.py`
این migrationها ساختار فعلی location/subdivision/remote sensing را ساخته‌اند.
---
## 17) محدودیت‌ها و فرضیات فعلی
### محدودیت‌های فعلی
1. `block_layout` canonical source نیست و summary است.
2. subdivision و analysis grid دو لایه جدا هستند.
3. slope ممکن است روی backend همیشه پشتیبانی نشود.
4. API GET remote-sensing فقط cache می‌خواند.
5. هنوز endpoint مجزای status run نداریم.
6. grid generation از projection محلی استفاده می‌کند، نه GIS stack سنگین.
7. openEO calls فعلاً برای batch metric processing طراحی شده‌اند، نه orchestration پیچیده job lifecycle.
### فرضیات
1. مزرعه‌ها آن‌قدر کوچک هستند که local projected approximation مناسب باشد.
2. `SUBDIVISION_CHUNK_SQM=900` برای workflow فعلی درست است.
3. `cell_code` deterministic بودن برای idempotency کافی است.
4. `AnalysisGridObservation` cache اصلی remote sensing است.
---
## 18) جریان کامل داده از ابتدا تا نتیجه remote sensing
### مرحله 1: ایجاد location
کاربر `POST /api/soil-data/` را صدا می‌زند.
نتیجه:
- `SoilLocation` ساخته می‌شود
- `farm_boundary` ذخیره می‌شود
- block layout ساخته می‌شود
- در صورت نیاز `BlockSubdivision` ساخته می‌شود
### مرحله 2: تولید analysis grid
وقتی task remote sensing اجرا می‌شود:
- اگر cellها قبلاً نباشند، `AnalysisGridCell`ها ساخته می‌شوند
### مرحله 3: اجرای openEO
Celery task:
- FeatureCollection از cellها می‌سازد
- metricها را batch اجرا می‌کند
- نتیجه را parse می‌کند
### مرحله 4: ذخیره observation
برای هر cell:
- یک `AnalysisGridObservation` برای بازه زمانی موردنظر ذخیره/آپدیت می‌شود
### مرحله 5: بازگشت نتیجه از API
کاربر `GET /api/soil-data/remote-sensing/` را صدا می‌زند.
سیستم:
- فقط DB را می‌خواند
- summary می‌سازد
- cells را برمی‌گرداند
---
## 19) پیشنهاد توسعه بعدی
برای ادامه توسعه، این‌ها منطقی‌ترین قدم‌ها هستند:
1. ساخت endpoint مستقل status برای `RemoteSensingRun`
2. اضافه کردن pagination برای cell responseها
3. اضافه کردن job reference واقعی openEO در metadata
4. پشتیبانی از چند resolution دیگر غیر از 30x30
5. ساخت serializer/model جدا برای summaryهای precomputed
6. اضافه کردن نمودارها یا aggregationهای block-level
7. اتصال remote sensing resultها به recommendation engine
---
## 20) جمع‌بندی نهایی
اپ `location_data` الان یک سیستم چندلایه است:
### لایه مکانی پایه
- `SoilLocation`
- `farm_boundary`
- `block_layout`
### لایه subdivision
- `BlockSubdivision`
- KMeans
- elbow plot
### لایه grid analysis
- `AnalysisGridCell`
### لایه observation
- `AnalysisGridObservation`
- `RemoteSensingRun`
### لایه سرویس
- `block_subdivision.py`
- `grid_analysis.py`
- `openeo_service.py`
- `tasks.py`
### لایه API
- `SoilDataView`
- `RemoteSensingAnalysisView`
- `NdviHealthView`
در نتیجه، `location_data` الان از یک app ساده location عبور کرده و به یک زیرسیستم کامل spatial + remote sensing تبدیل شده است.