-
-
Notifications
You must be signed in to change notification settings - Fork 735
/
map.ts
3496 lines (3215 loc) · 143 KB
/
map.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import {extend, warnOnce, uniqueId, isImageBitmap, type Complete, pick, type Subscription} from '../util/util';
import {browser} from '../util/browser';
import {DOM} from '../util/dom';
import packageJSON from '../../package.json' with {type: 'json'};
import {type GetResourceResponse, getJSON} from '../util/ajax';
import {ImageRequest} from '../util/image_request';
import {RequestManager, ResourceType} from '../util/request_manager';
import {Style, type StyleSwapOptions} from '../style/style';
import {EvaluationParameters} from '../style/evaluation_parameters';
import {Painter} from '../render/painter';
import {Hash} from './hash';
import {HandlerManager} from './handler_manager';
import {Camera, type CameraOptions, type CameraUpdateTransformFunction, type FitBoundsOptions} from './camera';
import {LngLat} from '../geo/lng_lat';
import {LngLatBounds} from '../geo/lng_lat_bounds';
import Point from '@mapbox/point-geometry';
import {AttributionControl, type AttributionControlOptions, defaultAttributionControlOptions} from './control/attribution_control';
import {LogoControl} from './control/logo_control';
import {RGBAImage} from '../util/image';
import {Event, ErrorEvent, type Listener} from '../util/evented';
import {type MapEventType, type MapLayerEventType, MapMouseEvent, type MapSourceDataEvent, type MapStyleDataEvent} from './events';
import {TaskQueue} from '../util/task_queue';
import {throttle} from '../util/throttle';
import {webpSupported} from '../util/webp_supported';
import {PerformanceMarkers, PerformanceUtils} from '../util/performance';
import {type Source} from '../source/source';
import {type StyleLayer} from '../style/style_layer';
import {Terrain} from '../render/terrain';
import {RenderToTexture} from '../render/render_to_texture';
import {config} from '../util/config';
import {defaultLocale} from './default_locale';
import type {RequestTransformFunction} from '../util/request_manager';
import type {LngLatLike} from '../geo/lng_lat';
import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
import type {AddLayerObject, FeatureIdentifier, StyleOptions, StyleSetterOptions} from '../style/style';
import type {MapDataEvent} from './events';
import type {StyleImage, StyleImageInterface, StyleImageMetadata} from '../style/style_image';
import type {PointLike} from './camera';
import type {ScrollZoomHandler} from './handler/scroll_zoom';
import type {BoxZoomHandler} from './handler/box_zoom';
import type {AroundCenterOptions, TwoFingersTouchPitchHandler} from './handler/two_fingers_touch';
import type {DragRotateHandler} from './handler/shim/drag_rotate';
import type {DragPanHandler, DragPanOptions} from './handler/shim/drag_pan';
import type {CooperativeGesturesHandler, GestureOptions} from './handler/cooperative_gestures';
import type {KeyboardHandler} from './handler/keyboard';
import type {DoubleClickZoomHandler} from './handler/shim/dblclick_zoom';
import type {TwoFingersTouchZoomRotateHandler} from './handler/shim/two_fingers_touch';
import type {TaskID} from '../util/task_queue';
import type {
FilterSpecification,
StyleSpecification,
LightSpecification,
SourceSpecification,
TerrainSpecification,
ProjectionSpecification,
SkySpecification
} from '@maplibre/maplibre-gl-style-spec';
import type {CanvasSourceSpecification} from '../source/canvas_source';
import type {MapGeoJSONFeature} from '../util/vectortile_to_geojson';
import type {ControlPosition, IControl} from './control/control';
import type {QueryRenderedFeaturesOptions, QuerySourceFeatureOptions} from '../source/query_features';
import {MercatorTransform} from '../geo/projection/mercator_transform';
import {type ITransform} from '../geo/transform_interface';
import {type ICameraHelper} from '../geo/projection/camera_helper';
import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper';
const version = packageJSON.version;
/**
* The {@link Map} options object.
*/
export type MapOptions = {
/**
* If `true`, the map's position (zoom, center latitude, center longitude, bearing, and pitch) will be synced with the hash fragment of the page's URL.
* For example, `http://path/to/my/page.html#2.59/39.26/53.07/-24.1/60`.
* An additional string may optionally be provided to indicate a parameter-styled hash,
* e.g. http://path/to/my/page.html#map=2.59/39.26/53.07/-24.1/60&foo=bar, where foo
* is a custom parameter and bar is an arbitrary hash distinct from the map hash.
* @defaultValue false
*/
hash?: boolean | string;
/**
* If `false`, no mouse, touch, or keyboard listeners will be attached to the map, so it will not respond to interaction.
* @defaultValue true
*/
interactive?: boolean;
/**
* The HTML element in which MapLibre GL JS will render the map, or the element's string `id`. The specified element must have no children.
*/
container: HTMLElement | string;
/**
* The threshold, measured in degrees, that determines when the map's
* bearing will snap to north. For example, with a `bearingSnap` of 7, if the user rotates
* the map within 7 degrees of north, the map will automatically snap to exact north.
* @defaultValue 7
*/
bearingSnap?: number;
/**
* If set, an {@link AttributionControl} will be added to the map with the provided options.
* To disable the attribution control, pass `false`.
* Note: showing the logo of MapLibre is not required for using MapLibre.
* @defaultValue compact: true, customAttribution: "MapLibre ...".
*/
attributionControl?: false | AttributionControlOptions;
/**
* If `true`, the MapLibre logo will be shown.
*/
maplibreLogo?: boolean;
/**
* A string representing the position of the MapLibre wordmark on the map. Valid options are `top-left`,`top-right`, `bottom-left`, or `bottom-right`.
* @defaultValue 'bottom-left'
*/
logoPosition?: ControlPosition;
/**
* If `true`, map creation will fail if the performance of MapLibre GL JS would be dramatically worse than expected
* (i.e. a software renderer would be used).
* @defaultValue false
*/
failIfMajorPerformanceCaveat?: boolean;
/**
* If `true`, the map's canvas can be exported to a PNG using `map.getCanvas().toDataURL()`. This is `false` by default as a performance optimization.
* @defaultValue false
*/
preserveDrawingBuffer?: boolean;
/**
* If `true`, the gl context will be created with MSAA antialiasing, which can be useful for antialiasing custom layers.
* Disabled by default as a performance optimization.
*/
antialias?: boolean;
/**
* If `false`, the map won't attempt to re-request tiles once they expire per their HTTP `cacheControl`/`expires` headers.
* @defaultValue true
*/
refreshExpiredTiles?: boolean;
/**
* If set, the map will be constrained to the given bounds.
*/
maxBounds?: LngLatBoundsLike;
/**
* If `true`, the "scroll to zoom" interaction is enabled. {@link AroundCenterOptions} are passed as options to {@link ScrollZoomHandler#enable}.
* @defaultValue true
*/
scrollZoom?: boolean | AroundCenterOptions;
/**
* The minimum zoom level of the map (0-24).
* @defaultValue 0
*/
minZoom?: number | null;
/**
* The maximum zoom level of the map (0-24).
* @defaultValue 22
*/
maxZoom?: number | null;
/**
* The minimum pitch of the map (0-180).
* @defaultValue 0
*/
minPitch?: number | null;
/**
* The maximum pitch of the map (0-180).
* @defaultValue 60
*/
maxPitch?: number | null;
/**
* If `true`, the "box zoom" interaction is enabled (see {@link BoxZoomHandler}).
* @defaultValue true
*/
boxZoom?: boolean;
/**
* If `true`, the "drag to rotate" interaction is enabled (see {@link DragRotateHandler}).
* @defaultValue true
*/
dragRotate?: boolean;
/**
* If `true`, the "drag to pan" interaction is enabled. An `Object` value is passed as options to {@link DragPanHandler#enable}.
* @defaultValue true
*/
dragPan?: boolean | DragPanOptions;
/**
* If `true`, keyboard shortcuts are enabled (see {@link KeyboardHandler}).
* @defaultValue true
*/
keyboard?: boolean;
/**
* If `true`, the "double click to zoom" interaction is enabled (see {@link DoubleClickZoomHandler}).
* @defaultValue true
*/
doubleClickZoom?: boolean;
/**
* If `true`, the "pinch to rotate and zoom" interaction is enabled. An `Object` value is passed as options to {@link TwoFingersTouchZoomRotateHandler#enable}.
* @defaultValue true
*/
touchZoomRotate?: boolean | AroundCenterOptions;
/**
* If `true`, the "drag to pitch" interaction is enabled. An `Object` value is passed as options to {@link TwoFingersTouchPitchHandler#enable}.
* @defaultValue true
*/
touchPitch?: boolean | AroundCenterOptions;
/**
* If `true` or set to an options object, the map is only accessible on desktop while holding Command/Ctrl and only accessible on mobile with two fingers. Interacting with the map using normal gestures will trigger an informational screen. With this option enabled, "drag to pitch" requires a three-finger gesture. Cooperative gestures are disabled when a map enters fullscreen using {@link FullscreenControl}.
* @defaultValue false
*/
cooperativeGestures?: GestureOptions;
/**
* If `true`, the map will automatically resize when the browser window resizes.
* @defaultValue true
*/
trackResize?: boolean;
/**
* The initial geographical centerpoint of the map. If `center` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `[0, 0]` Note: MapLibre GL JS uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match GeoJSON.
* @defaultValue [0, 0]
*/
center?: LngLatLike;
/**
* The elevation of the initial geographical centerpoint of the map, in meters above sea level. If `elevation` is not specified in the constructor options, it will default to `0`.
* @defaultValue 0
*/
elevation?: number;
/**
* The initial zoom level of the map. If `zoom` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`.
* @defaultValue 0
*/
zoom?: number;
/**
* The initial bearing (rotation) of the map, measured in degrees counter-clockwise from north. If `bearing` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`.
* @defaultValue 0
*/
bearing?: number;
/**
* The initial pitch (tilt) of the map, measured in degrees away from the plane of the screen (0-85). If `pitch` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`. Values greater than 60 degrees are experimental and may result in rendering issues. If you encounter any, please raise an issue with details in the MapLibre project.
* @defaultValue 0
*/
pitch?: number;
/**
* The initial roll angle of the map, measured in degrees counter-clockwise about the camera boresight. If `roll` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`.
* @defaultValue 0
*/
roll?: number;
/**
* If `true`, multiple copies of the world will be rendered side by side beyond -180 and 180 degrees longitude. If set to `false`:
*
* - When the map is zoomed out far enough that a single representation of the world does not fill the map's entire
* container, there will be blank space beyond 180 and -180 degrees longitude.
* - Features that cross 180 and -180 degrees longitude will be cut in two (with one portion on the right edge of the
* map and the other on the left edge of the map) at every zoom level.
* @defaultValue true
*/
renderWorldCopies?: boolean;
/**
* The maximum number of tiles stored in the tile cache for a given source. If omitted, the cache will be dynamically sized based on the current viewport which can be set using `maxTileCacheZoomLevels` constructor options.
* @defaultValue null
*/
maxTileCacheSize?: number | null;
/**
* The maximum number of zoom levels for which to store tiles for a given source. Tile cache dynamic size is calculated by multiplying `maxTileCacheZoomLevels` with the approximate number of tiles in the viewport for a given source.
* @defaultValue 5
*/
maxTileCacheZoomLevels?: number;
/**
* A callback run before the Map makes a request for an external URL. The callback can be used to modify the url, set headers, or set the credentials property for cross-origin requests.
* Expected to return an object with a `url` property and optionally `headers` and `credentials` properties.
* @defaultValue null
*/
transformRequest?: RequestTransformFunction | null;
/**
* A callback run before the map's camera is moved due to user input or animation. The callback can be used to modify the new center, zoom, pitch and bearing.
* Expected to return an object containing center, zoom, pitch or bearing values to overwrite.
* @defaultValue null
*/
transformCameraUpdate?: CameraUpdateTransformFunction | null;
/**
* A patch to apply to the default localization table for UI strings, e.g. control tooltips. The `locale` object maps namespaced UI string IDs to translated strings in the target language; see `src/ui/default_locale.js` for an example with all supported string IDs. The object may specify all UI strings (thereby adding support for a new translation) or only a subset of strings (thereby patching the default translation table).
* @defaultValue null
*/
locale?: any;
/**
* Controls the duration of the fade-in/fade-out animation for label collisions after initial map load, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading.
* @defaultValue 300
*/
fadeDuration?: number;
/**
* If `true`, symbols from multiple sources can collide with each other during collision detection. If `false`, collision detection is run separately for the symbols in each source.
* @defaultValue true
*/
crossSourceCollisions?: boolean;
/**
* If `true`, Resource Timing API information will be collected for requests made by GeoJSON and Vector Tile web workers (this information is normally inaccessible from the main Javascript thread). Information will be returned in a `resourceTiming` property of relevant `data` events.
* @defaultValue false
*/
collectResourceTiming?: boolean;
/**
* The max number of pixels a user can shift the mouse pointer during a click for it to be considered a valid click (as opposed to a mouse drag).
* @defaultValue 3
*/
clickTolerance?: number;
/**
* The initial bounds of the map. If `bounds` is specified, it overrides `center` and `zoom` constructor options.
*/
bounds?: LngLatBoundsLike;
/**
* A {@link FitBoundsOptions} options object to use _only_ when fitting the initial `bounds` provided above.
*/
fitBoundsOptions?: FitBoundsOptions;
/**
* Defines a CSS
* font-family for locally overriding generation of Chinese, Japanese, and Korean characters.
* For these characters, font settings from the map's style will be ignored, except for font-weight keywords (light/regular/medium/bold).
* Set to `false`, to enable font settings from the map's style for these glyph ranges.
* The purpose of this option is to avoid bandwidth-intensive glyph server requests. (See [Use locally generated ideographs](https://maplibre.org/maplibre-gl-js/docs/examples/local-ideographs).)
* @defaultValue 'sans-serif'
*/
localIdeographFontFamily?: string | false;
/**
* The map's MapLibre style. This must be a JSON object conforming to
* the schema described in the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/),
* or a URL to such JSON.
* When the style is not specified, calling {@link Map#setStyle} is required to render the map.
*/
style?: StyleSpecification | string;
/**
* If `false`, the map's pitch (tilt) control with "drag to rotate" interaction will be disabled.
* @defaultValue true
*/
pitchWithRotate?: boolean;
/**
* If `false`, the map's roll control with "drag to rotate" interaction will be disabled.
* @defaultValue false
*/
rollEnabled?: boolean;
/**
* The pixel ratio.
* The canvas' `width` attribute will be `container.clientWidth * pixelRatio` and its `height` attribute will be `container.clientHeight * pixelRatio`. Defaults to `devicePixelRatio` if not specified.
*/
pixelRatio?: number;
/**
* If false, style validation will be skipped. Useful in production environment.
* @defaultValue true
*/
validateStyle?: boolean;
/**
* The canvas' `width` and `height` max size. The values are passed as an array where the first element is max width and the second element is max height.
* You shouldn't set this above WebGl `MAX_TEXTURE_SIZE`.
* @defaultValue [4096, 4096].
*/
maxCanvasSize?: [number, number];
/**
* Determines whether to cancel, or retain, tiles from the current viewport which are still loading but which belong to a farther (smaller) zoom level than the current one.
* * If `true`, when zooming in, tiles which didn't manage to load for previous zoom levels will become canceled. This might save some computing resources for slower devices, but the map details might appear more abruptly at the end of the zoom.
* * If `false`, when zooming in, the previous zoom level(s) tiles will progressively appear, giving a smoother map details experience. However, more tiles will be rendered in a short period of time.
* @defaultValue true
*/
cancelPendingTileRequestsWhileZooming?: boolean;
/**
* If true, the elevation of the center point will automatically be set to the terrain elevation
* (or zero if terrain is not enabled). If false, the elevation of the center point will default
* to sea level and will not automatically update. Defaults to true. Needs to be set to false to
* keep the camera above ground when pitch \> 90 degrees.
*/
centerClampedToGround?: boolean;
};
export type AddImageOptions = {
}
// This type is used inside map since all properties are assigned a default value.
export type CompleteMapOptions = Complete<MapOptions>;
type DelegatedListener = {
layers: string[];
listener: Listener;
delegates: {[E in keyof MapEventType]?: Delegate<MapEventType[E]>};
}
type Delegate<E extends Event = Event> = (e: E) => void;
const defaultMinZoom = -2;
const defaultMaxZoom = 22;
// the default values, but also the valid range
const defaultMinPitch = 0;
const defaultMaxPitch = 60;
// use this variable to check maxPitch for validity
const maxPitchThreshold = 180;
const defaultOptions: Readonly<Partial<MapOptions>> = {
hash: false,
interactive: true,
bearingSnap: 7,
attributionControl: defaultAttributionControlOptions,
maplibreLogo: false,
failIfMajorPerformanceCaveat: false,
preserveDrawingBuffer: false,
refreshExpiredTiles: true,
scrollZoom: true,
minZoom: defaultMinZoom,
maxZoom: defaultMaxZoom,
minPitch: defaultMinPitch,
maxPitch: defaultMaxPitch,
boxZoom: true,
dragRotate: true,
dragPan: true,
keyboard: true,
doubleClickZoom: true,
touchZoomRotate: true,
touchPitch: true,
cooperativeGestures: false,
trackResize: true,
center: [0, 0],
elevation: 0,
zoom: 0,
bearing: 0,
pitch: 0,
roll: 0,
renderWorldCopies: true,
maxTileCacheSize: null,
maxTileCacheZoomLevels: config.MAX_TILE_CACHE_ZOOM_LEVELS,
transformRequest: null,
transformCameraUpdate: null,
fadeDuration: 300,
crossSourceCollisions: true,
clickTolerance: 3,
localIdeographFontFamily: 'sans-serif',
pitchWithRotate: true,
rollEnabled: false,
validateStyle: true,
/**Because GL MAX_TEXTURE_SIZE is usually at least 4096px. */
maxCanvasSize: [4096, 4096],
cancelPendingTileRequestsWhileZooming: true,
centerClampedToGround: true
};
/**
* The `Map` object represents the map on your page. It exposes methods
* and properties that enable you to programmatically change the map,
* and fires events as users interact with it.
*
* You create a `Map` by specifying a `container` and other options, see {@link MapOptions} for the full list.
* Then MapLibre GL JS initializes the map on the page and returns your `Map` object.
*
* @group Main
*
* @example
* ```ts
* let map = new Map({
* container: 'map',
* center: [-122.420679, 37.772537],
* zoom: 13,
* style: style_object,
* hash: true,
* transformRequest: (url, resourceType)=> {
* if(resourceType === 'Source' && url.startsWith('http://myHost')) {
* return {
* url: url.replace('http', 'https'),
* headers: { 'my-custom-header': true},
* credentials: 'include' // Include cookies for cross-origin requests
* }
* }
* }
* });
* ```
* @see [Display a map](https://maplibre.org/maplibre-gl-js/docs/examples/simple-map/)
*/
export class Map extends Camera {
style: Style;
painter: Painter;
handlers: HandlerManager;
_container: HTMLElement;
_canvasContainer: HTMLElement;
_controlContainer: HTMLElement;
_controlPositions: Record<string, HTMLElement>;
_interactive: boolean;
_showTileBoundaries: boolean;
_showCollisionBoxes: boolean;
_showPadding: boolean;
_showOverdrawInspector: boolean;
_repaint: boolean;
_vertices: boolean;
_canvas: HTMLCanvasElement;
_maxTileCacheSize: number | null;
_maxTileCacheZoomLevels: number;
_frameRequest: AbortController;
_styleDirty: boolean;
_sourcesDirty: boolean;
_placementDirty: boolean;
_loaded: boolean;
_idleTriggered = false;
// accounts for placement finishing as well
_fullyLoaded: boolean;
_trackResize: boolean;
_resizeObserver: ResizeObserver;
_preserveDrawingBuffer: boolean;
_failIfMajorPerformanceCaveat: boolean;
_antialias: boolean;
_refreshExpiredTiles: boolean;
_hash: Hash;
_delegatedListeners: Record<string, DelegatedListener[]>;
_fadeDuration: number;
_crossSourceCollisions: boolean;
_crossFadingFactor = 1;
_collectResourceTiming: boolean;
_renderTaskQueue = new TaskQueue();
_controls: Array<IControl> = [];
_mapId = uniqueId();
_localIdeographFontFamily: string | false;
_validateStyle: boolean;
_requestManager: RequestManager;
_locale: typeof defaultLocale;
_removed: boolean;
_clickTolerance: number;
_overridePixelRatio: number | null | undefined;
_maxCanvasSize: [number, number];
_terrainDataCallback: (e: MapStyleDataEvent | MapSourceDataEvent) => void;
/**
* @internal
* image queue throttling handle. To be used later when clean up
*/
_imageQueueHandle: number;
/**
* The map's {@link ScrollZoomHandler}, which implements zooming in and out with a scroll wheel or trackpad.
* Find more details and examples using `scrollZoom` in the {@link ScrollZoomHandler} section.
*/
scrollZoom: ScrollZoomHandler;
/**
* The map's {@link BoxZoomHandler}, which implements zooming using a drag gesture with the Shift key pressed.
* Find more details and examples using `boxZoom` in the {@link BoxZoomHandler} section.
*/
boxZoom: BoxZoomHandler;
/**
* The map's {@link DragRotateHandler}, which implements rotating the map while dragging with the right
* mouse button or with the Control key pressed. Find more details and examples using `dragRotate`
* in the {@link DragRotateHandler} section.
*/
dragRotate: DragRotateHandler;
/**
* The map's {@link DragPanHandler}, which implements dragging the map with a mouse or touch gesture.
* Find more details and examples using `dragPan` in the {@link DragPanHandler} section.
*/
dragPan: DragPanHandler;
/**
* The map's {@link KeyboardHandler}, which allows the user to zoom, rotate, and pan the map using keyboard
* shortcuts. Find more details and examples using `keyboard` in the {@link KeyboardHandler} section.
*/
keyboard: KeyboardHandler;
/**
* The map's {@link DoubleClickZoomHandler}, which allows the user to zoom by double clicking.
* Find more details and examples using `doubleClickZoom` in the {@link DoubleClickZoomHandler} section.
*/
doubleClickZoom: DoubleClickZoomHandler;
/**
* The map's {@link TwoFingersTouchZoomRotateHandler}, which allows the user to zoom or rotate the map with touch gestures.
* Find more details and examples using `touchZoomRotate` in the {@link TwoFingersTouchZoomRotateHandler} section.
*/
touchZoomRotate: TwoFingersTouchZoomRotateHandler;
/**
* The map's {@link TwoFingersTouchPitchHandler}, which allows the user to pitch the map with touch gestures.
* Find more details and examples using `touchPitch` in the {@link TwoFingersTouchPitchHandler} section.
*/
touchPitch: TwoFingersTouchPitchHandler;
/**
* The map's {@link CooperativeGesturesHandler}, which allows the user to see cooperative gesture info when user tries to zoom in/out.
* Find more details and examples using `cooperativeGestures` in the {@link CooperativeGesturesHandler} section.
*/
cooperativeGestures: CooperativeGesturesHandler;
/**
* The map's property which determines whether to cancel, or retain, tiles from the current viewport which are still loading but which belong to a farther (smaller) zoom level than the current one.
* * If `true`, when zooming in, tiles which didn't manage to load for previous zoom levels will become canceled. This might save some computing resources for slower devices, but the map details might appear more abruptly at the end of the zoom.
* * If `false`, when zooming in, the previous zoom level(s) tiles will progressively appear, giving a smoother map details experience. However, more tiles will be rendered in a short period of time.
* @defaultValue true
*/
cancelPendingTileRequestsWhileZooming: boolean;
constructor(options: MapOptions) {
PerformanceUtils.mark(PerformanceMarkers.create);
const resolvedOptions = {...defaultOptions, ...options} as CompleteMapOptions;
if (resolvedOptions.minZoom != null && resolvedOptions.maxZoom != null && resolvedOptions.minZoom > resolvedOptions.maxZoom) {
throw new Error('maxZoom must be greater than or equal to minZoom');
}
if (resolvedOptions.minPitch != null && resolvedOptions.maxPitch != null && resolvedOptions.minPitch > resolvedOptions.maxPitch) {
throw new Error('maxPitch must be greater than or equal to minPitch');
}
if (resolvedOptions.minPitch != null && resolvedOptions.minPitch < defaultMinPitch) {
throw new Error(`minPitch must be greater than or equal to ${defaultMinPitch}`);
}
if (resolvedOptions.maxPitch != null && resolvedOptions.maxPitch > maxPitchThreshold) {
throw new Error(`maxPitch must be less than or equal to ${maxPitchThreshold}`);
}
// For now we will use a temporary MercatorTransform instance.
// Transform specialization will later be set by style when it creates its projection instance.
// When this happens, the new transform will inherit all properties of this temporary transform.
const transform = new MercatorTransform();
const cameraHelper = new MercatorCameraHelper();
if (resolvedOptions.minZoom !== undefined) {
transform.setMinZoom(resolvedOptions.minZoom);
}
if (resolvedOptions.maxZoom !== undefined) {
transform.setMaxZoom(resolvedOptions.maxZoom);
}
if (resolvedOptions.minPitch !== undefined) {
transform.setMinPitch(resolvedOptions.minPitch);
}
if (resolvedOptions.maxPitch !== undefined) {
transform.setMaxPitch(resolvedOptions.maxPitch);
}
if (resolvedOptions.renderWorldCopies !== undefined) {
transform.setRenderWorldCopies(resolvedOptions.renderWorldCopies);
}
super(transform, cameraHelper, {bearingSnap: resolvedOptions.bearingSnap});
this._interactive = resolvedOptions.interactive;
this._maxTileCacheSize = resolvedOptions.maxTileCacheSize;
this._maxTileCacheZoomLevels = resolvedOptions.maxTileCacheZoomLevels;
this._failIfMajorPerformanceCaveat = resolvedOptions.failIfMajorPerformanceCaveat === true;
this._preserveDrawingBuffer = resolvedOptions.preserveDrawingBuffer === true;
this._antialias = resolvedOptions.antialias === true;
this._trackResize = resolvedOptions.trackResize === true;
this._bearingSnap = resolvedOptions.bearingSnap;
this._centerClampedToGround = resolvedOptions.centerClampedToGround;
this._refreshExpiredTiles = resolvedOptions.refreshExpiredTiles === true;
this._fadeDuration = resolvedOptions.fadeDuration;
this._crossSourceCollisions = resolvedOptions.crossSourceCollisions === true;
this._collectResourceTiming = resolvedOptions.collectResourceTiming === true;
this._locale = {...defaultLocale, ...resolvedOptions.locale};
this._clickTolerance = resolvedOptions.clickTolerance;
this._overridePixelRatio = resolvedOptions.pixelRatio;
this._maxCanvasSize = resolvedOptions.maxCanvasSize;
this.transformCameraUpdate = resolvedOptions.transformCameraUpdate;
this.cancelPendingTileRequestsWhileZooming = resolvedOptions.cancelPendingTileRequestsWhileZooming === true;
this._imageQueueHandle = ImageRequest.addThrottleControl(() => this.isMoving());
this._requestManager = new RequestManager(resolvedOptions.transformRequest);
if (typeof resolvedOptions.container === 'string') {
this._container = document.getElementById(resolvedOptions.container);
if (!this._container) {
throw new Error(`Container '${resolvedOptions.container}' not found.`);
}
} else if (resolvedOptions.container instanceof HTMLElement) {
this._container = resolvedOptions.container;
} else {
throw new Error('Invalid type: \'container\' must be a String or HTMLElement.');
}
if (resolvedOptions.maxBounds) {
this.setMaxBounds(resolvedOptions.maxBounds);
}
this._setupContainer();
this._setupPainter();
this.on('move', () => this._update(false));
this.on('moveend', () => this._update(false));
this.on('zoom', () => this._update(true));
this.on('terrain', () => {
this.painter.terrainFacilitator.dirty = true;
this._update(true);
});
this.once('idle', () => { this._idleTriggered = true; });
if (typeof window !== 'undefined') {
addEventListener('online', this._onWindowOnline, false);
let initialResizeEventCaptured = false;
const throttledResizeCallback = throttle((entries: ResizeObserverEntry[]) => {
if (this._trackResize && !this._removed) {
this.resize(entries);
this.redraw();
}
}, 50);
this._resizeObserver = new ResizeObserver((entries) => {
if (!initialResizeEventCaptured) {
initialResizeEventCaptured = true;
return;
}
throttledResizeCallback(entries);
});
this._resizeObserver.observe(this._container);
}
this.handlers = new HandlerManager(this, resolvedOptions);
const hashName = (typeof resolvedOptions.hash === 'string' && resolvedOptions.hash) || undefined;
this._hash = resolvedOptions.hash && (new Hash(hashName)).addTo(this);
// don't set position from options if set through hash
if (!this._hash || !this._hash._onHashChange()) {
this.jumpTo({
center: resolvedOptions.center,
elevation: resolvedOptions.elevation,
zoom: resolvedOptions.zoom,
bearing: resolvedOptions.bearing,
pitch: resolvedOptions.pitch,
roll: resolvedOptions.roll
});
if (resolvedOptions.bounds) {
this.resize();
this.fitBounds(resolvedOptions.bounds, extend({}, resolvedOptions.fitBoundsOptions, {duration: 0}));
}
}
this.resize();
this._localIdeographFontFamily = resolvedOptions.localIdeographFontFamily;
this._validateStyle = resolvedOptions.validateStyle;
if (resolvedOptions.style) this.setStyle(resolvedOptions.style, {localIdeographFontFamily: resolvedOptions.localIdeographFontFamily});
if (resolvedOptions.attributionControl)
this.addControl(new AttributionControl(typeof resolvedOptions.attributionControl === 'boolean' ? undefined : resolvedOptions.attributionControl));
if (resolvedOptions.maplibreLogo)
this.addControl(new LogoControl(), resolvedOptions.logoPosition);
this.on('style.load', () => {
if (this.transform.unmodified) {
const coercedOptions = pick(this.style.stylesheet, ['center', 'zoom', 'bearing', 'pitch', 'roll']) as CameraOptions;
this.jumpTo(coercedOptions);
}
});
this.on('data', (event: MapDataEvent) => {
this._update(event.dataType === 'style');
this.fire(new Event(`${event.dataType}data`, event));
});
this.on('dataloading', (event: MapDataEvent) => {
this.fire(new Event(`${event.dataType}dataloading`, event));
});
this.on('dataabort', (event: MapDataEvent) => {
this.fire(new Event('sourcedataabort', event));
});
}
/**
* @internal
* Returns a unique number for this map instance which is used for the MapLoadEvent
* to make sure we only fire one event per instantiated map object.
* @returns the uniq map ID
*/
_getMapId() {
return this._mapId;
}
/**
* Adds an {@link IControl} to the map, calling `control.onAdd(this)`.
*
* An {@link ErrorEvent} will be fired if the image parameter is invalid.
*
* @param control - The {@link IControl} to add.
* @param position - position on the map to which the control will be added.
* Valid values are `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. Defaults to `'top-right'`.
* @example
* Add zoom and rotation controls to the map.
* ```ts
* map.addControl(new NavigationControl());
* ```
* @see [Display map navigation controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigation/)
*/
addControl(control: IControl, position?: ControlPosition): Map {
if (position === undefined) {
if (control.getDefaultPosition) {
position = control.getDefaultPosition();
} else {
position = 'top-right';
}
}
if (!control || !control.onAdd) {
return this.fire(new ErrorEvent(new Error(
'Invalid argument to map.addControl(). Argument must be a control with onAdd and onRemove methods.')));
}
const controlElement = control.onAdd(this);
this._controls.push(control);
const positionContainer = this._controlPositions[position];
if (position.indexOf('bottom') !== -1) {
positionContainer.insertBefore(controlElement, positionContainer.firstChild);
} else {
positionContainer.appendChild(controlElement);
}
return this;
}
/**
* Removes the control from the map.
*
* An {@link ErrorEvent} will be fired if the image parameter is invalid.
*
* @param control - The {@link IControl} to remove.
* @example
* ```ts
* // Define a new navigation control.
* let navigation = new NavigationControl();
* // Add zoom and rotation controls to the map.
* map.addControl(navigation);
* // Remove zoom and rotation controls from the map.
* map.removeControl(navigation);
* ```
*/
removeControl(control: IControl): Map {
if (!control || !control.onRemove) {
return this.fire(new ErrorEvent(new Error(
'Invalid argument to map.removeControl(). Argument must be a control with onAdd and onRemove methods.')));
}
const ci = this._controls.indexOf(control);
if (ci > -1) this._controls.splice(ci, 1);
control.onRemove(this);
return this;
}
/**
* Checks if a control exists on the map.
*
* @param control - The {@link IControl} to check.
* @returns true if map contains control.
* @example
* ```ts
* // Define a new navigation control.
* let navigation = new NavigationControl();
* // Add zoom and rotation controls to the map.
* map.addControl(navigation);
* // Check that the navigation control exists on the map.
* map.hasControl(navigation);
* ```
*/
hasControl(control: IControl): boolean {
return this._controls.indexOf(control) > -1;
}
calculateCameraOptionsFromTo(from: LngLat, altitudeFrom: number, to: LngLat, altitudeTo?: number): CameraOptions {
if (altitudeTo == null && this.terrain) {
altitudeTo = this.terrain.getElevationForLngLatZoom(to, this.transform.tileZoom);
}
return super.calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo);
}
/**
* Resizes the map according to the dimensions of its
* `container` element.
*
* Checks if the map container size changed and updates the map if it has changed.
* This method must be called after the map's `container` is resized programmatically
* or when the map is shown after being initially hidden with CSS.
*
* Triggers the following events: `movestart`, `move`, `moveend`, and `resize`.
*
* @param eventData - Additional properties to be passed to `movestart`, `move`, `resize`, and `moveend`
* events that get triggered as a result of resize. This can be useful for differentiating the
* source of an event (for example, user-initiated or programmatically-triggered events).
* @example
* Resize the map when the map container is shown after being initially hidden with CSS.
* ```ts
* let mapDiv = document.getElementById('map');
* if (mapDiv.style.visibility === true) map.resize();
* ```
*/
resize(eventData?: any): Map {
const dimensions = this._containerDimensions();
const width = dimensions[0];
const height = dimensions[1];
const clampedPixelRatio = this._getClampedPixelRatio(width, height);
this._resizeCanvas(width, height, clampedPixelRatio);
this.painter.resize(width, height, clampedPixelRatio);
// check if we've reached GL limits, in that case further clamps pixelRatio
if (this.painter.overLimit()) {
const gl = this.painter.context.gl;
// store updated _maxCanvasSize value
this._maxCanvasSize = [gl.drawingBufferWidth, gl.drawingBufferHeight];
const clampedPixelRatio = this._getClampedPixelRatio(width, height);
this._resizeCanvas(width, height, clampedPixelRatio);
this.painter.resize(width, height, clampedPixelRatio);
}
this.transform.resize(width, height);
this._requestedCameraState?.resize(width, height);
const fireMoving = !this._moving;
if (fireMoving) {
this.stop();
this.fire(new Event('movestart', eventData))
.fire(new Event('move', eventData));
}
this.fire(new Event('resize', eventData));
if (fireMoving) this.fire(new Event('moveend', eventData));
return this;
}
/**
* @internal
* Return the map's pixel ratio eventually scaled down to respect maxCanvasSize.
* Internally you should use this and not getPixelRatio().
*/
_getClampedPixelRatio(width: number, height: number): number {
const {0: maxCanvasWidth, 1: maxCanvasHeight} = this._maxCanvasSize;
const pixelRatio = this.getPixelRatio();
const canvasWidth = width * pixelRatio;
const canvasHeight = height * pixelRatio;
const widthScaleFactor = canvasWidth > maxCanvasWidth ? (maxCanvasWidth / canvasWidth) : 1;
const heightScaleFactor = canvasHeight > maxCanvasHeight ? (maxCanvasHeight / canvasHeight) : 1;
return Math.min(widthScaleFactor, heightScaleFactor) * pixelRatio;
}
/**
* Returns the map's pixel ratio.
* Note that the pixel ratio actually applied may be lower to respect maxCanvasSize.
* @returns The pixel ratio.
*/
getPixelRatio(): number {
return this._overridePixelRatio ?? devicePixelRatio;
}
/**
* Sets the map's pixel ratio. This allows to override `devicePixelRatio`.
* After this call, the canvas' `width` attribute will be `container.clientWidth * pixelRatio`
* and its height attribute will be `container.clientHeight * pixelRatio`.
* Set this to null to disable `devicePixelRatio` override.
* Note that the pixel ratio actually applied may be lower to respect maxCanvasSize.
* @param pixelRatio - The pixel ratio.
*/
setPixelRatio(pixelRatio: number) {
this._overridePixelRatio = pixelRatio;
this.resize();
}
/**
* Returns the map's geographical bounds. When the bearing or pitch is non-zero, the visible region is not
* an axis-aligned rectangle, and the result is the smallest bounds that encompasses the visible region.
* @returns The geographical bounds of the map as {@link LngLatBounds}.
* @example
* ```ts
* let bounds = map.getBounds();
* ```
*/
getBounds(): LngLatBounds {
return this.transform.getBounds();
}
/**
* Returns the maximum geographical bounds the map is constrained to, or `null` if none set.
* @returns The map object.
* @example
* ```ts
* let maxBounds = map.getMaxBounds();
* ```
*/
getMaxBounds(): LngLatBounds | null {
return this.transform.getMaxBounds();
}
/**
* Sets or clears the map's geographical bounds.
*
* Pan and zoom operations are constrained within these bounds.
* If a pan or zoom is performed that would
* display regions outside these bounds, the map will
* instead display a position and zoom level
* as close as possible to the operation's request while still
* remaining within the bounds.
*
* @param bounds - The maximum bounds to set. If `null` or `undefined` is provided, the function removes the map's maximum bounds.
* @example
* Define bounds that conform to the `LngLatBoundsLike` object as set the max bounds.
* ```ts
* let bounds = [
* [-74.04728, 40.68392], // [west, south]
* [-73.91058, 40.87764] // [east, north]
* ];
* map.setMaxBounds(bounds);