mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
implement tile_path customization for cloudflare
This commit is contained in:
@@ -1,17 +1,51 @@
|
||||
import { test } from "zora";
|
||||
import { pmtiles_path } from "./index";
|
||||
import { pmtiles_path, tile_path } from "./index";
|
||||
|
||||
test("pmtiles path", (assertion) => {
|
||||
let result = pmtiles_path(undefined, "foo");
|
||||
let result = pmtiles_path("foo", undefined);
|
||||
assertion.equal(result, "foo.pmtiles");
|
||||
});
|
||||
|
||||
test("pmtiles path", (assertion) => {
|
||||
let result = pmtiles_path("folder/{name}/file.pmtiles", "foo");
|
||||
let result = pmtiles_path("foo","folder/{name}/file.pmtiles");
|
||||
assertion.equal(result, "folder/foo/file.pmtiles");
|
||||
});
|
||||
|
||||
test("pmtiles path with slash", (assertion) => {
|
||||
let result = pmtiles_path("folder/{name}/file.pmtiles", "foo/bar");
|
||||
let result = pmtiles_path("foo/bar","folder/{name}/file.pmtiles");
|
||||
assertion.equal(result, "folder/foo/bar/file.pmtiles");
|
||||
});
|
||||
|
||||
test("parse tile default", (assertion) => {
|
||||
let {ok, name, tile, ext} = tile_path("abcd");
|
||||
assertion.equal(ok, false);
|
||||
|
||||
({name, tile} = tile_path("/foo/11/22/33.pbf"));
|
||||
assertion.equal(name,"foo");
|
||||
assertion.equal(tile[0],11);
|
||||
assertion.equal(tile[1],22);
|
||||
assertion.equal(tile[2],33);
|
||||
});
|
||||
|
||||
test("parse tile path setting", (assertion) => {
|
||||
let {ok, name, tile, ext} = tile_path("/foo/11/22/33.pbf","/{name}/{z}/{y}/{x}.{ext}");
|
||||
assertion.equal(tile[1],33);
|
||||
assertion.equal(tile[2],22);
|
||||
assertion.equal(ext,"pbf");
|
||||
({ok, name, tile, ext} = tile_path("/tiles/foo/4/2/3.mvt","/tiles/{name}/{z}/{x}/{y}.{ext}"));
|
||||
assertion.equal(name,"foo");
|
||||
assertion.equal(tile[0],4);
|
||||
assertion.equal(tile[1],2);
|
||||
assertion.equal(tile[2],3);
|
||||
assertion.equal(ext,"mvt");
|
||||
});
|
||||
|
||||
test("parse tile path setting special chars", (assertion) => {
|
||||
let {ok, name, tile, ext} = tile_path("/folder(new/foo/11/22/33.pbf","/folder(new/{name}/{z}/{y}/{x}.{ext}");
|
||||
assertion.equal(name,"foo");
|
||||
});
|
||||
|
||||
test("parse tile path setting slash", (assertion) => {
|
||||
let {ok, name, tile, ext} = tile_path("/foo/bar/11/22/33.pbf","/{name}/{z}/{y}/{x}.{ext}");
|
||||
assertion.equal(name,"foo/bar");
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
interface Env {
|
||||
BUCKET: R2Bucket;
|
||||
PMTILES_PATH?: string;
|
||||
TILE_PATH?: string;
|
||||
}
|
||||
|
||||
class KeyNotFoundError extends Error {
|
||||
@@ -23,17 +24,46 @@ class KeyNotFoundError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const TILE = new RegExp(
|
||||
/^\/([0-9a-zA-Z\/!\-_\.\*\'\(\)]+)\/(\d+)\/(\d+)\/(\d+).([a-z]+)$/
|
||||
);
|
||||
|
||||
export const pmtiles_path = (p: string | undefined, name: string): string => {
|
||||
if (p) {
|
||||
return p.replace("{name}", name);
|
||||
export const pmtiles_path = (name: string, setting?: string): string => {
|
||||
if (setting) {
|
||||
return setting.replace("{name}", name);
|
||||
}
|
||||
return name + ".pmtiles";
|
||||
};
|
||||
|
||||
const TILE =
|
||||
/^\/(?<NAME>[0-9a-zA-Z\/!\-_\.\*\'\(\)]+)\/(?<Z>\d+)\/(?<X>\d+)\/(?<Y>\d+).(?<EXT>[a-z]+)$/;
|
||||
|
||||
export const tile_path = (
|
||||
path: string,
|
||||
setting?: string
|
||||
): {
|
||||
ok: boolean;
|
||||
name: string;
|
||||
tile: [number, number, number];
|
||||
ext: string;
|
||||
} => {
|
||||
let pattern = TILE;
|
||||
if (setting) {
|
||||
// escape regex
|
||||
setting = setting.replace(/[.*+?^$()|[\]\\]/g, "\\$&");
|
||||
setting = setting.replace("{name}", "(?<NAME>[0-9a-zA-Z/!-_.*'()]+)");
|
||||
setting = setting.replace("{z}", "(?<Z>\\d+)");
|
||||
setting = setting.replace("{x}", "(?<X>\\d+)");
|
||||
setting = setting.replace("{y}", "(?<Y>\\d+)");
|
||||
setting = setting.replace("{ext}", "(?<EXT>[a-z]+)");
|
||||
pattern = new RegExp(setting);
|
||||
}
|
||||
|
||||
let match = path.match(pattern);
|
||||
|
||||
if (match) {
|
||||
const g = match.groups!;
|
||||
return { ok: true, name: g.NAME, tile: [+g.Z, +g.X, +g.Y], ext: g.EXT };
|
||||
}
|
||||
return { ok: false, name: "", tile: [0, 0, 0], ext: "" };
|
||||
};
|
||||
|
||||
const CACHE = new ResolvedValueCache();
|
||||
|
||||
class R2Source implements Source {
|
||||
@@ -51,7 +81,7 @@ class R2Source implements Source {
|
||||
|
||||
async getBytes(offset: number, length: number): Promise<RangeResponse> {
|
||||
const resp = await this.env.BUCKET.get(
|
||||
pmtiles_path(this.env.PMTILES_PATH, this.archive_name),
|
||||
pmtiles_path(this.archive_name, this.env.PMTILES_PATH),
|
||||
{
|
||||
range: { offset: offset, length: length },
|
||||
}
|
||||
@@ -72,15 +102,10 @@ export default {
|
||||
ctx: ExecutionContext
|
||||
): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const match = url.pathname.match(TILE)!;
|
||||
const {ok, name, tile, ext} = tile_path(url.pathname, env.TILE_PATH);
|
||||
|
||||
if (match) {
|
||||
const archive_name = match[1];
|
||||
const z = +match[2];
|
||||
const x = +match[3];
|
||||
const y = +match[4];
|
||||
const ext = match[5];
|
||||
const source = new R2Source(env, archive_name);
|
||||
if (ok) {
|
||||
const source = new R2Source(env, name);
|
||||
const p = new PMTiles(source, CACHE);
|
||||
|
||||
let header = await p.getHeader();
|
||||
@@ -100,7 +125,7 @@ export default {
|
||||
|
||||
// TODO: optimize by checking header min/maxzoom
|
||||
try {
|
||||
const tile = await p.getZxy(z, x, y);
|
||||
const tiledata = await p.getZxy(tile[0], tile[1], tile[2]);
|
||||
const headers = new Headers();
|
||||
headers.set("Access-Control-Allow-Origin", "*"); // TODO: make configurable
|
||||
|
||||
@@ -120,8 +145,8 @@ export default {
|
||||
}
|
||||
|
||||
// TODO: optimize by making decompression optional
|
||||
if (tile) {
|
||||
return new Response(tile.data, { headers: headers, status: 200 });
|
||||
if (tiledata) {
|
||||
return new Response(tiledata.data, { headers: headers, status: 200 });
|
||||
} else {
|
||||
return new Response(undefined, { headers: headers, status: 204 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user