maplibre-gl-basemap-control 0.13.0 → 0.14.3

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
@@ -9,6 +9,7 @@ A MapLibre GL JS control for searching and switching public basemaps. It keeps t
9
9
 
10
10
  - Search-first basemap picker inspired by QuickMapServices
11
11
  - Built-in catalog for common public basemaps, MapTiler styles, Amazon Location styles, Mapbox styles, Maptoolkit styles, Protomaps styles, and Stadia Maps basemaps
12
+ - Tianditu, Amap, and Tencent basemaps, served from inside mainland China
12
13
  - Stackable traffic overlays for TomTom, HERE, Mapbox, and Google
13
14
  - Custom basemap and provider definitions
14
15
  - MapLibre `IControl` implementation
@@ -151,11 +152,13 @@ destructive style change, so the missing key can be entered and the basemap retr
151
152
  const control = new BasemapControl({
152
153
  defaultBasemapId: 'maptiler-streets',
153
154
  mapTilerApiKey: 'YOUR_MAPTILER_API_KEY',
155
+ cartoApiKey: 'YOUR_CARTO_API_KEY',
154
156
  amazonApiKey: 'YOUR_AMAZON_LOCATION_API_KEY',
155
157
  awsRegion: 'us-east-1',
156
158
  mapboxAccessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
157
159
  protomapsApiKey: 'YOUR_PROTOMAPS_API_KEY',
158
160
  stadiaApiKey: 'YOUR_STADIA_MAPS_API_KEY',
161
+ tiandituApiKey: 'YOUR_TIANDITU_API_KEY',
159
162
  });
160
163
  ```
161
164
 
@@ -203,6 +206,61 @@ transparent overlays, so pair them with `allowMultiple: true` to stack them over
203
206
  > this control always sends `api_key`, so a key is required here. Set `stadiaApiKey` or enter it in
204
207
  > the panel's API keys view.
205
208
 
209
+ ### China Basemaps
210
+
211
+ Most of the catalog is hosted outside mainland China with no presence inside it, so for users there
212
+ OpenStreetMap, OpenFreeMap, Protomaps, Carto, Google, and Mapbox range from slow to unreachable. The
213
+ catalog ships three providers that are served from inside China:
214
+
215
+ | Basemap id | Provider | Datum | Credential |
216
+ |------------|----------|-------|------------|
217
+ | `tianditu-vector`, `tianditu-imagery`, `tianditu-terrain` and their `-labels` overlays | Tianditu (天地图) | CGCS2000 | `tiandituApiKey` |
218
+ | `amap-street`, `amap-satellite`, `amap-labels` | Amap (高德地图) | GCJ-02 | none |
219
+ | `tencent-street`, `tencent-dark` | Tencent Maps (腾讯地图) | GCJ-02 | none |
220
+
221
+ They are searchable by their Chinese names as well as their English ones, so typing `天地图`, `高德`,
222
+ or `腾讯` into the panel's filter finds them.
223
+
224
+ > **The datum matters more than the speed.** Chinese law requires public map services to publish in
225
+ > GCJ-02, an offset datum that displaces features by roughly 100-700 m from WGS84. Neither this
226
+ > control nor MapLibre applies the shift, so WGS84 data laid over Amap or Tencent will visibly
227
+ > misalign. **Tianditu is the exception**: it publishes in CGCS2000, which is close enough to WGS84
228
+ > for web mapping, so ordinary data overlays correctly. Prefer Tianditu whenever you are also
229
+ > rendering your own data; reach for Amap or Tencent when the basemap is the whole point, or convert
230
+ > your data to GCJ-02 first.
231
+
232
+ Tianditu is China's official National Platform for Common Geospatial Information Services. Register
233
+ a free key at [console.tianditu.gov.cn](https://console.tianditu.gov.cn/api/key), then set
234
+ `tiandituApiKey` or enter it in the panel's API keys view. Its layers ride the `DataServer`
235
+ endpoint, the xyz-shaped form of its WMTS services:
236
+
237
+ ```text
238
+ https://t{0-7}.tianditu.gov.cn/DataServer?T={layer}&x={x}&y={y}&l={z}&tk={api-key}
239
+ ```
240
+
241
+ Tianditu splits every basemap from its labels, so the `-labels` overlays are separate transparent
242
+ layers. Pair them with `allowMultiple: true` to stack them over their base:
243
+
244
+ ```typescript
245
+ const control = new BasemapControl({
246
+ allowMultiple: true,
247
+ tiandituApiKey: 'YOUR_TIANDITU_API_KEY',
248
+ defaultBasemapId: 'tianditu-vector',
249
+ });
250
+ await control.addBasemap('tianditu-vector-labels');
251
+ ```
252
+
253
+ The same applies to `amap-labels`, a transparent roads-and-labels overlay meant to sit on top of
254
+ `amap-satellite`. Note that `tianditu-terrain` and `tianditu-terrain-labels` are only published to
255
+ zoom 14, where the vector and imagery layers reach 18, and Amap serves a "no imagery" placeholder
256
+ rather than a 404 past zoom 18, so `amap-satellite` caps there and lets MapLibre overzoom.
257
+
258
+ > **Terms of use.** Tianditu's key-based access is the sanctioned route and the one to build on. The
259
+ > Amap and Tencent tile endpoints are widely used but are not documented public APIs, and carry the
260
+ > same caveat as the keyless Google tiles below: fine for development and demos, but the operators
261
+ > may rate-limit, change, or block them at any time. Obtain a commercial key from the provider before
262
+ > shipping either one.
263
+
206
264
  ### Traffic Overlays
207
265
 
208
266
  The catalog ships real-time traffic overlays in the `Traffic` category. They are
@@ -297,11 +355,13 @@ API key and load straight from `https://styles.maptoolkit.org/{styleId}.json`:
297
355
  | `panelWidth` | `number` | `340` | Floating panel width in pixels |
