map-gl-offline 0.6.0 → 0.7.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/dist/index.umd.js CHANGED
@@ -29,7 +29,7 @@
29
29
  */
30
30
  // IndexedDB Configuration
31
31
  const DB_NAME = 'offline-map-db';
32
- const DB_VERSION = 3;
32
+ const DB_VERSION = 4;
33
33
  // Store Names (regions are stored inside styles.regions[], not as a separate store)
34
34
  const STORE_NAMES = {
35
35
  TILES: 'tiles',
@@ -37,6 +37,7 @@
37
37
  SPRITES: 'sprites',
38
38
  GLYPHS: 'glyphs',
39
39
  FONTS: 'fonts',
40
+ MODELS: 'models',
40
41
  };
41
42
  // Download Configuration
42
43
  const DOWNLOAD_DEFAULTS = {
@@ -244,7 +245,7 @@
244
245
  * Called during initial database creation or when stores are missing.
245
246
  */
246
247
  function createStores(db) {
247
- const stores = ['regions', 'tiles', 'styles', 'sprites', 'glyphs', 'fonts'];
248
+ const stores = ['regions', 'tiles', 'styles', 'sprites', 'glyphs', 'fonts', 'models'];
248
249
  for (const storeName of stores) {
249
250
  if (!db.objectStoreNames.contains(storeName)) {
250
251
  db.createObjectStore(storeName, { keyPath: 'key' });
@@ -326,6 +327,7 @@
326
327
  * - sprites: Sprite images and JSON
327
328
  * - glyphs: Font glyph data
328
329
  * - fonts: Font files
330
+ * - models: 3D model files (.glb) for Mapbox Standard tree/turbine layers
329
331
  * - regions: (deprecated) Legacy region storage, migrated to styles.regions[]
330
332
  *
331
333
  * @example
@@ -345,6 +347,9 @@
345
347
  if (oldVersion > 0 && oldVersion < 3) {
346
348
  migrateRegionsToStyles(transaction);
347
349
  }
350
+ // Migration: v3 -> v4
351
+ // Adds the `models` store for Mapbox Standard 3D model assets.
352
+ // No data migration needed — createStores above handles it.
348
353
  },
349
354
  });
350
355
  }
@@ -1201,6 +1206,7 @@
1201
1206
  // idb://{downloadId}/tile/{sourceKey}/{url}
1202
1207
  // idb://{downloadId}/glyph/{fontstack}/{range}.pbf
1203
1208
  // idb://{downloadId}/sprite/{spriteName}
1209
+ // idb://{styleId}/model/{modelName}
1204
1210
  // idb://{downloadId}/tilesjson/{url}
1205
1211
  // Cache for region ID to style mapping to avoid repeated DB queries
1206
1212
  const regionToStyleCache = new Map();
@@ -1572,6 +1578,33 @@
1572
1578
  }
1573
1579
  break;
1574
1580
  }
1581
+ case 'model': {
1582
+ // Model URLs are rewritten by patchStyleForOffline to:
1583
+ // idb://{styleId}/model/{modelName}
1584
+ // Models are keyed by {styleId}::model::{modelName} in the store.
1585
+ // Mirror the sprite resolution fallback: try the style ID first,
1586
+ // then the download/region ID (in case the request came through a
1587
+ // region-scoped URL).
1588
+ const styleEntry = await findStyleByRegionId(db, downloadId);
1589
+ const actualStyleId = styleEntry?.key || downloadId;
1590
+ const candidates = Array.from(new Set([
1591
+ `${actualStyleId}::model::${decodedResourcePath}`,
1592
+ `${downloadId}::model::${decodedResourcePath}`,
1593
+ ]));
1594
+ idbLogger.debug(`Model candidates for "${decodedResourcePath}":`, candidates);
1595
+ for (const candidateKey of candidates) {
1596
+ const resource = await db.get('models', candidateKey);
1597
+ if (resource?.data) {
1598
+ idbLogger.debug(`Found model using key: ${candidateKey}`);
1599
+ return new Response(resource.data, {
1600
+ status: 200,
1601
+ headers: { 'Content-Type': resource.contentType || 'model/gltf-binary' },
1602
+ });
1603
+ }
1604
+ }
1605
+ idbLogger.warn(`Model not found, tried keys: ${candidates.join(', ')}`);
1606
+ break;
1607
+ }
1575
1608
  case 'tilesjson': {
1576
1609
  idbLogger.debug(`Looking for tilejson with downloadId: ${downloadId}, resourcePath: ${decodedResourcePath}`);
1577
1610
  // First try direct lookup (for style-level downloads)
@@ -2027,14 +2060,29 @@
2027
2060
  });
