maplibre-tilepackage-protocol 1.2.0 → 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/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() {
@@ -326,6 +319,7 @@ class FetchSource extends Source {
326
319
  if (isWindows && isChromiumBased) {
327
320
  this.chromeWindowsNoCache = true;
328
321
  }
322
+ this.coverageCheck = 1;
329
323
  if (options && options.coverageCheck) {
330
324
  this.coverageCheck = options.coverageCheck;
331
325
  }
@@ -893,7 +887,7 @@ var ec = [
893
887
  'invalid distance',
894
888
  'stream finished',
895
889
  'no stream handler',
896
- ,
890
+ , // determined by compression function
897
891
  'no callback',
898
892
  'invalid UTF-8 data',
899
893
  'extra field too long',
@@ -1121,33 +1115,15 @@ var zls = function (d, dict) {
1121
1115
  err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');
1122
1116
  return (d[1] >> 3 & 4) + 2;
1123
1117
  };
1124
- /**
1125
- * Expands DEFLATE data with no wrapper
1126
- * @param data The data to decompress
1127
- * @param opts The decompression options
1128
- * @returns The decompressed version of the data
1129
- */
1130
1118
  function inflateSync(data, opts) {
1131
1119
  return inflt(data, { i: 2 }, opts, opts);
1132
1120
  }
1133
- /**
1134
- * Expands GZIP data
1135
- * @param data The data to decompress
1136
- * @param opts The decompression options
1137
- * @returns The decompressed version of the data
1138
- */
1139
1121
  function gunzipSync(data, opts) {
1140
1122
  var st = gzs(data);
1141
1123
  if (st + 8 > data.length)
1142
1124
  err(6, 'invalid gzip data');
1143
1125
  return inflt(data.subarray(st, -8), { i: 2 }, new u8(gzl(data)), opts);
1144
1126
  }
1145
- /**
1146
- * Expands Zlib data
1147
- * @param data The data to decompress
1148
- * @param opts The decompression options
1149
- * @returns The decompressed version of the data
1150
- */
1151
1127
  function unzlibSync(data, opts) {
1152
1128
  return inflt(data.subarray(zls(data), -4), { i: 2 }, opts, opts);
1153
1129
  }
@@ -1202,19 +1178,405 @@ async function defaultDecompress(buf, compression) {
1202
1178
  throw new Error("Compression method not supported");
1203
1179
  }
1204
1180
 
1205
- async function getJsonFromFile(file, tilePackageFiles, source) {
1181
+ async function getJsonFromZip(file, zipFiles, source) {
1182
+ //console.log("getJsonFromZip:", file);
1206
1183
  const decoder = new TextDecoder("utf-8");
1207
- if (tilePackageFiles[file]) {
1208
- const fileOffset = tilePackageFiles[file].absoluteOffset;
1209
- const fileJSON = await source.getBytes(
1210
- fileOffset,
1211
- tilePackageFiles[file].size,
1212
- );
1213
- return JSON.parse(decoder.decode(fileJSON.data));
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
+ }
1214
1224
  }
1225
+ console.warn(`File ${file} not found in Zip file`);
1215
1226
  return {};
1216
1227
  }
1217
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
+
1218
1580
  function xmlToJson(xmlString) {
1219
1581
  const parser = new DOMParser();
1220
1582
  const xmlDoc = parser.parseFromString(xmlString, "application/xml");
@@ -1286,137 +1648,14 @@ function jsonToHeader(json, files, etag) {
1286
1648
  */
1287
1649
  async function getHeaderAndFileList(source) {
1288
1650
  const key = source.getKey();
1289
- const fileSize = await source.getSize();
1290
- const resp = await source.getBytes(fileSize - 98, 98);
1291
- let v = new DataView(resp.data, 0, 98);
1292
- let entriesCentralDirectory, sizeCentralDirectory, offsetCentralDirectory;
1293
- //let bigZip64 = false;
1294
- if (v.getUint32(0, true) === 0x06064b50) {
1295
- // This is a ZIP64 tilepackage
1296
- //bigZip64 = true;
1297
- entriesCentralDirectory = Number(v.getBigUint64(32, true));
1298
- sizeCentralDirectory = Number(v.getBigUint64(40, true));
1299
- offsetCentralDirectory = Number(v.getBigUint64(48, true));
1300
- } else {
1301
- v = new DataView(resp.data, 98 - 22, 22);
1302
- if (v.getUint32(0, true) === 0x06054b50) {
1303
- // This is a ordinary zip archive
1304
- entriesCentralDirectory = Number(v.getUint16(10, true));
1305
- sizeCentralDirectory = Number(v.getUint32(12, true));
1306
- offsetCentralDirectory = Number(v.getUint32(16, true));
1307
- } else {
1308
- throw new Error("Wrong magic number for Zip archive");
1309
- }
1310
- }
1311
-
1312
- const centralDirectory = await source.getBytes(
1313
- offsetCentralDirectory,
1314
- sizeCentralDirectory,
1315
- );
1316
-
1317
- v = new DataView(centralDirectory.data, 0, sizeCentralDirectory);
1318
- if (v.getUint32(0, true) !== 0x02014b50) {
1319
- throw new Error("Wrong magic number for Central Directory archive");
1320
- }
1321
-
1322
- let entryStart = 0;
1323
- const tilePackageFiles = {};
1324
- for (let i = 0; i < entriesCentralDirectory; i++) {
1325
- if (entryStart >= sizeCentralDirectory) break;
1326
- /*
1327
- central file header signature 4 bytes (0x02014b50)
1328
- version made by 2 bytes
1329
- version needed to extract 2 bytes
1330
- general purpose bit flag 2 bytes
1331
- compression method 2 bytes
1332
- last mod file time 2 bytes
1333
- last mod file date 2 bytes
1334
- crc-32 4 bytes
1335
- compressed size 4 bytes
1336
- uncompressed size 4 bytes
1337
- file name length 2 bytes
1338
- extra field length 2 bytes
1339
- file comment length 2 bytes
1340
- disk number start 2 bytes
1341
- internal file attributes 2 bytes
1342
- external file attributes 4 bytes
1343
- relative offset of local header 4 bytes
1344
-
1345
- file name (variable size)
1346
- extra field (variable size)
1347
- file comment (variable size)
1348
- */
1349
- let sizeFile = v.getUint32(entryStart + 20, true);
1350
-
1351
- const sizeFileName = v.getUint16(entryStart + 28, true);
1352
-
1353
- const sizeExtraField = v.getUint16(entryStart + 30, true);
1354
-
1355
- const sizeComment = v.getUint16(entryStart + 32, true);
1356
-
1357
- let relativeOffset = v.getUint32(entryStart + 42, true);
1358
-
1359
- const vFilename = new DataView(
1360
- centralDirectory.data,
1361
- entryStart + 46,
1362
- sizeFileName,
1363
- );
1364
- const decoder = new TextDecoder("utf-8");
1365
- const filename = decoder.decode(vFilename);
1366
-
1367
- if (
1368
- (sizeFile == 0xffffffff || relativeOffset == 0xffffffff) &&
1369
- sizeExtraField > 0
1370
- ) {
1371
- const vExtended = new DataView(
1372
- centralDirectory.data,
1373
- entryStart + 46 + sizeFileName,
1374
- 32,
1375
- );
1376
- if (vExtended.getUint16(0, true) == 0x0001) {
1377
- /*
1378
- Value Size Description
1379
- ----- ---- -----------
1380
- (ZIP64) 0x0001 2 bytes Tag for this "extra" block type
1381
- Size 2 bytes Size of this "extra" block
1382
- Original
1383
- Size 8 bytes Original uncompressed file size
1384
- Compressed
1385
- Size 8 bytes Size of compressed data
1386
- Relative Header
1387
- Offset 8 bytes Offset of local header record
1388
- Disk Start
1389
- Number 4 bytes Number of the disk on which
1390
- this file starts
1391
- */
1392
-
1393
- let j = 0;
1394
- if (sizeFile == 0xffffffff) {
1395
- sizeFile = Number(vExtended.getBigUint64(4, true));
1396
- j += 8;
1397
- }
1398
- if (relativeOffset == 0xffffffff) {
1399
- relativeOffset = Number(vExtended.getBigUint64(4 + j, true));
1400
- }
1401
- }
1402
- }
1403
-
1404
- tilePackageFiles[filename] = {
1405
- filename: filename,
1406
- size: sizeFile,
1407
- relativeOffset: relativeOffset,
1408
- absoluteOffset: relativeOffset + 30 + filename.length,
1409
- };
1410
- entryStart += 46 + sizeFileName + sizeExtraField + sizeComment;
1411
- }
1412
-
1651
+ const { files: tilePackageFiles, etag } =
1652
+ await getFilelistFromZip(source);
1413
1653
  let root = {};
1414
-
1415
1654
  if (key.indexOf(".tpkx") !== -1) {
1416
1655
  // TPKX
1417
- root = await getJsonFromFile("root.json", tilePackageFiles, source);
1656
+ root = await getJsonFromZip("root.json", tilePackageFiles, source);
1418
1657
  root.type = "tpkx";
1419
- const iteminfo = await getJsonFromFile(
1658
+ const iteminfo = await getJsonFromZip(
1420
1659
  "iteminfo.json",
1421
1660
  tilePackageFiles,
1422
1661
  source,
@@ -1428,7 +1667,7 @@ async function getHeaderAndFileList(source) {
1428
1667
  });
1429
1668
  } else {
1430
1669
  // VTPK
1431
- root = await getJsonFromFile("p12/root.json", tilePackageFiles, source);
1670
+ root = await getJsonFromZip("p12/root.json", tilePackageFiles, source);
1432
1671
  root.type = "vtpk";
1433
1672
 
1434
1673
  const iteminfoFile = "esriinfo/iteminfo.xml";
@@ -1482,7 +1721,7 @@ async function getHeaderAndFileList(source) {
1482
1721
  }
1483
1722
  // Get and calculate the tilemap
1484
1723
  if (source.coverageCheck > 0 && tilePackageFiles["p12/tilemap/root.json"]) {
1485
- const tilemap = await getJsonFromFile(
1724
+ const tilemap = await getJsonFromZip(
1486
1725
  "p12/tilemap/root.json",
1487
1726
  tilePackageFiles,
1488
1727
  source,
@@ -1496,7 +1735,7 @@ async function getHeaderAndFileList(source) {
1496
1735
  }
1497
1736
  }
1498
1737
 
1499
- const header = jsonToHeader(root, tilePackageFiles, resp.etag);
1738
+ const header = jsonToHeader(root, tilePackageFiles, etag);
1500
1739
  return [header, ""];
1501
1740
  }
1502
1741
 
@@ -1723,7 +1962,12 @@ function calculateFilename(z, x, y, header) {
1723
1962
  const baseCol = Math.floor(x / 128) * 128;
1724
1963
  const rowHex = baseRow.toString(16).padStart(4, "0");
1725
1964
  const colHex = baseCol.toString(16).padStart(4, "0");
1726
- let basePath = header.type === "tpkx" ? "tile" : "p12/tile";
1965
+ let basePath;
1966
+ if (header.type === "tpkx") {
1967
+ basePath = `tile`;
1968
+ } else {
1969
+ basePath = `p12/tile`;
1970
+ }
1727
1971
  return `${basePath}/L${zoom}/R${rowHex}C${colHex}.bundle`;
1728
1972
  }
1729
1973
 
@@ -1733,6 +1977,10 @@ function getTileInfoFromTileIndex(tileIndex, z, x, y) {
1733
1977
  return tileIndex[row][col];
1734
1978
  }
1735
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
+ */
1736
1984
  class EtagMismatch extends Error {
1737
1985
  constructor(message) {
1738
1986
  super(message);
@@ -1745,25 +1993,23 @@ class TilePackage {
1745
1993
  // Default coverageCheck enabled; disable with coverageCheck:false
1746
1994
  this.coverageCheck = 1;
1747
1995
  if (options && options.coverageCheck === false) this.coverageCheck = 0;
1748
- // Max dz guard (skip subdivision beyond this): default 8
1749
- this.maxDz =
1750
- options && typeof options.maxDz === "number" ? options.maxDz : 8;
1751
1996
  if (typeof source === "string") {
1752
- const opts = this.coverageCheck
1753
- ? { coverageCheck: this.coverageCheck }
1754
- : undefined;
1755
- this.source = new FetchSource(source, opts);
1997
+ this.source = new FetchSource(source, this.coverageCheck);
1756
1998
  } else {
1757
1999
  this.source = source;
1758
- // Propagate default coverageCheck to existing Source instances if undefined
1759
- if (this.source && this.source.coverageCheck === undefined) {
1760
- this.source.coverageCheck = this.coverageCheck;
1761
- }
1762
2000
  }
1763
- this.decompress =
1764
- options && options.decompress ? options.decompress : defaultDecompress;
1765
- this.cache =
1766
- options && options.cache ? options.cache : new SharedPromiseCache();
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
+ }
1767
2013
  }
1768
2014
 
1769
2015
  async getHeader() {
@@ -1880,6 +2126,7 @@ class TilePackage {
1880
2126
 
1881
2127
  async getMetadataAttempt() {
1882
2128
  const header = await this.cache.getHeader(this.source);
2129
+
1883
2130
  let metadata = {};
1884
2131
  if (header.packageType === "vtpk") {
1885
2132
  const resp = await this.source.getBytes(
@@ -1888,10 +2135,12 @@ class TilePackage {
1888
2135
  undefined,
1889
2136
  header.etag,
1890
2137
  );
2138
+
1891
2139
  const decoder = new TextDecoder("utf-8");
1892
2140
  metadata = JSON.parse(decoder.decode(resp.data));
1893
2141
  }
1894
2142
  metadata.name = header.name;
2143
+
1895
2144
  return metadata;
1896
2145
  }
1897
2146
 
@@ -1909,7 +2158,10 @@ class TilePackage {
1909
2158
 
1910
2159
  async getResourceAttempt(file, signal) {
1911
2160
  const header = await this.cache.getHeader(this.source);
1912
- if (!header.files[file]) return undefined;
2161
+ if (!header.files[file]) {
2162
+ return undefined;
2163
+ }
2164
+
1913
2165
  const resource = await this.cache.getResource(
1914
2166
  this.source,
1915
2167
  file,
@@ -1939,59 +2191,46 @@ class TilePackage {
1939
2191
  const header = await this.cache.getHeader(this.source);
1940
2192
  const sourceKey = this.source.getKey();
1941
2193
  if (header.packageType === "vtpk") {
1942
- const metadata = await getJsonFromFile(
2194
+ const metadata = await getJsonFromZip(
1943
2195
  "p12/root.json",
1944
2196
  header.files,
1945
2197
  this.source,
1946
2198
  );
1947
- const style = await getJsonFromFile(
2199
+ const style = await getJsonFromZip(
1948
2200
  "p12/resources/styles/root.json",
1949
2201
  header.files,
1950
2202
  this.source,
1951
2203
  );
1952
- if (style.sources && style.sources.esri) {
1953
- delete style.sources.esri.url;
1954
- style.sources.esri.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
1955
- style.sources.esri.type = style.sources.esri.type || "vector";
1956
- style.sources.esri.minzoom = header.minZoom || 0;
1957
- style.sources.esri.maxzoom = header.maxZoom || 22;
1958
- }
1959
- for (const k in style.sources) {
1960
- const src = style.sources[k];
1961
- if (
1962
- src &&
1963
- typeof src.url === "string" &&
1964
- src.url.startsWith("tilepackage://")
1965
- ) {
1966
- delete src.url;
1967
- if (!src.tiles)
1968
- src.tiles = [`tilepackage://${sourceKey}/{z}/{x}/{y}`];
1969
- }
1970
- }
1971
- 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) {
1972
2208
  style.sources.esri.attribution = metadata.copyrightText;
2209
+ }
1973
2210
  style.glyphs = `tilepackage://${sourceKey}/{fontstack}/{range}`;
1974
2211
  style.sprite = `tilepackage://${sourceKey}/sprite`;
1975
- if (this.debug)
1976
- console.debug(
1977
- "[tilepackage style] rewritten esri source",
1978
- style.sources.esri,
1979
- );
1980
2212
  return style;
1981
- }
1982
- return {
1983
- version: 8,
1984
- sources: {
1985
- esri: {
1986
- type: "raster",
1987
- tileSize: header.tileSize || 256,
1988
- tiles: [`tilepackage://${sourceKey}/{z}/{x}/{y}`],
1989
- maxzoom: header.maxZoom || 22,
1990
- minzoom: header.minZoom || 0,
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
+ },
1991
2223
  },
1992
- },
1993
- layers: [{ id: "tilepackageraster", type: "raster", source: "esri" }],
1994
- };
2224
+ layers: [
2225
+ {
2226
+ id: "tilepackageraster",
2227
+ type: "raster",
2228
+ source: "esri",
2229
+ },
2230
+ ],
2231
+ };
2232
+ return style;
2233
+ }
1995
2234
  }
1996
2235
 
1997
2236
  async getStyle() {
@@ -2010,6 +2249,7 @@ class TilePackage {
2010
2249
  const header = await this.getHeader();
2011
2250
  const metadata = await this.getMetadata();
2012
2251
  const ext = header.tileType;
2252
+
2013
2253
  const tileJson = {
2014
2254
  tilejson: "3.0.0",
2015
2255
  scheme: "xyz",
@@ -2018,11 +2258,21 @@ class TilePackage {
2018
2258
  version: header.version,
2019
2259
  bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat],
2020
2260
  };
2021
- if (metadata.vector_layers) tileJson.vector_layers = metadata.vector_layers;
2022
- if (metadata.attribution) tileJson.attribution = metadata.attribution;
2023
- if (metadata.description) tileJson.description = metadata.description;
2024
- if (header.minZoom) tileJson.minzoom = header.minZoom;
2025
- if (header.maxZoom) tileJson.maxzoom = header.maxZoom;
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
+ }
2026
2276
  return tileJson;
2027
2277
  }
2028
2278
  }