Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 83 additions & 3 deletions src/utils/cesiumImagery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,63 @@ function cleanupWarpedFile(warpedFilePath: string): void {
});
}

/**
* Returns the path to the extents metadata file inside a tile cache directory.
*/
function getExtentsMetadataPath(tileCacheDir: string): string {
return path.join(tileCacheDir, ".extents.json");
}

/**
* Checks if valid cached tiles and extents exist for a given tile cache directory.
* Returns the cached extents if found, or null if no valid cache exists.
*/
function getCachedExtents(tileCacheDir: string): CesiumRectDeg | null {
const metadataPath = getExtentsMetadataPath(tileCacheDir);
try {
if (!fs.existsSync(metadataPath)) {
return null;
}

const data = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
if (
data.west === undefined ||
data.south === undefined ||
data.east === undefined ||
data.north === undefined
) {
return null;
}

// Verify at least one zoom-level directory exists alongside the metadata
const entries = fs.readdirSync(tileCacheDir);
const hasZoomDirs = entries.some(entry => /^\d+$/.test(entry));
if (!hasZoomDirs) {
return null;
}

logger.info("Found cached tiles with extents:", data);
return data as CesiumRectDeg;
} catch (err) {
logger.debug("No valid tile cache found:", err);
return null;
}
}

/**
* Persists extents metadata alongside cached tiles for future reuse.
*/
function saveCachedExtents(tileCacheDir: string, extents: CesiumRectDeg): void {
const metadataPath = getExtentsMetadataPath(tileCacheDir);
try {
fs.mkdirSync(tileCacheDir, { recursive: true });
fs.writeFileSync(metadataPath, JSON.stringify(extents, null, 2), "utf-8");
logger.debug("Saved tile cache metadata:", metadataPath);
} catch (err) {
logger.error("Failed to save tile cache metadata:", err);
}
}

/**
* Converts a local image to Cesium tiles using Docker-based ctb-tile.
* Images are first warped to EPSG:4326 via gdalwarp to handle projected
Expand All @@ -155,9 +212,29 @@ export async function convertImageToCesium(
fileName = fileName.replace(/^(\.\.(\/|\\|$))+/, "");
const imageFolder = path.resolve(LOCAL_IMAGE_DATA_FOLDER);
const tileFolder = path.resolve(CESIUM_IMAGERY_TILES_FOLDER);
const baseName = path.parse(fileName).name;
const tileCacheDir = path.join(tileFolder, baseName);
const warpedFileName = "_warped.tif";
const warpedFilePath = path.join(imageFolder, warpedFileName);

// Check for cached tiles before running the Docker pipeline
const cachedExtents = getCachedExtents(tileCacheDir);
if (cachedExtents) {
logger.info("Using cached tiles for:", fileName);
const tileBaseUrl = typeof window !== "undefined"
? window.location.origin
: FALLBACK_DEV_URL;
return addImageLayerWithExtents(
cesium.viewer,
`${tileBaseUrl}/src/data/tiles/imagery/${baseName}/{z}/{x}/{reverseY}.png`,
imageId,
cachedExtents,
setShowCredsExpiredAlert
);
}

logger.info("No cached tiles found for:", fileName, "- generating tiles...");

try {
return await new Promise<ImageryLayer | undefined>((resolve, reject) => {
exec("docker pull tumgis/ctb-quantized-mesh:alpine", async (err) => {
Expand Down Expand Up @@ -203,9 +280,9 @@ export async function convertImageToCesium(
const extents = JSON.parse(extentsOutput.trim());
logger.info("Successfully calculated extents:", extents);

// Step 3: Generate tiles from the warped image
// Step 3: Generate tiles into a per-image subdirectory
exec(
`docker exec ${jobName} sh -c "chmod -R 755 /data/tiles/ && ctb-tile -f PNG -R -C -N -s ${ZOOM_MAX} -e ${ZOOM_MIN} -t 256 -o /data/tiles /data/images/${warpedFileName}"`,
`docker exec ${jobName} sh -c "mkdir -p /data/tiles/${baseName} && chmod -R 755 /data/tiles/ && ctb-tile -f PNG -R -C -N -s ${ZOOM_MAX} -e ${ZOOM_MIN} -t 256 -o /data/tiles/${baseName} /data/images/${warpedFileName}"`,
async (err) => {
if (err) {
cleanupWarpedFile(warpedFilePath);
Expand All @@ -215,12 +292,15 @@ export async function convertImageToCesium(
}

try {
// Persist extents so subsequent loads skip the Docker pipeline
saveCachedExtents(tileCacheDir, extents);

const tileBaseUrl = typeof window !== "undefined"
? window.location.origin
: FALLBACK_DEV_URL;
const layer = await addImageLayerWithExtents(
cesium.viewer,
`${tileBaseUrl}/src/data/tiles/imagery/{z}/{x}/{reverseY}.png`,
`${tileBaseUrl}/src/data/tiles/imagery/${baseName}/{z}/{x}/{reverseY}.png`,
imageId,
extents,
setShowCredsExpiredAlert
Expand Down