2028
2061
  }
2029
2062
  }
2030
- // Patch top-level models (Mapbox Standard 3D landmarks)
2063
+ // Patch top-level models (Mapbox Standard 3D trees / wind turbines).
2064
+ // Two shapes exist in the wild:
2065
+ // - Mapbox Standard: `{ "maple1-lod1": "mapbox://models/mapbox/maple1-v4-lod1.glb" }` (string values)
2066
+ // - Older/generic: `{ "name": { "uri": "mapbox://..." } }` (object values)
2067
+ // Models are keyed on the style ID (like sprites) so they can be shared
2068
+ // across regions.
2031
2069
  if (style.models) {
2032
- for (const [modelId, modelConfig] of Object.entries(style.models)) {
2033
- if (modelConfig.uri) {
2034
- modelConfig.uri = `idb://${downloadId}/model/${modelId}`;
2070
+ const modelBaseId = styleId || downloadId;
2071
+ const models = style.models;
2072
+ let patchedCount = 0;
2073
+ for (const [modelId, value] of Object.entries(models)) {
2074
+ if (typeof value === 'string') {
2075
+ models[modelId] = `idb://${modelBaseId}/model/${modelId}`;
2076
+ patchedCount++;
2077
+ }
2078
+ else if (value && typeof value === 'object' && 'uri' in value && value.uri) {
2079
+ value.uri = `idb://${modelBaseId}/model/${modelId}`;
2080
+ patchedCount++;
2035
2081
  }
2036
2082
  }
2037
- styleLogger.debug(`Patched ${Object.keys(style.models).length} model URIs`);
2083
+ if (patchedCount > 0) {
2084
+ styleLogger.debug(`Patched ${patchedCount} model URIs (styleId: ${modelBaseId})`);
2085
+ }
2038
2086
  }
2039
2087
  styleLogger.debug(`Final patched style:`, style);
2040
2088
  return style;
@@ -2602,12 +2650,22 @@
2602
2650
  });
2603
2651
  }
2604
2652
  }
2605
- // Convert models
2653
+ // Convert models. Two shapes in the wild:
2654
+ // - Mapbox Standard: `{ name: "idb://..." }` (string value)
2655
+ // - Older/generic: `{ name: { uri: "idb://..." } }` (object value)
2606
2656
  if (converted.models) {
2607
- for (const modelKey of Object.keys(converted.models)) {
2608
- const model = converted.models[modelKey];
2609
- if (model.uri && typeof model.uri === 'string' && model.uri.startsWith('idb://')) {
2610
- model.uri = replace(model.uri);
2657
+ const models = converted.models;
2658
+ for (const modelKey of Object.keys(models)) {
2659
+ const value = models[modelKey];
2660
+ if (typeof value === 'string') {
2661
+ if (value.startsWith('idb://')) {
2662
+ models[modelKey] = replace(value);
2663
+ }
2664
+ }
2665
+ else if (value && typeof value === 'object') {
2666
+ if (typeof value.uri === 'string' && value.uri.startsWith('idb://')) {
2667
+ value.uri = replace(value.uri);
2668
+ }
2611
2669
  }
2612
2670
  }
2613
2671
  }
@@ -4928,7 +4986,15 @@
4928
4986
  deletedSprites++;
4929
4987
  }
4930
4988
  }
