maplibre-gl-basemap-control 0.5.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/README.md +47 -1
- package/dist/{BasemapControl-BITP2bjU.js → BasemapControl-CfFepxWc.js} +411 -13
- package/dist/BasemapControl-CfFepxWc.js.map +1 -0
- package/dist/{BasemapControl-B8WWNorQ.cjs → BasemapControl-qDrnfFqz.cjs} +411 -13
- package/dist/BasemapControl-qDrnfFqz.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/types/lib/core/BasemapControl.d.ts +19 -0
- package/dist/types/lib/core/BasemapControl.d.ts.map +1 -1
- package/dist/types/lib/core/catalog.d.ts.map +1 -1
- package/dist/types/lib/core/types.d.ts +55 -2
- package/dist/types/lib/core/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/BasemapControl-B8WWNorQ.cjs.map +0 -1
- package/dist/BasemapControl-BITP2bjU.js.map +0 -1
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, and Mapbox styles
|
|
12
|
+
- Stackable traffic overlays for TomTom, HERE, Mapbox, and Google
|
|
12
13
|
- Custom basemap and provider definitions
|
|
13
14
|
- MapLibre `IControl` implementation
|
|
14
15
|
- React wrapper and state hook
|
|
@@ -171,6 +172,45 @@ Mapbox style URLs follow this form:
|
|
|
171
172
|
https://api.mapbox.com/styles/v1/mapbox/{styleId}?access_token={api-key}
|
|
172
173
|
```
|
|
173
174
|
|
|
175
|
+
### Traffic Overlays
|
|
176
|
+
|
|
177
|
+
The catalog ships real-time traffic overlays in the `Traffic` category. They are
|
|
178
|
+
stackable overlays rather than full basemaps, so enable `allowMultiple: true` (or
|
|
179
|
+
toggle "Add basemaps" in the panel) to lay them on top of any basemap. Click an
|
|
180
|
+
active traffic layer again to remove it.
|
|
181
|
+
|
|
182
|
+
| Basemap id | Provider | Credential |
|
|
183
|
+
|------------|----------|------------|
|
|
184
|
+
| `tomtom-traffic-flow-relative`, `tomtom-traffic-flow-absolute`, `tomtom-traffic-flow-relative-delay` | TomTom | `tomtomApiKey` |
|
|
185
|
+
| `here-traffic-flow` | HERE | `hereApiKey` |
|
|
186
|
+
| `mapbox-traffic` | Mapbox | `mapboxAccessToken` |
|
|
187
|
+
| `google-traffic` | Google | `googleMapsApiKey` |
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const control = new BasemapControl({
|
|
191
|
+
allowMultiple: true,
|
|
192
|
+
tomtomApiKey: 'YOUR_TOMTOM_API_KEY',
|
|
193
|
+
hereApiKey: 'YOUR_HERE_API_KEY',
|
|
194
|
+
mapboxAccessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
|
|
195
|
+
googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The TomTom and HERE overlays are transparent raster flow tiles; Mapbox Traffic is
|
|
200
|
+
a vector overlay colored by congestion level. Google Traffic uses the
|
|
201
|
+
[Map Tiles API](https://developers.google.com/maps/documentation/tile/session_tokens):
|
|
202
|
+
the control creates a tile session (with `layerTypes: ['layerTraffic']`) using your
|
|
203
|
+
key, caches the session token until it expires, then loads the traffic tiles. The
|
|
204
|
+
key must have the Map Tiles API enabled.
|
|
205
|
+
|
|
206
|
+
> **Troubleshooting `API_KEY_SERVICE_BLOCKED`** ("Requests to this API tile method
|
|
207
|
+
> ...Bootstrap are blocked"): this is a key restriction, not project API
|
|
208
|
+
> enablement. Enabling the Map Tiles API on the project is not enough if the key
|
|
209
|
+
> itself has an API allow-list. In Cloud Console go to **APIs & Services →
|
|
210
|
+
> Credentials → your key → API restrictions** and either choose "Don't restrict
|
|
211
|
+
> key" or add **Map Tiles API** to the allowed list, then save (changes can take a
|
|
212
|
+
> few minutes to propagate). Make sure billing is enabled on the project too.
|
|
213
|
+
|
|
174
214
|
## API
|
|
175
215
|
|
|
176
216
|
### BasemapControl Options
|
|
@@ -185,7 +225,10 @@ https://api.mapbox.com/styles/v1/mapbox/{styleId}?access_token={api-key}
|
|
|
185
225
|
| `mapTilerApiKey` | `string` | `undefined` | Initial MapTiler API key for built-in MapTiler styles |
|
|
186
226
|
| `amazonApiKey` | `string` | `undefined` | Initial Amazon Location API key for built-in Amazon styles |
|
|
187
227
|
| `awsRegion` | `string` | `'us-east-1'` | AWS region for built-in Amazon Location styles |
|
|
188
|
-
| `mapboxAccessToken` | `string` | `undefined` | Initial Mapbox access token for built-in Mapbox styles |
|
|
228
|
+
| `mapboxAccessToken` | `string` | `undefined` | Initial Mapbox access token for built-in Mapbox styles and the Mapbox Traffic overlay |
|
|
229
|
+
| `tomtomApiKey` | `string` | `undefined` | Initial TomTom API key for the TomTom Traffic overlays |
|
|
230
|
+
| `hereApiKey` | `string` | `undefined` | Initial HERE API key for the HERE Traffic overlay |
|
|
231
|
+
| `googleMapsApiKey` | `string` | `undefined` | Initial Google Maps API key (Map Tiles API) for the Google Traffic overlay |
|
|
189
232
|
| `basemaps` | `BasemapDefinition[]` | `[]` | Custom basemaps to add or use |
|
|
190
233
|
| `providers` | `BasemapProvider[]` | `[]` | Custom provider labels |
|
|
191
234
|
| `includeDefaultBasemaps` | `boolean` | `true` | Include the built-in public catalog |
|
|
@@ -249,6 +292,9 @@ const control = new BasemapControl({
|
|
|
249
292
|
- `setMapTilerApiKey(apiKey)` - Set or update the MapTiler API key used by MapTiler styles
|
|
250
293
|
- `setAmazonCredentials(apiKey, awsRegion)` - Set or update Amazon Location credentials
|
|
251
294
|
- `setMapboxAccessToken(accessToken)` - Set or update the Mapbox access token
|
|
295
|
+
- `setTomTomApiKey(apiKey)` - Set or update the TomTom API key used by TomTom Traffic overlays
|
|
296
|
+
- `setHereApiKey(apiKey)` - Set or update the HERE API key used by the HERE Traffic overlay
|
|
297
|
+
- `setGoogleMapsApiKey(apiKey)` - Set or update the Google Maps API key used by the Google Traffic overlay
|
|
252
298
|
- `getActiveBasemap()` - Return the most recently selected basemap definition
|
|
253
299
|
- `getActiveBasemaps()` - Return all currently active basemap definitions
|
|
254
300
|
- `getBasemaps()` - Return the catalog
|
|
@@ -7,15 +7,18 @@ const DEFAULT_BASEMAP_PROVIDERS = [
|
|
|
7
7
|
{ id: "cyclosm", name: "CyclOSM", category: "Cycling" },
|
|
8
8
|
{ id: "esri", name: "ESRI", category: "Imagery" },
|
|
9
9
|
{ id: "google", name: "Google", category: "General" },
|
|
10
|
+
{ id: "here", name: "HERE", category: "Traffic" },
|
|
10
11
|
{ id: "mapbox", name: "Mapbox", category: "General" },
|
|
11
12
|
{ id: "maptiler", name: "MapTiler", category: "General" },
|
|
12
13
|
{ id: "nasa-gibs", name: "NASA GIBS", category: "Imagery" },
|
|
13
14
|
{ id: "nlmaps", name: "nlmaps", category: "Regional" },
|
|
15
|
+
{ id: "openbasiskaart", name: "Openbasiskaart", category: "Regional" },
|
|
14
16
|
{ id: "openfreemap", name: "OpenFreeMap", category: "Vector Styles" },
|
|
15
17
|
{ id: "openrailwaymap", name: "OpenRailwayMap", category: "Transport" },
|
|
16
18
|
{ id: "openstreetmap", name: "OpenStreetMap", category: "Community" },
|
|
17
19
|
{ id: "opentopomap", name: "OpenTopoMap", category: "Terrain" },
|
|
18
20
|
{ id: "swisstopo", name: "Swiss Federal Geoportal", category: "Regional" },
|
|
21
|
+
{ id: "tomtom", name: "TomTom", category: "Traffic" },
|
|
19
22
|
{ id: "topplusopen", name: "TopPlusOpen", category: "Regional" },
|
|
20
23
|
{ id: "usgs", name: "USGS", category: "United States" },
|
|
21
24
|
{ id: "waymarkedtrails", name: "Waymarked Trails", category: "Outdoor" }
|
|
@@ -143,6 +146,44 @@ const MAPBOX_ATTRIBUTION = "© Mapbox © OpenStreetMap contributors";
|
|
|
143
146
|
const MAPTILER_ATTRIBUTION = "© MapTiler © OpenStreetMap contributors";
|
|
144
147
|
const OSM_ATTRIBUTION = "© OpenStreetMap contributors";
|
|
145
148
|
const OPENFREEMAP_ATTRIBUTION = "OpenFreeMap © OpenMapTiles Data from OpenStreetMap";
|
|
149
|
+
const TOMTOM_ATTRIBUTION = "© TomTom";
|
|
150
|
+
const HERE_ATTRIBUTION = "© HERE";
|
|
151
|
+
const GOOGLE_ATTRIBUTION = "© Google";
|
|
152
|
+
const MAPBOX_TRAFFIC_CONGESTION_COLORS = [
|
|
153
|
+
"match",
|
|
154
|
+
["get", "congestion"],
|
|
155
|
+
"low",
|
|
156
|
+
"#4caf50",
|
|
157
|
+
"moderate",
|
|
158
|
+
"#ffc107",
|
|
159
|
+
"heavy",
|
|
160
|
+
"#ff5722",
|
|
161
|
+
"severe",
|
|
162
|
+
"#b71c1c",
|
|
163
|
+
"#9e9e9e"
|
|
164
|
+
];
|
|
165
|
+
function rasterTrafficBasemap({
|
|
166
|
+
id,
|
|
167
|
+
name,
|
|
168
|
+
provider,
|
|
169
|
+
description,
|
|
170
|
+
attribution,
|
|
171
|
+
tiles,
|
|
172
|
+
maxzoom = 22,
|
|
173
|
+
tags = []
|
|
174
|
+
}) {
|
|
175
|
+
return rasterBasemap({
|
|
176
|
+
id,
|
|
177
|
+
name,
|
|
178
|
+
provider,
|
|
179
|
+
category: "Traffic",
|
|
180
|
+
description,
|
|
181
|
+
attribution,
|
|
182
|
+
tiles,
|
|
183
|
+
maxzoom,
|
|
184
|
+
tags: ["traffic", "overlay", ...tags]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
146
187
|
const DEFAULT_BASEMAPS = [
|
|
147
188
|
rasterBasemap({
|
|
148
189
|
id: "osm-standard",
|
|
@@ -959,6 +1000,21 @@ const DEFAULT_BASEMAPS = [
|
|
|
959
1000
|
],
|
|
960
1001
|
tags: ["netherlands", "imagery", "aerial"]
|
|
961
1002
|
}),
|
|
1003
|
+
rasterBasemap({
|
|
1004
|
+
id: "openbasiskaart",
|
|
1005
|
+
name: "Openbasiskaart",
|
|
1006
|
+
provider: "openbasiskaart",
|
|
1007
|
+
category: "Regional",
|
|
1008
|
+
description: "Netherlands topographic background map based on OpenStreetMap data.",
|
|
1009
|
+
attribution: "Map data © OpenStreetMap contributors | Openbasiskaart",
|
|
1010
|
+
// The Google Maps Compatible (osm-g) layer uses the standard Web Mercator
|
|
1011
|
+
// grid, so its WMTS {TileMatrix}/{TileRow}/{TileCol} maps directly to xyz.
|
|
1012
|
+
tiles: [
|
|
1013
|
+
"https://www.openbasiskaart.nl/mapcache/wmts/1.0.0/osm-g/default/g/{z}/{y}/{x}.png"
|
|
1014
|
+
],
|
|
1015
|
+
maxzoom: 18,
|
|
1016
|
+
tags: ["netherlands", "regional", "topographic", "osm", "openbasiskaart"]
|
|
1017
|
+
}),
|
|
962
1018
|
{
|
|
963
1019
|
id: "openfreemap-positron",
|
|
964
1020
|
name: "OpenFreeMap Positron",
|
|
@@ -1048,6 +1104,89 @@ const DEFAULT_BASEMAPS = [
|
|
|
1048
1104
|
pitch: 60
|
|
1049
1105
|
},
|
|
1050
1106
|
tags: ["openfreemap", "vector", "style", "3d", "liberty"]
|
|
1107
|
+
},
|
|
1108
|
+
rasterTrafficBasemap({
|
|
1109
|
+
id: "tomtom-traffic-flow-relative",
|
|
1110
|
+
name: "TomTom Traffic Flow",
|
|
1111
|
+
provider: "tomtom",
|
|
1112
|
+
description: "TomTom traffic speeds relative to free-flow speed. Requires a TomTom API key.",
|
|
1113
|
+
attribution: TOMTOM_ATTRIBUTION,
|
|
1114
|
+
tiles: ["https://api.tomtom.com/traffic/map/4/tile/flow/relative0/{z}/{x}/{y}.png?key={api-key}"],
|
|
1115
|
+
tags: ["tomtom", "flow", "relative"]
|
|
1116
|
+
}),
|
|
1117
|
+
rasterTrafficBasemap({
|
|
1118
|
+
id: "tomtom-traffic-flow-absolute",
|
|
1119
|
+
name: "TomTom Traffic Flow Absolute",
|
|
1120
|
+
provider: "tomtom",
|
|
1121
|
+
description: "TomTom traffic colored by absolute speed. Requires a TomTom API key.",
|
|
1122
|
+
attribution: TOMTOM_ATTRIBUTION,
|
|
1123
|
+
tiles: ["https://api.tomtom.com/traffic/map/4/tile/flow/absolute/{z}/{x}/{y}.png?key={api-key}"],
|
|
1124
|
+
tags: ["tomtom", "flow", "absolute"]
|
|
1125
|
+
}),
|
|
1126
|
+
rasterTrafficBasemap({
|
|
1127
|
+
id: "tomtom-traffic-flow-relative-delay",
|
|
1128
|
+
name: "TomTom Traffic Flow Delay",
|
|
1129
|
+
provider: "tomtom",
|
|
1130
|
+
description: "TomTom traffic highlighting only roads delayed below free-flow speed. Requires a TomTom API key.",
|
|
1131
|
+
attribution: TOMTOM_ATTRIBUTION,
|
|
1132
|
+
tiles: [
|
|
1133
|
+
"https://api.tomtom.com/traffic/map/4/tile/flow/relative-delay/{z}/{x}/{y}.png?key={api-key}"
|
|
1134
|
+
],
|
|
1135
|
+
tags: ["tomtom", "flow", "delay"]
|
|
1136
|
+
}),
|
|
1137
|
+
rasterTrafficBasemap({
|
|
1138
|
+
id: "here-traffic-flow",
|
|
1139
|
+
name: "HERE Traffic Flow",
|
|
1140
|
+
provider: "here",
|
|
1141
|
+
description: "HERE real-time traffic flow overlay. Requires a HERE API key.",
|
|
1142
|
+
attribution: HERE_ATTRIBUTION,
|
|
1143
|
+
tiles: ["https://traffic.maps.hereapi.com/v3/flow/mc/{z}/{x}/{y}/png?apiKey={api-key}"],
|
|
1144
|
+
tags: ["here", "flow"]
|
|
1145
|
+
}),
|
|
1146
|
+
{
|
|
1147
|
+
id: "mapbox-traffic",
|
|
1148
|
+
name: "Mapbox Traffic",
|
|
1149
|
+
provider: "mapbox",
|
|
1150
|
+
type: "vector-overlay",
|
|
1151
|
+
category: "Traffic",
|
|
1152
|
+
description: "Mapbox real-time traffic congestion overlay. Requires a Mapbox access token.",
|
|
1153
|
+
attribution: MAPBOX_ATTRIBUTION,
|
|
1154
|
+
source: {
|
|
1155
|
+
type: "vector-overlay",
|
|
1156
|
+
url: "mapbox://mapbox.mapbox-traffic-v1",
|
|
1157
|
+
sourceLayer: "traffic",
|
|
1158
|
+
layerType: "line",
|
|
1159
|
+
layout: {
|
|
1160
|
+
"line-join": "round",
|
|
1161
|
+
"line-cap": "round"
|
|
1162
|
+
},
|
|
1163
|
+
paint: {
|
|
1164
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 6, 1.5, 14, 4, 18, 8],
|
|
1165
|
+
"line-color": MAPBOX_TRAFFIC_CONGESTION_COLORS
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
tags: ["mapbox", "traffic", "overlay", "congestion"]
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
id: "google-traffic",
|
|
1172
|
+
name: "Google Traffic",
|
|
1173
|
+
provider: "google",
|
|
1174
|
+
type: "raster",
|
|
1175
|
+
category: "Traffic",
|
|
1176
|
+
description: "Google real-time traffic overlay via the Map Tiles API. Requires a Google Maps API key.",
|
|
1177
|
+
attribution: GOOGLE_ATTRIBUTION,
|
|
1178
|
+
source: {
|
|
1179
|
+
type: "raster",
|
|
1180
|
+
tiles: ["https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?session={session}&key={api-key}"],
|
|
1181
|
+
tileSize: 256,
|
|
1182
|
+
maxzoom: 22,
|
|
1183
|
+
googleSession: {
|
|
1184
|
+
mapType: "roadmap",
|
|
1185
|
+
layerTypes: ["layerTraffic"],
|
|
1186
|
+
overlay: true
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
tags: ["google", "traffic", "overlay"]
|
|
1051
1190
|
}
|
|
1052
1191
|
];
|
|
1053
1192
|
function combineProviders(defaults, custom = []) {
|
|
@@ -1125,6 +1264,18 @@ const PROVIDER_CREDENTIAL_HELP = {
|
|
|
1125
1264
|
mapbox: {
|
|
1126
1265
|
url: "https://docs.mapbox.com/help/getting-started/access-tokens/",
|
|
1127
1266
|
label: "Get a Mapbox access token"
|
|
1267
|
+
},
|
|
1268
|
+
tomtom: {
|
|
1269
|
+
url: "https://developer.tomtom.com/how-to-get-tomtom-api-key",
|
|
1270
|
+
label: "Get a TomTom API key"
|
|
1271
|
+
},
|
|
1272
|
+
here: {
|
|
1273
|
+
url: "https://www.here.com/get-started/pricing",
|
|
1274
|
+
label: "Get a HERE API key"
|
|
1275
|
+
},
|
|
1276
|
+
google: {
|
|
1277
|
+
url: "https://developers.google.com/maps/documentation/tile/get-api-key",
|
|
1278
|
+
label: "Get a Google Maps API key"
|
|
1128
1279
|
}
|
|
1129
1280
|
};
|
|
1130
1281
|
const MIN_PANEL_WIDTH = 240;
|
|
@@ -1158,6 +1309,13 @@ class BasemapControl {
|
|
|
1158
1309
|
__publicField(this, "_amazonApiKey", "");
|
|
1159
1310
|
__publicField(this, "_awsRegion", "");
|
|
1160
1311
|
__publicField(this, "_mapboxAccessToken", "");
|
|
1312
|
+
__publicField(this, "_tomtomApiKey", "");
|
|
1313
|
+
__publicField(this, "_hereApiKey", "");
|
|
1314
|
+
__publicField(this, "_googleMapsApiKey", "");
|
|
1315
|
+
// Cached Google Map Tiles API session tokens keyed by their session config, so
|
|
1316
|
+
// a traffic overlay reuses a token until it expires instead of creating a new
|
|
1317
|
+
// session on every add. Keyed by the serialized config + API key.
|
|
1318
|
+
__publicField(this, "_googleSessions", new globalThis.Map());
|
|
1161
1319
|
__publicField(this, "_providerSettingsCollapsed", true);
|
|
1162
1320
|
// Provider of the most recent missing-credential error, used to pick the
|
|
1163
1321
|
// matching help link. Kept in sync with `_state.error` (both are set together
|
|
@@ -1197,6 +1355,9 @@ class BasemapControl {
|
|
|
1197
1355
|
this._amazonApiKey = (options == null ? void 0 : options.amazonApiKey) ?? "";
|
|
1198
1356
|
this._awsRegion = (options == null ? void 0 : options.awsRegion) ?? "us-east-1";
|
|
1199
1357
|
this._mapboxAccessToken = (options == null ? void 0 : options.mapboxAccessToken) ?? "";
|
|
1358
|
+
this._tomtomApiKey = (options == null ? void 0 : options.tomtomApiKey) ?? "";
|
|
1359
|
+
this._hereApiKey = (options == null ? void 0 : options.hereApiKey) ?? "";
|
|
1360
|
+
this._googleMapsApiKey = (options == null ? void 0 : options.googleMapsApiKey) ?? "";
|
|
1200
1361
|
this._basemaps = createBasemapCatalog(
|
|
1201
1362
|
options == null ? void 0 : options.basemaps,
|
|
1202
1363
|
this._options.includeDefaultBasemaps
|
|
@@ -1324,6 +1485,25 @@ class BasemapControl {
|
|
|
1324
1485
|
this._renderContent();
|
|
1325
1486
|
this._emit({ type: "statechange", state: this.getState() });
|
|
1326
1487
|
}
|
|
1488
|
+
setTomTomApiKey(apiKey) {
|
|
1489
|
+
this._tomtomApiKey = apiKey;
|
|
1490
|
+
this._state = { ...this._state, error: void 0 };
|
|
1491
|
+
this._renderContent();
|
|
1492
|
+
this._emit({ type: "statechange", state: this.getState() });
|
|
1493
|
+
}
|
|
1494
|
+
setHereApiKey(apiKey) {
|
|
1495
|
+
this._hereApiKey = apiKey;
|
|
1496
|
+
this._state = { ...this._state, error: void 0 };
|
|
1497
|
+
this._renderContent();
|
|
1498
|
+
this._emit({ type: "statechange", state: this.getState() });
|
|
1499
|
+
}
|
|
1500
|
+
setGoogleMapsApiKey(apiKey) {
|
|
1501
|
+
this._googleMapsApiKey = apiKey;
|
|
1502
|
+
this._googleSessions.clear();
|
|
1503
|
+
this._state = { ...this._state, error: void 0 };
|
|
1504
|
+
this._renderContent();
|
|
1505
|
+
this._emit({ type: "statechange", state: this.getState() });
|
|
1506
|
+
}
|
|
1327
1507
|
getActiveBasemap() {
|
|
1328
1508
|
return this._basemaps.find((basemap) => basemap.id === this._state.activeBasemapId);
|
|
1329
1509
|
}
|
|
@@ -1386,9 +1566,9 @@ class BasemapControl {
|
|
|
1386
1566
|
this._handleError(error, basemap);
|
|
1387
1567
|
throw error;
|
|
1388
1568
|
}
|
|
1389
|
-
const
|
|
1390
|
-
const effectiveMode =
|
|
1391
|
-
if (!
|
|
1569
|
+
const isOverlay = basemap.source.type === "raster" || basemap.source.type === "vector-overlay";
|
|
1570
|
+
const effectiveMode = isOverlay ? mode : "replace";
|
|
1571
|
+
if (!isOverlay && this._state.allowMultiple && this._managedRasters.size > 0 && this._options.confirmStyleReplace) {
|
|
1392
1572
|
const replacedBasemapIds = [...this._managedRasters.keys()];
|
|
1393
1573
|
let confirmed = false;
|
|
1394
1574
|
try {
|
|
@@ -1403,14 +1583,14 @@ class BasemapControl {
|
|
|
1403
1583
|
this._emit({ type: "statechange", state: this.getState() });
|
|
1404
1584
|
try {
|
|
1405
1585
|
let managedRaster;
|
|
1406
|
-
if (
|
|
1586
|
+
if (isOverlay) {
|
|
1407
1587
|
await this._waitForStyleReady();
|
|
1408
1588
|
if (effectiveMode === "replace") {
|
|
1409
1589
|
this._removeManagedBasemap();
|
|
1410
1590
|
} else {
|
|
1411
1591
|
this._removeManagedRaster(basemap.id);
|
|
1412
1592
|
}
|
|
1413
|
-
managedRaster = this.
|
|
1593
|
+
managedRaster = await this._addOverlay(basemap);
|
|
1414
1594
|
} else {
|
|
1415
1595
|
const styleUrl = this._resolveStyleUrl(basemap);
|
|
1416
1596
|
const styleOptions = this._getStyleOptions(basemap);
|
|
@@ -1457,8 +1637,8 @@ class BasemapControl {
|
|
|
1457
1637
|
// In multiple mode a raster basemap click toggles the overlay on or off.
|
|
1458
1638
|
_selectBasemap(id) {
|
|
1459
1639
|
const basemap = this._basemaps.find((candidate) => candidate.id === id);
|
|
1460
|
-
const
|
|
1461
|
-
if (this._state.allowMultiple &&
|
|
1640
|
+
const isOverlay = (basemap == null ? void 0 : basemap.source.type) === "raster" || (basemap == null ? void 0 : basemap.source.type) === "vector-overlay";
|
|
1641
|
+
if (this._state.allowMultiple && isOverlay) {
|
|
1462
1642
|
this.toggleBasemap(id).catch(() => {
|
|
1463
1643
|
});
|
|
1464
1644
|
return;
|
|
@@ -1706,6 +1886,49 @@ class BasemapControl {
|
|
|
1706
1886
|
})
|
|
1707
1887
|
);
|
|
1708
1888
|
}
|
|
1889
|
+
if (this._hasTomTomBasemaps()) {
|
|
1890
|
+
fields.appendChild(
|
|
1891
|
+
this._createProviderSettingsInput({
|
|
1892
|
+
className: "basemap-control-tomtom-key",
|
|
1893
|
+
type: "password",
|
|
1894
|
+
placeholder: "TomTom API key",
|
|
1895
|
+
ariaLabel: "TomTom API key",
|
|
1896
|
+
value: this._tomtomApiKey,
|
|
1897
|
+
onInput: (value) => {
|
|
1898
|
+
this._tomtomApiKey = value;
|
|
1899
|
+
}
|
|
1900
|
+
})
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (this._hasHereBasemaps()) {
|
|
1904
|
+
fields.appendChild(
|
|
1905
|
+
this._createProviderSettingsInput({
|
|
1906
|
+
className: "basemap-control-here-key",
|
|
1907
|
+
type: "password",
|
|
1908
|
+
placeholder: "HERE API key",
|
|
1909
|
+
ariaLabel: "HERE API key",
|
|
1910
|
+
value: this._hereApiKey,
|
|
1911
|
+
onInput: (value) => {
|
|
1912
|
+
this._hereApiKey = value;
|
|
1913
|
+
}
|
|
1914
|
+
})
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
if (this._hasGoogleApiKeyBasemaps()) {
|
|
1918
|
+
fields.appendChild(
|
|
1919
|
+
this._createProviderSettingsInput({
|
|
1920
|
+
className: "basemap-control-google-key",
|
|
1921
|
+
type: "password",
|
|
1922
|
+
placeholder: "Google Maps API key",
|
|
1923
|
+
ariaLabel: "Google Maps API key",
|
|
1924
|
+
value: this._googleMapsApiKey,
|
|
1925
|
+
onInput: (value) => {
|
|
1926
|
+
this._googleMapsApiKey = value;
|
|
1927
|
+
this._googleSessions.clear();
|
|
1928
|
+
}
|
|
1929
|
+
})
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1709
1932
|
if (this._hasAmazonBasemaps()) {
|
|
1710
1933
|
fields.appendChild(
|
|
1711
1934
|
this._createProviderSettingsInput({
|
|
@@ -1885,7 +2108,7 @@ class BasemapControl {
|
|
|
1885
2108
|
main.appendChild(meta);
|
|
1886
2109
|
const type = document.createElement("span");
|
|
1887
2110
|
type.className = "basemap-control-result-type";
|
|
1888
|
-
type.textContent = basemap.type === "raster" ? "Raster" : "Style";
|
|
2111
|
+
type.textContent = basemap.type === "raster" ? "Raster" : basemap.type === "vector-overlay" ? "Overlay" : "Style";
|
|
1889
2112
|
row.appendChild(main);
|
|
1890
2113
|
row.appendChild(type);
|
|
1891
2114
|
if (basemap.attribution) {
|
|
@@ -1916,11 +2139,25 @@ class BasemapControl {
|
|
|
1916
2139
|
_hasMapboxBasemaps() {
|
|
1917
2140
|
return this._basemaps.some((basemap) => basemap.provider === "mapbox");
|
|
1918
2141
|
}
|
|
2142
|
+
_hasTomTomBasemaps() {
|
|
2143
|
+
return this._basemaps.some((basemap) => basemap.provider === "tomtom");
|
|
2144
|
+
}
|
|
2145
|
+
_hasHereBasemaps() {
|
|
2146
|
+
return this._basemaps.some((basemap) => basemap.provider === "here");
|
|
2147
|
+
}
|
|
2148
|
+
// True when a Google basemap needs a Map Tiles API key. The existing Google
|
|
2149
|
+
// raster basemaps (Maps/Satellite/etc.) use keyless tiles, so only session or
|
|
2150
|
+
// api-key based Google layers (e.g. Google Traffic) require the key input.
|
|
2151
|
+
_hasGoogleApiKeyBasemaps() {
|
|
2152
|
+
return this._basemaps.some(
|
|
2153
|
+
(basemap) => basemap.provider === "google" && basemap.source.type === "raster" && (Boolean(basemap.source.googleSession) || basemap.source.tiles.some((tile) => tile.includes(API_KEY_PLACEHOLDER)))
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
1919
2156
|
_hasProviderSettings() {
|
|
1920
|
-
return this._hasMapTilerBasemaps() || this._hasAmazonBasemaps() || this._hasMapboxBasemaps();
|
|
2157
|
+
return this._hasMapTilerBasemaps() || this._hasAmazonBasemaps() || this._hasMapboxBasemaps() || this._hasTomTomBasemaps() || this._hasHereBasemaps() || this._hasGoogleApiKeyBasemaps();
|
|
1921
2158
|
}
|
|
1922
2159
|
_resolveStyleUrl(basemap) {
|
|
1923
|
-
if (basemap.source.type
|
|
2160
|
+
if (basemap.source.type !== "style" && basemap.source.type !== "vector-style") {
|
|
1924
2161
|
throw new Error(`Basemap "${basemap.id}" is not a style basemap.`);
|
|
1925
2162
|
}
|
|
1926
2163
|
const url = basemap.source.url;
|
|
@@ -2048,14 +2285,23 @@ class BasemapControl {
|
|
|
2048
2285
|
_isUrlLikeCredential(value) {
|
|
2049
2286
|
return /^https?:\/\//i.test(value);
|
|
2050
2287
|
}
|
|
2051
|
-
|
|
2288
|
+
// Add a stackable overlay (raster tiles or a vector overlay) to the map,
|
|
2289
|
+
// resolving any provider credentials first.
|
|
2290
|
+
async _addOverlay(basemap) {
|
|
2291
|
+
if (basemap.source.type === "vector-overlay") {
|
|
2292
|
+
return this._addVectorOverlay(basemap, basemap.source);
|
|
2293
|
+
}
|
|
2294
|
+
return this._addRasterBasemap(basemap);
|
|
2295
|
+
}
|
|
2296
|
+
async _addRasterBasemap(basemap) {
|
|
2052
2297
|
if (!this._map || basemap.source.type !== "raster") return void 0;
|
|
2298
|
+
const tiles = await this._resolveRasterTiles(basemap);
|
|
2053
2299
|
const sourceId = `${CONTROL_SOURCE_PREFIX}-${basemap.id}`;
|
|
2054
2300
|
const layerId = [CONTROL_LAYER_PREFIX, basemap.id].filter(Boolean).join("-");
|
|
2055
2301
|
const beforeId = this._getBasemapInsertBeforeId();
|
|
2056
2302
|
const source = {
|
|
2057
2303
|
type: "raster",
|
|
2058
|
-
tiles
|
|
2304
|
+
tiles,
|
|
2059
2305
|
tileSize: basemap.source.tileSize ?? 256,
|
|
2060
2306
|
minzoom: basemap.source.minzoom,
|
|
2061
2307
|
maxzoom: basemap.source.maxzoom,
|
|
@@ -2073,6 +2319,158 @@ class BasemapControl {
|
|
|
2073
2319
|
this._managedRasters.set(basemap.id, managed);
|
|
2074
2320
|
return managed;
|
|
2075
2321
|
}
|
|
2322
|
+
_addVectorOverlay(basemap, overlay) {
|
|
2323
|
+
if (!this._map) return void 0;
|
|
2324
|
+
const sourceId = `${CONTROL_SOURCE_PREFIX}-${basemap.id}`;
|
|
2325
|
+
const layerId = [CONTROL_LAYER_PREFIX, basemap.id].filter(Boolean).join("-");
|
|
2326
|
+
const beforeId = this._getBasemapInsertBeforeId();
|
|
2327
|
+
const source = {
|
|
2328
|
+
type: "vector",
|
|
2329
|
+
...this._resolveVectorOverlaySource(basemap, overlay),
|
|
2330
|
+
minzoom: overlay.minzoom,
|
|
2331
|
+
maxzoom: overlay.maxzoom,
|
|
2332
|
+
attribution: basemap.attribution
|
|
2333
|
+
};
|
|
2334
|
+
const layer = {
|
|
2335
|
+
id: layerId,
|
|
2336
|
+
type: overlay.layerType ?? "line",
|
|
2337
|
+
source: sourceId,
|
|
2338
|
+
"source-layer": overlay.sourceLayer,
|
|
2339
|
+
...overlay.layout ? { layout: overlay.layout } : {},
|
|
2340
|
+
...overlay.paint ? { paint: overlay.paint } : {}
|
|
2341
|
+
};
|
|
2342
|
+
this._map.addSource(sourceId, source);
|
|
2343
|
+
this._map.addLayer(layer, beforeId);
|
|
2344
|
+
const managed = { sourceId, layerId, beforeId };
|
|
2345
|
+
this._managedRasters.set(basemap.id, managed);
|
|
2346
|
+
return managed;
|
|
2347
|
+
}
|
|
2348
|
+
// Resolves the `{url}` or `{tiles}` of a vector overlay, substituting provider
|
|
2349
|
+
// credentials. Mapbox vector overlays are pointed at the v4 TileJSON endpoint
|
|
2350
|
+
// with the access token, reusing the same resolver as Mapbox style sources.
|
|
2351
|
+
_resolveVectorOverlaySource(basemap, overlay) {
|
|
2352
|
+
if (basemap.provider === "mapbox") {
|
|
2353
|
+
const accessToken = this._mapboxAccessToken.trim();
|
|
2354
|
+
if (!accessToken) {
|
|
2355
|
+
throw new MissingCredentialError(
|
|
2356
|
+
"Enter a Mapbox access token before applying this overlay.",
|
|
2357
|
+
"mapbox"
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
if (this._isUrlLikeCredential(accessToken)) {
|
|
2361
|
+
throw new MissingCredentialError(
|
|
2362
|
+
"Enter a valid Mapbox access token, not a URL.",
|
|
2363
|
+
"mapbox"
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
const encoded = encodeURIComponent(accessToken);
|
|
2367
|
+
if (overlay.url) {
|
|
2368
|
+
return { url: this._resolveMapboxInternalUrl(overlay.url, encoded) };
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (overlay.url) return { url: overlay.url };
|
|
2372
|
+
if (overlay.tiles) return { tiles: overlay.tiles };
|
|
2373
|
+
throw new Error(`Vector overlay "${basemap.id}" has no url or tiles.`);
|
|
2374
|
+
}
|
|
2375
|
+
// Resolves the tile templates of a raster basemap, substituting provider
|
|
2376
|
+
// credentials (and, for Google, a Map Tiles API session token). Throws a
|
|
2377
|
+
// MissingCredentialError when a required key is absent so the panel can
|
|
2378
|
+
// surface a "Get a ..." link and reveal the credential inputs.
|
|
2379
|
+
async _resolveRasterTiles(basemap) {
|
|
2380
|
+
if (basemap.source.type !== "raster") return [];
|
|
2381
|
+
const { tiles, googleSession } = basemap.source;
|
|
2382
|
+
if (googleSession) {
|
|
2383
|
+
return this._resolveGoogleSessionTiles(tiles, googleSession);
|
|
2384
|
+
}
|
|
2385
|
+
if (!tiles.some((tile) => tile.includes(API_KEY_PLACEHOLDER))) {
|
|
2386
|
+
return tiles;
|
|
2387
|
+
}
|
|
2388
|
+
const apiKey = this._rasterApiKeyFor(basemap.provider);
|
|
2389
|
+
if (!apiKey) {
|
|
2390
|
+
throw this._missingRasterKeyError(basemap.provider);
|
|
2391
|
+
}
|
|
2392
|
+
const encoded = encodeURIComponent(apiKey);
|
|
2393
|
+
return tiles.map((tile) => tile.split(API_KEY_PLACEHOLDER).join(encoded));
|
|
2394
|
+
}
|
|
2395
|
+
_rasterApiKeyFor(provider) {
|
|
2396
|
+
if (provider === "tomtom") return this._tomtomApiKey.trim();
|
|
2397
|
+
if (provider === "here") return this._hereApiKey.trim();
|
|
2398
|
+
if (provider === "google") return this._googleMapsApiKey.trim();
|
|
2399
|
+
return "";
|
|
2400
|
+
}
|
|
2401
|
+
_missingRasterKeyError(provider) {
|
|
2402
|
+
const label = provider === "tomtom" ? "TomTom API key" : provider === "here" ? "HERE API key" : provider === "google" ? "Google Maps API key" : "API key";
|
|
2403
|
+
const helpProvider = provider in PROVIDER_CREDENTIAL_HELP ? provider : "maptiler";
|
|
2404
|
+
return new MissingCredentialError(`Enter a ${label} before applying this layer.`, helpProvider);
|
|
2405
|
+
}
|
|
2406
|
+
// Resolves Google tile templates by minting (or reusing) a Map Tiles API
|
|
2407
|
+
// session token, then substituting both `{session}` and `{api-key}`.
|
|
2408
|
+
async _resolveGoogleSessionTiles(tiles, config) {
|
|
2409
|
+
const apiKey = this._googleMapsApiKey.trim();
|
|
2410
|
+
if (!apiKey) {
|
|
2411
|
+
throw new MissingCredentialError(
|
|
2412
|
+
"Enter a Google Maps API key before applying this layer.",
|
|
2413
|
+
"google"
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
const token = await this._getGoogleSessionToken(apiKey, config);
|
|
2417
|
+
const encodedKey = encodeURIComponent(apiKey);
|
|
2418
|
+
const encodedSession = encodeURIComponent(token);
|
|
2419
|
+
return tiles.map(
|
|
2420
|
+
(tile) => tile.split("{session}").join(encodedSession).split(API_KEY_PLACEHOLDER).join(encodedKey)
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
async _getGoogleSessionToken(apiKey, config) {
|
|
2424
|
+
const cacheKey = `${apiKey}:${JSON.stringify(config)}`;
|
|
2425
|
+
const cached = this._googleSessions.get(cacheKey);
|
|
2426
|
+
if (cached && cached.expiry - 6e4 > Date.now()) {
|
|
2427
|
+
return cached.token;
|
|
2428
|
+
}
|
|
2429
|
+
const response = await fetch(
|
|
2430
|
+
`https://tile.googleapis.com/v1/createSession?key=${encodeURIComponent(apiKey)}`,
|
|
2431
|
+
{
|
|
2432
|
+
method: "POST",
|
|
2433
|
+
headers: { "Content-Type": "application/json" },
|
|
2434
|
+
body: JSON.stringify({
|
|
2435
|
+
mapType: config.mapType,
|
|
2436
|
+
language: config.language ?? "en-US",
|
|
2437
|
+
region: config.region ?? "US",
|
|
2438
|
+
layerTypes: config.layerTypes,
|
|
2439
|
+
overlay: config.overlay
|
|
2440
|
+
})
|
|
2441
|
+
}
|
|
2442
|
+
);
|
|
2443
|
+
if (!response.ok) {
|
|
2444
|
+
const detail = await response.text().catch(() => "");
|
|
2445
|
+
throw new MissingCredentialError(
|
|
2446
|
+
`Google could not create a tile session (HTTP ${response.status}). ${this._summarizeGoogleError(
|
|
2447
|
+
detail
|
|
2448
|
+
)}`.trim(),
|
|
2449
|
+
"google"
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
const data = await response.json();
|
|
2453
|
+
if (!data.session) {
|
|
2454
|
+
throw new MissingCredentialError(
|
|
2455
|
+
"Google did not return a tile session token. Check that the Map Tiles API is enabled for your key.",
|
|
2456
|
+
"google"
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
const expirySeconds = data.expiry ? Number(data.expiry) : NaN;
|
|
2460
|
+
const expiry = Number.isFinite(expirySeconds) ? expirySeconds * 1e3 : Date.now() + 60 * 60 * 1e3;
|
|
2461
|
+
this._googleSessions.set(cacheKey, { token: data.session, expiry });
|
|
2462
|
+
return data.session;
|
|
2463
|
+
}
|
|
2464
|
+
_summarizeGoogleError(detail) {
|
|
2465
|
+
var _a;
|
|
2466
|
+
if (!detail) return "";
|
|
2467
|
+
try {
|
|
2468
|
+
const parsed = JSON.parse(detail);
|
|
2469
|
+
return ((_a = parsed.error) == null ? void 0 : _a.message) ?? "";
|
|
2470
|
+
} catch {
|
|
2471
|
+
return detail.slice(0, 200);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2076
2474
|
_waitForStyleReady() {
|
|
2077
2475
|
if (!this._map) return Promise.resolve();
|
|
2078
2476
|
if (typeof this._map.isStyleLoaded !== "function" || this._map.isStyleLoaded()) {
|
|
@@ -2237,4 +2635,4 @@ export {
|
|
|
2237
2635
|
getBasemapCategories as g,
|
|
2238
2636
|
resolveBasemapProviders as r
|
|
2239
2637
|
};
|
|
2240
|
-
//# sourceMappingURL=BasemapControl-
|
|
2638
|
+
//# sourceMappingURL=BasemapControl-CfFepxWc.js.map
|