298
356
  | `className` | `string` | `''` | Extra class for the control button container |
299
357
  | `mapTilerApiKey` | `string` | `undefined` | Initial MapTiler API key for built-in MapTiler styles |
358
+ | `cartoApiKey` | `string` | `undefined` | Initial CARTO API key for built-in CARTO raster basemaps |
300
359
  | `amazonApiKey` | `string` | `undefined` | Initial Amazon Location API key for built-in Amazon styles |
301
360
  | `awsRegion` | `string` | `'us-east-1'` | AWS region for built-in Amazon Location styles |
302
361
  | `mapboxAccessToken` | `string` | `undefined` | Initial Mapbox access token for built-in Mapbox styles and the Mapbox Traffic overlay |
303
362
  | `protomapsApiKey` | `string` | `undefined` | Initial Protomaps API key for built-in Protomaps styles |
304
363
  | `stadiaApiKey` | `string` | `undefined` | Initial Stadia Maps API key for built-in Stadia and Stadia x Stamen basemaps |
364
+ | `tiandituApiKey` | `string` | `undefined` | Initial Tianditu API key (`tk`) for the built-in Tianditu basemaps |
305
365
  | `tomtomApiKey` | `string` | `undefined` | Initial TomTom API key for the TomTom Traffic overlays |
306
366
  | `hereApiKey` | `string` | `undefined` | Initial HERE API key for the HERE Traffic overlay |
307
367
  | `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) |
@@ -366,10 +426,12 @@ const control = new BasemapControl({
366
426
  - `toggleBasemap(id)` - Add the raster basemap if inactive, otherwise remove it
367
427
  - `isBasemapActive(id)` - Whether the basemap is currently active
368
428
  - `setMapTilerApiKey(apiKey)` - Set or update the MapTiler API key used by MapTiler styles
429
+ - `setCartoApiKey(apiKey)` - Set or update the CARTO API key used by CARTO raster basemaps
369
430
  - `setAmazonCredentials(apiKey, awsRegion)` - Set or update Amazon Location credentials
370
431
  - `setMapboxAccessToken(accessToken)` - Set or update the Mapbox access token
371
432
  - `setProtomapsApiKey(apiKey)` - Set or update the Protomaps API key used by Protomaps styles
372
433
  - `setStadiaApiKey(apiKey)` - Set or update the Stadia Maps API key used by Stadia basemaps
434
+ - `setTiandituApiKey(apiKey)` - Set or update the Tianditu API key used by Tianditu basemaps
373
435
  - `setTomTomApiKey(apiKey)` - Set or update the TomTom API key used by TomTom Traffic overlays
374
436
  - `setHereApiKey(apiKey)` - Set or update the HERE API key used by the HERE Traffic overlay
375
437
  - `setGoogleMapsApiKey(apiKey)` - Set or update the Google Maps API key used by the Google Traffic overlay and the base Google basemaps
@@ -2,6 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  const DEFAULT_BASEMAP_PROVIDERS = [
5
+ { id: "amap", name: "Amap", category: "Regional" },
5
6
  { id: "amazon", name: "Amazon Location", category: "General" },
6
7
  { id: "carto", name: "Carto", category: "General" },
7
8
  { id: "cyclosm", name: "CyclOSM", category: "Cycling" },
@@ -21,6 +22,8 @@ const DEFAULT_BASEMAP_PROVIDERS = [
21
22
  { id: "protomaps", name: "Protomaps", category: "Vector Styles" },
22
23
  { id: "stadia", name: "Stadia Maps", category: "General" },
23
24
  { id: "swisstopo", name: "Swiss Federal Geoportal", category: "Regional" },
25
+ { id: "tencent", name: "Tencent Maps", category: "Regional" },
26
+ { id: "tianditu", name: "Tianditu", category: "Regional" },
24
27
  { id: "tomtom", name: "TomTom", category: "Traffic" },
25
28
  { id: "topplusopen", name: "TopPlusOpen", category: "Regional" },
26
29
  { id: "usgs", name: "USGS", category: "United States" },
@@ -35,6 +38,7 @@ function rasterBasemap({
35
38
  attribution,
36
39
  tiles,
37
40
  maxzoom = 19,
41
+ scheme,
38
42
  tags = []
39
43
  }) {
40
44
  return {
@@ -49,7 +53,8 @@ function rasterBasemap({
49
53
  type: "raster",
50
54
  tiles,
51
55
  tileSize: 256,
52
- maxzoom
56
+ maxzoom,
57
+ scheme
53
58
  },
54
59
  tags
55
60
  };
@@ -202,6 +207,35 @@ function stadiaRasterBasemap({
202
207
  tags: ["stadia", ...stamen ? ["stamen"] : [], ...tags]
203
208
  });
204
209
  }
210
+ const TIANDITU_HOSTS = [0, 1, 2, 3, 4, 5, 6, 7];
211
+ function tiandituRasterBasemap({
212
+ id,
213
+ name,
214
+ layer,
215
+ category,
216
+ description,
217
+ maxzoom = 18,
218
+ tags = []
219
+ }) {
220
+ return rasterBasemap({
221
+ id,
222
+ name,
223
+ provider: "tianditu",
224
+ category,
225
+ description,
226
+ attribution: TIANDITU_ATTRIBUTION,
227
+ tiles: TIANDITU_HOSTS.map(
228
+ (host) => `https://t${host}.tianditu.gov.cn/DataServer?T=${layer}&x={x}&y={y}&l={z}&tk={api-key}`
229
+ ),
230
+ maxzoom,
231
+ tags: ["tianditu", "china", "天地图", ...tags]
232
+ });
233
+ }
234
+ function amapTiles(host, query) {
235
+ return [1, 2, 3, 4].map(
236
+ (index) => `https://${host}0${index}.is.autonavi.com/appmaptile?${query}&x={x}&y={y}&z={z}`
237
+ );
238
+ }
205
239
  function sortProviders(providers) {
206
240
  return [...providers].sort((a, b) => a.name.localeCompare(b.name));
207
241
  }
