@maptiler/sdk 1.0.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/.eslintignore +1 -0
- package/.eslintrc.cjs +10 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/LICENSE +29 -0
- package/demos/maptiler-sdk.css +147 -0
- package/demos/maptiler-sdk.umd.js +3287 -0
- package/demos/simple.html +63 -0
- package/dist/maptiler-sdk.css +147 -0
- package/dist/maptiler-sdk.d.ts +531 -0
- package/dist/maptiler-sdk.min.mjs +1 -0
- package/dist/maptiler-sdk.mjs +1128 -0
- package/dist/maptiler-sdk.mjs.map +1 -0
- package/dist/maptiler-sdk.umd.js +3287 -0
- package/dist/maptiler-sdk.umd.js.map +1 -0
- package/dist/maptiler-sdk.umd.min.js +579 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/custom.css +118 -0
- package/docs/assets/highlight.css +134 -0
- package/docs/assets/main.js +54 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1257 -0
- package/docs/classes/Map.html +273 -0
- package/docs/classes/Point.html +549 -0
- package/docs/classes/SdkConfig.html +188 -0
- package/docs/demos/maptiler-sdk.css +147 -0
- package/docs/demos/maptiler-sdk.umd.js +3287 -0
- package/docs/demos/simple.html +63 -0
- package/docs/functions/addProtocol.html +146 -0
- package/docs/functions/clearPrewarmedResources.html +92 -0
- package/docs/functions/clearStorage.html +124 -0
- package/docs/functions/getRTLTextPluginStatus.html +92 -0
- package/docs/functions/prewarm.html +92 -0
- package/docs/functions/removeProtocol.html +106 -0
- package/docs/functions/setRTLTextPlugin.html +112 -0
- package/docs/functions/supported.html +97 -0
- package/docs/images/JS-logo.svg +4 -0
- package/docs/images/TS-logo.svg +6 -0
- package/docs/images/maptiler-logo.svg +19 -0
- package/docs/images/maptiler-sdk-logo.afdesign +0 -0
- package/docs/images/maptiler-sdk-logo.svg +66 -0
- package/docs/images/screenshots/alps.gif +0 -0
- package/docs/images/screenshots/grandcanyon.gif +0 -0
- package/docs/images/screenshots/lang-arabic.png +0 -0
- package/docs/images/screenshots/lang-hebrew.png +0 -0
- package/docs/images/screenshots/multilang.gif +0 -0
- package/docs/images/screenshots/static-bounded-europe-1024.png +0 -0
- package/docs/images/screenshots/static-bounded-europe-2048.png +0 -0
- package/docs/images/screenshots/static-bounded-portugal-1024x2048.png +0 -0
- package/docs/images/screenshots/static-bounded-portugal-2048x2048.png +0 -0
- package/docs/images/screenshots/static-with-path.png +0 -0
- package/docs/images/screenshots/style-basic-v2.png +0 -0
- package/docs/images/screenshots/style-bright.png +0 -0
- package/docs/images/screenshots/style-dataviz-dark.png +0 -0
- package/docs/images/screenshots/style-hybrid.png +0 -0
- package/docs/images/screenshots/style-osm.png +0 -0
- package/docs/images/screenshots/style-outdoor.png +0 -0
- package/docs/images/screenshots/style-pastel.png +0 -0
- package/docs/images/screenshots/style-satellite.png +0 -0
- package/docs/images/screenshots/style-streets-v2-dark.png +0 -0
- package/docs/images/screenshots/style-streets-v2-light.png +0 -0
- package/docs/images/screenshots/style-streets-v2.png +0 -0
- package/docs/images/screenshots/style-toner.png +0 -0
- package/docs/images/screenshots/style-topo.png +0 -0
- package/docs/images/screenshots/style-topographique.png +0 -0
- package/docs/images/screenshots/style-voyager.png +0 -0
- package/docs/images/screenshots/style-winter.png +0 -0
- package/docs/index.html +601 -0
- package/docs/modules.html +142 -0
- package/docs/types/LanguageKey.html +90 -0
- package/docs/types/LanguageString.html +90 -0
- package/docs/types/MapOptions.html +90 -0
- package/docs/types/Matrix2.html +90 -0
- package/docs/types/Unit.html +88 -0
- package/docs/variables/AJAXError.html +88 -0
- package/docs/variables/AttributionControl.html +88 -0
- package/docs/variables/CanvasSource.html +88 -0
- package/docs/variables/Evented.html +88 -0
- package/docs/variables/FullscreenControl.html +88 -0
- package/docs/variables/GeoJSONSource.html +88 -0
- package/docs/variables/GeolocateControl.html +88 -0
- package/docs/variables/GeolocationType.html +95 -0
- package/docs/variables/ImageSource.html +88 -0
- package/docs/variables/Language.html +249 -0
- package/docs/variables/LngLat.html +88 -0
- package/docs/variables/LngLatBounds.html +88 -0
- package/docs/variables/LogoControl.html +88 -0
- package/docs/variables/Marker.html +88 -0
- package/docs/variables/MercatorCoordinate.html +88 -0
- package/docs/variables/NavigationControl.html +88 -0
- package/docs/variables/Popup.html +88 -0
- package/docs/variables/RasterDEMTileSource.html +88 -0
- package/docs/variables/RasterTileSource.html +88 -0
- package/docs/variables/ScaleControl.html +88 -0
- package/docs/variables/Style.html +88 -0
- package/docs/variables/TerrainControl.html +88 -0
- package/docs/variables/VectorTileSource.html +88 -0
- package/docs/variables/VideoSource.html +88 -0
- package/docs/variables/config.html +88 -0
- package/docs/variables/maxParallelImageRequests.html +88 -0
- package/docs/variables/version.html +88 -0
- package/docs/variables/workerCount.html +88 -0
- package/docs/variables/workerUrl.html +88 -0
- package/docsmd/.nojekyll +1 -0
- package/docsmd/README.md +710 -0
- package/docsmd/assets/custom.css +118 -0
- package/docsmd/classes/Map.md +292 -0
- package/docsmd/classes/Point.md +603 -0
- package/docsmd/classes/SdkConfig.md +186 -0
- package/docsmd/images/JS-logo.svg +4 -0
- package/docsmd/images/TS-logo.svg +6 -0
- package/docsmd/images/maptiler-logo.svg +19 -0
- package/docsmd/images/maptiler-sdk-logo.afdesign +0 -0
- package/docsmd/images/maptiler-sdk-logo.svg +66 -0
- package/docsmd/images/screenshots/alps.gif +0 -0
- package/docsmd/images/screenshots/grandcanyon.gif +0 -0
- package/docsmd/images/screenshots/lang-arabic.png +0 -0
- package/docsmd/images/screenshots/lang-hebrew.png +0 -0
- package/docsmd/images/screenshots/multilang.gif +0 -0
- package/docsmd/images/screenshots/static-bounded-europe-1024.png +0 -0
- package/docsmd/images/screenshots/static-bounded-europe-2048.png +0 -0
- package/docsmd/images/screenshots/static-bounded-portugal-1024x2048.png +0 -0
- package/docsmd/images/screenshots/static-bounded-portugal-2048x2048.png +0 -0
- package/docsmd/images/screenshots/static-with-path.png +0 -0
- package/docsmd/images/screenshots/style-basic-v2.png +0 -0
- package/docsmd/images/screenshots/style-bright.png +0 -0
- package/docsmd/images/screenshots/style-dataviz-dark.png +0 -0
- package/docsmd/images/screenshots/style-hybrid.png +0 -0
- package/docsmd/images/screenshots/style-osm.png +0 -0
- package/docsmd/images/screenshots/style-outdoor.png +0 -0
- package/docsmd/images/screenshots/style-pastel.png +0 -0
- package/docsmd/images/screenshots/style-satellite.png +0 -0
- package/docsmd/images/screenshots/style-streets-v2-dark.png +0 -0
- package/docsmd/images/screenshots/style-streets-v2-light.png +0 -0
- package/docsmd/images/screenshots/style-streets-v2.png +0 -0
- package/docsmd/images/screenshots/style-toner.png +0 -0
- package/docsmd/images/screenshots/style-topo.png +0 -0
- package/docsmd/images/screenshots/style-topographique.png +0 -0
- package/docsmd/images/screenshots/style-voyager.png +0 -0
- package/docsmd/images/screenshots/style-winter.png +0 -0
- package/images/JS-logo.svg +4 -0
- package/images/TS-logo.svg +6 -0
- package/images/maptiler-logo.svg +19 -0
- package/images/maptiler-sdk-logo.afdesign +0 -0
- package/images/maptiler-sdk-logo.svg +66 -0
- package/images/screenshots/alps.gif +0 -0
- package/images/screenshots/grandcanyon.gif +0 -0
- package/images/screenshots/lang-arabic.png +0 -0
- package/images/screenshots/lang-hebrew.png +0 -0
- package/images/screenshots/multilang.gif +0 -0
- package/images/screenshots/static-bounded-europe-1024.png +0 -0
- package/images/screenshots/static-bounded-europe-2048.png +0 -0
- package/images/screenshots/static-bounded-portugal-1024x2048.png +0 -0
- package/images/screenshots/static-bounded-portugal-2048x2048.png +0 -0
- package/images/screenshots/static-with-path.png +0 -0
- package/images/screenshots/style-basic-v2.png +0 -0
- package/images/screenshots/style-bright.png +0 -0
- package/images/screenshots/style-dataviz-dark.png +0 -0
- package/images/screenshots/style-hybrid.png +0 -0
- package/images/screenshots/style-osm.png +0 -0
- package/images/screenshots/style-outdoor.png +0 -0
- package/images/screenshots/style-pastel.png +0 -0
- package/images/screenshots/style-satellite.png +0 -0
- package/images/screenshots/style-streets-v2-dark.png +0 -0
- package/images/screenshots/style-streets-v2-light.png +0 -0
- package/images/screenshots/style-streets-v2.png +0 -0
- package/images/screenshots/style-toner.png +0 -0
- package/images/screenshots/style-topo.png +0 -0
- package/images/screenshots/style-topographique.png +0 -0
- package/images/screenshots/style-voyager.png +0 -0
- package/images/screenshots/style-winter.png +0 -0
- package/package.json +71 -0
- package/readme.md +609 -0
- package/rollup.config.js +161 -0
- package/scripts/replace-path-with-content.js +51 -0
- package/src/CustomGeolocateControl.ts +193 -0
- package/src/CustomLogoControl.ts +59 -0
- package/src/Map.ts +897 -0
- package/src/MaptilerNavigationControl.ts +66 -0
- package/src/Point.ts +336 -0
- package/src/TerrainControl.ts +87 -0
- package/src/config.ts +92 -0
- package/src/defaults.ts +20 -0
- package/src/index.ts +171 -0
- package/src/language.ts +139 -0
- package/src/mapstyle.ts +38 -0
- package/src/style/style_template.css +146 -0
- package/src/style/svg/v6-compass.svg +12 -0
- package/src/style/svg/v6-fullscreen-off.svg +7 -0
- package/src/style/svg/v6-fullscreen.svg +7 -0
- package/src/style/svg/v6-geolocate-active-error.svg +10 -0
- package/src/style/svg/v6-geolocate-active.svg +7 -0
- package/src/style/svg/v6-geolocate-background.svg +8 -0
- package/src/style/svg/v6-geolocate-disabled.svg +10 -0
- package/src/style/svg/v6-geolocate.svg +7 -0
- package/src/style/svg/v6-terrain-on.svg +7 -0
- package/src/style/svg/v6-terrain.svg +7 -0
- package/src/style/svg/v6-zoom-minus.svg +7 -0
- package/src/style/svg/v6-zoom-plus.svg +7 -0
- package/src/tools.ts +45 -0
- package/src/unit.ts +1 -0
- package/tsconfig.json +11 -0
- package/typedoc.css +118 -0
- package/typedoc.json +13 -0
package/src/Map.ts
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
import maplibregl from "maplibre-gl";
|
|
2
|
+
import { Base64 } from "js-base64";
|
|
3
|
+
import type {
|
|
4
|
+
StyleSpecification,
|
|
5
|
+
MapOptions as MapOptionsML,
|
|
6
|
+
ControlPosition,
|
|
7
|
+
StyleOptions,
|
|
8
|
+
} from "maplibre-gl";
|
|
9
|
+
import { v4 as uuidv4 } from "uuid";
|
|
10
|
+
import { ReferenceMapStyle, MapStyleVariant } from "@maptiler/client";
|
|
11
|
+
import { config } from "./config";
|
|
12
|
+
import { defaults } from "./defaults";
|
|
13
|
+
import { CustomLogoControl } from "./CustomLogoControl";
|
|
14
|
+
import { enableRTL } from "./tools";
|
|
15
|
+
import {
|
|
16
|
+
getBrowserLanguage,
|
|
17
|
+
isLanguageSupported,
|
|
18
|
+
Language,
|
|
19
|
+
LanguageString,
|
|
20
|
+
} from "./language";
|
|
21
|
+
import { styleToStyle } from "./mapstyle";
|
|
22
|
+
import { TerrainControl } from "./TerrainControl";
|
|
23
|
+
import { MaptilerNavigationControl } from "./MaptilerNavigationControl";
|
|
24
|
+
import { geolocation } from "@maptiler/client";
|
|
25
|
+
import { CustomGeolocateControl } from "./CustomGeolocateControl";
|
|
26
|
+
|
|
27
|
+
// StyleSwapOptions is not exported by Maplibre, but we can redefine it (used for setStyle)
|
|
28
|
+
export type TransformStyleFunction = (
|
|
29
|
+
previous: StyleSpecification,
|
|
30
|
+
next: StyleSpecification
|
|
31
|
+
) => StyleSpecification;
|
|
32
|
+
|
|
33
|
+
export type StyleSwapOptions = {
|
|
34
|
+
diff?: boolean;
|
|
35
|
+
transformStyle?: TransformStyleFunction;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const MAPTILER_SESSION_ID = uuidv4();
|
|
39
|
+
|
|
40
|
+
export const GeolocationType: {
|
|
41
|
+
POINT: "POINT";
|
|
42
|
+
COUNTRY: "COUNTRY";
|
|
43
|
+
} = {
|
|
44
|
+
POINT: "POINT",
|
|
45
|
+
COUNTRY: "COUNTRY",
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Options to provide to the `Map` constructor
|
|
50
|
+
*/
|
|
51
|
+
export type MapOptions = Omit<MapOptionsML, "style" | "maplibreLogo"> & {
|
|
52
|
+
/**
|
|
53
|
+
* Style of the map. Can be:
|
|
54
|
+
* - a full style URL (possibly with API key)
|
|
55
|
+
* - a shorthand with only the MapTIler style name (eg. `"streets-v2"`)
|
|
56
|
+
* - a longer form with the prefix `"maptiler://"` (eg. `"maptiler://streets-v2"`)
|
|
57
|
+
*/
|
|
58
|
+
style?: ReferenceMapStyle | MapStyleVariant | StyleSpecification | string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Shows the MapTiler logo if `true`. Note that the logo is always displayed on free plan.
|
|
62
|
+
*/
|
|
63
|
+
maptilerLogo?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Enables 3D terrain if `true`. (default: `false`)
|
|
67
|
+
*/
|
|
68
|
+
terrain?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Exaggeration factor of the terrain. (default: `1`, no exaggeration)
|
|
72
|
+
*/
|
|
73
|
+
terrainExaggeration?: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show the navigation control. (default: `true`, will hide if `false`)
|
|
77
|
+
*/
|
|
78
|
+
navigationControl?: boolean | ControlPosition;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Show the terrain control. (default: `false`, will show if `true`)
|
|
82
|
+
*/
|
|
83
|
+
terrainControl?: boolean | ControlPosition;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Show the geolocate control. (default: `true`, will hide if `false`)
|
|
87
|
+
*/
|
|
88
|
+
geolocateControl?: boolean | ControlPosition;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Show the scale control. (default: `false`, will show if `true`)
|
|
92
|
+
*/
|
|
93
|
+
scaleControl?: boolean | ControlPosition;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Show the full screen control. (default: `false`, will show if `true`)
|
|
97
|
+
*/
|
|
98
|
+
fullscreenControl?: boolean | ControlPosition;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Method to position the map at a given geolocation. Only if:
|
|
102
|
+
* - `hash` is `false`
|
|
103
|
+
* - `center` is not provided
|
|
104
|
+
*
|
|
105
|
+
* If the value is `true` of `"POINT"` (given by `GeolocationType.POINT`) then the positionning uses the MapTiler Cloud
|
|
106
|
+
* Geolocation to find the non-GPS location point.
|
|
107
|
+
* The zoom level can be provided in the `Map` constructor with the `zoom` option or will be `13` if not provided.
|
|
108
|
+
*
|
|
109
|
+
* If the value is `"COUNTRY"` (given by `GeolocationType.COUNTRY`) then the map is centered around the bounding box of the country.
|
|
110
|
+
* In this case, the `zoom` option will be ignored.
|
|
111
|
+
*
|
|
112
|
+
* If the value is `false`, no geolocation is performed and the map centering and zooming depends on other options or on
|
|
113
|
+
* the built-in defaults.
|
|
114
|
+
*
|
|
115
|
+
* If this option is non-false and the options `center` is also provided, then `center` prevails.
|
|
116
|
+
*
|
|
117
|
+
* Default: `false`
|
|
118
|
+
*/
|
|
119
|
+
geolocate?: typeof GeolocationType[keyof typeof GeolocationType] | boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The Map class can be instanciated to display a map in a `<div>`
|
|
124
|
+
*/
|
|
125
|
+
export class Map extends maplibregl.Map {
|
|
126
|
+
private languageShouldUpdate = false;
|
|
127
|
+
private isStyleInitialized = false;
|
|
128
|
+
private isTerrainEnabled = false;
|
|
129
|
+
private terrainExaggeration = 1;
|
|
130
|
+
|
|
131
|
+
constructor(options: MapOptions) {
|
|
132
|
+
const style = styleToStyle(options.style);
|
|
133
|
+
console.log(style);
|
|
134
|
+
|
|
135
|
+
const hashPreConstructor = location.hash;
|
|
136
|
+
|
|
137
|
+
if (!config.apiKey) {
|
|
138
|
+
console.warn(
|
|
139
|
+
"MapTiler Cloud API key is not set. Visit https://maptiler.com and try Cloud for free!"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// calling the map constructor with full length style
|
|
144
|
+
super({
|
|
145
|
+
...options,
|
|
146
|
+
style,
|
|
147
|
+
maplibreLogo: false,
|
|
148
|
+
|
|
149
|
+
transformRequest: (url: string) => {
|
|
150
|
+
const reqUrl = new URL(url);
|
|
151
|
+
|
|
152
|
+
if (reqUrl.host === defaults.maptilerApiHost) {
|
|
153
|
+
if (!reqUrl.searchParams.has("key")) {
|
|
154
|
+
reqUrl.searchParams.append("key", config.apiKey);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (config.session) {
|
|
158
|
+
reqUrl.searchParams.append("mtsid", MAPTILER_SESSION_ID);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
url: reqUrl.href,
|
|
164
|
+
headers: {},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Map centering and geolocation
|
|
170
|
+
this.once("styledata", async () => {
|
|
171
|
+
// Not using geolocation centering if...
|
|
172
|
+
|
|
173
|
+
if (options.geolocate === false) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ... a center is provided in options
|
|
178
|
+
if (options.center) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ... the hash option is enabled and a hash is present in the URL
|
|
183
|
+
if (options.hash && !!hashPreConstructor) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If the geolocation is set to COUNTRY:
|
|
188
|
+
try {
|
|
189
|
+
if (options.geolocate === GeolocationType.COUNTRY) {
|
|
190
|
+
await this.fitToIpBounds();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// not raising
|
|
195
|
+
console.warn(e.message);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// As a fallback, we want to center the map on the visitor. First with IP geolocation...
|
|
199
|
+
let ipLocatedCameraHash = null;
|
|
200
|
+
try {
|
|
201
|
+
await this.centerOnIpPoint(options.zoom);
|
|
202
|
+
ipLocatedCameraHash = this.getCameraHash();
|
|
203
|
+
} catch (e) {
|
|
204
|
+
// not raising
|
|
205
|
+
console.warn(e.message);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Then, the get a more precise location, we rely on the browser location, but only if it was already granted
|
|
209
|
+
// before (we don't want to ask wih a popup at launch time)
|
|
210
|
+
const locationResult = await navigator.permissions.query({
|
|
211
|
+
name: "geolocation",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (locationResult.state === "granted") {
|
|
215
|
+
navigator.geolocation.getCurrentPosition(
|
|
216
|
+
// success callback
|
|
217
|
+
(data) => {
|
|
218
|
+
// If the user has already moved since the ip location, then we no longer want to move the center
|
|
219
|
+
if (ipLocatedCameraHash !== this.getCameraHash()) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.easeTo({
|
|
224
|
+
center: [data.coords.longitude, data.coords.latitude],
|
|
225
|
+
zoom: options.zoom || 12,
|
|
226
|
+
duration: 2000,
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// error callback
|
|
231
|
+
null,
|
|
232
|
+
|
|
233
|
+
// options
|
|
234
|
+
{
|
|
235
|
+
maximumAge: 24 * 3600 * 1000, // a day in millisec
|
|
236
|
+
timeout: 5000, // milliseconds
|
|
237
|
+
enableHighAccuracy: false,
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Check if language has been modified and. If so, it will be updated during the next lifecycle step
|
|
244
|
+
this.on("styledataloading", () => {
|
|
245
|
+
this.languageShouldUpdate =
|
|
246
|
+
!!config.primaryLanguage || !!config.secondaryLanguage;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// If the config includes language changing, we must update the map language
|
|
250
|
+
this.on("styledata", () => {
|
|
251
|
+
if (
|
|
252
|
+
config.primaryLanguage &&
|
|
253
|
+
(this.languageShouldUpdate || !this.isStyleInitialized)
|
|
254
|
+
) {
|
|
255
|
+
this.setPrimaryLanguage(config.primaryLanguage);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
config.secondaryLanguage &&
|
|
260
|
+
(this.languageShouldUpdate || !this.isStyleInitialized)
|
|
261
|
+
) {
|
|
262
|
+
this.setSecondaryLanguage(config.secondaryLanguage);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.languageShouldUpdate = false;
|
|
266
|
+
this.isStyleInitialized = true;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// this even is in charge of reaplying the terrain elevation after the
|
|
270
|
+
// style has changed because depending on the src/tgt style,
|
|
271
|
+
// the style logic is not always able to resolve the application of terrain
|
|
272
|
+
this.on("styledata", () => {
|
|
273
|
+
// the styling resolver did no manage to reaply the terrain,
|
|
274
|
+
// so let's reload it
|
|
275
|
+
if (this.getTerrain() === null && this.isTerrainEnabled) {
|
|
276
|
+
this.enableTerrain(this.terrainExaggeration);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// load the Right-to-Left text plugin (will happen only once)
|
|
281
|
+
this.once("load", async () => {
|
|
282
|
+
enableRTL();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Update logo and attibution
|
|
286
|
+
this.once("load", async () => {
|
|
287
|
+
let tileJsonContent = { logo: null };
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const possibleSources = Object.keys(this.style.sourceCaches)
|
|
291
|
+
.map((sourceName) => this.getSource(sourceName))
|
|
292
|
+
.filter(
|
|
293
|
+
(s: any) =>
|
|
294
|
+
typeof s.url === "string" && s.url.includes("tiles.json")
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const styleUrl = new URL(
|
|
298
|
+
(possibleSources[0] as maplibregl.VectorTileSource).url
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (!styleUrl.searchParams.has("key")) {
|
|
302
|
+
styleUrl.searchParams.append("key", config.apiKey);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const tileJsonRes = await fetch(styleUrl.href);
|
|
306
|
+
tileJsonContent = await tileJsonRes.json();
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// No tiles.json found (should not happen on maintained styles)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// The attribution and logo must show when required
|
|
312
|
+
if ("logo" in tileJsonContent && tileJsonContent.logo) {
|
|
313
|
+
const logoURL: string = tileJsonContent.logo;
|
|
314
|
+
|
|
315
|
+
this.addControl(
|
|
316
|
+
new CustomLogoControl({ logoURL }),
|
|
317
|
+
options.logoPosition
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// if attribution in option is `false` but the the logo shows up in the tileJson, then the attribution must show anyways
|
|
321
|
+
if (options.attributionControl === false) {
|
|
322
|
+
this.addControl(new maplibregl.AttributionControl(options));
|
|
323
|
+
}
|
|
324
|
+
} else if (options.maptilerLogo) {
|
|
325
|
+
this.addControl(new CustomLogoControl(), options.logoPosition);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// the other controls at init time but be after
|
|
329
|
+
// (due to the async nature of logo control)
|
|
330
|
+
|
|
331
|
+
// By default, no scale control
|
|
332
|
+
if (options.scaleControl) {
|
|
333
|
+
// default position, if not provided, is top left corner
|
|
334
|
+
const position = (
|
|
335
|
+
options.scaleControl === true || options.scaleControl === undefined
|
|
336
|
+
? "bottom-right"
|
|
337
|
+
: options.scaleControl
|
|
338
|
+
) as ControlPosition;
|
|
339
|
+
|
|
340
|
+
const scaleControl = new maplibregl.ScaleControl({ unit: config.unit });
|
|
341
|
+
this.addControl(scaleControl, position);
|
|
342
|
+
config.on("unit", (unit) => {
|
|
343
|
+
scaleControl.setUnit(unit);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (options.navigationControl !== false) {
|
|
348
|
+
// default position, if not provided, is top left corner
|
|
349
|
+
const position = (
|
|
350
|
+
options.navigationControl === true ||
|
|
351
|
+
options.navigationControl === undefined
|
|
352
|
+
? "top-right"
|
|
353
|
+
: options.navigationControl
|
|
354
|
+
) as ControlPosition;
|
|
355
|
+
this.addControl(new MaptilerNavigationControl(), position);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (options.geolocateControl !== false) {
|
|
359
|
+
// default position, if not provided, is top left corner
|
|
360
|
+
const position = (
|
|
361
|
+
options.geolocateControl === true ||
|
|
362
|
+
options.geolocateControl === undefined
|
|
363
|
+
? "top-right"
|
|
364
|
+
: options.geolocateControl
|
|
365
|
+
) as ControlPosition;
|
|
366
|
+
|
|
367
|
+
this.addControl(
|
|
368
|
+
// new maplibregl.GeolocateControl({
|
|
369
|
+
new CustomGeolocateControl({
|
|
370
|
+
positionOptions: {
|
|
371
|
+
enableHighAccuracy: true,
|
|
372
|
+
maximumAge: 0,
|
|
373
|
+
timeout: 6000 /* 6 sec */,
|
|
374
|
+
},
|
|
375
|
+
fitBoundsOptions: {
|
|
376
|
+
maxZoom: 15,
|
|
377
|
+
},
|
|
378
|
+
trackUserLocation: true,
|
|
379
|
+
showAccuracyCircle: true,
|
|
380
|
+
showUserLocation: true,
|
|
381
|
+
}),
|
|
382
|
+
position
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (options.terrainControl) {
|
|
387
|
+
// default position, if not provided, is top left corner
|
|
388
|
+
const position = (
|
|
389
|
+
options.terrainControl === true ||
|
|
390
|
+
options.terrainControl === undefined
|
|
391
|
+
? "top-right"
|
|
392
|
+
: options.terrainControl
|
|
393
|
+
) as ControlPosition;
|
|
394
|
+
this.addControl(new TerrainControl(), position);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// By default, no fullscreen control
|
|
398
|
+
if (options.fullscreenControl) {
|
|
399
|
+
// default position, if not provided, is top left corner
|
|
400
|
+
const position = (
|
|
401
|
+
options.fullscreenControl === true ||
|
|
402
|
+
options.fullscreenControl === undefined
|
|
403
|
+
? "top-right"
|
|
404
|
+
: options.fullscreenControl
|
|
405
|
+
) as ControlPosition;
|
|
406
|
+
|
|
407
|
+
this.addControl(new maplibregl.FullscreenControl({}), position);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// enable 3D terrain if provided in options
|
|
412
|
+
if (options.terrain) {
|
|
413
|
+
this.enableTerrain(
|
|
414
|
+
options.terrainExaggeration ?? this.terrainExaggeration
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Update the style of the map.
|
|
421
|
+
* Can be:
|
|
422
|
+
* - a full style URL (possibly with API key)
|
|
423
|
+
* - a shorthand with only the MapTIler style name (eg. `"streets-v2"`)
|
|
424
|
+
* - a longer form with the prefix `"maptiler://"` (eg. `"maptiler://streets-v2"`)
|
|
425
|
+
* @param style
|
|
426
|
+
* @param options
|
|
427
|
+
* @returns
|
|
428
|
+
*/
|
|
429
|
+
setStyle(
|
|
430
|
+
style: ReferenceMapStyle | MapStyleVariant | StyleSpecification | string,
|
|
431
|
+
options?: StyleSwapOptions & StyleOptions
|
|
432
|
+
) {
|
|
433
|
+
return super.setStyle(styleToStyle(style), options);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Define the primary language of the map. Note that not all the languages shorthands provided are available.
|
|
438
|
+
* This function is a short for `.setPrimaryLanguage()`
|
|
439
|
+
* @param language
|
|
440
|
+
*/
|
|
441
|
+
setLanguage(language: LanguageString = defaults.primaryLanguage) {
|
|
442
|
+
if (language === Language.AUTO) {
|
|
443
|
+
return this.setLanguage(getBrowserLanguage());
|
|
444
|
+
}
|
|
445
|
+
this.setPrimaryLanguage(language);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Define the primary language of the map. Note that not all the languages shorthands provided are available.
|
|
450
|
+
* @param language
|
|
451
|
+
*/
|
|
452
|
+
setPrimaryLanguage(language: LanguageString = defaults.primaryLanguage) {
|
|
453
|
+
if (!isLanguageSupported(language as string)) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this.onStyleReady(() => {
|
|
458
|
+
if (language === Language.AUTO) {
|
|
459
|
+
return this.setPrimaryLanguage(getBrowserLanguage());
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// We want to keep track of it to apply the language again when changing the style
|
|
463
|
+
config.primaryLanguage = language;
|
|
464
|
+
|
|
465
|
+
const layers = this.getStyle().layers;
|
|
466
|
+
|
|
467
|
+
// detects pattern like "{name:somelanguage}" with loose spacing
|
|
468
|
+
const strLanguageRegex = /^\s*{\s*name\s*(:\s*(\S*))?\s*}$/;
|
|
469
|
+
|
|
470
|
+
// detects pattern like "name:somelanguage" with loose spacing
|
|
471
|
+
const strLanguageInArrayRegex = /^\s*name\s*(:\s*(\S*))?\s*$/;
|
|
472
|
+
|
|
473
|
+
// for string based bilingual lang such as "{name:latin} {name:nonlatin}" or "{name:latin} {name}"
|
|
474
|
+
const strBilingualRegex =
|
|
475
|
+
/^\s*{\s*name\s*(:\s*(\S*))?\s*}(\s*){\s*name\s*(:\s*(\S*))?\s*}$/;
|
|
476
|
+
|
|
477
|
+
// Regex to capture when there are more info, such as mountains elevation with unit m/ft
|
|
478
|
+
const strMoreInfoRegex = /^(.*)({\s*name\s*(:\s*(\S*))?\s*})(.*)$/;
|
|
479
|
+
|
|
480
|
+
const langStr = language ? `name:${language}` : "name"; // to handle local lang
|
|
481
|
+
const replacer = [
|
|
482
|
+
"case",
|
|
483
|
+
["has", langStr],
|
|
484
|
+
["get", langStr],
|
|
485
|
+
["get", "name:latin"],
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
for (let i = 0; i < layers.length; i += 1) {
|
|
489
|
+
const layer = layers[i];
|
|
490
|
+
const layout = layer.layout;
|
|
491
|
+
|
|
492
|
+
if (!layout) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!layout["text-field"]) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const textFieldLayoutProp = this.getLayoutProperty(
|
|
501
|
+
layer.id,
|
|
502
|
+
"text-field"
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
// Note:
|
|
506
|
+
// The value of the 'text-field' property can take multiple shape;
|
|
507
|
+
// 1. can be an array with 'concat' on its first element (most likely means bilingual)
|
|
508
|
+
// 2. can be an array with 'get' on its first element (monolingual)
|
|
509
|
+
// 3. can be a string of shape '{name:latin}'
|
|
510
|
+
// 4. can be a string referencing another prop such as '{housenumber}' or '{ref}'
|
|
511
|
+
//
|
|
512
|
+
// The case 1, 2 and 3 will be updated while maintaining their original type and shape.
|
|
513
|
+
// The case 3 will not be updated
|
|
514
|
+
|
|
515
|
+
let regexMatch;
|
|
516
|
+
|
|
517
|
+
// This is case 1
|
|
518
|
+
if (
|
|
519
|
+
Array.isArray(textFieldLayoutProp) &&
|
|
520
|
+
textFieldLayoutProp.length >= 2 &&
|
|
521
|
+
textFieldLayoutProp[0].trim().toLowerCase() === "concat"
|
|
522
|
+
) {
|
|
523
|
+
const newProp = textFieldLayoutProp.slice(); // newProp is Array
|
|
524
|
+
// The style could possibly have defined more than 2 concatenated language strings but we only want to edit the first
|
|
525
|
+
// The style could also define that there are more things being concatenated and not only languages
|
|
526
|
+
|
|
527
|
+
for (let j = 0; j < textFieldLayoutProp.length; j += 1) {
|
|
528
|
+
const elem = textFieldLayoutProp[j];
|
|
529
|
+
|
|
530
|
+
// we are looking for an elem of shape '{name:somelangage}' (string) of `["get", "name:somelanguage"]` (array)
|
|
531
|
+
|
|
532
|
+
// the entry of of shape '{name:somelangage}', possibly with loose spacing
|
|
533
|
+
if (
|
|
534
|
+
(typeof elem === "string" || elem instanceof String) &&
|
|
535
|
+
strLanguageRegex.exec(elem.toString())
|
|
536
|
+
) {
|
|
537
|
+
newProp[j] = replacer;
|
|
538
|
+
break; // we just want to update the primary language
|
|
539
|
+
}
|
|
540
|
+
// the entry is of an array of shape `["get", "name:somelanguage"]`
|
|
541
|
+
else if (
|
|
542
|
+
Array.isArray(elem) &&
|
|
543
|
+
elem.length >= 2 &&
|
|
544
|
+
elem[0].trim().toLowerCase() === "get" &&
|
|
545
|
+
strLanguageInArrayRegex.exec(elem[1].toString())
|
|
546
|
+
) {
|
|
547
|
+
newProp[j] = replacer;
|
|
548
|
+
break; // we just want to update the primary language
|
|
549
|
+
} else if (
|
|
550
|
+
Array.isArray(elem) &&
|
|
551
|
+
elem.length === 4 &&
|
|
552
|
+
elem[0].trim().toLowerCase() === "case"
|
|
553
|
+
) {
|
|
554
|
+
newProp[j] = replacer;
|
|
555
|
+
break; // we just want to update the primary language
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// This is case 2
|
|
563
|
+
else if (
|
|
564
|
+
Array.isArray(textFieldLayoutProp) &&
|
|
565
|
+
textFieldLayoutProp.length >= 2 &&
|
|
566
|
+
textFieldLayoutProp[0].trim().toLowerCase() === "get" &&
|
|
567
|
+
strLanguageInArrayRegex.exec(textFieldLayoutProp[1].toString())
|
|
568
|
+
) {
|
|
569
|
+
const newProp = replacer;
|
|
570
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// This is case 3
|
|
574
|
+
else if (
|
|
575
|
+
(typeof textFieldLayoutProp === "string" ||
|
|
576
|
+
textFieldLayoutProp instanceof String) &&
|
|
577
|
+
strLanguageRegex.exec(textFieldLayoutProp.toString())
|
|
578
|
+
) {
|
|
579
|
+
const newProp = replacer;
|
|
580
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
581
|
+
} else if (
|
|
582
|
+
Array.isArray(textFieldLayoutProp) &&
|
|
583
|
+
textFieldLayoutProp.length === 4 &&
|
|
584
|
+
textFieldLayoutProp[0].trim().toLowerCase() === "case"
|
|
585
|
+
) {
|
|
586
|
+
const newProp = replacer;
|
|
587
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
588
|
+
} else if (
|
|
589
|
+
(typeof textFieldLayoutProp === "string" ||
|
|
590
|
+
textFieldLayoutProp instanceof String) &&
|
|
591
|
+
(regexMatch = strBilingualRegex.exec(
|
|
592
|
+
textFieldLayoutProp.toString()
|
|
593
|
+
)) !== null
|
|
594
|
+
) {
|
|
595
|
+
const newProp = `{${langStr}}${regexMatch[3]}{name${
|
|
596
|
+
regexMatch[4] || ""
|
|
597
|
+
}}`;
|
|
598
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
599
|
+
} else if (
|
|
600
|
+
(typeof textFieldLayoutProp === "string" ||
|
|
601
|
+
textFieldLayoutProp instanceof String) &&
|
|
602
|
+
(regexMatch = strMoreInfoRegex.exec(
|
|
603
|
+
textFieldLayoutProp.toString()
|
|
604
|
+
)) !== null
|
|
605
|
+
) {
|
|
606
|
+
const newProp = `${regexMatch[1]}{${langStr}}${regexMatch[5]}`;
|
|
607
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Define the secondary language of the map.
|
|
615
|
+
* Note that most styles do not allow a secondary language and this function only works if the style allows (no force adding)
|
|
616
|
+
* @param language
|
|
617
|
+
*/
|
|
618
|
+
setSecondaryLanguage(language: LanguageString = defaults.secondaryLanguage) {
|
|
619
|
+
if (!isLanguageSupported(language as string)) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.onStyleReady(() => {
|
|
624
|
+
if (language === Language.AUTO) {
|
|
625
|
+
return this.setSecondaryLanguage(getBrowserLanguage());
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// We want to keep track of it to apply the language again when changing the style
|
|
629
|
+
config.secondaryLanguage = language;
|
|
630
|
+
|
|
631
|
+
const layers = this.getStyle().layers;
|
|
632
|
+
|
|
633
|
+
// detects pattern like "{name:somelanguage}" with loose spacing
|
|
634
|
+
const strLanguageRegex = /^\s*{\s*name\s*(:\s*(\S*))?\s*}$/;
|
|
635
|
+
|
|
636
|
+
// detects pattern like "name:somelanguage" with loose spacing
|
|
637
|
+
const strLanguageInArrayRegex = /^\s*name\s*(:\s*(\S*))?\s*$/;
|
|
638
|
+
|
|
639
|
+
// for string based bilingual lang such as "{name:latin} {name:nonlatin}" or "{name:latin} {name}"
|
|
640
|
+
const strBilingualRegex =
|
|
641
|
+
/^\s*{\s*name\s*(:\s*(\S*))?\s*}(\s*){\s*name\s*(:\s*(\S*))?\s*}$/;
|
|
642
|
+
|
|
643
|
+
let regexMatch;
|
|
644
|
+
|
|
645
|
+
for (let i = 0; i < layers.length; i += 1) {
|
|
646
|
+
const layer = layers[i];
|
|
647
|
+
const layout = layer.layout;
|
|
648
|
+
|
|
649
|
+
if (!layout) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (!layout["text-field"]) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const textFieldLayoutProp = this.getLayoutProperty(
|
|
658
|
+
layer.id,
|
|
659
|
+
"text-field"
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
let newProp;
|
|
663
|
+
|
|
664
|
+
// Note:
|
|
665
|
+
// The value of the 'text-field' property can take multiple shape;
|
|
666
|
+
// 1. can be an array with 'concat' on its first element (most likely means bilingual)
|
|
667
|
+
// 2. can be an array with 'get' on its first element (monolingual)
|
|
668
|
+
// 3. can be a string of shape '{name:latin}'
|
|
669
|
+
// 4. can be a string referencing another prop such as '{housenumber}' or '{ref}'
|
|
670
|
+
//
|
|
671
|
+
// Only the case 1 will be updated because we don't want to change the styling (read: add a secondary language where the original styling is only displaying 1)
|
|
672
|
+
|
|
673
|
+
// This is case 1
|
|
674
|
+
if (
|
|
675
|
+
Array.isArray(textFieldLayoutProp) &&
|
|
676
|
+
textFieldLayoutProp.length >= 2 &&
|
|
677
|
+
textFieldLayoutProp[0].trim().toLowerCase() === "concat"
|
|
678
|
+
) {
|
|
679
|
+
newProp = textFieldLayoutProp.slice(); // newProp is Array
|
|
680
|
+
// The style could possibly have defined more than 2 concatenated language strings but we only want to edit the first
|
|
681
|
+
// The style could also define that there are more things being concatenated and not only languages
|
|
682
|
+
|
|
683
|
+
let languagesAlreadyFound = 0;
|
|
684
|
+
|
|
685
|
+
for (let j = 0; j < textFieldLayoutProp.length; j += 1) {
|
|
686
|
+
const elem = textFieldLayoutProp[j];
|
|
687
|
+
|
|
688
|
+
// we are looking for an elem of shape '{name:somelangage}' (string) of `["get", "name:somelanguage"]` (array)
|
|
689
|
+
|
|
690
|
+
// the entry of of shape '{name:somelangage}', possibly with loose spacing
|
|
691
|
+
if (
|
|
692
|
+
(typeof elem === "string" || elem instanceof String) &&
|
|
693
|
+
strLanguageRegex.exec(elem.toString())
|
|
694
|
+
) {
|
|
695
|
+
if (languagesAlreadyFound === 1) {
|
|
696
|
+
newProp[j] = `{name:${language}}`;
|
|
697
|
+
break; // we just want to update the secondary language
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
languagesAlreadyFound += 1;
|
|
701
|
+
}
|
|
702
|
+
// the entry is of an array of shape `["get", "name:somelanguage"]`
|
|
703
|
+
else if (
|
|
704
|
+
Array.isArray(elem) &&
|
|
705
|
+
elem.length >= 2 &&
|
|
706
|
+
elem[0].trim().toLowerCase() === "get" &&
|
|
707
|
+
strLanguageInArrayRegex.exec(elem[1].toString())
|
|
708
|
+
) {
|
|
709
|
+
if (languagesAlreadyFound === 1) {
|
|
710
|
+
newProp[j][1] = `name:${language}`;
|
|
711
|
+
break; // we just want to update the secondary language
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
languagesAlreadyFound += 1;
|
|
715
|
+
} else if (
|
|
716
|
+
Array.isArray(elem) &&
|
|
717
|
+
elem.length === 4 &&
|
|
718
|
+
elem[0].trim().toLowerCase() === "case"
|
|
719
|
+
) {
|
|
720
|
+
if (languagesAlreadyFound === 1) {
|
|
721
|
+
newProp[j] = ["get", `name:${language}`]; // the situation with 'case' is supposed to only happen with the primary lang
|
|
722
|
+
break; // but in case a styling also does that for secondary...
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
languagesAlreadyFound += 1;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// the language (both first and second) are defined into a single string model
|
|
733
|
+
else if (
|
|
734
|
+
(typeof textFieldLayoutProp === "string" ||
|
|
735
|
+
textFieldLayoutProp instanceof String) &&
|
|
736
|
+
(regexMatch = strBilingualRegex.exec(
|
|
737
|
+
textFieldLayoutProp.toString()
|
|
738
|
+
)) !== null
|
|
739
|
+
) {
|
|
740
|
+
const langStr = language ? `name:${language}` : "name"; // to handle local lang
|
|
741
|
+
newProp = `{name${regexMatch[1] || ""}}${regexMatch[3]}{${langStr}}`;
|
|
742
|
+
this.setLayoutProperty(layer.id, "text-field", newProp);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Get the exaggeration factor applied to the terrain
|
|
750
|
+
* @returns
|
|
751
|
+
*/
|
|
752
|
+
getTerrainExaggeration(): number {
|
|
753
|
+
return this.terrainExaggeration;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Know if terrian is enabled or not
|
|
758
|
+
* @returns
|
|
759
|
+
*/
|
|
760
|
+
hasTerrain(): boolean {
|
|
761
|
+
return this.isTerrainEnabled;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Enables the 3D terrain visualization
|
|
766
|
+
* @param exaggeration
|
|
767
|
+
* @returns
|
|
768
|
+
*/
|
|
769
|
+
enableTerrain(exaggeration = this.terrainExaggeration) {
|
|
770
|
+
if (exaggeration < 0) {
|
|
771
|
+
console.warn("Terrain exaggeration cannot be negative.");
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const terrainInfo = this.getTerrain();
|
|
776
|
+
|
|
777
|
+
const addTerrain = () => {
|
|
778
|
+
// When style is changed,
|
|
779
|
+
this.isTerrainEnabled = true;
|
|
780
|
+
this.terrainExaggeration = exaggeration;
|
|
781
|
+
|
|
782
|
+
this.addSource(defaults.terrainSourceId, {
|
|
783
|
+
type: "raster-dem",
|
|
784
|
+
url: defaults.terrainSourceURL,
|
|
785
|
+
});
|
|
786
|
+
this.setTerrain({
|
|
787
|
+
source: defaults.terrainSourceId,
|
|
788
|
+
exaggeration: exaggeration,
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// The terrain has already been loaded,
|
|
793
|
+
// we just update the exaggeration.
|
|
794
|
+
if (terrainInfo) {
|
|
795
|
+
this.setTerrain({ ...terrainInfo, exaggeration });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (this.loaded() || this.isTerrainEnabled) {
|
|
800
|
+
addTerrain();
|
|
801
|
+
} else {
|
|
802
|
+
this.once("load", () => {
|
|
803
|
+
if (this.getTerrain() && this.getSource(defaults.terrainSourceId)) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
addTerrain();
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Disable the 3D terrain visualization
|
|
813
|
+
*/
|
|
814
|
+
disableTerrain() {
|
|
815
|
+
this.isTerrainEnabled = false;
|
|
816
|
+
this.setTerrain(null);
|
|
817
|
+
if (this.getSource(defaults.terrainSourceId)) {
|
|
818
|
+
this.removeSource(defaults.terrainSourceId);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Sets the 3D terrain exageration factor.
|
|
824
|
+
* Note: this is only a shortcut to `.enableTerrain()`
|
|
825
|
+
* @param exaggeration
|
|
826
|
+
*/
|
|
827
|
+
setTerrainExaggeration(exaggeration: number) {
|
|
828
|
+
this.enableTerrain(exaggeration);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// getLanguages() {
|
|
832
|
+
// const layers = this.getStyle().layers;
|
|
833
|
+
|
|
834
|
+
// for (let i = 0; i < layers.length; i += 1) {
|
|
835
|
+
// const layer = layers[i];
|
|
836
|
+
// const layout = layer.layout;
|
|
837
|
+
|
|
838
|
+
// if (!layout) {
|
|
839
|
+
// continue;
|
|
840
|
+
// }
|
|
841
|
+
|
|
842
|
+
// if (!layout["text-field"]) {
|
|
843
|
+
// continue;
|
|
844
|
+
// }
|
|
845
|
+
|
|
846
|
+
// const textFieldLayoutProp = this.getLayoutProperty(
|
|
847
|
+
// layer.id,
|
|
848
|
+
// "text-field"
|
|
849
|
+
// );
|
|
850
|
+
// }
|
|
851
|
+
// }
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Perform an action when the style is ready. It could be at the moment of calling this method
|
|
855
|
+
* or later.
|
|
856
|
+
* @param cb
|
|
857
|
+
*/
|
|
858
|
+
private onStyleReady(cb) {
|
|
859
|
+
if (this.isStyleLoaded()) {
|
|
860
|
+
cb();
|
|
861
|
+
} else {
|
|
862
|
+
this.once("styledata", () => {
|
|
863
|
+
cb();
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async fitToIpBounds() {
|
|
869
|
+
const ipGeolocateResult = await geolocation.info();
|
|
870
|
+
this.fitBounds(
|
|
871
|
+
ipGeolocateResult.country_bounds as [number, number, number, number],
|
|
872
|
+
{
|
|
873
|
+
duration: 0,
|
|
874
|
+
padding: 100,
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
async centerOnIpPoint(zoom: number | undefined) {
|
|
880
|
+
const ipGeolocateResult = await geolocation.info();
|
|
881
|
+
this.jumpTo({
|
|
882
|
+
center: [ipGeolocateResult.longitude, ipGeolocateResult.latitude],
|
|
883
|
+
zoom: zoom || 11,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
getCameraHash() {
|
|
888
|
+
const hashBin = new Float32Array(5);
|
|
889
|
+
const center = this.getCenter();
|
|
890
|
+
hashBin[0] = center.lng;
|
|
891
|
+
hashBin[1] = center.lat;
|
|
892
|
+
hashBin[2] = this.getZoom();
|
|
893
|
+
hashBin[3] = this.getPitch();
|
|
894
|
+
hashBin[4] = this.getBearing();
|
|
895
|
+
return Base64.fromUint8Array(new Uint8Array(hashBin.buffer));
|
|
896
|
+
}
|
|
897
|
+
}
|