maplibre-gl-basemap-control 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -213,6 +213,21 @@ key must have the Map Tiles API enabled.
213
213
  > key" or add **Map Tiles API** to the allowed list, then save (changes can take a
214
214
  > few minutes to propagate). Make sure billing is enabled on the project too.
215
215
 
216
+ The same `googleMapsApiKey` also upgrades the base Google basemaps
217
+ (`google-maps`, `google-satellite`, `google-terrain`, `google-hybrid`). When a
218
+ key is set they load from the authorized Map Tiles API (via a tile session, like
219
+ Google Traffic); without a key they fall back to keyless `mt1.google.com` xyz
220
+ tiles so they keep working out of the box.
221
+
222
+ > **Licensing caveat.** The keyless `mt1.google.com` tiles are Google Maps'
223
+ > internal endpoints. They are **not** covered by any public or open license, and
224
+ > accessing Google map content outside an official Google Maps Platform API
225
+ > violates the [Google Maps Platform Terms of Service](https://cloud.google.com/maps-platform/terms).
226
+ > They are convenient for local development and demos, but Google may rate-limit,
227
+ > change, or block them at any time. For production use, set a `googleMapsApiKey`
228
+ > so these basemaps use the authorized [Map Tiles API](https://developers.google.com/maps/documentation/tile)
229
+ > (which requires the Map Tiles API enabled and billing on your project).
230
+
216
231
  ## API
217
232
 
218
233
  ### BasemapControl Options
@@ -230,7 +245,7 @@ key must have the Map Tiles API enabled.
230
245
  | `mapboxAccessToken` | `string` | `undefined` | Initial Mapbox access token for built-in Mapbox styles and the Mapbox Traffic overlay |
231
246
  | `tomtomApiKey` | `string` | `undefined` | Initial TomTom API key for the TomTom Traffic overlays |
232
247
  | `hereApiKey` | `string` | `undefined` | Initial HERE API key for the HERE Traffic overlay |
233
- | `googleMapsApiKey` | `string` | `undefined` | Initial Google Maps API key (Map Tiles API) for the Google Traffic overlay |
248
+ | `googleMapsApiKey` | `string` | `undefined` | Initial Google Maps API key (Map Tiles API) for the Google Traffic overlay and the base Google Maps/Satellite/Terrain/Hybrid basemaps (which fall back to keyless tiles without a key) |
234
249
  | `basemaps` | `BasemapDefinition[]` | `[]` | Custom basemaps to add or use |
235
250
  | `providers` | `BasemapProvider[]` | `[]` | Custom provider labels |
236
251
  | `includeDefaultBasemaps` | `boolean` | `true` | Include the built-in public catalog |
@@ -296,7 +311,7 @@ const control = new BasemapControl({
296
311
  - `setMapboxAccessToken(accessToken)` - Set or update the Mapbox access token
297
312
  - `setTomTomApiKey(apiKey)` - Set or update the TomTom API key used by TomTom Traffic overlays
298
313
  - `setHereApiKey(apiKey)` - Set or update the HERE API key used by the HERE Traffic overlay
299
- - `setGoogleMapsApiKey(apiKey)` - Set or update the Google Maps API key used by the Google Traffic overlay
314
+ - `setGoogleMapsApiKey(apiKey)` - Set or update the Google Maps API key used by the Google Traffic overlay and the base Google basemaps
300
315
  - `getActiveBasemap()` - Return the most recently selected basemap definition
301
316
  - `getActiveBasemaps()` - Return all currently active basemap definitions
302
317
  - `getBasemaps()` - Return the catalog
@@ -151,6 +151,37 @@ const OPENFREEMAP_ATTRIBUTION = "OpenFreeMap © OpenMapTiles Data from OpenS
151
151
  const TOMTOM_ATTRIBUTION = "© TomTom";
152
152
  const HERE_ATTRIBUTION = "© HERE";
153
153
  const GOOGLE_ATTRIBUTION = "© Google";
154
+ const GOOGLE_2DTILES_URL = "https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?session={session}&key={api-key}";
155
+ function googleRasterBasemap({
156
+ id,
157
+ name,
158
+ category,
159
+ description,
160
+ fallbackLyrs,
161
+ session,
162
+ tags
163
+ }) {
164
+ return {
165
+ id,
166
+ name,
167
+ provider: "google",
168
+ type: "raster",
169
+ category,
170
+ description,
171
+ attribution: GOOGLE_ATTRIBUTION,
172
+ source: {
173
+ type: "raster",
174
+ tiles: [GOOGLE_2DTILES_URL],
175
+ fallbackTiles: [
176
+ `https://mt1.google.com/vt/lyrs=${fallbackLyrs}&x={x}&y={y}&z={z}`
177
+ ],
178
+ tileSize: 256,
179
+ maxzoom: 20,
180
+ googleSession: session
181
+ },
182
+ tags
183
+ };
184
+ }
154
185
  const eoxS2CloudlessAttribution = (year) => `Sentinel-2 cloudless <a href="https://cloudless.eox.at" target="_blank" rel="noopener">EOxCloudless</a> by EOX IT Services GmbH (Contains modified Copernicus Sentinel data ${year})`;
155
186
  const EOX_S2CLOUDLESS_YEARS = [
156
187
  2018,
@@ -268,48 +299,44 @@ const DEFAULT_BASEMAPS = [
268
299
  tiles: ["https://tile.osm.ch/switzerland/{z}/{x}/{y}.png"],
269
300
  tags: ["osm", "switzerland", "regional"]
270
301
  }),
271
- rasterBasemap({
302
+ googleRasterBasemap({
272
303
  id: "google-maps",
273
304
  name: "Google Maps",
274
- provider: "google",
275
305
  category: "Street",
276
- description: "Google road map tiles from the QGIS basemaps source.",
277
- attribution: "&copy; Google",
278
- tiles: ["https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"],
279
- maxzoom: 20,
306
+ description: "Google road map tiles. Uses the Map Tiles API when a Google Maps API key is set, otherwise keyless xyz tiles.",
307
+ fallbackLyrs: "m",
308
+ session: { mapType: "roadmap" },
280
309
  tags: ["google", "street"]
281
310
  }),
282
- rasterBasemap({
311
+ googleRasterBasemap({
283
312
  id: "google-satellite",
284
313
  name: "Google Satellite",
285
- provider: "google",
286
314
  category: "Imagery",
287
- description: "Google satellite tiles from the QGIS basemaps source.",
288
- attribution: "&copy; Google",
289
- tiles: ["https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"],
290
- maxzoom: 20,
315
+ description: "Google satellite tiles. Uses the Map Tiles API when a Google Maps API key is set, otherwise keyless xyz tiles.",
316
+ fallbackLyrs: "s",
317
+ session: { mapType: "satellite" },
291
318
  tags: ["google", "satellite", "imagery"]
292
319
  }),
293
- rasterBasemap({
320
+ googleRasterBasemap({
294
321
  id: "google-terrain",
295
322
  name: "Google Terrain",
296
- provider: "google",
297
323
  category: "Terrain",
298
- description: "Google terrain tiles from the QGIS basemaps source.",
299
- attribution: "&copy; Google",
300
- tiles: ["https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}"],
301
- maxzoom: 20,
324
+ description: "Google terrain tiles. Uses the Map Tiles API when a Google Maps API key is set, otherwise keyless xyz tiles.",
325
+ fallbackLyrs: "p",
326
+ // The Map Tiles API requires `layerRoadmap` alongside the terrain map type
327
+ // for the terrain tiles to render.
328
+ session: { mapType: "terrain", layerTypes: ["layerRoadmap"] },
302
329
  tags: ["google", "terrain"]
303
330
  }),
304
- rasterBasemap({
331
+ googleRasterBasemap({
305
332
  id: "google-hybrid",
306
333
  name: "Google Hybrid",
307
- provider: "google",
308
334
  category: "Imagery",
309
- description: "Google hybrid imagery tiles from the QGIS basemaps source.",
310
- attribution: "&copy; Google",
311
- tiles: ["https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}"],
312
- maxzoom: 20,
335
+ description: "Google hybrid imagery tiles. Uses the Map Tiles API when a Google Maps API key is set, otherwise keyless xyz tiles.",
336
+ fallbackLyrs: "y",
337
+ // Hybrid is satellite imagery with the roadmap layer (roads and labels)
338
+ // painted on top, matching the classic Google "hybrid" map type.
339
+ session: { mapType: "satellite", layerTypes: ["layerRoadmap"] },
313
340
  tags: ["google", "hybrid", "imagery"]
314
341
  }),
315
342
  amazonStyleBasemap({
@@ -1482,6 +1509,11 @@ class BasemapControl {
1482
1509
  // The basemap whose application last failed for a missing credential, so the
1483
1510
  // inline credential field's Enter key can re-attempt it (#837).
1484
1511
  __publicField(this, "_lastFailedBasemapId");
1512
+ // The id of a just-applied basemap that is showing its keyless public tiles
1513
+ // because no API key is set (currently the Google Maps/Satellite/Terrain/
1514
+ // Hybrid basemaps). The panel offers an optional inline key input that
1515
+ // upgrades it to the authorized tiles; leaving it blank keeps the fallback.
1516
+ __publicField(this, "_optionalKeyBasemapId");
1485
1517
  // Detaches the listeners watching the in-flight style load (see
1486
1518
  // _watchStyleLoad). Set while a style swap is settling, cleared once it
1487
1519
  // succeeds, fails, or is superseded by a newer swap.
@@ -1790,6 +1822,7 @@ class BasemapControl {
1790
1822
  loading: false,
1791
1823
  error: void 0
1792
1824
  };
1825
+ this._optionalKeyBasemapId = this._basemapUsesKeylessFallback(basemap) ? basemap.id : void 0;
1793
1826
  this._renderContent(true);
1794
1827
  this._emit({
1795
1828
  type: "basemapchange",
@@ -2418,6 +2451,55 @@ class BasemapControl {
2418
2451
  _retryLastFailedBasemap() {
2419
2452
  if (this._lastFailedBasemapId) this._selectBasemap(this._lastFailedBasemapId);
2420
2453
  }
2454
+ // Renders the optional API key input shown after a basemap falls back to its
2455
+ // keyless public tiles, so the user can enter a key to upgrade to the
2456
+ // authorized provider tiles (or leave it blank to keep the public ones).
2457
+ // Returns null unless such a basemap is the active selection and still keyless.
2458
+ _createOptionalKeyPrompt() {
2459
+ const id = this._optionalKeyBasemapId;
2460
+ if (!id) return null;
2461
+ const basemap = this._basemaps.find((candidate) => candidate.id === id);
2462
+ if (!basemap || !this._state.activeBasemapIds.includes(id) || !this._basemapUsesKeylessFallback(basemap)) {
2463
+ return null;
2464
+ }
2465
+ const def = this._providerFieldDefs().find(
2466
+ (candidate) => candidate.provider === basemap.provider && candidate.has
2467
+ );
2468
+ if (!def) return null;
2469
+ const wrapper = document.createElement("div");
2470
+ wrapper.className = "basemap-control-optional-key";
2471
+ const message = document.createElement("span");
2472
+ message.className = "basemap-control-optional-key-message";
2473
+ message.textContent = `Enter a ${def.fields[0].placeholder} to load the official ${def.label} tiles.`;
2474
+ const help = PROVIDER_CREDENTIAL_HELP[def.provider];
2475
+ if (help) {
2476
+ message.appendChild(document.createTextNode(" "));
2477
+ const link = document.createElement("a");
2478
+ link.className = "basemap-control-status-link";
2479
+ link.href = help.url;
2480
+ link.target = "_blank";
2481
+ link.rel = "noopener noreferrer";
2482
+ link.textContent = help.label;
2483
+ message.appendChild(link);
2484
+ }
2485
+ wrapper.appendChild(message);
2486
+ const fields = document.createElement("div");
2487
+ fields.className = "basemap-control-status-fields";
2488
+ for (const field of def.fields) {
2489
+ fields.appendChild(
2490
+ this._createProviderSettingsInput({
2491
+ ...field,
2492
+ // Keep the field on screen while typing; Enter re-applies the basemap
2493
+ // so the freshly entered key takes effect immediately.
2494
+ rerenderOnInput: false,
2495
+ clearErrorOnInput: false,
2496
+ onEnter: () => this._reapplyBasemap(id)
2497
+ })
2498
+ );
2499
+ }
2500
+ wrapper.appendChild(fields);
2501
+ return wrapper;
2502
+ }
2421
2503
  _createProviderSettingsInput({
2422
2504
  className,
2423
2505
  type,
@@ -2539,7 +2621,15 @@ class BasemapControl {
2539
2621
  const inlineFields = this._createInlineCredentialFields();
2540
2622
  if (inlineFields) status.appendChild(inlineFields);
2541
2623
  } else {
2542
- status.textContent = `${resultCount} basemap${resultCount === 1 ? "" : "s"}`;
2624
+ const count = document.createElement("span");
2625
+ count.className = "basemap-control-status-message";
2626
+ count.textContent = `${resultCount} basemap${resultCount === 1 ? "" : "s"}`;
2627
+ status.appendChild(count);
2628
+ const optionalKey = this._createOptionalKeyPrompt();
2629
+ if (optionalKey) {
2630
+ status.classList.add("has-optional-key");
2631
+ status.appendChild(optionalKey);
2632
+ }
2543
2633
  }
2544
2634
  return status;
2545
2635
  }
@@ -2620,9 +2710,10 @@ class BasemapControl {
2620
2710
  _hasHereBasemaps() {
2621
2711
  return this._basemaps.some((basemap) => basemap.provider === "here");
2622
2712
  }
2623
- // True when a Google basemap needs a Map Tiles API key. The existing Google
2624
- // raster basemaps (Maps/Satellite/etc.) use keyless tiles, so only session or
2625
- // api-key based Google layers (e.g. Google Traffic) require the key input.
2713
+ // True when a Google basemap can use a Map Tiles API key. Session-based Google
2714
+ // layers (Google Traffic, plus the base Maps/Satellite/Terrain/Hybrid layers
2715
+ // that upgrade to the authorized Map Tiles API when a key is present) and any
2716
+ // api-key based Google tiles reveal the key input.
2626
2717
  _hasGoogleApiKeyBasemaps() {
2627
2718
  return this._basemaps.some(
2628
2719
  (basemap) => basemap.provider === "google" && basemap.source.type === "raster" && (Boolean(basemap.source.googleSession) || basemap.source.tiles.some((tile) => tile.includes(API_KEY_PLACEHOLDER)))
@@ -2853,8 +2944,11 @@ class BasemapControl {
2853
2944
  // surface a "Get a ..." link and reveal the credential inputs.
2854
2945
  async _resolveRasterTiles(basemap) {
2855
2946
  if (basemap.source.type !== "raster") return [];
2856
- const { tiles, googleSession } = basemap.source;
2947
+ const { tiles, googleSession, fallbackTiles } = basemap.source;
2857
2948
  if (googleSession) {
2949
+ if (!this._googleMapsApiKey.trim() && (fallbackTiles == null ? void 0 : fallbackTiles.length)) {
2950
+ return fallbackTiles;
2951
+ }
2858
2952
  return this._resolveGoogleSessionTiles(tiles, googleSession);
2859
2953
  }
2860
2954
  if (!tiles.some((tile) => tile.includes(API_KEY_PLACEHOLDER))) {
@@ -2873,6 +2967,29 @@ class BasemapControl {
2873
2967
  if (provider === "google") return this._googleMapsApiKey.trim();
2874
2968
  return "";
2875
2969
  }
2970
+ // True when a basemap is (or would be) served from its keyless public
2971
+ // fallback tiles because its provider API key is not set. Drives the optional
2972
+ // key prompt: these basemaps work without a key but can upgrade to the
2973
+ // authorized provider tiles once one is entered.
2974
+ _basemapUsesKeylessFallback(basemap) {
2975
+ var _a;
2976
+ return basemap.source.type === "raster" && Boolean(basemap.source.googleSession) && (((_a = basemap.source.fallbackTiles) == null ? void 0 : _a.length) ?? 0) > 0 && !this._rasterApiKeyFor(basemap.provider);
2977
+ }
2978
+ // Re-applies an already-active basemap so a newly entered key takes effect.
2979
+ // Unlike `_selectBasemap`, this never toggles the basemap off in multiple
2980
+ // mode: a stacked raster is re-added on top (replacing its prior instance),
2981
+ // and a single-mode basemap is simply re-set.
2982
+ _reapplyBasemap(id) {
2983
+ const basemap = this._basemaps.find((candidate) => candidate.id === id);
2984
+ const isOverlay = (basemap == null ? void 0 : basemap.source.type) === "raster" || (basemap == null ? void 0 : basemap.source.type) === "vector-overlay";
2985
+ if (this._state.allowMultiple && isOverlay) {
2986
+ this.addBasemap(id).catch(() => {
2987
+ });
2988
+ } else {
2989
+ this.setBasemap(id).catch(() => {
2990
+ });
2991
+ }
2992
+ }
2876
2993
  _missingRasterKeyError(provider) {
2877
2994
  const label = provider === "tomtom" ? "TomTom API key" : provider === "here" ? "HERE API key" : provider === "google" ? "Google Maps API key" : "API key";
2878
2995
  const helpProvider = provider in PROVIDER_CREDENTIAL_HELP ? provider : "maptiler";
@@ -3108,4 +3225,4 @@ exports.createBasemapCatalog = createBasemapCatalog;
3108
3225
  exports.filterBasemaps = filterBasemaps;
3109
3226
  exports.getBasemapCategories = getBasemapCategories;
3110
3227
  exports.resolveBasemapProviders = resolveBasemapProviders;
3111
- //# sourceMappingURL=BasemapControl-CDGZrQ2Y.cjs.map
3228
+ //# sourceMappingURL=BasemapControl-BbsxylnF.cjs.map