@@ -216,6 +250,10 @@ const STADIA_ATTRIBUTION = '&copy; <a href="https://stadiamaps.com/" target="_bl
216
250
  const STADIA_STAMEN_ATTRIBUTION = '&copy; <a href="https://stadiamaps.com/" target="_blank" rel="noopener">Stadia Maps</a> &copy; <a href="https://stamen.com/" target="_blank" rel="noopener">Stamen Design</a> &copy; <a href="https://openmaptiles.org/" target="_blank" rel="noopener">OpenMapTiles</a> &copy; OpenStreetMap contributors';
217
251
  const PROTOMAPS_ATTRIBUTION = '<a href="https://protomaps.com" target="_blank" rel="noopener">Protomaps</a> &copy; <a href="https://openstreetmap.org" target="_blank" rel="noopener">OpenStreetMap</a>';
218
252
  const OPENFREEMAP_ATTRIBUTION = "OpenFreeMap &copy; OpenMapTiles Data from OpenStreetMap";
253
+ const TIANDITU_ATTRIBUTION = '&copy; <a href="https://www.tianditu.gov.cn" target="_blank" rel="noopener">天地图 Tianditu</a> (National Platform for Common Geospatial Information Services)';
254
+ const AMAP_ATTRIBUTION = '&copy; <a href="https://www.amap.com" target="_blank" rel="noopener">高德地图 Amap</a>';
255
+ const TENCENT_ATTRIBUTION = '&copy; <a href="https://map.qq.com" target="_blank" rel="noopener">腾讯地图 Tencent Maps</a>';
256
+ const GCJ02_WARNING = "Tiles are in the GCJ-02 datum, so WGS84 data overlaid on them appears offset by roughly 100-700 m.";
219
257
  const TOMTOM_ATTRIBUTION = "&copy; TomTom";
220
258
  const HERE_ATTRIBUTION = "&copy; HERE";
221
259
  const GOOGLE_ATTRIBUTION = "&copy; Google";
@@ -623,10 +661,10 @@ const DEFAULT_BASEMAPS = [
623
661
  description: "Light basemap for data overlays.",
624
662
  attribution: CARTO_ATTRIBUTION,
625
663
  tiles: [
626
- "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
627
- "https://b.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
628
- "https://c.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
629
- "https://d.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"
664
+ "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png?key={api-key}",
665
+ "https://b.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png?key={api-key}",
666
+ "https://c.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png?key={api-key}",
667
+ "https://d.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png?key={api-key}"
630
668
  ],
631
669
  maxzoom: 20,
632
670
  tags: ["carto", "light", "positron"]
@@ -638,7 +676,7 @@ const DEFAULT_BASEMAPS = [
638
676
  category: "Light",
639
677
  description: "Light basemap without labels.",
640
678
  attribution: CARTO_ATTRIBUTION,
641
- tiles: ["https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"],
679
+ tiles: ["https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png?key={api-key}"],
642
680
  maxzoom: 20,
643
681
  tags: ["carto", "light", "no labels"]
644
682
  }),
