@maptiler/sdk 1.0.9 → 1.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maptiler/sdk",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "The Javascript & TypeScript map SDK tailored for MapTiler Cloud",
5
5
  "module": "dist/maptiler-sdk.mjs",
6
6
  "types": "dist/maptiler-sdk.d.ts",
@@ -20,52 +20,50 @@
20
20
  "sdk",
21
21
  "webmap",
22
22
  "cloud",
23
- "webGL"
23
+ "webGL",
24
+ "maplibre"
24
25
  ],
25
- "homepage": "https://www.maptiler.com/cloud/",
26
+ "homepage": "https://docs.maptiler.com/sdk-js/",
26
27
  "license": "BSD-3-Clause",
27
28
  "repository": {
28
29
  "type": "git",
29
30
  "url": "https://github.com/maptiler/maptiler-sdk-js.git"
30
31
  },
31
32
  "scripts": {
32
- "build": "rm -rf dist/* ; NODE_ENV=production rollup -c",
33
- "dev": "rm -rf dist/* ; NODE_ENV=development rollup -c -w",
33
+ "build": "rm -rf dist/* && NODE_ENV=production rollup -c",
34
+ "dev": "rm -rf dist/* && NODE_ENV=development rollup -c -w",
34
35
  "format": "prettier --write \"src/**/*.{js,ts,tsx}\"",
35
36
  "lint": "eslint --fix \"src/**/*.{js,ts}\"",
36
- "docmd": "rm -rf docsmd/*; typedoc --readme none --plugin typedoc-plugin-markdown --out docsmd src/index.ts; cp -r images docsmd/",
37
- "dochtml": "rm -rf docs/*; typedoc --plugin none --out docs; cp -r images docs/",
38
- "doc": "npm run docmd; npm run dochtml",
39
- "prepare": "npm run format; npm run lint; npm run build; npm run doc; cp -r demos docs/"
37
+ "doc": "rm -rf docs/* && typedoc --out docs && cp -r images docs/",
38
+ "prepare": "npm run format && npm run lint && npm run build && npm run doc && cp -r demos docs/"
40
39
  },
41
40
  "author": "MapTiler",
42
41
  "devDependencies": {
43
- "@rollup/plugin-commonjs": "^22.0.2",
44
- "@rollup/plugin-json": "^5.0.1",
45
- "@rollup/plugin-node-resolve": "^14.1.0",
46
- "@typescript-eslint/eslint-plugin": "^5.41.0",
47
- "@typescript-eslint/parser": "^5.41.0",
48
- "eslint": "^8.26.0",
49
- "prettier": "^2.7.1",
50
- "rollup": "^2.79.0",
51
- "rollup-plugin-copy-merge": "^0.3.5",
52
- "rollup-plugin-dts": "^4.2.2",
53
- "rollup-plugin-esbuild": "^4.10.1",
42
+ "@rollup/plugin-commonjs": "^24.1.0",
43
+ "@rollup/plugin-json": "^6.0.0",
44
+ "@rollup/plugin-node-resolve": "^15.0.2",
45
+ "@typescript-eslint/eslint-plugin": "^5.59.0",
46
+ "@typescript-eslint/parser": "^5.59.0",
47
+ "eslint": "^8.38.0",
48
+ "prettier": "^2.8.7",
49
+ "rollup": "^3.20.6",
50
+ "rollup-plugin-copy-merge": "^1.0.0",
51
+ "rollup-plugin-dts": "^5.3.0",
52
+ "rollup-plugin-esbuild": "^5.0.0",
54
53
  "rollup-plugin-node-globals": "^1.4.0",
55
54
  "rollup-plugin-shell": "^1.0.9",
56
55
  "rollup-plugin-string": "^3.0.0",
57
56
  "rollup-plugin-swc": "^0.2.1",
58
- "serve": "^14.0.1",
59
- "terser": "^5.15.0",
60
- "typedoc": "^0.23.21",
61
- "typedoc-plugin-markdown": "^3.13.6",
62
- "typescript": "^4.8.4"
57
+ "serve": "^14.2.0",
58
+ "terser": "^5.17.1",
59
+ "typedoc": "^0.24.4",
60
+ "typescript": "^5.0.4"
63
61
  },
