UPDATE
This commit is contained in:
+280
-36
@@ -1,42 +1,49 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import SoilDepthData, SoilLocation
|
||||
from .soil_adapters import DEPTHS
|
||||
from .data_driven_subdivision import SUPPORTED_CLUSTER_FEATURES
|
||||
from .models import (
|
||||
AnalysisGridObservation,
|
||||
BlockSubdivision,
|
||||
RemoteSensingRun,
|
||||
RemoteSensingClusterAssignment,
|
||||
RemoteSensingSubdivisionResult,
|
||||
SoilLocation,
|
||||
)
|
||||
from .satellite_snapshot import build_location_block_satellite_snapshots
|
||||
|
||||
|
||||
class SoilDataRequestSerializer(serializers.Serializer):
|
||||
"""سریالایزر ورودی: lon و lat برای درخواست داده خاک."""
|
||||
"""ورودی ثبت مزرعه و بلوکهای تعریفشده توسط کشاورز."""
|
||||
|
||||
class BlockInputSerializer(serializers.Serializer):
|
||||
block_code = serializers.CharField(max_length=64)
|
||||
boundary = serializers.JSONField()
|
||||
order = serializers.IntegerField(required=False, min_value=1)
|
||||
|
||||
lon = serializers.DecimalField(max_digits=9, decimal_places=6, required=True)
|
||||
lat = serializers.DecimalField(max_digits=9, decimal_places=6, required=True)
|
||||
block_count = serializers.IntegerField(required=False, min_value=1, default=1)
|
||||
block_code = serializers.CharField(required=False, default="block-1", max_length=64)
|
||||
farm_boundary = serializers.JSONField(required=False)
|
||||
blocks = BlockInputSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilDepthDataSerializer(serializers.ModelSerializer):
|
||||
"""سریالایزر خروجی برای هر عمق خاک."""
|
||||
|
||||
class Meta:
|
||||
model = SoilDepthData
|
||||
fields = [
|
||||
"depth_label",
|
||||
"bdod",
|
||||
"cec",
|
||||
"cfvo",
|
||||
"clay",
|
||||
"nitrogen",
|
||||
"ocd",
|
||||
"ocs",
|
||||
"phh2o",
|
||||
"sand",
|
||||
"silt",
|
||||
"soc",
|
||||
"wv0010",
|
||||
"wv0033",
|
||||
"wv1500",
|
||||
]
|
||||
def validate(self, attrs):
|
||||
blocks = attrs.get("blocks") or []
|
||||
if self.context.get("require_farm_boundary") and not attrs.get("farm_boundary"):
|
||||
raise serializers.ValidationError(
|
||||
{"farm_boundary": ["مختصات گوشههای کل زمین باید ارسال شود."]}
|
||||
)
|
||||
if self.context.get("require_farm_boundary") and not blocks:
|
||||
raise serializers.ValidationError(
|
||||
{"blocks": ["مختصات بلوکهای تعریفشده توسط کشاورز باید ارسال شود."]}
|
||||
)
|
||||
if blocks:
|
||||
attrs["block_count"] = len(blocks)
|
||||
return attrs
|
||||
|
||||
|
||||
class SoilLocationResponseSerializer(serializers.ModelSerializer):
|
||||
"""سریالایزر خروجی برای SoilLocation همراه با depths."""
|
||||
"""سریالایزر خروجی برای SoilLocation همراه با خلاصه سنجشازدور."""
|
||||
|
||||
lon = serializers.DecimalField(
|
||||
source="longitude",
|
||||
@@ -50,19 +57,51 @@ class SoilLocationResponseSerializer(serializers.ModelSerializer):
|
||||
decimal_places=6,
|
||||
read_only=True,
|
||||
)
|
||||
depths = serializers.SerializerMethodField()
|
||||
input_block_count = serializers.IntegerField(read_only=True)
|
||||
farm_boundary = serializers.JSONField(read_only=True)
|
||||
block_layout = serializers.JSONField(read_only=True)
|
||||
block_subdivisions = serializers.SerializerMethodField()
|
||||
satellite_snapshots = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SoilLocation
|
||||
fields = ["id", "lon", "lat", "depths"]
|
||||
fields = [
|
||||
"id",
|
||||
"lon",
|
||||
"lat",
|
||||
"input_block_count",
|
||||
"farm_boundary",
|
||||
"block_layout",
|
||||
"block_subdivisions",
|
||||
"satellite_snapshots",
|
||||
]
|
||||
|
||||
def get_depths(self, obj):
|
||||
depth_qs = obj.depths.all()
|
||||
order = {d: i for i, d in enumerate(DEPTHS)}
|
||||
sorted_depths = sorted(
|
||||
depth_qs, key=lambda d: order.get(d.depth_label, 99)
|
||||
)
|
||||
return SoilDepthDataSerializer(sorted_depths, many=True).data
|
||||
def get_block_subdivisions(self, obj):
|
||||
subdivisions = obj.block_subdivisions.all().order_by("block_code", "id")
|
||||
return BlockSubdivisionSerializer(subdivisions, many=True).data
|
||||
|
||||
def get_satellite_snapshots(self, obj):
|
||||
return build_location_block_satellite_snapshots(obj)
|
||||
|
||||
|
||||
class BlockSubdivisionSerializer(serializers.ModelSerializer):
|
||||
elbow_plot = serializers.ImageField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = BlockSubdivision
|
||||
fields = [
|
||||
"block_code",
|
||||
"chunk_size_sqm",
|
||||
"grid_points",
|
||||
"centroid_points",
|
||||
"grid_point_count",
|
||||
"centroid_count",
|
||||
"elbow_plot",
|
||||
"status",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class SoilDataTaskResponseSerializer(serializers.Serializer):
|
||||
@@ -94,3 +133,208 @@ class NdviHealthResponseSerializer(serializers.Serializer):
|
||||
observation_date = serializers.CharField(allow_null=True)
|
||||
satellite_source = serializers.CharField(allow_null=True)
|
||||
healthData = NdviHealthDataItemSerializer(many=True)
|
||||
|
||||
|
||||
class RemoteSensingTriggerSerializer(serializers.Serializer):
|
||||
lon = serializers.DecimalField(max_digits=9, decimal_places=6, required=True)
|
||||
lat = serializers.DecimalField(max_digits=9, decimal_places=6, required=True)
|
||||
block_code = serializers.CharField(required=False, allow_blank=True, default="", max_length=64)
|
||||
start_date = serializers.DateField(required=True)
|
||||
end_date = serializers.DateField(required=True)
|
||||
force_refresh = serializers.BooleanField(required=False, default=False)
|
||||
cluster_count = serializers.IntegerField(required=False, min_value=1, allow_null=True, default=None)
|
||||
selected_features = serializers.ListField(
|
||||
child=serializers.CharField(max_length=64),
|
||||
required=False,
|
||||
allow_empty=False,
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs["start_date"] > attrs["end_date"]:
|
||||
raise serializers.ValidationError("start_date نمیتواند بعد از end_date باشد.")
|
||||
selected_features = attrs.get("selected_features") or []
|
||||
invalid_features = sorted(
|
||||
feature_name
|
||||
for feature_name in selected_features
|
||||
if feature_name not in SUPPORTED_CLUSTER_FEATURES
|
||||
)
|
||||
if invalid_features:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"selected_features": [
|
||||
"ویژگیهای نامعتبر برای خوشهبندی: "
|
||||
+ ", ".join(invalid_features)
|
||||
]
|
||||
}
|
||||
)
|
||||
return attrs
|
||||
|
||||
|
||||
class RemoteSensingResultQuerySerializer(RemoteSensingTriggerSerializer):
|
||||
page = serializers.IntegerField(required=False, min_value=1, default=1)
|
||||
page_size = serializers.IntegerField(required=False, min_value=1, max_value=200, default=100)
|
||||
|
||||
|
||||
class RemoteSensingCellObservationSerializer(serializers.ModelSerializer):
|
||||
cell_code = serializers.CharField(source="cell.cell_code", read_only=True)
|
||||
block_code = serializers.CharField(source="cell.block_code", read_only=True)
|
||||
chunk_size_sqm = serializers.IntegerField(source="cell.chunk_size_sqm", read_only=True)
|
||||
centroid_lat = serializers.DecimalField(source="cell.centroid_lat", max_digits=9, decimal_places=6, read_only=True)
|
||||
centroid_lon = serializers.DecimalField(source="cell.centroid_lon", max_digits=9, decimal_places=6, read_only=True)
|
||||
geometry = serializers.JSONField(source="cell.geometry", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AnalysisGridObservation
|
||||
fields = [
|
||||
"cell_code",
|
||||
"block_code",
|
||||
"chunk_size_sqm",
|
||||
"centroid_lat",
|
||||
"centroid_lon",
|
||||
"geometry",
|
||||
"temporal_start",
|
||||
"temporal_end",
|
||||
"ndvi",
|
||||
"ndwi",
|
||||
"lst_c",
|
||||
"soil_vv",
|
||||
"soil_vv_db",
|
||||
"dem_m",
|
||||
"slope_deg",
|
||||
"metadata",
|
||||
]
|
||||
|
||||
|
||||
class RemoteSensingSummarySerializer(serializers.Serializer):
|
||||
cell_count = serializers.IntegerField()
|
||||
ndvi_mean = serializers.FloatField(allow_null=True)
|
||||
ndwi_mean = serializers.FloatField(allow_null=True)
|
||||
lst_c_mean = serializers.FloatField(allow_null=True)
|
||||
soil_vv_db_mean = serializers.FloatField(allow_null=True)
|
||||
dem_m_mean = serializers.FloatField(allow_null=True)
|
||||
slope_deg_mean = serializers.FloatField(allow_null=True)
|
||||
|
||||
|
||||
class RemoteSensingRunSerializer(serializers.ModelSerializer):
|
||||
status_label = serializers.SerializerMethodField()
|
||||
pipeline_status = serializers.SerializerMethodField()
|
||||
stage = serializers.SerializerMethodField()
|
||||
selected_features = serializers.SerializerMethodField()
|
||||
requested_cluster_count = serializers.SerializerMethodField()
|
||||
|
||||
def get_status_label(self, obj):
|
||||
return obj.normalized_status
|
||||
|
||||
def get_pipeline_status(self, obj):
|
||||
return obj.normalized_status
|
||||
|
||||
def get_stage(self, obj):
|
||||
return (obj.metadata or {}).get("stage")
|
||||
|
||||
def get_selected_features(self, obj):
|
||||
return (obj.metadata or {}).get("selected_features", [])
|
||||
|
||||
def get_requested_cluster_count(self, obj):
|
||||
return (obj.metadata or {}).get("requested_cluster_count")
|
||||
|
||||
class Meta:
|
||||
model = RemoteSensingRun
|
||||
fields = [
|
||||
"id",
|
||||
"block_code",
|
||||
"chunk_size_sqm",
|
||||
"temporal_start",
|
||||
"temporal_end",
|
||||
"status",
|
||||
"status_label",
|
||||
"pipeline_status",
|
||||
"stage",
|
||||
"selected_features",
|
||||
"requested_cluster_count",
|
||||
"metadata",
|
||||
"error_message",
|
||||
"started_at",
|
||||
"finished_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class RemoteSensingClusterAssignmentSerializer(serializers.ModelSerializer):
|
||||
cell_code = serializers.CharField(source="cell.cell_code", read_only=True)
|
||||
centroid_lat = serializers.DecimalField(source="cell.centroid_lat", max_digits=9, decimal_places=6, read_only=True)
|
||||
centroid_lon = serializers.DecimalField(source="cell.centroid_lon", max_digits=9, decimal_places=6, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RemoteSensingClusterAssignment
|
||||
fields = [
|
||||
"cell_code",
|
||||
"cluster_label",
|
||||
"centroid_lat",
|
||||
"centroid_lon",
|
||||
"raw_feature_values",
|
||||
"scaled_feature_values",
|
||||
]
|
||||
|
||||
|
||||
class RemoteSensingSubdivisionResultSerializer(serializers.ModelSerializer):
|
||||
assignments = serializers.SerializerMethodField()
|
||||
|
||||
def get_assignments(self, obj):
|
||||
assignments = self.context.get("paginated_assignments")
|
||||
if assignments is None:
|
||||
assignments = obj.assignments.all().order_by("cluster_label", "cell__cell_code")
|
||||
return RemoteSensingClusterAssignmentSerializer(assignments, many=True).data
|
||||
|
||||
class Meta:
|
||||
model = RemoteSensingSubdivisionResult
|
||||
fields = [
|
||||
"id",
|
||||
"block_code",
|
||||
"chunk_size_sqm",
|
||||
"temporal_start",
|
||||
"temporal_end",
|
||||
"cluster_count",
|
||||
"selected_features",
|
||||
"skipped_cell_codes",
|
||||
"metadata",
|
||||
"assignments",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class RemoteSensingResponseSerializer(serializers.Serializer):
|
||||
status = serializers.CharField()
|
||||
source = serializers.CharField()
|
||||
location = SoilLocationResponseSerializer()
|
||||
block_code = serializers.CharField(allow_blank=True)
|
||||
chunk_size_sqm = serializers.IntegerField(allow_null=True)
|
||||
temporal_extent = serializers.JSONField()
|
||||
summary = RemoteSensingSummarySerializer()
|
||||
cells = RemoteSensingCellObservationSerializer(many=True)
|
||||
run = RemoteSensingRunSerializer(allow_null=True)
|
||||
subdivision_result = RemoteSensingSubdivisionResultSerializer(allow_null=True)
|
||||
pagination = serializers.JSONField(required=False)
|
||||
|
||||
|
||||
|
||||
class RemoteSensingRunStatusResponseSerializer(serializers.Serializer):
|
||||
status = serializers.CharField()
|
||||
source = serializers.CharField()
|
||||
run = RemoteSensingRunSerializer()
|
||||
task_id = serializers.CharField(allow_blank=True, allow_null=True, required=False)
|
||||
|
||||
|
||||
class RemoteSensingRunResultResponseSerializer(serializers.Serializer):
|
||||
status = serializers.CharField()
|
||||
source = serializers.CharField()
|
||||
location = SoilLocationResponseSerializer()
|
||||
block_code = serializers.CharField(allow_blank=True)
|
||||
chunk_size_sqm = serializers.IntegerField(allow_null=True)
|
||||
temporal_extent = serializers.JSONField()
|
||||
summary = RemoteSensingSummarySerializer()
|
||||
cells = RemoteSensingCellObservationSerializer(many=True)
|
||||
run = RemoteSensingRunSerializer()
|
||||
subdivision_result = RemoteSensingSubdivisionResultSerializer(allow_null=True)
|
||||
pagination = serializers.JSONField(required=False)
|
||||
|
||||
Reference in New Issue
Block a user