const express = require("express"); const Database = require("better-sqlite3"); const path = require("path"); const router = express.Router(); // ======================= // MBTiles DB (READONLY) // ======================= const mbtilesPath = path.join(__dirname, "..", "data", "map.mbtiles"); const tileDb = new Database(mbtilesPath, { readonly: true }); // ======================= // 📊 METADATA // ======================= const metadataRows = tileDb.prepare("SELECT name, value FROM metadata").all(); const metadata = {}; for (const row of metadataRows) { metadata[row.name] = row.value; } // decide content-type let contentType = "application/octet-stream"; if (metadata.format === "pbf") { contentType = "application/x-protobuf"; } else if (metadata.format === "png") { contentType = "image/png"; } else if (metadata.format === "jpg" || metadata.format === "jpeg") { contentType = "image/jpeg"; } // ======================= // METADATA API // ======================= router.get("/metadata/info", (req, res) => { res.json(metadata); }); // ======================= // TILE API (XYZ → TMS) // ======================= router.get("/:z/:x/:y", (req, res) => { const z = Number(req.params.z); const x = Number(req.params.x); const y = Number(req.params.y); if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) { return res.status(400).json({ error: "Invalid tile coordinates" }); } // convert XYZ → TMS const tmsY = (1 << z) - 1 - y; const stmt = tileDb.prepare(` SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ? `); const tile = stmt.get(z, x, tmsY); if (!tile) { return res.status(404).json({ error: "Tile not found" }); } res.setHeader("Content-Type", contentType); if (metadata.format === "pbf") { res.setHeader("Content-Encoding", "gzip"); } res.send(tile.tile_data); }); module.exports = router;