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.js CHANGED
@@ -35,7 +35,7 @@ var tilebelt__namespace = /*#__PURE__*/_interopNamespaceDefault(tilebelt);
35
35
  */
36
36
  // IndexedDB Configuration
37
37
  const DB_NAME = 'offline-map-db';
38
- const DB_VERSION = 3;
38
+ const DB_VERSION = 4;
39
39
  // Store Names (regions are stored inside styles.regions[], not as a separate store)
40
40
  const STORE_NAMES = {
41
41
  TILES: 'tiles',
@@ -43,6 +43,7 @@ const STORE_NAMES = {
43
43
  SPRITES: 'sprites',
44
44
  GLYPHS: 'glyphs',
45
45
  FONTS: 'fonts',
46
+ MODELS: 'models',
46
47
  };
47
48
  // Download Configuration
48
49
  const DOWNLOAD_DEFAULTS = {
@@ -250,7 +251,7 @@ async function resetOfflineMapDB() {
250
251
  * Called during initial database creation or when stores are missing.
251
252
  */
252
253
  function createStores(db) {
253
- const stores = ['regions', 'tiles', 'styles', 'sprites', 'glyphs', 'fonts'];
254
+ const stores = ['regions', 'tiles', 'styles', 'sprites', 'glyphs', 'fonts', 'models'];
254
255
  for (const storeName of stores) {
255
256
  if (!db.objectStoreNames.contains(storeName)) {
256
257
  db.createObjectStore(storeName, { keyPath: 'key' });
@@ -332,6 +333,7 @@ function migrateRegionsToStyles(transaction) {
332
333
  * - sprites: Sprite images and JSON
333
334
  * - glyphs: Font glyph data
334
335
  * - fonts: Font files
336
+ * - models: 3D model files (.glb) for Mapbox Standard tree/turbine layers
335
337
  * - regions: (deprecated) Legacy region storage, migrated to styles.regions[]
336
338
  *
337
339
  * @example
@@ -351,6 +353,9 @@ async function openOfflineMapDB() {
351
353
  if (oldVersion > 0 && oldVersion < 3) {
352
354
  migrateRegionsToStyles(transaction);
353
355
  }
356
+ // Migration: v3 -> v4
357
+ // Adds the `models` store for Mapbox Standard 3D model assets.
358
+ // No data migration needed — createStores above handles it.
354
359
  },
355
360
  });
356
361
  }
@@ -1207,6 +1212,7 @@ const idbLogger = logger.scope('IDBFetch');
1207
1212
  // idb://{downloadId}/tile/{sourceKey}/{url}
1208
1213
  // idb://{downloadId}/glyph/{fontstack}/{range}.pbf
1209
1214
  // idb://{downloadId}/sprite/{spriteName}
1215
+ // idb://{styleId}/model/{modelName}
1210
1216
  // idb://{downloadId}/tilesjson/{url}
1211
1217
  // Cache for region ID to style mapping to avoid repeated DB queries
1212
1218
  const regionToStyleCache = new Map();
@@ -1578,6 +1584,33 @@ async function idbFetchHandler(url, init) {
1578
1584
  }
1579
1585
  break;
1580
1586
  }
1587
+ case 'model': {
1588
+ // Model URLs are rewritten by patchStyleForOffline to:
1589
+ // idb://{styleId}/model/{modelName}
1590
+ // Models are keyed by {styleId}::model::{modelName} in the store.
1591
+ // Mirror the sprite resolution fallback: try the style ID first,
1592
+ // then the download/region ID (in case the request came through a
1593
+ // region-scoped URL).
1594
+ const styleEntry = await findStyleByRegionId(db, downloadId);
1595
+ const actualStyleId = styleEntry?.key || downloadId;
1596
+ const candidates = Array.from(new Set([
1597
+ `${actualStyleId}::model::${decodedResourcePath}`,
1598
+ `${downloadId}::model::${decodedResourcePath}`,
1599
+ ]));
1600
+ idbLogger.debug(`Model candidates for "${decodedResourcePath}":`, candidates);
1601
+ for (const candidateKey of candidates) {
1602
+ const resource = await db.get('models', candidateKey);
1603
+ if (resource?.data) {
1604
+ idbLogger.debug(`Found model using key: ${candidateKey}`);
1605
+ return new Response(resource.data, {
1606
+ status: 200,
1607
+ headers: { 'Content-Type': resource.contentType || 'model/gltf-binary' },
1608
+ });
1609
+ }
1610
+ }
1611
+ idbLogger.warn(`Model not found, tried keys: ${candidates.join(', ')}`);
1612
+ break;
1613
+ }
1581
1614
  case 'tilesjson': {
1582
1615
  idbLogger.debug(`Looking for tilejson with downloadId: ${downloadId}, resourcePath: ${decodedResourcePath}`);
1583
1616
  // First try direct lookup (for style-level downloads)
@@ -2033,14 +2066,29 @@ function patchStyleForOffline(style, downloadId, maxZoom, tileExtension, styleId
2033
2066
  });
2034
2067
  }
2035
2068
  }
2036
- // Patch top-level models (Mapbox Standard 3D landmarks)
2069
+ // Patch top-level models (Mapbox Standard 3D trees / wind turbines).
2070
+ // Two shapes exist in the wild:
2071
+ // - Mapbox Standard: `{ "maple1-lod1": "mapbox://models/mapbox/maple1-v4-lod1.glb" }` (string values)
2072
+ // - Older/generic: `{ "name": { "uri": "mapbox://..." } }` (object values)
2073
+ // Models are keyed on the style ID (like sprites) so they can be shared
2074
+ // across regions.
2037
2075
  if (style.models) {
2038
- for (const [modelId, modelConfig] of Object.entries(style.models)) {
2039
- if (modelConfig.uri) {
2040
- modelConfig.uri = `idb://${downloadId}/model/${modelId}`;
2076
+ const modelBaseId = styleId || downloadId;
2077
+ const models = style.models;
2078
+ let patchedCount = 0;
2079
+ for (const [modelId, value] of Object.entries(models)) {
2080
+ if (typeof value === 'string') {
2081
+ models[modelId] = `idb://${modelBaseId}/model/${modelId}`;
2082
+ patchedCount++;
2083
+ }
2084
+ else if (value && typeof value === 'object' && 'uri' in value && value.uri) {
2085
+ value.uri = `idb://${modelBaseId}/model/${modelId}`;
2086
+ patchedCount++;
2041
2087
  }
2042
2088
  }
2043
- styleLogger.debug(`Patched ${Object.keys(style.models).length} model URIs`);
2089
+ if (patchedCount > 0) {
2090
+ styleLogger.debug(`Patched ${patchedCount} model URIs (styleId: ${modelBaseId})`);
2091
+ }
2044
2092
  }
2045
2093
  styleLogger.debug(`Final patched style:`, style);
2046
2094
  return style;
@@ -2608,12 +2656,22 @@ function convertStyleForServiceWorker(style) {
2608
2656
  });
2609
2657
  }
2610
2658
  }