4931
- regionLogger$1.info(`Deleted style resources: ${deletedFonts} fonts, ${deletedGlyphs} glyphs, ${deletedSprites} sprites`);
4989
+ let deletedModels = 0;
4990
+ const modelTx = db.transaction('models', 'readwrite');
4991
+ for await (const cursor of modelTx.store) {
4992
+ if (resourceKeyBelongsToStyle(cursor.value.key, styleId)) {
4993
+ await cursor.delete();
4994
+ deletedModels++;
4995
+ }
4996
+ }
4997
+ regionLogger$1.info(`Deleted style resources: ${deletedFonts} fonts, ${deletedGlyphs} glyphs, ${deletedSprites} sprites, ${deletedModels} models`);
4932
4998
  }
4933
4999
  /**
4934
5000
  * Delete all tiles for a style
@@ -5036,7 +5102,7 @@
5036
5102
  if (!region.styleUrl) {
5037
5103
  throw new Error('Region must have a styleUrl');
5038
5104
  }
5039
- const { onProgress, provider = 'auto', accessToken, skipGlyphs = false, skipSprites = false, glyphRanges, tileOptions, } = options;
5105
+ const { onProgress, provider = 'auto', accessToken, skipGlyphs = false, skipSprites = false, skipModels = false, glyphRanges, tileOptions, } = options;
5040
5106
  const emit = (phase, completed, total, message) => {
5041
5107
  if (!onProgress)
5042
5108
  return;
@@ -5091,8 +5157,13 @@
5091
5157
  const spriteSources = normalizeSpriteProperty(originalSpriteUrl);
5092
5158
  if (spriteSources.length > 0) {
5093
5159
  const { downloadSprites } = await Promise.resolve().then(function () { return spriteService$1; });
5094
- const suffixes = ['.json', '.png', '@2x.json', '@2x.png'];
5095
- const totalFiles = spriteSources.length * suffixes.length;
5160
+ // Standard four sprite variants. For Mapbox Standard, an `iconset.pbf`
5161
+ // sibling is also served under the same /styles/v1/.../<hash>/ path
5162
+ // — we detect that case per-source below and append it to the list.
5163
+ const baseSuffixes = ['.json', '.png', '@2x.json', '@2x.png'];
5164
+ // Estimate total files assuming iconset is always included (the actual
5165
+ // number may be smaller; the emit helper clamps progress to total).
5166
+ const totalFiles = spriteSources.length * (baseSuffixes.length + 1);
5096
5167
  let completed = 0;
5097
5168
  emit('sprites', 0, totalFiles, 'Downloading sprites');
5098
5169
  for (const source of spriteSources) {
@@ -5101,9 +5172,27 @@
5101
5172
  spriteBase = resolveMapboxUrl(spriteBase, effectiveAccessToken);
5102
5173
  }
5103
5174
  const qIndex = spriteBase.indexOf('?');
5104
- const spriteUrls = suffixes.map(suffix => qIndex !== -1
5105
- ? spriteBase.slice(0, qIndex) + suffix + spriteBase.slice(qIndex)
5106
- : spriteBase + suffix);
5175
+ const suffixes = [...baseSuffixes];
5176
+ // Mapbox Standard serves an iconset.pbf alongside the sprite under
5177
+ // /styles/v1/{owner}/{style}/{hash}/sprite the sibling file is
5178
+ // /styles/v1/{owner}/{style}/{hash}/iconset.pbf. The last path
5179
+ // segment is `sprite`, so replacing it with `iconset.pbf` works.
5180
+ const pathWithoutQuery = qIndex !== -1 ? spriteBase.slice(0, qIndex) : spriteBase;
5181
+ const isMapboxStandardSprite = /api\.mapbox\.com\/styles\/v1\/.+\/sprite$/.test(pathWithoutQuery);
5182
+ if (isMapboxStandardSprite) {
5183
+ // The path-rewrite suffix replaces the trailing `sprite` segment.
5184
+ suffixes.push('__ICONSET__');
5185
+ }
5186
+ const spriteUrls = suffixes.map(suffix => {
5187
+ if (suffix === '__ICONSET__') {
5188
+ // Replace trailing `sprite` with `iconset.pbf`, preserving query.
5189
+ const base = pathWithoutQuery.replace(/sprite$/, 'iconset.pbf');
5190
+ return qIndex !== -1 ? base + spriteBase.slice(qIndex) : base;
5191
+ }
5192
+ return qIndex !== -1
5193
+ ? spriteBase.slice(0, qIndex) + suffix + spriteBase.slice(qIndex)
5194
+ : spriteBase + suffix;
5195
+ });
5107
5196
  try {
5108
5197
  const result = await downloadSprites(spriteUrls, styleId, {
5109
5198
  enableValidation: true,
@@ -5149,7 +5238,42 @@
5149
5238
  }
5150
5239
  }
5151
5240
  }
5152
- // 4. Tilesuse the stored (source-embedded) style, which still has HTTP tile URLs
5241
+ // 4. ModelsMapbox Standard's `style.models` references 3D tree /
5242
+ // turbine .glb assets. Two value shapes exist in the wild
5243
+ // (plain string or `{ uri }`) — we accept both.
5244
+ let modelResult;
5245
+ if (!skipModels && storedStyle.models) {
5246
+ const rawModels = storedStyle.models;
5247
+ const resolved = {};
5248
+ for (const [name, value] of Object.entries(rawModels)) {
5249
+ const uri = typeof value === 'string' ? value : value?.uri;
5250
+ if (!uri)
5251
+ continue;
5252
+ if (uri.startsWith('idb://'))
5253
+ continue; // already patched
5254
+ const httpUrl = isMapboxProtocol(uri) && effectiveAccessToken
5255
+ ? resolveMapboxUrl(uri, effectiveAccessToken)
5256
+ : uri;
5257
+ if (httpUrl.startsWith('http://') || httpUrl.startsWith('https://')) {
5258
+ resolved[name] = httpUrl;
5259
+ }
5260
+ }
5261
+ if (Object.keys(resolved).length > 0) {
5262
+ const { downloadModels } = await Promise.resolve().then(function () { return modelService$1; });
5263
+ emit('models', 0, Object.keys(resolved).length, 'Downloading 3D models');
5264
+ try {
5265
+ modelResult = await downloadModels(resolved, styleId, {
5266
+ onProgress: (progress) => {
5267
+ emit('models', progress.completed, progress.total, 'Downloading 3D models');
5268
+ },
5269
+ });
5270
+ }
5271
+ catch (error) {
5272
+ regionLogger$1.warn('Model download failed (non-fatal):', error);
5273
+ }
5274
+ }
5275
+ }
5276
+ // 5. Tiles — use the stored (source-embedded) style, which still has HTTP tile URLs
5153
5277
  const { downloadTiles } = await Promise.resolve().then(function () { return tileService$1; });
5154
5278
  const regionForTiles = { ...region, styleId };
5155
5279
  emit('tiles', 0, 100, 'Downloading tiles');
@@ -5162,7 +5286,7 @@
5162
5286
  tileOptions?.onProgress?.(progress);
5163
5287
  },
5164
5288
  });
5165
- // 5. Metadata — must run last, since addRegion patches style URLs to idb://.
5289
+ // 6. Metadata — must run last, since addRegion patches style URLs to idb://.
5166
5290
  // Do NOT auto-fill tileExtension from tileResult: that's only the first
5167
5291
  // source's extension, and addRegion feeds it to patchStyleForOffline which
5168
5292
  // would override ALL sources — breaking mixed raster+vector styles. The
@@ -5178,6 +5302,7 @@
5178
5302
  styleResult,
5179
5303
  spriteResults,
5180
5304
  glyphResult,
5305
+ modelResult,
5181
5306
  tileResult,
5182
5307
  };
5183
5308
  }
@@ -6316,10 +6441,15 @@
6316
6441
  tilesLength: config.tiles ? config.tiles.length : 0,
6317
6442
  url: config.url,
6318
6443
  });
6319
- // Handle tile-based sources (vector, raster, raster-dem, batched-model)
6444
+ // Handle tile-based sources (vector, raster, raster-dem, batched-model,
6445
+ // raster-array). `raster-array` is used by Mapbox Standard for layers
6446
+ // like `mapbox-landmarks` (mapbox.mapbox-landmark-icons-v1) — the tiles
6447
+ // are fetched from the same /v4/ endpoint as other tilesets, so the
6448
+ // TileJSON resolution path below handles them uniformly.
6320
6449
  if (config.type === 'vector' ||
6321
6450
  config.type === 'raster' ||
6322
6451
  config.type === 'raster-dem' ||
6452
+ config.type === 'raster-array' ||
6323
6453
  config.type === 'batched-model') {
6324
6454
  // Handle direct tile URLs in the source config
6325
6455
  if (config.tiles && Array.isArray(config.tiles) && config.tiles.length > 0) {
@@ -7052,6 +7182,201 @@
7052
7182
  verifyAndRepairGlyphs: verifyAndRepairGlyphs
7053
7183
  });
7054
7184
 
7185
+ const modelLogger = logger.scope('ModelService');
7186
+ /**
7187
+ * Build the storage key for a model. Kept consistent with sprite/glyph
7188
+ * conventions: `{styleId}::model::{modelName}`.
7189
+ */
7190
+ function modelKey(styleId, modelName) {
7191
+ return `${styleId}::model::${modelName}`;
7192
+ }
7193
+ /** True when the given key belongs to the given styleId's model store prefix. */
7194
+ function modelKeyBelongsToStyle(key, styleId) {
7195
+ return key.startsWith(`${styleId}::model::`);
7196
+ }
7197
+ /**
7198
+ * Service for downloading, storing, and serving Mapbox 3D model (.glb) files.
7199
+ *
7200
+ * Mapbox Standard declares 32 models at the top of `style.models`:
7201
+ *
7202
+ * ```json
7203
+ * {
7204
+ * "maple1-lod1": "mapbox://models/mapbox/maple1-v4-lod1.glb",
7205
+ * ...
7206
+ * }
7207
+ * ```
7208
+ *
7209
+ * `model` layers (e.g. `trees`, `wind-turbine-towers`) pick one by name at
7210
+ * render time. For offline use each referenced URL is fetched and stored
7211
+ * here, and `patchStyleForOffline` rewrites the dictionary entries to
7212
+ * `idb://{styleId}/model/{name}` URLs.
7213
+ */
7214
+ class ModelService {
7215
+ db = dbPromise;
7216
+ /**
7217
+ * Download the set of models referenced by `style.models` for one style.
7218
+ *
7219
+ * @param models `{ modelName: resolvedHttpUrl }` — URLs must already be
7220
+ * resolved (mapbox:// URLs should be resolved by the caller).
7221
+ * @param styleId The owning style's key.
7222
+ */
7223
+ async downloadModels(models, styleId, options = {}) {
7224
+ const db = await this.db;
7225
+ const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, } = options;
7226
+ const entries = Object.entries(models);
7227
+ const progressTracker = createProgressTracker(entries.length);
7228
+ const result = {
7229
+ totalModels: entries.length,
7230
+ downloadedModels: 0,
7231
+ skippedModels: 0,
7232
+ failedModels: 0,
7233
+ totalSize: 0,
7234
+ errors: [],
7235
+ };
7236
+ const emit = () => onProgress?.(progressTracker.getProgress());
7237
+ emit();
7238
+ if (entries.length === 0)
7239
+ return result;
7240
+ // Pre-compute existing keys for skipExisting
7241
+ const existingKeys = new Set();
7242
+ if (skipExisting) {
7243
+ const tx = db.transaction('models', 'readonly');
7244
+ for await (const cursor of tx.store) {
7245
+ existingKeys.add(cursor.value.key);
7246
+ }
7247
+ }
7248
+ await processBatch(entries, async ([modelName, url]) => {
7249
+ const key = modelKey(styleId, modelName);
7250
+ const label = `${styleId}::${modelName}`;
7251
+ if (skipExisting && existingKeys.has(key)) {
7252
+ result.skippedModels++;
7253
+ progressTracker.update(1, label);
7254
+ emit();
7255
+ return;
7256
+ }
7257
+ try {
7258
+ const response = await fetchResourceWithRetry(url, {
7259
+ retries: maxRetries,
7260
+ timeout: timeoutMs,
7261
+ proxyType: 'tiles',
7262
+ });
7263
+ if (response.type === 'json') {
7264
+ throw new Error('Unexpected JSON response for model');
7265
+ }
7266
+ const data = response.data;
7267
+ const contentType = ('contentType' in response && response.contentType) || 'model/gltf-binary';
7268
+ const entry = {
7269
+ key,
7270
+ data,
7271
+ contentType,
7272
+ size: data.byteLength,
7273
+ url,
7274
+ styleId,
7275
+ modelName,
7276
+ lastModified: Date.now(),
7277
+ downloadedAt: new Date().toISOString(),
7278
+ expires: response.expires,
7279
+ };
7280
+ await db.put('models', entry);
7281
+ result.downloadedModels++;
7282
+ result.totalSize += data.byteLength;
7283
+ progressTracker.update(1, label);
7284
+ }
7285
+ catch (err) {
7286
+ const message = err instanceof Error ? err.message : String(err);
7287
+ result.failedModels++;
7288
+ result.errors.push({ url, error: message });
7289
+ modelLogger.warn(`Failed to download model "${modelName}" from ${url}:`, err);
7290
+ progressTracker.update(1, label, message);
7291
+ }
7292
+ emit();
7293
+ }, { batchSize });
7294
+ modelLogger.info(`Models downloaded for style ${styleId}: ${result.downloadedModels} new, ${result.skippedModels} skipped, ${result.failedModels} failed`);
7295
+ return result;
7296
+ }
7297
+ /** Retrieve a single model by `{styleId, modelName}`. */
7298
+ async getModel(styleId, modelName) {
7299
+ const db = await this.db;
7300
+ return db.get('models', modelKey(styleId, modelName));
7301
+ }
7302
+ /** Aggregate stats across all stored models. */
7303
+ async getModelStats() {
7304
+ const db = await this.db;
7305
+ const stats = {
7306
+ count: 0,
7307
+ totalSize: 0,
7308
+ averageSize: 0,
7309
+ models: [],
7310
+ modelsByStyle: {},
7311
+ };
7312
+ const tx = db.transaction('models', 'readonly');
7313
+ for await (const cursor of tx.store) {
7314
+ const m = cursor.value;
7315
+ stats.count++;
7316
+ stats.totalSize += m.size;
7317
+ stats.models.push({ name: m.modelName, size: m.size, lastModified: m.lastModified });
7318
+ stats.modelsByStyle[m.styleId] = (stats.modelsByStyle[m.styleId] ?? 0) + 1;
7319
+ }
7320
+ stats.averageSize = stats.count > 0 ? stats.totalSize / stats.count : 0;
7321
+ return stats;
7322
+ }
7323
+ /**
7324
+ * Delete models older than `maxAge` days. Defaults to 30.
7325
+ */
7326
+ async cleanupOldModels(maxAge = 30) {
7327
+ const db = await this.db;
7328
+ const cutoff = Date.now() - maxAge * 24 * 60 * 60 * 1000;
7329
+ let deleted = 0;
7330
+ const tx = db.transaction('models', 'readwrite');
7331
+ for await (const cursor of tx.store) {
7332
+ if (cursor.value.lastModified < cutoff) {
7333
+ await cursor.delete();
7334
+ deleted++;
7335
+ }
7336
+ }
7337
+ return deleted;
7338
+ }
7339
+ /**
7340
+ * Basic integrity check: remove entries with empty/missing data.
7341
+ */
7342
+ async verifyAndRepairModels() {
7343
+ const db = await this.db;
7344
+ let verified = 0;
7345
+ let removed = 0;
7346
+ const tx = db.transaction('models', 'readwrite');
7347
+ for await (const cursor of tx.store) {
7348
+ const m = cursor.value;
7349
+ if (!m.data || m.data.byteLength === 0) {
7350
+ await cursor.delete();
7351
+ removed++;
7352
+ }
7353
+ else {
7354
+ verified++;
7355
+ }
7356
+ }
7357
+ return { verified, repaired: 0, removed };
7358
+ }
7359
+ }
7360
+ // Singleton + convenience exports, matching other service modules.
7361
+ const modelService = new ModelService();
7362
+ const downloadModels = (models, styleId, options) => modelService.downloadModels(models, styleId, options);
7363
+ const getModel = (styleId, modelName) => modelService.getModel(styleId, modelName);
7364
+ const getModelStats = () => modelService.getModelStats();
7365
+ const cleanupOldModels = (maxAge) => modelService.cleanupOldModels(maxAge);
7366
+ const verifyAndRepairModels = () => modelService.verifyAndRepairModels();
7367
+
7368
+ var modelService$1 = /*#__PURE__*/Object.freeze({
7369
+ __proto__: null,
7370
+ ModelService: ModelService,
7371
+ cleanupOldModels: cleanupOldModels,
7372
+ downloadModels: downloadModels,
7373
+ getModel: getModel,
7374
+ getModelStats: getModelStats,
7375
+ modelKeyBelongsToStyle: modelKeyBelongsToStyle,
7376
+ modelService: modelService,
7377
+ verifyAndRepairModels: verifyAndRepairModels
7378
+ });
7379
+
7055
7380
  class ResourceService {
7056
7381
  // Tile Management Methods
7057
7382
  async downloadTilesWithOptions(region, style, styleId, options = {}) {
@@ -7120,6 +7445,19 @@
7120
7445
  async verifyAndRepairGlyphs() {
7121
7446
  return verifyAndRepairGlyphs();
7122
7447
  }
7448
+ // 3D Model Management Methods
7449
+ async downloadModelsWithOptions(models, styleId, options = {}) {
7450
+ return downloadModels(models, styleId, options);
7451
+ }
7452
+ async getModelStats() {
7453
+ return getModelStats();
7454
+ }
7455
+ async cleanupOldModels(options) {
7456
+ return cleanupOldModels(options?.maxAge);
7457
+ }
7458
+ async verifyAndRepairModels() {
7459
+ return verifyAndRepairModels();
7460
+ }
7123
7461
  }
7124
7462
 
7125
7463
  // The underlying stats functions already iterate every entry in their
@@ -7914,6 +8252,10 @@
7914
8252
  loadGlyphsForStyle: (...args) => services.resourceService.loadGlyphsForStyle(...args),
7915
8253
  cleanupOldGlyphs: (...args) => services.resourceService.cleanupOldGlyphs(...args),
7916
8254
  verifyAndRepairGlyphs: (...args) => services.resourceService.verifyAndRepairGlyphs(...args),
8255
+ downloadModelsWithOptions: (...args) => services.resourceService.downloadModelsWithOptions(...args),
8256
+ getModelStats: (...args) => services.resourceService.getModelStats(...args),
8257
+ cleanupOldModels: (...args) => services.resourceService.cleanupOldModels(...args),
8258
+ verifyAndRepairModels: (...args) => services.resourceService.verifyAndRepairModels(...args),
7917
8259
  });
