Files
fquiz/api/app/schemas/elevation.py
T
chengkai3 2a5cbe474b fix:[FL-92][高程数据管理预览时地形图渲染不出来] - 添加minzoom字段
修复地形图layer.json缺少minzoom字段导致Cesium无法正确加载地形的问题。

根本原因:
- Cesium地形加载需要layer.json同时包含minzoom和maxzoom字段
- 之前只有maxzoom字段,导致Cesium不知道从哪个层级开始加载地形
- 虽然瓦片数据正常(API返回200),但前端无法正确渲染三维地形

修改内容:
1. 在ElevationTerrainLayerResponse模型中添加minzoom字段(schemas/elevation.py)
2. 在地形构建代码中设置minzoom=0(services/elevation_service.py)
3. 地形瓦片的zoom level范围现在完整定义为0-6

影响:
- 已构建的地形数据需要重新触发构建任务才能生成包含minzoom的新layer.json
- 新构建的地形将自动包含minzoom字段

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-12 22:57:25 +08:00

291 lines
9.1 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, Field
ElevationDatasetStatus = Literal["active", "disabled"]
ElevationDatasetUsageStatus = Literal["idle", "in_use"]
ElevationDatasetTerrainStatus = Literal["pending", "processing", "ready", "failed", "not_supported"]
ElevationApplyMode = Literal["fill_null_only", "overwrite_all"]
ElevationApplyJobStatus = Literal["pending", "running", "success", "failed"]
ElevationDataImportJobStatus = Literal["pending", "running", "success", "failed"]
class ElevationDatasetSummary(BaseModel):
id: str
code: str
name: str
source: str | None = None
file_format: str
mount_code: str
dataset_dir: str
file_path: str
resolution_m: float | None = None
status: ElevationDatasetStatus
usage_status: ElevationDatasetUsageStatus
sample_count: int = 0
bbox_min_lon: float | None = None
bbox_max_lon: float | None = None
bbox_min_lat: float | None = None
bbox_max_lat: float | None = None
analysis_task_id: str | None = None
analysis_status: str = "not_started"
analysis_error_message: str | None = None
analysis_started_at: datetime | None = None
analysis_finished_at: datetime | None = None
terrain_status: ElevationDatasetTerrainStatus = "not_supported"
terrain_task_id: str | None = None
terrain_error_message: str | None = None
terrain_root_path: str | None = None
terrain_url_template: str | None = None
terrain_min_zoom: int | None = None
terrain_max_zoom: int | None = None
terrain_bounds: dict[str, Any] | None = None
terrain_metadata: dict[str, Any] | None = None
notes: str | None = None
create_date: datetime
create_user: str | None = None
update_date: datetime
update_user: str | None = None
class ElevationDatasetListResponse(BaseModel):
items: list[ElevationDatasetSummary]
total: int
class ElevationDatasetBatchImportResponse(BaseModel):
imported_count: int
analyzed_count: int
skipped_count: int
warning_count: int
warnings: list[str] = Field(default_factory=list)
items: list[ElevationDatasetSummary] = Field(default_factory=list)
class ElevationDatasetCreateRequest(BaseModel):
code: str = Field(min_length=2, max_length=64)
name: str = Field(min_length=2, max_length=255)
source: str | None = Field(default=None, max_length=128)
mount_code: str | None = Field(default=None, min_length=2, max_length=64)
file_name: str | None = Field(default=None, min_length=1, max_length=255)
resolution_m: float | None = Field(default=None, gt=0)
notes: str | None = Field(default=None, max_length=2000)
class ElevationDatasetUpdateRequest(BaseModel):
name: str | None = Field(default=None, min_length=2, max_length=255)
source: str | None = Field(default=None, max_length=128)
resolution_m: float | None = Field(default=None, gt=0)
status: ElevationDatasetStatus | None = None
notes: str | None = Field(default=None, max_length=2000)
class ElevationDatasetAnalyzeResponse(BaseModel):
dataset: ElevationDatasetSummary
task_id: str | None = None
queued: bool = True
detail: str | None = None
warnings: list[str] = Field(default_factory=list)
class ElevationDatasetTerrainBuildResponse(BaseModel):
dataset: ElevationDatasetSummary
task_id: str | None = None
queued: bool = True
detail: str | None = None
warnings: list[str] = Field(default_factory=list)
class ElevationDatasetPreviewPoint(BaseModel):
longitude: float
latitude: float
altitude_m: float
class ElevationDatasetPreviewCell(BaseModel):
min_longitude: float
max_longitude: float
min_latitude: float
max_latitude: float
altitude_m: float
class ElevationDatasetPreviewDiagnostics(BaseModel):
source_crs: str | None = None
source_bounds_min_x: float | None = None
source_bounds_max_x: float | None = None
source_bounds_min_y: float | None = None
source_bounds_max_y: float | None = None
wgs84_bounds_min_lon: float | None = None
wgs84_bounds_max_lon: float | None = None
wgs84_bounds_min_lat: float | None = None
wgs84_bounds_max_lat: float | None = None
raster_width: int | None = None
raster_height: int | None = None
target_samples: int | None = None
sampling_step: int | None = None
scanned_candidates: int | None = None
valid_preview_count: int | None = None
skip_read_error: int = 0
skip_masked: int = 0
skip_nodata: int = 0
skip_nonfinite: int = 0
skip_sample_transform_error: int = 0
sample_tx_first_error: str | None = None
skip_sample_out_of_range: int = 0
skip_cell_transform_error: int = 0
skip_cell_out_of_range: int = 0
class ElevationDatasetPreviewResponse(BaseModel):
dataset: ElevationDatasetSummary
preview_mode: Literal["point_cloud", "terrain_grid"]
total_points: int
sampled_points: int
points: list[ElevationDatasetPreviewPoint] = Field(default_factory=list)
cells: list[ElevationDatasetPreviewCell] = Field(default_factory=list)
diagnostics: ElevationDatasetPreviewDiagnostics | None = None
warnings: list[str] = Field(default_factory=list)
class ElevationDatasetDataImportResponse(BaseModel):
job: "ElevationDataImportJobSummary"
queued: bool = True
detail: str | None = None
warnings: list[str] = Field(default_factory=list)
class ElevationDatasetFileItem(BaseModel):
path: str
name: str
size: int
modified_at: datetime | None = None
mime_type: str | None = None
class ElevationDatasetFileListResponse(BaseModel):
dataset_id: str
dataset_code: str
dataset_dir: str
mount_code: str
items: list[ElevationDatasetFileItem] = Field(default_factory=list)
total: int = 0
class ElevationDatasetAnalysisTaskStatusResponse(BaseModel):
dataset_id: str
dataset_code: str
task_id: str | None = None
status: Literal["queued", "running", "success", "failed", "unknown", "not_found"] = "not_found"
detail: str | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
update_date: datetime | None = None
class ElevationDatasetTerrainTaskStatusResponse(BaseModel):
dataset_id: str
dataset_code: str
task_id: str | None = None
status: Literal["queued", "running", "success", "failed", "unknown", "not_found"] = "not_found"
detail: str | None = None
terrain_url_template: str | None = None
terrain_min_zoom: int | None = None
terrain_max_zoom: int | None = None
update_date: datetime | None = None
class ElevationTerrainLayerResponse(BaseModel):
tilejson: str = "2.1.0"
format: str = "heightmap-1.0"
version: str = "1.0.0"
scheme: Literal["tms"] = "tms"
projection: Literal["EPSG:4326"] = "EPSG:4326"
tiles: list[str]
minzoom: int
maxzoom: int
extensions: list[str] = Field(default_factory=list)
attribution: str | None = None
bounds: list[float] | None = None
available: list[list[dict[str, int]]] | None = None
class ElevationApplyJobSummary(BaseModel):
id: str
line_id: str
line_code: str | None = None
line_name: str | None = None
dataset_id: str
dataset_code: str | None = None
dataset_name: str | None = None
mode: ElevationApplyMode
status: ElevationApplyJobStatus
task_id: str | None = None
total_tower_count: int = 0
updated_tower_count: int = 0
skipped_tower_count: int = 0
missing_geo_count: int = 0
unmatched_count: int = 0
error_message: str | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
create_date: datetime
create_user: str | None = None
update_date: datetime
update_user: str | None = None
class ElevationApplyJobListResponse(BaseModel):
items: list[ElevationApplyJobSummary]
total: int
class ElevationApplyJobCreateRequest(BaseModel):
line_id: str = Field(min_length=1, max_length=64)
dataset_id: str = Field(min_length=1, max_length=64)
mode: ElevationApplyMode = "fill_null_only"
class ElevationApplyJobCreateResponse(BaseModel):
job: ElevationApplyJobSummary
queued: bool = True
class ElevationDataImportJobSummary(BaseModel):
id: str
dataset_id: str
dataset_code: str | None = None
dataset_name: str | None = None
status: ElevationDataImportJobStatus
task_id: str | None = None
progress_percent: int = 0
current_stage: str | None = None
detail_message: str | None = None
trigger_analysis: bool = True
analysis_task_queued: bool = False
analysis_task_id: str | None = None
uploaded_file_count: int = 0
extracted_file_count: int = 0
imported_file_count: int = 0
warning_count: int = 0
warnings: list[str] = Field(default_factory=list)
imported_files: list[str] = Field(default_factory=list)
started_at: datetime | None = None
finished_at: datetime | None = None
create_date: datetime
create_user: str | None = None
update_date: datetime
update_user: str | None = None
class ElevationDataImportJobListResponse(BaseModel):
items: list[ElevationDataImportJobSummary]
total: int
ElevationDatasetDataImportResponse.model_rebuild()