2611
- // Convert models
2659
+ // Convert models. Two shapes in the wild:
2660
+ // - Mapbox Standard: `{ name: "idb://..." }` (string value)
2661
+ // - Older/generic: `{ name: { uri: "idb://..." } }` (object value)
2612
2662
  if (converted.models) {
2613
- for (const modelKey of Object.keys(converted.models)) {
2614
- const model = converted.models[modelKey];
2615
- if (model.uri && typeof model.uri === 'string' && model.uri.startsWith('idb://')) {
2616
- model.uri = replace(model.uri);
2663
+ const models = converted.models;
2664
+ for (const modelKey of Object.keys(models)) {
2665
+ const value = models[modelKey];
2666
+ if (typeof value === 'string') {
2667
+ if (value.startsWith('idb://')) {
2668
+ models[modelKey] = replace(value);
2669
+ }
2670
+ }
2671
+ else if (value && typeof value === 'object') {
2672
+ if (typeof value.uri === 'string' && value.uri.startsWith('idb://')) {
2673
+ value.uri = replace(value.uri);
2674
+ }
2617
2675
  }
2618
2676
  }
2619
2677
  }
@@ -4934,7 +4992,15 @@ class RegionService {
4934
4992
  deletedSprites++;
4935
4993
  }
4936
4994
  }
4937
- regionLogger$1.info(`Deleted style resources: ${deletedFonts} fonts, ${deletedGlyphs} glyphs, ${deletedSprites} sprites`);
4995
+ let deletedModels = 0;
4996
+ const modelTx = db.transaction('models', 'readwrite');
4997
+ for await (const cursor of modelTx.store) {
4998
+ if (resourceKeyBelongsToStyle(cursor.value.key, styleId)) {
4999
+ await cursor.delete();
5000
+ deletedModels++;
5001
+ }
5002
+ }
5003
+ regionLogger$1.info(`Deleted style resources: ${deletedFonts} fonts, ${deletedGlyphs} glyphs, ${deletedSprites} sprites, ${deletedModels} models`);
4938
5004
  }