64
62
  "dependencies": {
65
63
  "@maptiler/client": "^1.3.0",
66
64
  "events": "^3.3.0",
67
65
  "js-base64": "^3.7.4",
68
- "maplibre-gl": "^3.0.0-pre.3",
66
+ "maplibre-gl": "3.0.0-pre.4",
69
67
  "uuid": "^9.0.0"
70
68
  }
71
69
  }
package/readme.md CHANGED
@@ -389,6 +389,114 @@ Languages that are written right-to-left such as arabic and hebrew are fully sup
389
389
  <img src="images/screenshots/lang-hebrew.jpeg" width="48%"></img>
390
390
  </p>
391
391
 
392
+ # Custom Events and Map Lifecycle
393
+ ## Events
394
+ Since the SDK is fully compatible with MapLibre, [all these events](https://maplibre.org/maplibre-gl-js-docs/api/map/#map-events) are available, yet we have added one more: `loadWithTerrain`.
395
+
396
+ The `loadWithTerrain` event is triggered only *once* in a `Map` instance lifecycle, when both the `load` event and the `terrain` event **with non-null terrain** are fired.
397
+
398
+ **Why a new event?**
399
+ When a map is instanciated with the option `terrain: true`, then MapTiler terrain is directly added to it and some animation functions such as `.flyTo()` or `.easeTo()` if started straight after the map initialization will actually need to wait a few milliseconds that the terrain is properly initialized before running.
400
+ Relying on the `load` event to run an animation with a map with terrain may fail in some cases for this reason, and this is why waiting for `loadWithTerrain` is safer in this particular situation.
401
+
402
+ ## Lifecycle Methods
403
+ The events `load` and `loadWithTerrain` are both called *at most once* and require a callback function to add more elements such as markers, layers, popups and data sources. Even though MapTiler SDK fully supports this logic, we have also included a *promise* logic to provide a more linear and less nested way to wait for a Map instance to be ready. Let's compare the two ways:
404
+
405
+ - Classic: with a callback on the `load` event:
406
+ ```ts
407
+ function init() {
408
+
409
+ const map = new Map({
410
+ container,
411
+ center: [2.34804, 48.85439], // Paris, France
412
+ zoom: 14,
413
+ });
414
+
415
+ // We wait for the event.
416
+ // Once triggered, the callback is ranin it's own scope.
417
+ map.on("load", (evt) => {
418
+ // Adding a data source
419
+ map.addSource('my-gps-track-source', {
420
+ type: "geojson",
421
+ data: "https://example.com/some-gps-track.geojson",
422
+ });
423
+ })
424
+ }
425
+ ```
426
+
427
+ - Modern: with a promise returned by the method `.onLoadAsync()`, used in an `async` function:
428
+ ```ts
429
+ async function init() {
430
+
431
+ const map = new Map({
432
+ container,
433
+ center: [2.34804, 48.85439], // Paris, France
434
+ zoom: 14,
435
+ });
436
+
437
+ // We wait for the promise to resolve.
438
+ // Once triggered, the rest of the init function runs
439
+ await map.onLoadAsync();
440
+
441
+ // Adding a data source
442
+ map.addSource('my-gps-track-source', {
443
+ type: "geojson",
444
+ data: "https://example.com/some-gps-track.geojson",
445
+ });
446
+ }
447
+ ```
448
+
449
+ We deployed exactely the same logic for the `loadWithTerrain` event. Let's see how they two ways compares.
450
+ - Classic: with a callback on the `loadWithTerrain` event:
451
+ ```ts
452
+ function init() {
453
+
454
+ const map = new Map({
455
+ container,
456
+ center: [2.34804, 48.85439], // Paris, France
457
+ zoom: 14,
458
+ terrain: true,
459
+ });
460
+
461
+ // We wait for the event.
462
+ // Once triggered, the callback is ran in its own scope.
463
+ map.on("loadWithTerrain", (evt) => {
464
+ // make an animation
465
+ map.flyTo({
466
+ center: [-0.09956, 51.50509], // London, UK
467
+ zoom: 12.5,
468
+ })
469
+ })
470
+ }
471
+ ```
472
+
473
+ - Modern: with a promise returned by the method `.onLoadWithTerrainAsync()`, used in an `async` function:
474
+ ```ts
475
+ async function init() {
476
+
477
+ const map = new Map({
478
+ container,
479
+ center: [2.34804, 48.85439], // Paris, France
480
+ zoom: 14,
481
+ terrain: true,
482
+ });
483
+
484
+ // We wait for the promise to resolve.
485
+ // Once triggered, the rest of the init function runs
486
+ await map.onLoadWithTerrainAsync();
487
+
488
+ // make an animation
489
+ map.flyTo({
490
+ center: [-0.09956, 51.50509], // London, UK
491
+ zoom: 12.5,
492
+ })
493
+ }
494
+ ```
495
+
496
+ We believe that the *promise* approach is better because it does not nest scopes and will allow for a linear non-nested stream of execution. It also corresponds to more modern development standards.
497
+
498
+ > 📣 *__Note:__* Generally speaking, *promises* are not a go to replacement for all event+callback and are suitable only for events that are called only once in the lifecycle of a Map instance. This is the reason why we have decided to provide a *promise* equivalent only for the `load` and `loadWithTerrain` events.
499
+
392
500
  # Easy access to MapTiler Cloud API
393
501
  Our map SDK is not only about maps! We also provide plenty of wrapper to our API calls!
394
502
 
package/src/Map.ts CHANGED
@@ -5,6 +5,11 @@ import type {
5
5
  MapOptions as MapOptionsML,
6
6
  ControlPosition,
7
7
  StyleOptions,
8
+ MapDataEvent,
9
+ Tile,
10
+ RasterDEMSourceSpecification,
11
+ TerrainSpecification,
12
+ MapTerrainEvent,
8
13
  } from "maplibre-gl";
9
14
  import { v4 as uuidv4 } from "uuid";
10
15
  import { ReferenceMapStyle, MapStyleVariant } from "@maptiler/client";
@@ -27,6 +32,19 @@ import { AttributionControl } from "./AttributionControl";
27
32
  import { ScaleControl } from "./ScaleControl";
28
33
  import { FullscreenControl } from "./FullscreenControl";
29
34
 
35
+ function sleepAsync(ms: number) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+
39
+ export type LoadWithTerrainEvent = {
40
+ type: "loadWithTerrain";
41
+ target: Map;
42
+ terrain: {
43
+ source: string;
44
+ exaggeration: number;
45
+ };
46
+ };
47
+
30
48
  // StyleSwapOptions is not exported by Maplibre, but we can redefine it (used for setStyle)
31
49
  export type TransformStyleFunction = (
32
50
  previous: StyleSpecification,
@@ -48,6 +66,13 @@ export const GeolocationType: {
48
66
  COUNTRY: "COUNTRY",
49
67
  } as const;
50
68
 
69
+ type MapTerrainDataEvent = MapDataEvent & {
70
+ isSourceLoaded: boolean;
71
+ tile: Tile;
72
+ sourceId: string;
73
+ source: RasterDEMSourceSpecification;
74
+ };
75
+
51
76
  /**
52
77
  * Options to provide to the `Map` constructor
53
78
  */
@@ -132,7 +157,7 @@ export type MapOptions = Omit<MapOptionsML, "style" | "maplibreLogo"> & {
132
157
  *
133
158
  * Default: `false`
134
159
  */
135
- geolocate?: typeof GeolocationType[keyof typeof GeolocationType] | boolean;
160
+ geolocate?: (typeof GeolocationType)[keyof typeof GeolocationType] | boolean;
136
161
  };
137
162
 
138
163
  /**
@@ -143,6 +168,8 @@ export class Map extends maplibregl.Map {
143
168
  private terrainExaggeration = 1;
144
169
  private primaryLanguage: LanguageString | null = null;
145
170
  private secondaryLanguage: LanguageString | null = null;
171
+ private terrainGrowing = false;
172
+ private terrainFlattening = false;
146
173
 
147
174
  constructor(options: MapOptions) {
148
175
  if (options.apiKey) {
@@ -198,6 +225,8 @@ export class Map extends maplibregl.Map {
198
225
 
199
226
  this.primaryLanguage = options.language ?? config.primaryLanguage;
200
227
  this.secondaryLanguage = config.secondaryLanguage;
228
+ this.terrainExaggeration =
229
+ options.terrainExaggeration ?? this.terrainExaggeration;
201
230
 
202
231
  // Map centering and geolocation
203
232
  this.once("styledata", async () => {
@@ -239,6 +268,11 @@ export class Map extends maplibregl.Map {
239
268
  console.warn(e.message);
240
269
  }
241
270
 
271
+ // A more precise localization
272
+
273
+ // This more advanced localization is commented out because the easeTo animation
274
+ // triggers an error if the terrain grow is enabled (due to being nable to project the center while moving)
275
+
242
276
  // Then, the get a more precise location, we rely on the browser location, but only if it was already granted
243
277
  // before (we don't want to ask wih a popup at launch time)
244
278
  const locationResult = await navigator.permissions.query({
@@ -254,11 +288,21 @@ export class Map extends maplibregl.Map {
254
288
  return;
255
289
  }
256
290
 
257
- this.easeTo({
258
- center: [data.coords.longitude, data.coords.latitude],
259
- zoom: options.zoom || 12,
260
- duration: 2000,
261
- });
291
+ if (this.terrain) {
292
+ this.easeTo({
293
+ center: [data.coords.longitude, data.coords.latitude],
294
+ zoom: options.zoom || 12,
295
+ duration: 2000,
296
+ });
297
+ } else {
298
+ this.once("terrain", () => {
299
+ this.easeTo({
300
+ center: [data.coords.longitude, data.coords.latitude],
301
+ zoom: options.zoom || 12,
302
+ duration: 2000,
303
+ });
304
+ });
305
+ }
262
306
  },
263
307
 
264
308
  // error callback
@@ -422,6 +466,40 @@ export class Map extends maplibregl.Map {
422
466
  }
423
467
  });
424
468
 
469
+ // Creating a custom event: "loadWithTerrain"
470
+ // that fires only once when both:
471
+ // - the map has full loaded (corresponds to the the "load" event)
472
+ // - the terrain has loaded (corresponds to the "terrain" event with terrain beion non-null)
473
+ // This custom event is necessary to wait for when the map is instanciated with `terrain: true`
474
+ // and some animation (flyTo, easeTo) are running from the begining.
475
+ let loadEventTriggered = false;
476
+ let terrainEventTriggered = false;
477
+ let terrainEventData: LoadWithTerrainEvent = null;
478
+
479
+ this.once("load", (_) => {
480
+ loadEventTriggered = true;
481
+ if (terrainEventTriggered) {
482
+ this.fire("loadWithTerrain", terrainEventData);
483
+ }
484
+ });
485
+
486
+ const terrainCallback = (evt) => {
487
+ if (!evt.terrain) return;
488
+ terrainEventTriggered = true;
489
+ terrainEventData = {
490
+ type: "loadWithTerrain",
491
+ target: this,
492
+ terrain: evt.terrain,
493
+ };
494
+ this.off("terrain", terrainCallback);
495
+
496
+ if (loadEventTriggered) {
497
+ this.fire("loadWithTerrain", terrainEventData as LoadWithTerrainEvent);
498
+ }
499
+ };
500
+
501
+ this.on("terrain", terrainCallback);
502
+
425
503
  // enable 3D terrain if provided in options
426
504
  if (options.terrain) {
427
505
  this.enableTerrain(
@@ -430,6 +508,43 @@ export class Map extends maplibregl.Map {
430
508
  }
431
509
  }
432
510
 
511
+ /**
512
+ * Awaits for _this_ Map instance to be "loaded" and returns a Promise to the Map.
513
+ * If _this_ Map instance is already loaded, the Promise is resolved directly,
514
+ * otherwise, it is resolved as a result of the "load" event.
515
+ * @returns
516
+ */
517
+ async onLoadAsync() {
518
+ return new Promise<Map>((resolve, reject) => {
519
+ if (this.loaded()) {
520
+ return resolve(this);
521
+ }
522
+
523
+ this.once("load", (_) => {
524
+ resolve(this);
525
+ });
526
+ });
527
+ }
528
+
529
+ /**
530
+ * Awaits for _this_ Map instance to be "loaded" as well as with terrain being non-null for the first time
531
+ * and returns a Promise to the Map.
532
+ * If _this_ Map instance is already loaded with terrain, the Promise is resolved directly,
533
+ * otherwise, it is resolved as a result of the "loadWithTerrain" event.
534
+ * @returns
535
+ */
536
+ async onLoadWithTerrainAsync() {
537
+ return new Promise<Map>((resolve, reject) => {
538
+ if (this.loaded() && this.terrain) {
539
+ return resolve(this);
540
+ }
541
+
542
+ this.once("loadWithTerrain", (_) => {
543
+ resolve(this);
544
+ });
545
+ });
546
+ }
547
+
433
548
  /**
434
549
  * Update the style of the map.
435
550
  * Can be:
@@ -789,6 +904,54 @@ export class Map extends maplibregl.Map {
789
904
  return this.isTerrainEnabled;
790
905
  }
791
906
 
907
+ private growTerrain(exaggeration, durationMs = 1000) {
908
+ // This method assumes the terrain is already built
909
+ if (!this.terrain) {
910
+ return;
911
+ }
912
+
913
+ const startTime = performance.now();
914
+ // This is supposedly 0, but it could be something else (e.g. already in the middle of growing, or user defined other)
915
+ const currentExaggeration = this.terrain.exaggeration;
916
+ const deltaExaggeration = exaggeration - currentExaggeration;
917
+
918
+ // This is again called in a requestAnimationFrame ~loop, until the terrain has grown enough
919
+ // that it has reached the target
920
+ const updateExaggeration = () => {
921
+ if (!this.terrain) {
922
+ return;
923
+ }
924
+
925
+ // If the flattening animation is triggered while the growing animation
926
+ // is running, then the flattening animation is stopped
927
+ if (this.terrainFlattening) {
928
+ return;
929
+ }
930
+
931
+ // normalized value in interval [0, 1] of where we are currently in the animation loop
932
+ const positionInLoop = (performance.now() - startTime) / durationMs;
933
+
934
+ // The animation goes on until we reached 99% of the growing sequence duration
935
+ if (positionInLoop < 0.99) {
936
+ const exaggerationFactor = 1 - Math.pow(1 - positionInLoop, 4);
937
+ const newExaggeration =
938
+ currentExaggeration + exaggerationFactor * deltaExaggeration;
939
+ this.terrain.exaggeration = newExaggeration;
940
+ requestAnimationFrame(updateExaggeration);
941
+ } else {
942
+ this.terrainGrowing = false;
943
+ this.terrainFlattening = false;
944
+ this.terrain.exaggeration = exaggeration;
945
+ }
946
+
947
+ this.triggerRepaint();
948
+ };
949
+
950
+ this.terrainGrowing = true;
951
+ this.terrainFlattening = false;
952
+ requestAnimationFrame(updateExaggeration);
953
+ }
954
+
792
955
  /**
793
956
  * Enables the 3D terrain visualization
794
957
  * @param exaggeration
@@ -800,27 +963,72 @@ export class Map extends maplibregl.Map {
800
963
  return;
801
964
  }
802
965
 
803
- const terrainInfo = this.getTerrain();
966
+ // This function is mapped to a map "data" event. It checks that the terrain
967
+ // tiles are loaded and when so, it starts an animation to make the terrain grow
968
+ const dataEventTerrainGrow = async (evt: MapTerrainDataEvent) => {
969
+ if (!this.terrain) {
970
+ return;
971
+ }
972
+
973
+ if (
974
+ evt.type !== "data" ||
975
+ evt.dataType !== "source" ||
976
+ !("source" in evt)
977
+ ) {
978
+ return;
979
+ }
980
+
981
+ if (evt.sourceId !== "maptiler-terrain") {
982
+ return;
983
+ }
984
+
985
+ const source = evt.source;
804
986
 
987
+ if (source.type !== "raster-dem") {
988
+ return;
989
+ }
990
+
991
+ if (!evt.isSourceLoaded) {
992
+ return;
993
+ }
994
+
995
+ // We shut this event off because we want it to happen only once.
996
+ // Yet, we cannot use the "once" method because only the last event of the series
997
+ // has `isSourceLoaded` true
998
+ this.off("data", dataEventTerrainGrow);
999
+
1000
+ this.growTerrain(exaggeration);
1001
+ };
1002
+
1003
+ // This is put into a function so that it can be called regardless
1004
+ // of the loading state of _this_ the map instance
805
1005
  const addTerrain = () => {
806
1006
  // When style is changed,
807
1007
  this.isTerrainEnabled = true;
808
1008
  this.terrainExaggeration = exaggeration;
809
1009
 
1010
+ // Mapping it to the "data" event so that we can check that the terrain
1011
+ // growing starts only when terrain tiles are loaded (to reduce glitching)
1012
+ this.on("data", dataEventTerrainGrow);
1013
+
810
1014
  this.addSource(defaults.terrainSourceId, {
811
1015
  type: "raster-dem",
812
1016
  url: defaults.terrainSourceURL,
813
1017
  });
1018
+
1019
+ // Setting up the terrain with a 0 exaggeration factor
1020
+ // so it loads ~seamlessly and then can grow from there
814
1021
  this.setTerrain({
815
1022
  source: defaults.terrainSourceId,
816
- exaggeration: exaggeration,
1023
+ exaggeration: 0,
817
1024
  });
818
1025
  };
819
1026
 
820
1027
  // The terrain has already been loaded,
821
1028
  // we just update the exaggeration.
822
- if (terrainInfo) {
823
- this.setTerrain({ ...terrainInfo, exaggeration });
1029
+ if (this.getTerrain()) {
1030
+ this.isTerrainEnabled = true;
1031
+ this.growTerrain(exaggeration);
824
1032
  return;
825
1033
  }
826
1034
 
@@ -840,44 +1048,80 @@ export class Map extends maplibregl.Map {
840
1048
  * Disable the 3D terrain visualization
841
1049
  */
842
1050
  disableTerrain() {
843
- this.isTerrainEnabled = false;
844
- this.setTerrain(null);
845
- if (this.getSource(defaults.terrainSourceId)) {
846
- this.removeSource(defaults.terrainSourceId);
1051
+ // It could be disabled already
1052
+ if (!this.terrain) {
1053
+ return;
847
1054
  }
1055
+
1056
+ this.isTerrainEnabled = false;
1057
+ // this.stopFlattening = false;
1058
+
1059
+ // Duration of the animation in millisec
1060
+ const animationLoopDuration = 1 * 1000;
1061
+ const startTime = performance.now();
1062
+ // This is supposedly 0, but it could be something else (e.g. already in the middle of growing, or user defined other)
1063
+ const currentExaggeration = this.terrain.exaggeration;
1064
+
1065
+ // This is again called in a requestAnimationFrame ~loop, until the terrain has grown enough
1066
+ // that it has reached the target
1067
+ const updateExaggeration = () => {
1068
+ if (!this.terrain) {
1069
+ return;
1070
+ }
1071
+
1072
+ // If the growing animation is triggered while flattening,
1073
+ // then we exist the flatening
1074
+ if (this.terrainGrowing) {
1075
+ return;
1076
+ }
1077
+
1078
+ // normalized value in interval [0, 1] of where we are currently in the animation loop
1079
+ const positionInLoop =
1080
+ (performance.now() - startTime) / animationLoopDuration;
1081
+
1082
+ // The animation goes on until we reached 99% of the growing sequence duration
1083
+ if (positionInLoop < 0.99) {
1084
+ const exaggerationFactor = Math.pow(1 - positionInLoop, 4);
1085
+ const newExaggeration = currentExaggeration * exaggerationFactor;
1086
+ this.terrain.exaggeration = newExaggeration;
1087
+ requestAnimationFrame(updateExaggeration);
1088
+ } else {
1089
+ this.terrain.exaggeration = 0;
1090
+ this.terrainGrowing = false;
1091
+ this.terrainFlattening = false;
1092
+ this.setTerrain(null);
1093
+ if (this.getSource(defaults.terrainSourceId)) {
1094
+ this.removeSource(defaults.terrainSourceId);
1095
+ }
1096
+ }
1097
+
1098
+ this.triggerRepaint();
1099
+ };
1100
+
1101
+ this.terrainGrowing = false;
1102
+ this.terrainFlattening = true;
1103
+ requestAnimationFrame(updateExaggeration);
848
1104
  }
849
1105
 
850
1106
  /**
851
1107
  * Sets the 3D terrain exageration factor.
852
- * Note: this is only a shortcut to `.enableTerrain()`
1108
+ * If the terrain was not enabled prior to the call of this method,
1109
+ * the method `.enableTerrain()` will be called.
1110
+ * If `animate` is `true`, the terrain transformation will be animated in the span of 1 second.
1111
+ * If `animate` is `false`, no animated transition to the newly defined exaggeration.
853
1112
  * @param exaggeration
1113
+ * @param animate
854
1114
  */
855
- setTerrainExaggeration(exaggeration: number) {
856
- this.enableTerrain(exaggeration);
1115
+ setTerrainExaggeration(exaggeration: number, animate = true) {
1116
+ if (!animate && this.terrain) {
1117
+ this.terrainExaggeration = exaggeration;
1118
+ this.terrain.exaggeration = exaggeration;
1119
+ this.triggerRepaint();
1120
+ } else {
1121
+ this.enableTerrain(exaggeration);
1122
+ }
857
1123
  }
858
1124
 
859
- // getLanguages() {
860
- // const layers = this.getStyle().layers;
861
-
862
- // for (let i = 0; i < layers.length; i += 1) {
863
- // const layer = layers[i];
864
- // const layout = layer.layout;
865
-
866
- // if (!layout) {
867
- // continue;
868
- // }
869
-
870
- // if (!layout["text-field"]) {
871
- // continue;
872
- // }
873
-
874
- // const textFieldLayoutProp = this.getLayoutProperty(
875
- // layer.id,
876
- // "text-field"
877
- // );
878
- // }
879
- // }
880
-
881
1125
  /**
882
1126
  * Perform an action when the style is ready. It could be at the moment of calling this method
883
1127
  * or later.