@@ -650,7 +688,7 @@ const DEFAULT_BASEMAPS = [
650
688
  description: "Carto Positron labels layer.",
651
689
  attribution: CARTO_ATTRIBUTION,
652
690
  tiles: [
653
- "https://a.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"
691
+ "https://a.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png?key={api-key}"
654
692
  ],
655
693
  maxzoom: 20,
656
694
  tags: ["carto", "labels"]
@@ -663,10 +701,10 @@ const DEFAULT_BASEMAPS = [
663
701
  description: "Dark basemap for high-contrast overlays.",
664
702
  attribution: CARTO_ATTRIBUTION,
665
703
  tiles: [
666
- "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
667
- "https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
668
- "https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
669
- "https://d.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
704
+ "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png?key={api-key}",
705
+ "https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png?key={api-key}",
706
+ "https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png?key={api-key}",
707
+ "https://d.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png?key={api-key}"
670
708
  ],
671
709
  maxzoom: 20,
672
710
  tags: ["carto", "dark", "matter"]
@@ -678,7 +716,7 @@ const DEFAULT_BASEMAPS = [
678
716
  category: "Dark",
679
717
  description: "Dark basemap without labels.",
680
718
  attribution: CARTO_ATTRIBUTION,
681
- tiles: ["https://a.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png"],
719
+ tiles: ["https://a.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png?key={api-key}"],
682
720
  maxzoom: 20,
683
721
  tags: ["carto", "dark", "no labels"]
684
722
  }),
@@ -689,7 +727,7 @@ const DEFAULT_BASEMAPS = [
689
727
  category: "Labels",
690
728
  description: "Carto Dark Matter labels layer.",
691
729
  attribution: CARTO_ATTRIBUTION,
692
- tiles: ["https://a.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}.png"],
730
+ tiles: ["https://a.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}.png?key={api-key}"],
693
731
  maxzoom: 20,
694
732
  tags: ["carto", "dark", "labels"]
695
733
  }),