4939
5005
  /**
4940
5006
  * Delete all tiles for a style
@@ -5042,7 +5108,7 @@ class RegionService {
5042
5108
  if (!region.styleUrl) {
5043
5109
  throw new Error('Region must have a styleUrl');
5044
5110
  }
5045
- const { onProgress, provider = 'auto', accessToken, skipGlyphs = false, skipSprites = false, glyphRanges, tileOptions, } = options;
5111
+ const { onProgress, provider = 'auto', accessToken, skipGlyphs = false, skipSprites = false, skipModels = false, glyphRanges, tileOptions, } = options;
5046
5112
  const emit = (phase, completed, total, message) => {
5047
5113
  if (!onProgress)
5048
5114
  return;
@@ -5097,8 +5163,13 @@ class RegionService {
5097
5163
  const spriteSources = normalizeSpriteProperty(originalSpriteUrl);
5098
5164
  if (spriteSources.length > 0) {
5099
5165
  const { downloadSprites } = await Promise.resolve().then(function () { return spriteService$1; });
5100
- const suffixes = ['.json', '.png', '@2x.json', '@2x.png'];
5101
- const totalFiles = spriteSources.length * suffixes.length;
5166
+ // Standard four sprite variants. For Mapbox Standard, an `iconset.pbf`
5167
+ // sibling is also served under the same /styles/v1/.../<hash>/ path
5168
+ // — we detect that case per-source below and append it to the list.
5169
+ const baseSuffixes = ['.json', '.png', '@2x.json', '@2x.png'];
5170
+ // Estimate total files assuming iconset is always included (the actual
5171
+ // number may be smaller; the emit helper clamps progress to total).
5172
+ const totalFiles = spriteSources.length * (baseSuffixes.length + 1);
5102
5173
  let completed = 0;
5103
5174
  emit('sprites', 0, totalFiles, 'Downloading sprites');
5104
5175
  for (const source of spriteSources) {
@@ -5107,9 +5178,27 @@ class RegionService {
5107
5178
  spriteBase = resolveMapboxUrl(spriteBase, effectiveAccessToken);
5108
5179
  }
5109
5180
  const qIndex = spriteBase.indexOf('?');
5110
- const spriteUrls = suffixes.map(suffix => qIndex !== -1
5111
- ? spriteBase.slice(0, qIndex) + suffix + spriteBase.slice(qIndex)
5112
- : spriteBase + suffix);
5181
+ const suffixes = [...baseSuffixes];
5182
+ // Mapbox Standard serves an iconset.pbf alongside the sprite under
5183
+ // /styles/v1/{owner}/{style}/{hash}/sprite the sibling file is
5184
+ // /styles/v1/{owner}/{style}/{hash}/iconset.pbf. The last path
5185
+ // segment is `sprite`, so replacing it with `iconset.pbf` works.
5186
+ const pathWithoutQuery = qIndex !== -1 ? spriteBase.slice(0, qIndex) : spriteBase;
5187
+ const isMapboxStandardSprite = /api\.mapbox\.com\/styles\/v1\/.+\/sprite$/.test(pathWithoutQuery);
5188
+ if (isMapboxStandardSprite) {
5189
+ // The path-rewrite suffix replaces the trailing `sprite` segment.
5190
+ suffixes.push('__ICONSET__');
5191
+ }
5192
+ const spriteUrls = suffixes.map(suffix => {
5193
+ if (suffix === '__ICONSET__') {
5194
+ // Replace trailing `sprite` with `iconset.pbf`, preserving query.
5195
+ const base = pathWithoutQuery.replace(/sprite$/, 'iconset.pbf');
5196
+ return qIndex !== -1 ? base + spriteBase.slice(qIndex) : base;
5197
+ }
5198
+ return qIndex !== -1
5199
+ ? spriteBase.slice(0, qIndex) + suffix + spriteBase.slice(qIndex)
5200
+ : spriteBase + suffix;
5201
+ });
5113
5202
  try {
5114
5203
  const result = await downloadSprites(spriteUrls, styleId, {
5115
5204
  enableValidation: true,
@@ -5155,7 +5244,42 @@ class RegionService {
5155
5244
  }
5156
5245
  }
5157
5246
  }
5158
- // 4. Tilesuse the stored (source-embedded) style, which still has HTTP tile URLs
5247
+ // 4. ModelsMapbox Standard's `style.models` references 3D tree /
5248
+ // turbine .glb assets. Two value shapes exist in the wild
5249
+ // (plain string or `{ uri }`) — we accept both.
5250
+ let modelResult;
5251
+ if (!skipModels && storedStyle.models) {
5252
+ const rawModels = storedStyle.models;
5253
+ const resolved = {};
5254
+ for (const [name, value] of Object.entries(rawModels)) {
5255
+ const uri = typeof value === 'string' ? value : value?.uri;
5256
+ if (!uri)
5257
+ continue;
5258
+ if (uri.startsWith('idb://'))
5259
+ continue; // already patched
5260
+ const httpUrl = isMapboxProtocol(uri) && effectiveAccessToken
5261
+ ? resolveMapboxUrl(uri, effectiveAccessToken)
5262
+ : uri;
5263
+ if (httpUrl.startsWith('http://') || httpUrl.startsWith('https://')) {
5264
+ resolved[name] = httpUrl;
5265
+ }
5266
+ }
5267
+ if (Object.keys(resolved).length > 0) {
5268
+ const { downloadModels } = await Promise.resolve().then(function () { return modelService$1; });
5269
+ emit('models', 0, Object.keys(resolved).length, 'Downloading 3D models');
5270
+ try {
5271
+ modelResult = await downloadModels(resolved, styleId, {
5272
+ onProgress: (progress) => {
5273
+ emit('models', progress.completed, progress.total, 'Downloading 3D models');
5274
+ },
5275
+ });
5276
+ }
5277
+ catch (error) {
5278
+ regionLogger$1.warn('Model download failed (non-fatal):', error);
5279
+ }
5280
+ }
5281
+ }
5282
+ // 5. Tiles — use the stored (source-embedded) style, which still has HTTP tile URLs
5159
5283
  const { downloadTiles } = await Promise.resolve().then(function () { return tileService$1; });
5160
5284
  const regionForTiles = { ...region, styleId };
5161
5285
  emit('tiles', 0, 100, 'Downloading tiles');
@@ -5168,7 +5292,7 @@ class RegionService {
5168
5292
  tileOptions?.onProgress?.(progress);
5169
5293
  },
5170
5294
  });
5171
- // 5. Metadata — must run last, since addRegion patches style URLs to idb://.
5295
+ // 6. Metadata — must run last, since addRegion patches style URLs to idb://.
5172
5296
  // Do NOT auto-fill tileExtension from tileResult: that's only the first
5173
5297
  // source's extension, and addRegion feeds it to patchStyleForOffline which
5174
5298
  // would override ALL sources — breaking mixed raster+vector styles. The
@@ -5184,6 +5308,7 @@ class RegionService {
5184
5308
  styleResult,
5185
5309
  spriteResults,
5186
5310
  glyphResult,
5311
+ modelResult,
5187
5312
  tileResult,
5188
5313
  };
5189
5314
  }
@@ -6322,10 +6447,15 @@ class TileService {
6322
6447
  tilesLength: config.tiles ? config.tiles.length : 0,
6323
6448
  url: config.url,
6324
6449
  });
6325
- // Handle tile-based sources (vector, raster, raster-dem, batched-model)
6450
+ // Handle tile-based sources (vector, raster, raster-dem, batched-model,
6451
+ // raster-array). `raster-array` is used by Mapbox Standard for layers
6452
+ // like `mapbox-landmarks` (mapbox.mapbox-landmark-icons-v1) — the tiles
6453
+ // are fetched from the same /v4/ endpoint as other tilesets, so the
6454
+ // TileJSON resolution path below handles them uniformly.
6326
6455
  if (config.type === 'vector' ||
6327
6456
  config.type === 'raster' ||
6328
6457
  config.type === 'raster-dem' ||
6458
+ config.type === 'raster-array' ||
6329
6459
  config.type === 'batched-model') {
6330
6460
  // Handle direct tile URLs in the source config
6331
6461
  if (config.tiles && Array.isArray(config.tiles) && config.tiles.length > 0) {
@@ -7058,6 +7188,201 @@ var glyphService$1 = /*#__PURE__*/Object.freeze({
7058
7188
  verifyAndRepairGlyphs: verifyAndRepairGlyphs
7059
7189
  });
