@versatiles/google-cloud
Version:
A server for VersaTiles in Google Cloud Run
86 lines (85 loc) • 3.41 kB
JavaScript
import { Container as VersatilesContainer } from '@versatiles/container';
import { guessStyle } from '@versatiles/style';
import { readFileSync } from 'fs';
const filenamePreview = new URL('../../../static/preview.html', import.meta.url).pathname;
const bufferPreview = readFileSync(filenamePreview);
export class Versatiles {
etag;
#container;
#header;
#metadata;
#url;
constructor(container, header, metadata, url, etag) {
this.#container = container;
this.#header = header;
this.#metadata = metadata;
this.#url = url;
this.etag = etag;
}
static async fromReader(reader, url, etag) {
const container = new VersatilesContainer(reader);
const header = await container.getHeader();
const metadata = await container.getMetadata() ?? '';
return new Versatiles(container, header, metadata, url, etag);
}
async serve(query, responder) {
// Log serving versatiles if verbose mode is enabled
responder.log(`serve versatiles query: ${JSON.stringify(query)}`);
// Handle different queries: preview, meta.json, style.json, or tile queries
switch (query) {
case '?preview':
await this.sendPreview(responder);
return;
case '?meta.json':
await this.sendMeta(responder);
return;
case '?style.json':
await this.sendStyle(responder);
return;
}
// Extract tile coordinates from the query and serve the requested tile
const match = /^\?(?<z>\d+)\/(?<x>\d+)\/(?<y>\d+)/.exec(query);
if (match != null) {
const { z, x, y } = match.groups;
const coordinates = { x: parseInt(x, 10), y: parseInt(y, 10), z: parseInt(z, 10) };
await this.sendTile(responder, coordinates);
return;
}
responder.error(400, 'get parameter must be "?preview", "?meta.json", "?style.json", or "?{z}/{x}/{y}"');
return;
}
async sendPreview(responder) {
await responder.respond(bufferPreview, 'text/html', 'raw');
}
async sendMeta(responder) {
await responder.respond(this.#metadata, 'application/json', 'raw');
}
async sendStyle(responder) {
responder.log('respond with style.json');
try {
const tileJson = JSON.parse(this.#metadata);
tileJson.tiles = [`${this.#url}?{z}/{x}/{y}`];
const style = await guessStyle(tileJson, {});
await responder.respond(JSON.stringify(style), 'application/json', 'raw');
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
responder.error(500, `server side error: ${message}`);
}
return;
}
async sendTile(responder, coordinates) {
const { x, y, z } = coordinates;
responder.log(`fetch tile x:${x}, y:${y}, z:${z}`);
const tile = await this.#container.getTile(z, x, y);
// Return error for invalid queries
if (tile == null) {
responder.error(204, `no map tile at ${z}/${x}/${y}`);
}
else {
responder.log(`return tile ${z}/${x}/${y}`);
await responder.respond(tile, this.#header.tileMime, this.#header.tileCompression);
}
return;
}
}