UNPKG

mapbox-gl

Version:
1,138 lines (1,023 loc) 76 kB
'use strict'; const util = require('../util/util'); const browser = require('../util/browser'); const window = require('../util/window'); const DOM = require('../util/dom'); const ajax = require('../util/ajax'); const Style = require('../style/style'); const AnimationLoop = require('../style/animation_loop'); const Painter = require('../render/painter'); const Transform = require('../geo/transform'); const Hash = require('./hash'); const bindHandlers = require('./bind_handlers'); const Camera = require('./camera'); const LngLat = require('../geo/lng_lat'); const LngLatBounds = require('../geo/lng_lat_bounds'); const Point = require('point-geometry'); const AttributionControl = require('./control/attribution_control'); const LogoControl = require('./control/logo_control'); const isSupported = require('mapbox-gl-supported'); const defaultMinZoom = 0; const defaultMaxZoom = 22; const defaultOptions = { center: [0, 0], zoom: 0, bearing: 0, pitch: 0, minZoom: defaultMinZoom, maxZoom: defaultMaxZoom, interactive: true, scrollZoom: true, boxZoom: true, dragRotate: true, dragPan: true, keyboard: true, doubleClickZoom: true, touchZoomRotate: true, bearingSnap: 7, hash: false, attributionControl: true, failIfMajorPerformanceCaveat: false, preserveDrawingBuffer: false, trackResize: true, renderWorldCopies: true, refreshExpiredTiles: 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. * 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-22). * @param {number} [options.maxZoom=22] The maximum zoom level of the map (0-22). * @param {Object|string} [options.style] 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. * * 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. Or you can use one of the following * [the predefined Mapbox styles](https://www.mapbox.com/maps/): * * * `mapbox://styles/mapbox/streets-v9` * * `mapbox://styles/mapbox/outdoors-v9` * * `mapbox://styles/mapbox/light-v9` * * `mapbox://styles/mapbox/dark-v9` * * `mapbox://styles/mapbox/satellite-v9` * * `mapbox://styles/mapbox/satellite-streets-v9` * * 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-v9?optimize=true`. * Learn more about style-optimized vector tiles in our [API documentation](https://www.mapbox.com/api-documentation/#retrieve-tiles). * * @param {boolean} [options.hash=false] 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`. * @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 (rotation) 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 {Array<string>} [options.classes] Mapbox style class names with which to initialize the map. * Keep in mind that these classes are used for controlling a style layer's paint properties, so are *not* reflected * in an HTML element's `class` attribute. To learn more about Mapbox style classes, read about * [Layers](https://www.mapbox.com/mapbox-gl-style-spec/#layers) in the style specification. * @param {boolean} [options.attributionControl=true] If `true`, an [AttributionControl](#AttributionControl) will be added to the map. * @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 (i.e. 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.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] 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 [`ScrollZoomHandler#enable`](#ScrollZoomHandler#enable). * @param {boolean} [options.boxZoom=true] If `true`, the "box zoom" interaction is enabled (see [`BoxZoomHandler`](#BoxZoomHandler)). * @param {boolean} [options.dragRotate=true] If `true`, the "drag to rotate" interaction is enabled (see [`DragRotateHandler`](#DragRotateHandler)). * @param {boolean} [options.dragPan=true] If `true`, the "drag to pan" interaction is enabled (see [`DragPanHandler`](#DragPanHandler)). * @param {boolean} [options.keyboard=true] If `true`, keyboard shortcuts are enabled (see [`KeyboardHandler`](#KeyboardHandler)). * @param {boolean} [options.doubleClickZoom=true] If `true`, the "double click to zoom" interaction is enabled (see [`DoubleClickZoomHandler`](#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 [`TouchZoomRotateHandler#enable`](#TouchZoomRotateHandler#enable). * @param {boolean} [options.trackResize=true] If `true`, the map will automatically resize when the browser window resizes. * @param {LngLatLike} [options.center=[0, 0]] The inital geographical centerpoint 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]`. * @param {number} [options.zoom=0] The initial 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 (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 (tilt) of the map, measured in degrees away from the plane of the screen (0-60). 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 {boolean} [options.renderWorldCopies=true] If `true`, multiple copies of the world will be rendered, when zoomed out. * @example * var map = new mapboxgl.Map({ * container: 'map', * center: [-122.420679, 37.772537], * zoom: 13, * style: style_object, * hash: true * }); * @see [Display a map](https://www.mapbox.com/mapbox-gl-js/examples/) */ class Map extends Camera { constructor(options) { options = util.extend({}, defaultOptions, options); if (options.minZoom != null && options.maxZoom != null && options.minZoom > options.maxZoom) { throw new Error(`maxZoom must be greater than minZoom`); } const transform = new Transform(options.minZoom, options.maxZoom, options.renderWorldCopies); super(transform, options); this._interactive = options.interactive; this._failIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat; this._preserveDrawingBuffer = options.preserveDrawingBuffer; this._trackResize = options.trackResize; this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; if (typeof options.container === 'string') { this._container = window.document.getElementById(options.container); if (!this._container) throw new Error(`Container '${options.container}' not found.`); } else { this._container = options.container; } this.animationLoop = new AnimationLoop(); if (options.maxBounds) { this.setMaxBounds(options.maxBounds); } util.bindAll([ '_onWindowOnline', '_onWindowResize', '_contextLost', '_contextRestored', '_update', '_render', '_onData', '_onDataLoading' ], this); this._setupContainer(); this._setupPainter(); this.on('move', this._update.bind(this, false)); this.on('zoom', this._update.bind(this, true)); this.on('moveend', () => { this.animationLoop.set(300); // text fading this._rerender(); }); if (typeof window !== 'undefined') { window.addEventListener('online', this._onWindowOnline, false); window.addEventListener('resize', this._onWindowResize, false); } bindHandlers(this, options); this._hash = options.hash && (new Hash()).addTo(this); // don't set position from options if set through hash if (!this._hash || !this._hash._onHashChange()) { this.jumpTo({ center: options.center, zoom: options.zoom, bearing: options.bearing, pitch: options.pitch }); } this._classes = []; this.resize(); if (options.classes) this.setClasses(options.classes); if (options.style) this.setStyle(options.style); if (options.attributionControl) this.addControl(new AttributionControl()); this.addControl(new LogoControl(), options.logoPosition); this.on('style.load', function() { if (this.transform.unmodified) { this.jumpTo(this.style.stylesheet); } this.style.update(this._classes, {transition: false}); }); this.on('data', this._onData); this.on('dataloading', this._onDataLoading); } /** * Adds a [`IControl`](#IControl) to the map, calling `control.onAdd(this)`. * * @param {IControl} control The [`IControl`](#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} `this` * @see [Display map navigation controls](https://www.mapbox.com/mapbox-gl-js/example/navigation/) */ addControl(control, position) { if (position === undefined && control.getDefaultPosition) { position = control.getDefaultPosition(); } if (position === undefined) { position = 'top-right'; } const controlElement = control.onAdd(this); 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 [`IControl`](#IControl) to remove. * @returns {Map} `this` */ removeControl(control) { control.onRemove(this); return this; } /** * Adds a Mapbox style class to the map. * * Keep in mind that these classes are used for controlling a style layer's paint properties, so are *not* reflected * in an HTML element's `class` attribute. To learn more about Mapbox style classes, read about * [Layers](https://www.mapbox.com/mapbox-gl-style-spec/#layers) in the style specification. * * **Note:** Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS. * * @param {string} klass The style class to add. * @param {Object} [options] * @param {boolean} [options.transition] If `true`, property changes will smoothly transition. * @fires change * @returns {Map} `this` */ addClass(klass, options) { util.warnOnce('Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS.'); if (this._classes.indexOf(klass) >= 0 || klass === '') return this; this._classes.push(klass); this._classOptions = options; if (this.style) this.style.updateClasses(); return this._update(true); } /** * Removes a Mapbox style class from the map. * * **Note:** Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS. * * @param {string} klass The style class to remove. * @param {Object} [options] * @param {boolean} [options.transition] If `true`, property changes will smoothly transition. * @fires change * @returns {Map} `this` */ removeClass(klass, options) { util.warnOnce('Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS.'); const i = this._classes.indexOf(klass); if (i < 0 || klass === '') return this; this._classes.splice(i, 1); this._classOptions = options; if (this.style) this.style.updateClasses(); return this._update(true); } /** * Replaces the map's existing Mapbox style classes with a new array of classes. * * **Note:** Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS. * * @param {Array<string>} klasses The style classes to set. * @param {Object} [options] * @param {boolean} [options.transition] If `true`, property changes will smoothly transition. * @fires change * @returns {Map} `this` */ setClasses(klasses, options) { util.warnOnce('Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS.'); const uniqueClasses = {}; for (let i = 0; i < klasses.length; i++) { if (klasses[i] !== '') uniqueClasses[klasses[i]] = true; } this._classes = Object.keys(uniqueClasses); this._classOptions = options; if (this.style) this.style.updateClasses(); return this._update(true); } /** * Returns a Boolean indicating whether the map has the * specified Mapbox style class. * * **Note:** Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS. * * @param {string} klass The style class to test. * @returns {boolean} `true` if the map has the specified style class. */ hasClass(klass) { util.warnOnce('Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS.'); return this._classes.indexOf(klass) >= 0; } /** * Returns the map's Mapbox style classes. * * **Note:** Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS. * * @returns {Array<string>} The map's style classes. */ getClasses() { util.warnOnce('Style classes are deprecated and will be removed in an upcoming release of Mapbox GL JS.'); return this._classes; } /** * Resizes the map according to the dimensions of its * `container` element. * * This method must be called after the map's `container` is resized by another script, * or when the map is shown after being initially hidden with CSS. * * @returns {Map} `this` */ resize() { const dimensions = this._containerDimensions(); const width = dimensions[0]; const height = dimensions[1]; this._resizeCanvas(width, height); this.transform.resize(width, height); this.painter.resize(width, height); return this .fire('movestart') .fire('move') .fire('resize') .fire('moveend'); } /** * Returns the map's geographical bounds. * * @returns {LngLatBounds} The map's geographical bounds. */ getBounds() { const bounds = new LngLatBounds( this.transform.pointLocation(new Point(0, this.transform.height)), this.transform.pointLocation(new Point(this.transform.width, 0))); if (this.transform.angle || this.transform.pitch) { bounds.extend(this.transform.pointLocation(new Point(this.transform.size.x, 0))); bounds.extend(this.transform.pointLocation(new Point(0, this.transform.size.y))); } return bounds; } /** * 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 {LngLatBoundsLike | null | undefined} lnglatbounds The maximum bounds to set. If `null` or `undefined` is provided, the function removes the map's maximum bounds. * @returns {Map} `this` */ setMaxBounds (lnglatbounds) { if (lnglatbounds) { const b = LngLatBounds.convert(lnglatbounds); this.transform.lngRange = [b.getWest(), b.getEast()]; this.transform.latRange = [b.getSouth(), b.getNorth()]; this.transform._constrain(); this._update(); } else if (lnglatbounds === null || lnglatbounds === undefined) { this.transform.lngRange = []; this.transform.latRange = []; this._update(); } return this; } /** * 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. * * @param {?number} minZoom The minimum zoom level to set (0-20). * If `null` or `undefined` is provided, the function removes the current minimum zoom (i.e. sets it to 0). * @returns {Map} `this` */ setMinZoom(minZoom) { minZoom = minZoom === null || minZoom === undefined ? defaultMinZoom : minZoom; if (minZoom >= defaultMinZoom && minZoom <= this.transform.maxZoom) { this.transform.minZoom = minZoom; this._update(); if (this.getZoom() < minZoom) this.setZoom(minZoom); return this; } else throw new Error(`minZoom must be between ${defaultMinZoom} and the current maxZoom, inclusive`); } /** * Returns the map's minimum allowable zoom level. * * @returns {number} minZoom */ getMinZoom() { return this.transform.minZoom; } /** * Sets or clears the map's maximum zoom level. * If the map's current zoom level is higher than the new maximum, * the map will zoom to the new maximum. * * @param {?number} maxZoom The maximum zoom level to set. * If `null` or `undefined` is provided, the function removes the current maximum zoom (sets it to 20). * @returns {Map} `this` */ setMaxZoom(maxZoom) { maxZoom = maxZoom === null || maxZoom === undefined ? defaultMaxZoom : maxZoom; if (maxZoom >= this.transform.minZoom) { this.transform.maxZoom = maxZoom; this._update(); if (this.getZoom() > maxZoom) this.setZoom(maxZoom); return this; } else throw new Error(`maxZoom must be greater than the current minZoom`); } /** * Returns the map's maximum allowable zoom level. * * @returns {number} maxZoom */ getMaxZoom() { return this.transform.maxZoom; } /** * Returns a [`Point`](#Point) representing pixel coordinates, relative to the map's `container`, * that correspond to the specified geographical location. * * @param {LngLatLike} lnglat The geographical location to project. * @returns {Point} The [`Point`](#Point) corresponding to `lnglat`, relative to the map's `container`. */ project(lnglat) { return this.transform.locationPoint(LngLat.convert(lnglat)); } /** * Returns a [`LngLat`](#LngLat) representing geographical coordinates that correspond * to the specified pixel coordinates. * * @param {PointLike} point The pixel coordinates to unproject. * @returns {LngLat} The [`LngLat`](#LngLat) corresponding to `point`. * @see [Show polygon information on click](https://www.mapbox.com/mapbox-gl-js/example/polygon-popup-on-click/) */ unproject(point) { return this.transform.pointLocation(Point.convert(point)); } /** * Adds a listener for events of a specified type. * * @method * @name on * @memberof Map * @instance * @param {string} type The event type to add a listen for. * @param {Function} listener The function to be called when the event is fired. * The listener function is called with the data object passed to `fire`, * extended with `target` and `type` properties. * @returns {Map} `this` */ /** * Adds a listener for events of a specified type occurring on features in a specified style layer. * * @param {string} type The event type to listen for; one of `'mousedown'`, `'mouseup'`, `'click'`, `'dblclick'`, * `'mousemove'`, `'mouseenter'`, `'mouseleave'`, `'mouseover'`, `'mouseout'`, `'contextmenu'`, `'touchstart'`, * `'touchend'`, or `'touchcancel'`. `mouseenter` and `mouseover` events are triggered when the cursor enters * a visible portion of the specified layer from outside that layer or outside the map canvas. `mouseleave` * and `mouseout` events are triggered when the cursor leaves a visible portion of the specified layer, or leaves * the map canvas. * @param {string} layer The ID of a style layer. Only events whose location is within a visible * feature in this layer will trigger the listener. The event will have a `features` property containing * an array of the matching features. * @param {Function} listener The function to be called when the event is fired. * @returns {Map} `this` */ on(type, layer, listener) { if (listener === undefined) { return super.on(type, layer); } const delegatedListener = (() => { if (type === 'mouseenter' || type === 'mouseover') { let mousein = false; const mousemove = (e) => { const features = this.queryRenderedFeatures(e.point, {layers: [layer]}); if (!features.length) { mousein = false; } else if (!mousein) { mousein = true; listener.call(this, util.extend({features}, e, {type})); } }; const mouseout = () => { mousein = false; }; return {layer, listener, delegates: {mousemove, mouseout}}; } else if (type === 'mouseleave' || type === 'mouseout') { let mousein = false; const mousemove = (e) => { const features = this.queryRenderedFeatures(e.point, {layers: [layer]}); if (features.length) { mousein = true; } else if (mousein) { mousein = false; listener.call(this, util.extend({}, e, {type})); } }; const mouseout = (e) => { if (mousein) { mousein = false; listener.call(this, util.extend({}, e, {type})); } }; return {layer, listener, delegates: {mousemove, mouseout}}; } else { const delegate = (e) => { const features = this.queryRenderedFeatures(e.point, {layers: [layer]}); if (features.length) { listener.call(this, util.extend({features}, e)); } }; return {layer, listener, delegates: {[type]: delegate}}; } })(); this._delegatedListeners = this._delegatedListeners || {}; this._delegatedListeners[type] = this._delegatedListeners[type] || []; this._delegatedListeners[type].push(delegatedListener); for (const event in delegatedListener.delegates) { this.on(event, delegatedListener.delegates[event]); } return this; } /** * Removes an event listener previously added with `Map#on`. * * @method * @name off * @memberof Map * @instance * @param {string} type The event type previously used to install the listener. * @param {Function} listener The function previously installed as a listener. * @returns {Map} `this` */ /** * Removes an event listener for layer-specific events previously added with `Map#on`. * * @param {string} type The event type previously used to install the listener. * @param {string} layer The layer ID previously used to install the listener. * @param {Function} listener The function previously installed as a listener. * @returns {Map} `this` */ off(type, layer, listener) { if (listener === undefined) { return super.off(type, layer); } if (this._delegatedListeners && this._delegatedListeners[type]) { const listeners = this._delegatedListeners[type]; for (let i = 0; i < listeners.length; i++) { const delegatedListener = listeners[i]; if (delegatedListener.layer === layer && delegatedListener.listener === listener) { for (const event in delegatedListener.delegates) { this.off(event, delegatedListener.delegates[event]); } listeners.splice(i, 1); return this; } } } } /** * Returns an array of [GeoJSON](http://geojson.org/) * [Feature objects](http://geojson.org/geojson-spec.html#feature-objects) * representing visible features that satisfy the query parameters. * * @param {PointLike|Array<PointLike>} [geometry] - The geometry of the query region: * either a single point or southwest and northeast points describing a bounding box. * Omitting this parameter (i.e. calling [`Map#queryRenderedFeatures`](#Map#queryRenderedFeatures) with zero arguments, * or with only a `parameters` argument) is equivalent to passing a bounding box encompassing the entire * map viewport. * @param {Object} [parameters] * @param {Array<string>} [parameters.layers] An array of style layer IDs for the query to inspect. * Only features within these layers will be returned. If this parameter is undefined, all layers will be checked. * @param {Array} [parameters.filter] A [filter](https://www.mapbox.com/mapbox-gl-style-spec/#types-filter) * to limit query results. * * @returns {Array<Object>} An array of [GeoJSON](http://geojson.org/) * [feature objects](http://geojson.org/geojson-spec.html#feature-objects). * * The `properties` value of each returned feature object contains the properties of its source feature. For GeoJSON sources, only * string and numeric property values are supported (i.e. `null`, `Array`, and `Object` values are not supported). * * Each feature includes a top-level `layer` property whose value is an object representing the style layer to * which the feature belongs. Layout and paint properties in this object contain values which are fully evaluated * for the given zoom level and feature. * * Features from layers whose `visibility` property is `"none"`, or from layers whose zoom range excludes the * current zoom level are not included. Symbol features that have been hidden due to text or icon collision are * not included. Features from all other layers are included, including features that may have no visible * contribution to the rendered result; for example, because the layer's opacity or color alpha component is set to * 0. * * The topmost rendered feature appears first in the returned array, and subsequent features are sorted by * descending z-order. Features that are rendered multiple times (due to wrapping across the antimeridian at low * zoom levels) are returned only once (though subject to the following caveat). * * Because features come from tiled vector data or GeoJSON data that is converted to tiles internally, feature * geometries may be split or duplicated across tile boundaries and, as a result, features may appear multiple * times in query results. For example, suppose there is a highway running through the bounding rectangle of a query. * The results of the query will be those parts of the highway that lie within the map tiles covering the bounding * rectangle, even if the highway extends into other tiles, and the portion of the highway within each map tile * will be returned as a separate feature. Similarly, a point feature near a tile boundary may appear in multiple * tiles due to tile buffering. * * @example * // Find all features at a point * var features = map.queryRenderedFeatures( * [20, 35], * { layers: ['my-layer-name'] } * ); * * @example * // Find all features within a static bounding box * var features = map.queryRenderedFeatures( * [[10, 20], [30, 50]], * { layers: ['my-layer-name'] } * ); * * @example * // Find all features within a bounding box around a point * var width = 10; * var height = 20; * var features = map.queryRenderedFeatures([ * [point.x - width / 2, point.y - height / 2], * [point.x + width / 2, point.y + height / 2] * ], { layers: ['my-layer-name'] }); * * @example * // Query all rendered features from a single layer * var features = map.queryRenderedFeatures({ layers: ['my-layer-name'] }); * @see [Get features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/queryrenderedfeatures/) * @see [Highlight features within a bounding box](https://www.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) * @see [Center the map on a clicked symbol](https://www.mapbox.com/mapbox-gl-js/example/center-on-symbol/) */ queryRenderedFeatures() { let params = {}; let geometry; if (arguments.length === 2) { geometry = arguments[0]; params = arguments[1]; } else if (arguments.length === 1 && isPointLike(arguments[0])) { geometry = arguments[0]; } else if (arguments.length === 1) { params = arguments[0]; } if (!this.style) { return []; } return this.style.queryRenderedFeatures( this._makeQueryGeometry(geometry), params, this.transform.zoom, this.transform.angle ); function isPointLike(input) { return input instanceof Point || Array.isArray(input); } } _makeQueryGeometry(pointOrBox) { if (pointOrBox === undefined) { // bounds was omitted: use full viewport pointOrBox = [ Point.convert([0, 0]), Point.convert([this.transform.width, this.transform.height]) ]; } let queryGeometry; const isPoint = pointOrBox instanceof Point || typeof pointOrBox[0] === 'number'; if (isPoint) { const point = Point.convert(pointOrBox); queryGeometry = [point]; } else { const box = [Point.convert(pointOrBox[0]), Point.convert(pointOrBox[1])]; queryGeometry = [ box[0], new Point(box[1].x, box[0].y), box[1], new Point(box[0].x, box[1].y), box[0] ]; } queryGeometry = queryGeometry.map((p) => { return this.transform.pointCoordinate(p); }); return queryGeometry; } /** * Returns an array of [GeoJSON](http://geojson.org/) * [Feature objects](http://geojson.org/geojson-spec.html#feature-objects) * representing features within the specified vector tile or GeoJSON source that satisfy the query parameters. * * @param {string} sourceID The ID of the vector tile or GeoJSON source to query. * @param {Object} [parameters] * @param {string} [parameters.sourceLayer] The name of the vector tile layer to query. *For vector tile * sources, this parameter is required.* For GeoJSON sources, it is ignored. * @param {Array} [parameters.filter] A [filter](https://www.mapbox.com/mapbox-gl-style-spec/#types-filter) * to limit query results. * * @returns {Array<Object>} An array of [GeoJSON](http://geojson.org/) * [Feature objects](http://geojson.org/geojson-spec.html#feature-objects). * * In contrast to [`Map#queryRenderedFeatures`](#Map#queryRenderedFeatures), this function * returns all features matching the query parameters, * whether or not they are rendered by the current style (i.e. visible). The domain of the query includes all currently-loaded * vector tiles and GeoJSON source tiles: this function does not check tiles outside the currently * visible viewport. * * Because features come from tiled vector data or GeoJSON data that is converted to tiles internally, feature * geometries may be split or duplicated across tile boundaries and, as a result, features may appear multiple * times in query results. For example, suppose there is a highway running through the bounding rectangle of a query. * The results of the query will be those parts of the highway that lie within the map tiles covering the bounding * rectangle, even if the highway extends into other tiles, and the portion of the highway within each map tile * will be returned as a separate feature. Similarly, a point feature near a tile boundary may appear in multiple * tiles due to tile buffering. * @see [Filter features within map view](https://www.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/) * @see [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) */ querySourceFeatures(sourceID, parameters) { return this.style.querySourceFeatures(sourceID, parameters); } /** * Updates the map's Mapbox style object with a new value. If the given * value is style JSON object, compares it against the the map's current * state and perform only the changes necessary to make the map style match * the desired state. * * @param {Object|string} style 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. * @param {Object} [options] * @param {boolean} [options.diff=true] If false, force a 'full' update, removing the current style * and adding building the given one instead of attempting a diff-based update. * @returns {Map} `this` * @see [Change a map's style](https://www.mapbox.com/mapbox-gl-js/example/setstyle/) */ setStyle(style, options) { const shouldTryDiff = (!options || options.diff !== false) && this.style && style && !(style instanceof Style) && typeof style !== 'string'; if (shouldTryDiff) { try { if (this.style.setState(style)) { this._update(true); } return this; } catch (e) { util.warnOnce(`Unable to perform style diff: ${e.message || e.error || e}. Rebuilding the style from scratch.`); } } if (this.style) { this.style.setEventedParent(null); this.style._remove(); this.off('rotate', this.style._redoPlacement); this.off('pitch', this.style._redoPlacement); } if (!style) { this.style = null; return this; } else if (style instanceof Style) { this.style = style; } else { this.style = new Style(style, this); } this.style.setEventedParent(this, {style: this.style}); this.on('rotate', this.style._redoPlacement); this.on('pitch', this.style._redoPlacement); return this; } /** * Returns the map's Mapbox style object, which can be used to recreate the map's style. * * @returns {Object} The map's style object. */ getStyle() { if (this.style) { return this.style.serialize(); } } /** * Adds a source to the map's style. * * @param {string} id The ID of the source to add. Must not conflict with existing sources. * @param {Object} source The source object, conforming to the * Mapbox Style Specification's [source definition](https://www.mapbox.com/mapbox-gl-style-spec/#sources). * @param {string} source.type The source type, which must be either one of the core Mapbox GL source types defined in the style specification or a custom type that has been added to the map with {@link Map#addSourceType}. * @fires source.add * @returns {Map} `this` * @see [Draw GeoJSON points](https://www.mapbox.com/mapbox-gl-js/example/geojson-markers/) * @see [Style circles using data-driven styling](https://www.mapbox.com/mapbox-gl-js/example/data-driven-circle-colors/) * @see [Set a point after Geocoder result](https://www.mapbox.com/mapbox-gl-js/example/point-from-geocoder-result/) */ addSource(id, source) { this.style.addSource(id, source); this._update(true); return this; } /** * Returns a Boolean indicating whether the source is loaded. * * @param {string} id The ID of the source to be checked. * @returns {boolean} A Boolean indicating whether the source is loaded. */ isSourceLoaded(id) { const source = this.style && this.style.sourceCaches[id]; if (source === undefined) { this.fire('error', { error: new Error(`There is no source with ID '${id}'`) }); return; } return source.loaded(); } /** * Adds a [custom source type](#Custom Sources), making it available for use with * {@link Map#addSource}. * @private * @param {string} name The name of the source type; source definition objects use this name in the `{type: ...}` field. * @param {Function} SourceType A {@link Source} constructor. * @param {Function} callback Called when the source type is ready or with an error argument if there is an error. */ addSourceType(name, SourceType, callback) { return this.style.addSourceType(name, SourceType, callback); } /** * Removes a source from the map's style. * * @param {string} id The ID of the source to remove. * @returns {Map} `this` */ removeSource(id) { this.style.removeSource(id); this._update(true); return this; } /** * Returns the source with the specified ID in the map's style. * * @param {string} id The ID of the source to get. * @returns {?Object} The style source with the specified ID, or `undefined` * if the ID corresponds to no existing sources. * @see [Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) * @see [Animate a point](https://www.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) * @see [Add live realtime data](https://www.mapbox.com/mapbox-gl-js/example/live-geojson/) */ getSource(id) { return this.style.getSource(id); } /** * Add an image to the style. This image can be used in `icon-image`, * `background-pattern`, `fill-pattern`, and `line-pattern`. An * {@link Map#error} event will be fired if there is not enough space in the * sprite to add this image. * * @see [Add an icon to the map](https://www.mapbox.com/mapbox-gl-js/example/add-image/) * @see [Add a generated icon to the map](https://www.mapbox.com/mapbox-gl-js/example/add-image-generated/) * @param {string} name The name of the image. * @param {HTMLImageElement|ArrayBufferView} image The image as an `HTMLImageElement` or `ArrayBufferView` (using the format of [`ImageData#data`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData/data)) * @param {Object} [options] Required if and only if passing an `ArrayBufferView` * @param {number} [options.width] The pixel width of the `ArrayBufferView` image * @param {number} [options.height] The pixel height of the `ArrayBufferView` image * @param {number} [options.pixelRatio] The ratio of pixels in the `ArrayBufferView` image to physical pixels on the screen */ addImage(name, image, options) { this.style.spriteAtlas.addImage(name, image, options); } /** * Remove an image from the style (such as one used by `icon-image` or `background-pattern`). * * @param {string} name The name of the image. */ removeImage(name) { this.style.spriteAtlas.removeImage(name); } /** * Load an image from an external URL for use with `Map#addImage`. External * domains must support [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). * * @param {string} url The URL of the image. * @param {Function} callback Called when the image has loaded or with an error argument if there is an error. * @see [Add an icon to the map](https://www.mapbox.com/mapbox-gl-js/example/add-image/) */ loadImage(url, callback) { ajax.getImage(url, callback); } /** * Adds a [Mapbox style layer](https://www.mapbox.com/mapbox-gl-style-spec/#layers) * to the map's style. * * A layer defines styling for data from a specified source. * * @param {Object} layer The style layer to add, conforming to the Mapbox Style Specification's * [layer definition](https://www.mapbox.com/mapbox-gl-style-spec/#layers). * @param {string} [before] The ID of an existing layer to insert the new layer before. * If this argument is omitted, the layer will be appended to the end of the layers array. * @returns {Map} `this` * @see [Create and style clusters](https://www.mapbox.com/mapbox-gl-js/example/cluster/) * @see [Add a vector tile source](https://www.mapbox.com/mapbox-gl-js/example/vector-source/) * @see [Add a WMS source](https://www.mapbox.com/mapbox-gl-js/example/wms/) */ addLayer(layer, before) { this.style.addLayer(layer, before); this._update(true); return this; } /** * Moves a layer to a different z-position. * * @param {string} id The ID of the layer to move. * @param {string} [beforeId] The ID of an existing layer to insert the new layer before. * If this argument is omitted, the layer will be appended to the end of the layers array. * @returns {Map} `this` */ moveLayer(id, beforeId) { this.style.moveLayer(id, beforeId); this._update(true); return this; } /** * Removes the layer with the given id from the map's style. * * If no such layer exists, an `error` event is fired. * * @param {string} id id of the layer to remove * @fires error */ removeLayer(id) { this.style.removeLayer(id); this._update(true); return this; } /** * Returns the layer with the specified ID in the map's style. * * @param {string} id The ID of the layer to get. * @returns {?Object} The layer with the specified ID, or `undefined` * if the ID corresponds to no existing layers. * @see [Filter symbols by toggling a list](https://www.mapbox.com/mapbox-gl-js/example/filter-markers/) * @see [Filter symbols by text input](https://www.mapbox.com/mapbox-gl-js/example/filter-markers-by-input/) */ getLayer(id) { return this.style.getLayer(id); } /** * Sets the filter for the specified style layer. * * @param {string} layer The ID of the layer to which the filter will be applied. * @param {Array} filter The filter, conforming to the Mapbox Style Specification's * [filter definition](https://www.mapbox.com/mapbox-gl-style-spec/#types-filter). * @returns {Map} `this` * @example * map.setFilter('my-layer', ['==', 'name', 'USA']); * @see [Filter features within map view](https://www.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/) * @see [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) * @see [Create a timeline animation](https://www.mapbox.com/mapbox-gl-js/example/timeline-animation/) */ setFilter(layer, filter) { this.style.setFilter(layer, filter); this._update(true); return this; } /** * Sets the zoom extent for the specified style layer. * * @param {string} layerId The ID of the layer to which the zoom extent will be applied. * @param {number} minzoom The minimum zoom to set (0-20). * @param {number} maxzoom The maximum zoom to set (0-20). * @returns {Map} `this` * @example * map.setLayerZoomRange('my-layer', 2, 5); */ setLayerZoomRange(layerId, minzoom, maxzoom) { this.style.setLayerZoomRange(layerId, minzoom, maxzoom); this._update(true); return this; } /** * Returns the filter applied to the specified style layer. * * @param {string} layer The ID of the style layer whose filter to get. * @returns {Array} The layer's filter. */ getFilter(layer) { return this.style.getFilter(layer); } /** * Sets the value of a paint property in the specified style layer. * * @param {string} layer The ID of the layer to set the paint property in. * @param {string} name The name of the paint property to set. * @param {*} value The value of the paint propery to set. * Must be of a type appropriate for the property, as defined in the [Mapbox Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/). * @param {string=} klass A style class specifier for the paint property. * @returns {Map} `this` * @example * map.setPaintProperty('my-layer', 'fill-color', '#faafee'); * @see [Change a layer's color with buttons](https://www.mapbox.com/mapbox-gl-js/example/color-switcher/) * @see [Adjust a layer's opacity](https://www.mapbox.com/mapbox-gl-js/example/adjust-layer-opacity/) * @see [Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ setPaintProperty(layer, name, value, klass) { this.style.setPaintProperty(layer, name, value, klass); this._update(true); return this; } /** * Returns the value of a paint property in the specified style layer. * * @param {string} layer The ID of the layer to get the paint property from. * @param {string} name The name of a paint property to get. * @param {string=} klass A class specifier for the paint property. * @returns {*} The value of the specified paint property. */ getPaintProperty(layer, name, klass) { return this.style.getPaintProperty(layer, name, klass); } /** * Sets the value of a layout property in the specified style layer. *