7060
7190
 
7191
+ const modelLogger = logger.scope('ModelService');
7192
+ /**
7193
+ * Build the storage key for a model. Kept consistent with sprite/glyph
7194
+ * conventions: `{styleId}::model::{modelName}`.
7195
+ */
7196
+ function modelKey(styleId, modelName) {
7197
+ return `${styleId}::model::${modelName}`;
7198
+ }
7199
+ /** True when the given key belongs to the given styleId's model store prefix. */
7200
+ function modelKeyBelongsToStyle(key, styleId) {
7201
+ return key.startsWith(`${styleId}::model::`);
7202
+ }
7203
+ /**
7204
+ * Service for downloading, storing, and serving Mapbox 3D model (.glb) files.
7205
+ *
7206
+ * Mapbox Standard declares 32 models at the top of `style.models`:
7207
+ *
7208
+ * ```json
7209
+ * {
7210
+ * "maple1-lod1": "mapbox://models/mapbox/maple1-v4-lod1.glb",
7211
+ * ...
7212
+ * }
7213
+ * ```
7214
+ *
7215
+ * `model` layers (e.g. `trees`, `wind-turbine-towers`) pick one by name at
7216
+ * render time. For offline use each referenced URL is fetched and stored
7217
+ * here, and `patchStyleForOffline` rewrites the dictionary entries to
7218
+ * `idb://{styleId}/model/{name}` URLs.
7219
+ */
7220
+ class ModelService {
7221
+ db = dbPromise;
7222
+ /**
7223
+ * Download the set of models referenced by `style.models` for one style.
7224
+ *
7225
+ * @param models `{ modelName: resolvedHttpUrl }` — URLs must already be
7226
+ * resolved (mapbox:// URLs should be resolved by the caller).
7227
+ * @param styleId The owning style's key.
7228
+ */
7229
+ async downloadModels(models, styleId, options = {}) {
7230
+ const db = await this.db;
7231
+ const { onProgress, batchSize = 4, maxRetries = 3, skipExisting = true, timeoutMs = 30000, } = options;
7232
+ const entries = Object.entries(models);
7233
+ const progressTracker = createProgressTracker(entries.length);
7234
+ const result = {
7235
+ totalModels: entries.length,
7236
+ downloadedModels: 0,
7237
+ skippedModels: 0,
7238
+ failedModels: 0,
7239
+ totalSize: 0,
7240
+ errors: [],
7241
+ };
7242
+ const emit = () => onProgress?.(progressTracker.getProgress());
7243
+ emit();
7244
+ if (entries.length === 0)
7245
+ return result;
7246
+ // Pre-compute existing keys for skipExisting
7247
+ const existingKeys = new Set();
7248
+ if (skipExisting) {
7249
+ const tx = db.transaction('models', 'readonly');
7250
+ for await (const cursor of tx.store) {
7251
+ existingKeys.add(cursor.value.key);
7252
+ }
7253
+ }
7254
+ await processBatch(entries, async ([modelName, url]) => {
7255
+ const key = modelKey(styleId, modelName);
7256
+ const label = `${styleId}::${modelName}`;
7257
+ if (skipExisting && existingKeys.has(key)) {
7258
+ result.skippedModels++;
7259
+ progressTracker.update(1, label);
7260
+ emit();
7261
+ return;
7262
+ }
7263
+ try {
7264
+ const response = await fetchResourceWithRetry(url, {
7265
+ retries: maxRetries,
7266
+ timeout: timeoutMs,
7267
+ proxyType: 'tiles',
7268
+ });
7269
+ if (response.type === 'json') {
7270
+ throw new Error('Unexpected JSON response for model');
7271
+ }
7272
+ const data = response.data;
7273
+ const contentType = ('contentType' in response && response.contentType) || 'model/gltf-binary';
7274
+ const entry = {
7275
+ key,
7276
+ data,
7277
+ contentType,
7278
+ size: data.byteLength,
7279
+ url,
7280
+ styleId,
7281
+ modelName,
7282
+ lastModified: Date.now(),
7283
+ downloadedAt: new Date().toISOString(),
7284
+ expires: response.expires,
7285
+ };
7286
+ await db.put('models', entry);
7287
+ result.downloadedModels++;
7288
+ result.totalSize += data.byteLength;
7289
+ progressTracker.update(1, label);
7290
+ }
7291
+ catch (err) {
7292
+ const message = err instanceof Error ? err.message : String(err);
7293
+ result.failedModels++;
7294
+ result.errors.push({ url, error: message });
7295
+ modelLogger.warn(`Failed to download model "${modelName}" from ${url}:`, err);
7296
+ progressTracker.update(1, label, message);
7297
+ }
7298
+ emit();
7299
+ }, { batchSize });
7300
+ modelLogger.info(`Models downloaded for style ${styleId}: ${result.downloadedModels} new, ${result.skippedModels} skipped, ${result.failedModels} failed`);
7301
+ return result;
7302
+ }
7303
+ /** Retrieve a single model by `{styleId, modelName}`. */
7304
+ async getModel(styleId, modelName) {
7305
+ const db = await this.db;
7306
+ return db.get('models', modelKey(styleId, modelName));
7307
+ }
7308
+ /** Aggregate stats across all stored models. */
7309
+ async getModelStats() {
7310
+ const db = await this.db;
7311
+ const stats = {
7312
+ count: 0,
7313
+ totalSize: 0,
7314
+ averageSize: 0,
7315
+ models: [],
7316
+ modelsByStyle: {},
7317
+ };
7318
+ const tx = db.transaction('models', 'readonly');
7319
+ for await (const cursor of tx.store) {
7320
+ const m = cursor.value;
7321
+ stats.count++;
7322
+ stats.totalSize += m.size;
7323
+ stats.models.push({ name: m.modelName, size: m.size, lastModified: m.lastModified });
7324
+ stats.modelsByStyle[m.styleId] = (stats.modelsByStyle[m.styleId] ?? 0) + 1;
7325
+ }
7326
+ stats.averageSize = stats.count > 0 ? stats.totalSize / stats.count : 0;
7327
+ return stats;
7328
+ }
7329
+ /**
7330
+ * Delete models older than `maxAge` days. Defaults to 30.
7331
+ */
7332
+ async cleanupOldModels(maxAge = 30) {
7333
+ const db = await this.db;
7334
+ const cutoff = Date.now() - maxAge * 24 * 60 * 60 * 1000;
7335
+ let deleted = 0;
7336
+ const tx = db.transaction('models', 'readwrite');
7337
+ for await (const cursor of tx.store) {
7338
+ if (cursor.value.lastModified < cutoff) {
7339
+ await cursor.delete();
7340
+ deleted++;
7341
+ }
7342
+ }
7343
+ return deleted;
7344
+ }
7345
+ /**
7346
+ * Basic integrity check: remove entries with empty/missing data.
7347
+ */
7348
+ async verifyAndRepairModels() {
7349
+ const db = await this.db;
7350
+ let verified = 0;
7351
+ let removed = 0;
7352
+ const tx = db.transaction('models', 'readwrite');
7353
+ for await (const cursor of tx.store) {
7354
+ const m = cursor.value;
7355
+ if (!m.data || m.data.byteLength === 0) {
7356
+ await cursor.delete();
7357
+ removed++;
7358
+ }
7359
+ else {
7360
+ verified++;
7361
+ }
7362
+ }
7363
+ return { verified, repaired: 0, removed };
7364
+ }
7365
+ }
7366
+ // Singleton + convenience exports, matching other service modules.
7367
+ const modelService = new ModelService();
7368
+ const downloadModels = (models, styleId, options) => modelService.downloadModels(models, styleId, options);
7369
+ const getModel = (styleId, modelName) => modelService.getModel(styleId, modelName);
7370
+ const getModelStats = () => modelService.getModelStats();
7371
+ const cleanupOldModels = (maxAge) => modelService.cleanupOldModels(maxAge);
7372
+ const verifyAndRepairModels = () => modelService.verifyAndRepairModels();
7373
+
7374
+ var modelService$1 = /*#__PURE__*/Object.freeze({
7375
+ __proto__: null,
7376
+ ModelService: ModelService,
7377
+ cleanupOldModels: cleanupOldModels,
7378
+ downloadModels: downloadModels,
7379
+ getModel: getModel,
7380
+ getModelStats: getModelStats,
7381
+ modelKeyBelongsToStyle: modelKeyBelongsToStyle,
7382
+ modelService: modelService,
7383
+ verifyAndRepairModels: verifyAndRepairModels
7384
+ });
7385
+
7061
7386
  class ResourceService {
7062
7387
  // Tile Management Methods
7063
7388
  async downloadTilesWithOptions(region, style, styleId, options = {}) {
@@ -7126,6 +7451,19 @@ class ResourceService {
7126
7451
  async verifyAndRepairGlyphs() {
7127
7452
  return verifyAndRepairGlyphs();
7128
7453
  }
7454
+ // 3D Model Management Methods
7455
+ async downloadModelsWithOptions(models, styleId, options = {}) {
7456
+ return downloadModels(models, styleId, options);
7457
+ }
7458
+ async getModelStats() {
7459
+ return getModelStats();
7460
+ }
7461
+ async cleanupOldModels(options) {
7462
+ return cleanupOldModels(options?.maxAge);
7463
+ }
7464
+ async verifyAndRepairModels() {
7465
+ return verifyAndRepairModels();
7466
+ }
7129
7467
  }
7130
7468
 
7131
7469
  // The underlying stats functions already iterate every entry in their
@@ -7920,6 +8258,10 @@ const createResourceManagement = (services) => ({
7920
8258
  loadGlyphsForStyle: (...args) => services.resourceService.loadGlyphsForStyle(...args),
7921
8259
  cleanupOldGlyphs: (...args) => services.resourceService.cleanupOldGlyphs(...args),
7922
8260
  verifyAndRepairGlyphs: (...args) => services.resourceService.verifyAndRepairGlyphs(...args),
8261
+ downloadModelsWithOptions: (...args) => services.resourceService.downloadModelsWithOptions(...args),
8262
+ getModelStats: (...args) => services.resourceService.getModelStats(...args),
8263
+ cleanupOldModels: (...args) => services.resourceService.cleanupOldModels(...args),
8264
+ verifyAndRepairModels: (...args) => services.resourceService.verifyAndRepairModels(...args),
7923
8265
  });
7924
8266
 
7925
8267
  const createAnalyticsManagement = (services, deps) => ({
@@ -11566,10 +11908,12 @@ class PanelRenderer extends BaseComponent {
11566
11908
  panelLogger.debug(`Fixed legacy source ${sourceId}: added tiles, removed url`);
11567
11909
  }
11568
11910
  }
11569
- // Apply maxzoom to all tile sources (including batched-model for 3D buildings)
11911
+ // Apply maxzoom to all tile sources (including batched-model for 3D
11912
+ // buildings and raster-array for Mapbox Standard landmark icons).
11570
11913
  if (src.type === 'vector' ||
11571
11914
  src.type === 'raster' ||
11572
11915
  src.type === 'raster-dem' ||
11916
+ src.type === 'raster-array' ||
11573
11917
  src.type === 'batched-model') {
11574
11918
  const originalMaxzoom = src.maxzoom;
11575
11919
  // Use the lower of region maxZoom and source's original maxzoom so we
@@ -13650,6 +13994,7 @@ exports.MAPBOX_CACHE_TTL = MAPBOX_CACHE_TTL;
13650
13994
  exports.MAPBOX_CLASSIC_STYLES = MAPBOX_CLASSIC_STYLES;
13651
13995
  exports.MAP_PROVIDERS = MAP_PROVIDERS;
13652
13996
  exports.MaintenanceService = MaintenanceService;
13997
+ exports.ModelService = ModelService;
13653
13998
  exports.OfflineManagerControl = OfflineManagerControl;
13654
13999
  exports.OfflineMapDBVersionError = OfflineMapDBVersionError;
13655
14000
  exports.OfflineMapManager = OfflineMapManager;
@@ -13672,6 +14017,7 @@ exports.cleanupCompressedTiles = cleanupCompressedTiles;
13672
14017
  exports.cleanupExpiredTiles = cleanupExpiredTiles;
13673
14018
  exports.cleanupOldFonts = cleanupOldFonts;
13674
14019
  exports.cleanupOldGlyphs = cleanupOldGlyphs;
14020
+ exports.cleanupOldModels = cleanupOldModels;
13675
14021
  exports.cleanupOldSprites = cleanupOldSprites;
13676
14022
  exports.cleanupOldStyles = cleanupOldStyles;
13677
14023
  exports.cleanupOldTiles = cleanupOldTiles;
@@ -13692,6 +14038,7 @@ exports.detectCssPrefix = detectCssPrefix;
13692
14038
  exports.detectStyleProvider = detectStyleProvider;
13693
14039
  exports.downloadFonts = downloadFonts;
13694
14040
  exports.downloadGlyphs = downloadGlyphs;
14041
+ exports.downloadModels = downloadModels;
13695
14042
  exports.downloadSprites = downloadSprites;
13696
14043
  exports.downloadStyleWithProvider = downloadStyleWithProvider;
13697
14044
  exports.downloadStyles = downloadStyles;
@@ -13712,6 +14059,8 @@ exports.getFontStats = getFontStats;
13712
14059
  exports.getGlyphAnalytics = getGlyphAnalytics;
13713
14060
  exports.getGlyphStats = getGlyphStats;
13714
14061
  exports.getIcon = getIcon;
14062
+ exports.getModel = getModel;
14063
+ exports.getModelStats = getModelStats;
13715
14064
  exports.getRegionAnalytics = getRegionAnalytics;
13716
14065
  exports.getSpriteAnalytics = getSpriteAnalytics;
13717
14066
  exports.getSpriteStats = getSpriteStats;
@@ -13731,6 +14080,8 @@ exports.loadGlyphs = loadGlyphs;
13731
14080
  exports.loadStyleById = loadStyleById;
13732
14081
  exports.loadStyles = loadStyles;
13733
14082
  exports.logger = logger;
14083
+ exports.modelKeyBelongsToStyle = modelKeyBelongsToStyle;
14084
+ exports.modelService = modelService;
13734
14085
  exports.normalizeSpriteProperty = normalizeSpriteProperty;
13735
14086
  exports.normalizeStyleUrl = normalizeStyleUrl;
13736
14087
  exports.optimizeStorage = optimizeStorage;
@@ -13760,5 +14111,6 @@ exports.validateStyleForProvider = validateStyleForProvider;
13760
14111
  exports.validateZoomLevels = validateZoomLevels;
13761
14112
  exports.verifyAndRepairFonts = verifyAndRepairFonts;
13762
14113
  exports.verifyAndRepairGlyphs = verifyAndRepairGlyphs;
14114
+ exports.verifyAndRepairModels = verifyAndRepairModels;
13763
14115
  exports.verifyAndRepairSprites = verifyAndRepairSprites;
13764
14116
  //# sourceMappingURL=index.js.map