UNPKG

mapbox-gl

Version:
957 lines (863 loc) 204 kB
// @flow import {version} from '../../package.json'; import {asyncAll, extend, bindAll, warnOnce, uniqueId, isSafariWithAntialiasingBug} from '../util/util.js'; import browser from '../util/browser.js'; import * as DOM from '../util/dom.js'; import {getImage, getJSON, ResourceType} from '../util/ajax.js'; import {RequestManager, getMapSessionAPI, postPerformanceEvent, postMapLoadEvent, AUTH_ERR_MSG, storeAuthState, removeAuthState} from '../util/mapbox.js'; import Style from '../style/style.js'; import EvaluationParameters from '../style/evaluation_parameters.js'; import Painter from '../render/painter.js'; import Transform from '../geo/transform.js'; import Hash from './hash.js'; import HandlerManager from './handler_manager.js'; import Camera from './camera.js'; import LngLat from '../geo/lng_lat.js'; import LngLatBounds from '../geo/lng_lat_bounds.js'; import Point from '@mapbox/point-geometry'; import AttributionControl from './control/attribution_control.js'; import LogoControl from './control/logo_control.js'; import {supported} from '@mapbox/mapbox-gl-supported'; import {RGBAImage} from '../util/image.js'; import {Event, ErrorEvent} from '../util/evented.js'; import {MapMouseEvent} from './events.js'; import TaskQueue from '../util/task_queue.js'; import webpSupported from '../util/webp_supported.js'; import {PerformanceUtils} from '../util/performance.js'; import {PerformanceMarkers, LivePerformanceUtils} from '../util/live_performance.js'; import Marker from '../ui/marker.js'; import Popup from '../ui/popup.js'; import EasedVariable from '../util/eased_variable.js'; import SourceCache from '../source/source_cache.js'; import {GLOBE_ZOOM_THRESHOLD_MAX} from '../geo/projection/globe_util.js'; import {setCacheLimits} from '../util/tile_request_cache.js'; import {Debug} from '../util/debug.js'; import config from '../util/config.js'; import {isFQID} from '../util/fqid.js'; import type {Listener} from '../util/evented.js'; import type {PointLike} from '@mapbox/point-geometry'; import type {RequestTransformFunction} from '../util/mapbox.js'; import type {LngLatLike} from '../geo/lng_lat.js'; import type {LngLatBoundsLike} from '../geo/lng_lat_bounds.js'; import type {StyleOptions, StyleSetterOptions} from '../style/style.js'; import type {MapEvent, MapDataEvent} from './events.js'; import type {CustomLayerInterface} from '../style/style_layer/custom_style_layer.js'; import type {StyleImageInterface, StyleImageMetadata} from '../style/style_image.js'; import type ScrollZoomHandler from './handler/scroll_zoom.js'; import type BoxZoomHandler from './handler/box_zoom.js'; import type {TouchPitchHandler} from './handler/touch_zoom_rotate.js'; import type DragRotateHandler from './handler/shim/drag_rotate.js'; import type DragPanHandler from './handler/shim/drag_pan.js'; import type {DragPanOptions} from './handler/shim/drag_pan.js'; import type KeyboardHandler from './handler/keyboard.js'; import type DoubleClickZoomHandler from './handler/shim/dblclick_zoom.js'; import type TouchZoomRotateHandler from './handler/shim/touch_zoom_rotate.js'; import defaultLocale from './default_locale.js'; import type {TaskID} from '../util/task_queue.js'; import type {Cancelable} from '../types/cancelable.js'; import type { LayerSpecification, FilterSpecification, StyleSpecification, LightSpecification, FlatLightSpecification, LightsSpecification, TerrainSpecification, FogSpecification, SourceSpecification, ProjectionSpecification, PropertyValueSpecification, TransitionSpecification, CameraSpecification } from '../style-spec/types.js'; import type StyleLayer from '../style/style_layer.js'; import type {Source} from '../source/source.js'; import type {QueryFeature} from '../util/vectortile_to_geojson.js'; import type {QueryResult} from '../data/feature_index.js'; import type {EasingOptions} from './camera.js'; import type {ContextOptions} from '../gl/context.js'; import * as TP from '../tracked-parameters/tracked_parameters.js'; export type ControlPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; /* eslint-disable no-use-before-define */ interface IControl { +onAdd: (map: Map) => HTMLElement; +onRemove: (map: Map) => void; +getDefaultPosition?: () => ControlPosition; +_setLanguage?: (language: ?string | ?string[]) => void; } /* eslint-enable no-use-before-define */ export const AVERAGE_ELEVATION_SAMPLING_INTERVAL = 500; // ms export const AVERAGE_ELEVATION_EASE_TIME = 300; // ms export const AVERAGE_ELEVATION_EASE_THRESHOLD = 1; // meters export const AVERAGE_ELEVATION_CHANGE_THRESHOLD = 1e-4; // meters type MapOptions = { style: StyleSpecification | string | void, hash?: boolean | string, interactive?: boolean, container: HTMLElement | string, bearingSnap?: number, clickTolerance?: number, pitchWithRotate?: boolean, attributionControl?: boolean, customAttribution?: string | Array<string>, logoPosition?: ControlPosition, failIfMajorPerformanceCaveat?: boolean, preserveDrawingBuffer?: boolean, antialias?: boolean, refreshExpiredTiles?: boolean, bounds?: LngLatBoundsLike, maxBounds?: LngLatBoundsLike, fitBoundsOptions?: EasingOptions, scrollZoom?: boolean, minZoom?: ?number, maxZoom?: ?number, minPitch?: ?number, maxPitch?: ?number, boxZoom?: boolean, dragRotate?: boolean, dragPan?: DragPanOptions, keyboard?: boolean, doubleClickZoom?: boolean, touchZoomRotate?: boolean, touchPitch?: boolean, cooperativeGestures?: boolean, trackResize?: boolean, center?: LngLatLike, zoom?: number, bearing?: number, pitch?: number, projection?: ProjectionSpecification | string, renderWorldCopies?: boolean, minTileCacheSize?: number, maxTileCacheSize?: number, transformRequest?: RequestTransformFunction, accessToken: string, testMode: ?boolean, locale?: Object, language?: string, worldview?: string, crossSourceCollisions?: boolean, collectResourceTiming?: boolean, respectPrefersReducedMotion?: boolean, contextCreateOptions?: ContextOptions, devtools?: boolean }; const defaultMinZoom = -2; const defaultMaxZoom = 22; // the default values, but also the valid range const defaultMinPitch = 0; const defaultMaxPitch = 85; const defaultOptions = { center: [0, 0], zoom: 0, bearing: 0, pitch: 0, minZoom: defaultMinZoom, maxZoom: defaultMaxZoom, minPitch: defaultMinPitch, maxPitch: defaultMaxPitch, interactive: true, scrollZoom: true, boxZoom: true, dragRotate: true, dragPan: true, keyboard: true, doubleClickZoom: true, touchZoomRotate: true, touchPitch: true, cooperativeGestures: false, performanceMetricsCollection: true, bearingSnap: 7, clickTolerance: 3, pitchWithRotate: true, hash: false, attributionControl: true, antialias: false, failIfMajorPerformanceCaveat: false, preserveDrawingBuffer: false, trackResize: true, renderWorldCopies: true, refreshExpiredTiles: true, minTileCacheSize: null, maxTileCacheSize: null, localIdeographFontFamily: 'sans-serif', localFontFamily: null, transformRequest: null, accessToken: null, fadeDuration: 300, respectPrefersReducedMotion: true, crossSourceCollisions: true, collectResourceTiming: false, testMode: false, }; class DebugParams { showOverdrawInspector: boolean; showTileBoundaries: boolean; continuousRedraw: boolean; showTerrainWireframe: boolean; showLayers2DWireframe: boolean; showLayers3DWireframe: boolean; constructor() { this.showOverdrawInspector = false; this.showTileBoundaries = false; this.continuousRedraw = false; this.showTerrainWireframe = false; this.showLayers2DWireframe = false; this.showLayers3DWireframe = false; } } /** * 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. * Then Mapbox GL JS initializes the map on the page and returns your `Map` * object. * * @extends Evented * @param {Object} options * @param {HTMLElement|string} options.container The HTML element in which Mapbox GL JS will render the map, or the element's string `id`. The specified element must have no children. * @param {number} [options.minZoom=0] The minimum zoom level of the map (0-24). * @param {number} [options.maxZoom=22] The maximum zoom level of the map (0-24). * @param {number} [options.minPitch=0] The minimum pitch of the map (0-85). * @param {number} [options.maxPitch=85] The maximum pitch of the map (0-85). * @param {Object | string} [options.style='mapbox://styles/mapbox/standard'] The map's Mapbox style. This must be an a JSON object conforming to * the schema described in the [Mapbox Style Specification](https://mapbox.com/mapbox-gl-style-spec/), or a URL * to such JSON. Can accept a null value to allow adding a style manually. * * To load a style from the Mapbox API, you can use a URL of the form `mapbox://styles/:owner/:style`, * where `:owner` is your Mapbox account name and `:style` is the style ID. You can also use a * [Mapbox-owned style](https://docs.mapbox.com/api/maps/styles/#mapbox-styles): * * * `mapbox://styles/mapbox/standard` * * `mapbox://styles/mapbox/streets-v12` * * `mapbox://styles/mapbox/outdoors-v12` * * `mapbox://styles/mapbox/light-v11` * * `mapbox://styles/mapbox/dark-v11` * * `mapbox://styles/mapbox/satellite-v9` * * `mapbox://styles/mapbox/satellite-streets-v12` * * `mapbox://styles/mapbox/navigation-day-v1` * * `mapbox://styles/mapbox/navigation-night-v1`. * * Tilesets hosted with Mapbox can be style-optimized if you append `?optimize=true` to the end of your style URL, like `mapbox://styles/mapbox/streets-v11?optimize=true`. * Learn more about style-optimized vector tiles in our [API documentation](https://www.mapbox.com/api-documentation/maps/#retrieve-tiles). * * @param {(boolean|string)} [options.hash=false] If `true`, the map's [position](https://docs.mapbox.com/help/glossary/camera) (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, * for example 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. * @param {boolean} [options.interactive=true] If `false`, no mouse, touch, or keyboard listeners will be attached to the map, so it will not respond to interaction. * @param {number} [options.bearingSnap=7] 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. * @param {boolean} [options.pitchWithRotate=true] If `false`, the map's pitch (tilt) control with "drag to rotate" interaction will be disabled. * @param {number} [options.clickTolerance=3] 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). * @param {boolean} [options.attributionControl=true] If `true`, an {@link AttributionControl} will be added to the map. * @param {string | Array<string>} [options.customAttribution=null] String or strings to show in an {@link AttributionControl}. Only applicable if `options.attributionControl` is `true`. * @param {string} [options.logoPosition='bottom-left'] A string representing the position of the Mapbox wordmark on the map. Valid options are `top-left`,`top-right`, `bottom-left`, `bottom-right`. * @param {boolean} [options.failIfMajorPerformanceCaveat=false] If `true`, map creation will fail if the performance of Mapbox GL JS would be dramatically worse than expected (a software renderer would be used). * @param {boolean} [options.preserveDrawingBuffer=false] 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. * @param {boolean} [options.antialias=false] If `true`, the gl context will be created with [MSAA antialiasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing), which can be useful for antialiasing custom layers. This is `false` by default as a performance optimization. * @param {boolean} [options.refreshExpiredTiles=true] If `false`, the map won't attempt to re-request tiles once they expire per their HTTP `cacheControl`/`expires` headers. * @param {LngLatBoundsLike} [options.maxBounds=null] If set, the map will be constrained to the given bounds. * @param {boolean|Object} [options.scrollZoom=true] If `true`, the "scroll to zoom" interaction is enabled. An `Object` value is passed as options to {@link ScrollZoomHandler#enable}. * @param {boolean} [options.boxZoom=true] If `true`, the "box zoom" interaction is enabled (see {@link BoxZoomHandler}). * @param {boolean} [options.dragRotate=true] If `true`, the "drag to rotate" interaction is enabled (see {@link DragRotateHandler}). * @param {boolean | Object} [options.dragPan=true] If `true`, the "drag to pan" interaction is enabled. An `Object` value is passed as options to {@link DragPanHandler#enable}. * @param {boolean} [options.keyboard=true] If `true`, keyboard shortcuts are enabled (see {@link KeyboardHandler}). * @param {boolean} [options.doubleClickZoom=true] If `true`, the "double click to zoom" interaction is enabled (see {@link DoubleClickZoomHandler}). * @param {boolean | Object} [options.touchZoomRotate=true] If `true`, the "pinch to rotate and zoom" interaction is enabled. An `Object` value is passed as options to {@link TouchZoomRotateHandler#enable}. * @param {boolean | Object} [options.touchPitch=true] If `true`, the "drag to pitch" interaction is enabled. An `Object` value is passed as options to {@link TouchPitchHandler}. * @param {boolean} [options.cooperativeGestures] If `true`, scroll zoom will require pressing the ctrl or ⌘ key while scrolling to zoom map, and touch pan will require using two fingers while panning to move the map. Touch pitch will require three fingers to activate if enabled. * @param {boolean} [options.trackResize=true] If `true`, the map will automatically resize when the browser window resizes. * @param {boolean} [options.performanceMetricsCollection=true] If `true`, mapbox-gl will collect and send performance metrics. * @param {LngLatLike} [options.center=[0, 0]] The initial geographical [centerpoint](https://docs.mapbox.com/help/glossary/camera#center) of the map. If `center` is not specified in the constructor options, Mapbox 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: Mapbox GL uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match GeoJSON. * @param {number} [options.zoom=0] The initial [zoom](https://docs.mapbox.com/help/glossary/camera#zoom) level of the map. If `zoom` is not specified in the constructor options, Mapbox 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`. * @param {number} [options.bearing=0] The initial [bearing](https://docs.mapbox.com/help/glossary/camera#bearing) (rotation) of the map, measured in degrees counter-clockwise from north. If `bearing` is not specified in the constructor options, Mapbox 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`. * @param {number} [options.pitch=0] The initial [pitch](https://docs.mapbox.com/help/glossary/camera#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, Mapbox 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`. * @param {LngLatBoundsLike} [options.bounds=null] The initial bounds of the map. If `bounds` is specified, it overrides `center` and `zoom` constructor options. * @param {Object} [options.fitBoundsOptions=null] A {@link Map#fitBounds} options object to use _only_ when fitting the initial `bounds` provided above. * @param {'auto' | string | string[]} [options.language=null] A string with a BCP 47 language tag, or an array of such strings representing the desired languages used for the map's labels and UI components. Languages can only be set on Mapbox vector tile sources. * By default, GL JS will not set a language so that the language of Mapbox tiles will be determined by the vector tile source's TileJSON. * Valid language strings must be a [BCP-47 language code](https://en.wikipedia.org/wiki/IETF_language_tag#List_of_subtags). Unsupported BCP-47 codes will not include any translations. Invalid codes will result in an recoverable error. * If a label has no translation for the selected language, it will display in the label's local language. * If option is set to `auto`, GL JS will select a user's preferred language as determined by the browser's [`window.navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language) property. * If the `locale` property is not set separately, this language will also be used to localize the UI for supported languages. * @param {string} [options.worldview=null] Sets the map's worldview. A worldview determines the way that certain disputed boundaries * are rendered. By default, GL JS will not set a worldview so that the worldview of Mapbox tiles will be determined by the vector tile source's TileJSON. * Valid worldview strings must be an [ISO alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes). Unsupported * ISO alpha-2 codes will fall back to the TileJSON's default worldview. Invalid codes will result in a recoverable error. * @param {boolean} [options.renderWorldCopies=true] 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. * @param {number} [options.minTileCacheSize=null] The minimum number of tiles stored in the tile cache for a given source. Larger viewports use more tiles and need larger caches. Larger viewports are more likely to be found on devices with more memory and on pages where the map is more important. If omitted, the cache will be dynamically sized based on the current viewport. * @param {number} [options.maxTileCacheSize=null] 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. * @param {string} [options.localIdeographFontFamily='sans-serif'] Defines a CSS font-family for locally overriding generation of glyphs in the 'CJK Unified Ideographs', 'Hiragana', 'Katakana', 'Hangul Syllables' and 'CJK Symbols and Punctuation' ranges. * In these ranges, 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. Note that [Mapbox Studio](https://studio.mapbox.com/) sets this value to `false` by default. * The purpose of this option is to avoid bandwidth-intensive glyph server requests. For an example of this option in use, see [Use locally generated ideographs](https://www.mapbox.com/mapbox-gl-js/example/local-ideographs). * @param {string} [options.localFontFamily=null] Defines a CSS * font-family for locally overriding generation of all glyphs. Font settings from the map's style will be ignored, except for font-weight keywords (light/regular/medium/bold). * If set, this option overrides the setting in localIdeographFontFamily. * @param {RequestTransformFunction} [options.transformRequest=null] 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 a {@link RequestParameters} object with a `url` property and optionally `headers` and `credentials` properties. * @param {boolean} [options.collectResourceTiming=false] 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. * @param {number} [options.fadeDuration=300] Controls the duration of the fade-in/fade-out animation for label collisions, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading. * @param {boolean} [options.respectPrefersReducedMotion=true] If set to `true`, the map will respect the user's `prefers-reduced-motion` browser setting and apply a reduced motion mode, minimizing animations and transitions. When set to `false`, the map will always ignore the `prefers-reduced-motion` settings, regardless of the user's preference, making all animations essential. * @param {boolean} [options.crossSourceCollisions=true] 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. * @param {string} [options.accessToken=null] If specified, map will use this [token](https://docs.mapbox.com/help/glossary/access-token/) instead of the one defined in `mapboxgl.accessToken`. * @param {Object} [options.locale=null] A patch to apply to the default localization table for UI strings such as control tooltips. The `locale` object maps namespaced UI string IDs to translated strings in the target language; * see [`src/ui/default_locale.js`](https://github.com/mapbox/mapbox-gl-js/blob/main/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). * @param {boolean} [options.testMode=false] Silences errors and warnings generated due to an invalid accessToken, useful when using the library to write unit tests. * @param {ProjectionSpecification} [options.projection='mercator'] The [projection](https://docs.mapbox.com/mapbox-gl-js/style-spec/projection/) the map should be rendered in. * Supported projections are: * * [Albers](https://en.wikipedia.org/wiki/Albers_projection) equal-area conic projection as `albers` * * [Equal Earth](https://en.wikipedia.org/wiki/Equal_Earth_projection) equal-area pseudocylindrical projection as `equalEarth` * * [Equirectangular](https://en.wikipedia.org/wiki/Equirectangular_projection) (Plate Carrée/WGS84) as `equirectangular` * * 3d Globe as `globe` * * [Lambert Conformal Conic](https://en.wikipedia.org/wiki/Lambert_conformal_conic_projection) as `lambertConformalConic` * * [Mercator](https://en.wikipedia.org/wiki/Mercator_projection) cylindrical map projection as `mercator` * * [Natural Earth](https://en.wikipedia.org/wiki/Natural_Earth_projection) pseudocylindrical map projection as `naturalEarth` * * [Winkel Tripel](https://en.wikipedia.org/wiki/Winkel_tripel_projection) azimuthal map projection as `winkelTripel` * Conic projections such as Albers and Lambert have configurable `center` and `parallels` properties that allow developers to define the region in which the projection has minimal distortion; see the example for how to configure these properties. * @example * const map = new mapboxgl.Map({ * container: 'map', // container ID * center: [-122.420679, 37.772537], // starting position [lng, lat] * zoom: 13, // starting zoom * style: 'mapbox://styles/mapbox/streets-v11', // style URL or style object * hash: true, // sync `center`, `zoom`, `pitch`, and `bearing` with URL * // Use `transformRequest` to modify requests that begin with `http://myHost`. * 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 [Example: Display a map on a webpage](https://docs.mapbox.com/mapbox-gl-js/example/simple-map/) * @see [Example: Display a map with a custom style](https://docs.mapbox.com/mapbox-gl-js/example/custom-style-id/) * @see [Example: Check if Mapbox GL JS is supported](https://docs.mapbox.com/mapbox-gl-js/example/check-for-support/) */ class Map extends Camera { style: Style; painter: Painter; handlers: ?HandlerManager; _container: HTMLElement; _missingCSSCanary: HTMLElement; _canvasContainer: HTMLElement; _controlContainer: HTMLElement; _controlPositions: {[_: string]: HTMLElement}; _interactive: ?boolean; _showTileBoundaries: ?boolean; _showTerrainWireframe: ?boolean; _showLayers2DWireframe: ?boolean; _showLayers3DWireframe: ?boolean; _showQueryGeometry: ?boolean; _showCollisionBoxes: ?boolean; _showPadding: ?boolean; _showTileAABBs: ?boolean; _showOverdrawInspector: boolean; _repaint: ?boolean; _vertices: ?boolean; _canvas: HTMLCanvasElement; _minTileCacheSize: ?number; _maxTileCacheSize: ?number; _frame: ?Cancelable; _renderNextFrame: ?boolean; _styleDirty: ?boolean; _sourcesDirty: ?boolean; _placementDirty: ?boolean; _loaded: boolean; _fullyLoaded: boolean; // accounts for placement finishing as well _trackResize: boolean; _preserveDrawingBuffer: boolean; _failIfMajorPerformanceCaveat: boolean; _antialias: boolean; _refreshExpiredTiles: boolean; _hash: Hash; _delegatedListeners: any; _fullscreenchangeEvent: "fullscreenchange" | "webkitfullscreenchange"; _isInitialLoad: boolean; _shouldCheckAccess: boolean; _fadeDuration: number; _crossSourceCollisions: boolean; _collectResourceTiming: boolean; _renderTaskQueue: TaskQueue; _domRenderTaskQueue: TaskQueue; _controls: Array<IControl>; _markers: Array<Marker>; _popups: Array<Popup>; _logoControl: IControl; _mapId: number; _localIdeographFontFamily: string; _localFontFamily: ?string; _requestManager: RequestManager; _locale: Object; _removed: boolean; _speedIndexTiming: boolean; _clickTolerance: number; _cooperativeGestures: boolean; _silenceAuthErrors: boolean; _averageElevationLastSampledAt: number; _averageElevationExaggeration: number; _averageElevation: EasedVariable; _containerWidth: number; _containerHeight: number; _language: ?string | ?string[]; _worldview: ?string; _interactionRange: [number, number]; _visibilityHidden: number; _performanceMetricsCollection: boolean; // `_useExplicitProjection` indicates that a projection is set by a call to map.setProjection() _useExplicitProjection: boolean; /** @section {Interaction handlers} */ /** * 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 TouchZoomRotateHandler}, which allows the user to zoom or rotate the map with touch gestures. * Find more details and examples using `touchZoomRotate` in the {@link TouchZoomRotateHandler} section. */ touchZoomRotate: TouchZoomRotateHandler; /** * The map's {@link TouchPitchHandler}, which allows the user to pitch the map with touch gestures. * Find more details and examples using `touchPitch` in the {@link TouchPitchHandler} section. */ touchPitch: TouchPitchHandler; _contextCreateOptions: ContextOptions; _tp: TP.TrackedParameters | TP.TrackedParametersMock; _debugParams: DebugParams; constructor(options: MapOptions) { LivePerformanceUtils.mark(PerformanceMarkers.create); const initialOptions = options; options = (extend({}, defaultOptions, options): typeof defaultOptions & MapOptions); if (options.minZoom != null && options.maxZoom != null && options.minZoom > options.maxZoom) { throw new Error(`maxZoom must be greater than or equal to minZoom`); } if (options.minPitch != null && options.maxPitch != null && options.minPitch > options.maxPitch) { throw new Error(`maxPitch must be greater than or equal to minPitch`); } if (options.minPitch != null && options.minPitch < defaultMinPitch) { throw new Error(`minPitch must be greater than or equal to ${defaultMinPitch}`); } if (options.maxPitch != null && options.maxPitch > defaultMaxPitch) { throw new Error(`maxPitch must be less than or equal to ${defaultMaxPitch}`); } // disable antialias with OS/iOS 15.4 and 15.5 due to rendering bug if (options.antialias && isSafariWithAntialiasingBug(window)) { options.antialias = false; warnOnce('Antialiasing is disabled for this WebGL context to avoid browser bug: https://github.com/mapbox/mapbox-gl-js/issues/11609'); } const transform = new Transform(options.minZoom, options.maxZoom, options.minPitch, options.maxPitch, options.renderWorldCopies); super(transform, options); this._repaint = false; this._interactive = options.interactive; this._minTileCacheSize = options.minTileCacheSize; this._maxTileCacheSize = options.maxTileCacheSize; this._failIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat; this._preserveDrawingBuffer = options.preserveDrawingBuffer; this._antialias = options.antialias; this._trackResize = options.trackResize; this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; this._fadeDuration = options.fadeDuration; this._isInitialLoad = true; this._crossSourceCollisions = options.crossSourceCollisions; this._collectResourceTiming = options.collectResourceTiming; this._language = this._parseLanguage(options.language); this._worldview = options.worldview; this._renderTaskQueue = new TaskQueue(); this._domRenderTaskQueue = new TaskQueue(); this._controls = []; this._markers = []; this._popups = []; this._mapId = uniqueId(); this._locale = extend({}, defaultLocale, options.locale); this._clickTolerance = options.clickTolerance; this._cooperativeGestures = options.cooperativeGestures; this._performanceMetricsCollection = options.performanceMetricsCollection; this._containerWidth = 0; this._containerHeight = 0; this._averageElevationLastSampledAt = -Infinity; this._averageElevationExaggeration = 0; this._averageElevation = new EasedVariable(0); this._interactionRange = [+Infinity, -Infinity]; this._visibilityHidden = 0; this._useExplicitProjection = false; // Fallback to stylesheet by default this._requestManager = new RequestManager(options.transformRequest, options.accessToken, options.testMode); this._silenceAuthErrors = !!options.testMode; if (options.contextCreateOptions) { this._contextCreateOptions = {...options.contextCreateOptions}; } else { this._contextCreateOptions = {}; } if (typeof options.container === 'string') { const container = document.getElementById(options.container); if (container) { this._container = container; } else { throw new Error(`Container '${options.container.toString()}' not found.`); } } else if (options.container instanceof HTMLElement) { this._container = options.container; } else { throw new Error(`Invalid type: 'container' must be a String or HTMLElement.`); } if (this._container.childNodes.length > 0) { warnOnce(`The map container element should be empty, otherwise the map's interactivity will be negatively impacted. If you want to display a message when WebGL is not supported, use the Mapbox GL Supported plugin instead.`); } if (options.maxBounds) { this.setMaxBounds(options.maxBounds); } bindAll([ '_onWindowOnline', '_onWindowResize', '_onVisibilityChange', '_onMapScroll', '_contextLost', '_contextRestored' ], this); this._setupContainer(); this._debugParams = new DebugParams(); if (options.devtools) { this._tp = new TP.TrackedParameters(this); } else { this._tp = new TP.TrackedParametersMock(); } this._tp.registerParameter(this._debugParams, ["Debug"], "showOverdrawInspector", undefined, () => { this._update(); }); this._tp.registerParameter(this._debugParams, ["Debug"], "showTileBoundaries", undefined, () => { this._update(); }); this._tp.registerParameter(this._debugParams, ["Debug"], "continuousRedraw", undefined, (value) => { this.repaint = value; }); this._tp.registerParameter(this._debugParams, ["Debug", "Wireframe"], "showTerrainWireframe", undefined, () => { this._update(); }); this._tp.registerParameter(this._debugParams, ["Debug", "Wireframe"], "showLayers2DWireframe", undefined, () => { this._update(); }); this._tp.registerParameter(this._debugParams, ["Debug", "Wireframe"], "showLayers3DWireframe", undefined, () => { this._update(); }); this._setupPainter(); if (this.painter === undefined) { throw new Error(`Failed to initialize WebGL.`); } this.on('move', () => this._update(false)); this.on('moveend', () => this._update(false)); this.on('zoom', () => this._update(true)); this._fullscreenchangeEvent = 'onfullscreenchange' in document ? 'fullscreenchange' : 'webkitfullscreenchange'; // $FlowFixMe[method-unbinding] window.addEventListener('online', this._onWindowOnline, false); // $FlowFixMe[method-unbinding] window.addEventListener('resize', this._onWindowResize, false); // $FlowFixMe[method-unbinding] window.addEventListener('orientationchange', this._onWindowResize, false); // $FlowFixMe[method-unbinding] window.addEventListener(this._fullscreenchangeEvent, this._onWindowResize, false); // $FlowFixMe[method-unbinding] window.addEventListener('visibilitychange', this._onVisibilityChange, false); this.handlers = new HandlerManager(this, options); this._localFontFamily = options.localFontFamily; this._localIdeographFontFamily = options.localIdeographFontFamily; if (options.style || !options.testMode) { const style = options.style || config.DEFAULT_STYLE; this.setStyle(style, {localFontFamily: this._localFontFamily, localIdeographFontFamily: this._localIdeographFontFamily}); } if (options.projection) { this.setProjection(options.projection); } const hashName = (typeof options.hash === 'string' && options.hash) || undefined; if (options.hash) this._hash = (new Hash(hashName)).addTo(this); // don't set position from options if set through hash if (!this._hash || !this._hash._onHashChange()) { // if we set `center`/`zoom` explicitly, mark as modified even if the values match defaults if (initialOptions.center != null || initialOptions.zoom != null) { this.transform._unmodified = false; } this.jumpTo({ center: options.center, zoom: options.zoom, bearing: options.bearing, pitch: options.pitch }); const bounds = options.bounds; if (bounds) { this.resize(); this.fitBounds(bounds, extend({}, options.fitBoundsOptions, {duration: 0})); } } this.resize(); if (options.attributionControl) // $FlowFixMe[method-unbinding] this.addControl(new AttributionControl({customAttribution: options.customAttribution})); // $FlowFixMe[method-unbinding] this._logoControl = new LogoControl(); // $FlowFixMe[method-unbinding] this.addControl(this._logoControl, options.logoPosition); this.on('style.load', () => { if (this.transform.unmodified) { this.jumpTo((this.style.stylesheet: any)); } }); 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)); }); } /* * 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. * @private * @returns {number} */ _getMapId(): number { return this._mapId; } /** @section {Controls} */ /** * Adds an {@link IControl} to the map, calling `control.onAdd(this)`. * * @param {IControl} control The {@link IControl} to add. * @param {string} [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'`. * @returns {Map} Returns itself to allow for method chaining. * @example * // Add zoom and rotation controls to the map. * map.addControl(new mapboxgl.NavigationControl()); * @see [Example: Display map navigation controls](https://www.mapbox.com/mapbox-gl-js/example/navigation/) */ addControl(control: IControl, position?: ControlPosition): this { 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. * * @param {IControl} control The {@link IControl} to remove. * @returns {Map} Returns itself to allow for method chaining. * @example * // Define a new navigation control. * const navigation = new mapboxgl.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): this { 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 is on the map. * * @param {IControl} control The {@link IControl} to check. * @returns {boolean} True if map contains control. * @example * // Define a new navigation control. * const navigation = new mapboxgl.NavigationControl(); * // Add zoom and rotation controls to the map. * map.addControl(navigation); * // Check that the navigation control exists on the map. * const added = map.hasControl(navigation); * // added === true */ hasControl(control: IControl): boolean { return this._controls.indexOf(control) > -1; } /** * Returns the map's containing HTML element. * * @returns {HTMLElement} The map's container. * @example * const container = map.getContainer(); */ getContainer(): HTMLElement { return this._container; } /** * Returns the HTML element containing the map's `<canvas>` element. * * If you want to add non-GL overlays to the map, you should append them to this element. * * This is the element to which event bindings for map interactivity (such as panning and zooming) are * attached. It will receive bubbled events from child elements such as the `<canvas>`, but not from * map controls. * * @returns {HTMLElement} The container of the map's `<canvas>`. * @example * const canvasContainer = map.getCanvasContainer(); * @see [Example: Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) * @see [Example: Highlight features within a bounding box](https://www.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) */ getCanvasContainer(): HTMLElement { return this._canvasContainer; } /** * Returns the map's `<canvas>` element. * * @returns {HTMLCanvasElement} The map's `<canvas>` element. * @example * const canvas = map.getCanvas(); * @see [Example: Measure distances](https://www.mapbox.com/mapbox-gl-js/example/measure/) * @see [Example: Display a popup on hover](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/) * @see [Example: Center the map on a clicked symbol](https://www.mapbox.com/mapbox-gl-js/example/center-on-symbol/) */ getCanvas(): HTMLCanvasElement { return this._canvas; } /** @section {Map constraints} */ /** * 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. * * @param {Object | null} 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). * @returns {Map} Returns itself to allow for method chaining. * @example * // Resize the map when the map container is shown * // after being initially hidden with CSS. * const mapDiv = document.getElementById('map'); * if (mapDiv.style.visibility === true) map.resize(); */ resize(eventData?: Object): this { this._updateContainerDimensions(); // do nothing if container remained the same size if (this._containerWidth === this.transform.width && this._containerHeight === this.transform.height) return this; this._resizeCanvas(this._containerWidth, this._containerHeight); this.transform.resize(this._containerWidth, this._containerHeight); this.painter.resize(Math.ceil(this._containerWidth), Math.ceil(this._containerHeight)); const fireMoving = !this._moving; if (fireMoving) { 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; } /** * 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. * If a padding is set on the map, the bounds returned are for the inset. * With globe projection, the smallest bounds encompassing the visible region * may not precisely represent the visible region due to the earth's curvature. * * @returns {LngLatBounds} The geographical bounds of the map as {@link LngLatBounds}. * @example * const bounds = map.getBounds(); */ getBounds(): LngLatBounds | null { return this.transform.getBounds(); } /** * Returns the maximum geographical bounds the map is constrained to, or `null` if none set. * * @returns {Map} The map object. * * @example * const maxBounds = map.getMaxBounds(); */ getMaxBounds(): LngLatBounds | null { return this.transform.getMaxBounds() || null; } /** * 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. * * For `mercator` projection, the viewport will be constrained to the bounds. * For other projections such as `globe`, only the map center will be constrained. * * @param {LngLatBoundsLike | null | undefined} bounds The maximum bounds to set. If `null` or `undefined` is provided, the function removes the map's maximum bounds. * @returns {Map} Returns itself to allow for method chaining. * @example * // Define bounds that conform to the `LngLatBoundsLike` object. * const bounds = [ * [-74.04728, 40.68392], // [west, south] * [-73.91058, 40.87764] // [east, north] * ]; * // Set the map's max bounds. * map.setMaxBounds(bounds); */ setMaxBounds(bounds: LngLatBoundsLike): this { this.transform.setMaxBounds(LngLatBounds.convert(bounds)); return this._update(); } /** * Sets or clears the map's minimum zoom level. * If the map's current zoom level is lower than the new minimum, * the map will zoom to the new minimum. * * It is not always possible to zoom out and reach the set `minZoom`. * Other factors such as map height may restrict zooming. For example, * if the map is 512px tall it will not be possible to zoom below zoom 0 * no matter what the `minZoom` is set to. * * @param {number | null | undefined} minZoom The minimum zoom level to