mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 02:41:09 +00:00
* Properly abort requests using AbortController * add basic adapter tests --------- Co-authored-by: Andrew Dassonville <dassonville.andrew@gmail.com>
This commit is contained in:
@@ -215,12 +215,13 @@ export class Protocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.metadata) {
|
if (this.metadata) {
|
||||||
return {
|
const data = await instance.getTileJson(params.url);
|
||||||
data: await instance.getTileJson(params.url),
|
abortController.signal.throwIfAborted();
|
||||||
};
|
return { data };
|
||||||
}
|
}
|
||||||
|
|
||||||
const h = await instance.getHeader();
|
const h = await instance.getHeader();
|
||||||
|
abortController.signal.throwIfAborted();
|
||||||
|
|
||||||
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
|
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -255,6 +256,7 @@ export class Protocol {
|
|||||||
|
|
||||||
const header = await instance.getHeader();
|
const header = await instance.getHeader();
|
||||||
const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
|
const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
|
||||||
|
abortController.signal.throwIfAborted();
|
||||||
if (resp) {
|
if (resp) {
|
||||||
return {
|
return {
|
||||||
data: new Uint8Array(resp.data),
|
data: new Uint8Array(resp.data),
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ export class FetchSource implements Source {
|
|||||||
let userAgent = "";
|
let userAgent = "";
|
||||||
if ("navigator" in globalThis) {
|
if ("navigator" in globalThis) {
|
||||||
//biome-ignore lint: cf workers
|
//biome-ignore lint: cf workers
|
||||||
userAgent = (globalThis as any).navigator.userAgent || "";
|
userAgent = (globalThis as any).navigator?.userAgent ?? "";
|
||||||
}
|
}
|
||||||
const isWindows = userAgent.indexOf("Windows") > -1;
|
const isWindows = userAgent.indexOf("Windows") > -1;
|
||||||
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);
|
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);
|
||||||
|
|||||||
119
js/test/adapter.test.ts
Normal file
119
js/test/adapter.test.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import assert from "node:assert";
|
||||||
|
import { describe, mock, test } from "node:test";
|
||||||
|
import { PMTiles, Protocol } from "../src";
|
||||||
|
import { mockServer } from "./utils";
|
||||||
|
|
||||||
|
describe("Protocol", () => {
|
||||||
|
test("get TileJSON", async () => {
|
||||||
|
mockServer.reset();
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol();
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const result = await protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles",
|
||||||
|
type: "json",
|
||||||
|
},
|
||||||
|
new AbortController()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result.data, {
|
||||||
|
tiles: ["pmtiles://http://localhost:1337/example.pmtiles/{z}/{x}/{y}"],
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 0,
|
||||||
|
bounds: [0, 0, 0.9999999, 1],
|
||||||
|
});
|
||||||
|
assert.equal(mockServer.numRequests, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("get tile data", async () => {
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol();
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const result = await protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
|
||||||
|
type: "arrayBuffer",
|
||||||
|
},
|
||||||
|
new AbortController()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(result.data instanceof Uint8Array);
|
||||||
|
assert.strictEqual(result.data.length, 49);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns empty data for missing tile if errorOnMissingTile is false", async () => {
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol({ errorOnMissingTile: false });
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const result = await protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
|
||||||
|
type: "arrayBuffer",
|
||||||
|
},
|
||||||
|
new AbortController()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(result.data instanceof Uint8Array);
|
||||||
|
assert.strictEqual(result.data.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws error for missing tile if errorOnMissingTile is true", async () => {
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol({ errorOnMissingTile: true });
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const promise = protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
|
||||||
|
type: "arrayBuffer",
|
||||||
|
},
|
||||||
|
new AbortController()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.rejects(promise, { message: "Tile not found." });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws AbortError when AbortController is signaled while accessing TileJSON", async () => {
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol();
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
const resultPromise = protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles",
|
||||||
|
type: "json",
|
||||||
|
},
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
|
abortController.abort();
|
||||||
|
|
||||||
|
await assert.rejects(resultPromise, { name: "AbortError" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws AbortError when AbortController is signaled while accessing tile data", async () => {
|
||||||
|
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
const protocol = new Protocol();
|
||||||
|
protocol.add(pmtiles);
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
const resultPromise = protocol.tilev4(
|
||||||
|
{
|
||||||
|
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
|
||||||
|
type: "arrayBuffer",
|
||||||
|
},
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
|
abortController.abort();
|
||||||
|
|
||||||
|
await assert.rejects(resultPromise, { name: "AbortError" });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
import "./adapter.test";
|
||||||
import "./v3.test";
|
import "./v3.test";
|
||||||
|
|||||||
44
js/test/utils.ts
Normal file
44
js/test/utils.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import { http, HttpResponse } from "msw";
|
||||||
|
import { setupServer } from "msw/node";
|
||||||
|
|
||||||
|
class MockServer {
|
||||||
|
etag?: string;
|
||||||
|
numRequests: number;
|
||||||
|
lastCache?: string;
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.numRequests = 0;
|
||||||
|
this.etag = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.numRequests = 0;
|
||||||
|
this.etag = undefined;
|
||||||
|
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
||||||
|
const server = setupServer(
|
||||||
|
http.get(
|
||||||
|
"http://localhost:1337/example.pmtiles",
|
||||||
|
({ request, params }) => {
|
||||||
|
this.lastCache = request.cache;
|
||||||
|
this.numRequests++;
|
||||||
|
const range = request.headers.get("range")?.substr(6).split("-");
|
||||||
|
if (!range) {
|
||||||
|
throw new Error("invalid range");
|
||||||
|
}
|
||||||
|
const offset = +range[0];
|
||||||
|
const length = +range[1];
|
||||||
|
const body = serverBuffer.slice(offset, offset + length - 1);
|
||||||
|
return new HttpResponse(body, {
|
||||||
|
status: 206,
|
||||||
|
statusText: "OK",
|
||||||
|
headers: { etag: this.etag } as HeadersInit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
server.listen({ onUnhandledRequest: "error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mockServer = new MockServer();
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { afterEach, beforeEach, describe, it, test } from "node:test";
|
import { afterEach, beforeEach, describe, it, test } from "node:test";
|
||||||
import { http, HttpResponse } from "msw";
|
import { mockServer } from "./utils";
|
||||||
import { setupServer } from "msw/node";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BufferPosition,
|
BufferPosition,
|
||||||
@@ -20,424 +19,390 @@ import {
|
|||||||
zxyToTileId,
|
zxyToTileId,
|
||||||
} from "../src/index";
|
} from "../src/index";
|
||||||
|
|
||||||
class MockServer {
|
describe("pmtiles v3", () => {
|
||||||
etag?: string;
|
test("varint", () => {
|
||||||
numRequests: number;
|
let b: BufferPosition = {
|
||||||
lastCache?: string;
|
buf: new Uint8Array([0, 1, 127, 0xe5, 0x8e, 0x26]),
|
||||||
|
pos: 0,
|
||||||
|
};
|
||||||
|
assert.strictEqual(readVarint(b), 0);
|
||||||
|
assert.strictEqual(readVarint(b), 1);
|
||||||
|
assert.strictEqual(readVarint(b), 127);
|
||||||
|
assert.strictEqual(readVarint(b), 624485);
|
||||||
|
b = {
|
||||||
|
buf: new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f]),
|
||||||
|
pos: 0,
|
||||||
|
};
|
||||||
|
assert.strictEqual(readVarint(b), 9007199254740991);
|
||||||
|
});
|
||||||
|
|
||||||
reset() {
|
test("zxy to tile id", () => {
|
||||||
this.numRequests = 0;
|
assert.strictEqual(zxyToTileId(0, 0, 0), 0);
|
||||||
this.etag = undefined;
|
assert.strictEqual(zxyToTileId(1, 0, 0), 1);
|
||||||
}
|
assert.strictEqual(zxyToTileId(1, 0, 1), 2);
|
||||||
|
assert.strictEqual(zxyToTileId(1, 1, 1), 3);
|
||||||
|
assert.strictEqual(zxyToTileId(1, 1, 0), 4);
|
||||||
|
assert.strictEqual(zxyToTileId(2, 0, 0), 5);
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {
|
test("tile id to zxy", () => {
|
||||||
this.numRequests = 0;
|
assert.deepEqual(tileIdToZxy(0), [0, 0, 0]);
|
||||||
this.etag = undefined;
|
assert.deepEqual(tileIdToZxy(1), [1, 0, 0]);
|
||||||
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
assert.deepEqual(tileIdToZxy(2), [1, 0, 1]);
|
||||||
const server = setupServer(
|
assert.deepEqual(tileIdToZxy(3), [1, 1, 1]);
|
||||||
http.get(
|
assert.deepEqual(tileIdToZxy(4), [1, 1, 0]);
|
||||||
"http://localhost:1337/example.pmtiles",
|
assert.deepEqual(tileIdToZxy(5), [2, 0, 0]);
|
||||||
({ request, params }) => {
|
});
|
||||||
this.lastCache = request.cache;
|
|
||||||
this.numRequests++;
|
test("a lot of tiles", () => {
|
||||||
const range = request.headers.get("range")?.substr(6).split("-");
|
for (let z = 0; z < 9; z++) {
|
||||||
if (!range) {
|
for (let x = 0; x < 1 << z; x++) {
|
||||||
throw new Error("invalid range");
|
for (let y = 0; y < 1 << z; y++) {
|
||||||
|
const result = tileIdToZxy(zxyToTileId(z, x, y));
|
||||||
|
if (result[0] !== z || result[1] !== x || result[2] !== y) {
|
||||||
|
assert.fail("roundtrip failed");
|
||||||
}
|
}
|
||||||
const offset = +range[0];
|
|
||||||
const length = +range[1];
|
|
||||||
const body = serverBuffer.slice(offset, offset + length - 1);
|
|
||||||
return new HttpResponse(body, {
|
|
||||||
status: 206,
|
|
||||||
statusText: "OK",
|
|
||||||
headers: { etag: this.etag } as HeadersInit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
server.listen({ onUnhandledRequest: "error" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockserver = new MockServer();
|
|
||||||
|
|
||||||
test("varint", () => {
|
|
||||||
let b: BufferPosition = {
|
|
||||||
buf: new Uint8Array([0, 1, 127, 0xe5, 0x8e, 0x26]),
|
|
||||||
pos: 0,
|
|
||||||
};
|
|
||||||
assert.strictEqual(readVarint(b), 0);
|
|
||||||
assert.strictEqual(readVarint(b), 1);
|
|
||||||
assert.strictEqual(readVarint(b), 127);
|
|
||||||
assert.strictEqual(readVarint(b), 624485);
|
|
||||||
b = {
|
|
||||||
buf: new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f]),
|
|
||||||
pos: 0,
|
|
||||||
};
|
|
||||||
assert.strictEqual(readVarint(b), 9007199254740991);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("zxy to tile id", () => {
|
|
||||||
assert.strictEqual(zxyToTileId(0, 0, 0), 0);
|
|
||||||
assert.strictEqual(zxyToTileId(1, 0, 0), 1);
|
|
||||||
assert.strictEqual(zxyToTileId(1, 0, 1), 2);
|
|
||||||
assert.strictEqual(zxyToTileId(1, 1, 1), 3);
|
|
||||||
assert.strictEqual(zxyToTileId(1, 1, 0), 4);
|
|
||||||
assert.strictEqual(zxyToTileId(2, 0, 0), 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tile id to zxy", () => {
|
|
||||||
assert.deepEqual(tileIdToZxy(0), [0, 0, 0]);
|
|
||||||
assert.deepEqual(tileIdToZxy(1), [1, 0, 0]);
|
|
||||||
assert.deepEqual(tileIdToZxy(2), [1, 0, 1]);
|
|
||||||
assert.deepEqual(tileIdToZxy(3), [1, 1, 1]);
|
|
||||||
assert.deepEqual(tileIdToZxy(4), [1, 1, 0]);
|
|
||||||
assert.deepEqual(tileIdToZxy(5), [2, 0, 0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a lot of tiles", () => {
|
|
||||||
for (let z = 0; z < 9; z++) {
|
|
||||||
for (let x = 0; x < 1 << z; x++) {
|
|
||||||
for (let y = 0; y < 1 << z; y++) {
|
|
||||||
const result = tileIdToZxy(zxyToTileId(z, x, y));
|
|
||||||
if (result[0] !== z || result[1] !== x || result[2] !== y) {
|
|
||||||
assert.fail("roundtrip failed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tile extremes", () => {
|
|
||||||
for (let z = 0; z < 27; z++) {
|
|
||||||
const dim = 2 ** z - 1;
|
|
||||||
const tl = tileIdToZxy(zxyToTileId(z, 0, 0));
|
|
||||||
assert.deepEqual([z, 0, 0], tl);
|
|
||||||
const tr = tileIdToZxy(zxyToTileId(z, dim, 0));
|
|
||||||
assert.deepEqual([z, dim, 0], tr);
|
|
||||||
const bl = tileIdToZxy(zxyToTileId(z, 0, dim));
|
|
||||||
assert.deepEqual([z, 0, dim], bl);
|
|
||||||
const br = tileIdToZxy(zxyToTileId(z, dim, dim));
|
|
||||||
assert.deepEqual([z, dim, dim], br);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("invalid tiles", () => {
|
|
||||||
assert.throws(() => {
|
|
||||||
tileIdToZxy(Number.MAX_SAFE_INTEGER);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
test("tile extremes", () => {
|
||||||
zxyToTileId(27, 0, 0);
|
for (let z = 0; z < 27; z++) {
|
||||||
|
const dim = 2 ** z - 1;
|
||||||
|
const tl = tileIdToZxy(zxyToTileId(z, 0, 0));
|
||||||
|
assert.deepEqual([z, 0, 0], tl);
|
||||||
|
const tr = tileIdToZxy(zxyToTileId(z, dim, 0));
|
||||||
|
assert.deepEqual([z, dim, 0], tr);
|
||||||
|
const bl = tileIdToZxy(zxyToTileId(z, 0, dim));
|
||||||
|
assert.deepEqual([z, 0, dim], bl);
|
||||||
|
const br = tileIdToZxy(zxyToTileId(z, dim, dim));
|
||||||
|
assert.deepEqual([z, dim, dim], br);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
test("invalid tiles", () => {
|
||||||
zxyToTileId(0, 1, 1);
|
assert.throws(() => {
|
||||||
});
|
tileIdToZxy(Number.MAX_SAFE_INTEGER);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("tile search for missing entry", () => {
|
assert.throws(() => {
|
||||||
const entries: Entry[] = [];
|
zxyToTileId(27, 0, 0);
|
||||||
assert.strictEqual(findTile(entries, 101), null);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test("tile search for first entry == id", () => {
|
assert.throws(() => {
|
||||||
const entries: Entry[] = [
|
zxyToTileId(0, 1, 1);
|
||||||
{ tileId: 100, offset: 1, length: 1, runLength: 1 },
|
|
||||||
];
|
|
||||||
const entry = findTile(entries, 100);
|
|
||||||
assert.strictEqual(entry?.offset, 1);
|
|
||||||
assert.strictEqual(entry?.length, 1);
|
|
||||||
assert.strictEqual(findTile(entries, 101), null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tile search with runlength", () => {
|
|
||||||
const entries: Entry[] = [
|
|
||||||
{ tileId: 3, offset: 3, length: 1, runLength: 2 },
|
|
||||||
{ tileId: 5, offset: 5, length: 1, runLength: 2 },
|
|
||||||
];
|
|
||||||
const entry = findTile(entries, 4);
|
|
||||||
assert.strictEqual(entry?.offset, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tile search with multiple tile entries", () => {
|
|
||||||
let entries: Entry[] = [{ tileId: 100, offset: 1, length: 1, runLength: 2 }];
|
|
||||||
let entry = findTile(entries, 101);
|
|
||||||
assert.strictEqual(entry?.offset, 1);
|
|
||||||
assert.strictEqual(entry?.length, 1);
|
|
||||||
|
|
||||||
entries = [
|
|
||||||
{ tileId: 100, offset: 1, length: 1, runLength: 1 },
|
|
||||||
{ tileId: 150, offset: 2, length: 2, runLength: 2 },
|
|
||||||
];
|
|
||||||
entry = findTile(entries, 151);
|
|
||||||
assert.strictEqual(entry?.offset, 2);
|
|
||||||
assert.strictEqual(entry?.length, 2);
|
|
||||||
|
|
||||||
entries = [
|
|
||||||
{ tileId: 50, offset: 1, length: 1, runLength: 2 },
|
|
||||||
{ tileId: 100, offset: 2, length: 2, runLength: 1 },
|
|
||||||
{ tileId: 150, offset: 3, length: 3, runLength: 1 },
|
|
||||||
];
|
|
||||||
entry = findTile(entries, 51);
|
|
||||||
assert.strictEqual(entry?.offset, 1);
|
|
||||||
assert.strictEqual(entry?.length, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("leaf search", () => {
|
|
||||||
const entries: Entry[] = [
|
|
||||||
{ tileId: 100, offset: 1, length: 1, runLength: 0 },
|
|
||||||
];
|
|
||||||
const entry = findTile(entries, 150);
|
|
||||||
assert.strictEqual(entry?.offset, 1);
|
|
||||||
assert.strictEqual(entry?.length, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// inefficient method only for testing
|
|
||||||
class TestNodeFileSource implements Source {
|
|
||||||
buffer: ArrayBuffer;
|
|
||||||
path: string;
|
|
||||||
key: string;
|
|
||||||
etag?: string;
|
|
||||||
|
|
||||||
constructor(path: string, key: string) {
|
|
||||||
this.path = path;
|
|
||||||
this.buffer = fs.readFileSync(path);
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
getKey() {
|
|
||||||
return this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceData(path: string) {
|
|
||||||
this.path = path;
|
|
||||||
this.buffer = fs.readFileSync(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBytes(offset: number, length: number): Promise<RangeResponse> {
|
|
||||||
const slice = new Uint8Array(this.buffer.slice(offset, offset + length))
|
|
||||||
.buffer;
|
|
||||||
return { data: slice, etag: this.etag };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_1.pmtiles
|
|
||||||
test("cache getHeader", async () => {
|
|
||||||
const source = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
const cache = new SharedPromiseCache();
|
|
||||||
const header = await cache.getHeader(source);
|
|
||||||
assert.strictEqual(header.rootDirectoryOffset, 127);
|
|
||||||
assert.strictEqual(header.rootDirectoryLength, 25);
|
|
||||||
assert.strictEqual(header.jsonMetadataOffset, 152);
|
|
||||||
assert.strictEqual(header.jsonMetadataLength, 247);
|
|
||||||
assert.strictEqual(header.leafDirectoryOffset, 0);
|
|
||||||
assert.strictEqual(header.leafDirectoryLength, 0);
|
|
||||||
assert.strictEqual(header.tileDataOffset, 399);
|
|
||||||
assert.strictEqual(header.tileDataLength, 69);
|
|
||||||
assert.strictEqual(header.numAddressedTiles, 1);
|
|
||||||
assert.strictEqual(header.numTileEntries, 1);
|
|
||||||
assert.strictEqual(header.numTileContents, 1);
|
|
||||||
assert.strictEqual(header.clustered, false);
|
|
||||||
assert.strictEqual(header.internalCompression, 2);
|
|
||||||
assert.strictEqual(header.tileCompression, 2);
|
|
||||||
assert.strictEqual(header.tileType, 1);
|
|
||||||
assert.strictEqual(header.minZoom, 0);
|
|
||||||
assert.strictEqual(header.maxZoom, 0);
|
|
||||||
assert.strictEqual(header.minLon, 0);
|
|
||||||
assert.strictEqual(header.minLat, 0);
|
|
||||||
// assert.strictEqual(header.maxLon,1); // TODO fix me
|
|
||||||
assert.strictEqual(header.maxLat, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getUint64", async () => {
|
|
||||||
const view = new DataView(new ArrayBuffer(8));
|
|
||||||
view.setBigUint64(0, 0n, true);
|
|
||||||
assert.strictEqual(getUint64(view, 0), 0);
|
|
||||||
view.setBigUint64(0, 1n, true);
|
|
||||||
assert.strictEqual(getUint64(view, 0), 1);
|
|
||||||
view.setBigUint64(0, 9007199254740991n, true);
|
|
||||||
assert.strictEqual(getUint64(view, 0), 9007199254740991);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cache check against empty", async () => {
|
|
||||||
const source = new TestNodeFileSource("test/data/empty.pmtiles", "1");
|
|
||||||
const cache = new SharedPromiseCache();
|
|
||||||
assert.rejects(async () => {
|
|
||||||
await cache.getHeader(source);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cache check magic number", async () => {
|
|
||||||
const source = new TestNodeFileSource("test/data/invalid.pmtiles", "1");
|
|
||||||
const cache = new SharedPromiseCache();
|
|
||||||
assert.rejects(async () => {
|
|
||||||
await cache.getHeader(source);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cache check future spec version", async () => {
|
|
||||||
const source = new TestNodeFileSource("test/data/invalid_v4.pmtiles", "1");
|
|
||||||
const cache = new SharedPromiseCache();
|
|
||||||
assert.rejects(async () => {
|
|
||||||
await cache.getHeader(source);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cache getDirectory", async () => {
|
|
||||||
const source = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
|
|
||||||
const cache = new SharedPromiseCache(6400);
|
|
||||||
const header = await cache.getHeader(source);
|
|
||||||
|
|
||||||
// prepopulates the root directory
|
|
||||||
assert.strictEqual(cache.cache.size, 2);
|
|
||||||
|
|
||||||
const directory = await cache.getDirectory(
|
|
||||||
source,
|
|
||||||
header.rootDirectoryOffset,
|
|
||||||
header.rootDirectoryLength,
|
|
||||||
header
|
|
||||||
);
|
|
||||||
assert.strictEqual(directory.length, 1);
|
|
||||||
assert.strictEqual(directory[0].tileId, 0);
|
|
||||||
assert.strictEqual(directory[0].offset, 0);
|
|
||||||
assert.strictEqual(directory[0].length, 69);
|
|
||||||
assert.strictEqual(directory[0].runLength, 1);
|
|
||||||
|
|
||||||
for (const v of cache.cache.values()) {
|
|
||||||
assert.ok(v.lastUsed > 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("multiple sources in a single cache", async () => {
|
|
||||||
const cache = new SharedPromiseCache();
|
|
||||||
const source1 = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
const source2 = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"2"
|
|
||||||
);
|
|
||||||
await cache.getHeader(source1);
|
|
||||||
assert.strictEqual(cache.cache.size, 2);
|
|
||||||
await cache.getHeader(source2);
|
|
||||||
assert.strictEqual(cache.cache.size, 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("etag change", async () => {
|
|
||||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
|
||||||
const tile = await p.getZxy(0, 0, 0);
|
|
||||||
// header + tile
|
|
||||||
assert.strictEqual(2, mockserver.numRequests);
|
|
||||||
mockserver.etag = "etag_2";
|
|
||||||
await p.getZxy(0, 0, 0);
|
|
||||||
// tile + header again + tile
|
|
||||||
assert.strictEqual(5, mockserver.numRequests);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("weak etags", async () => {
|
|
||||||
mockserver.reset();
|
|
||||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
|
||||||
const tile = await p.getZxy(0, 0, 0);
|
|
||||||
// header + tile
|
|
||||||
assert.strictEqual(2, mockserver.numRequests);
|
|
||||||
mockserver.etag = "W/weak_etag";
|
|
||||||
await p.getZxy(0, 0, 0);
|
|
||||||
assert.strictEqual(3, mockserver.numRequests);
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle < 16384 bytes archive case
|
|
||||||
// handle DigitalOcean case returning 200 instead of 206
|
|
||||||
|
|
||||||
test("cache pruning by byte size", async () => {
|
|
||||||
const cache = new SharedPromiseCache(2);
|
|
||||||
cache.cache.set("0", { lastUsed: 0, data: Promise.resolve([]) });
|
|
||||||
cache.cache.set("1", { lastUsed: 1, data: Promise.resolve([]) });
|
|
||||||
cache.cache.set("2", { lastUsed: 2, data: Promise.resolve([]) });
|
|
||||||
cache.prune();
|
|
||||||
assert.strictEqual(cache.cache.size, 2);
|
|
||||||
assert.ok(cache.cache.get("2"));
|
|
||||||
assert.ok(cache.cache.get("1"));
|
|
||||||
assert.ok(!cache.cache.get("0"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("pmtiles get metadata", async () => {
|
|
||||||
const source = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
const p = new PMTiles(source);
|
|
||||||
const metadata = await p.getMetadata();
|
|
||||||
assert.ok((metadata as { name: string }).name);
|
|
||||||
});
|
|
||||||
|
|
||||||
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_2.pmtiles
|
|
||||||
|
|
||||||
test("get file extension", async () => {
|
|
||||||
assert.equal("", tileTypeExt(TileType.Unknown));
|
|
||||||
assert.equal(".mvt", tileTypeExt(TileType.Mvt));
|
|
||||||
assert.equal(".png", tileTypeExt(TileType.Png));
|
|
||||||
assert.equal(".jpg", tileTypeExt(TileType.Jpeg));
|
|
||||||
assert.equal(".webp", tileTypeExt(TileType.Webp));
|
|
||||||
assert.equal(".avif", tileTypeExt(TileType.Avif));
|
|
||||||
});
|
|
||||||
|
|
||||||
interface TileJsonLike {
|
|
||||||
tilejson: string;
|
|
||||||
scheme: string;
|
|
||||||
tiles: string[];
|
|
||||||
description?: string;
|
|
||||||
name?: string;
|
|
||||||
attribution?: string;
|
|
||||||
version?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("pmtiles get TileJSON", async () => {
|
|
||||||
const source = new TestNodeFileSource(
|
|
||||||
"test/data/test_fixture_1.pmtiles",
|
|
||||||
"1"
|
|
||||||
);
|
|
||||||
const p = new PMTiles(source);
|
|
||||||
const tilejson = (await p.getTileJson(
|
|
||||||
"https://example.com/foo"
|
|
||||||
)) as TileJsonLike;
|
|
||||||
assert.equal("3.0.0", tilejson.tilejson);
|
|
||||||
assert.equal("xyz", tilejson.scheme);
|
|
||||||
assert.equal("https://example.com/foo/{z}/{x}/{y}.mvt", tilejson.tiles[0]);
|
|
||||||
assert.equal(undefined, tilejson.attribution);
|
|
||||||
assert.equal("test_fixture_1.pmtiles", tilejson.description);
|
|
||||||
assert.equal("test_fixture_1.pmtiles", tilejson.name);
|
|
||||||
assert.equal("2", tilejson.version);
|
|
||||||
});
|
|
||||||
|
|
||||||
const originalUserAgent = navigator.userAgent;
|
|
||||||
|
|
||||||
describe("user agent", async () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
Object.defineProperty(globalThis.navigator, "userAgent", {
|
|
||||||
value: "Windows Chrome",
|
|
||||||
configurable: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
test("tile search for missing entry", () => {
|
||||||
Object.defineProperty(globalThis.navigator, "userAgent", {
|
const entries: Entry[] = [];
|
||||||
value: originalUserAgent,
|
assert.strictEqual(findTile(entries, 101), null);
|
||||||
configurable: true,
|
});
|
||||||
|
|
||||||
|
test("tile search for first entry == id", () => {
|
||||||
|
const entries: Entry[] = [
|
||||||
|
{ tileId: 100, offset: 1, length: 1, runLength: 1 },
|
||||||
|
];
|
||||||
|
const entry = findTile(entries, 100);
|
||||||
|
assert.strictEqual(entry?.offset, 1);
|
||||||
|
assert.strictEqual(entry?.length, 1);
|
||||||
|
assert.strictEqual(findTile(entries, 101), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile search with runlength", () => {
|
||||||
|
const entries: Entry[] = [
|
||||||
|
{ tileId: 3, offset: 3, length: 1, runLength: 2 },
|
||||||
|
{ tileId: 5, offset: 5, length: 1, runLength: 2 },
|
||||||
|
];
|
||||||
|
const entry = findTile(entries, 4);
|
||||||
|
assert.strictEqual(entry?.offset, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile search with multiple tile entries", () => {
|
||||||
|
let entries: Entry[] = [
|
||||||
|
{ tileId: 100, offset: 1, length: 1, runLength: 2 },
|
||||||
|
];
|
||||||
|
let entry = findTile(entries, 101);
|
||||||
|
assert.strictEqual(entry?.offset, 1);
|
||||||
|
assert.strictEqual(entry?.length, 1);
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
{ tileId: 100, offset: 1, length: 1, runLength: 1 },
|
||||||
|
{ tileId: 150, offset: 2, length: 2, runLength: 2 },
|
||||||
|
];
|
||||||
|
entry = findTile(entries, 151);
|
||||||
|
assert.strictEqual(entry?.offset, 2);
|
||||||
|
assert.strictEqual(entry?.length, 2);
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
{ tileId: 50, offset: 1, length: 1, runLength: 2 },
|
||||||
|
{ tileId: 100, offset: 2, length: 2, runLength: 1 },
|
||||||
|
{ tileId: 150, offset: 3, length: 3, runLength: 1 },
|
||||||
|
];
|
||||||
|
entry = findTile(entries, 51);
|
||||||
|
assert.strictEqual(entry?.offset, 1);
|
||||||
|
assert.strictEqual(entry?.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("leaf search", () => {
|
||||||
|
const entries: Entry[] = [
|
||||||
|
{ tileId: 100, offset: 1, length: 1, runLength: 0 },
|
||||||
|
];
|
||||||
|
const entry = findTile(entries, 150);
|
||||||
|
assert.strictEqual(entry?.offset, 1);
|
||||||
|
assert.strictEqual(entry?.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// inefficient method only for testing
|
||||||
|
class TestNodeFileSource implements Source {
|
||||||
|
buffer: ArrayBuffer;
|
||||||
|
path: string;
|
||||||
|
key: string;
|
||||||
|
etag?: string;
|
||||||
|
|
||||||
|
constructor(path: string, key: string) {
|
||||||
|
this.path = path;
|
||||||
|
this.buffer = fs.readFileSync(path);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceData(path: string) {
|
||||||
|
this.path = path;
|
||||||
|
this.buffer = fs.readFileSync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBytes(offset: number, length: number): Promise<RangeResponse> {
|
||||||
|
const slice = new Uint8Array(this.buffer.slice(offset, offset + length))
|
||||||
|
.buffer;
|
||||||
|
return { data: slice, etag: this.etag };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_1.pmtiles
|
||||||
|
test("cache getHeader", async () => {
|
||||||
|
const source = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
const cache = new SharedPromiseCache();
|
||||||
|
const header = await cache.getHeader(source);
|
||||||
|
assert.strictEqual(header.rootDirectoryOffset, 127);
|
||||||
|
assert.strictEqual(header.rootDirectoryLength, 25);
|
||||||
|
assert.strictEqual(header.jsonMetadataOffset, 152);
|
||||||
|
assert.strictEqual(header.jsonMetadataLength, 247);
|
||||||
|
assert.strictEqual(header.leafDirectoryOffset, 0);
|
||||||
|
assert.strictEqual(header.leafDirectoryLength, 0);
|
||||||
|
assert.strictEqual(header.tileDataOffset, 399);
|
||||||
|
assert.strictEqual(header.tileDataLength, 69);
|
||||||
|
assert.strictEqual(header.numAddressedTiles, 1);
|
||||||
|
assert.strictEqual(header.numTileEntries, 1);
|
||||||
|
assert.strictEqual(header.numTileContents, 1);
|
||||||
|
assert.strictEqual(header.clustered, false);
|
||||||
|
assert.strictEqual(header.internalCompression, 2);
|
||||||
|
assert.strictEqual(header.tileCompression, 2);
|
||||||
|
assert.strictEqual(header.tileType, 1);
|
||||||
|
assert.strictEqual(header.minZoom, 0);
|
||||||
|
assert.strictEqual(header.maxZoom, 0);
|
||||||
|
assert.strictEqual(header.minLon, 0);
|
||||||
|
assert.strictEqual(header.minLat, 0);
|
||||||
|
// assert.strictEqual(header.maxLon,1); // TODO fix me
|
||||||
|
assert.strictEqual(header.maxLat, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getUint64", async () => {
|
||||||
|
const view = new DataView(new ArrayBuffer(8));
|
||||||
|
view.setBigUint64(0, 0n, true);
|
||||||
|
assert.strictEqual(getUint64(view, 0), 0);
|
||||||
|
view.setBigUint64(0, 1n, true);
|
||||||
|
assert.strictEqual(getUint64(view, 0), 1);
|
||||||
|
view.setBigUint64(0, 9007199254740991n, true);
|
||||||
|
assert.strictEqual(getUint64(view, 0), 9007199254740991);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cache check against empty", async () => {
|
||||||
|
const source = new TestNodeFileSource("test/data/empty.pmtiles", "1");
|
||||||
|
const cache = new SharedPromiseCache();
|
||||||
|
assert.rejects(async () => {
|
||||||
|
await cache.getHeader(source);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("works around caching bug on chrome on windows", async () => {
|
test("cache check magic number", async () => {
|
||||||
|
const source = new TestNodeFileSource("test/data/invalid.pmtiles", "1");
|
||||||
|
const cache = new SharedPromiseCache();
|
||||||
|
assert.rejects(async () => {
|
||||||
|
await cache.getHeader(source);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cache check future spec version", async () => {
|
||||||
|
const source = new TestNodeFileSource("test/data/invalid_v4.pmtiles", "1");
|
||||||
|
const cache = new SharedPromiseCache();
|
||||||
|
assert.rejects(async () => {
|
||||||
|
await cache.getHeader(source);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cache getDirectory", async () => {
|
||||||
|
const source = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
|
||||||
|
const cache = new SharedPromiseCache(6400);
|
||||||
|
const header = await cache.getHeader(source);
|
||||||
|
|
||||||
|
// prepopulates the root directory
|
||||||
|
assert.strictEqual(cache.cache.size, 2);
|
||||||
|
|
||||||
|
const directory = await cache.getDirectory(
|
||||||
|
source,
|
||||||
|
header.rootDirectoryOffset,
|
||||||
|
header.rootDirectoryLength,
|
||||||
|
header
|
||||||
|
);
|
||||||
|
assert.strictEqual(directory.length, 1);
|
||||||
|
assert.strictEqual(directory[0].tileId, 0);
|
||||||
|
assert.strictEqual(directory[0].offset, 0);
|
||||||
|
assert.strictEqual(directory[0].length, 69);
|
||||||
|
assert.strictEqual(directory[0].runLength, 1);
|
||||||
|
|
||||||
|
for (const v of cache.cache.values()) {
|
||||||
|
assert.ok(v.lastUsed > 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple sources in a single cache", async () => {
|
||||||
|
const cache = new SharedPromiseCache();
|
||||||
|
const source1 = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
const source2 = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"2"
|
||||||
|
);
|
||||||
|
await cache.getHeader(source1);
|
||||||
|
assert.strictEqual(cache.cache.size, 2);
|
||||||
|
await cache.getHeader(source2);
|
||||||
|
assert.strictEqual(cache.cache.size, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("etag change", async () => {
|
||||||
|
mockServer.reset();
|
||||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
await p.getZxy(0, 0, 0);
|
await p.getZxy(0, 0, 0);
|
||||||
assert.equal("no-store", mockserver.lastCache);
|
// header + tile
|
||||||
|
assert.strictEqual(2, mockServer.numRequests);
|
||||||
|
mockServer.etag = "etag_2";
|
||||||
|
await p.getZxy(0, 0, 0);
|
||||||
|
// tile + header again + tile
|
||||||
|
assert.strictEqual(5, mockServer.numRequests);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("weak etags", async () => {
|
||||||
|
mockServer.reset();
|
||||||
|
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
await p.getZxy(0, 0, 0);
|
||||||
|
// header + tile
|
||||||
|
assert.strictEqual(2, mockServer.numRequests);
|
||||||
|
mockServer.etag = "W/weak_etag";
|
||||||
|
await p.getZxy(0, 0, 0);
|
||||||
|
assert.strictEqual(3, mockServer.numRequests);
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle < 16384 bytes archive case
|
||||||
|
// handle DigitalOcean case returning 200 instead of 206
|
||||||
|
|
||||||
|
test("cache pruning by byte size", async () => {
|
||||||
|
const cache = new SharedPromiseCache(2);
|
||||||
|
cache.cache.set("0", { lastUsed: 0, data: Promise.resolve([]) });
|
||||||
|
cache.cache.set("1", { lastUsed: 1, data: Promise.resolve([]) });
|
||||||
|
cache.cache.set("2", { lastUsed: 2, data: Promise.resolve([]) });
|
||||||
|
cache.prune();
|
||||||
|
assert.strictEqual(cache.cache.size, 2);
|
||||||
|
assert.ok(cache.cache.get("2"));
|
||||||
|
assert.ok(cache.cache.get("1"));
|
||||||
|
assert.ok(!cache.cache.get("0"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pmtiles get metadata", async () => {
|
||||||
|
const source = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
const p = new PMTiles(source);
|
||||||
|
const metadata = await p.getMetadata();
|
||||||
|
assert.ok((metadata as { name: string }).name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_2.pmtiles
|
||||||
|
|
||||||
|
test("get file extension", async () => {
|
||||||
|
assert.equal("", tileTypeExt(TileType.Unknown));
|
||||||
|
assert.equal(".mvt", tileTypeExt(TileType.Mvt));
|
||||||
|
assert.equal(".png", tileTypeExt(TileType.Png));
|
||||||
|
assert.equal(".jpg", tileTypeExt(TileType.Jpeg));
|
||||||
|
assert.equal(".webp", tileTypeExt(TileType.Webp));
|
||||||
|
assert.equal(".avif", tileTypeExt(TileType.Avif));
|
||||||
|
});
|
||||||
|
|
||||||
|
interface TileJsonLike {
|
||||||
|
tilejson: string;
|
||||||
|
scheme: string;
|
||||||
|
tiles: string[];
|
||||||
|
description?: string;
|
||||||
|
name?: string;
|
||||||
|
attribution?: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("pmtiles get TileJSON", async () => {
|
||||||
|
const source = new TestNodeFileSource(
|
||||||
|
"test/data/test_fixture_1.pmtiles",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
const p = new PMTiles(source);
|
||||||
|
const tilejson = (await p.getTileJson(
|
||||||
|
"https://example.com/foo"
|
||||||
|
)) as TileJsonLike;
|
||||||
|
assert.equal("3.0.0", tilejson.tilejson);
|
||||||
|
assert.equal("xyz", tilejson.scheme);
|
||||||
|
assert.equal("https://example.com/foo/{z}/{x}/{y}.mvt", tilejson.tiles[0]);
|
||||||
|
assert.equal(undefined, tilejson.attribution);
|
||||||
|
assert.equal("test_fixture_1.pmtiles", tilejson.description);
|
||||||
|
assert.equal("test_fixture_1.pmtiles", tilejson.name);
|
||||||
|
assert.equal("2", tilejson.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalUserAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
describe("user agent", async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
Object.defineProperty(global, "navigator", {
|
||||||
|
value: { userAgent: "Windows Chrome" },
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(global, "navigator", {
|
||||||
|
value: undefined,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works around caching bug on chrome on windows", async () => {
|
||||||
|
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||||
|
await p.getZxy(0, 0, 0);
|
||||||
|
assert.equal("no-store", mockServer.lastCache);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user