feat(map): adopt DC Default scene
This commit is contained in:
@@ -13,6 +13,7 @@ from fastapi import APIRouter, HTTPException, Path, Query, Request
|
||||
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
||||
|
||||
MAP_GATEWAY_URL_ENV: Final = "MISSIONCORE_MAP_GATEWAY_INTERNAL_URL"
|
||||
MAP_SANDBOX_HIDE_CREDITS_ENV: Final = "MISSIONCORE_MAP_SANDBOX_HIDE_CREDITS"
|
||||
MAP_SCHEMA_VERSION: Final = "missioncore.map-gateway/v1"
|
||||
MAP_RUNTIME_SCHEMA_VERSION: Final = "missioncore.map-runtime/v1"
|
||||
MAP_PAGE_VERSION: Final = "0.1.0"
|
||||
@@ -149,6 +150,11 @@ class MapGatewayProxy:
|
||||
"terrain": 1,
|
||||
"buildings": 96188,
|
||||
},
|
||||
"sandbox": {
|
||||
"hide_credit_overlay": _environment_flag(
|
||||
MAP_SANDBOX_HIDE_CREDITS_ENV
|
||||
)
|
||||
},
|
||||
},
|
||||
headers={"Cache-Control": "no-store"},
|
||||
)
|
||||
@@ -309,6 +315,15 @@ def _validate_internal_url(raw_url: str) -> str:
|
||||
return raw_url.rstrip("/")
|
||||
|
||||
|
||||
def _environment_flag(name: str) -> bool:
|
||||
return os.environ.get(name, "").strip().lower() in {
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
}
|
||||
|
||||
|
||||
def _validate_cache_target(target_url: str) -> None:
|
||||
if len(target_url) > MAX_CACHE_TARGET_LENGTH:
|
||||
raise HTTPException(status_code=422, detail="Map resource URL is too long.")
|
||||
|
||||
+128
-15
@@ -10,7 +10,7 @@ from typing import Literal
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
MAP_VIEW_SCHEMA_VERSION: Literal["missioncore.map-view/v1"] = "missioncore.map-view/v1"
|
||||
MAP_VIEW_SCHEMA_VERSION: Literal["missioncore.map-view/v2"] = "missioncore.map-view/v2"
|
||||
MapInspectorSection = Literal[
|
||||
"base-terrain",
|
||||
"atmosphere-light",
|
||||
@@ -37,15 +37,49 @@ class MapCamera(StrictMapModel):
|
||||
|
||||
|
||||
class MapVisualSettings(StrictMapModel):
|
||||
atmosphere_enabled: bool = True
|
||||
lighting_enabled: bool = True
|
||||
atmosphere_enabled: bool = False
|
||||
atmosphere_hue: float = Field(default=0.0, ge=-100.0, le=100.0)
|
||||
atmosphere_saturation: float = Field(default=0.0, ge=-100.0, le=100.0)
|
||||
atmosphere_brightness: float = Field(default=0.0, ge=-100.0, le=100.0)
|
||||
fog_enabled: bool = True
|
||||
fog_density: float = Field(default=2.0, ge=0.0, le=100.0)
|
||||
sun_enabled: bool = True
|
||||
sun_hour: float = Field(default=12.0, ge=0.0, le=24.0)
|
||||
sun_intensity: float = Field(default=200.0, ge=0.0, le=200.0)
|
||||
shadows_enabled: bool = True
|
||||
monochrome_enabled: bool = False
|
||||
terrain_exaggeration: float = Field(default=1.0, ge=0.1, le=20.0)
|
||||
monochrome_color: str = Field(default="#15151b", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
imagery_brightness: float = Field(default=118.0, ge=0.0, le=200.0)
|
||||
imagery_contrast: float = Field(default=102.0, ge=0.0, le=200.0)
|
||||
imagery_saturation: float = Field(default=0.0, ge=0.0, le=200.0)
|
||||
imagery_gamma: float = Field(default=57.0, ge=0.0, le=300.0)
|
||||
imagery_hue: float = Field(default=13.0, ge=-180.0, le=180.0)
|
||||
imagery_alpha: float = Field(default=27.0, ge=0.0, le=100.0)
|
||||
globe_color: str = Field(default="#15151b", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
background_color: str = Field(default="#08090d", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
terrain_exaggeration: float = Field(default=1.0, ge=0.25, le=3.0)
|
||||
buildings_color: str = Field(default="#a27aff", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
buildings_opacity: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
buildings_maximum_screen_space_error: float = Field(
|
||||
default=16.0,
|
||||
ge=1.0,
|
||||
le=64.0,
|
||||
default=4.0,
|
||||
ge=4.0,
|
||||
le=32.0,
|
||||
)
|
||||
grid_lod_enabled: bool = True
|
||||
grid_height_meters: float = Field(default=500.0, ge=0.0, le=1000.0)
|
||||
grid_lod_1_max_height_km: float = Field(default=10.0, ge=1.0, le=50.0)
|
||||
grid_lod_1_step_km: float = Field(default=1.0, ge=1.0, le=10.0)
|
||||
grid_lod_2_max_height_km: float = Field(default=50.0, ge=10.0, le=200.0)
|
||||
grid_lod_2_step_km: float = Field(default=5.0, ge=1.0, le=25.0)
|
||||
grid_lod_3_step_km: float = Field(default=25.0, ge=5.0, le=100.0)
|
||||
grid_radius_km: float = Field(default=40.0, ge=5.0, le=150.0)
|
||||
grid_line_width: float = Field(default=4.0, ge=1.0, le=8.0)
|
||||
grid_color: str = Field(default="#f5f5f5", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
grid_opacity: float = Field(default=12.0, ge=0.0, le=100.0)
|
||||
grid_dots_enabled: bool = True
|
||||
grid_dots_size: float = Field(default=7.0, ge=2.0, le=28.0)
|
||||
grid_dots_color: str = Field(default="#ffffff", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
grid_dots_opacity: float = Field(default=58.0, ge=0.0, le=100.0)
|
||||
camera_animation_enabled: bool = True
|
||||
|
||||
|
||||
@@ -53,7 +87,7 @@ class MapLayerVisibility(StrictMapModel):
|
||||
imagery: bool = True
|
||||
terrain: bool = True
|
||||
buildings: bool = True
|
||||
grid: bool = False
|
||||
grid: bool = True
|
||||
targets: bool = True
|
||||
|
||||
|
||||
@@ -63,9 +97,18 @@ class MapCacheIntent(StrictMapModel):
|
||||
|
||||
|
||||
class MapViewContent(StrictMapModel):
|
||||
camera: MapCamera | None = None
|
||||
camera: MapCamera | None = Field(
|
||||
default_factory=lambda: MapCamera(
|
||||
longitude=37.618423,
|
||||
latitude=55.751244,
|
||||
height=40_000.0,
|
||||
heading=0.0,
|
||||
pitch=-51.56620156177409,
|
||||
roll=0.0,
|
||||
)
|
||||
)
|
||||
visual_settings: MapVisualSettings = Field(default_factory=MapVisualSettings)
|
||||
map_height: int = Field(default=720, ge=420, le=2160)
|
||||
map_height: int = Field(default=694, ge=420, le=2160)
|
||||
inspector_open_sections: list[MapInspectorSection] = Field(
|
||||
default_factory=list,
|
||||
max_length=1,
|
||||
@@ -91,7 +134,43 @@ class MapViewPut(StrictMapModel):
|
||||
|
||||
|
||||
class MapViewDocument(MapViewPut):
|
||||
schema_version: Literal["missioncore.map-view/v1"] = MAP_VIEW_SCHEMA_VERSION
|
||||
schema_version: Literal["missioncore.map-view/v2"] = MAP_VIEW_SCHEMA_VERSION
|
||||
|
||||
|
||||
class LegacyMapVisualSettings(StrictMapModel):
|
||||
atmosphere_enabled: bool = True
|
||||
lighting_enabled: bool = True
|
||||
monochrome_enabled: bool = False
|
||||
terrain_exaggeration: float = Field(default=1.0, ge=0.1, le=20.0)
|
||||
buildings_maximum_screen_space_error: float = Field(
|
||||
default=16.0,
|
||||
ge=1.0,
|
||||
le=64.0,
|
||||
)
|
||||
camera_animation_enabled: bool = True
|
||||
|
||||
|
||||
class LegacyMapViewContent(StrictMapModel):
|
||||
camera: MapCamera | None = None
|
||||
visual_settings: LegacyMapVisualSettings = Field(default_factory=LegacyMapVisualSettings)
|
||||
map_height: int = Field(default=720, ge=420, le=2160)
|
||||
inspector_open_sections: list[MapInspectorSection] = Field(
|
||||
default_factory=list,
|
||||
max_length=1,
|
||||
)
|
||||
cache_intent: MapCacheIntent = Field(default_factory=MapCacheIntent)
|
||||
selected_subject_id: str | None = Field(
|
||||
default=None,
|
||||
max_length=160,
|
||||
pattern=r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$",
|
||||
)
|
||||
layer_visibility: MapLayerVisibility = Field(default_factory=MapLayerVisibility)
|
||||
|
||||
|
||||
class LegacyMapViewDocument(StrictMapModel):
|
||||
schema_version: Literal["missioncore.map-view/v1"]
|
||||
revision: int = Field(ge=0)
|
||||
view: LegacyMapViewContent
|
||||
|
||||
|
||||
def default_map_view() -> MapViewDocument:
|
||||
@@ -101,6 +180,36 @@ def default_map_view() -> MapViewDocument:
|
||||
)
|
||||
|
||||
|
||||
def upgrade_legacy_map_view(document: LegacyMapViewDocument) -> MapViewDocument:
|
||||
legacy_settings = document.view.visual_settings
|
||||
settings = MapVisualSettings(
|
||||
atmosphere_enabled=legacy_settings.atmosphere_enabled,
|
||||
sun_enabled=legacy_settings.lighting_enabled,
|
||||
monochrome_enabled=legacy_settings.monochrome_enabled,
|
||||
terrain_exaggeration=min(
|
||||
3.0,
|
||||
max(0.25, legacy_settings.terrain_exaggeration),
|
||||
),
|
||||
buildings_maximum_screen_space_error=min(
|
||||
32.0,
|
||||
max(4.0, legacy_settings.buildings_maximum_screen_space_error),
|
||||
),
|
||||
camera_animation_enabled=legacy_settings.camera_animation_enabled,
|
||||
)
|
||||
return MapViewDocument(
|
||||
revision=document.revision,
|
||||
view=MapViewContent(
|
||||
camera=document.view.camera,
|
||||
visual_settings=settings,
|
||||
map_height=document.view.map_height,
|
||||
inspector_open_sections=document.view.inspector_open_sections,
|
||||
cache_intent=document.view.cache_intent,
|
||||
selected_subject_id=document.view.selected_subject_id,
|
||||
layer_visibility=document.view.layer_visibility,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MapViewStore:
|
||||
def __init__(self, root: Path) -> None:
|
||||
self.root = root.expanduser().resolve()
|
||||
@@ -138,10 +247,14 @@ class MapViewStore:
|
||||
if not self.document_path.is_file():
|
||||
return default_map_view()
|
||||
try:
|
||||
return MapViewDocument.model_validate_json(
|
||||
self.document_path.read_text(encoding="utf-8")
|
||||
)
|
||||
except (OSError, ValueError) as exc:
|
||||
payload = json.loads(self.document_path.read_text(encoding="utf-8"))
|
||||
if (
|
||||
isinstance(payload, dict)
|
||||
and payload.get("schema_version") == "missioncore.map-view/v1"
|
||||
):
|
||||
return upgrade_legacy_map_view(LegacyMapViewDocument.model_validate(payload))
|
||||
return MapViewDocument.model_validate(payload)
|
||||
except (OSError, ValueError, TypeError) as exc:
|
||||
raise RuntimeError("map view is corrupt") from exc
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user