maplibre-tilepackage-protocol 1.2.1 → 1.2.2
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/CHANGELOG.md +13 -0
- package/dist/bundle.js +4314 -5342
- package/dist/bundle.js.map +1 -1
- package/dist/index.esm.js +487 -266
- package/dist/index.esm.js.map +1 -1
- package/package.json +42 -43
- package/rollup.config.mjs +0 -1
- package/src/default-decompress.js +4 -11
- package/src/get-header-and-filelist.js +5 -129
- package/src/get-json-from-file.js +1 -12
- package/src/index.js +75 -77
- package/src/maplibre-gl-js-protocol.js +0 -9
- package/src/source.js +5 -11
- package/src/zip/get-filelist.js +350 -0
- package/src/zip/get-json-from-file.js +47 -0
package/dist/index.esm.js
CHANGED
|
@@ -2,7 +2,6 @@ import { VectorTile, VectorTileFeature, VectorTileLayer } from '@mapbox/vector-t
|
|
|
2
2
|
import Protobuf from 'pbf';
|
|
3
3
|
import Point from '@mapbox/point-geometry';
|
|
4
4
|
import { fromVectorTileJs } from '@maplibre/vt-pbf';
|
|
5
|
-
import protocolGlyphs from 'maplibre-local-glyphs';
|
|
6
5
|
|
|
7
6
|
function stringEndsIn(string, endings) {
|
|
8
7
|
for (let i = 0; i < endings.length; i++) {
|
|
@@ -175,14 +174,6 @@ class Protocol {
|
|
|
175
174
|
};
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
// Fallback to local glyphs since we couldn't load the coorect from the vtpk
|
|
179
|
-
const fallback = await protocolGlyphs({
|
|
180
|
-
url: `glyphs://${result[2]}/${result[3]}`,
|
|
181
|
-
});
|
|
182
|
-
if (fallback) {
|
|
183
|
-
return fallback;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
177
|
return { data: null };
|
|
187
178
|
}
|
|
188
179
|
}
|
|
@@ -281,9 +272,11 @@ class FileSource extends Source {
|
|
|
281
272
|
constructor(file, options) {
|
|
282
273
|
super();
|
|
283
274
|
this.file = file;
|
|
275
|
+
this.coverageCheck = 1;
|
|
284
276
|
if (options && options.coverageCheck) {
|
|
285
277
|
this.coverageCheck = options.coverageCheck;
|
|
286
278
|
}
|
|
279
|
+
//console.log("Tilepackage FileSource initialized", this.file);
|
|
287
280
|
}
|
|
288
281
|
|
|
289
282
|
getKey() {
|
|
@@ -316,18 +309,9 @@ class FetchSource extends Source {
|
|
|
316
309
|
this.url = url;
|
|
317
310
|
this.customHeaders = customHeaders;
|
|
318
311
|
this.mustReload = false;
|
|
319
|
-
// Safe global detection for legacy environments without globalThis
|
|
320
|
-
const g =
|
|
321
|
-
typeof globalThis !== "undefined"
|
|
322
|
-
? globalThis
|
|
323
|
-
: typeof window !== "undefined"
|
|
324
|
-
? window
|
|
325
|
-
: typeof self !== "undefined"
|
|
326
|
-
? self
|
|
327
|
-
: {};
|
|
328
312
|
let userAgent = "";
|
|
329
|
-
if (
|
|
330
|
-
userAgent =
|
|
313
|
+
if ("navigator" in globalThis) {
|
|
314
|
+
userAgent = globalThis.navigator.userAgent || "";
|
|
331
315
|
}
|
|
332
316
|
const isWindows = userAgent.indexOf("Windows") > -1;
|
|
333
317
|
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);
|
|
@@ -335,6 +319,7 @@ class FetchSource extends Source {
|
|
|
335
319
|
if (isWindows && isChromiumBased) {
|
|
336
320
|
this.chromeWindowsNoCache = true;
|
|
337
321
|
}
|
|
322
|
+
this.coverageCheck = 1;
|
|
338
323
|
if (options && options.coverageCheck) {
|
|
339
324
|
this.coverageCheck = options.coverageCheck;
|
|
340
325
|
}
|
|
@@ -902,7 +887,7 @@ var ec = [
|
|
|
902
887
|
'invalid distance',
|
|
903
888
|
'stream finished',
|
|
904
889
|
'no stream handler',
|
|
905
|
-
,
|
|
890
|
+
, // determined by compression function
|
|
906
891
|
'no callback',
|
|
907
892
|
'invalid UTF-8 data',
|
|
908
893
|
'extra field too long',
|
|
@@ -1130,33 +1115,15 @@ var zls = function (d, dict) {
|
|
|
1130
1115
|
err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');
|
|
1131
1116
|
return (d[1] >> 3 & 4) + 2;
|
|
1132
1117
|
};
|
|
1133
|
-
/**
|
|
1134
|
-
* Expands DEFLATE data with no wrapper
|
|
1135
|
-
* @param data The data to decompress
|
|
1136
|
-
* @param opts The decompression options
|
|
1137
|
-
* @returns The decompressed version of the data
|
|
1138
|
-
*/
|
|
1139
1118
|
function inflateSync(data, opts) {
|
|
1140
1119
|
return inflt(data, { i: 2 }, opts, opts);
|
|
1141
1120
|
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Expands GZIP data
|
|
1144
|
-
* @param data The data to decompress
|
|
1145
|
-
* @param opts The decompression options
|
|
1146
|
-
* @returns The decompressed version of the data
|
|
1147
|
-
*/
|
|
1148
1121
|
function gunzipSync(data, opts) {
|
|
1149
1122
|
var st = gzs(data);
|
|
1150
1123
|
if (st + 8 > data.length)
|
|
1151
1124
|
err(6, 'invalid gzip data');
|
|
1152
1125
|
return inflt(data.subarray(st, -8), { i: 2 }, new u8(gzl(data)), opts);
|
|
1153
1126
|
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Expands Zlib data
|
|
1156
|
-
* @param data The data to decompress
|
|
1157
|
-
* @param opts The decompression options
|
|
1158
|
-
* @returns The decompressed version of the data
|
|
1159
|
-
*/
|
|
1160
1127
|
function unzlibSync(data, opts) {
|
|
1161
1128
|
return inflt(data.subarray(zls(data), -4), { i: 2 }, opts, opts);
|
|
1162
1129
|
}
|
|
@@ -1196,41 +1163,420 @@ async function defaultDecompress(buf, compression) {
|
|
|
1196
1163
|
return buf;
|
|
1197
1164
|
}
|
|
1198
1165
|
if (compression === "gzip") {
|
|
1199
|
-
|
|
1200
|
-
const g =
|
|
1201
|
-
typeof globalThis !== "undefined"
|
|
1202
|
-
? globalThis
|
|
1203
|
-
: typeof window !== "undefined"
|
|
1204
|
-
? window
|
|
1205
|
-
: typeof self !== "undefined"
|
|
1206
|
-
? self
|
|
1207
|
-
: {};
|
|
1208
|
-
if (typeof g.DecompressionStream === "undefined") {
|
|
1166
|
+
if (typeof globalThis.DecompressionStream === "undefined") {
|
|
1209
1167
|
return decompressSync(new Uint8Array(buf));
|
|
1210
1168
|
}
|
|
1211
1169
|
const stream = new Response(buf).body;
|
|
1212
1170
|
if (!stream) {
|
|
1213
1171
|
throw new Error("Failed to read response stream");
|
|
1214
1172
|
}
|
|
1215
|
-
const result = stream.pipeThrough(
|
|
1173
|
+
const result = stream.pipeThrough(
|
|
1174
|
+
new globalThis.DecompressionStream("gzip"),
|
|
1175
|
+
);
|
|
1216
1176
|
return new Response(result).arrayBuffer();
|
|
1217
1177
|
}
|
|
1218
1178
|
throw new Error("Compression method not supported");
|
|
1219
1179
|
}
|
|
1220
1180
|
|
|
1221
|
-
async function
|
|
1181
|
+
async function getJsonFromZip(file, zipFiles, source) {
|
|
1182
|
+
//console.log("getJsonFromZip:", file);
|
|
1222
1183
|
const decoder = new TextDecoder("utf-8");
|
|
1223
|
-
if (
|
|
1224
|
-
const fileOffset =
|
|
1225
|
-
const fileJSON = await source.getBytes(
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1184
|
+
if (zipFiles[file]) {
|
|
1185
|
+
const fileOffset = zipFiles[file].absoluteOffset;
|
|
1186
|
+
const fileJSON = await source.getBytes(fileOffset, zipFiles[file].size);
|
|
1187
|
+
let text;
|
|
1188
|
+
try {
|
|
1189
|
+
text = decoder.decode(fileJSON.data);
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
"Failed to decode JSON bytes from " + file + ": " + error.message,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const trimmed = text.trim();
|
|
1197
|
+
if (!trimmed) {
|
|
1198
|
+
throw new Error("JSON file " + file + " is empty or whitespace only");
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
const firstChar = trimmed[0];
|
|
1202
|
+
const lastChar = trimmed[trimmed.length - 1];
|
|
1203
|
+
const looksLikeJsonObject = firstChar === "{" && lastChar === "}";
|
|
1204
|
+
const looksLikeJsonArray = firstChar === "[" && lastChar === "]";
|
|
1205
|
+
if (!looksLikeJsonObject && !looksLikeJsonArray) {
|
|
1206
|
+
throw new Error(
|
|
1207
|
+
"File " +
|
|
1208
|
+
file +
|
|
1209
|
+
" does not appear to contain JSON (starts with '" +
|
|
1210
|
+
firstChar +
|
|
1211
|
+
"' and ends with '" +
|
|
1212
|
+
lastChar +
|
|
1213
|
+
"')",
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
try {
|
|
1218
|
+
return JSON.parse(trimmed);
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
"Failed to parse JSON from " + file + ": " + error.message,
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1230
1224
|
}
|
|
1225
|
+
console.warn(`File ${file} not found in Zip file`);
|
|
1231
1226
|
return {};
|
|
1232
1227
|
}
|
|
1233
1228
|
|
|
1229
|
+
const ZIP64_EOCD_SIGNATURE = 0x06064b50;
|
|
1230
|
+
const EOCD_SIGNATURE = 0x06054b50;
|
|
1231
|
+
const CENTRAL_DIRECTORY_SIGNATURE = 0x02014b50;
|
|
1232
|
+
const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
1233
|
+
const MIN_EOCD_SIZE = 22;
|
|
1234
|
+
const ZIP64_EOCD_SIZE = 56;
|
|
1235
|
+
const MAX_TAIL_SCAN = 65536; // 64KB
|
|
1236
|
+
const MAX_CENTRAL_DIR_SIZE = 500 * 1024 * 1024; // 500MB
|
|
1237
|
+
const MAX_DIRECTORY_ENTRIES = 1000000;
|
|
1238
|
+
const LOCAL_FILE_HEADER_BASE = 30;
|
|
1239
|
+
|
|
1240
|
+
function scanForSignature(view, signature, minSize) {
|
|
1241
|
+
for (let i = view.byteLength - minSize; i >= 0; i--) {
|
|
1242
|
+
if (view.getUint32(i, true) === signature) {
|
|
1243
|
+
return i;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return -1;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function parseZip64EndOfCentralDirectory(view, position) {
|
|
1250
|
+
return {
|
|
1251
|
+
entries: Number(view.getBigUint64(position + 32, true)),
|
|
1252
|
+
size: Number(view.getBigUint64(position + 40, true)),
|
|
1253
|
+
offset: Number(view.getBigUint64(position + 48, true)),
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function parseEndOfCentralDirectory(view, position) {
|
|
1258
|
+
return {
|
|
1259
|
+
entries: view.getUint16(position + 10, true),
|
|
1260
|
+
size: view.getUint32(position + 12, true),
|
|
1261
|
+
offset: view.getUint32(position + 16, true),
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
async function readLocalFileHeader(source, offset, decoder) {
|
|
1266
|
+
const headerResp = await source.getBytes(offset, LOCAL_FILE_HEADER_BASE);
|
|
1267
|
+
if (headerResp.data.byteLength < LOCAL_FILE_HEADER_BASE) {
|
|
1268
|
+
throw new Error("Local file header truncated before 30 bytes");
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const headerView = new DataView(headerResp.data, 0, LOCAL_FILE_HEADER_BASE);
|
|
1272
|
+
const signature = headerView.getUint32(0, true);
|
|
1273
|
+
if (signature !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
1274
|
+
throw new Error(
|
|
1275
|
+
`Local file header signature mismatch (0x${signature.toString(16)})`,
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const filenameLength = headerView.getUint16(26, true);
|
|
1280
|
+
const extraFieldLength = headerView.getUint16(28, true);
|
|
1281
|
+
const trailingLength = filenameLength + extraFieldLength;
|
|
1282
|
+
let filename = "";
|
|
1283
|
+
|
|
1284
|
+
if (filenameLength > 0) {
|
|
1285
|
+
const trailingResp = await source.getBytes(
|
|
1286
|
+
offset + LOCAL_FILE_HEADER_BASE,
|
|
1287
|
+
trailingLength,
|
|
1288
|
+
);
|
|
1289
|
+
if (trailingResp.data.byteLength < trailingLength) {
|
|
1290
|
+
throw new Error("Local header filename/extra data truncated");
|
|
1291
|
+
}
|
|
1292
|
+
const filenameView = new DataView(trailingResp.data, 0, filenameLength);
|
|
1293
|
+
filename = decoder.decode(filenameView);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return {
|
|
1297
|
+
filename,
|
|
1298
|
+
filenameLength,
|
|
1299
|
+
extraFieldLength,
|
|
1300
|
+
dataOffset:
|
|
1301
|
+
offset + LOCAL_FILE_HEADER_BASE + filenameLength + extraFieldLength,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async function verifyLocalHeaderOffset({
|
|
1306
|
+
source,
|
|
1307
|
+
filename,
|
|
1308
|
+
relativeOffset,
|
|
1309
|
+
computedOffset,
|
|
1310
|
+
decoder,
|
|
1311
|
+
logger,
|
|
1312
|
+
}) {
|
|
1313
|
+
try {
|
|
1314
|
+
const localHeader = await readLocalFileHeader(
|
|
1315
|
+
source,
|
|
1316
|
+
relativeOffset,
|
|
1317
|
+
decoder,
|
|
1318
|
+
);
|
|
1319
|
+
if (localHeader.filename !== filename) {
|
|
1320
|
+
logger(
|
|
1321
|
+
`Local header filename mismatch for ${filename}: found ${localHeader.filename} at offset ${relativeOffset}`,
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
if (localHeader.dataOffset !== computedOffset) {
|
|
1325
|
+
logger(
|
|
1326
|
+
`Data offset mismatch for ${filename}: calculated ${computedOffset}, actual ${localHeader.dataOffset}`,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
return localHeader;
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
logger(
|
|
1332
|
+
`Failed to verify local header for ${filename} at ${relativeOffset}: ${error.message}`,
|
|
1333
|
+
);
|
|
1334
|
+
return null;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
async function getFilelistFromZip(source, options) {
|
|
1339
|
+
const resolvedOptions = {};
|
|
1340
|
+
const verifyOffsets =
|
|
1341
|
+
typeof resolvedOptions.verifyOffsets === "boolean"
|
|
1342
|
+
? resolvedOptions.verifyOffsets
|
|
1343
|
+
: false;
|
|
1344
|
+
const verificationLogger =
|
|
1345
|
+
typeof resolvedOptions.verificationLogger === "function"
|
|
1346
|
+
? resolvedOptions.verificationLogger
|
|
1347
|
+
: console.warn;
|
|
1348
|
+
const fileSize = await source.getSize();
|
|
1349
|
+
if (fileSize < MIN_EOCD_SIZE) {
|
|
1350
|
+
throw new Error(
|
|
1351
|
+
"File too small to be a valid ZIP archive (minimum 22 bytes)",
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const tailReadSize = Math.min(MAX_TAIL_SCAN, fileSize);
|
|
1356
|
+
const tailOffset = fileSize - tailReadSize;
|
|
1357
|
+
const tailResp = await source.getBytes(tailOffset, tailReadSize);
|
|
1358
|
+
const tailView = new DataView(tailResp.data, 0, tailReadSize);
|
|
1359
|
+
|
|
1360
|
+
let entriesCentralDirectory;
|
|
1361
|
+
let sizeCentralDirectory;
|
|
1362
|
+
let offsetCentralDirectory;
|
|
1363
|
+
|
|
1364
|
+
const zip64Pos = scanForSignature(
|
|
1365
|
+
tailView,
|
|
1366
|
+
ZIP64_EOCD_SIGNATURE,
|
|
1367
|
+
ZIP64_EOCD_SIZE,
|
|
1368
|
+
);
|
|
1369
|
+
if (zip64Pos !== -1) {
|
|
1370
|
+
const zip64Data = parseZip64EndOfCentralDirectory(tailView, zip64Pos);
|
|
1371
|
+
entriesCentralDirectory = zip64Data.entries;
|
|
1372
|
+
sizeCentralDirectory = zip64Data.size;
|
|
1373
|
+
offsetCentralDirectory = zip64Data.offset;
|
|
1374
|
+
} else {
|
|
1375
|
+
const eocdPos = scanForSignature(tailView, EOCD_SIGNATURE, MIN_EOCD_SIZE);
|
|
1376
|
+
if (eocdPos === -1) {
|
|
1377
|
+
throw new Error("End of Central Directory record not found in zip");
|
|
1378
|
+
}
|
|
1379
|
+
const eocdData = parseEndOfCentralDirectory(tailView, eocdPos);
|
|
1380
|
+
entriesCentralDirectory = eocdData.entries;
|
|
1381
|
+
sizeCentralDirectory = eocdData.size;
|
|
1382
|
+
offsetCentralDirectory = eocdData.offset;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (sizeCentralDirectory > MAX_CENTRAL_DIR_SIZE) {
|
|
1386
|
+
throw new Error("Central directory size exceeds maximum allowed (500MB)");
|
|
1387
|
+
}
|
|
1388
|
+
if (entriesCentralDirectory > MAX_DIRECTORY_ENTRIES) {
|
|
1389
|
+
throw new Error("Too many entries in central directory (max 1000000)");
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (offsetCentralDirectory < 0) {
|
|
1393
|
+
throw new Error("Central directory offset is negative, file is corrupted");
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const availableBytes = fileSize - offsetCentralDirectory;
|
|
1397
|
+
if (availableBytes < 0) {
|
|
1398
|
+
throw new Error("Central directory offset is beyond file size");
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
let safeCentralDirectorySize = sizeCentralDirectory;
|
|
1402
|
+
if (safeCentralDirectorySize > availableBytes) {
|
|
1403
|
+
console.warn(
|
|
1404
|
+
"Central directory size extends past end of file, clamping to available bytes",
|
|
1405
|
+
);
|
|
1406
|
+
safeCentralDirectorySize = availableBytes;
|
|
1407
|
+
}
|
|
1408
|
+
if (safeCentralDirectorySize <= 0) {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
"Central directory size is zero or negative after validation",
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const centralDirectory = await source.getBytes(
|
|
1415
|
+
offsetCentralDirectory,
|
|
1416
|
+
safeCentralDirectorySize,
|
|
1417
|
+
);
|
|
1418
|
+
const v = new DataView(centralDirectory.data, 0, safeCentralDirectorySize);
|
|
1419
|
+
|
|
1420
|
+
if (v.getUint32(0, true) !== CENTRAL_DIRECTORY_SIGNATURE) {
|
|
1421
|
+
throw new Error("Wrong magic number for Central Directory archive");
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const zipFiles = {};
|
|
1425
|
+
let entryStart = 0;
|
|
1426
|
+
const decoder = new TextDecoder("utf-8");
|
|
1427
|
+
|
|
1428
|
+
for (let i = 0; i < entriesCentralDirectory; i++) {
|
|
1429
|
+
if (entryStart + 46 > safeCentralDirectorySize) {
|
|
1430
|
+
break;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const generalPurposeFlag = v.getUint16(entryStart + 8, true);
|
|
1434
|
+
const compressionMethod = v.getUint16(entryStart + 10, true);
|
|
1435
|
+
if (compressionMethod !== 0 && compressionMethod !== 8) {
|
|
1436
|
+
throw new Error(
|
|
1437
|
+
"Unsupported compression method: " +
|
|
1438
|
+
compressionMethod +
|
|
1439
|
+
" only STORE(0) and DEFLATE(8) are supported",
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
let sizeFile = v.getUint32(entryStart + 20, true);
|
|
1443
|
+
const uncompressedSize = v.getUint32(entryStart + 24, true);
|
|
1444
|
+
const sizeFileName = v.getUint16(entryStart + 28, true);
|
|
1445
|
+
const sizeExtraField = v.getUint16(entryStart + 30, true);
|
|
1446
|
+
const sizeComment = v.getUint16(entryStart + 32, true);
|
|
1447
|
+
let relativeOffset = v.getUint32(entryStart + 42, true);
|
|
1448
|
+
|
|
1449
|
+
const headerLength = 46 + sizeFileName + sizeExtraField + sizeComment;
|
|
1450
|
+
if (entryStart + headerLength > safeCentralDirectorySize) {
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const filenameView = new DataView(
|
|
1455
|
+
centralDirectory.data,
|
|
1456
|
+
entryStart + 46,
|
|
1457
|
+
sizeFileName,
|
|
1458
|
+
);
|
|
1459
|
+
let filename;
|
|
1460
|
+
try {
|
|
1461
|
+
filename = decoder.decode(filenameView);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
throw new Error(
|
|
1464
|
+
"Failed to decode filename in ZIP entry: " + error.message,
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const zip64SizeOverflow = sizeFile === 0xffffffff;
|
|
1469
|
+
const zip64UncompressedOverflow = uncompressedSize === 0xffffffff;
|
|
1470
|
+
const zip64OffsetOverflow = relativeOffset === 0xffffffff;
|
|
1471
|
+
const hasZip64Marker =
|
|
1472
|
+
zip64SizeOverflow || zip64UncompressedOverflow || zip64OffsetOverflow;
|
|
1473
|
+
let localExtraFieldZip64Length = 0;
|
|
1474
|
+
|
|
1475
|
+
if (hasZip64Marker && sizeExtraField > 0) {
|
|
1476
|
+
const extraStart = entryStart + 46 + sizeFileName;
|
|
1477
|
+
const extraEnd = extraStart + sizeExtraField;
|
|
1478
|
+
if (extraEnd <= safeCentralDirectorySize) {
|
|
1479
|
+
const extraView = new DataView(
|
|
1480
|
+
centralDirectory.data,
|
|
1481
|
+
extraStart,
|
|
1482
|
+
sizeExtraField,
|
|
1483
|
+
);
|
|
1484
|
+
let extraOffset = 0;
|
|
1485
|
+
let iterations = 100;
|
|
1486
|
+
while (extraOffset + 4 <= sizeExtraField && iterations > 0) {
|
|
1487
|
+
iterations--;
|
|
1488
|
+
const headerId = extraView.getUint16(extraOffset, true);
|
|
1489
|
+
const dataSize = extraView.getUint16(extraOffset + 2, true);
|
|
1490
|
+
|
|
1491
|
+
if (headerId === 0x0001) {
|
|
1492
|
+
// APPNOTE.TXT (4.5.3) – local header ZIP64 field only stores
|
|
1493
|
+
// the compressed/uncompressed sizes when a 64-bit value is needed.
|
|
1494
|
+
const dataStart = extraOffset + 4;
|
|
1495
|
+
const dataEnd = dataStart + dataSize;
|
|
1496
|
+
let cursor = dataStart;
|
|
1497
|
+
let localZip64DataSize = 0;
|
|
1498
|
+
|
|
1499
|
+
if (zip64UncompressedOverflow) {
|
|
1500
|
+
if (cursor + 8 > dataEnd) {
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
"ZIP64 extra field truncated while reading uncompressed size",
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
extraView.getBigUint64(cursor, true);
|
|
1506
|
+
cursor += 8;
|
|
1507
|
+
localZip64DataSize += 8;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
if (zip64SizeOverflow) {
|
|
1511
|
+
if (cursor + 8 > dataEnd) {
|
|
1512
|
+
throw new Error(
|
|
1513
|
+
"ZIP64 extra field truncated while reading compressed size",
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
sizeFile = Number(extraView.getBigUint64(cursor, true));
|
|
1517
|
+
cursor += 8;
|
|
1518
|
+
localZip64DataSize += 8;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (zip64OffsetOverflow) {
|
|
1522
|
+
if (cursor + 8 > dataEnd) {
|
|
1523
|
+
throw new Error(
|
|
1524
|
+
"ZIP64 extra field truncated while reading relative offset",
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
relativeOffset = Number(extraView.getBigUint64(cursor, true));
|
|
1528
|
+
cursor += 8;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
if (localZip64DataSize > 0) {
|
|
1532
|
+
localExtraFieldZip64Length = 4 + localZip64DataSize;
|
|
1533
|
+
}
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (dataSize === 0) {
|
|
1538
|
+
extraOffset += 4;
|
|
1539
|
+
} else {
|
|
1540
|
+
extraOffset += 4 + dataSize;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
let absoluteOffset =
|
|
1546
|
+
relativeOffset + 30 + sizeFileName + localExtraFieldZip64Length;
|
|
1547
|
+
|
|
1548
|
+
if (verifyOffsets) {
|
|
1549
|
+
const verification = await verifyLocalHeaderOffset({
|
|
1550
|
+
source,
|
|
1551
|
+
filename,
|
|
1552
|
+
relativeOffset,
|
|
1553
|
+
computedOffset: absoluteOffset,
|
|
1554
|
+
decoder,
|
|
1555
|
+
logger: verificationLogger,
|
|
1556
|
+
});
|
|
1557
|
+
if (verification && typeof verification.dataOffset === "number") {
|
|
1558
|
+
absoluteOffset = verification.dataOffset;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
zipFiles[filename] = {
|
|
1562
|
+
filename,
|
|
1563
|
+
size: sizeFile,
|
|
1564
|
+
generalPurposeFlag,
|
|
1565
|
+
compressionMethod,
|
|
1566
|
+
uncompressedSize,
|
|
1567
|
+
relativeOffset,
|
|
1568
|
+
absoluteOffset,
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
entryStart += headerLength;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
return {
|
|
1575
|
+
files: zipFiles,
|
|
1576
|
+
etag: tailResp.etag,
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1234
1580
|
function xmlToJson(xmlString) {
|
|
1235
1581
|
const parser = new DOMParser();
|
|
1236
1582
|
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
|
|
@@ -1302,137 +1648,14 @@ function jsonToHeader(json, files, etag) {
|
|
|
1302
1648
|
*/
|
|
1303
1649
|
async function getHeaderAndFileList(source) {
|
|
1304
1650
|
const key = source.getKey();
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
let v = new DataView(resp.data, 0, 98);
|
|
1308
|
-
let entriesCentralDirectory, sizeCentralDirectory, offsetCentralDirectory;
|
|
1309
|
-
//let bigZip64 = false;
|
|
1310
|
-
if (v.getUint32(0, true) === 0x06064b50) {
|
|
1311
|
-
// This is a ZIP64 tilepackage
|
|
1312
|
-
//bigZip64 = true;
|
|
1313
|
-
entriesCentralDirectory = Number(v.getBigUint64(32, true));
|
|
1314
|
-
sizeCentralDirectory = Number(v.getBigUint64(40, true));
|
|
1315
|
-
offsetCentralDirectory = Number(v.getBigUint64(48, true));
|
|
1316
|
-
} else {
|
|
1317
|
-
v = new DataView(resp.data, 98 - 22, 22);
|
|
1318
|
-
if (v.getUint32(0, true) === 0x06054b50) {
|
|
1319
|
-
// This is a ordinary zip archive
|
|
1320
|
-
entriesCentralDirectory = Number(v.getUint16(10, true));
|
|
1321
|
-
sizeCentralDirectory = Number(v.getUint32(12, true));
|
|
1322
|
-
offsetCentralDirectory = Number(v.getUint32(16, true));
|
|
1323
|
-
} else {
|
|
1324
|
-
throw new Error("Wrong magic number for Zip archive");
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
const centralDirectory = await source.getBytes(
|
|
1329
|
-
offsetCentralDirectory,
|
|
1330
|
-
sizeCentralDirectory,
|
|
1331
|
-
);
|
|
1332
|
-
|
|
1333
|
-
v = new DataView(centralDirectory.data, 0, sizeCentralDirectory);
|
|
1334
|
-
if (v.getUint32(0, true) !== 0x02014b50) {
|
|
1335
|
-
throw new Error("Wrong magic number for Central Directory archive");
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
let entryStart = 0;
|
|
1339
|
-
const tilePackageFiles = {};
|
|
1340
|
-
for (let i = 0; i < entriesCentralDirectory; i++) {
|
|
1341
|
-
if (entryStart >= sizeCentralDirectory) break;
|
|
1342
|
-
/*
|
|
1343
|
-
central file header signature 4 bytes (0x02014b50)
|
|
1344
|
-
version made by 2 bytes
|
|
1345
|
-
version needed to extract 2 bytes
|
|
1346
|
-
general purpose bit flag 2 bytes
|
|
1347
|
-
compression method 2 bytes
|
|
1348
|
-
last mod file time 2 bytes
|
|
1349
|
-
last mod file date 2 bytes
|
|
1350
|
-
crc-32 4 bytes
|
|
1351
|
-
compressed size 4 bytes
|
|
1352
|
-
uncompressed size 4 bytes
|
|
1353
|
-
file name length 2 bytes
|
|
1354
|
-
extra field length 2 bytes
|
|
1355
|
-
file comment length 2 bytes
|
|
1356
|
-
disk number start 2 bytes
|
|
1357
|
-
internal file attributes 2 bytes
|
|
1358
|
-
external file attributes 4 bytes
|
|
1359
|
-
relative offset of local header 4 bytes
|
|
1360
|
-
|
|
1361
|
-
file name (variable size)
|
|
1362
|
-
extra field (variable size)
|
|
1363
|
-
file comment (variable size)
|
|
1364
|
-
*/
|
|
1365
|
-
let sizeFile = v.getUint32(entryStart + 20, true);
|
|
1366
|
-
|
|
1367
|
-
const sizeFileName = v.getUint16(entryStart + 28, true);
|
|
1368
|
-
|
|
1369
|
-
const sizeExtraField = v.getUint16(entryStart + 30, true);
|
|
1370
|
-
|
|
1371
|
-
const sizeComment = v.getUint16(entryStart + 32, true);
|
|
1372
|
-
|
|
1373
|
-
let relativeOffset = v.getUint32(entryStart + 42, true);
|
|
1374
|
-
|
|
1375
|
-
const vFilename = new DataView(
|
|
1376
|
-
centralDirectory.data,
|
|
1377
|
-
entryStart + 46,
|
|
1378
|
-
sizeFileName,
|
|
1379
|
-
);
|
|
1380
|
-
const decoder = new TextDecoder("utf-8");
|
|
1381
|
-
const filename = decoder.decode(vFilename);
|
|
1382
|
-
|
|
1383
|
-
if (
|
|
1384
|
-
(sizeFile == 0xffffffff || relativeOffset == 0xffffffff) &&
|
|
1385
|
-
sizeExtraField > 0
|
|
1386
|
-
) {
|
|
1387
|
-
const vExtended = new DataView(
|
|
1388
|
-
centralDirectory.data,
|
|
1389
|
-
entryStart + 46 + sizeFileName,
|
|
1390
|
-
32,
|
|
1391
|
-
);
|
|
1392
|
-
if (vExtended.getUint16(0, true) == 0x0001) {
|
|
1393
|
-
/*
|
|
1394
|
-
Value Size Description
|
|
1395
|
-
----- ---- -----------
|
|
1396
|
-
(ZIP64) 0x0001 2 bytes Tag for this "extra" block type
|
|
1397
|
-
Size 2 bytes Size of this "extra" block
|
|
1398
|
-
Original
|
|
1399
|
-
Size 8 bytes Original uncompressed file size
|
|
1400
|
-
Compressed
|
|
1401
|
-
Size 8 bytes Size of compressed data
|
|
1402
|
-
Relative Header
|
|
1403
|
-
Offset 8 bytes Offset of local header record
|
|
1404
|
-
Disk Start
|
|
1405
|
-
Number 4 bytes Number of the disk on which
|
|
1406
|
-
this file starts
|
|
1407
|
-
*/
|
|
1408
|
-
|
|
1409
|
-
let j = 0;
|
|
1410
|
-
if (sizeFile == 0xffffffff) {
|
|
1411
|
-
sizeFile = Number(vExtended.getBigUint64(4, true));
|
|
1412
|
-
j += 8;
|
|
1413
|
-
}
|
|
1414
|
-
if (relativeOffset == 0xffffffff) {
|
|
1415
|
-
relativeOffset = Number(vExtended.getBigUint64(4 + j, true));
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
tilePackageFiles[filename] = {
|
|
1421
|
-
filename: filename,
|
|
1422
|
-
size: sizeFile,
|
|
1423
|
-
relativeOffset: relativeOffset,
|
|
1424
|
-
absoluteOffset: relativeOffset + 30 + filename.length,
|
|
1425
|
-
};
|
|
1426
|
-
entryStart += 46 + sizeFileName + sizeExtraField + sizeComment;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1651
|
+
const { files: tilePackageFiles, etag } =
|
|
1652
|
+
await getFilelistFromZip(source);
|
|
1429
1653
|
let root = {};
|
|
1430
|
-
|
|
1431
1654
|
if (key.indexOf(".tpkx") !== -1) {
|
|
1432
1655
|
// TPKX
|
|
1433
|
-
root = await
|
|
1656
|
+
root = await getJsonFromZip("root.json", tilePackageFiles, source);
|
|
1434
1657
|
root.type = "tpkx";
|
|
1435
|
-
const iteminfo = await
|
|
1658
|
+
const iteminfo = await getJsonFromZip(
|
|
1436
1659
|
"iteminfo.json",
|
|
1437
1660
|
tilePackageFiles,
|
|
1438
1661
|
source,
|
|
@@ -1444,7 +1667,7 @@ async function getHeaderAndFileList(source) {
|
|
|
1444
1667
|
});
|
|
1445
1668
|
} else {
|
|
1446
1669
|
// VTPK
|
|
1447
|
-
root = await
|
|
1670
|
+
root = await getJsonFromZip("p12/root.json", tilePackageFiles, source);
|
|
1448
1671
|
root.type = "vtpk";
|
|
1449
1672
|
|
|
1450
1673
|
const iteminfoFile = "esriinfo/iteminfo.xml";
|
|
@@ -1498,7 +1721,7 @@ async function getHeaderAndFileList(source) {
|
|
|
1498
1721
|
}
|
|
1499
1722
|
// Get and calculate the tilemap
|
|
1500
1723
|
if (source.coverageCheck > 0 && tilePackageFiles["p12/tilemap/root.json"]) {
|
|
1501
|
-
const tilemap = await
|
|
1724
|
+
const tilemap = await getJsonFromZip(
|
|
1502
1725
|
"p12/tilemap/root.json",
|
|
1503
1726
|
tilePackageFiles,
|
|
1504
1727
|
source,
|
|
@@ -1512,7 +1735,7 @@ async function getHeaderAndFileList(source) {
|
|
|
1512
1735
|
}
|
|
1513
1736
|
}
|
|
1514
1737
|
|
|
1515
|
-
const header = jsonToHeader(root, tilePackageFiles,
|
|
1738
|
+
const header = jsonToHeader(root, tilePackageFiles, etag);
|
|
1516
1739
|
return [header, ""];
|
|
1517
1740
|
}
|
|
1518
1741
|
|
|
@@ -1733,23 +1956,18 @@ class SharedPromiseCache {
|
|
|
1733
1956
|
}
|
|
1734
1957
|
}
|
|
1735
1958
|
|
|
1736
|
-
/**
|
|
1737
|
-
* Escape literal newlines in JSON text.
|
|
1738
|
-
* Replaces actual newline characters with the JSON escape sequence.
|
|
1739
|
-
*/
|
|
1740
|
-
function escapeJsonNewlines(text) {
|
|
1741
|
-
// Replace literal newlines with JSON escape sequences
|
|
1742
|
-
// This handles \r\n, \n, and \r
|
|
1743
|
-
return text.replace(/\r\n/g, "\\n").replace(/[\r\n]/g, "\\n");
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
1959
|
function calculateFilename(z, x, y, header) {
|
|
1747
1960
|
const zoom = z.toString().padStart(2, "0");
|
|
1748
1961
|
const baseRow = Math.floor(y / 128) * 128;
|
|
1749
1962
|
const baseCol = Math.floor(x / 128) * 128;
|
|
1750
1963
|
const rowHex = baseRow.toString(16).padStart(4, "0");
|
|
1751
1964
|
const colHex = baseCol.toString(16).padStart(4, "0");
|
|
1752
|
-
|
|
1965
|
+
let basePath;
|
|
1966
|
+
if (header.type === "tpkx") {
|
|
1967
|
+
basePath = `tile`;
|
|
1968
|
+
} else {
|
|
1969
|
+
basePath = `p12/tile`;
|
|
1970
|
+
}
|
|
1753
1971
|
return `${basePath}/L${zoom}/R${rowHex}C${colHex}.bundle`;
|
|
1754
1972
|
}
|
|
1755
1973
|
|
|
@@ -1759,6 +1977,10 @@ function getTileInfoFromTileIndex(tileIndex, z, x, y) {
|
|
|
1759
1977
|
return tileIndex[row][col];
|
|
1760
1978
|
}
|
|
1761
1979
|
|
|
1980
|
+
/**
|
|
1981
|
+
* Error thrown when a response for TilePackage over HTTP does not match previous, cached parts of the archive.
|
|
1982
|
+
* The default TilePackage implementation will catch this error once internally and retry a request.
|
|
1983
|
+
*/
|
|
1762
1984
|
class EtagMismatch extends Error {
|
|
1763
1985
|
constructor(message) {
|
|
1764
1986
|
super(message);
|
|
@@ -1771,25 +1993,23 @@ class TilePackage {
|
|
|
1771
1993
|
// Default coverageCheck enabled; disable with coverageCheck:false
|
|
1772
1994
|
this.coverageCheck = 1;
|
|
1773
1995
|
if (options && options.coverageCheck === false) this.coverageCheck = 0;
|
|
1774
|
-
// Max dz guard (skip subdivision beyond this): default 8
|
|
1775
|
-
this.maxDz =
|
|
1776
|
-
options && typeof options.maxDz === "number" ? options.maxDz : 8;
|
|
1777
1996
|
if (typeof source === "string") {
|
|
1778
|
-
|
|
1779
|
-
? { coverageCheck: this.coverageCheck }
|
|
1780
|
-
: undefined;
|
|
1781
|
-
this.source = new FetchSource(source, opts);
|
|
1997
|
+
this.source = new FetchSource(source, this.coverageCheck);
|
|
1782
1998
|
} else {
|
|
1783
1999
|
this.source = source;
|
|
1784
|
-
// Propagate default coverageCheck to existing Source instances if undefined
|
|
1785
|
-
if (this.source && this.source.coverageCheck === undefined) {
|
|
1786
|
-
this.source.coverageCheck = this.coverageCheck;
|
|
1787
|
-
}
|
|
1788
2000
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2001
|
+
|
|
2002
|
+
if (options && options.decompress) {
|
|
2003
|
+
this.decompress = options.decompress;
|
|
2004
|
+
} else {
|
|
2005
|
+
this.decompress = defaultDecompress;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
if (options && options.cache) {
|
|
2009
|
+
this.cache = options.cache;
|
|
2010
|
+
} else {
|
|
2011
|
+
this.cache = new SharedPromiseCache();
|
|
2012
|
+
}
|
|
1793
2013
|
}
|
|
1794
2014
|
|
|
1795
2015
|
async getHeader() {
|
|
@@ -1906,21 +2126,21 @@ class TilePackage {
|
|
|
1906
2126
|
|
|
1907
2127
|
async getMetadataAttempt() {
|
|
1908
2128
|
const header = await this.cache.getHeader(this.source);
|
|
2129
|
+
|
|
1909
2130
|
let metadata = {};
|
|
1910
2131
|
if (header.packageType === "vtpk") {
|
|
1911
2132
|
const resp = await this.source.getBytes(
|
|
1912
|
-
header.
|
|
1913
|
-
header.
|
|
2133
|
+
header.jsonMetadataOffset,
|
|
2134
|
+
header.jsonMetadataLength,
|
|
1914
2135
|
undefined,
|
|
1915
2136
|
header.etag,
|
|
1916
2137
|
);
|
|
2138
|
+
|
|
1917
2139
|
const decoder = new TextDecoder("utf-8");
|
|
1918
|
-
|
|
1919
|
-
console.log("test", jsonText);
|
|
1920
|
-
console.log("escaped text", escapeJsonNewlines(jsonText));
|
|
1921
|
-
metadata = JSON.parse(escapeJsonNewlines(jsonText));
|
|
2140
|
+
metadata = JSON.parse(decoder.decode(resp.data));
|
|
1922
2141
|
}
|
|
1923
2142
|
metadata.name = header.name;
|
|
2143
|
+
|
|
1924
2144
|
return metadata;
|
|
1925
2145
|
}
|
|
1926
2146
|
|
|
@@ -1938,7 +2158,10 @@ class TilePackage {
|
|
|
1938
2158
|
|
|
1939
2159
|
async getResourceAttempt(file, signal) {
|
|
1940
2160
|
const header = await this.cache.getHeader(this.source);
|
|
1941
|
-
if (!header.files[file])
|
|
2161
|
+
if (!header.files[file]) {
|
|
2162
|
+
return undefined;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
1942
2165
|
const resource = await this.cache.getResource(
|
|
1943
2166
|
this.source,
|
|
1944
2167
|
file,
|
|
@@ -1968,59 +2191,46 @@ class TilePackage {
|
|
|
1968
2191
|
const header = await this.cache.getHeader(this.source);
|
|
1969
2192
|
const sourceKey = this.source.getKey();
|
|
1970
2193
|
if (header.packageType === "vtpk") {
|
|
1971
|
-
const metadata = await
|
|
2194
|
+
const metadata = await getJsonFromZip(
|
|
1972
2195
|
"p12/root.json",
|
|
1973
2196
|
header.files,
|
|
1974
2197
|
this.source,
|
|
1975
2198
|
);
|
|
1976
|
-
const style = await
|
|
2199
|
+
const style = await getJsonFromZip(
|
|
1977
2200
|
"p12/resources/styles/root.json",
|
|
1978
2201
|
header.files,
|
|
1979
2202
|
this.source,
|
|
1980
2203
|
);
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
style.sources.esri.minzoom = header.minZoom || 0;
|
|
1986
|
-
style.sources.esri.maxzoom = header.maxZoom || 22;
|
|
1987
|
-
}
|
|
1988
|
-
for (const k in style.sources) {
|
|
1989
|
-
const src = style.sources[k];
|
|
1990
|
-
if (
|
|
1991
|
-
src &&
|
|
1992
|
-
typeof src.url === "string" &&
|
|
1993
|
-
src.url.startsWith("tilepackage://")
|
|
1994
|
-
) {
|
|
1995
|
-
delete src.url;
|
|
1996
|
-
if (!src.tiles)
|
|
1997
|
-
src.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
if (metadata.copyrightText)
|
|
2204
|
+
style.sources.esri.url = `tilepackage://${sourceKey}`;
|
|
2205
|
+
style.sources.esri.minzoom = 0; //header.maxZoom || 22;
|
|
2206
|
+
style.sources.esri.maxzoom = header.maxZoom || 22;
|
|
2207
|
+
if (metadata.copyrightText) {
|
|
2001
2208
|
style.sources.esri.attribution = metadata.copyrightText;
|
|
2209
|
+
}
|
|
2002
2210
|
style.glyphs = `tilepackage://${sourceKey}/{fontstack}/{range}`;
|
|
2003
2211
|
style.sprite = `tilepackage://${sourceKey}/sprite`;
|
|
2004
|
-
if (this.debug)
|
|
2005
|
-
console.debug(
|
|
2006
|
-
"[tilepackage style] rewritten esri source",
|
|
2007
|
-
style.sources.esri,
|
|
2008
|
-
);
|
|
2009
2212
|
return style;
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2213
|
+
} else {
|
|
2214
|
+
const style = {
|
|
2215
|
+
version: 8,
|
|
2216
|
+
sources: {
|
|
2217
|
+
esri: {
|
|
2218
|
+
type: "raster",
|
|
2219
|
+
tileSize: header.tileSize || 256,
|
|
2220
|
+
url: `tilepackage://${sourceKey}`,
|
|
2221
|
+
maxzoom: header.maxZoom || 22,
|
|
2222
|
+
},
|
|
2020
2223
|
},
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2224
|
+
layers: [
|
|
2225
|
+
{
|
|
2226
|
+
id: "tilepackageraster",
|
|
2227
|
+
type: "raster",
|
|
2228
|
+
source: "esri",
|
|
2229
|
+
},
|
|
2230
|
+
],
|
|
2231
|
+
};
|
|
2232
|
+
return style;
|
|
2233
|
+
}
|
|
2024
2234
|
}
|
|
2025
2235
|
|
|
2026
2236
|
async getStyle() {
|
|
@@ -2039,6 +2249,7 @@ class TilePackage {
|
|
|
2039
2249
|
const header = await this.getHeader();
|
|
2040
2250
|
const metadata = await this.getMetadata();
|
|
2041
2251
|
const ext = header.tileType;
|
|
2252
|
+
|
|
2042
2253
|
const tileJson = {
|
|
2043
2254
|
tilejson: "3.0.0",
|
|
2044
2255
|
scheme: "xyz",
|
|
@@ -2047,11 +2258,21 @@ class TilePackage {
|
|
|
2047
2258
|
version: header.version,
|
|
2048
2259
|
bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat],
|
|
2049
2260
|
};
|
|
2050
|
-
if (metadata.vector_layers)
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
if (
|
|
2054
|
-
|
|
2261
|
+
if (metadata.vector_layers) {
|
|
2262
|
+
tileJson.vector_layers = metadata.vector_layers;
|
|
2263
|
+
}
|
|
2264
|
+
if (metadata.attribution) {
|
|
2265
|
+
tileJson.attribution = metadata.attribution;
|
|
2266
|
+
}
|
|
2267
|
+
if (metadata.description) {
|
|
2268
|
+
tileJson.description = metadata.description;
|
|
2269
|
+
}
|
|
2270
|
+
if (header.minZoom) {
|
|
2271
|
+
tileJson.minzoom = header.minZoom;
|
|
2272
|
+
}
|
|
2273
|
+
if (header.maxZoom) {
|
|
2274
|
+
tileJson.maxzoom = header.maxZoom;
|
|
2275
|
+
}
|
|
2055
2276
|
return tileJson;
|
|
2056
2277
|
}
|
|
2057
2278
|
}
|