diff --git a/src/ol/map.js b/src/ol/map.js
index 7308c66ccb..dbb82b2e99 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -1,38 +1,12 @@
-// FIXME recheck layer/map projection compatibility when projection changes
-// FIXME layer renderers should skip when they can't reproject
-// FIXME add tilt and height?
-
goog.provide('ol.Map');
goog.require('ol');
-goog.require('ol.Collection');
-goog.require('ol.CollectionEventType');
-goog.require('ol.MapBrowserEvent');
-goog.require('ol.MapBrowserEventHandler');
-goog.require('ol.MapBrowserEventType');
-goog.require('ol.MapEvent');
-goog.require('ol.MapEventType');
-goog.require('ol.MapProperty');
-goog.require('ol.Object');
-goog.require('ol.ObjectEventType');
+goog.require('ol.PluggableMap');
goog.require('ol.PluginType');
-goog.require('ol.TileQueue');
-goog.require('ol.View');
-goog.require('ol.ViewHint');
-goog.require('ol.asserts');
goog.require('ol.control');
-goog.require('ol.dom');
-goog.require('ol.events');
-goog.require('ol.events.Event');
-goog.require('ol.events.EventType');
-goog.require('ol.extent');
-goog.require('ol.functions');
-goog.require('ol.has');
goog.require('ol.interaction');
-goog.require('ol.layer.Group');
goog.require('ol.obj');
goog.require('ol.plugins');
-goog.require('ol.renderer.Type');
goog.require('ol.renderer.canvas.ImageLayer');
goog.require('ol.renderer.canvas.Map');
goog.require('ol.renderer.canvas.TileLayer');
@@ -42,9 +16,6 @@ goog.require('ol.renderer.webgl.ImageLayer');
goog.require('ol.renderer.webgl.Map');
goog.require('ol.renderer.webgl.TileLayer');
goog.require('ol.renderer.webgl.VectorLayer');
-goog.require('ol.size');
-goog.require('ol.structs.PriorityQueue');
-goog.require('ol.transform');
if (ol.ENABLE_CANVAS) {
@@ -67,51 +38,6 @@ if (ol.ENABLE_WEBGL) {
}
-/**
- * @const
- * @type {string}
- */
-ol.OL_URL = 'https://openlayers.org/';
-
-
-/**
- * @const
- * @type {string}
- */
-ol.OL_LOGO_URL = 'data:image/png;base64,' +
- 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' +
- 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' +
- 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' +
- 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' +
- 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' +
- 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' +
- 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' +
- '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' +
- 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' +
- 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' +
- 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' +
- 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' +
- 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' +
- 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' +
- 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' +
- 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' +
- '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' +
- 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' +
- 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' +
- 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' +
- 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC';
-
-
-/**
- * @type {Array.
}
- * @const
- */
-ol.DEFAULT_RENDERER_TYPES = [
- ol.renderer.Type.CANVAS,
- ol.renderer.Type.WEBGL
-];
-
-
/**
* @classdesc
* The map is the core component of OpenLayers. For a map to render, a view,
@@ -152,7 +78,7 @@ ol.DEFAULT_RENDERER_TYPES = [
* contain further groups, and so on.
*
* @constructor
- * @extends {ol.Object}
+ * @extends {ol.PluggableMap}
* @param {olx.MapOptions} options Map options.
* @fires ol.MapBrowserEvent
* @fires ol.MapEvent
@@ -161,1401 +87,14 @@ ol.DEFAULT_RENDERER_TYPES = [
* @api
*/
ol.Map = function(options) {
-
- ol.Object.call(this);
-
- var optionsInternal = ol.Map.createOptionsInternal(options);
-
- /**
- * @type {boolean}
- * @private
- */
- this.loadTilesWhileAnimating_ =
- options.loadTilesWhileAnimating !== undefined ?
- options.loadTilesWhileAnimating : false;
-
- /**
- * @type {boolean}
- * @private
- */
- this.loadTilesWhileInteracting_ =
- options.loadTilesWhileInteracting !== undefined ?
- options.loadTilesWhileInteracting : false;
-
- /**
- * @private
- * @type {number}
- */
- this.pixelRatio_ = options.pixelRatio !== undefined ?
- options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
-
- /**
- * @private
- * @type {Object.}
- */
- this.logos_ = optionsInternal.logos;
-
- /**
- * @private
- * @type {number|undefined}
- */
- this.animationDelayKey_;
-
- /**
- * @private
- */
- this.animationDelay_ = function() {
- this.animationDelayKey_ = undefined;
- this.renderFrame_.call(this, Date.now());
- }.bind(this);
-
- /**
- * @private
- * @type {ol.Transform}
- */
- this.coordinateToPixelTransform_ = ol.transform.create();
-
- /**
- * @private
- * @type {ol.Transform}
- */
- this.pixelToCoordinateTransform_ = ol.transform.create();
-
- /**
- * @private
- * @type {number}
- */
- this.frameIndex_ = 0;
-
- /**
- * @private
- * @type {?olx.FrameState}
- */
- this.frameState_ = null;
-
- /**
- * The extent at the previous 'moveend' event.
- * @private
- * @type {ol.Extent}
- */
- this.previousExtent_ = null;
-
- /**
- * @private
- * @type {?ol.EventsKey}
- */
- this.viewPropertyListenerKey_ = null;
-
- /**
- * @private
- * @type {?ol.EventsKey}
- */
- this.viewChangeListenerKey_ = null;
-
- /**
- * @private
- * @type {Array.}
- */
- this.layerGroupPropertyListenerKeys_ = null;
-
- /**
- * @private
- * @type {Element}
- */
- this.viewport_ = document.createElement('DIV');
- this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : '');
- this.viewport_.style.position = 'relative';
- this.viewport_.style.overflow = 'hidden';
- this.viewport_.style.width = '100%';
- this.viewport_.style.height = '100%';
- // prevent page zoom on IE >= 10 browsers
- this.viewport_.style.msTouchAction = 'none';
- this.viewport_.style.touchAction = 'none';
-
- /**
- * @private
- * @type {!Element}
- */
- this.overlayContainer_ = document.createElement('DIV');
- this.overlayContainer_.className = 'ol-overlaycontainer';
- this.viewport_.appendChild(this.overlayContainer_);
-
- /**
- * @private
- * @type {!Element}
- */
- this.overlayContainerStopEvent_ = document.createElement('DIV');
- this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
- var overlayEvents = [
- ol.events.EventType.CLICK,
- ol.events.EventType.DBLCLICK,
- ol.events.EventType.MOUSEDOWN,
- ol.events.EventType.TOUCHSTART,
- ol.events.EventType.MSPOINTERDOWN,
- ol.MapBrowserEventType.POINTERDOWN,
- ol.events.EventType.MOUSEWHEEL,
- ol.events.EventType.WHEEL
- ];
- for (var i = 0, ii = overlayEvents.length; i < ii; ++i) {
- ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i],
- ol.events.Event.stopPropagation);
+ options = ol.obj.assign({}, options);
+ if (!options.controls) {
+ options.controls = ol.control.defaults();
}
- this.viewport_.appendChild(this.overlayContainerStopEvent_);
-
- /**
- * @private
- * @type {ol.MapBrowserEventHandler}
- */
- this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this, options.moveTolerance);
- for (var key in ol.MapBrowserEventType) {
- ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEventType[key],
- this.handleMapBrowserEvent, this);
+ if (!options.interactions) {
+ options.interactions = ol.interaction.defaults();
}
- /**
- * @private
- * @type {Element|Document}
- */
- this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
-
- /**
- * @private
- * @type {Array.}
- */
- this.keyHandlerKeys_ = null;
-
- ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
- this.handleBrowserEvent, this);
- ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
- this.handleBrowserEvent, this);
-
- /**
- * @type {ol.Collection.}
- * @private
- */
- this.controls_ = optionsInternal.controls;
-
- /**
- * @type {ol.Collection.}
- * @private
- */
- this.interactions_ = optionsInternal.interactions;
-
- /**
- * @type {ol.Collection.}
- * @private
- */
- this.overlays_ = optionsInternal.overlays;
-
- /**
- * A lookup of overlays by id.
- * @private
- * @type {Object.}
- */
- this.overlayIdIndex_ = {};
-
- /**
- * @type {ol.renderer.Map}
- * @private
- */
- this.renderer_ = optionsInternal.mapRendererPlugin.create(this.viewport_, this);
-
- /**
- * @type {function(Event)|undefined}
- * @private
- */
- this.handleResize_;
-
- /**
- * @private
- * @type {ol.Coordinate}
- */
- this.focus_ = null;
-
- /**
- * @private
- * @type {Array.}
- */
- this.postRenderFunctions_ = [];
-
- /**
- * @private
- * @type {ol.TileQueue}
- */
- this.tileQueue_ = new ol.TileQueue(
- this.getTilePriority.bind(this),
- this.handleTileChange_.bind(this));
-
- /**
- * Uids of features to skip at rendering time.
- * @type {Object.}
- * @private
- */
- this.skippedFeatureUids_ = {};
-
- ol.events.listen(
- this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
- this.handleLayerGroupChanged_, this);
- ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
- this.handleViewChanged_, this);
- ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
- this.handleSizeChanged_, this);
- ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
- this.handleTargetChanged_, this);
-
- // setProperties will trigger the rendering of the map if the map
- // is "defined" already.
- this.setProperties(optionsInternal.values);
-
- this.controls_.forEach(
- /**
- * @param {ol.control.Control} control Control.
- * @this {ol.Map}
- */
- function(control) {
- control.setMap(this);
- }, this);
-
- ol.events.listen(this.controls_, ol.CollectionEventType.ADD,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- event.element.setMap(this);
- }, this);
-
- ol.events.listen(this.controls_, ol.CollectionEventType.REMOVE,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- event.element.setMap(null);
- }, this);
-
- this.interactions_.forEach(
- /**
- * @param {ol.interaction.Interaction} interaction Interaction.
- * @this {ol.Map}
- */
- function(interaction) {
- interaction.setMap(this);
- }, this);
-
- ol.events.listen(this.interactions_, ol.CollectionEventType.ADD,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- event.element.setMap(this);
- }, this);
-
- ol.events.listen(this.interactions_, ol.CollectionEventType.REMOVE,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- event.element.setMap(null);
- }, this);
-
- this.overlays_.forEach(this.addOverlayInternal_, this);
-
- ol.events.listen(this.overlays_, ol.CollectionEventType.ADD,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
- }, this);
-
- ol.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
- /**
- * @param {ol.Collection.Event} event Collection event.
- */
- function(event) {
- var overlay = /** @type {ol.Overlay} */ (event.element);
- var id = overlay.getId();
- if (id !== undefined) {
- delete this.overlayIdIndex_[id.toString()];
- }
- event.element.setMap(null);
- }, this);
-
-};
-ol.inherits(ol.Map, ol.Object);
-
-
-/**
- * Add the given control to the map.
- * @param {ol.control.Control} control Control.
- * @api
- */
-ol.Map.prototype.addControl = function(control) {
- this.getControls().push(control);
-};
-
-
-/**
- * Add the given interaction to the map.
- * @param {ol.interaction.Interaction} interaction Interaction to add.
- * @api
- */
-ol.Map.prototype.addInteraction = function(interaction) {
- this.getInteractions().push(interaction);
-};
-
-
-/**
- * Adds the given layer to the top of this map. If you want to add a layer
- * elsewhere in the stack, use `getLayers()` and the methods available on
- * {@link ol.Collection}.
- * @param {ol.layer.Base} layer Layer.
- * @api
- */
-ol.Map.prototype.addLayer = function(layer) {
- var layers = this.getLayerGroup().getLayers();
- layers.push(layer);
-};
-
-
-/**
- * Add the given overlay to the map.
- * @param {ol.Overlay} overlay Overlay.
- * @api
- */
-ol.Map.prototype.addOverlay = function(overlay) {
- this.getOverlays().push(overlay);
-};
-
-
-/**
- * This deals with map's overlay collection changes.
- * @param {ol.Overlay} overlay Overlay.
- * @private
- */
-ol.Map.prototype.addOverlayInternal_ = function(overlay) {
- var id = overlay.getId();
- if (id !== undefined) {
- this.overlayIdIndex_[id.toString()] = overlay;
- }
- overlay.setMap(this);
-};
-
-
-/**
- *
- * @inheritDoc
- */
-ol.Map.prototype.disposeInternal = function() {
- this.mapBrowserEventHandler_.dispose();
- this.renderer_.dispose();
- ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
- this.handleBrowserEvent, this);
- ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
- this.handleBrowserEvent, this);
- if (this.handleResize_ !== undefined) {
- window.removeEventListener(ol.events.EventType.RESIZE,
- this.handleResize_, false);
- this.handleResize_ = undefined;
- }
- if (this.animationDelayKey_) {
- cancelAnimationFrame(this.animationDelayKey_);
- this.animationDelayKey_ = undefined;
- }
- this.setTarget(null);
- ol.Object.prototype.disposeInternal.call(this);
-};
-
-
-/**
- * Detect features that intersect a pixel on the viewport, and execute a
- * callback with each intersecting feature. Layers included in the detection can
- * be configured through the `layerFilter` option in `opt_options`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, (ol.Feature|ol.render.Feature),
- * ol.layer.Layer): T} callback Feature callback. The callback will be
- * called with two arguments. The first argument is one
- * {@link ol.Feature feature} or
- * {@link ol.render.Feature render feature} at the pixel, the second is
- * the {@link ol.layer.Layer layer} of the feature and will be null for
- * unmanaged layers. To stop detection, callback functions can return a
- * truthy value.
- * @param {olx.AtPixelOptions=} opt_options Optional options.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T
- * @api
- */
-ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) {
- if (!this.frameState_) {
- return;
- }
- var coordinate = this.getCoordinateFromPixel(pixel);
- opt_options = opt_options !== undefined ? opt_options : {};
- var hitTolerance = opt_options.hitTolerance !== undefined ?
- opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
- var layerFilter = opt_options.layerFilter !== undefined ?
- opt_options.layerFilter : ol.functions.TRUE;
- return this.renderer_.forEachFeatureAtCoordinate(
- coordinate, this.frameState_, hitTolerance, callback, null,
- layerFilter, null);
-};
-
-
-/**
- * Get all features that intersect a pixel on the viewport.
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.AtPixelOptions=} opt_options Optional options.
- * @return {Array.} The detected features or
- * `null` if none were found.
- * @api
- */
-ol.Map.prototype.getFeaturesAtPixel = function(pixel, opt_options) {
- var features = null;
- this.forEachFeatureAtPixel(pixel, function(feature) {
- if (!features) {
- features = [];
- }
- features.push(feature);
- }, opt_options);
- return features;
-};
-
-/**
- * Detect layers that have a color value at a pixel on the viewport, and
- * execute a callback with each matching layer. Layers included in the
- * detection can be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback
- * Layer callback. This callback will receive two arguments: first is the
- * {@link ol.layer.Layer layer}, second argument is an array representing
- * [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types
- * that do not currently support this argument. To stop detection, callback
- * functions can return a truthy value.
- * @param {S=} opt_this Value to use as `this` when executing `callback`.
- * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
- * filter function. The filter function will receive one argument, the
- * {@link ol.layer.Layer layer-candidate} and it should return a boolean
- * value. Only layers which are visible and for which this function returns
- * `true` will be tested for features. By default, all visible layers will
- * be tested.
- * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
- * @return {T|undefined} Callback result, i.e. the return value of last
- * callback execution, or the first truthy callback return value.
- * @template S,T,U
- * @api
- */
-ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
- if (!this.frameState_) {
- return;
- }
- var thisArg = opt_this !== undefined ? opt_this : null;
- var layerFilter = opt_layerFilter !== undefined ?
- opt_layerFilter : ol.functions.TRUE;
- var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
- return this.renderer_.forEachLayerAtPixel(
- pixel, this.frameState_, callback, thisArg,
- layerFilter, thisArg2);
-};
-
-
-/**
- * Detect if features intersect a pixel on the viewport. Layers included in the
- * detection can be configured through `opt_layerFilter`.
- * @param {ol.Pixel} pixel Pixel.
- * @param {olx.AtPixelOptions=} opt_options Optional options.
- * @return {boolean} Is there a feature at the given pixel?
- * @template U
- * @api
- */
-ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_options) {
- if (!this.frameState_) {
- return false;
- }
- var coordinate = this.getCoordinateFromPixel(pixel);
- opt_options = opt_options !== undefined ? opt_options : {};
- var layerFilter = opt_options.layerFilter !== undefined ?
- opt_options.layerFilter : ol.functions.TRUE;
- var hitTolerance = opt_options.hitTolerance !== undefined ?
- opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
- return this.renderer_.hasFeatureAtCoordinate(
- coordinate, this.frameState_, hitTolerance, layerFilter, null);
-};
-
-
-/**
- * Returns the coordinate in view projection for a browser event.
- * @param {Event} event Event.
- * @return {ol.Coordinate} Coordinate.
- * @api
- */
-ol.Map.prototype.getEventCoordinate = function(event) {
- return this.getCoordinateFromPixel(this.getEventPixel(event));
-};
-
-
-/**
- * Returns the map pixel position for a browser event relative to the viewport.
- * @param {Event} event Event.
- * @return {ol.Pixel} Pixel.
- * @api
- */
-ol.Map.prototype.getEventPixel = function(event) {
- var viewportPosition = this.viewport_.getBoundingClientRect();
- var eventPosition = event.changedTouches ? event.changedTouches[0] : event;
- return [
- eventPosition.clientX - viewportPosition.left,
- eventPosition.clientY - viewportPosition.top
- ];
-};
-
-
-/**
- * Get the target in which this map is rendered.
- * Note that this returns what is entered as an option or in setTarget:
- * if that was an element, it returns an element; if a string, it returns that.
- * @return {Element|string|undefined} The Element or id of the Element that the
- * map is rendered in.
- * @observable
- * @api
- */
-ol.Map.prototype.getTarget = function() {
- return /** @type {Element|string|undefined} */ (
- this.get(ol.MapProperty.TARGET));
-};
-
-
-/**
- * Get the DOM element into which this map is rendered. In contrast to
- * `getTarget` this method always return an `Element`, or `null` if the
- * map has no target.
- * @return {Element} The element that the map is rendered in.
- * @api
- */
-ol.Map.prototype.getTargetElement = function() {
- var target = this.getTarget();
- if (target !== undefined) {
- return typeof target === 'string' ?
- document.getElementById(target) :
- target;
- } else {
- return null;
- }
-};
-
-
-/**
- * Get the coordinate for a given pixel. This returns a coordinate in the
- * map view projection.
- * @param {ol.Pixel} pixel Pixel position in the map viewport.
- * @return {ol.Coordinate} The coordinate for the pixel position.
- * @api
- */
-ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
- var frameState = this.frameState_;
- if (!frameState) {
- return null;
- } else {
- return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
- }
-};
-
-
-/**
- * Get the map controls. Modifying this collection changes the controls
- * associated with the map.
- * @return {ol.Collection.} Controls.
- * @api
- */
-ol.Map.prototype.getControls = function() {
- return this.controls_;
-};
-
-
-/**
- * Get the map overlays. Modifying this collection changes the overlays
- * associated with the map.
- * @return {ol.Collection.} Overlays.
- * @api
- */
-ol.Map.prototype.getOverlays = function() {
- return this.overlays_;
-};
-
-
-/**
- * Get an overlay by its identifier (the value returned by overlay.getId()).
- * Note that the index treats string and numeric identifiers as the same. So
- * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
- * @param {string|number} id Overlay identifier.
- * @return {ol.Overlay} Overlay.
- * @api
- */
-ol.Map.prototype.getOverlayById = function(id) {
- var overlay = this.overlayIdIndex_[id.toString()];
- return overlay !== undefined ? overlay : null;
-};
-
-
-/**
- * Get the map interactions. Modifying this collection changes the interactions
- * associated with the map.
- *
- * Interactions are used for e.g. pan, zoom and rotate.
- * @return {ol.Collection.} Interactions.
- * @api
- */
-ol.Map.prototype.getInteractions = function() {
- return this.interactions_;
-};
-
-
-/**
- * Get the layergroup associated with this map.
- * @return {ol.layer.Group} A layer group containing the layers in this map.
- * @observable
- * @api
- */
-ol.Map.prototype.getLayerGroup = function() {
- return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
-};
-
-
-/**
- * Get the collection of layers associated with this map.
- * @return {!ol.Collection.} Layers.
- * @api
- */
-ol.Map.prototype.getLayers = function() {
- var layers = this.getLayerGroup().getLayers();
- return layers;
-};
-
-
-/**
- * Get the pixel for a coordinate. This takes a coordinate in the map view
- * projection and returns the corresponding pixel.
- * @param {ol.Coordinate} coordinate A map coordinate.
- * @return {ol.Pixel} A pixel position in the map viewport.
- * @api
- */
-ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
- var frameState = this.frameState_;
- if (!frameState) {
- return null;
- } else {
- return ol.transform.apply(frameState.coordinateToPixelTransform,
- coordinate.slice(0, 2));
- }
-};
-
-
-/**
- * Get the map renderer.
- * @return {ol.renderer.Map} Renderer
- */
-ol.Map.prototype.getRenderer = function() {
- return this.renderer_;
-};
-
-
-/**
- * Get the size of this map.
- * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
- * @observable
- * @api
- */
-ol.Map.prototype.getSize = function() {
- return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
-};
-
-
-/**
- * Get the view associated with this map. A view manages properties such as
- * center and resolution.
- * @return {ol.View} The view that controls this map.
- * @observable
- * @api
- */
-ol.Map.prototype.getView = function() {
- return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
-};
-
-
-/**
- * Get the element that serves as the map viewport.
- * @return {Element} Viewport.
- * @api
- */
-ol.Map.prototype.getViewport = function() {
- return this.viewport_;
-};
-
-
-/**
- * Get the element that serves as the container for overlays. Elements added to
- * this container will let mousedown and touchstart events through to the map,
- * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
- * events.
- * @return {!Element} The map's overlay container.
- */
-ol.Map.prototype.getOverlayContainer = function() {
- return this.overlayContainer_;
-};
-
-
-/**
- * Get the element that serves as a container for overlays that don't allow
- * event propagation. Elements added to this container won't let mousedown and
- * touchstart events through to the map, so clicks and gestures on an overlay
- * don't trigger any {@link ol.MapBrowserEvent}.
- * @return {!Element} The map's overlay container that stops events.
- */
-ol.Map.prototype.getOverlayContainerStopEvent = function() {
- return this.overlayContainerStopEvent_;
-};
-
-
-/**
- * @param {ol.Tile} tile Tile.
- * @param {string} tileSourceKey Tile source key.
- * @param {ol.Coordinate} tileCenter Tile center.
- * @param {number} tileResolution Tile resolution.
- * @return {number} Tile priority.
- */
-ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) {
- // Filter out tiles at higher zoom levels than the current zoom level, or that
- // are outside the visible extent.
- var frameState = this.frameState_;
- if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
- return ol.structs.PriorityQueue.DROP;
- }
- if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
- return ol.structs.PriorityQueue.DROP;
- }
- // Prioritize the highest zoom level tiles closest to the focus.
- // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
- // Within a zoom level, tiles are prioritized by the distance in pixels
- // between the center of the tile and the focus. The factor of 65536 means
- // that the prioritization should behave as desired for tiles up to
- // 65536 * Math.log(2) = 45426 pixels from the focus.
- var deltaX = tileCenter[0] - frameState.focus[0];
- var deltaY = tileCenter[1] - frameState.focus[1];
- return 65536 * Math.log(tileResolution) +
- Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
-};
-
-
-/**
- * @param {Event} browserEvent Browser event.
- * @param {string=} opt_type Type.
- */
-ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
- var type = opt_type || browserEvent.type;
- var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
- this.handleMapBrowserEvent(mapBrowserEvent);
-};
-
-
-/**
- * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
- */
-ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
- if (!this.frameState_) {
- // With no view defined, we cannot translate pixels into geographical
- // coordinates so interactions cannot be used.
- return;
- }
- this.focus_ = mapBrowserEvent.coordinate;
- mapBrowserEvent.frameState = this.frameState_;
- var interactionsArray = this.getInteractions().getArray();
- var i;
- if (this.dispatchEvent(mapBrowserEvent) !== false) {
- for (i = interactionsArray.length - 1; i >= 0; i--) {
- var interaction = interactionsArray[i];
- if (!interaction.getActive()) {
- continue;
- }
- var cont = interaction.handleEvent(mapBrowserEvent);
- if (!cont) {
- break;
- }
- }
- }
-};
-
-
-/**
- * @protected
- */
-ol.Map.prototype.handlePostRender = function() {
-
- var frameState = this.frameState_;
-
- // Manage the tile queue
- // Image loads are expensive and a limited resource, so try to use them
- // efficiently:
- // * When the view is static we allow a large number of parallel tile loads
- // to complete the frame as quickly as possible.
- // * When animating or interacting, image loads can cause janks, so we reduce
- // the maximum number of loads per frame and limit the number of parallel
- // tile loads to remain reactive to view changes and to reduce the chance of
- // loading tiles that will quickly disappear from view.
- var tileQueue = this.tileQueue_;
- if (!tileQueue.isEmpty()) {
- var maxTotalLoading = 16;
- var maxNewLoads = maxTotalLoading;
- if (frameState) {
- var hints = frameState.viewHints;
- if (hints[ol.ViewHint.ANIMATING]) {
- maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0;
- maxNewLoads = 2;
- }
- if (hints[ol.ViewHint.INTERACTING]) {
- maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0;
- maxNewLoads = 2;
- }
- }
- if (tileQueue.getTilesLoading() < maxTotalLoading) {
- tileQueue.reprioritize(); // FIXME only call if view has changed
- tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
- }
- }
-
- var postRenderFunctions = this.postRenderFunctions_;
- var i, ii;
- for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
- postRenderFunctions[i](this, frameState);
- }
- postRenderFunctions.length = 0;
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleSizeChanged_ = function() {
- this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleTargetChanged_ = function() {
- // target may be undefined, null, a string or an Element.
- // If it's a string we convert it to an Element before proceeding.
- // If it's not now an Element we remove the viewport from the DOM.
- // If it's an Element we append the viewport element to it.
-
- var targetElement;
- if (this.getTarget()) {
- targetElement = this.getTargetElement();
- }
-
- if (this.keyHandlerKeys_) {
- for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
- ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
- }
- this.keyHandlerKeys_ = null;
- }
-
- if (!targetElement) {
- ol.dom.removeNode(this.viewport_);
- if (this.handleResize_ !== undefined) {
- window.removeEventListener(ol.events.EventType.RESIZE,
- this.handleResize_, false);
- this.handleResize_ = undefined;
- }
- } else {
- targetElement.appendChild(this.viewport_);
-
- var keyboardEventTarget = !this.keyboardEventTarget_ ?
- targetElement : this.keyboardEventTarget_;
- this.keyHandlerKeys_ = [
- ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN,
- this.handleBrowserEvent, this),
- ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS,
- this.handleBrowserEvent, this)
- ];
-
- if (!this.handleResize_) {
- this.handleResize_ = this.updateSize.bind(this);
- window.addEventListener(ol.events.EventType.RESIZE,
- this.handleResize_, false);
- }
- }
-
- this.updateSize();
- // updateSize calls setSize, so no need to call this.render
- // ourselves here.
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleTileChange_ = function() {
- this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleViewPropertyChanged_ = function() {
- this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleViewChanged_ = function() {
- if (this.viewPropertyListenerKey_) {
- ol.events.unlistenByKey(this.viewPropertyListenerKey_);
- this.viewPropertyListenerKey_ = null;
- }
- if (this.viewChangeListenerKey_) {
- ol.events.unlistenByKey(this.viewChangeListenerKey_);
- this.viewChangeListenerKey_ = null;
- }
- var view = this.getView();
- if (view) {
- this.viewport_.setAttribute('data-view', ol.getUid(view));
- this.viewPropertyListenerKey_ = ol.events.listen(
- view, ol.ObjectEventType.PROPERTYCHANGE,
- this.handleViewPropertyChanged_, this);
- this.viewChangeListenerKey_ = ol.events.listen(
- view, ol.events.EventType.CHANGE,
- this.handleViewPropertyChanged_, this);
- }
- this.render();
-};
-
-
-/**
- * @private
- */
-ol.Map.prototype.handleLayerGroupChanged_ = function() {
- if (this.layerGroupPropertyListenerKeys_) {
- this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey);
- this.layerGroupPropertyListenerKeys_ = null;
- }
- var layerGroup = this.getLayerGroup();
- if (layerGroup) {
- this.layerGroupPropertyListenerKeys_ = [
- ol.events.listen(
- layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
- this.render, this),
- ol.events.listen(
- layerGroup, ol.events.EventType.CHANGE,
- this.render, this)
- ];
- }
- this.render();
-};
-
-
-/**
- * @return {boolean} Is rendered.
- */
-ol.Map.prototype.isRendered = function() {
- return !!this.frameState_;
-};
-
-
-/**
- * Requests an immediate render in a synchronous manner.
- * @api
- */
-ol.Map.prototype.renderSync = function() {
- if (this.animationDelayKey_) {
- cancelAnimationFrame(this.animationDelayKey_);
- }
- this.animationDelay_();
-};
-
-
-/**
- * Request a map rendering (at the next animation frame).
- * @api
- */
-ol.Map.prototype.render = function() {
- if (this.animationDelayKey_ === undefined) {
- this.animationDelayKey_ = requestAnimationFrame(
- this.animationDelay_);
- }
-};
-
-
-/**
- * Remove the given control from the map.
- * @param {ol.control.Control} control Control.
- * @return {ol.control.Control|undefined} The removed control (or undefined
- * if the control was not found).
- * @api
- */
-ol.Map.prototype.removeControl = function(control) {
- return this.getControls().remove(control);
-};
-
-
-/**
- * Remove the given interaction from the map.
- * @param {ol.interaction.Interaction} interaction Interaction to remove.
- * @return {ol.interaction.Interaction|undefined} The removed interaction (or
- * undefined if the interaction was not found).
- * @api
- */
-ol.Map.prototype.removeInteraction = function(interaction) {
- return this.getInteractions().remove(interaction);
-};
-
-
-/**
- * Removes the given layer from the map.
- * @param {ol.layer.Base} layer Layer.
- * @return {ol.layer.Base|undefined} The removed layer (or undefined if the
- * layer was not found).
- * @api
- */
-ol.Map.prototype.removeLayer = function(layer) {
- var layers = this.getLayerGroup().getLayers();
- return layers.remove(layer);
-};
-
-
-/**
- * Remove the given overlay from the map.
- * @param {ol.Overlay} overlay Overlay.
- * @return {ol.Overlay|undefined} The removed overlay (or undefined
- * if the overlay was not found).
- * @api
- */
-ol.Map.prototype.removeOverlay = function(overlay) {
- return this.getOverlays().remove(overlay);
-};
-
-
-/**
- * @param {number} time Time.
- * @private
- */
-ol.Map.prototype.renderFrame_ = function(time) {
- var i, ii, viewState;
-
- var size = this.getSize();
- var view = this.getView();
- var extent = ol.extent.createEmpty();
- var previousFrameState = this.frameState_;
- /** @type {?olx.FrameState} */
- var frameState = null;
- if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
- var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
- var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
- var layerStates = {};
- for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
- layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
- }
- viewState = view.getState();
- frameState = /** @type {olx.FrameState} */ ({
- animate: false,
- attributions: {},
- coordinateToPixelTransform: this.coordinateToPixelTransform_,
- extent: extent,
- focus: !this.focus_ ? viewState.center : this.focus_,
- index: this.frameIndex_++,
- layerStates: layerStates,
- layerStatesArray: layerStatesArray,
- logos: ol.obj.assign({}, this.logos_),
- pixelRatio: this.pixelRatio_,
- pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
- postRenderFunctions: [],
- size: size,
- skippedFeatureUids: this.skippedFeatureUids_,
- tileQueue: this.tileQueue_,
- time: time,
- usedTiles: {},
- viewState: viewState,
- viewHints: viewHints,
- wantedTiles: {}
- });
- }
-
- if (frameState) {
- frameState.extent = ol.extent.getForViewAndSize(viewState.center,
- viewState.resolution, viewState.rotation, frameState.size, extent);
- }
-
- this.frameState_ = frameState;
- this.renderer_.renderFrame(frameState);
-
- if (frameState) {
- if (frameState.animate) {
- this.render();
- }
- Array.prototype.push.apply(
- this.postRenderFunctions_, frameState.postRenderFunctions);
-
- if (previousFrameState) {
- var moveStart = !this.previousExtent_ ||
- (!ol.extent.isEmpty(this.previousExtent_) &&
- !ol.extent.equals(frameState.extent, this.previousExtent_));
- if (moveStart) {
- this.dispatchEvent(
- new ol.MapEvent(ol.MapEventType.MOVESTART, this, previousFrameState));
- this.previousExtent_ = ol.extent.createOrUpdateEmpty(this.previousExtent_);
- }
- }
-
- var idle = this.previousExtent_ &&
- !frameState.viewHints[ol.ViewHint.ANIMATING] &&
- !frameState.viewHints[ol.ViewHint.INTERACTING] &&
- !ol.extent.equals(frameState.extent, this.previousExtent_);
-
- if (idle) {
- this.dispatchEvent(
- new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
- ol.extent.clone(frameState.extent, this.previousExtent_);
- }
- }
-
- this.dispatchEvent(
- new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
-
- setTimeout(this.handlePostRender.bind(this), 0);
-
-};
-
-
-/**
- * Sets the layergroup of this map.
- * @param {ol.layer.Group} layerGroup A layer group containing the layers in
- * this map.
- * @observable
- * @api
- */
-ol.Map.prototype.setLayerGroup = function(layerGroup) {
- this.set(ol.MapProperty.LAYERGROUP, layerGroup);
-};
-
-
-/**
- * Set the size of this map.
- * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
- * @observable
- * @api
- */
-ol.Map.prototype.setSize = function(size) {
- this.set(ol.MapProperty.SIZE, size);
-};
-
-
-/**
- * Set the target element to render this map into.
- * @param {Element|string|undefined} target The Element or id of the Element
- * that the map is rendered in.
- * @observable
- * @api
- */
-ol.Map.prototype.setTarget = function(target) {
- this.set(ol.MapProperty.TARGET, target);
-};
-
-
-/**
- * Set the view for this map.
- * @param {ol.View} view The view that controls this map.
- * @observable
- * @api
- */
-ol.Map.prototype.setView = function(view) {
- this.set(ol.MapProperty.VIEW, view);
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- */
-ol.Map.prototype.skipFeature = function(feature) {
- var featureUid = ol.getUid(feature).toString();
- this.skippedFeatureUids_[featureUid] = true;
- this.render();
-};
-
-
-/**
- * Force a recalculation of the map viewport size. This should be called when
- * third-party code changes the size of the map viewport.
- * @api
- */
-ol.Map.prototype.updateSize = function() {
- var targetElement = this.getTargetElement();
-
- if (!targetElement) {
- this.setSize(undefined);
- } else {
- var computedStyle = getComputedStyle(targetElement);
- this.setSize([
- targetElement.offsetWidth -
- parseFloat(computedStyle['borderLeftWidth']) -
- parseFloat(computedStyle['paddingLeft']) -
- parseFloat(computedStyle['paddingRight']) -
- parseFloat(computedStyle['borderRightWidth']),
- targetElement.offsetHeight -
- parseFloat(computedStyle['borderTopWidth']) -
- parseFloat(computedStyle['paddingTop']) -
- parseFloat(computedStyle['paddingBottom']) -
- parseFloat(computedStyle['borderBottomWidth'])
- ]);
- }
-};
-
-
-/**
- * @param {ol.Feature} feature Feature.
- */
-ol.Map.prototype.unskipFeature = function(feature) {
- var featureUid = ol.getUid(feature).toString();
- delete this.skippedFeatureUids_[featureUid];
- this.render();
-};
-
-
-/**
- * @param {olx.MapOptions} options Map options.
- * @return {ol.MapOptionsInternal} Internal map options.
- */
-ol.Map.createOptionsInternal = function(options) {
-
- /**
- * @type {Element|Document}
- */
- var keyboardEventTarget = null;
- if (options.keyboardEventTarget !== undefined) {
- keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
- document.getElementById(options.keyboardEventTarget) :
- options.keyboardEventTarget;
- }
-
- /**
- * @type {Object.}
- */
- var values = {};
-
- var logos = {};
- if (options.logo === undefined ||
- (typeof options.logo === 'boolean' && options.logo)) {
- logos[ol.OL_LOGO_URL] = ol.OL_URL;
- } else {
- var logo = options.logo;
- if (typeof logo === 'string') {
- logos[logo] = '';
- } else if (logo instanceof HTMLElement) {
- logos[ol.getUid(logo).toString()] = logo;
- } else if (logo) {
- ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
- ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
- logos[logo.src] = logo.href;
- }
- }
-
- var layerGroup = (options.layers instanceof ol.layer.Group) ?
- options.layers : new ol.layer.Group({layers: options.layers});
- values[ol.MapProperty.LAYERGROUP] = layerGroup;
-
- values[ol.MapProperty.TARGET] = options.target;
-
- values[ol.MapProperty.VIEW] = options.view !== undefined ?
- options.view : new ol.View();
-
- /**
- * @type {Array.}
- */
- var rendererTypes;
- if (options.renderer !== undefined) {
- if (Array.isArray(options.renderer)) {
- rendererTypes = options.renderer;
- } else if (typeof options.renderer === 'string') {
- rendererTypes = [options.renderer];
- } else {
- ol.asserts.assert(false, 46); // Incorrect format for `renderer` option
- }
- if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) {
- rendererTypes = rendererTypes.concat(ol.DEFAULT_RENDERER_TYPES);
- }
- } else {
- rendererTypes = ol.DEFAULT_RENDERER_TYPES;
- }
-
- /**
- * @type {olx.MapRendererPlugin}
- */
- var mapRendererPlugin;
-
- var mapRendererPlugins = ol.plugins.getMapRendererPlugins();
- outer: for (var i = 0, ii = rendererTypes.length; i < ii; ++i) {
- var rendererType = rendererTypes[i];
- for (var j = 0, jj = mapRendererPlugins.length; j < jj; ++j) {
- var candidate = mapRendererPlugins[j];
- if (candidate.handles(rendererType)) {
- mapRendererPlugin = candidate;
- break outer;
- }
- }
- }
-
- if (!mapRendererPlugin) {
- throw new Error('Unable to create a map renderer for types: ' + rendererTypes.join(', '));
- }
-
- var controls;
- if (options.controls !== undefined) {
- if (Array.isArray(options.controls)) {
- controls = new ol.Collection(options.controls.slice());
- } else {
- ol.asserts.assert(options.controls instanceof ol.Collection,
- 47); // Expected `controls` to be an array or an `ol.Collection`
- controls = options.controls;
- }
- } else {
- controls = ol.control.defaults();
- }
-
- var interactions;
- if (options.interactions !== undefined) {
- if (Array.isArray(options.interactions)) {
- interactions = new ol.Collection(options.interactions.slice());
- } else {
- ol.asserts.assert(options.interactions instanceof ol.Collection,
- 48); // Expected `interactions` to be an array or an `ol.Collection`
- interactions = options.interactions;
- }
- } else {
- interactions = ol.interaction.defaults();
- }
-
- var overlays;
- if (options.overlays !== undefined) {
- if (Array.isArray(options.overlays)) {
- overlays = new ol.Collection(options.overlays.slice());
- } else {
- ol.asserts.assert(options.overlays instanceof ol.Collection,
- 49); // Expected `overlays` to be an array or an `ol.Collection`
- overlays = options.overlays;
- }
- } else {
- overlays = new ol.Collection();
- }
-
- return {
- controls: controls,
- interactions: interactions,
- keyboardEventTarget: keyboardEventTarget,
- logos: logos,
- overlays: overlays,
- mapRendererPlugin: mapRendererPlugin,
- values: values
- };
-
+ ol.PluggableMap.call(this, options);
};
+ol.inherits(ol.Map, ol.PluggableMap);
diff --git a/src/ol/pluggablemap.js b/src/ol/pluggablemap.js
new file mode 100644
index 0000000000..74a93b75af
--- /dev/null
+++ b/src/ol/pluggablemap.js
@@ -0,0 +1,1477 @@
+goog.provide('ol.PluggableMap');
+
+goog.require('ol');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEventType');
+goog.require('ol.MapBrowserEvent');
+goog.require('ol.MapBrowserEventHandler');
+goog.require('ol.MapBrowserEventType');
+goog.require('ol.MapEvent');
+goog.require('ol.MapEventType');
+goog.require('ol.MapProperty');
+goog.require('ol.Object');
+goog.require('ol.ObjectEventType');
+goog.require('ol.TileQueue');
+goog.require('ol.View');
+goog.require('ol.ViewHint');
+goog.require('ol.asserts');
+goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.Event');
+goog.require('ol.events.EventType');
+goog.require('ol.extent');
+goog.require('ol.functions');
+goog.require('ol.has');
+goog.require('ol.layer.Group');
+goog.require('ol.obj');
+goog.require('ol.plugins');
+goog.require('ol.renderer.Type');
+goog.require('ol.size');
+goog.require('ol.structs.PriorityQueue');
+goog.require('ol.transform');
+
+
+/**
+ * @constructor
+ * @extends {ol.Object}
+ * @param {olx.MapOptions} options Map options.
+ * @fires ol.MapBrowserEvent
+ * @fires ol.MapEvent
+ * @fires ol.render.Event#postcompose
+ * @fires ol.render.Event#precompose
+ * @api
+ */
+ol.PluggableMap = function(options) {
+
+ ol.Object.call(this);
+
+ var optionsInternal = ol.PluggableMap.createOptionsInternal(options);
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.loadTilesWhileAnimating_ =
+ options.loadTilesWhileAnimating !== undefined ?
+ options.loadTilesWhileAnimating : false;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.loadTilesWhileInteracting_ =
+ options.loadTilesWhileInteracting !== undefined ?
+ options.loadTilesWhileInteracting : false;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.pixelRatio_ = options.pixelRatio !== undefined ?
+ options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;
+
+ /**
+ * @private
+ * @type {Object.}
+ */
+ this.logos_ = optionsInternal.logos;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.animationDelayKey_;
+
+ /**
+ * @private
+ */
+ this.animationDelay_ = function() {
+ this.animationDelayKey_ = undefined;
+ this.renderFrame_.call(this, Date.now());
+ }.bind(this);
+
+ /**
+ * @private
+ * @type {ol.Transform}
+ */
+ this.coordinateToPixelTransform_ = ol.transform.create();
+
+ /**
+ * @private
+ * @type {ol.Transform}
+ */
+ this.pixelToCoordinateTransform_ = ol.transform.create();
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.frameIndex_ = 0;
+
+ /**
+ * @private
+ * @type {?olx.FrameState}
+ */
+ this.frameState_ = null;
+
+ /**
+ * The extent at the previous 'moveend' event.
+ * @private
+ * @type {ol.Extent}
+ */
+ this.previousExtent_ = null;
+
+ /**
+ * @private
+ * @type {?ol.EventsKey}
+ */
+ this.viewPropertyListenerKey_ = null;
+
+ /**
+ * @private
+ * @type {?ol.EventsKey}
+ */
+ this.viewChangeListenerKey_ = null;
+
+ /**
+ * @private
+ * @type {Array.}
+ */
+ this.layerGroupPropertyListenerKeys_ = null;
+
+ /**
+ * @private
+ * @type {Element}
+ */
+ this.viewport_ = document.createElement('DIV');
+ this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : '');
+ this.viewport_.style.position = 'relative';
+ this.viewport_.style.overflow = 'hidden';
+ this.viewport_.style.width = '100%';
+ this.viewport_.style.height = '100%';
+ // prevent page zoom on IE >= 10 browsers
+ this.viewport_.style.msTouchAction = 'none';
+ this.viewport_.style.touchAction = 'none';
+
+ /**
+ * @private
+ * @type {!Element}
+ */
+ this.overlayContainer_ = document.createElement('DIV');
+ this.overlayContainer_.className = 'ol-overlaycontainer';
+ this.viewport_.appendChild(this.overlayContainer_);
+
+ /**
+ * @private
+ * @type {!Element}
+ */
+ this.overlayContainerStopEvent_ = document.createElement('DIV');
+ this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
+ var overlayEvents = [
+ ol.events.EventType.CLICK,
+ ol.events.EventType.DBLCLICK,
+ ol.events.EventType.MOUSEDOWN,
+ ol.events.EventType.TOUCHSTART,
+ ol.events.EventType.MSPOINTERDOWN,
+ ol.MapBrowserEventType.POINTERDOWN,
+ ol.events.EventType.MOUSEWHEEL,
+ ol.events.EventType.WHEEL
+ ];
+ for (var i = 0, ii = overlayEvents.length; i < ii; ++i) {
+ ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i],
+ ol.events.Event.stopPropagation);
+ }
+ this.viewport_.appendChild(this.overlayContainerStopEvent_);
+
+ /**
+ * @private
+ * @type {ol.MapBrowserEventHandler}
+ */
+ this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this, options.moveTolerance);
+ for (var key in ol.MapBrowserEventType) {
+ ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEventType[key],
+ this.handleMapBrowserEvent, this);
+ }
+
+ /**
+ * @private
+ * @type {Element|Document}
+ */
+ this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
+
+ /**
+ * @private
+ * @type {Array.}
+ */
+ this.keyHandlerKeys_ = null;
+
+ ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
+ this.handleBrowserEvent, this);
+ ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
+ this.handleBrowserEvent, this);
+
+ /**
+ * @type {ol.Collection.}
+ * @protected
+ */
+ this.controls = optionsInternal.controls || new ol.Collection();
+
+ /**
+ * @type {ol.Collection.}
+ * @protected
+ */
+ this.interactions = optionsInternal.interactions || new ol.Collection();
+
+ /**
+ * @type {ol.Collection.}
+ * @private
+ */
+ this.overlays_ = optionsInternal.overlays;
+
+ /**
+ * A lookup of overlays by id.
+ * @private
+ * @type {Object.}
+ */
+ this.overlayIdIndex_ = {};
+
+ /**
+ * @type {ol.renderer.Map}
+ * @private
+ */
+ this.renderer_ = optionsInternal.mapRendererPlugin.create(this.viewport_, this);
+
+ /**
+ * @type {function(Event)|undefined}
+ * @private
+ */
+ this.handleResize_;
+
+ /**
+ * @private
+ * @type {ol.Coordinate}
+ */
+ this.focus_ = null;
+
+ /**
+ * @private
+ * @type {Array.}
+ */
+ this.postRenderFunctions_ = [];
+
+ /**
+ * @private
+ * @type {ol.TileQueue}
+ */
+ this.tileQueue_ = new ol.TileQueue(
+ this.getTilePriority.bind(this),
+ this.handleTileChange_.bind(this));
+
+ /**
+ * Uids of features to skip at rendering time.
+ * @type {Object.}
+ * @private
+ */
+ this.skippedFeatureUids_ = {};
+
+ ol.events.listen(
+ this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
+ this.handleLayerGroupChanged_, this);
+ ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
+ this.handleViewChanged_, this);
+ ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
+ this.handleSizeChanged_, this);
+ ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
+ this.handleTargetChanged_, this);
+
+ // setProperties will trigger the rendering of the map if the map
+ // is "defined" already.
+ this.setProperties(optionsInternal.values);
+
+ this.controls.forEach(
+ /**
+ * @param {ol.control.Control} control Control.
+ * @this {ol.Map}
+ */
+ function(control) {
+ control.setMap(this);
+ }, this);
+
+ ol.events.listen(this.controls, ol.CollectionEventType.ADD,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ event.element.setMap(this);
+ }, this);
+
+ ol.events.listen(this.controls, ol.CollectionEventType.REMOVE,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ event.element.setMap(null);
+ }, this);
+
+ this.interactions.forEach(
+ /**
+ * @param {ol.interaction.Interaction} interaction Interaction.
+ * @this {ol.Map}
+ */
+ function(interaction) {
+ interaction.setMap(this);
+ }, this);
+
+ ol.events.listen(this.interactions, ol.CollectionEventType.ADD,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ event.element.setMap(this);
+ }, this);
+
+ ol.events.listen(this.interactions, ol.CollectionEventType.REMOVE,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ event.element.setMap(null);
+ }, this);
+
+ this.overlays_.forEach(this.addOverlayInternal_, this);
+
+ ol.events.listen(this.overlays_, ol.CollectionEventType.ADD,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
+ }, this);
+
+ ol.events.listen(this.overlays_, ol.CollectionEventType.REMOVE,
+ /**
+ * @param {ol.Collection.Event} event Collection event.
+ */
+ function(event) {
+ var overlay = /** @type {ol.Overlay} */ (event.element);
+ var id = overlay.getId();
+ if (id !== undefined) {
+ delete this.overlayIdIndex_[id.toString()];
+ }
+ event.element.setMap(null);
+ }, this);
+
+};
+ol.inherits(ol.PluggableMap, ol.Object);
+
+
+/**
+ * Add the given control to the map.
+ * @param {ol.control.Control} control Control.
+ * @api
+ */
+ol.PluggableMap.prototype.addControl = function(control) {
+ this.getControls().push(control);
+};
+
+
+/**
+ * Add the given interaction to the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to add.
+ * @api
+ */
+ol.PluggableMap.prototype.addInteraction = function(interaction) {
+ this.getInteractions().push(interaction);
+};
+
+
+/**
+ * Adds the given layer to the top of this map. If you want to add a layer
+ * elsewhere in the stack, use `getLayers()` and the methods available on
+ * {@link ol.Collection}.
+ * @param {ol.layer.Base} layer Layer.
+ * @api
+ */
+ol.PluggableMap.prototype.addLayer = function(layer) {
+ var layers = this.getLayerGroup().getLayers();
+ layers.push(layer);
+};
+
+
+/**
+ * Add the given overlay to the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @api
+ */
+ol.PluggableMap.prototype.addOverlay = function(overlay) {
+ this.getOverlays().push(overlay);
+};
+
+
+/**
+ * This deals with map's overlay collection changes.
+ * @param {ol.Overlay} overlay Overlay.
+ * @private
+ */
+ol.PluggableMap.prototype.addOverlayInternal_ = function(overlay) {
+ var id = overlay.getId();
+ if (id !== undefined) {
+ this.overlayIdIndex_[id.toString()] = overlay;
+ }
+ overlay.setMap(this);
+};
+
+
+/**
+ *
+ * @inheritDoc
+ */
+ol.PluggableMap.prototype.disposeInternal = function() {
+ this.mapBrowserEventHandler_.dispose();
+ this.renderer_.dispose();
+ ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
+ this.handleBrowserEvent, this);
+ ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
+ this.handleBrowserEvent, this);
+ if (this.handleResize_ !== undefined) {
+ window.removeEventListener(ol.events.EventType.RESIZE,
+ this.handleResize_, false);
+ this.handleResize_ = undefined;
+ }
+ if (this.animationDelayKey_) {
+ cancelAnimationFrame(this.animationDelayKey_);
+ this.animationDelayKey_ = undefined;
+ }
+ this.setTarget(null);
+ ol.Object.prototype.disposeInternal.call(this);
+};
+
+
+/**
+ * Detect features that intersect a pixel on the viewport, and execute a
+ * callback with each intersecting feature. Layers included in the detection can
+ * be configured through the `layerFilter` option in `opt_options`.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {function(this: S, (ol.Feature|ol.render.Feature),
+ * ol.layer.Layer): T} callback Feature callback. The callback will be
+ * called with two arguments. The first argument is one
+ * {@link ol.Feature feature} or
+ * {@link ol.render.Feature render feature} at the pixel, the second is
+ * the {@link ol.layer.Layer layer} of the feature and will be null for
+ * unmanaged layers. To stop detection, callback functions can return a
+ * truthy value.
+ * @param {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {T|undefined} Callback result, i.e. the return value of last
+ * callback execution, or the first truthy callback return value.
+ * @template S,T
+ * @api
+ */
+ol.PluggableMap.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) {
+ if (!this.frameState_) {
+ return;
+ }
+ var coordinate = this.getCoordinateFromPixel(pixel);
+ opt_options = opt_options !== undefined ? opt_options : {};
+ var hitTolerance = opt_options.hitTolerance !== undefined ?
+ opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
+ var layerFilter = opt_options.layerFilter !== undefined ?
+ opt_options.layerFilter : ol.functions.TRUE;
+ return this.renderer_.forEachFeatureAtCoordinate(
+ coordinate, this.frameState_, hitTolerance, callback, null,
+ layerFilter, null);
+};
+
+
+/**
+ * Get all features that intersect a pixel on the viewport.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {Array.} The detected features or
+ * `null` if none were found.
+ * @api
+ */
+ol.PluggableMap.prototype.getFeaturesAtPixel = function(pixel, opt_options) {
+ var features = null;
+ this.forEachFeatureAtPixel(pixel, function(feature) {
+ if (!features) {
+ features = [];
+ }
+ features.push(feature);
+ }, opt_options);
+ return features;
+};
+
+/**
+ * Detect layers that have a color value at a pixel on the viewport, and
+ * execute a callback with each matching layer. Layers included in the
+ * detection can be configured through `opt_layerFilter`.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback
+ * Layer callback. This callback will receive two arguments: first is the
+ * {@link ol.layer.Layer layer}, second argument is an array representing
+ * [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types
+ * that do not currently support this argument. To stop detection, callback
+ * functions can return a truthy value.
+ * @param {S=} opt_this Value to use as `this` when executing `callback`.
+ * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
+ * filter function. The filter function will receive one argument, the
+ * {@link ol.layer.Layer layer-candidate} and it should return a boolean
+ * value. Only layers which are visible and for which this function returns
+ * `true` will be tested for features. By default, all visible layers will
+ * be tested.
+ * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
+ * @return {T|undefined} Callback result, i.e. the return value of last
+ * callback execution, or the first truthy callback return value.
+ * @template S,T,U
+ * @api
+ */
+ol.PluggableMap.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
+ if (!this.frameState_) {
+ return;
+ }
+ var thisArg = opt_this !== undefined ? opt_this : null;
+ var layerFilter = opt_layerFilter !== undefined ?
+ opt_layerFilter : ol.functions.TRUE;
+ var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
+ return this.renderer_.forEachLayerAtPixel(
+ pixel, this.frameState_, callback, thisArg,
+ layerFilter, thisArg2);
+};
+
+
+/**
+ * Detect if features intersect a pixel on the viewport. Layers included in the
+ * detection can be configured through `opt_layerFilter`.
+ * @param {ol.Pixel} pixel Pixel.
+ * @param {olx.AtPixelOptions=} opt_options Optional options.
+ * @return {boolean} Is there a feature at the given pixel?
+ * @template U
+ * @api
+ */
+ol.PluggableMap.prototype.hasFeatureAtPixel = function(pixel, opt_options) {
+ if (!this.frameState_) {
+ return false;
+ }
+ var coordinate = this.getCoordinateFromPixel(pixel);
+ opt_options = opt_options !== undefined ? opt_options : {};
+ var layerFilter = opt_options.layerFilter !== undefined ?
+ opt_options.layerFilter : ol.functions.TRUE;
+ var hitTolerance = opt_options.hitTolerance !== undefined ?
+ opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
+ return this.renderer_.hasFeatureAtCoordinate(
+ coordinate, this.frameState_, hitTolerance, layerFilter, null);
+};
+
+
+/**
+ * Returns the coordinate in view projection for a browser event.
+ * @param {Event} event Event.
+ * @return {ol.Coordinate} Coordinate.
+ * @api
+ */
+ol.PluggableMap.prototype.getEventCoordinate = function(event) {
+ return this.getCoordinateFromPixel(this.getEventPixel(event));
+};
+
+
+/**
+ * Returns the map pixel position for a browser event relative to the viewport.
+ * @param {Event} event Event.
+ * @return {ol.Pixel} Pixel.
+ * @api
+ */
+ol.PluggableMap.prototype.getEventPixel = function(event) {
+ var viewportPosition = this.viewport_.getBoundingClientRect();
+ var eventPosition = event.changedTouches ? event.changedTouches[0] : event;
+ return [
+ eventPosition.clientX - viewportPosition.left,
+ eventPosition.clientY - viewportPosition.top
+ ];
+};
+
+
+/**
+ * Get the target in which this map is rendered.
+ * Note that this returns what is entered as an option or in setTarget:
+ * if that was an element, it returns an element; if a string, it returns that.
+ * @return {Element|string|undefined} The Element or id of the Element that the
+ * map is rendered in.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.getTarget = function() {
+ return /** @type {Element|string|undefined} */ (
+ this.get(ol.MapProperty.TARGET));
+};
+
+
+/**
+ * Get the DOM element into which this map is rendered. In contrast to
+ * `getTarget` this method always return an `Element`, or `null` if the
+ * map has no target.
+ * @return {Element} The element that the map is rendered in.
+ * @api
+ */
+ol.PluggableMap.prototype.getTargetElement = function() {
+ var target = this.getTarget();
+ if (target !== undefined) {
+ return typeof target === 'string' ?
+ document.getElementById(target) :
+ target;
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * Get the coordinate for a given pixel. This returns a coordinate in the
+ * map view projection.
+ * @param {ol.Pixel} pixel Pixel position in the map viewport.
+ * @return {ol.Coordinate} The coordinate for the pixel position.
+ * @api
+ */
+ol.PluggableMap.prototype.getCoordinateFromPixel = function(pixel) {
+ var frameState = this.frameState_;
+ if (!frameState) {
+ return null;
+ } else {
+ return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
+ }
+};
+
+
+/**
+ * Get the map controls. Modifying this collection changes the controls
+ * associated with the map.
+ * @return {ol.Collection.} Controls.
+ * @api
+ */
+ol.PluggableMap.prototype.getControls = function() {
+ return this.controls;
+};
+
+
+/**
+ * Get the map overlays. Modifying this collection changes the overlays
+ * associated with the map.
+ * @return {ol.Collection.} Overlays.
+ * @api
+ */
+ol.PluggableMap.prototype.getOverlays = function() {
+ return this.overlays_;
+};
+
+
+/**
+ * Get an overlay by its identifier (the value returned by overlay.getId()).
+ * Note that the index treats string and numeric identifiers as the same. So
+ * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
+ * @param {string|number} id Overlay identifier.
+ * @return {ol.Overlay} Overlay.
+ * @api
+ */
+ol.PluggableMap.prototype.getOverlayById = function(id) {
+ var overlay = this.overlayIdIndex_[id.toString()];
+ return overlay !== undefined ? overlay : null;
+};
+
+
+/**
+ * Get the map interactions. Modifying this collection changes the interactions
+ * associated with the map.
+ *
+ * Interactions are used for e.g. pan, zoom and rotate.
+ * @return {ol.Collection.} Interactions.
+ * @api
+ */
+ol.PluggableMap.prototype.getInteractions = function() {
+ return this.interactions;
+};
+
+
+/**
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.Group} A layer group containing the layers in this map.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.getLayerGroup = function() {
+ return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
+};
+
+
+/**
+ * Get the collection of layers associated with this map.
+ * @return {!ol.Collection.} Layers.
+ * @api
+ */
+ol.PluggableMap.prototype.getLayers = function() {
+ var layers = this.getLayerGroup().getLayers();
+ return layers;
+};
+
+
+/**
+ * Get the pixel for a coordinate. This takes a coordinate in the map view
+ * projection and returns the corresponding pixel.
+ * @param {ol.Coordinate} coordinate A map coordinate.
+ * @return {ol.Pixel} A pixel position in the map viewport.
+ * @api
+ */
+ol.PluggableMap.prototype.getPixelFromCoordinate = function(coordinate) {
+ var frameState = this.frameState_;
+ if (!frameState) {
+ return null;
+ } else {
+ return ol.transform.apply(frameState.coordinateToPixelTransform,
+ coordinate.slice(0, 2));
+ }
+};
+
+
+/**
+ * Get the map renderer.
+ * @return {ol.renderer.Map} Renderer
+ */
+ol.PluggableMap.prototype.getRenderer = function() {
+ return this.renderer_;
+};
+
+
+/**
+ * Get the size of this map.
+ * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.getSize = function() {
+ return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
+};
+
+
+/**
+ * Get the view associated with this map. A view manages properties such as
+ * center and resolution.
+ * @return {ol.View} The view that controls this map.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.getView = function() {
+ return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
+};
+
+
+/**
+ * Get the element that serves as the map viewport.
+ * @return {Element} Viewport.
+ * @api
+ */
+ol.PluggableMap.prototype.getViewport = function() {
+ return this.viewport_;
+};
+
+
+/**
+ * Get the element that serves as the container for overlays. Elements added to
+ * this container will let mousedown and touchstart events through to the map,
+ * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
+ * events.
+ * @return {!Element} The map's overlay container.
+ */
+ol.PluggableMap.prototype.getOverlayContainer = function() {
+ return this.overlayContainer_;
+};
+
+
+/**
+ * Get the element that serves as a container for overlays that don't allow
+ * event propagation. Elements added to this container won't let mousedown and
+ * touchstart events through to the map, so clicks and gestures on an overlay
+ * don't trigger any {@link ol.MapBrowserEvent}.
+ * @return {!Element} The map's overlay container that stops events.
+ */
+ol.PluggableMap.prototype.getOverlayContainerStopEvent = function() {
+ return this.overlayContainerStopEvent_;
+};
+
+
+/**
+ * @param {ol.Tile} tile Tile.
+ * @param {string} tileSourceKey Tile source key.
+ * @param {ol.Coordinate} tileCenter Tile center.
+ * @param {number} tileResolution Tile resolution.
+ * @return {number} Tile priority.
+ */
+ol.PluggableMap.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) {
+ // Filter out tiles at higher zoom levels than the current zoom level, or that
+ // are outside the visible extent.
+ var frameState = this.frameState_;
+ if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
+ return ol.structs.PriorityQueue.DROP;
+ }
+ if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
+ return ol.structs.PriorityQueue.DROP;
+ }
+ // Prioritize the highest zoom level tiles closest to the focus.
+ // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
+ // Within a zoom level, tiles are prioritized by the distance in pixels
+ // between the center of the tile and the focus. The factor of 65536 means
+ // that the prioritization should behave as desired for tiles up to
+ // 65536 * Math.log(2) = 45426 pixels from the focus.
+ var deltaX = tileCenter[0] - frameState.focus[0];
+ var deltaY = tileCenter[1] - frameState.focus[1];
+ return 65536 * Math.log(tileResolution) +
+ Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
+};
+
+
+/**
+ * @param {Event} browserEvent Browser event.
+ * @param {string=} opt_type Type.
+ */
+ol.PluggableMap.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
+ var type = opt_type || browserEvent.type;
+ var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
+ this.handleMapBrowserEvent(mapBrowserEvent);
+};
+
+
+/**
+ * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
+ */
+ol.PluggableMap.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
+ if (!this.frameState_) {
+ // With no view defined, we cannot translate pixels into geographical
+ // coordinates so interactions cannot be used.
+ return;
+ }
+ this.focus_ = mapBrowserEvent.coordinate;
+ mapBrowserEvent.frameState = this.frameState_;
+ var interactionsArray = this.getInteractions().getArray();
+ var i;
+ if (this.dispatchEvent(mapBrowserEvent) !== false) {
+ for (i = interactionsArray.length - 1; i >= 0; i--) {
+ var interaction = interactionsArray[i];
+ if (!interaction.getActive()) {
+ continue;
+ }
+ var cont = interaction.handleEvent(mapBrowserEvent);
+ if (!cont) {
+ break;
+ }
+ }
+ }
+};
+
+
+/**
+ * @protected
+ */
+ol.PluggableMap.prototype.handlePostRender = function() {
+
+ var frameState = this.frameState_;
+
+ // Manage the tile queue
+ // Image loads are expensive and a limited resource, so try to use them
+ // efficiently:
+ // * When the view is static we allow a large number of parallel tile loads
+ // to complete the frame as quickly as possible.
+ // * When animating or interacting, image loads can cause janks, so we reduce
+ // the maximum number of loads per frame and limit the number of parallel
+ // tile loads to remain reactive to view changes and to reduce the chance of
+ // loading tiles that will quickly disappear from view.
+ var tileQueue = this.tileQueue_;
+ if (!tileQueue.isEmpty()) {
+ var maxTotalLoading = 16;
+ var maxNewLoads = maxTotalLoading;
+ if (frameState) {
+ var hints = frameState.viewHints;
+ if (hints[ol.ViewHint.ANIMATING]) {
+ maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0;
+ maxNewLoads = 2;
+ }
+ if (hints[ol.ViewHint.INTERACTING]) {
+ maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0;
+ maxNewLoads = 2;
+ }
+ }
+ if (tileQueue.getTilesLoading() < maxTotalLoading) {
+ tileQueue.reprioritize(); // FIXME only call if view has changed
+ tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
+ }
+ }
+
+ var postRenderFunctions = this.postRenderFunctions_;
+ var i, ii;
+ for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
+ postRenderFunctions[i](this, frameState);
+ }
+ postRenderFunctions.length = 0;
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleSizeChanged_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleTargetChanged_ = function() {
+ // target may be undefined, null, a string or an Element.
+ // If it's a string we convert it to an Element before proceeding.
+ // If it's not now an Element we remove the viewport from the DOM.
+ // If it's an Element we append the viewport element to it.
+
+ var targetElement;
+ if (this.getTarget()) {
+ targetElement = this.getTargetElement();
+ }
+
+ if (this.keyHandlerKeys_) {
+ for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
+ ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
+ }
+ this.keyHandlerKeys_ = null;
+ }
+
+ if (!targetElement) {
+ ol.dom.removeNode(this.viewport_);
+ if (this.handleResize_ !== undefined) {
+ window.removeEventListener(ol.events.EventType.RESIZE,
+ this.handleResize_, false);
+ this.handleResize_ = undefined;
+ }
+ } else {
+ targetElement.appendChild(this.viewport_);
+
+ var keyboardEventTarget = !this.keyboardEventTarget_ ?
+ targetElement : this.keyboardEventTarget_;
+ this.keyHandlerKeys_ = [
+ ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN,
+ this.handleBrowserEvent, this),
+ ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS,
+ this.handleBrowserEvent, this)
+ ];
+
+ if (!this.handleResize_) {
+ this.handleResize_ = this.updateSize.bind(this);
+ window.addEventListener(ol.events.EventType.RESIZE,
+ this.handleResize_, false);
+ }
+ }
+
+ this.updateSize();
+ // updateSize calls setSize, so no need to call this.render
+ // ourselves here.
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleTileChange_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleViewPropertyChanged_ = function() {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleViewChanged_ = function() {
+ if (this.viewPropertyListenerKey_) {
+ ol.events.unlistenByKey(this.viewPropertyListenerKey_);
+ this.viewPropertyListenerKey_ = null;
+ }
+ if (this.viewChangeListenerKey_) {
+ ol.events.unlistenByKey(this.viewChangeListenerKey_);
+ this.viewChangeListenerKey_ = null;
+ }
+ var view = this.getView();
+ if (view) {
+ this.viewport_.setAttribute('data-view', ol.getUid(view));
+ this.viewPropertyListenerKey_ = ol.events.listen(
+ view, ol.ObjectEventType.PROPERTYCHANGE,
+ this.handleViewPropertyChanged_, this);
+ this.viewChangeListenerKey_ = ol.events.listen(
+ view, ol.events.EventType.CHANGE,
+ this.handleViewPropertyChanged_, this);
+ }
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.PluggableMap.prototype.handleLayerGroupChanged_ = function() {
+ if (this.layerGroupPropertyListenerKeys_) {
+ this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey);
+ this.layerGroupPropertyListenerKeys_ = null;
+ }
+ var layerGroup = this.getLayerGroup();
+ if (layerGroup) {
+ this.layerGroupPropertyListenerKeys_ = [
+ ol.events.listen(
+ layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
+ this.render, this),
+ ol.events.listen(
+ layerGroup, ol.events.EventType.CHANGE,
+ this.render, this)
+ ];
+ }
+ this.render();
+};
+
+
+/**
+ * @return {boolean} Is rendered.
+ */
+ol.PluggableMap.prototype.isRendered = function() {
+ return !!this.frameState_;
+};
+
+
+/**
+ * Requests an immediate render in a synchronous manner.
+ * @api
+ */
+ol.PluggableMap.prototype.renderSync = function() {
+ if (this.animationDelayKey_) {
+ cancelAnimationFrame(this.animationDelayKey_);
+ }
+ this.animationDelay_();
+};
+
+
+/**
+ * Request a map rendering (at the next animation frame).
+ * @api
+ */
+ol.PluggableMap.prototype.render = function() {
+ if (this.animationDelayKey_ === undefined) {
+ this.animationDelayKey_ = requestAnimationFrame(
+ this.animationDelay_);
+ }
+};
+
+
+/**
+ * Remove the given control from the map.
+ * @param {ol.control.Control} control Control.
+ * @return {ol.control.Control|undefined} The removed control (or undefined
+ * if the control was not found).
+ * @api
+ */
+ol.PluggableMap.prototype.removeControl = function(control) {
+ return this.getControls().remove(control);
+};
+
+
+/**
+ * Remove the given interaction from the map.
+ * @param {ol.interaction.Interaction} interaction Interaction to remove.
+ * @return {ol.interaction.Interaction|undefined} The removed interaction (or
+ * undefined if the interaction was not found).
+ * @api
+ */
+ol.PluggableMap.prototype.removeInteraction = function(interaction) {
+ return this.getInteractions().remove(interaction);
+};
+
+
+/**
+ * Removes the given layer from the map.
+ * @param {ol.layer.Base} layer Layer.
+ * @return {ol.layer.Base|undefined} The removed layer (or undefined if the
+ * layer was not found).
+ * @api
+ */
+ol.PluggableMap.prototype.removeLayer = function(layer) {
+ var layers = this.getLayerGroup().getLayers();
+ return layers.remove(layer);
+};
+
+
+/**
+ * Remove the given overlay from the map.
+ * @param {ol.Overlay} overlay Overlay.
+ * @return {ol.Overlay|undefined} The removed overlay (or undefined
+ * if the overlay was not found).
+ * @api
+ */
+ol.PluggableMap.prototype.removeOverlay = function(overlay) {
+ return this.getOverlays().remove(overlay);
+};
+
+
+/**
+ * @param {number} time Time.
+ * @private
+ */
+ol.PluggableMap.prototype.renderFrame_ = function(time) {
+ var i, ii, viewState;
+
+ var size = this.getSize();
+ var view = this.getView();
+ var extent = ol.extent.createEmpty();
+ var previousFrameState = this.frameState_;
+ /** @type {?olx.FrameState} */
+ var frameState = null;
+ if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
+ var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
+ var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
+ var layerStates = {};
+ for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
+ layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
+ }
+ viewState = view.getState();
+ frameState = /** @type {olx.FrameState} */ ({
+ animate: false,
+ attributions: {},
+ coordinateToPixelTransform: this.coordinateToPixelTransform_,
+ extent: extent,
+ focus: !this.focus_ ? viewState.center : this.focus_,
+ index: this.frameIndex_++,
+ layerStates: layerStates,
+ layerStatesArray: layerStatesArray,
+ logos: ol.obj.assign({}, this.logos_),
+ pixelRatio: this.pixelRatio_,
+ pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
+ postRenderFunctions: [],
+ size: size,
+ skippedFeatureUids: this.skippedFeatureUids_,
+ tileQueue: this.tileQueue_,
+ time: time,
+ usedTiles: {},
+ viewState: viewState,
+ viewHints: viewHints,
+ wantedTiles: {}
+ });
+ }
+
+ if (frameState) {
+ frameState.extent = ol.extent.getForViewAndSize(viewState.center,
+ viewState.resolution, viewState.rotation, frameState.size, extent);
+ }
+
+ this.frameState_ = frameState;
+ this.renderer_.renderFrame(frameState);
+
+ if (frameState) {
+ if (frameState.animate) {
+ this.render();
+ }
+ Array.prototype.push.apply(
+ this.postRenderFunctions_, frameState.postRenderFunctions);
+
+ if (previousFrameState) {
+ var moveStart = !this.previousExtent_ ||
+ (!ol.extent.isEmpty(this.previousExtent_) &&
+ !ol.extent.equals(frameState.extent, this.previousExtent_));
+ if (moveStart) {
+ this.dispatchEvent(
+ new ol.MapEvent(ol.MapEventType.MOVESTART, this, previousFrameState));
+ this.previousExtent_ = ol.extent.createOrUpdateEmpty(this.previousExtent_);
+ }
+ }
+
+ var idle = this.previousExtent_ &&
+ !frameState.viewHints[ol.ViewHint.ANIMATING] &&
+ !frameState.viewHints[ol.ViewHint.INTERACTING] &&
+ !ol.extent.equals(frameState.extent, this.previousExtent_);
+
+ if (idle) {
+ this.dispatchEvent(
+ new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
+ ol.extent.clone(frameState.extent, this.previousExtent_);
+ }
+ }
+
+ this.dispatchEvent(
+ new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));
+
+ setTimeout(this.handlePostRender.bind(this), 0);
+
+};
+
+
+/**
+ * Sets the layergroup of this map.
+ * @param {ol.layer.Group} layerGroup A layer group containing the layers in
+ * this map.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.setLayerGroup = function(layerGroup) {
+ this.set(ol.MapProperty.LAYERGROUP, layerGroup);
+};
+
+
+/**
+ * Set the size of this map.
+ * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.setSize = function(size) {
+ this.set(ol.MapProperty.SIZE, size);
+};
+
+
+/**
+ * Set the target element to render this map into.
+ * @param {Element|string|undefined} target The Element or id of the Element
+ * that the map is rendered in.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.setTarget = function(target) {
+ this.set(ol.MapProperty.TARGET, target);
+};
+
+
+/**
+ * Set the view for this map.
+ * @param {ol.View} view The view that controls this map.
+ * @observable
+ * @api
+ */
+ol.PluggableMap.prototype.setView = function(view) {
+ this.set(ol.MapProperty.VIEW, view);
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.PluggableMap.prototype.skipFeature = function(feature) {
+ var featureUid = ol.getUid(feature).toString();
+ this.skippedFeatureUids_[featureUid] = true;
+ this.render();
+};
+
+
+/**
+ * Force a recalculation of the map viewport size. This should be called when
+ * third-party code changes the size of the map viewport.
+ * @api
+ */
+ol.PluggableMap.prototype.updateSize = function() {
+ var targetElement = this.getTargetElement();
+
+ if (!targetElement) {
+ this.setSize(undefined);
+ } else {
+ var computedStyle = getComputedStyle(targetElement);
+ this.setSize([
+ targetElement.offsetWidth -
+ parseFloat(computedStyle['borderLeftWidth']) -
+ parseFloat(computedStyle['paddingLeft']) -
+ parseFloat(computedStyle['paddingRight']) -
+ parseFloat(computedStyle['borderRightWidth']),
+ targetElement.offsetHeight -
+ parseFloat(computedStyle['borderTopWidth']) -
+ parseFloat(computedStyle['paddingTop']) -
+ parseFloat(computedStyle['paddingBottom']) -
+ parseFloat(computedStyle['borderBottomWidth'])
+ ]);
+ }
+};
+
+
+/**
+ * @param {ol.Feature} feature Feature.
+ */
+ol.PluggableMap.prototype.unskipFeature = function(feature) {
+ var featureUid = ol.getUid(feature).toString();
+ delete this.skippedFeatureUids_[featureUid];
+ this.render();
+};
+
+
+/**
+ * @type {Array.}
+ * @const
+ */
+ol.PluggableMap.DEFAULT_RENDERER_TYPES = [
+ ol.renderer.Type.CANVAS,
+ ol.renderer.Type.WEBGL
+];
+
+
+/**
+ * @const
+ * @type {string}
+ */
+ol.PluggableMap.LOGO_URL = 'data:image/png;base64,' +
+ 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' +
+ 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' +
+ 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' +
+ 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' +
+ 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' +
+ 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' +
+ 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' +
+ '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' +
+ 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' +
+ 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' +
+ 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' +
+ 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' +
+ 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' +
+ 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' +
+ 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' +
+ 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' +
+ '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' +
+ 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' +
+ 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' +
+ 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' +
+ 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC';
+
+
+/**
+ * @param {olx.MapOptions} options Map options.
+ * @return {ol.MapOptionsInternal} Internal map options.
+ */
+ol.PluggableMap.createOptionsInternal = function(options) {
+
+ /**
+ * @type {Element|Document}
+ */
+ var keyboardEventTarget = null;
+ if (options.keyboardEventTarget !== undefined) {
+ keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
+ document.getElementById(options.keyboardEventTarget) :
+ options.keyboardEventTarget;
+ }
+
+ /**
+ * @type {Object.}
+ */
+ var values = {};
+
+ var logos = {};
+ if (options.logo === undefined ||
+ (typeof options.logo === 'boolean' && options.logo)) {
+ logos[ol.PluggableMap.LOGO_URL] = 'https://openlayers.org/';
+ } else {
+ var logo = options.logo;
+ if (typeof logo === 'string') {
+ logos[logo] = '';
+ } else if (logo instanceof HTMLElement) {
+ logos[ol.getUid(logo).toString()] = logo;
+ } else if (logo) {
+ ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
+ ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
+ logos[logo.src] = logo.href;
+ }
+ }
+
+ var layerGroup = (options.layers instanceof ol.layer.Group) ?
+ options.layers : new ol.layer.Group({layers: options.layers});
+ values[ol.MapProperty.LAYERGROUP] = layerGroup;
+
+ values[ol.MapProperty.TARGET] = options.target;
+
+ values[ol.MapProperty.VIEW] = options.view !== undefined ?
+ options.view : new ol.View();
+
+ /**
+ * @type {Array.}
+ */
+ var rendererTypes;
+
+ if (options.renderer !== undefined) {
+ if (Array.isArray(options.renderer)) {
+ rendererTypes = options.renderer;
+ } else if (typeof options.renderer === 'string') {
+ rendererTypes = [options.renderer];
+ } else {
+ ol.asserts.assert(false, 46); // Incorrect format for `renderer` option
+ }
+ if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) {
+ rendererTypes = rendererTypes.concat(ol.PluggableMap.DEFAULT_RENDERER_TYPES);
+ }
+ } else {
+ rendererTypes = ol.PluggableMap.DEFAULT_RENDERER_TYPES;
+ }
+
+ /**
+ * @type {olx.MapRendererPlugin}
+ */
+ var mapRendererPlugin;
+
+ var mapRendererPlugins = ol.plugins.getMapRendererPlugins();
+ outer: for (var i = 0, ii = rendererTypes.length; i < ii; ++i) {
+ var rendererType = rendererTypes[i];
+ for (var j = 0, jj = mapRendererPlugins.length; j < jj; ++j) {
+ var candidate = mapRendererPlugins[j];
+ if (candidate.handles(rendererType)) {
+ mapRendererPlugin = candidate;
+ break outer;
+ }
+ }
+ }
+
+ if (!mapRendererPlugin) {
+ throw new Error('Unable to create a map renderer for types: ' + rendererTypes.join(', '));
+ }
+
+ var controls;
+ if (options.controls !== undefined) {
+ if (Array.isArray(options.controls)) {
+ controls = new ol.Collection(options.controls.slice());
+ } else {
+ ol.asserts.assert(options.controls instanceof ol.Collection,
+ 47); // Expected `controls` to be an array or an `ol.Collection`
+ controls = options.controls;
+ }
+ }
+
+ var interactions;
+ if (options.interactions !== undefined) {
+ if (Array.isArray(options.interactions)) {
+ interactions = new ol.Collection(options.interactions.slice());
+ } else {
+ ol.asserts.assert(options.interactions instanceof ol.Collection,
+ 48); // Expected `interactions` to be an array or an `ol.Collection`
+ interactions = options.interactions;
+ }
+ }
+
+ var overlays;
+ if (options.overlays !== undefined) {
+ if (Array.isArray(options.overlays)) {
+ overlays = new ol.Collection(options.overlays.slice());
+ } else {
+ ol.asserts.assert(options.overlays instanceof ol.Collection,
+ 49); // Expected `overlays` to be an array or an `ol.Collection`
+ overlays = options.overlays;
+ }
+ } else {
+ overlays = new ol.Collection();
+ }
+
+ return {
+ controls: controls,
+ interactions: interactions,
+ keyboardEventTarget: keyboardEventTarget,
+ logos: logos,
+ overlays: overlays,
+ mapRendererPlugin: mapRendererPlugin,
+ values: values
+ };
+
+};
diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js
index 0d51e4fc64..7300bbd210 100644
--- a/src/ol/typedefs.js
+++ b/src/ol/typedefs.js
@@ -365,8 +365,8 @@ ol.LRUCacheEntry;
/**
- * @typedef {{controls: ol.Collection.,
- * interactions: ol.Collection.,
+ * @typedef {{controls: (ol.Collection.|undefined),
+ * interactions: (ol.Collection.|undefined),
* keyboardEventTarget: (Element|Document),
* logos: (Object.),
* overlays: ol.Collection.,