maplibre-tilepackage-protocol 1.0.0 → 1.2.0
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 +114 -45
- package/dist/bundle.js +5628 -0
- package/dist/bundle.js.map +1 -0
- package/dist/index.esm.js +2031 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +43 -20
- package/rollup.config.mjs +45 -0
- package/src/get-header-and-filelist.js +42 -3
- package/src/index.js +159 -100
- package/src/maplibre-gl-js-protocol.js +29 -13
- package/src/shared-promise-cache.js +23 -0
- package/src/source.js +8 -2
- package/src/tilecutter/IMPLEMENTATION_PLAN.md +135 -0
- package/src/tilecutter/subdivide.js +247 -0
|
@@ -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
|
+
];
|
|
@@ -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,6 +1,7 @@
|
|
|
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";
|
|
@@ -11,12 +12,7 @@ function calculateFilename(z, x, y, header) {
|
|
|
11
12
|
const baseCol = Math.floor(x / 128) * 128;
|
|
12
13
|
const rowHex = baseRow.toString(16).padStart(4, "0");
|
|
13
14
|
const colHex = baseCol.toString(16).padStart(4, "0");
|
|
14
|
-
let basePath;
|
|
15
|
-
if (header.type === "tpkx") {
|
|
16
|
-
basePath = `tile`;
|
|
17
|
-
} else {
|
|
18
|
-
basePath = `p12/tile`;
|
|
19
|
-
}
|
|
15
|
+
let basePath = header.type === "tpkx" ? "tile" : "p12/tile";
|
|
20
16
|
return `${basePath}/L${zoom}/R${rowHex}C${colHex}.bundle`;
|
|
21
17
|
}
|
|
22
18
|
|
|
@@ -26,10 +22,6 @@ function getTileInfoFromTileIndex(tileIndex, z, x, y) {
|
|
|
26
22
|
return tileIndex[row][col];
|
|
27
23
|
}
|
|
28
24
|
|
|
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
25
|
class EtagMismatch extends Error {
|
|
34
26
|
constructor(message) {
|
|
35
27
|
super(message);
|
|
@@ -38,24 +30,29 @@ class EtagMismatch extends Error {
|
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
export class TilePackage {
|
|
41
|
-
constructor(source,
|
|
33
|
+
constructor(source, options) {
|
|
34
|
+
// Default coverageCheck enabled; disable with coverageCheck:false
|
|
35
|
+
this.coverageCheck = 1;
|
|
36
|
+
if (options && options.coverageCheck === false) this.coverageCheck = 0;
|
|
37
|
+
// Max dz guard (skip subdivision beyond this): default 8
|
|
38
|
+
this.maxDz =
|
|
39
|
+
options && typeof options.maxDz === "number" ? options.maxDz : 8;
|
|
42
40
|
if (typeof source === "string") {
|
|
43
|
-
|
|
41
|
+
const opts = this.coverageCheck
|
|
42
|
+
? { coverageCheck: this.coverageCheck }
|
|
43
|
+
: undefined;
|
|
44
|
+
this.source = new FetchSource(source, opts);
|
|
44
45
|
} else {
|
|
45
46
|
this.source = source;
|
|
47
|
+
// Propagate default coverageCheck to existing Source instances if undefined
|
|
48
|
+
if (this.source && this.source.coverageCheck === undefined) {
|
|
49
|
+
this.source.coverageCheck = this.coverageCheck;
|
|
50
|
+
}
|
|
46
51
|
}
|
|
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
|
-
}
|
|
52
|
+
this.decompress =
|
|
53
|
+
options && options.decompress ? options.decompress : defaultDecompress;
|
|
54
|
+
this.cache =
|
|
55
|
+
options && options.cache ? options.cache : new SharedPromiseCache();
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
async getHeader() {
|
|
@@ -63,42 +60,99 @@ export class TilePackage {
|
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
async getZxyAttempt(z, x, y, signal) {
|
|
66
|
-
//y = (1 << z) - 1 - y; //TMS vs XYZ
|
|
67
63
|
const header = await this.cache.getHeader(this.source);
|
|
68
|
-
if (z < header.minZoom || z > header.maxZoom)
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
64
|
+
if (z < header.minZoom || z > header.maxZoom) return undefined;
|
|
71
65
|
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,
|
|
66
|
+
let tileInfo = null;
|
|
67
|
+
if (header.files[file]) {
|
|
68
|
+
const tileIndex = await this.cache.getTileIndex(
|
|
69
|
+
this.source,
|
|
70
|
+
file,
|
|
71
|
+
header,
|
|
88
72
|
signal,
|
|
89
|
-
header.etag,
|
|
90
73
|
);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
74
|
+
tileInfo = getTileInfoFromTileIndex(tileIndex, z, x, y);
|
|
75
|
+
if (tileInfo && tileInfo.tileSize > 0) {
|
|
76
|
+
const tileOffset =
|
|
77
|
+
header.files[file].absoluteOffset + tileInfo.tileOffset;
|
|
78
|
+
const resp = await this.source.getBytes(
|
|
79
|
+
tileOffset,
|
|
80
|
+
tileInfo.tileSize,
|
|
81
|
+
signal,
|
|
82
|
+
header.etag,
|
|
83
|
+
);
|
|
84
|
+
const data = await this.decompress(resp.data, header.tileCompression);
|
|
85
|
+
return { data, cacheControl: resp.cacheControl, expires: resp.expires };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (header.packageType === "vtpk" && header.coverageMap) {
|
|
89
|
+
// Ascend to find parent with value 1
|
|
90
|
+
let pz = z,
|
|
91
|
+
px = x,
|
|
92
|
+
py = y,
|
|
93
|
+
foundParent = null;
|
|
94
|
+
while (pz > header.minZoom) {
|
|
95
|
+
pz -= 1;
|
|
96
|
+
px = Math.floor(px / 2);
|
|
97
|
+
py = Math.floor(py / 2);
|
|
98
|
+
const level = header.coverageMap[pz];
|
|
99
|
+
const val = level && level[px] ? level[px][py] : undefined;
|
|
100
|
+
if (val === 1) {
|
|
101
|
+
foundParent = { pz, px, py };
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (foundParent) {
|
|
106
|
+
const parent = await this.getZxyAttempt(
|
|
107
|
+
foundParent.pz,
|
|
108
|
+
foundParent.px,
|
|
109
|
+
foundParent.py,
|
|
110
|
+
signal,
|
|
111
|
+
);
|
|
112
|
+
if (parent && parent.data) {
|
|
113
|
+
const cached = this.cache.getSubdivided(this.source, z, x, y);
|
|
114
|
+
if (cached) {
|
|
115
|
+
return {
|
|
116
|
+
data: cached,
|
|
117
|
+
cacheControl: parent.cacheControl,
|
|
118
|
+
expires: parent.expires,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const dz = z - foundParent.pz;
|
|
122
|
+
if (dz > this.maxDz) {
|
|
123
|
+
return undefined; // exceed hard cap
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const { data: overzoomed } = subdivideVectorTile(
|
|
127
|
+
parent.data,
|
|
128
|
+
foundParent.pz,
|
|
129
|
+
foundParent.px,
|
|
130
|
+
foundParent.py,
|
|
131
|
+
z,
|
|
132
|
+
x,
|
|
133
|
+
y,
|
|
134
|
+
{ buffer: 128, maxDzWarn: this.maxDz - 1 },
|
|
135
|
+
);
|
|
136
|
+
this.cache.setSubdivided(this.source, z, x, y, overzoomed);
|
|
137
|
+
return {
|
|
138
|
+
data: overzoomed,
|
|
139
|
+
cacheControl: parent.cacheControl,
|
|
140
|
+
expires: parent.expires,
|
|
141
|
+
};
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`[tilepackage subdivide] failed for z=${z} x=${x} y=${y}:`,
|
|
145
|
+
e,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
console.warn(
|
|
150
|
+
`[tilepackage subdivide] parent tile missing at z=${foundParent.pz} x=${foundParent.px} y=${foundParent.py}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
101
154
|
}
|
|
155
|
+
return undefined;
|
|
102
156
|
}
|
|
103
157
|
|
|
104
158
|
async getZxy(z, x, y, signal) {
|
|
@@ -115,7 +169,6 @@ export class TilePackage {
|
|
|
115
169
|
|
|
116
170
|
async getMetadataAttempt() {
|
|
117
171
|
const header = await this.cache.getHeader(this.source);
|
|
118
|
-
|
|
119
172
|
let metadata = {};
|
|
120
173
|
if (header.packageType === "vtpk") {
|
|
121
174
|
const resp = await this.source.getBytes(
|
|
@@ -124,12 +177,10 @@ export class TilePackage {
|
|
|
124
177
|
undefined,
|
|
125
178
|
header.etag,
|
|
126
179
|
);
|
|
127
|
-
|
|
128
180
|
const decoder = new TextDecoder("utf-8");
|
|
129
181
|
metadata = JSON.parse(decoder.decode(resp.data));
|
|
130
182
|
}
|
|
131
183
|
metadata.name = header.name;
|
|
132
|
-
|
|
133
184
|
return metadata;
|
|
134
185
|
}
|
|
135
186
|
|
|
@@ -147,10 +198,7 @@ export class TilePackage {
|
|
|
147
198
|
|
|
148
199
|
async getResourceAttempt(file, signal) {
|
|
149
200
|
const header = await this.cache.getHeader(this.source);
|
|
150
|
-
if (!header.files[file])
|
|
151
|
-
return undefined;
|
|
152
|
-
}
|
|
153
|
-
|
|
201
|
+
if (!header.files[file]) return undefined;
|
|
154
202
|
const resource = await this.cache.getResource(
|
|
155
203
|
this.source,
|
|
156
204
|
file,
|
|
@@ -180,37 +228,59 @@ export class TilePackage {
|
|
|
180
228
|
const header = await this.cache.getHeader(this.source);
|
|
181
229
|
const sourceKey = this.source.getKey();
|
|
182
230
|
if (header.packageType === "vtpk") {
|
|
231
|
+
const metadata = await getJsonFromFile(
|
|
232
|
+
"p12/root.json",
|
|
233
|
+
header.files,
|
|
234
|
+
this.source,
|
|
235
|
+
);
|
|
183
236
|
const style = await getJsonFromFile(
|
|
184
237
|
"p12/resources/styles/root.json",
|
|
185
238
|
header.files,
|
|
186
239
|
this.source,
|
|
187
240
|
);
|
|
188
|
-
style.sources.esri
|
|
189
|
-
|
|
241
|
+
if (style.sources && style.sources.esri) {
|
|
242
|
+
delete style.sources.esri.url;
|
|
243
|
+
style.sources.esri.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
|
|
244
|
+
style.sources.esri.type = style.sources.esri.type || "vector";
|
|
245
|
+
style.sources.esri.minzoom = header.minZoom || 0;
|
|
246
|
+
style.sources.esri.maxzoom = header.maxZoom || 22;
|
|
247
|
+
}
|
|
248
|
+
for (const k in style.sources) {
|
|
249
|
+
const src = style.sources[k];
|
|
250
|
+
if (
|
|
251
|
+
src &&
|
|
252
|
+
typeof src.url === "string" &&
|
|
253
|
+
src.url.startsWith("tilepackage://")
|
|
254
|
+
) {
|
|
255
|
+
delete src.url;
|
|
256
|
+
if (!src.tiles)
|
|
257
|
+
src.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (metadata.copyrightText)
|
|
261
|
+
style.sources.esri.attribution = metadata.copyrightText;
|
|
190
262
|
style.glyphs = `tilepackage://${sourceKey}/{fontstack}/{range}`;
|
|
191
263
|
style.sprite = `tilepackage://${sourceKey}/sprite`;
|
|
192
|
-
|
|
264
|
+
if (this.debug)
|
|
265
|
+
console.debug(
|
|
266
|
+
"[tilepackage style] rewritten esri source",
|
|
267
|
+
style.sources.esri,
|
|
268
|
+
);
|
|
193
269
|
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
270
|
}
|
|
271
|
+
return {
|
|
272
|
+
version: 8,
|
|
273
|
+
sources: {
|
|
274
|
+
esri: {
|
|
275
|
+
type: "raster",
|
|
276
|
+
tileSize: header.tileSize || 256,
|
|
277
|
+
tiles: [`tilepackage://${sourceKey}/{z}/{x}/{y}`],
|
|
278
|
+
maxzoom: header.maxZoom || 22,
|
|
279
|
+
minzoom: header.minZoom || 0,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
layers: [{ id: "tilepackageraster", type: "raster", source: "esri" }],
|
|
283
|
+
};
|
|
214
284
|
}
|
|
215
285
|
|
|
216
286
|
async getStyle() {
|
|
@@ -229,7 +299,6 @@ export class TilePackage {
|
|
|
229
299
|
const header = await this.getHeader();
|
|
230
300
|
const metadata = await this.getMetadata();
|
|
231
301
|
const ext = header.tileType;
|
|
232
|
-
|
|
233
302
|
const tileJson = {
|
|
234
303
|
tilejson: "3.0.0",
|
|
235
304
|
scheme: "xyz",
|
|
@@ -238,21 +307,11 @@ export class TilePackage {
|
|
|
238
307
|
version: header.version,
|
|
239
308
|
bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat],
|
|
240
309
|
};
|
|
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
|
-
}
|
|
310
|
+
if (metadata.vector_layers) tileJson.vector_layers = metadata.vector_layers;
|
|
311
|
+
if (metadata.attribution) tileJson.attribution = metadata.attribution;
|
|
312
|
+
if (metadata.description) tileJson.description = metadata.description;
|
|
313
|
+
if (header.minZoom) tileJson.minzoom = header.minZoom;
|
|
314
|
+
if (header.maxZoom) tileJson.maxzoom = header.maxZoom;
|
|
256
315
|
return tileJson;
|
|
257
316
|
}
|
|
258
317
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TilePackage } from "./index.js";
|
|
2
|
-
import protocolGlyphs from "
|
|
2
|
+
import protocolGlyphs from "maplibre-local-glyphs";
|
|
3
3
|
|
|
4
4
|
function stringEndsIn(string, endings) {
|
|
5
5
|
for (let i = 0; i < endings.length; i++) {
|
|
@@ -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
|
}
|
|
@@ -80,6 +80,7 @@ export default class SharedPromiseCache {
|
|
|
80
80
|
this.maxCacheEntries = maxCacheEntries;
|
|
81
81
|
this.counter = 1;
|
|
82
82
|
//this.decompress = decompress;
|
|
83
|
+
this.subdivided = new Map(); // key: sourceKey|z|x|y -> Uint8Array
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async getHeader(source) {
|
|
@@ -174,6 +175,28 @@ export default class SharedPromiseCache {
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
getSubdivided(source, z, x, y) {
|
|
179
|
+
const key = `${source.getKey()}|${z}|${x}|${y}|Subdivided`;
|
|
180
|
+
return this.subdivided.get(key);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setSubdivided(source, z, x, y, bytes) {
|
|
184
|
+
const key = `${source.getKey()}|${z}|${x}|${y}|Subdivided`;
|
|
185
|
+
if (!this.subdivided.has(key)) {
|
|
186
|
+
this.subdivided.set(key, bytes);
|
|
187
|
+
// Optional lightweight prune if huge:
|
|
188
|
+
if (this.subdivided.size > this.maxCacheEntries * 2) {
|
|
189
|
+
// Remove oldest half (no usage tracking kept; iterate arbitrary)
|
|
190
|
+
let toRemove = Math.floor(this.subdivided.size / 2);
|
|
191
|
+
for (const k of this.subdivided.keys()) {
|
|
192
|
+
this.subdivided.delete(k);
|
|
193
|
+
toRemove--;
|
|
194
|
+
if (toRemove <= 0) break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
async invalidate(source) {
|
|
178
201
|
const key = source.getKey();
|
|
179
202
|
if (this.invalidations.get(key)) {
|
package/src/source.js
CHANGED
|
@@ -18,9 +18,12 @@ export class Source {
|
|
|
18
18
|
* see https://developer.mozilla.org/en-US/docs/Web/API/File_API
|
|
19
19
|
*/
|
|
20
20
|
export class FileSource extends Source {
|
|
21
|
-
constructor(file) {
|
|
21
|
+
constructor(file, options) {
|
|
22
22
|
super();
|
|
23
23
|
this.file = file;
|
|
24
|
+
if (options && options.coverageCheck) {
|
|
25
|
+
this.coverageCheck = options.coverageCheck;
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
getKey() {
|
|
@@ -48,7 +51,7 @@ export class FileSource extends Source {
|
|
|
48
51
|
* This also works around browser and storage-specific edge cases.
|
|
49
52
|
*/
|
|
50
53
|
export class FetchSource extends Source {
|
|
51
|
-
constructor(url, customHeaders = new Headers()) {
|
|
54
|
+
constructor(url, options, customHeaders = new Headers()) {
|
|
52
55
|
super();
|
|
53
56
|
this.url = url;
|
|
54
57
|
this.customHeaders = customHeaders;
|
|
@@ -63,6 +66,9 @@ export class FetchSource extends Source {
|
|
|
63
66
|
if (isWindows && isChromiumBased) {
|
|
64
67
|
this.chromeWindowsNoCache = true;
|
|
65
68
|
}
|
|
69
|
+
if (options && options.coverageCheck) {
|
|
70
|
+
this.coverageCheck = options.coverageCheck;
|
|
71
|
+
}
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
getKey() {
|