@@ -701,10 +739,10 @@ const DEFAULT_BASEMAPS = [
701
739
  description: "Detailed street basemap with subtle styling.",
702
740
  attribution: CARTO_ATTRIBUTION,
703
741
  tiles: [
704
- "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
705
- "https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
706
- "https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
707
- "https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png"
742
+ "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png?key={api-key}",
743
+ "https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png?key={api-key}",
744
+ "https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png?key={api-key}",
745
+ "https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png?key={api-key}"
708
746
  ],
709
747
  maxzoom: 20,
710
748
  tags: ["carto", "street", "voyager"]
@@ -717,7 +755,7 @@ const DEFAULT_BASEMAPS = [
717
755
  description: "Carto Voyager without labels.",
718
756
  attribution: CARTO_ATTRIBUTION,
719
757
  tiles: [
720
- "https://a.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png"
758
+ "https://a.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png?key={api-key}"
721
759
  ],
722
760
  maxzoom: 20,
723
761
  tags: ["carto", "voyager", "no labels"]
@@ -730,7 +768,7 @@ const DEFAULT_BASEMAPS = [
730
768
  description: "Carto Voyager labels layer.",
731
769
  attribution: CARTO_ATTRIBUTION,
732
770
  tiles: [
733
- "https://a.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}.png"
771
+ "https://a.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}.png?key={api-key}"
734
772
  ],
735
773
  maxzoom: 20,
736
774
  tags: ["carto", "voyager", "labels"]
@@ -743,7 +781,7 @@ const DEFAULT_BASEMAPS = [
743
781
  description: "Carto Voyager labels-under variant.",
744
782
  attribution: CARTO_ATTRIBUTION,
745
783
  tiles: [
746
- "https://a.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png"
784
+ "https://a.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png?key={api-key}"
747
785
  ],
748
786
  maxzoom: 20,
749
787
  tags: ["carto", "voyager", "labels"]
@@ -1216,6 +1254,130 @@ const DEFAULT_BASEMAPS = [
1216
1254
  maxzoom: 18,
1217
1255
  tags: ["switzerland", "imagery"]
1218
1256
  }),
1257
+ tiandituRasterBasemap({
1258
+ id: "tianditu-vector",
1259
+ name: "Tianditu Vector",
1260
+ layer: "vec_w",
1261
+ category: "Regional",
1262
+ description: "China's official street map, reachable from mainland China. Published in CGCS2000, so WGS84 data overlays without a datum shift. Pair with Tianditu Vector Labels. Requires a free Tianditu API key.",
1263
+ tags: ["vector", "street", "vec"]
1264
+ }),
1265
+ tiandituRasterBasemap({
1266
+ id: "tianditu-vector-labels",
1267
+ name: "Tianditu Vector Labels",
1268
+ layer: "cva_w",
1269
+ category: "Labels",
1270
+ description: "Place name and road label overlay for Tianditu Vector. Requires a free Tianditu API key.",
1271
+ tags: ["labels", "annotation", "overlay", "cva"]
1272
+ }),
1273
+ tiandituRasterBasemap({
1274
+ id: "tianditu-imagery",
1275
+ name: "Tianditu Imagery",
1276
+ layer: "img_w",
1277
+ category: "Imagery",
1278
+ description: "China's official satellite imagery, reachable from mainland China. Pair with Tianditu Imagery Labels. Requires a free Tianditu API key.",
1279
+ tags: ["imagery", "satellite", "img"]
1280
+ }),
1281
+ tiandituRasterBasemap({
1282
+ id: "tianditu-imagery-labels",
1283
+ name: "Tianditu Imagery Labels",
1284
+ layer: "cia_w",
1285
+ category: "Labels",
1286
+ description: "Place name and road label overlay for Tianditu Imagery. Requires a free Tianditu API key.",
1287
+ tags: ["labels", "annotation", "overlay", "cia"]
1288
+ }),
1289
+ tiandituRasterBasemap({
1290
+ id: "tianditu-terrain",
1291
+ name: "Tianditu Terrain",
1292
+ layer: "ter_w",
1293
+ category: "Terrain",
1294
+ description: "China's official shaded relief terrain map. Pair with Tianditu Terrain Labels. Requires a free Tianditu API key.",
1295
+ // The terrain pair is only published to zoom 14, unlike the vector and
1296
+ // imagery layers, which reach 18.
1297
+ maxzoom: 14,
1298
+ tags: ["terrain", "relief", "hillshade", "ter"]
1299
+ }),
1300
+ tiandituRasterBasemap({
1301
+ id: "tianditu-terrain-labels",
1302
+ name: "Tianditu Terrain Labels",
1303
+ layer: "cta_w",
1304
+ category: "Labels",
1305
+ description: "Place name label overlay for Tianditu Terrain. Requires a free Tianditu API key.",
1306
+ maxzoom: 14,
1307
+ tags: ["labels", "annotation", "overlay", "cta"]
1308
+ }),
1309
+ rasterBasemap({
1310
+ id: "amap-street",
1311
+ name: "Amap Street",
1312
+ provider: "amap",
1313
+ category: "Regional",
1314
+ description: `Amap street map of China, reachable from mainland China. ${GCJ02_WARNING}`,
1315
+ attribution: AMAP_ATTRIBUTION,
1316
+ tiles: amapTiles("wprd", "lang=zh_cn&size=1&scl=1&style=7"),
1317
+ tags: ["amap", "gaode", "高德", "china", "street", "gcj02"]
1318
+ }),
1319
+ rasterBasemap({
1320
+ id: "amap-satellite",
1321
+ name: "Amap Satellite",
1322
+ provider: "amap",
1323
+ category: "Imagery",
1324
+ description: `Amap satellite imagery, reachable from mainland China. Pair with Amap Labels. ${GCJ02_WARNING}`,
1325
+ attribution: AMAP_ATTRIBUTION,
1326
+ tiles: amapTiles("webst", "style=6"),
1327
+ // Past zoom 18 Amap returns a "no imagery" placeholder tile rather than a
1328
+ // 404, so cap here and let MapLibre overzoom instead.
1329
+ maxzoom: 18,
1330
+ tags: ["amap", "gaode", "高德", "china", "satellite", "imagery", "gcj02"]
1331
+ }),
1332
+ rasterBasemap({
1333
+ id: "amap-labels",
1334
+ name: "Amap Labels",
1335
+ provider: "amap",
1336
+ category: "Labels",
1337
+ description: `Transparent Amap road and label overlay, for use over Amap Satellite. ${GCJ02_WARNING}`,
1338
+ attribution: AMAP_ATTRIBUTION,
1339
+ tiles: amapTiles("webst", "style=8"),
1340
+ tags: [
1341
+ "amap",
1342
+ "gaode",
1343
+ "高德",
1344
+ "china",
1345
+ "labels",
1346
+ "roads",
1347
+ "overlay",
1348
+ "gcj02"
1349
+ ]
1350
+ }),
1351
+ rasterBasemap({
1352
+ id: "tencent-street",
1353
+ name: "Tencent Street",
1354
+ provider: "tencent",
1355
+ category: "Regional",
1356
+ description: `Tencent street map of China, reachable from mainland China. ${GCJ02_WARNING}`,
1357
+ attribution: TENCENT_ATTRIBUTION,
1358
+ // Tencent numbers tile rows from the bottom, so the source is TMS rather
1359
+ // than xyz; MapLibre flips `{y}` when `scheme` says so.
1360
+ tiles: [0, 1, 2, 3].map(
1361
+ (host) => `https://rt${host}.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=1&scene=0`
1362
+ ),
1363
+ maxzoom: 18,
1364
+ scheme: "tms",
1365
+ tags: ["tencent", "qq", "腾讯", "china", "street", "gcj02"]
1366
+ }),
1367
+ rasterBasemap({
1368
+ id: "tencent-dark",
1369
+ name: "Tencent Dark",
1370
+ provider: "tencent",
1371
+ category: "Dark",
1372
+ description: `Dark Tencent street map for high-contrast overlays, reachable from mainland China. ${GCJ02_WARNING}`,
1373
+ attribution: TENCENT_ATTRIBUTION,
1374
+ tiles: [0, 1, 2, 3].map(
1375
+ (host) => `https://rt${host}.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=4&scene=0`
1376
+ ),
1377
+ maxzoom: 18,
1378
+ scheme: "tms",
1379
+ tags: ["tencent", "qq", "腾讯", "china", "dark", "gcj02"]
1380
+ }),
1219
1381
  rasterBasemap({
1220
1382
  id: "topplusopen-color",
1221
1383
  name: "TopPlusOpen Color",
@@ -1726,6 +1888,10 @@ const MAPTILER_API_KEY_QUERY = "?key";
1726
1888
  const MAPTILER_API_KEY_EMPTY_QUERY = "?key=";
1727
1889
  const AWS_REGION_PLACEHOLDER = "{aws-region}";
1728
1890
  const PROVIDER_CREDENTIAL_HELP = {
1891
+ carto: {
1892
+ url: "https://carto.com/basemaps/apikey/",
1893
+ label: "Get a CARTO API key"
1894
+ },
1729
1895
  amazon: {
1730
1896
  url: "https://docs.aws.amazon.com/location/latest/developerguide/using-apikeys.html",
1731
1897
  label: "Get an Amazon API key"
@@ -1746,6 +1912,10 @@ const PROVIDER_CREDENTIAL_HELP = {
1746
1912
  url: "https://client.stadiamaps.com/signup/",
1747
1913
  label: "Get a Stadia Maps API key"
1748
1914
  },
1915
+ tianditu: {
1916
+ url: "https://console.tianditu.gov.cn/api/key",
1917
+ label: "Get a Tianditu API key"
1918
+ },
1749
1919
  tomtom: {
1750
1920
  url: "https://developer.tomtom.com/how-to-get-tomtom-api-key",
1751
1921
  label: "Get a TomTom API key"
@@ -1759,6 +1929,14 @@ const PROVIDER_CREDENTIAL_HELP = {
1759
1929
  label: "Get a Google Maps API key"
1760
1930
  }
1761
1931
  };
1932
+ const RASTER_KEY_LABELS = {
1933
+ carto: "CARTO API key",
1934
+ google: "Google Maps API key",
1935
+ here: "HERE API key",
1936
+ stadia: "Stadia Maps API key",
1937
+ tianditu: "Tianditu API key",
1938
+ tomtom: "TomTom API key"
1939
+ };
1762
1940
  const STYLE_AUTH_GRACE_MS = 2500;
1763
1941
  const MIN_PANEL_WIDTH = 240;
1764
1942
  const MIN_PANEL_HEIGHT = 200;
@@ -1812,11 +1990,13 @@ class BasemapControl {
1812
1990
  // stacked raster overlay so each can be added or removed independently.
1813
1991
  __publicField(this, "_managedRasters", new globalThis.Map());
1814
1992
  __publicField(this, "_mapTilerApiKey", "");
1993
+ __publicField(this, "_cartoApiKey", "");
1815
1994
  __publicField(this, "_amazonApiKey", "");
1816
1995
  __publicField(this, "_awsRegion", "");
1817
1996
  __publicField(this, "_mapboxAccessToken", "");
1818
1997
  __publicField(this, "_protomapsApiKey", "");
1819
1998
  __publicField(this, "_stadiaApiKey", "");
1999
+ __publicField(this, "_tiandituApiKey", "");
1820
2000
  __publicField(this, "_tomtomApiKey", "");
1821
2001
  __publicField(this, "_hereApiKey", "");
1822
2002
  __publicField(this, "_googleMapsApiKey", "");
@@ -1876,11 +2056,13 @@ class BasemapControl {
1876
2056
  });
1877
2057
  this._options = { ...DEFAULT_OPTIONS, ...options };
1878
2058
  this._mapTilerApiKey = (options == null ? void 0 : options.mapTilerApiKey) ?? "";
2059
+ this._cartoApiKey = (options == null ? void 0 : options.cartoApiKey) ?? "";
1879
2060
  this._amazonApiKey = (options == null ? void 0 : options.amazonApiKey) ?? "";
1880
2061
  this._awsRegion = (options == null ? void 0 : options.awsRegion) ?? "us-east-1";
1881
2062
  this._mapboxAccessToken = (options == null ? void 0 : options.mapboxAccessToken) ?? "";
1882
2063
  this._protomapsApiKey = (options == null ? void 0 : options.protomapsApiKey) ?? "";
1883
2064
  this._stadiaApiKey = (options == null ? void 0 : options.stadiaApiKey) ?? "";
2065
+ this._tiandituApiKey = (options == null ? void 0 : options.tiandituApiKey) ?? "";
1884
2066
  this._tomtomApiKey = (options == null ? void 0 : options.tomtomApiKey) ?? "";
1885
2067
  this._hereApiKey = (options == null ? void 0 : options.hereApiKey) ?? "";
1886
2068
  this._googleMapsApiKey = (options == null ? void 0 : options.googleMapsApiKey) ?? "";
@@ -1999,6 +2181,12 @@ class BasemapControl {
1999
2181
  this._renderContent();
2000
2182
  this._emit({ type: "statechange", state: this.getState() });
2001
2183
  }
2184
+ setCartoApiKey(apiKey) {
2185
+ this._cartoApiKey = apiKey;
2186
+ this._clearError();
2187
+ this._renderContent();
2188
+ this._emit({ type: "statechange", state: this.getState() });
2189
+ }
2002
2190
  setAmazonCredentials(apiKey, awsRegion = this._awsRegion) {
2003
2191
  this._amazonApiKey = apiKey;
2004
2192
  this._awsRegion = awsRegion;
@@ -2024,6 +2212,12 @@ class BasemapControl {
2024
2212
  this._renderContent();
2025
2213
  this._emit({ type: "statechange", state: this.getState() });
2026
2214
  }
2215
+ setTiandituApiKey(apiKey) {
2216
+ this._tiandituApiKey = apiKey;
2217
+ this._clearError();
2218
+ this._renderContent();
2219
+ this._emit({ type: "statechange", state: this.getState() });
2220
+ }
2027
2221
  setTomTomApiKey(apiKey) {
2028
2222
  this._tomtomApiKey = apiKey;
2029
2223
  this._clearError();
@@ -2136,12 +2330,16 @@ class BasemapControl {
2136
2330
  let resolvedStyleUrl;
2137
2331
  if (isOverlay) {
2138
2332
  await this._waitForStyleReady();
2333
+ const resolvedRasterTiles = basemap.source.type === "raster" ? await this._resolveRasterTiles(basemap) : void 0;
2334
+ if (basemap.source.type === "vector-overlay") {
2335
+ this._resolveVectorOverlaySource(basemap, basemap.source);
2336
+ }
2139
2337
  if (effectiveMode === "replace") {
2140
2338
  this._removeManagedBasemap();
2141
2339
  } else {
2142
2340
  this._removeManagedRaster(basemap.id);
2143
2341
  }
2144
- managedRaster = await this._addOverlay(basemap);
2342
+ managedRaster = await this._addOverlay(basemap, resolvedRasterTiles);
2145
2343
  } else {
2146
2344
  const styleUrl = this._resolveStyleUrl(basemap);
2147
2345
  resolvedStyleUrl = styleUrl;
@@ -2598,6 +2796,23 @@ class BasemapControl {
2598
2796
  // missing-credential error, so the two never drift (#837).
2599
2797
  _providerFieldDefs() {
2600
2798
  return [
2799
+ {
2800
+ provider: "carto",
2801
+ label: "CARTO",
2802
+ has: this._hasCartoBasemaps(),
2803
+ fields: [
2804
+ {
2805
+ className: "basemap-control-carto-key",
2806
+ type: "password",
2807
+ placeholder: "CARTO API key",
2808
+ ariaLabel: "CARTO API key",
2809
+ value: this._cartoApiKey,
2810
+ onInput: (value) => {
2811
+ this._cartoApiKey = value;
2812
+ }
2813
+ }
2814
+ ]
2815
+ },
2601
2816
  {
2602
2817
  provider: "maptiler",
2603
2818
  label: "MapTiler",
@@ -2666,6 +2881,23 @@ class BasemapControl {
2666
2881
  }
2667
2882
  ]
2668
2883
  },
2884
+ {
2885
+ provider: "tianditu",
2886
+ label: "Tianditu",
2887
+ has: this._hasTiandituBasemaps(),
2888
+ fields: [
2889
+ {
2890
+ className: "basemap-control-tianditu-key",
2891
+ type: "password",
2892
+ placeholder: "Tianditu API key",
2893
+ ariaLabel: "Tianditu API key",
2894
+ value: this._tiandituApiKey,
2895
+ onInput: (value) => {
2896
+ this._tiandituApiKey = value;
2897
+ }
2898
+ }
2899
+ ]
2900
+ },
2669
2901
  {
2670
2902
  provider: "tomtom",
2671
2903
  label: "TomTom",
@@ -3082,9 +3314,15 @@ class BasemapControl {
3082
3314
  _hasProtomapsBasemaps() {
3083
3315
  return this._basemaps.some((basemap) => basemap.provider === "protomaps");
3084
3316
  }
3317
+ _hasCartoBasemaps() {
3318
+ return this._basemaps.some((basemap) => basemap.provider === "carto");
3319
+ }
3085
3320
  _hasStadiaBasemaps() {
3086
3321
  return this._basemaps.some((basemap) => basemap.provider === "stadia");
3087
3322
  }
3323
+ _hasTiandituBasemaps() {
3324
+ return this._basemaps.some((basemap) => basemap.provider === "tianditu");
3325
+ }
3088
3326
  _hasTomTomBasemaps() {
3089
3327
  return this._basemaps.some((basemap) => basemap.provider === "tomtom");
3090
3328
  }
@@ -3101,7 +3339,7 @@ class BasemapControl {
3101
3339
  );
3102
3340
  }
3103
3341
  _hasProviderSettings() {
3104
- return this._hasMapTilerBasemaps() || this._hasAmazonBasemaps() || this._hasMapboxBasemaps() || this._hasProtomapsBasemaps() || this._hasStadiaBasemaps() || this._hasTomTomBasemaps() || this._hasHereBasemaps() || this._hasGoogleApiKeyBasemaps();
3342
+ return this._hasMapTilerBasemaps() || this._hasCartoBasemaps() || this._hasAmazonBasemaps() || this._hasMapboxBasemaps() || this._hasProtomapsBasemaps() || this._hasStadiaBasemaps() || this._hasTiandituBasemaps() || this._hasTomTomBasemaps() || this._hasHereBasemaps() || this._hasGoogleApiKeyBasemaps();
3105
3343
  }
3106
3344
  _resolveStyleUrl(basemap) {
3107
3345
  if (basemap.source.type !== "style" && basemap.source.type !== "vector-style") {
@@ -3248,15 +3486,15 @@ class BasemapControl {
3248
3486
  }
3249
3487
  // Add a stackable overlay (raster tiles or a vector overlay) to the map,
3250
3488
  // resolving any provider credentials first.
3251
- async _addOverlay(basemap) {
3489
+ async _addOverlay(basemap, resolvedRasterTiles) {
3252
3490
  if (basemap.source.type === "vector-overlay") {
3253
3491
  return this._addVectorOverlay(basemap, basemap.source);
3254
3492
  }
3255
- return this._addRasterBasemap(basemap);
3493
+ return this._addRasterBasemap(basemap, resolvedRasterTiles);
3256
3494
  }
3257
- async _addRasterBasemap(basemap) {
3495
+ async _addRasterBasemap(basemap, resolvedTiles) {
3258
3496
  if (!this._map || basemap.source.type !== "raster") return void 0;
3259
- const tiles = await this._resolveRasterTiles(basemap);
3497
+ const tiles = resolvedTiles ?? await this._resolveRasterTiles(basemap);
3260
3498
  const sourceId = `${CONTROL_SOURCE_PREFIX}-${basemap.id}`;
3261
3499
  const layerId = [CONTROL_LAYER_PREFIX, basemap.id].filter(Boolean).join("-");
3262
3500
  const beforeId = this._getBasemapInsertBeforeId();
@@ -3358,9 +3596,11 @@ class BasemapControl {
3358
3596
  return tiles.map((tile) => tile.split(API_KEY_PLACEHOLDER).join(encoded));
3359
3597
  }
3360
3598
  _rasterApiKeyFor(provider) {
3599
+ if (provider === "carto") return this._cartoApiKey.trim();
3361
3600
  if (provider === "tomtom") return this._tomtomApiKey.trim();
3362
3601
  if (provider === "here") return this._hereApiKey.trim();
3363
3602
  if (provider === "stadia") return this._stadiaApiKey.trim();
3603
+ if (provider === "tianditu") return this._tiandituApiKey.trim();
3364
3604
  if (provider === "google") return this._googleMapsApiKey.trim();
3365
3605
  return "";
3366
3606
  }
@@ -3388,7 +3628,7 @@ class BasemapControl {
3388
3628
  }
3389
3629
  }
3390
3630
  _missingRasterKeyError(provider) {
3391
- const label = provider === "tomtom" ? "TomTom API key" : provider === "here" ? "HERE API key" : provider === "stadia" ? "Stadia Maps API key" : provider === "google" ? "Google Maps API key" : "API key";
3631
+ const label = RASTER_KEY_LABELS[provider] ?? "API key";
3392
3632
  const helpProvider = provider in PROVIDER_CREDENTIAL_HELP ? provider : "maptiler";
3393
3633
  return new MissingCredentialError(`Enter a ${label} before applying this layer.`, helpProvider);
3394
3634
  }
@@ -3624,4 +3864,4 @@ export {
3624
3864
  getBasemapCategories as g,
3625
3865
  resolveBasemapProviders as r
3626
3866
  };
3627
- //# sourceMappingURL=BasemapControl-Cdg7HMeU.js.map
3867
+ //# sourceMappingURL=BasemapControl-CSw9YZ4S.js.map