7918
8260
 
7919
8261
  const createAnalyticsManagement = (services, deps) => ({
@@ -11560,10 +11902,12 @@
11560
11902
  panelLogger.debug(`Fixed legacy source ${sourceId}: added tiles, removed url`);
11561
11903
  }
11562
11904
  }
11563
- // Apply maxzoom to all tile sources (including batched-model for 3D buildings)
11905
+ // Apply maxzoom to all tile sources (including batched-model for 3D
11906
+ // buildings and raster-array for Mapbox Standard landmark icons).
11564
11907
  if (src.type === 'vector' ||
11565
11908
  src.type === 'raster' ||
11566
11909
  src.type === 'raster-dem' ||
11910
+ src.type === 'raster-array' ||
11567
11911
  src.type === 'batched-model') {
11568
11912
  const originalMaxzoom = src.maxzoom;
11569
11913
  // Use the lower of region maxZoom and source's original maxzoom so we
@@ -13644,6 +13988,7 @@
13644
13988
  exports.MAPBOX_CLASSIC_STYLES = MAPBOX_CLASSIC_STYLES;
13645
13989
  exports.MAP_PROVIDERS = MAP_PROVIDERS;
13646
13990
  exports.MaintenanceService = MaintenanceService;
13991
+ exports.ModelService = ModelService;
13647
13992
  exports.OfflineManagerControl = OfflineManagerControl;
13648
13993
  exports.OfflineMapDBVersionError = OfflineMapDBVersionError;
13649
13994
  exports.OfflineMapManager = OfflineMapManager;
@@ -13666,6 +14011,7 @@
13666
14011
  exports.cleanupExpiredTiles = cleanupExpiredTiles;
13667
14012
  exports.cleanupOldFonts = cleanupOldFonts;
13668
14013
  exports.cleanupOldGlyphs = cleanupOldGlyphs;
14014
+ exports.cleanupOldModels = cleanupOldModels;
13669
14015
  exports.cleanupOldSprites = cleanupOldSprites;
13670
14016
  exports.cleanupOldStyles = cleanupOldStyles;
13671
14017
  exports.cleanupOldTiles = cleanupOldTiles;
@@ -13686,6 +14032,7 @@
13686
14032
  exports.detectStyleProvider = detectStyleProvider;
13687
14033
  exports.downloadFonts = downloadFonts;
13688
14034
  exports.downloadGlyphs = downloadGlyphs;
14035
+ exports.downloadModels = downloadModels;
13689
14036
  exports.downloadSprites = downloadSprites;
13690
14037
  exports.downloadStyleWithProvider = downloadStyleWithProvider;
13691
14038
  exports.downloadStyles = downloadStyles;
@@ -13706,6 +14053,8 @@
13706
14053
  exports.getGlyphAnalytics = getGlyphAnalytics;
13707
14054
  exports.getGlyphStats = getGlyphStats;
13708
14055
  exports.getIcon = getIcon;
14056
+ exports.getModel = getModel;
14057
+ exports.getModelStats = getModelStats;
13709
14058
  exports.getRegionAnalytics = getRegionAnalytics;
13710
14059
  exports.getSpriteAnalytics = getSpriteAnalytics;
13711
14060
  exports.getSpriteStats = getSpriteStats;
@@ -13725,6 +14074,8 @@
13725
14074
  exports.loadStyleById = loadStyleById;
13726
14075
  exports.loadStyles = loadStyles;
13727
14076
  exports.logger = logger;
14077
+ exports.modelKeyBelongsToStyle = modelKeyBelongsToStyle;
14078
+ exports.modelService = modelService;
13728
14079
  exports.normalizeSpriteProperty = normalizeSpriteProperty;
13729
14080
  exports.normalizeStyleUrl = normalizeStyleUrl;
13730
14081
  exports.optimizeStorage = optimizeStorage;
@@ -13754,6 +14105,7 @@
13754
14105
  exports.validateZoomLevels = validateZoomLevels;
13755
14106
  exports.verifyAndRepairFonts = verifyAndRepairFonts;
13756
14107
  exports.verifyAndRepairGlyphs = verifyAndRepairGlyphs;
14108
+ exports.verifyAndRepairModels = verifyAndRepairModels;
13757
14109
  exports.verifyAndRepairSprites = verifyAndRepairSprites;
13758
14110
 
13759
14111
  Object.defineProperty(exports, '__esModule', { value: true });