fix tilesets for vectors map
This commit is contained in:
parent
2715c56b4f
commit
f009a57b8e
7 changed files with 209 additions and 18 deletions
|
|
@ -38,6 +38,7 @@ export default function App() {
|
||||||
const [vectorSource, setVectorSource] = useState<"mbtiles" | "tileset">("mbtiles");
|
const [vectorSource, setVectorSource] = useState<"mbtiles" | "tileset">("mbtiles");
|
||||||
const [tilesets, setTilesets] = useState<string[]>([]);
|
const [tilesets, setTilesets] = useState<string[]>([]);
|
||||||
const [activeTileset, setActiveTileset] = useState<string>("");
|
const [activeTileset, setActiveTileset] = useState<string>("");
|
||||||
|
const [tilesetKinds, setTilesetKinds] = useState<Record<string, string>>({});
|
||||||
const [selectedFileName, setSelectedFileName] = useState<string>("");
|
const [selectedFileName, setSelectedFileName] = useState<string>("");
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const [sliceParams, setSliceParams] = useState<SliceParams>(initialSliceParams);
|
const [sliceParams, setSliceParams] = useState<SliceParams>(initialSliceParams);
|
||||||
|
|
@ -96,6 +97,7 @@ export default function App() {
|
||||||
if (!res.ok) throw new Error(data?.detail || `status ${res.status}`);
|
if (!res.ok) throw new Error(data?.detail || `status ${res.status}`);
|
||||||
setTilesets(data.tilesets || []);
|
setTilesets(data.tilesets || []);
|
||||||
setActiveTileset(data.active || "");
|
setActiveTileset(data.active || "");
|
||||||
|
setTilesetKinds(data.kinds || {});
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -164,8 +166,15 @@ export default function App() {
|
||||||
if (!res.ok) throw new Error(data?.detail || `status ${res.status}`);
|
if (!res.ok) throw new Error(data?.detail || `status ${res.status}`);
|
||||||
setActiveTileset(data.active || name);
|
setActiveTileset(data.active || name);
|
||||||
sendFrontLog(`tileset activated: ${data.active || name}`, "info");
|
sendFrontLog(`tileset activated: ${data.active || name}`, "info");
|
||||||
|
const kind = (data.kind as string | undefined) || tilesetKinds[name] || "";
|
||||||
|
if (kind.startsWith("vector")) {
|
||||||
|
setMapMode("vector");
|
||||||
|
setVectorSource(kind === "vector-dir" ? "tileset" : "mbtiles");
|
||||||
|
} else if (kind.startsWith("raster")) {
|
||||||
|
setMapMode("raster");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[config, sendFrontLog],
|
[config, sendFrontLog, tilesetKinds],
|
||||||
);
|
);
|
||||||
|
|
||||||
const createTileset = useCallback(
|
const createTileset = useCallback(
|
||||||
|
|
@ -273,9 +282,13 @@ export default function App() {
|
||||||
setMapMode("raster");
|
setMapMode("raster");
|
||||||
}
|
}
|
||||||
sendFrontLog(`map ingested: kind=${data.kind} driver=${data.driver}`, "info");
|
sendFrontLog(`map ingested: kind=${data.kind} driver=${data.driver}`, "info");
|
||||||
|
if (data.tileset) {
|
||||||
|
refreshTilesets().catch(() => {});
|
||||||
|
activateTileset(String(data.tileset)).catch(() => {});
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
[sendFrontLog, sliceParams.max_zoom, sliceParams.min_zoom, sliceParams.tileset],
|
[activateTileset, refreshTilesets, sendFrontLog, sliceParams.max_zoom, sliceParams.min_zoom, sliceParams.tileset],
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadFileBlob = useCallback(
|
const uploadFileBlob = useCallback(
|
||||||
|
|
@ -291,6 +304,10 @@ export default function App() {
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setSliceParams((p) => ({ ...p, source: res.path }));
|
setSliceParams((p) => ({ ...p, source: res.path }));
|
||||||
setSelectedFileName(res.filename);
|
setSelectedFileName(res.filename);
|
||||||
|
const lower = String(res.filename || "").toLowerCase();
|
||||||
|
if (lower.endsWith(".mbtiles")) setSliceParams((p) => ({ ...p, source_format: "mbtiles" }));
|
||||||
|
if (lower.endsWith(".tif") || lower.endsWith(".tiff") || lower.endsWith(".vrt"))
|
||||||
|
setSliceParams((p) => ({ ...p, source_format: "geotiff" }));
|
||||||
setSliceParams((p) => {
|
setSliceParams((p) => {
|
||||||
if (p.tileset) return p;
|
if (p.tileset) return p;
|
||||||
const proposed = String(res.filename || "")
|
const proposed = String(res.filename || "")
|
||||||
|
|
@ -326,7 +343,11 @@ export default function App() {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (typeof selected === "string") {
|
if (typeof selected === "string") {
|
||||||
|
const lower = selected.toLowerCase();
|
||||||
setSliceParams((p) => ({ ...p, source: selected }));
|
setSliceParams((p) => ({ ...p, source: selected }));
|
||||||
|
if (lower.endsWith(".mbtiles")) setSliceParams((p) => ({ ...p, source_format: "mbtiles" }));
|
||||||
|
if (lower.endsWith(".tif") || lower.endsWith(".tiff") || lower.endsWith(".vrt"))
|
||||||
|
setSliceParams((p) => ({ ...p, source_format: "geotiff" }));
|
||||||
setSelectedFileName(selected.split("/").pop() || selected);
|
setSelectedFileName(selected.split("/").pop() || selected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
|
@ -7,6 +8,8 @@ from ..dependencies import get_raster_mbtiles_service, get_tile_service, get_vec
|
||||||
from ...services.raster_mbtiles import RasterMBTilesError, RasterMBTilesService
|
from ...services.raster_mbtiles import RasterMBTilesError, RasterMBTilesService
|
||||||
from ...services.tiles import TileCutError, TileService
|
from ...services.tiles import TileCutError, TileService
|
||||||
from ...services.vector_mbtiles import VectorMBTilesError, VectorMBTilesService
|
from ...services.vector_mbtiles import VectorMBTilesError, VectorMBTilesService
|
||||||
|
from ...services.tilesets import TilesetError, TilesetService
|
||||||
|
from ... import config
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["maps"])
|
router = APIRouter(tags=["maps"])
|
||||||
|
|
@ -30,21 +33,61 @@ async def ingest_map(
|
||||||
if not p.exists():
|
if not p.exists():
|
||||||
raise HTTPException(status_code=400, detail=f"path not found: {p}")
|
raise HTTPException(status_code=400, detail=f"path not found: {p}")
|
||||||
|
|
||||||
|
tileset_svc = TilesetService(tilesets_dir=config.TILESSETS_DIR, default_tileset=config.DEFAULT_TILESET)
|
||||||
|
tileset_svc.ensure()
|
||||||
|
suggested_tileset = (payload.tileset or p.stem or "").strip()
|
||||||
|
tileset_name: str | None = None
|
||||||
|
if suggested_tileset:
|
||||||
|
try:
|
||||||
|
tileset_name = tileset_svc.create(suggested_tileset)
|
||||||
|
tileset_svc.set_active(tileset_name)
|
||||||
|
tile_service.set_tiles_dir(tileset_svc.resolve(tileset_name))
|
||||||
|
except TilesetError as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
if p.suffix.lower() == ".mbtiles":
|
if p.suffix.lower() == ".mbtiles":
|
||||||
try:
|
try:
|
||||||
info = vector_mbtiles.activate(p)
|
info = vector_mbtiles.activate(p)
|
||||||
return {"status": "ok", "kind": "vector", "driver": "mbtiles", "path": str(info.path), "format": info.metadata.get("format")}
|
if tileset_name:
|
||||||
|
manifest = {"kind": "vector-mbtiles", "source": str(info.path), "format": info.metadata.get("format")}
|
||||||
|
(tileset_svc.resolve(tileset_name) / "gnss_tileset.json").write_text(
|
||||||
|
json.dumps(manifest, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"kind": "vector",
|
||||||
|
"driver": "mbtiles",
|
||||||
|
"path": str(info.path),
|
||||||
|
"format": info.metadata.get("format"),
|
||||||
|
"tileset": tileset_name,
|
||||||
|
}
|
||||||
except VectorMBTilesError:
|
except VectorMBTilesError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
info = raster_mbtiles.activate(p)
|
info = raster_mbtiles.activate(p)
|
||||||
except RasterMBTilesError as exc:
|
except RasterMBTilesError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
return {"status": "ok", "kind": "raster", "driver": "mbtiles", "path": str(info.path), "format": info.metadata.get("format")}
|
if tileset_name:
|
||||||
|
manifest = {"kind": "raster-mbtiles", "source": str(info.path), "format": info.metadata.get("format")}
|
||||||
|
(tileset_svc.resolve(tileset_name) / "gnss_tileset.json").write_text(
|
||||||
|
json.dumps(manifest, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"kind": "raster",
|
||||||
|
"driver": "mbtiles",
|
||||||
|
"path": str(info.path),
|
||||||
|
"format": info.metadata.get("format"),
|
||||||
|
"tileset": tileset_name,
|
||||||
|
}
|
||||||
|
|
||||||
if p.suffix.lower() in {".tif", ".tiff", ".vrt"}:
|
if p.suffix.lower() in {".tif", ".tiff", ".vrt"}:
|
||||||
if payload.max_zoom < payload.min_zoom:
|
if payload.max_zoom < payload.min_zoom:
|
||||||
raise HTTPException(status_code=400, detail="max_zoom must be >= min_zoom")
|
raise HTTPException(status_code=400, detail="max_zoom must be >= min_zoom")
|
||||||
|
if tileset_name:
|
||||||
|
tile_service.set_tiles_dir(tileset_svc.resolve(tileset_name))
|
||||||
try:
|
try:
|
||||||
tile_service.slice_to_tiles(
|
tile_service.slice_to_tiles(
|
||||||
source=p,
|
source=p,
|
||||||
|
|
@ -55,7 +98,13 @@ async def ingest_map(
|
||||||
)
|
)
|
||||||
except TileCutError as exc:
|
except TileCutError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
return {"status": "ok", "kind": "raster", "driver": "gdal2tiles", "path": str(p), "tiles_dir": str(tile_service.tiles_dir)}
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"kind": "raster",
|
||||||
|
"driver": "gdal2tiles",
|
||||||
|
"path": str(p),
|
||||||
|
"tiles_dir": str(tile_service.tiles_dir),
|
||||||
|
"tileset": tileset_name,
|
||||||
|
}
|
||||||
|
|
||||||
raise HTTPException(status_code=400, detail="unsupported input; expected .mbtiles or GeoTIFF/VRT")
|
raise HTTPException(status_code=400, detail="unsupported input; expected .mbtiles or GeoTIFF/VRT")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ...api.dependencies import get_raster_mbtiles_service, get_tile_service
|
from ...api.dependencies import get_raster_mbtiles_service, get_tile_service, get_vector_mbtiles_service
|
||||||
from ...models.tiles import TileSliceRequest
|
from ...models.tiles import TileSliceRequest
|
||||||
|
from ...services.raster_mbtiles import RasterMBTilesError, RasterMBTilesService
|
||||||
from ...services.tiles import TileCutError, TileService
|
from ...services.tiles import TileCutError, TileService
|
||||||
|
from ...services.vector_mbtiles import VectorMBTilesError, VectorMBTilesService
|
||||||
from ... import config
|
from ... import config
|
||||||
from ...services.tilesets import TilesetService, TilesetError
|
from ...services.tilesets import TilesetService, TilesetError
|
||||||
from ...services.raster_mbtiles import RasterMBTilesService
|
|
||||||
|
|
||||||
router = APIRouter(tags=["tiles"])
|
router = APIRouter(tags=["tiles"])
|
||||||
|
|
||||||
|
|
@ -53,7 +54,12 @@ async def serve_tile_tms(
|
||||||
|
|
||||||
|
|
||||||
@router.post("/tiles/slice")
|
@router.post("/tiles/slice")
|
||||||
async def slice_tiles(payload: TileSliceRequest, tile_service: TileService = Depends(get_tile_service)) -> dict:
|
async def slice_tiles(
|
||||||
|
payload: TileSliceRequest,
|
||||||
|
tile_service: TileService = Depends(get_tile_service),
|
||||||
|
raster_mbtiles: RasterMBTilesService = Depends(get_raster_mbtiles_service),
|
||||||
|
vector_mbtiles: VectorMBTilesService = Depends(get_vector_mbtiles_service),
|
||||||
|
) -> dict:
|
||||||
source = payload.source_path()
|
source = payload.source_path()
|
||||||
tileset_svc = TilesetService(tilesets_dir=config.TILESSETS_DIR, default_tileset=config.DEFAULT_TILESET)
|
tileset_svc = TilesetService(tilesets_dir=config.TILESSETS_DIR, default_tileset=config.DEFAULT_TILESET)
|
||||||
tileset_svc.ensure()
|
tileset_svc.ensure()
|
||||||
|
|
@ -66,6 +72,28 @@ async def slice_tiles(payload: TileSliceRequest, tile_service: TileService = Dep
|
||||||
destination_default = tileset_svc.resolve(tileset_name)
|
destination_default = tileset_svc.resolve(tileset_name)
|
||||||
destination = payload.destination_path(destination_default)
|
destination = payload.destination_path(destination_default)
|
||||||
try:
|
try:
|
||||||
|
if payload.source_format == "mbtiles":
|
||||||
|
try:
|
||||||
|
info = vector_mbtiles.activate(source)
|
||||||
|
kind = "vector"
|
||||||
|
manifest = {"kind": "vector-mbtiles", "source": str(info.path), "format": info.metadata.get("format")}
|
||||||
|
except VectorMBTilesError:
|
||||||
|
info = raster_mbtiles.activate(source)
|
||||||
|
kind = "raster"
|
||||||
|
manifest = {"kind": "raster-mbtiles", "source": str(info.path), "format": info.metadata.get("format")}
|
||||||
|
active_tileset = tileset_name or tileset_svc.get_active()
|
||||||
|
(tileset_svc.resolve(active_tileset) / "gnss_tileset.json").write_text(
|
||||||
|
json.dumps(manifest, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"kind": kind,
|
||||||
|
"driver": "mbtiles",
|
||||||
|
"path": str(source),
|
||||||
|
"scheme": payload.scheme,
|
||||||
|
"tileset": active_tileset,
|
||||||
|
}
|
||||||
if payload.source_format in {"tms", "xyz"}:
|
if payload.source_format in {"tms", "xyz"}:
|
||||||
src = tile_service.unpack_if_archive(source)
|
src = tile_service.unpack_if_archive(source)
|
||||||
tile_service.copy_tile_tree(src, destination)
|
tile_service.copy_tile_tree(src, destination)
|
||||||
|
|
@ -83,6 +111,8 @@ async def slice_tiles(payload: TileSliceRequest, tile_service: TileService = Dep
|
||||||
)
|
)
|
||||||
except TileCutError as exc:
|
except TileCutError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
except (RasterMBTilesError, VectorMBTilesError) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
active_tileset = tileset_name or tileset_svc.get_active()
|
active_tileset = tileset_name or tileset_svc.get_active()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ from pathlib import Path
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ..dependencies import get_tile_service
|
from ..dependencies import get_raster_mbtiles_service, get_tile_service, get_vector_mbtiles_service
|
||||||
from ... import config
|
from ... import config
|
||||||
|
from ...services.raster_mbtiles import RasterMBTilesError, RasterMBTilesService
|
||||||
from ...services.tiles import TileService
|
from ...services.tiles import TileService
|
||||||
from ...services.tilesets import TilesetError, TilesetService
|
from ...services.tilesets import TilesetError, TilesetService
|
||||||
|
from ...services.vector_mbtiles import VectorMBTilesError, VectorMBTilesService
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["tilesets"])
|
router = APIRouter(tags=["tilesets"])
|
||||||
|
|
@ -31,6 +33,15 @@ def _is_gzipped(data: bytes) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def _detect_kind(path: Path) -> str:
|
def _detect_kind(path: Path) -> str:
|
||||||
|
manifest = path / "gnss_tileset.json"
|
||||||
|
if manifest.is_file():
|
||||||
|
try:
|
||||||
|
parsed = json.loads(manifest.read_text(encoding="utf-8"))
|
||||||
|
kind = str(parsed.get("kind") or "").lower()
|
||||||
|
if kind in {"vector-mbtiles", "raster-mbtiles"}:
|
||||||
|
return kind
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
meta = path / "metadata.json"
|
meta = path / "metadata.json"
|
||||||
if meta.is_file():
|
if meta.is_file():
|
||||||
try:
|
try:
|
||||||
|
|
@ -218,14 +229,40 @@ async def create_tileset(payload: CreateTileset) -> dict:
|
||||||
|
|
||||||
|
|
||||||
@router.post("/tilesets/activate")
|
@router.post("/tilesets/activate")
|
||||||
async def activate_tileset(payload: ActivateTileset, tile_service: TileService = Depends(get_tile_service)) -> dict:
|
async def activate_tileset(
|
||||||
|
payload: ActivateTileset,
|
||||||
|
tile_service: TileService = Depends(get_tile_service),
|
||||||
|
raster_mbtiles: RasterMBTilesService = Depends(get_raster_mbtiles_service),
|
||||||
|
vector_mbtiles: VectorMBTilesService = Depends(get_vector_mbtiles_service),
|
||||||
|
) -> dict:
|
||||||
svc = _tileset_service()
|
svc = _tileset_service()
|
||||||
try:
|
try:
|
||||||
active = svc.set_active(payload.name)
|
active = svc.set_active(payload.name)
|
||||||
except TilesetError as exc:
|
except TilesetError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
tile_service.set_tiles_dir(svc.resolve(active))
|
tile_service.set_tiles_dir(svc.resolve(active))
|
||||||
return {"status": "ok", "active": active}
|
tileset_dir = svc.resolve(active)
|
||||||
|
kind = _detect_kind(tileset_dir)
|
||||||
|
activated: dict | None = None
|
||||||
|
manifest_path = tileset_dir / "gnss_tileset.json"
|
||||||
|
if manifest_path.is_file() and kind in {"vector-mbtiles", "raster-mbtiles"}:
|
||||||
|
try:
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
src = manifest.get("source") or manifest.get("path") or manifest.get("mbtiles")
|
||||||
|
if not src:
|
||||||
|
raise ValueError("gnss_tileset.json missing 'source'")
|
||||||
|
mbtiles_path = Path(str(src)).expanduser().resolve()
|
||||||
|
if kind == "vector-mbtiles":
|
||||||
|
info = vector_mbtiles.activate(mbtiles_path)
|
||||||
|
activated = {"kind": "vector", "driver": "mbtiles", "path": str(info.path)}
|
||||||
|
else:
|
||||||
|
info = raster_mbtiles.activate(mbtiles_path)
|
||||||
|
activated = {"kind": "raster", "driver": "mbtiles", "path": str(info.path)}
|
||||||
|
except (VectorMBTilesError, RasterMBTilesError, ValueError) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=f"failed to activate mbtiles tileset: {exc}")
|
||||||
|
return {"status": "ok", "active": active, "kind": kind, "activated": activated}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tilesets/active/vector/metadata")
|
@router.get("/tilesets/active/vector/metadata")
|
||||||
|
|
@ -255,4 +292,3 @@ async def active_vector_tile(z: int, x: int, y: int) -> Response:
|
||||||
if _is_gzipped(data):
|
if _is_gzipped(data):
|
||||||
headers["Content-Encoding"] = "gzip"
|
headers["Content-Encoding"] = "gzip"
|
||||||
return Response(content=data, media_type="application/x-protobuf", headers=headers)
|
return Response(content=data, media_type="application/x-protobuf", headers=headers)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ tiles_dir = tileset_service.resolve(None)
|
||||||
placeholder_tile = Path(config.TILE_PLACEHOLDER)
|
placeholder_tile = Path(config.TILE_PLACEHOLDER)
|
||||||
tile_service = TileService(tiles_dir=tiles_dir, placeholder=placeholder_tile, upload_dir=Path(config.UPLOAD_DIR))
|
tile_service = TileService(tiles_dir=tiles_dir, placeholder=placeholder_tile, upload_dir=Path(config.UPLOAD_DIR))
|
||||||
log_service = LogService(base_dir=Path("logs"))
|
log_service = LogService(base_dir=Path("logs"))
|
||||||
raster_mbtiles_service = RasterMBTilesService()
|
raster_mbtiles_service = RasterMBTilesService(state_file=Path(config.DATA_DIR) / "raster_mbtiles.active")
|
||||||
vector_mbtiles_service = VectorMBTilesService()
|
vector_mbtiles_service = VectorMBTilesService(state_file=Path(config.DATA_DIR) / "vector_mbtiles.active")
|
||||||
gnss_service = create_gnss_service()
|
gnss_service = create_gnss_service()
|
||||||
state.set_services(
|
state.set_services(
|
||||||
gnss=gnss_service,
|
gnss=gnss_service,
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,36 @@ def _format_to_media_type(fmt: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
class RasterMBTilesService:
|
class RasterMBTilesService:
|
||||||
def __init__(self):
|
def __init__(self, state_file: Optional[Path] = None):
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._active: Optional[RasterMBTilesInfo] = None
|
self._active: Optional[RasterMBTilesInfo] = None
|
||||||
|
self._state_file = Path(state_file).expanduser().resolve() if state_file else None
|
||||||
|
self._restore()
|
||||||
|
|
||||||
|
def _restore(self) -> None:
|
||||||
|
if not self._state_file:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
raw = self._state_file.read_text(encoding="utf-8").strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
if not raw:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.activate(Path(raw))
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
def _persist(self, info: RasterMBTilesInfo) -> None:
|
||||||
|
if not self._state_file:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._state_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._state_file.write_text(str(info.path), encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
def activate(self, path: Path) -> RasterMBTilesInfo:
|
def activate(self, path: Path) -> RasterMBTilesInfo:
|
||||||
p = Path(path).expanduser().resolve()
|
p = Path(path).expanduser().resolve()
|
||||||
|
|
@ -66,6 +93,7 @@ class RasterMBTilesService:
|
||||||
info = RasterMBTilesInfo(path=p, metadata=metadata)
|
info = RasterMBTilesInfo(path=p, metadata=metadata)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._active = info
|
self._active = info
|
||||||
|
self._persist(info)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_active(self) -> RasterMBTilesInfo:
|
def get_active(self) -> RasterMBTilesInfo:
|
||||||
|
|
@ -118,4 +146,3 @@ class RasterMBTilesService:
|
||||||
|
|
||||||
fmt = str(info.metadata.get("format") or "").lower()
|
fmt = str(info.metadata.get("format") or "").lower()
|
||||||
return data, _format_to_media_type(fmt)
|
return data, _format_to_media_type(fmt)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,36 @@ def _has_table(conn: sqlite3.Connection, table: str) -> bool:
|
||||||
|
|
||||||
|
|
||||||
class VectorMBTilesService:
|
class VectorMBTilesService:
|
||||||
def __init__(self):
|
def __init__(self, state_file: Optional[Path] = None):
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._active: Optional[VectorMBTilesInfo] = None
|
self._active: Optional[VectorMBTilesInfo] = None
|
||||||
|
self._state_file = Path(state_file).expanduser().resolve() if state_file else None
|
||||||
|
self._restore()
|
||||||
|
|
||||||
|
def _restore(self) -> None:
|
||||||
|
if not self._state_file:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
raw = self._state_file.read_text(encoding="utf-8").strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
if not raw:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.activate(Path(raw))
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
def _persist(self, info: VectorMBTilesInfo) -> None:
|
||||||
|
if not self._state_file:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._state_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._state_file.write_text(str(info.path), encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
def activate(self, path: Path) -> VectorMBTilesInfo:
|
def activate(self, path: Path) -> VectorMBTilesInfo:
|
||||||
p = Path(path).expanduser().resolve()
|
p = Path(path).expanduser().resolve()
|
||||||
|
|
@ -55,6 +82,7 @@ class VectorMBTilesService:
|
||||||
info = VectorMBTilesInfo(path=p, metadata=metadata)
|
info = VectorMBTilesInfo(path=p, metadata=metadata)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._active = info
|
self._active = info
|
||||||
|
self._persist(info)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_active(self) -> VectorMBTilesInfo:
|
def get_active(self) -> VectorMBTilesInfo:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue