19 KiB
مستند کامل عملکرد فعلی location_data
این فایل شرح میدهد که اپ location_data در وضعیت فعلی دقیقاً چه کاری انجام میدهد، چه مدلهایی دارد، جریان درخواستها چگونه است، منطق تقسیمبندی بلوکها چگونه اجرا میشود و چه بخشهایی فقط داده ذخیرهشده را برمیگردانند.
1) هدف فعلی اپ location_data
اپ location_data در وضعیت فعلی چند مسئولیت اصلی دارد:
- نگهداری موقعیت جغرافیایی زمین با
latوlon - نگهداری مرز زمین یا بلوک در
farm_boundary - نگهداری ساختار بلوکهای اصلی زمین در
block_layout - نگهداری نتیجه خردسازی هوشمند هر بلوک در مدل
BlockSubdivision - تولید نقاط شبکهای 100 متری یا هر اندازهای که با
SUBDIVISION_CHUNK_SQMتنظیم شود - اجرای خوشهبندی
KMeansروی نقاط شبکهای - پیدا کردن تعداد بهینه خوشهها با روش
Elbow - ذخیره centroidهای نهایی هر بخش خردشده
- تولید و ذخیره تصویر نمودار
K-SSEبرای هر subdivision - نگهداری دادههای خاک در
SoilDepthData - نگهداری دادههای NDVI در
NdviObservation
نکته مهم:
- در فاز فعلی،
GETهیچ پردازش جدیدی انجام نمیدهد. - تمام پردازش subdivision فقط در زمان
POSTو فقط اگر subdivision آن بلوک قبلاً ساخته نشده باشد اجرا میشود.
2) تنظیمات محیطی
SUBDIVISION_CHUNK_SQM
در config/settings.py یک متغیر جدید اضافه شده است:
SUBDIVISION_CHUNK_SQM- مقدار پیشفرض:
100 - واحد: متر مربع
کاربرد:
- تعیین میکند شبکه اولیه برای subdivision با چه اندازهای ساخته شود.
- اگر مقدار
100باشد، هر chunk تقریباً یک سلول10m x 10mخواهد بود، چون:
step = sqrt(100) = 10 meters
این مقدار از .env یا environment خوانده میشود:
SUBDIVISION_CHUNK_SQM=100
3) مدلهای اصلی اپ
3.1) SoilLocation
این مدل رکورد اصلی location را نگه میدارد.
فیلدها
latitudelongitudetask_idfarm_boundaryinput_block_countblock_layoutcreated_atupdated_at
نقش
- هر location با ترکیب
latitude + longitudeیکتا است. - اطلاعات کلی زمین یا مرکز زمین را نگه میدارد.
- اگر هنوز هیچ تقسیمبندی انجام نشده باشد، ساختار اولیه بلوکها را در
block_layoutنگه میدارد.
block_layout
این فیلد 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": 100,
"grid_point_count": 24,
"centroid_count": 3,
"optimal_k": 3
}
}
]
}
رفتار مهم
- اگر
block_layoutخالی باشد، به صورت پیشفرض با یک بلوک کامل ساخته میشود. - متد
set_input_block_count()ساختار اولیه بلوکهای اصلی را میسازد.
3.2) BlockSubdivision
این مدل نتیجه واقعی subdivision برای هر بلوک را ذخیره میکند.
فیلدها
soil_location: ارتباط باSoilLocationblock_code: شناسه بلوکی که subdivision روی آن اجرا شدهsource_boundary: مرز همان بلوکchunk_size_sqm: اندازه هر chunkgrid_points: نقاط اولیه شبکهcentroid_points: centroidهای نهایی خوشههاgrid_point_count: تعداد نقاط اولیهcentroid_count: تعداد centroidهای نهاییelbow_plot: تصویر نمودار elbowstatus: وضعیت رکوردmetadata: داده تکمیلی مانندoptimal_kوinertia_curvecreated_atupdated_at
نقش
این مدل منبع اصلی داده subdivision است.
یعنی:
- نقاط خام شبکه در این مدل ذخیره میشوند
- centroidهای نهایی هم در این مدل ذخیره میشوند
- نمودار elbow هم در همین مدل ذخیره میشود
قید یکتا
برای هر location و هر block_code فقط یک subdivision وجود دارد:
(soil_location, block_code) unique
بنابراین اگر برای یک بلوک قبلاً subdivision ساخته شده باشد، دوباره ایجاد نمیشود.
3.3) SoilDepthData
این مدل دادههای خاک برای عمقهای مختلف را نگه میدارد.
عمقهای فعلی:
0-5cm5-15cm15-30cm
این بخش در حال حاضر مستقل از subdivision است و هنوز برای هر sub-block جداگانه داده خاک تولید نمیکند.
3.4) NdviObservation
این مدل دادههای NDVI و سلامت پوشش گیاهی را نگه میدارد.
این بخش هم فعلاً مستقل از منطق subdivision است.
4) فایل block_subdivision.py
فایل location_data/block_subdivision.py مرکز اصلی منطق هوشمند subdivision است.
وظایف اصلی این فایل
- استخراج polygon از ورودی
- تبدیل مختصات جغرافیایی به صفحه محلی متری
- ساخت grid points با اندازه chunk مشخص
- اجرای
KMeansبرایK=1..10 - ذخیره
SSEیا همانInertia - پیدا کردن elbow point
- ساخت centroidهای نهایی خوشهها
- sync کردن نتیجه با
block_layout - تولید تصویر نمودار elbow
- ذخیره تصویر در مدل با
ContentFile
5) روند هندسی subdivision
5.1) استخراج Polygon
ورودی boundary میتواند به چند شکل بیاید:
- GeoJSON Polygon
corners- آرایه مستقیم از نقاط
تابع extract_polygon() این ورودی را به لیستی از نقاط جغرافیایی تبدیل میکند.
نمونه ورودی معتبر:
{
"type": "Polygon",
"coordinates": [
[
[51.3890, 35.6890],
[51.3902, 35.6890],
[51.3902, 35.6900],
[51.3890, 35.6900],
[51.3890, 35.6890]
]
]
}
5.2) تبدیل مختصات به فضای محلی متری
برای اینکه بتوانیم فاصلهها و گریدبندی را بر اساس متر حساب کنیم، polygon از مختصات جغرافیایی به مختصات محلی متری تبدیل میشود.
تابع مربوط:
project_polygon_to_local_meters()
ویژگی این تبدیل:
- نقطه اول polygon به عنوان origin در نظر گرفته میشود
- با تقریب محلی،
lat/lonبهx/yدر واحد متر تبدیل میشوند
این تبدیل برای subdivision کوچک و محلی مناسب است.
5.3) تولید grid points
تابع:
generate_grid_points()
منطق:
- ابتدا اندازه گام محاسبه میشود:
step_m = sqrt(chunk_size_sqm)
- روی bounding box polygon، نقاط مرکزی grid بررسی میشوند.
- هر نقطهای که داخل polygon باشد نگه داشته میشود.
خروجی:
grid_points: مختصات جغرافیایی قابل ذخیره در JSONgrid_vectors: مختصات محلی متری برای ورود بهKMeans
نمونه هر grid point:
{
"point_code": "pt-1",
"lat": 35.689123,
"lon": 51.389456
}
6) الگوریتم خوشهبندی هوشمند
6.1) اجرای KMeans
تابع:
cluster_grid_points()
منطق:
- روی
grid_vectorsخوشهبندی انجام میشود - برای
K=1تاK=10اجرا میشود - اگر تعداد نقاط کمتر از 10 باشد،
max_k = len(grid_vectors)در نظر گرفته میشود
برای هر K:
- مدل
KMeansساخته میشود fit()اجرا میشود- مقدار
model.inertia_به عنوانSSEذخیره میشود
خروجی میانی:
[
{"k": 1, "sse": 1300.5},
{"k": 2, "sse": 640.2},
{"k": 3, "sse": 390.1}
]
6.2) پیدا کردن Elbow Point
تابع:
detect_elbow_point()
منطق فعلی:
- از روی SSEها، شیب افت بین نقاط متوالی محاسبه میشود.
- سپس تغییرات شیب محاسبه میشود.
- هر جایی که افت شیب ناگهان متوقف شود، همان نقطه elbow در نظر گرفته میشود.
یعنی در عمل:
- ابتدا
slopesمحاسبه میشود - سپس اختلاف شیبها بررسی میشود
- بیشترین تغییر شیب به عنوان elbow انتخاب میشود
خروجی:
optimal_k
6.3) تولید centroidهای نهایی
بعد از پیدا شدن optimal_k:
- مدل
KMeansهمانKنهایی انتخاب میشود - مختصات مراکز خوشهها (
cluster_centers_) گرفته میشود - از فضای متری به
lat/lonتبدیل میشود - در
centroid_pointsذخیره میشود
نمونه centroid:
{
"sub_block_code": "sub-block-1",
"centroid_lat": 35.689321,
"centroid_lon": 51.389789
}
این centroidها در عمل همان مراکز بخشهای کوچکتر زمین هستند.
7) تولید و ذخیره نمودار Elbow
تابع
render_elbow_plot()
منطق
پس از محاسبه inertia_curve و optimal_k:
- نمودار
Kدر برابرSSEرسم میشود - نقطه elbow با رنگ قرمز مشخص میشود
- تصویر به صورت PNG در
BytesIOذخیره میشود - با
ContentFileبهImageFieldمدلBlockSubdivisionداده میشود
نکته مهم حافظه
برای جلوگیری از memory leak:
- از backend غیرتعاملی
Aggاستفاده میشود - بعد از ذخیره تصویر،
plt.close(fig)اجرا میشود - buffer هم بسته میشود
این برای پردازشهای همزمان سرور ضروری است.
8) جریان کامل POST /api/soil-data/
این endpoint الان مهمترین ورودی subdivision است.
ورودیهای قابل پشتیبانی
latlonblock_countblock_codefarm_boundary
سناریوی اجرا
مرحله 1: اعتبارسنجی ورودی
سریالایزر SoilDataRequestSerializer داده را validate میکند.
مرحله 2: پیدا کردن یا ساخت location
بر اساس lat/lon:
- اگر location وجود نداشته باشد ساخته میشود
- اگر وجود داشته باشد از همان رکورد استفاده میشود
مرحله 3: آپدیت ساختار اولیه بلوکها
اگر block_count فرق کرده باشد:
block_layoutدوباره باset_input_block_count()ساخته میشود
مرحله 4: انتخاب boundary برای subdivision
اولویت:
farm_boundaryارسالی در request- اگر نبود،
location.farm_boundaryذخیرهشده
مرحله 5: اجرای subdivision فقط در صورت نیاز
تابع:
create_or_get_block_subdivision()
اگر رکورد (location, block_code) از قبل وجود داشته باشد:
- هیچ پردازش جدیدی اجرا نمیشود
- همان رکورد قبلی برگردانده میشود
اگر وجود نداشته باشد:
- grid ساخته میشود
- KMeans اجرا میشود
- elbow پیدا میشود
- centroidها ساخته میشوند
- نمودار elbow ساخته میشود
- همه چیز در
BlockSubdivisionذخیره میشود block_layoutباsub_blockssync میشود
مرحله 6: response
خروجی شامل اینهاست:
- اطلاعات
SoilLocation farm_boundaryblock_layoutblock_subdivisionsdepths
فیلد source در response:
createdاگر location یا subdivision جدید ساخته شده باشدdatabaseاگر قبلاً وجود داشته باشد
9) جریان کامل GET /api/soil-data/
این endpoint الان فقط برای read استفاده میشود.
ورودی
latlonblock_codeاختیاری
رفتار
- location را از دیتابیس پیدا میکند
- subdivisionهای ذخیرهشده را میخواند
- هیچ الگوریتمی را اجرا نمیکند
- هیچ
KMeansیا پردازش هندسی انجام نمیدهد
پاسخ
داده ذخیرهشده را با source = database برمیگرداند.
اگر location پیدا نشود:
404
10) نقش serializers.py
SoilDataRequestSerializer
ورودی endpoint اصلی را مدیریت میکند:
latlonblock_countblock_codefarm_boundary
SoilLocationResponseSerializer
خروجی location را برمیگرداند:
idlatloninput_block_countfarm_boundaryblock_layoutblock_subdivisionsdepths
BlockSubdivisionSerializer
خروجی subdivision را برمیگرداند:
block_codechunk_size_sqmgrid_pointscentroid_pointsgrid_point_countcentroid_countelbow_plotstatusmetadatacreated_atupdated_at
11) نقش block_layout در کنار BlockSubdivision
در معماری فعلی دو سطح ذخیرهسازی داریم:
11.1) BlockSubdivision
منبع اصلی و canonical برای subdivision
11.2) block_layout
خلاصهای از نتیجه subdivision برای مصرف سریعتر در response و ساختار کلی location
یعنی:
- داده دقیق در
BlockSubdivisionاست - خلاصه آن در
block_layout.blocks[].sub_blocksقرار میگیرد
12) وضعیت فعلی بخشهای قدیمیتر اپ
12.1) tasks.py
این فایل هنوز وجود دارد و برای fetch داده خاک به صورت قدیمی استفاده میشود، اما در مسیر subdivision فعلی نقشی ندارد.
12.2) soil_adapters.py
این فایل adapterهای داده خاک را نگه میدارد و فعلاً برای subdivision استفاده نمیشود.
12.3) remote_sensing.py
منطق سنجشازدور را نگه میدارد و هنوز مستقیماً به subdivision وصل نشده است.
12.4) ndvi.py
برای endpoint مربوط به NDVI استفاده میشود و فعلاً از centroidهای subdivision استفاده نمیکند.
13) وابستگیهای جدید
برای عملکرد فعلی subdivision این dependencyها لازم هستند:
scikit-learnmatplotlibPillownumpy
دلیل هرکدام
scikit-learn: اجرایKMeansmatplotlib: رسم elbow plotPillow: پشتیبانی ازImageFieldnumpy: وابستگی پایهscikit-learn
14) migrationهای مهم مرتبط با ساختار فعلی
-
0008_soillocation_block_layout.py- اضافه شدن
input_block_count - اضافه شدن
block_layout
- اضافه شدن
-
0009_blocksubdivision.py- اضافه شدن مدل
BlockSubdivision
- اضافه شدن مدل
-
0010_blocksubdivision_elbow_plot.py- اضافه شدن فیلد
elbow_plot
- اضافه شدن فیلد
15) محدودیتهای فعلی
چند محدودیت مهم در پیادهسازی فعلی وجود دارد:
- subdivision فعلاً بر اساس هندسه و خوشهبندی نقاط انجام میشود، نه بر اساس داده واقعی خاک یا NDVI
- برای هر
block_codeفرض میشود یک مرز مستقل از بیرون داده میشود - هنوز برای هر
sub_blockرکورد location مستقل ساخته نمیشود - هنوز داده خاک، هوا و NDVI برای centroidهای جدید به صورت جداگانه fetch نمیشود
- elbow detection فعلی heuristic-based است و هنوز نسخه پیشرفتهتر آماری ندارد
16) تستهای مرتبط
location_data/test_block_subdivision.py
این تستها بررسی میکنند:
- elbow detection کار میکند
- payload subdivision ساخته میشود
- grid points و centroid points خروجی دارند
location_data/test_soil_api.py
این تستها بررسی میکنند:
POSTsubdivision جدید میسازدGETفقط داده ذخیرهشده را برمیگرداند- الگوریتم در
GETدوباره اجرا نمیشود
17) جمعبندی معماری فعلی
در وضعیت فعلی، location_data این معماری را دارد:
لایه 1: Location پایه
SoilLocationfarm_boundaryblock_layout
لایه 2: Subdivision هوشمند
BlockSubdivision- grid generation
- KMeans
- elbow detection
- centroid generation
- elbow plot generation
لایه 3: دادههای مکمل
SoilDepthDataNdviObservation- بخشهای legacy مثل
tasks.py
در نتیجه، اپ الان میتواند:
- یک بلوک با مرز مشخص بگیرد
- آن را به نقاط شبکهای خرد کند
- تعداد بهینه بخشها را با KMeans + Elbow پیدا کند
- centroidهای نهایی را ذخیره کند
- نمودار elbow را ذخیره کند
- و در درخواستهای بعدی فقط همان نتیجه ذخیرهشده را بدون پردازش مجدد برگرداند
18) پیشنهاد برای مراحل بعدی
اگر در مرحله بعد بخواهی این ساختار را توسعه بدهی، منطقیترین قدمها اینها هستند:
- ساخت endpoint مستقل برای subdivision هر block
- اتصال هر centroid به fetch داده خاک و هوا
- ساخت رکورد مستقل برای هر
sub_block - استفاده از NDVI یا داده سنسور برای تعیین
Kیا وزندهی خوشهها - نمایش مستقیم
elbow_plotبا URL کامل media