maplibre-tilepackage-protocol 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -48
- package/dist/bundle.js +5657 -0
- package/dist/bundle.js.map +1 -0
- package/dist/index.esm.js +2060 -0
- package/dist/index.esm.js.map +1 -0
- package/eslint.config.mjs +28 -0
- package/package.json +42 -22
- package/rollup.config.mjs +45 -0
- package/src/default-decompress.js +11 -4
- package/src/get-header-and-filelist.js +42 -3
- package/src/index.js +175 -103
- package/src/maplibre-gl-js-protocol.js +28 -12
- package/src/shared-promise-cache.js +23 -0
- package/src/source.js +19 -4
- package/src/tilecutter/IMPLEMENTATION_PLAN.md +135 -0
- package/src/tilecutter/subdivide.js +247 -0
package/package.json
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "maplibre-tilepackage-protocol",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "Maplibre-gl-js protocol for reading VTPK and TPKX files",
|
|
5
|
-
"homepage": "https://github.com/spatialillusions/maplibre-tilepackage-protocol#readme",
|
|
6
|
-
"bugs": {
|
|
7
|
-
"url": "https://github.com/spatialillusions/maplibre-tilepackage-protocol/issues"
|
|
8
|
-
},
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/spatialillusions/maplibre-tilepackage-protocol.git"
|
|
12
|
-
},
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"author": "Måns Beckman",
|
|
15
|
-
"type": "module",
|
|
16
|
-
"main": "src/index.js",
|
|
17
|
-
"scripts": {
|
|
18
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "maplibre-tilepackage-protocol",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Maplibre-gl-js protocol for reading VTPK and TPKX files",
|
|
5
|
+
"homepage": "https://github.com/spatialillusions/maplibre-tilepackage-protocol#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/spatialillusions/maplibre-tilepackage-protocol/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/spatialillusions/maplibre-tilepackage-protocol.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Måns Beckman",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "src/index.js",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
19
|
+
"build": "rollup -c",
|
|
20
|
+
"build:esm": "rollup -c --configPlugin @rollup/plugin-node-resolve",
|
|
21
|
+
"demo": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mapbox/point-geometry": "^0.1.0",
|
|
25
|
+
"@mapbox/vector-tile": "^1.3.1",
|
|
26
|
+
"@maplibre/vt-pbf": "^4.1.0",
|
|
27
|
+
"fflate": "^0.8.2",
|
|
28
|
+
"maplibre-local-glyphs": "^1.2.0",
|
|
29
|
+
"pbf": "^3.2.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@rollup/plugin-commonjs": "^26.0.3",
|
|
33
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
34
|
+
"rollup": "^4.53.3"
|
|
35
|
+
},
|
|
36
|
+
"module": "dist/index.esm.js",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": "./dist/index.esm.js",
|
|
40
|
+
"require": "./src/index.js"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// rollup.config.mjs
|
|
2
|
+
// node resolves makes npm packages be included in the bundle
|
|
3
|
+
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
|
4
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
5
|
+
|
|
6
|
+
const externals = [
|
|
7
|
+
"@mapbox/vector-tile",
|
|
8
|
+
"pbf",
|
|
9
|
+
"@mapbox/point-geometry",
|
|
10
|
+
"@maplibre/vt-pbf",
|
|
11
|
+
"maplibre-local-glyphs",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function onwarn(warning) {
|
|
15
|
+
if (warning.code === "CIRCULAR_DEPENDENCY") return;
|
|
16
|
+
console.warn(warning.message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default [
|
|
20
|
+
// ESM library build (dependencies external)
|
|
21
|
+
{
|
|
22
|
+
input: "src/index.js",
|
|
23
|
+
output: {
|
|
24
|
+
file: "dist/index.esm.js",
|
|
25
|
+
format: "esm",
|
|
26
|
+
sourcemap: true,
|
|
27
|
+
},
|
|
28
|
+
// Keep selected externals; bundle fflate (omit from externals)
|
|
29
|
+
external: externals,
|
|
30
|
+
plugins: [nodeResolve({ browser: true }), commonjs()],
|
|
31
|
+
onwarn,
|
|
32
|
+
},
|
|
33
|
+
// IIFE demo build (fully bundled)
|
|
34
|
+
{
|
|
35
|
+
input: "src/index.js",
|
|
36
|
+
output: {
|
|
37
|
+
file: "dist/bundle.js",
|
|
38
|
+
format: "iife",
|
|
39
|
+
name: "TilePackageProtocol",
|
|
40
|
+
sourcemap: true,
|
|
41
|
+
},
|
|
42
|
+
plugins: [nodeResolve({ browser: true }), commonjs()],
|
|
43
|
+
onwarn,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
@@ -12,16 +12,23 @@ export default async function defaultDecompress(buf, compression) {
|
|
|
12
12
|
return buf;
|
|
13
13
|
}
|
|
14
14
|
if (compression === "gzip") {
|
|
15
|
-
|
|
15
|
+
// Safe global detection for DecompressionStream feature test
|
|
16
|
+
const g =
|
|
17
|
+
typeof globalThis !== "undefined"
|
|
18
|
+
? globalThis
|
|
19
|
+
: typeof window !== "undefined"
|
|
20
|
+
? window
|
|
21
|
+
: typeof self !== "undefined"
|
|
22
|
+
? self
|
|
23
|
+
: {};
|
|
24
|
+
if (typeof g.DecompressionStream === "undefined") {
|
|
16
25
|
return fflate.decompressSync(new Uint8Array(buf));
|
|
17
26
|
}
|
|
18
27
|
const stream = new Response(buf).body;
|
|
19
28
|
if (!stream) {
|
|
20
29
|
throw new Error("Failed to read response stream");
|
|
21
30
|
}
|
|
22
|
-
const result = stream.pipeThrough(
|
|
23
|
-
new globalThis.DecompressionStream("gzip"),
|
|
24
|
-
);
|
|
31
|
+
const result = stream.pipeThrough(new g.DecompressionStream("gzip"));
|
|
25
32
|
return new Response(result).arrayBuffer();
|
|
26
33
|
}
|
|
27
34
|
throw new Error("Compression method not supported");
|
|
@@ -59,6 +59,7 @@ export function jsonToHeader(json, files, etag) {
|
|
|
59
59
|
maxLon: Number(json.extent.xmax),
|
|
60
60
|
maxLat: Number(json.extent.ymax),
|
|
61
61
|
files: files,
|
|
62
|
+
coverageMap: json.coverageMap,
|
|
62
63
|
etag: etag,
|
|
63
64
|
};
|
|
64
65
|
}
|
|
@@ -239,11 +240,49 @@ export default async function getHeaderAndFileList(source) {
|
|
|
239
240
|
root.metadataOffset = metadataOffset;
|
|
240
241
|
root.metadataLength = tilePackageFiles["p12/metadata.json"].size;
|
|
241
242
|
}
|
|
243
|
+
|
|
244
|
+
async function calculateCoverage(z, x, y, tilemap, coverageMap) {
|
|
245
|
+
z = z + 1;
|
|
246
|
+
x = x * 2;
|
|
247
|
+
y = y * 2;
|
|
248
|
+
if (coverageMap[z] == undefined) coverageMap[z] = {};
|
|
249
|
+
if (coverageMap[z][x] == undefined) coverageMap[z][x] = {};
|
|
250
|
+
if (coverageMap[z][x + 1] == undefined) coverageMap[z][x + 1] = {};
|
|
251
|
+
coverageMap[z][x][y] = tilemap[0];
|
|
252
|
+
if (isNaN(tilemap[0])) {
|
|
253
|
+
calculateCoverage(z, x, y, tilemap[0], coverageMap);
|
|
254
|
+
}
|
|
255
|
+
coverageMap[z][x + 1][y] = tilemap[1];
|
|
256
|
+
if (isNaN(tilemap[1])) {
|
|
257
|
+
calculateCoverage(z, x + 1, y, tilemap[1], coverageMap);
|
|
258
|
+
}
|
|
259
|
+
coverageMap[z][x][y + 1] = tilemap[2];
|
|
260
|
+
if (isNaN(tilemap[2])) {
|
|
261
|
+
calculateCoverage(z, x, y + 1, tilemap[2], coverageMap);
|
|
262
|
+
}
|
|
263
|
+
coverageMap[z][x + 1][y + 1] = tilemap[3];
|
|
264
|
+
if (isNaN(tilemap[3])) {
|
|
265
|
+
calculateCoverage(z, x + 1, y + 1, tilemap[3], coverageMap);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Get and calculate the tilemap
|
|
269
|
+
if (source.coverageCheck > 0 && tilePackageFiles["p12/tilemap/root.json"]) {
|
|
270
|
+
const tilemap = await getJsonFromFile(
|
|
271
|
+
"p12/tilemap/root.json",
|
|
272
|
+
tilePackageFiles,
|
|
273
|
+
source,
|
|
274
|
+
);
|
|
275
|
+
const coverageMap = {};
|
|
276
|
+
coverageMap[0] = {};
|
|
277
|
+
coverageMap[0][0] = {};
|
|
278
|
+
coverageMap[0][0][0] = "blob";
|
|
279
|
+
calculateCoverage(0, 0, 0, tilemap.index, coverageMap);
|
|
280
|
+
root.coverageMap = coverageMap;
|
|
281
|
+
} else {
|
|
282
|
+
// Coverage map unavailable (either disabled or missing tilemap/root.json)
|
|
283
|
+
}
|
|
242
284
|
}
|
|
243
|
-
//TODO read iteminfo.xml....
|
|
244
285
|
|
|
245
286
|
const header = jsonToHeader(root, tilePackageFiles, resp.etag);
|
|
246
|
-
|
|
247
|
-
//return [header, [dirKey, rootDir.length, rootDir]];
|
|
248
287
|
return [header, ""];
|
|
249
288
|
}
|
package/src/index.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
export * from "./maplibre-gl-js-protocol.js";
|
|
2
2
|
export * from "./source.js";
|
|
3
3
|
import { FetchSource } from "./source.js";
|
|
4
|
+
import subdivideVectorTile from "./tilecutter/subdivide.js";
|
|
4
5
|
import defaultDecompress from "./default-decompress.js";
|
|
5
6
|
import getJsonFromFile from "./get-json-from-file.js";
|
|
6
7
|
import SharedPromiseCache from "./shared-promise-cache.js";
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Escape literal newlines in JSON text.
|
|
11
|
+
* Replaces actual newline characters with the JSON escape sequence.
|
|
12
|
+
*/
|
|
13
|
+
function escapeJsonNewlines(text) {
|
|
14
|
+
// Replace literal newlines with JSON escape sequences
|
|
15
|
+
// This handles \r\n, \n, and \r
|
|
16
|
+
return text.replace(/\r\n/g, "\\n").replace(/[\r\n]/g, "\\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
function calculateFilename(z, x, y, header) {
|
|
9
20
|
const zoom = z.toString().padStart(2, "0");
|
|
10
21
|
const baseRow = Math.floor(y / 128) * 128;
|
|
11
22
|
const baseCol = Math.floor(x / 128) * 128;
|
|
12
23
|
const rowHex = baseRow.toString(16).padStart(4, "0");
|
|
13
24
|
const colHex = baseCol.toString(16).padStart(4, "0");
|
|
14
|
-
|
|
15
|
-
if (header.type === "tpkx") {
|
|
16
|
-
basePath = `tile`;
|
|
17
|
-
} else {
|
|
18
|
-
basePath = `p12/tile`;
|
|
19
|
-
}
|
|
25
|
+
const basePath = header.type === "tpkx" ? "tile" : "p12/tile";
|
|
20
26
|
return `${basePath}/L${zoom}/R${rowHex}C${colHex}.bundle`;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -26,10 +32,6 @@ function getTileInfoFromTileIndex(tileIndex, z, x, y) {
|
|
|
26
32
|
return tileIndex[row][col];
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
/**
|
|
30
|
-
* Error thrown when a response for TilePackage over HTTP does not match previous, cached parts of the archive.
|
|
31
|
-
* The default TilePackage implementation will catch this error once internally and retry a request.
|
|
32
|
-
*/
|
|
33
35
|
class EtagMismatch extends Error {
|
|
34
36
|
constructor(message) {
|
|
35
37
|
super(message);
|
|
@@ -38,24 +40,29 @@ class EtagMismatch extends Error {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export class TilePackage {
|
|
41
|
-
constructor(source,
|
|
43
|
+
constructor(source, options) {
|
|
44
|
+
// Default coverageCheck enabled; disable with coverageCheck:false
|
|
45
|
+
this.coverageCheck = 1;
|
|
46
|
+
if (options && options.coverageCheck === false) this.coverageCheck = 0;
|
|
47
|
+
// Max dz guard (skip subdivision beyond this): default 8
|
|
48
|
+
this.maxDz =
|
|
49
|
+
options && typeof options.maxDz === "number" ? options.maxDz : 8;
|
|
42
50
|
if (typeof source === "string") {
|
|
43
|
-
|
|
51
|
+
const opts = this.coverageCheck
|
|
52
|
+
? { coverageCheck: this.coverageCheck }
|
|
53
|
+
: undefined;
|
|
54
|
+
this.source = new FetchSource(source, opts);
|
|
44
55
|
} else {
|
|
45
56
|
this.source = source;
|
|
57
|
+
// Propagate default coverageCheck to existing Source instances if undefined
|
|
58
|
+
if (this.source && this.source.coverageCheck === undefined) {
|
|
59
|
+
this.source.coverageCheck = this.coverageCheck;
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.decompress = defaultDecompress;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (cache) {
|
|
55
|
-
this.cache = cache;
|
|
56
|
-
} else {
|
|
57
|
-
this.cache = new SharedPromiseCache();
|
|
58
|
-
}
|
|
62
|
+
this.decompress =
|
|
63
|
+
options && options.decompress ? options.decompress : defaultDecompress;
|
|
64
|
+
this.cache =
|
|
65
|
+
options && options.cache ? options.cache : new SharedPromiseCache();
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
async getHeader() {
|
|
@@ -63,42 +70,99 @@ export class TilePackage {
|
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
async getZxyAttempt(z, x, y, signal) {
|
|
66
|
-
//y = (1 << z) - 1 - y; //TMS vs XYZ
|
|
67
73
|
const header = await this.cache.getHeader(this.source);
|
|
68
|
-
if (z < header.minZoom || z > header.maxZoom)
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
74
|
+
if (z < header.minZoom || z > header.maxZoom) return undefined;
|
|
71
75
|
const file = calculateFilename(z, x, y, header);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
header,
|
|
79
|
-
signal,
|
|
80
|
-
);
|
|
81
|
-
const tileInfo = getTileInfoFromTileIndex(tileIndex, z, x, y);
|
|
82
|
-
if (tileInfo && tileInfo.tileSize > 0) {
|
|
83
|
-
const tileOffset =
|
|
84
|
-
header.files[file].absoluteOffset + tileInfo.tileOffset;
|
|
85
|
-
const resp = await this.source.getBytes(
|
|
86
|
-
tileOffset,
|
|
87
|
-
tileInfo.tileSize,
|
|
76
|
+
let tileInfo = null;
|
|
77
|
+
if (header.files[file]) {
|
|
78
|
+
const tileIndex = await this.cache.getTileIndex(
|
|
79
|
+
this.source,
|
|
80
|
+
file,
|
|
81
|
+
header,
|
|
88
82
|
signal,
|
|
89
|
-
header.etag,
|
|
90
83
|
);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
84
|
+
tileInfo = getTileInfoFromTileIndex(tileIndex, z, x, y);
|
|
85
|
+
if (tileInfo && tileInfo.tileSize > 0) {
|
|
86
|
+
const tileOffset =
|
|
87
|
+
header.files[file].absoluteOffset + tileInfo.tileOffset;
|
|
88
|
+
const resp = await this.source.getBytes(
|
|
89
|
+
tileOffset,
|
|
90
|
+
tileInfo.tileSize,
|
|
91
|
+
signal,
|
|
92
|
+
header.etag,
|
|
93
|
+
);
|
|
94
|
+
const data = await this.decompress(resp.data, header.tileCompression);
|
|
95
|
+
return { data, cacheControl: resp.cacheControl, expires: resp.expires };
|
|
96
|
+
}
|
|
101
97
|
}
|
|
98
|
+
if (header.packageType === "vtpk" && header.coverageMap) {
|
|
99
|
+
// Ascend to find parent with value 1
|
|
100
|
+
let pz = z,
|
|
101
|
+
px = x,
|
|
102
|
+
py = y,
|
|
103
|
+
foundParent = null;
|
|
104
|
+
while (pz > header.minZoom) {
|
|
105
|
+
pz -= 1;
|
|
106
|
+
px = Math.floor(px / 2);
|
|
107
|
+
py = Math.floor(py / 2);
|
|
108
|
+
const level = header.coverageMap[pz];
|
|
109
|
+
const val = level && level[px] ? level[px][py] : undefined;
|
|
110
|
+
if (val === 1) {
|
|
111
|
+
foundParent = { pz, px, py };
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (foundParent) {
|
|
116
|
+
const parent = await this.getZxyAttempt(
|
|
117
|
+
foundParent.pz,
|
|
118
|
+
foundParent.px,
|
|
119
|
+
foundParent.py,
|
|
120
|
+
signal,
|
|
121
|
+
);
|
|
122
|
+
if (parent && parent.data) {
|
|
123
|
+
const cached = this.cache.getSubdivided(this.source, z, x, y);
|
|
124
|
+
if (cached) {
|
|
125
|
+
return {
|
|
126
|
+
data: cached,
|
|
127
|
+
cacheControl: parent.cacheControl,
|
|
128
|
+
expires: parent.expires,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const dz = z - foundParent.pz;
|
|
132
|
+
if (dz > this.maxDz) {
|
|
133
|
+
return undefined; // exceed hard cap
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const { data: overzoomed } = subdivideVectorTile(
|
|
137
|
+
parent.data,
|
|
138
|
+
foundParent.pz,
|
|
139
|
+
foundParent.px,
|
|
140
|
+
foundParent.py,
|
|
141
|
+
z,
|
|
142
|
+
x,
|
|
143
|
+
y,
|
|
144
|
+
{ buffer: 128, maxDzWarn: this.maxDz - 1 },
|
|
145
|
+
);
|
|
146
|
+
this.cache.setSubdivided(this.source, z, x, y, overzoomed);
|
|
147
|
+
return {
|
|
148
|
+
data: overzoomed,
|
|
149
|
+
cacheControl: parent.cacheControl,
|
|
150
|
+
expires: parent.expires,
|
|
151
|
+
};
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.warn(
|
|
154
|
+
`[tilepackage subdivide] failed for z=${z} x=${x} y=${y}:`,
|
|
155
|
+
e,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
console.warn(
|
|
160
|
+
`[tilepackage subdivide] parent tile missing at z=${foundParent.pz} x=${foundParent.px} y=${foundParent.py}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
102
166
|
}
|
|
103
167
|
|
|
104
168
|
async getZxy(z, x, y, signal) {
|
|
@@ -115,21 +179,21 @@ export class TilePackage {
|
|
|
115
179
|
|
|
116
180
|
async getMetadataAttempt() {
|
|
117
181
|
const header = await this.cache.getHeader(this.source);
|
|
118
|
-
|
|
119
182
|
let metadata = {};
|
|
120
183
|
if (header.packageType === "vtpk") {
|
|
121
184
|
const resp = await this.source.getBytes(
|
|
122
|
-
header.
|
|
123
|
-
header.
|
|
185
|
+
header.metadataOffset,
|
|
186
|
+
header.metadataLength,
|
|
124
187
|
undefined,
|
|
125
188
|
header.etag,
|
|
126
189
|
);
|
|
127
|
-
|
|
128
190
|
const decoder = new TextDecoder("utf-8");
|
|
129
|
-
|
|
191
|
+
const jsonText = decoder.decode(resp.data);
|
|
192
|
+
console.log("test", jsonText);
|
|
193
|
+
console.log("escaped text", escapeJsonNewlines(jsonText));
|
|
194
|
+
metadata = JSON.parse(escapeJsonNewlines(jsonText));
|
|
130
195
|
}
|
|
131
196
|
metadata.name = header.name;
|
|
132
|
-
|
|
133
197
|
return metadata;
|
|
134
198
|
}
|
|
135
199
|
|
|
@@ -147,10 +211,7 @@ export class TilePackage {
|
|
|
147
211
|
|
|
148
212
|
async getResourceAttempt(file, signal) {
|
|
149
213
|
const header = await this.cache.getHeader(this.source);
|
|
150
|
-
if (!header.files[file])
|
|
151
|
-
return undefined;
|
|
152
|
-
}
|
|
153
|
-
|
|
214
|
+
if (!header.files[file]) return undefined;
|
|
154
215
|
const resource = await this.cache.getResource(
|
|
155
216
|
this.source,
|
|
156
217
|
file,
|
|
@@ -180,37 +241,59 @@ export class TilePackage {
|
|
|
180
241
|
const header = await this.cache.getHeader(this.source);
|
|
181
242
|
const sourceKey = this.source.getKey();
|
|
182
243
|
if (header.packageType === "vtpk") {
|
|
244
|
+
const metadata = await getJsonFromFile(
|
|
245
|
+
"p12/root.json",
|
|
246
|
+
header.files,
|
|
247
|
+
this.source,
|
|
248
|
+
);
|
|
183
249
|
const style = await getJsonFromFile(
|
|
184
250
|
"p12/resources/styles/root.json",
|
|
185
251
|
header.files,
|
|
186
252
|
this.source,
|
|
187
253
|
);
|
|
188
|
-
style.sources.esri
|
|
189
|
-
|
|
254
|
+
if (style.sources && style.sources.esri) {
|
|
255
|
+
delete style.sources.esri.url;
|
|
256
|
+
style.sources.esri.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
|
|
257
|
+
style.sources.esri.type = style.sources.esri.type || "vector";
|
|
258
|
+
style.sources.esri.minzoom = header.minZoom || 0;
|
|
259
|
+
style.sources.esri.maxzoom = header.maxZoom || 22;
|
|
260
|
+
}
|
|
261
|
+
for (const k in style.sources) {
|
|
262
|
+
const src = style.sources[k];
|
|
263
|
+
if (
|
|
264
|
+
src &&
|
|
265
|
+
typeof src.url === "string" &&
|
|
266
|
+
src.url.startsWith("tilepackage://")
|
|
267
|
+
) {
|
|
268
|
+
delete src.url;
|
|
269
|
+
if (!src.tiles)
|
|
270
|
+
src.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (metadata.copyrightText)
|
|
274
|
+
style.sources.esri.attribution = metadata.copyrightText;
|
|
190
275
|
style.glyphs = `tilepackage://${sourceKey}/{fontstack}/{range}`;
|
|
191
276
|
style.sprite = `tilepackage://${sourceKey}/sprite`;
|
|
192
|
-
|
|
277
|
+
if (this.debug)
|
|
278
|
+
console.debug(
|
|
279
|
+
"[tilepackage style] rewritten esri source",
|
|
280
|
+
style.sources.esri,
|
|
281
|
+
);
|
|
193
282
|
return style;
|
|
194
|
-
} else {
|
|
195
|
-
return {
|
|
196
|
-
version: 8,
|
|
197
|
-
sources: {
|
|
198
|
-
esri: {
|
|
199
|
-
type: "raster",
|
|
200
|
-
tileSize: header.tileSize || 256,
|
|
201
|
-
url: `"tilepackage://${sourceKey}`,
|
|
202
|
-
maxzoom: header.maxZoom || 22,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
layers: [
|
|
206
|
-
{
|
|
207
|
-
id: "tilepackageraster",
|
|
208
|
-
type: "raster",
|
|
209
|
-
source: "esri",
|
|
210
|
-
},
|
|
211
|
-
],
|
|
212
|
-
};
|
|
213
283
|
}
|
|
284
|
+
return {
|
|
285
|
+
version: 8,
|
|
286
|
+
sources: {
|
|
287
|
+
esri: {
|
|
288
|
+
type: "raster",
|
|
289
|
+
tileSize: header.tileSize || 256,
|
|
290
|
+
tiles: [`tilepackage://${sourceKey}/{z}/{x}/{y}`],
|
|
291
|
+
maxzoom: header.maxZoom || 22,
|
|
292
|
+
minzoom: header.minZoom || 0,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
layers: [{ id: "tilepackageraster", type: "raster", source: "esri" }],
|
|
296
|
+
};
|
|
214
297
|
}
|
|
215
298
|
|
|
216
299
|
async getStyle() {
|
|
@@ -229,7 +312,6 @@ export class TilePackage {
|
|
|
229
312
|
const header = await this.getHeader();
|
|
230
313
|
const metadata = await this.getMetadata();
|
|
231
314
|
const ext = header.tileType;
|
|
232
|
-
|
|
233
315
|
const tileJson = {
|
|
234
316
|
tilejson: "3.0.0",
|
|
235
317
|
scheme: "xyz",
|
|
@@ -238,21 +320,11 @@ export class TilePackage {
|
|
|
238
320
|
version: header.version,
|
|
239
321
|
bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat],
|
|
240
322
|
};
|
|
241
|
-
if (metadata.vector_layers)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
if (metadata.description) {
|
|
248
|
-
tileJson.description = metadata.description;
|
|
249
|
-
}
|
|
250
|
-
if (header.minZoom) {
|
|
251
|
-
tileJson.minzoom = header.minZoom;
|
|
252
|
-
}
|
|
253
|
-
if (header.maxZoom) {
|
|
254
|
-
tileJson.maxzoom = header.maxZoom;
|
|
255
|
-
}
|
|
323
|
+
if (metadata.vector_layers) tileJson.vector_layers = metadata.vector_layers;
|
|
324
|
+
if (metadata.attribution) tileJson.attribution = metadata.attribution;
|
|
325
|
+
if (metadata.description) tileJson.description = metadata.description;
|
|
326
|
+
if (header.minZoom) tileJson.minzoom = header.minZoom;
|
|
327
|
+
if (header.maxZoom) tileJson.maxzoom = header.maxZoom;
|
|
256
328
|
return tileJson;
|
|
257
329
|
}
|
|
258
330
|
}
|
|
@@ -52,6 +52,7 @@ export class Protocol {
|
|
|
52
52
|
this.errorOnMissingTile = options
|
|
53
53
|
? options.errorOnMissingTile || false
|
|
54
54
|
: false;
|
|
55
|
+
this.debug = options ? options.debug || false : false;
|
|
55
56
|
this.getData = async (params, abortController) => {
|
|
56
57
|
if (params.type === "json") {
|
|
57
58
|
let tilePackageUrl = params.url.substr(14); // TODO fix this to be more robust
|
|
@@ -91,9 +92,10 @@ export class Protocol {
|
|
|
91
92
|
}
|
|
92
93
|
// This is a request for a tile json
|
|
93
94
|
if (this.metadata) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
const tj = await instance.getTileJson(params.url);
|
|
96
|
+
if (this.debug)
|
|
97
|
+
console.debug("[tilepackage] tilejson (metadata)", tj);
|
|
98
|
+
return { data: tj };
|
|
97
99
|
}
|
|
98
100
|
const h = await instance.getHeader();
|
|
99
101
|
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
|
|
@@ -102,14 +104,15 @@ export class Protocol {
|
|
|
102
104
|
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat],
|
|
111
|
-
},
|
|
107
|
+
const synthesized = {
|
|
108
|
+
tiles: [`${params.url}/{z}/{x}/{y}`],
|
|
109
|
+
minzoom: h.minZoom,
|
|
110
|
+
maxzoom: h.maxZoom,
|
|
111
|
+
bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat],
|
|
112
112
|
};
|
|
113
|
+
if (this.debug)
|
|
114
|
+
console.debug("[tilepackage] tilejson (synthesized)", synthesized);
|
|
115
|
+
return { data: synthesized };
|
|
113
116
|
//}
|
|
114
117
|
}
|
|
115
118
|
// TODO handle other paths ???
|
|
@@ -197,6 +200,13 @@ export class Protocol {
|
|
|
197
200
|
const y = result[4];
|
|
198
201
|
const header = await instance.getHeader();
|
|
199
202
|
const resp = await instance.getZxy(+z, +x, +y, abortController.signal);
|
|
203
|
+
if (this.debug)
|
|
204
|
+
console.debug("[tilepackage] tile fetch", {
|
|
205
|
+
z: +z,
|
|
206
|
+
x: +x,
|
|
207
|
+
y: +y,
|
|
208
|
+
found: !!resp,
|
|
209
|
+
});
|
|
200
210
|
if (resp) {
|
|
201
211
|
return {
|
|
202
212
|
data: new Uint8Array(resp.data),
|
|
@@ -206,10 +216,13 @@ export class Protocol {
|
|
|
206
216
|
}
|
|
207
217
|
if (header.tileType === "pbf") {
|
|
208
218
|
if (this.errorOnMissingTile) {
|
|
219
|
+
//*
|
|
209
220
|
const e = new Error(
|
|
210
|
-
|
|
211
|
-
);
|
|
221
|
+
`Tile [${z},${x},${y}] not found in Tile Package, normal for VTPK with variable depth.`,
|
|
222
|
+
);
|
|
212
223
|
e.name = "TileError";
|
|
224
|
+
if (this.debug)
|
|
225
|
+
console.debug("[tilepackage] missing tile", { z, x, y });
|
|
213
226
|
throw e;
|
|
214
227
|
}
|
|
215
228
|
return { data: new Uint8Array() };
|
|
@@ -229,12 +242,15 @@ export class Protocol {
|
|
|
229
242
|
*/
|
|
230
243
|
add(p) {
|
|
231
244
|
this.tiles.set(p.source.getKey(), p);
|
|
245
|
+
if (this.debug)
|
|
246
|
+
console.debug("[tilepackage] add instance", p.source.getKey());
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
/**
|
|
235
250
|
* Fetch a {@link TilePackage} instance by URL, for remote TilePackage instances.
|
|
236
251
|
*/
|
|
237
252
|
get(url) {
|
|
253
|
+
if (this.debug) console.debug("[tilepackage] get instance", url);
|
|
238
254
|
return this.tiles.get(url);
|
|
239
255
|
}
|
|
240
256
|
}
|