diff --git a/doc/tutorials/closure.md b/doc/tutorials/closure.md index f9c6a51aa5..f1c56d62ac 100644 --- a/doc/tutorials/closure.md +++ b/doc/tutorials/closure.md @@ -124,7 +124,7 @@ goog.require('ol.source.OSM'); /** - * @type {ol.Map} + * @type {ol.PluggableMap} */ app.map = new ol.Map({ target: 'map', diff --git a/examples/icon-sprite-webgl.js b/examples/icon-sprite-webgl.js index 3f6ceaaf4e..dfaeb8e220 100644 --- a/examples/icon-sprite-webgl.js +++ b/examples/icon-sprite-webgl.js @@ -80,7 +80,7 @@ var vector = new ol.layer.Vector({ }); var map = new ol.Map({ - renderer: /** @type {ol.renderer.Type} */ ('webgl'), + renderer: /** @type {Array} */ (['webgl', 'canvas']), layers: [vector], target: document.getElementById('map'), view: new ol.View({ diff --git a/examples/layer-clipping-webgl.js b/examples/layer-clipping-webgl.js index 0a16b56205..70b4d99c7f 100644 --- a/examples/layer-clipping-webgl.js +++ b/examples/layer-clipping-webgl.js @@ -19,7 +19,7 @@ if (!ol.has.WEBGL) { var map = new ol.Map({ layers: [osm], - renderer: /** @type {ol.renderer.Type} */ ('webgl'), + renderer: /** @type {Array} */ (['webgl', 'canvas']), target: 'map', controls: ol.control.defaults({ attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ diff --git a/examples/side-by-side.js b/examples/side-by-side.js index e62aba39a7..4567fdfb25 100644 --- a/examples/side-by-side.js +++ b/examples/side-by-side.js @@ -22,7 +22,7 @@ var map1 = new ol.Map({ if (ol.has.WEBGL) { var map2 = new ol.Map({ target: 'webglMap', - renderer: /** @type {ol.renderer.Type} */ ('webgl'), + renderer: /** @type {Array} */ (['webgl', 'canvas']), layers: [layer], view: view }); diff --git a/examples/symbol-atlas-webgl.js b/examples/symbol-atlas-webgl.js index 672a371020..dccb014246 100644 --- a/examples/symbol-atlas-webgl.js +++ b/examples/symbol-atlas-webgl.js @@ -107,7 +107,7 @@ var vector = new ol.layer.Vector({ }); var map = new ol.Map({ - renderer: /** @type {ol.renderer.Type} */ ('webgl'), + renderer: /** @type {Array} */ (['webgl', 'canvas']), layers: [vector], target: document.getElementById('map'), view: new ol.View({ diff --git a/examples/zoomslider.js b/examples/zoomslider.js index ca3f1e7df2..6d81a37e40 100644 --- a/examples/zoomslider.js +++ b/examples/zoomslider.js @@ -9,7 +9,7 @@ goog.require('ol.source.OSM'); * Helper method for map-creation. * * @param {string} divId The id of the div for the map. - * @return {ol.Map} The ol.Map instance. + * @return {ol.PluggableMap} The ol.Map instance. */ var createMap = function(divId) { var source, layer, map, zoomslider; diff --git a/externs/oli.js b/externs/oli.js index c39b57d96d..dfaf33c7dd 100644 --- a/externs/oli.js +++ b/externs/oli.js @@ -181,7 +181,7 @@ oli.MapEvent = function() {}; /** - * @type {ol.Map} + * @type {ol.PluggableMap} */ oli.MapEvent.prototype.map; @@ -229,7 +229,7 @@ oli.control.Control = function() {}; /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @return {undefined} Undefined. */ oli.control.Control.prototype.setMap = function(map) {}; diff --git a/externs/olx.js b/externs/olx.js index c002f9543c..a010c68160 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -109,7 +109,7 @@ olx.LogoOptions.prototype.src; /** - * @typedef {{map: (ol.Map|undefined), + * @typedef {{map: (ol.PluggableMap|undefined), * maxLines: (number|undefined), * strokeStyle: (ol.style.Stroke|undefined), * targetSize: (number|undefined), @@ -126,7 +126,7 @@ olx.GraticuleOptions; /** * Reference to an `ol.Map` object. - * @type {ol.Map|undefined} + * @type {ol.PluggableMap|undefined} * @api */ olx.GraticuleOptions.prototype.map; @@ -4011,7 +4011,7 @@ olx.layer.HeatmapOptions.prototype.zIndex; /** * @typedef {{opacity: (number|undefined), - * map: (ol.Map|undefined), + * map: (ol.PluggableMap|undefined), * source: (ol.source.Image|undefined), * visible: (boolean|undefined), * extent: (ol.Extent|undefined), @@ -4043,7 +4043,7 @@ olx.layer.ImageOptions.prototype.source; * layers collection, and the layer will be rendered on top. This is useful for * temporary layers. The standard way to add a layer to a map and have it * managed by the map is to use {@link ol.Map#addLayer}. - * @type {ol.Map|undefined} + * @type {ol.PluggableMap|undefined} * @api */ olx.layer.ImageOptions.prototype.map; @@ -4095,7 +4095,7 @@ olx.layer.ImageOptions.prototype.zIndex; * @typedef {{opacity: (number|undefined), * preload: (number|undefined), * source: (ol.source.Tile|undefined), - * map: (ol.Map|undefined), + * map: (ol.PluggableMap|undefined), * visible: (boolean|undefined), * extent: (ol.Extent|undefined), * minResolution: (number|undefined), @@ -4136,7 +4136,7 @@ olx.layer.TileOptions.prototype.source; * layers collection, and the layer will be rendered on top. This is useful for * temporary layers. The standard way to add a layer to a map and have it * managed by the map is to use {@link ol.Map#addLayer}. - * @type {ol.Map|undefined} + * @type {ol.PluggableMap|undefined} * @api */ olx.layer.TileOptions.prototype.map; @@ -4199,7 +4199,7 @@ olx.layer.TileOptions.prototype.zIndex; * opacity: (number|undefined), * renderBuffer: (number|undefined), * source: (ol.source.Vector|undefined), - * map: (ol.Map|undefined), + * map: (ol.PluggableMap|undefined), * style: (ol.style.Style|Array.|ol.StyleFunction|undefined), * updateWhileAnimating: (boolean|undefined), * updateWhileInteracting: (boolean|undefined), @@ -4224,7 +4224,7 @@ olx.layer.VectorOptions.prototype.renderOrder; * layers collection, and the layer will be rendered on top. This is useful for * temporary layers. The standard way to add a layer to a map and have it * managed by the map is to use {@link ol.Map#addLayer}. - * @type {ol.Map|undefined} + * @type {ol.PluggableMap|undefined} * @api */ olx.layer.VectorOptions.prototype.map; @@ -4330,7 +4330,7 @@ olx.layer.VectorOptions.prototype.zIndex; /** * @typedef {{extent: (ol.Extent|undefined), - * map: (ol.Map|undefined), + * map: (ol.PluggableMap|undefined), * minResolution: (number|undefined), * maxResolution: (number|undefined), * opacity: (number|undefined), @@ -4392,7 +4392,7 @@ olx.layer.VectorTileOptions.prototype.renderOrder; * layers collection, and the layer will be rendered on top. This is useful for * temporary layers. The standard way to add a layer to a map and have it * managed by the map is to use {@link ol.Map#addLayer}. - * @type {ol.Map|undefined} + * @type {ol.PluggableMap|undefined} * @api */ olx.layer.VectorTileOptions.prototype.map; @@ -8290,3 +8290,49 @@ olx.style.AtlasManagerOptions.prototype.maxSize; * @api */ olx.style.AtlasManagerOptions.prototype.space; + + +/** + * @typedef {{handles: function(ol.renderer.Type):boolean, + * create: function(Element, ol.PluggableMap):ol.renderer.Map}} + */ +olx.MapRendererPlugin; + + +/** + * Determine if this renderer handles the provided layer. + * @type {function(ol.renderer.Type):boolean} + * @api + */ +olx.MapRendererPlugin.prototype.handles; + + +/** + * Create the map renderer. + * @type {function(Element, ol.PluggableMap):ol.renderer.Map} + * @api + */ +olx.MapRendererPlugin.prototype.create; + + +/** + * @typedef {{handles: function(ol.renderer.Type, ol.layer.Layer):boolean, + * create: function(ol.renderer.Map, ol.layer.Layer):ol.renderer.Layer}} + */ +olx.LayerRendererPlugin; + + +/** + * Determine if this renderer handles the provided layer. + * @type {function(ol.renderer.Type, ol.layer.Layer):boolean} + * @api + */ +olx.LayerRendererPlugin.prototype.handles; + + +/** + * Create a layer renderer. + * @type {function(ol.renderer.Map, ol.layer.Layer):ol.renderer.Layer} + * @api + */ +olx.LayerRendererPlugin.prototype.create; diff --git a/externs/readme.md b/externs/readme.md index 72bf6d5556..a05104ef2e 100644 --- a/externs/readme.md +++ b/externs/readme.md @@ -53,7 +53,7 @@ For custom subclasses in applications, which can be created using `ol.inherits`, oli.control.Control = function() {}; /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @return {undefined} Undefined. */ oli.control.Control.prototype.setMap = function(map) {}; @@ -74,7 +74,7 @@ ol.control.Control = function(options) { /** * Application subclasses may override this. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @api */ ol.control.Control.prototype.setMap = function(map) { diff --git a/src/ol/canvasmap.js b/src/ol/canvasmap.js new file mode 100644 index 0000000000..93bca407e2 --- /dev/null +++ b/src/ol/canvasmap.js @@ -0,0 +1,86 @@ +goog.provide('ol.CanvasMap'); + +goog.require('ol'); +goog.require('ol.PluggableMap'); +goog.require('ol.PluginType'); +goog.require('ol.control'); +goog.require('ol.interaction'); +goog.require('ol.obj'); +goog.require('ol.plugins'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.Map'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.canvas.VectorLayer'); +goog.require('ol.renderer.canvas.VectorTileLayer'); + + +ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.canvas.Map); +ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [ + ol.renderer.canvas.ImageLayer, + ol.renderer.canvas.TileLayer, + ol.renderer.canvas.VectorLayer, + ol.renderer.canvas.VectorTileLayer +]); + + +/** + * @classdesc + * The map is the core component of OpenLayers. For a map to render, a view, + * one or more layers, and a target container are needed: + * + * var map = new ol.CanvasMap({ + * view: new ol.View({ + * center: [0, 0], + * zoom: 1 + * }), + * layers: [ + * new ol.layer.Tile({ + * source: new ol.source.OSM() + * }) + * ], + * target: 'map' + * }); + * + * The above snippet creates a map using a {@link ol.layer.Tile} to display + * {@link ol.source.OSM} OSM data and render it to a DOM element with the + * id `map`. + * + * The constructor places a viewport container (with CSS class name + * `ol-viewport`) in the target element (see `getViewport()`), and then two + * further elements within the viewport: one with CSS class name + * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with + * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` + * option of {@link ol.Overlay} for the difference). The map itself is placed in + * a further element within the viewport. + * + * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is + * provided by the library. This is what is accessed by `getLayerGroup` and + * `setLayerGroup`. Layers entered in the options are added to this group, and + * `addLayer` and `removeLayer` change the layer collection in the group. + * `getLayers` is a convenience function for `getLayerGroup().getLayers()`. + * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers + * entered in the options or added with `addLayer` can be groups, which can + * contain further groups, and so on. + * + * @constructor + * @extends {ol.PluggableMap} + * @param {olx.MapOptions} options Map options. + * @fires ol.MapBrowserEvent + * @fires ol.MapEvent + * @fires ol.render.Event#postcompose + * @fires ol.render.Event#precompose + * @api + */ +ol.CanvasMap = function(options) { + options = ol.obj.assign({}, options); + delete options.renderer; + if (!options.controls) { + options.controls = ol.control.defaults(); + } + if (!options.interactions) { + options.interactions = ol.interaction.defaults(); + } + + ol.PluggableMap.call(this, options); +}; +ol.inherits(ol.CanvasMap, ol.PluggableMap); diff --git a/src/ol/control/control.js b/src/ol/control/control.js index e3330e76ae..79922655a3 100644 --- a/src/ol/control/control.js +++ b/src/ol/control/control.js @@ -54,7 +54,7 @@ ol.control.Control = function(options) { /** * @private - * @type {ol.Map} + * @type {ol.PluggableMap} */ this.map_ = null; @@ -88,7 +88,7 @@ ol.control.Control.prototype.disposeInternal = function() { /** * Get the map associated with this control. - * @return {ol.Map} Map. + * @return {ol.PluggableMap} Map. * @api */ ol.control.Control.prototype.getMap = function() { @@ -100,7 +100,7 @@ ol.control.Control.prototype.getMap = function() { * Remove the control from its current map and attach it to the new map. * Subclasses may set up event handlers to get notified about changes to * the map here. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @override * @api */ diff --git a/src/ol/control/overviewmap.js b/src/ol/control/overviewmap.js index 1244f16de8..a692e53463 100644 --- a/src/ol/control/overviewmap.js +++ b/src/ol/control/overviewmap.js @@ -2,7 +2,7 @@ goog.provide('ol.control.OverviewMap'); goog.require('ol'); goog.require('ol.Collection'); -goog.require('ol.Map'); +goog.require('ol.PluggableMap'); goog.require('ol.MapEventType'); goog.require('ol.MapProperty'); goog.require('ol.Object'); @@ -97,10 +97,10 @@ ol.control.OverviewMap = function(opt_options) { this.ovmapDiv_.className = 'ol-overviewmap-map'; /** - * @type {ol.Map} + * @type {ol.PluggableMap} * @private */ - this.ovmap_ = new ol.Map({ + this.ovmap_ = new ol.PluggableMap({ controls: new ol.Collection(), interactions: new ol.Collection(), view: options.view @@ -551,7 +551,7 @@ ol.control.OverviewMap.prototype.getCollapsed = function() { /** * Return the overview map. - * @return {ol.Map} Overview map. + * @return {ol.PluggableMap} Overview map. * @api */ ol.control.OverviewMap.prototype.getOverviewMap = function() { diff --git a/src/ol/graticule.js b/src/ol/graticule.js index 38bc387a3b..a35eb51b41 100644 --- a/src/ol/graticule.js +++ b/src/ol/graticule.js @@ -24,7 +24,7 @@ ol.Graticule = function(opt_options) { var options = opt_options || {}; /** - * @type {ol.Map} + * @type {ol.PluggableMap} * @private */ this.map_ = null; @@ -469,7 +469,7 @@ ol.Graticule.prototype.getInterval_ = function(resolution) { /** * Get the map associated with this graticule. - * @return {ol.Map} The map. + * @return {ol.PluggableMap} The map. * @api */ ol.Graticule.prototype.getMap = function() { @@ -642,7 +642,7 @@ ol.Graticule.prototype.updateProjectionInfo_ = function(projection) { /** * Set the map for this graticule. The graticule will be rendered on the * provided map. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @api */ ol.Graticule.prototype.setMap = function(map) { diff --git a/src/ol/interaction/extent.js b/src/ol/interaction/extent.js index 9a905d44b9..66f7dc9897 100644 --- a/src/ol/interaction/extent.js +++ b/src/ol/interaction/extent.js @@ -306,7 +306,7 @@ ol.interaction.Extent.getSegments_ = function(extent) { /** * @param {ol.Pixel} pixel cursor location - * @param {ol.Map} map map + * @param {ol.PluggableMap} map map * @returns {ol.Coordinate|null} snapped vertex on extent * @private */ diff --git a/src/ol/interaction/interaction.js b/src/ol/interaction/interaction.js index ace0c1307d..3fdb640b2f 100644 --- a/src/ol/interaction/interaction.js +++ b/src/ol/interaction/interaction.js @@ -31,7 +31,7 @@ ol.interaction.Interaction = function(options) { /** * @private - * @type {ol.Map} + * @type {ol.PluggableMap} */ this.map_ = null; @@ -60,7 +60,7 @@ ol.interaction.Interaction.prototype.getActive = function() { /** * Get the map associated with this interaction. - * @return {ol.Map} Map. + * @return {ol.PluggableMap} Map. * @api */ ol.interaction.Interaction.prototype.getMap = function() { @@ -83,7 +83,7 @@ ol.interaction.Interaction.prototype.setActive = function(active) { * Remove the interaction from its current map and attach it to the new map. * Subclasses may set up event handlers to get notified about changes to * the map here. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. */ ol.interaction.Interaction.prototype.setMap = function(map) { this.map_ = map; diff --git a/src/ol/interaction/modify.js b/src/ol/interaction/modify.js index 92024e86f0..73856fbd93 100644 --- a/src/ol/interaction/modify.js +++ b/src/ol/interaction/modify.js @@ -839,7 +839,7 @@ ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) { /** * @param {ol.Pixel} pixel Pixel - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @private */ ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { diff --git a/src/ol/interaction/mousewheelzoom.js b/src/ol/interaction/mousewheelzoom.js index ca3d7930a8..213b7a0ae8 100644 --- a/src/ol/interaction/mousewheelzoom.js +++ b/src/ol/interaction/mousewheelzoom.js @@ -244,7 +244,7 @@ ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() { /** * @private - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. */ ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) { var view = map.getView(); diff --git a/src/ol/interaction/select.js b/src/ol/interaction/select.js index 483884c811..a72e70ca55 100644 --- a/src/ol/interaction/select.js +++ b/src/ol/interaction/select.js @@ -304,7 +304,7 @@ ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) { /** * Remove the interaction from its current map, if any, and attach it to a new * map, if any. Pass `null` to just remove the interaction from the current map. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @override * @api */ diff --git a/src/ol/interaction/snap.js b/src/ol/interaction/snap.js index f4756c8b3a..6f5fc75c41 100644 --- a/src/ol/interaction/snap.js +++ b/src/ol/interaction/snap.js @@ -336,7 +336,7 @@ ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE; /** * @param {ol.Pixel} pixel Pixel * @param {ol.Coordinate} pixelCoordinate Coordinate - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @return {ol.SnapResultType} Snap result */ ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { diff --git a/src/ol/interaction/translate.js b/src/ol/interaction/translate.js index 9e54d5a013..dc13926510 100644 --- a/src/ol/interaction/translate.js +++ b/src/ol/interaction/translate.js @@ -185,7 +185,7 @@ ol.interaction.Translate.handleMoveEvent_ = function(event) { * Tests to see if the given coordinates intersects any of our selected * features. * @param {ol.Pixel} pixel Pixel coordinate to test for intersection. - * @param {ol.Map} map Map to test the intersection on. + * @param {ol.PluggableMap} map Map to test the intersection on. * @return {ol.Feature} Returns the feature found at the specified pixel * coordinates. * @private @@ -245,7 +245,7 @@ ol.interaction.Translate.prototype.handleActiveChanged_ = function() { /** - * @param {ol.Map} oldMap Old map. + * @param {ol.PluggableMap} oldMap Old map. * @private */ ol.interaction.Translate.prototype.updateState_ = function(oldMap) { diff --git a/src/ol/layer/base.js b/src/ol/layer/base.js index 62006d51e3..adc0d3da45 100644 --- a/src/ol/layer/base.js +++ b/src/ol/layer/base.js @@ -51,17 +51,24 @@ ol.layer.Base = function(options) { managed: true }); + /** + * The layer type. + * @type {ol.LayerType} + * @protected; + */ + this.type; + }; ol.inherits(ol.layer.Base, ol.Object); /** - * Create a renderer for this layer. - * @abstract - * @param {ol.renderer.Map} mapRenderer The map renderer. - * @return {ol.renderer.Layer} A layer renderer. + * Get the layer type (used when creating a layer renderer). + * @return {ol.LayerType} The layer type. */ -ol.layer.Base.prototype.createRenderer = function(mapRenderer) {}; +ol.layer.Base.prototype.getType = function() { + return this.type; +}; /** diff --git a/src/ol/layer/group.js b/src/ol/layer/group.js index 8706fc496d..53cc68dee8 100644 --- a/src/ol/layer/group.js +++ b/src/ol/layer/group.js @@ -70,12 +70,6 @@ ol.layer.Group = function(opt_options) { ol.inherits(ol.layer.Group, ol.layer.Base); -/** - * @inheritDoc - */ -ol.layer.Group.prototype.createRenderer = function(mapRenderer) {}; - - /** * @private */ diff --git a/src/ol/layer/image.js b/src/ol/layer/image.js index 04aa4e9ba7..9efa10dfbe 100644 --- a/src/ol/layer/image.js +++ b/src/ol/layer/image.js @@ -1,10 +1,8 @@ goog.provide('ol.layer.Image'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.layer.Layer'); -goog.require('ol.renderer.Type'); -goog.require('ol.renderer.canvas.ImageLayer'); -goog.require('ol.renderer.webgl.ImageLayer'); /** @@ -24,25 +22,18 @@ goog.require('ol.renderer.webgl.ImageLayer'); ol.layer.Image = function(opt_options) { var options = opt_options ? opt_options : {}; ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (options)); + + /** + * The layer type. + * @protected + * @type {ol.LayerType} + */ + this.type = ol.LayerType.IMAGE; + }; ol.inherits(ol.layer.Image, ol.layer.Layer); -/** - * @inheritDoc - */ -ol.layer.Image.prototype.createRenderer = function(mapRenderer) { - var renderer = null; - var type = mapRenderer.getType(); - if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) { - renderer = new ol.renderer.canvas.ImageLayer(this); - } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) { - renderer = new ol.renderer.webgl.ImageLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this); - } - return renderer; -}; - - /** * Return the associated {@link ol.source.Image source} of the image layer. * @function diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index b2118abbf2..2e3089012b 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -161,7 +161,7 @@ ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { * * To add the layer to a map and have it managed by the map, use * {@link ol.Map#addLayer} instead. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @api */ ol.layer.Layer.prototype.setMap = function(map) { diff --git a/src/ol/layer/tile.js b/src/ol/layer/tile.js index 6a1306575f..61c70f443a 100644 --- a/src/ol/layer/tile.js +++ b/src/ol/layer/tile.js @@ -1,12 +1,10 @@ goog.provide('ol.layer.Tile'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.layer.Layer'); goog.require('ol.layer.TileProperty'); goog.require('ol.obj'); -goog.require('ol.renderer.Type'); -goog.require('ol.renderer.canvas.TileLayer'); -goog.require('ol.renderer.webgl.TileLayer'); /** @@ -35,25 +33,18 @@ ol.layer.Tile = function(opt_options) { this.setPreload(options.preload !== undefined ? options.preload : 0); this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ? options.useInterimTilesOnError : true); + + /** + * The layer type. + * @protected + * @type {ol.LayerType} + */ + this.type = ol.LayerType.TILE; + }; ol.inherits(ol.layer.Tile, ol.layer.Layer); -/** - * @inheritDoc - */ -ol.layer.Tile.prototype.createRenderer = function(mapRenderer) { - var renderer = null; - var type = mapRenderer.getType(); - if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) { - renderer = new ol.renderer.canvas.TileLayer(this); - } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) { - renderer = new ol.renderer.webgl.TileLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this); - } - return renderer; -}; - - /** * Return the level as number to which we will preload tiles up to. * @return {number} The level to preload tiles up to. diff --git a/src/ol/layer/vector.js b/src/ol/layer/vector.js index 0f24ad9853..2d315e9c92 100644 --- a/src/ol/layer/vector.js +++ b/src/ol/layer/vector.js @@ -1,11 +1,9 @@ goog.provide('ol.layer.Vector'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.layer.Layer'); goog.require('ol.obj'); -goog.require('ol.renderer.Type'); -goog.require('ol.renderer.canvas.VectorLayer'); -goog.require('ol.renderer.webgl.VectorLayer'); goog.require('ol.style.Style'); @@ -70,25 +68,18 @@ ol.layer.Vector = function(opt_options) { */ this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ? options.updateWhileInteracting : false; + + /** + * The layer type. + * @protected + * @type {ol.LayerType} + */ + this.type = ol.LayerType.VECTOR; + }; ol.inherits(ol.layer.Vector, ol.layer.Layer); -/** - * @inheritDoc - */ -ol.layer.Vector.prototype.createRenderer = function(mapRenderer) { - var renderer = null; - var type = mapRenderer.getType(); - if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) { - renderer = new ol.renderer.canvas.VectorLayer(this); - } else if (ol.ENABLE_WEBGL && type === ol.renderer.Type.WEBGL) { - renderer = new ol.renderer.webgl.VectorLayer(/** @type {ol.renderer.webgl.Map} */ (mapRenderer), this); - } - return renderer; -}; - - /** * @return {number|undefined} Render buffer. */ diff --git a/src/ol/layer/vectortile.js b/src/ol/layer/vectortile.js index e248a92384..c7f96569ef 100644 --- a/src/ol/layer/vectortile.js +++ b/src/ol/layer/vectortile.js @@ -1,13 +1,12 @@ goog.provide('ol.layer.VectorTile'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.asserts'); goog.require('ol.layer.TileProperty'); goog.require('ol.layer.Vector'); goog.require('ol.layer.VectorTileRenderType'); goog.require('ol.obj'); -goog.require('ol.renderer.Type'); -goog.require('ol.renderer.canvas.VectorTileLayer'); /** @@ -47,23 +46,17 @@ ol.layer.VectorTile = function(opt_options) { */ this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID; + /** + * The layer type. + * @protected + * @type {ol.LayerType} + */ + this.type = ol.LayerType.VECTOR_TILE; + }; ol.inherits(ol.layer.VectorTile, ol.layer.Vector); -/** - * @inheritDoc - */ -ol.layer.VectorTile.prototype.createRenderer = function(mapRenderer) { - var renderer = null; - var type = mapRenderer.getType(); - if (ol.ENABLE_CANVAS && type === ol.renderer.Type.CANVAS) { - renderer = new ol.renderer.canvas.VectorTileLayer(this); - } - return renderer; -}; - - /** * Return the level as number to which we will preload tiles up to. * @return {number} The level to preload tiles up to. diff --git a/src/ol/layertype.js b/src/ol/layertype.js new file mode 100644 index 0000000000..cab765ef5a --- /dev/null +++ b/src/ol/layertype.js @@ -0,0 +1,12 @@ +goog.provide('ol.LayerType'); + +/** + * A layer type used when creating layer renderers. + * @enum {string} + */ +ol.LayerType = { + IMAGE: 'IMAGE', + TILE: 'TILE', + VECTOR_TILE: 'VECTOR_TILE', + VECTOR: 'VECTOR' +}; diff --git a/src/ol/map.js b/src/ol/map.js index 69b3fcb5b8..dbb82b2e99 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -1,87 +1,41 @@ -// 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.TileQueue'); -goog.require('ol.View'); -goog.require('ol.ViewHint'); -goog.require('ol.asserts'); +goog.require('ol.PluggableMap'); +goog.require('ol.PluginType'); 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.renderer.Map'); -goog.require('ol.renderer.Type'); +goog.require('ol.plugins'); +goog.require('ol.renderer.canvas.ImageLayer'); goog.require('ol.renderer.canvas.Map'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.canvas.VectorLayer'); +goog.require('ol.renderer.canvas.VectorTileLayer'); +goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.Map'); -goog.require('ol.size'); -goog.require('ol.structs.PriorityQueue'); -goog.require('ol.transform'); +goog.require('ol.renderer.webgl.TileLayer'); +goog.require('ol.renderer.webgl.VectorLayer'); -/** - * @const - * @type {string} - */ -ol.OL_URL = 'https://openlayers.org/'; +if (ol.ENABLE_CANVAS) { + ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.canvas.Map); + ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [ + ol.renderer.canvas.ImageLayer, + ol.renderer.canvas.TileLayer, + ol.renderer.canvas.VectorLayer, + ol.renderer.canvas.VectorTileLayer + ]); +} - -/** - * @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 -]; +if (ol.ENABLE_WEBGL) { + ol.plugins.register(ol.PluginType.MAP_RENDERER, ol.renderer.webgl.Map); + ol.plugins.registerMultiple(ol.PluginType.LAYER_RENDERER, [ + ol.renderer.webgl.ImageLayer, + ol.renderer.webgl.TileLayer, + ol.renderer.webgl.VectorLayer + ]); +} /** @@ -124,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 @@ -133,1402 +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_ = new /** @type {Function} */ (optionsInternal.rendererConstructor)(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 {function(new: ol.renderer.Map, Element, ol.Map)} - */ - var rendererConstructor = ol.renderer.Map; - - /** - * @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; - } - - var i, ii; - for (i = 0, ii = rendererTypes.length; i < ii; ++i) { - /** @type {ol.renderer.Type} */ - var rendererType = rendererTypes[i]; - if (ol.ENABLE_CANVAS && rendererType == ol.renderer.Type.CANVAS) { - if (ol.has.CANVAS) { - rendererConstructor = ol.renderer.canvas.Map; - break; - } - } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) { - if (ol.has.WEBGL) { - rendererConstructor = ol.renderer.webgl.Map; - break; - } - } - } - - 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, - rendererConstructor: rendererConstructor, - values: values - }; - + ol.PluggableMap.call(this, options); }; +ol.inherits(ol.Map, ol.PluggableMap); diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 80aec04f34..2c705569ab 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -13,7 +13,7 @@ goog.require('ol.MapEvent'); * @extends {ol.MapEvent} * @implements {oli.MapBrowserEvent} * @param {string} type Event type. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {Event} browserEvent Browser event. * @param {boolean=} opt_dragging Is the map currently being dragged? * @param {?olx.FrameState=} opt_frameState Frame state. diff --git a/src/ol/mapbrowsereventhandler.js b/src/ol/mapbrowsereventhandler.js index 72394f6329..ea06f1bc39 100644 --- a/src/ol/mapbrowsereventhandler.js +++ b/src/ol/mapbrowsereventhandler.js @@ -11,7 +11,7 @@ goog.require('ol.pointer.PointerEventHandler'); /** - * @param {ol.Map} map The map with the viewport to listen to events on. + * @param {ol.PluggableMap} map The map with the viewport to listen to events on. * @param {number|undefined} moveTolerance The minimal distance the pointer must travel to trigger a move. * @constructor * @extends {ol.events.EventTarget} @@ -22,7 +22,7 @@ ol.MapBrowserEventHandler = function(map, moveTolerance) { /** * This is the element that we will listen to the real events on. - * @type {ol.Map} + * @type {ol.PluggableMap} * @private */ this.map_ = map; diff --git a/src/ol/mapbrowserpointerevent.js b/src/ol/mapbrowserpointerevent.js index 83b73fe8f7..1f95493a4f 100644 --- a/src/ol/mapbrowserpointerevent.js +++ b/src/ol/mapbrowserpointerevent.js @@ -8,7 +8,7 @@ goog.require('ol.MapBrowserEvent'); * @constructor * @extends {ol.MapBrowserEvent} * @param {string} type Event type. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. * @param {boolean=} opt_dragging Is the map currently being dragged? * @param {?olx.FrameState=} opt_frameState Frame state. diff --git a/src/ol/mapevent.js b/src/ol/mapevent.js index aeae5ae663..f0c66d9fb0 100644 --- a/src/ol/mapevent.js +++ b/src/ol/mapevent.js @@ -13,7 +13,7 @@ goog.require('ol.events.Event'); * @extends {ol.events.Event} * @implements {oli.MapEvent} * @param {string} type Event type. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {?olx.FrameState=} opt_frameState Frame state. */ ol.MapEvent = function(type, map, opt_frameState) { @@ -22,7 +22,7 @@ ol.MapEvent = function(type, map, opt_frameState) { /** * The map where the event occurred. - * @type {ol.Map} + * @type {ol.PluggableMap} * @api */ this.map = map; diff --git a/src/ol/overlay.js b/src/ol/overlay.js index 51e99df3c3..96a76bb3f4 100644 --- a/src/ol/overlay.js +++ b/src/ol/overlay.js @@ -166,12 +166,12 @@ ol.Overlay.prototype.getId = function() { /** * Get the map associated with this overlay. - * @return {ol.Map|undefined} The map that the overlay is part of. + * @return {ol.PluggableMap|undefined} The map that the overlay is part of. * @observable * @api */ ol.Overlay.prototype.getMap = function() { - return /** @type {ol.Map|undefined} */ ( + return /** @type {ol.PluggableMap|undefined} */ ( this.get(ol.Overlay.Property_.MAP)); }; @@ -299,7 +299,7 @@ ol.Overlay.prototype.setElement = function(element) { /** * Set the map to be associated with this overlay. - * @param {ol.Map|undefined} map The map that the overlay is part of. + * @param {ol.PluggableMap|undefined} map The map that the overlay is part of. * @observable * @api */ diff --git a/src/ol/pluggablemap.js b/src/ol/pluggablemap.js new file mode 100644 index 0000000000..0b667ccb25 --- /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.PluggableMap} + */ + 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.PluggableMap} + */ + 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/plugins.js b/src/ol/plugins.js new file mode 100644 index 0000000000..8ad12f1d42 --- /dev/null +++ b/src/ol/plugins.js @@ -0,0 +1,73 @@ +goog.provide('ol.plugins'); + +goog.require('ol.PluginType'); + +/** + * The registry of map renderer plugins. + * @type {Array} + * @private + */ +ol.plugins.mapRendererPlugins_ = []; + + +/** + * Get all registered map renderer plugins. + * @return {Array} The registered map renderer plugins. + */ +ol.plugins.getMapRendererPlugins = function() { + return ol.plugins.mapRendererPlugins_; +}; + + +/** + * The registry of layer renderer plugins. + * @type {Array} + * @private + */ +ol.plugins.layerRendererPlugins_ = []; + + +/** + * Get all registered layer renderer plugins. + * @return {Array} The registered layer renderer plugins. + */ +ol.plugins.getLayerRendererPlugins = function() { + return ol.plugins.layerRendererPlugins_; +}; + + +/** + * Register a plugin. + * @param {ol.PluginType} type The plugin type. + * @param {*} plugin The plugin. + */ +ol.plugins.register = function(type, plugin) { + var plugins; + switch (type) { + case ol.PluginType.MAP_RENDERER: { + plugins = ol.plugins.mapRendererPlugins_; + plugins.push(/** @type {olx.MapRendererPlugin} */ (plugin)); + break; + } + case ol.PluginType.LAYER_RENDERER: { + plugins = ol.plugins.layerRendererPlugins_; + plugins.push(/** @type {olx.LayerRendererPlugin} */ (plugin)); + break; + } + default: { + throw new Error('Unsupported plugin type: ' + type); + } + } +}; + + +/** + * Register multiple plugins. + * @param {ol.PluginType} type The plugin type. + * @param {Array} plugins The plugins. + */ +ol.plugins.registerMultiple = function(type, plugins) { + for (var i = 0, ii = plugins.length; i < ii; ++i) { + ol.plugins.register(type, plugins[i]); + } +}; diff --git a/src/ol/plugintype.js b/src/ol/plugintype.js new file mode 100644 index 0000000000..817fea5c8a --- /dev/null +++ b/src/ol/plugintype.js @@ -0,0 +1,11 @@ +goog.provide('ol.PluginType'); + +/** + * A plugin type used when registering a plugin. The supported plugin types are + * 'MAP_RENDERER', and 'LAYER_RENDERER'. + * @enum {string} + */ +ol.PluginType = { + MAP_RENDERER: 'MAP_RENDERER', + LAYER_RENDERER: 'LAYER_RENDERER' +}; diff --git a/src/ol/render/box.js b/src/ol/render/box.js index 6e44032ec2..cd289c9281 100644 --- a/src/ol/render/box.js +++ b/src/ol/render/box.js @@ -30,7 +30,7 @@ ol.render.Box = function(className) { /** * @private - * @type {ol.Map} + * @type {ol.PluggableMap} */ this.map_ = null; @@ -74,7 +74,7 @@ ol.render.Box.prototype.render_ = function() { /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. */ ol.render.Box.prototype.setMap = function(map) { if (this.map_) { diff --git a/src/ol/renderer/canvas/imagelayer.js b/src/ol/renderer/canvas/imagelayer.js index 3d828e2515..43b0b015c3 100644 --- a/src/ol/renderer/canvas/imagelayer.js +++ b/src/ol/renderer/canvas/imagelayer.js @@ -1,8 +1,10 @@ goog.provide('ol.renderer.canvas.ImageLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.ViewHint'); goog.require('ol.extent'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.canvas.IntermediateCanvas'); goog.require('ol.transform'); @@ -11,6 +13,7 @@ goog.require('ol.transform'); * @constructor * @extends {ol.renderer.canvas.IntermediateCanvas} * @param {ol.layer.Image} imageLayer Single image layer. + * @api */ ol.renderer.canvas.ImageLayer = function(imageLayer) { @@ -32,6 +35,28 @@ ol.renderer.canvas.ImageLayer = function(imageLayer) { ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas); +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.canvas.ImageLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.IMAGE; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.canvas.ImageLayer} The layer renderer. + */ +ol.renderer.canvas.ImageLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.canvas.ImageLayer(/** @type {ol.layer.Image} */ (layer)); +}; + + /** * @inheritDoc */ diff --git a/src/ol/renderer/canvas/map.js b/src/ol/renderer/canvas/map.js index 509695e4dd..65f9b8c643 100644 --- a/src/ol/renderer/canvas/map.js +++ b/src/ol/renderer/canvas/map.js @@ -21,7 +21,8 @@ goog.require('ol.source.State'); * @constructor * @extends {ol.renderer.Map} * @param {Element} container Container. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. + * @api */ ol.renderer.canvas.Map = function(container, map) { @@ -61,6 +62,27 @@ ol.renderer.canvas.Map = function(container, map) { ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map); +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.canvas.Map['handles'] = function(type) { + return type === ol.renderer.Type.CANVAS; +}; + + +/** + * Create the map renderer. + * @param {Element} container Container. + * @param {ol.PluggableMap} map Map. + * @return {ol.renderer.canvas.Map} The map renderer. + */ +ol.renderer.canvas.Map['create'] = function(container, map) { + return new ol.renderer.canvas.Map(container, map); +}; + + /** * @param {ol.render.EventType} type Event type. * @param {olx.FrameState} frameState Frame state. diff --git a/src/ol/renderer/canvas/tilelayer.js b/src/ol/renderer/canvas/tilelayer.js index b20a916b41..27901a3b4d 100644 --- a/src/ol/renderer/canvas/tilelayer.js +++ b/src/ol/renderer/canvas/tilelayer.js @@ -3,12 +3,14 @@ goog.provide('ol.renderer.canvas.TileLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.TileRange'); goog.require('ol.TileState'); goog.require('ol.ViewHint'); goog.require('ol.array'); goog.require('ol.dom'); goog.require('ol.extent'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.canvas.IntermediateCanvas'); goog.require('ol.transform'); @@ -17,6 +19,7 @@ goog.require('ol.transform'); * @constructor * @extends {ol.renderer.canvas.IntermediateCanvas} * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer. + * @api */ ol.renderer.canvas.TileLayer = function(tileLayer) { @@ -80,6 +83,28 @@ ol.renderer.canvas.TileLayer = function(tileLayer) { ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas); +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.canvas.TileLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.TILE; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.canvas.TileLayer} The layer renderer. + */ +ol.renderer.canvas.TileLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.canvas.TileLayer(/** @type {ol.layer.Tile} */ (layer)); +}; + + /** * @private * @param {ol.Tile} tile Tile. diff --git a/src/ol/renderer/canvas/vectorlayer.js b/src/ol/renderer/canvas/vectorlayer.js index 73491e8525..f494d0f9b8 100644 --- a/src/ol/renderer/canvas/vectorlayer.js +++ b/src/ol/renderer/canvas/vectorlayer.js @@ -1,12 +1,14 @@ goog.provide('ol.renderer.canvas.VectorLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.ViewHint'); goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.render.EventType'); goog.require('ol.render.canvas'); goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.canvas.Layer'); goog.require('ol.renderer.vector'); @@ -15,6 +17,7 @@ goog.require('ol.renderer.vector'); * @constructor * @extends {ol.renderer.canvas.Layer} * @param {ol.layer.Vector} vectorLayer Vector layer. + * @api */ ol.renderer.canvas.VectorLayer = function(vectorLayer) { @@ -66,6 +69,28 @@ ol.renderer.canvas.VectorLayer = function(vectorLayer) { ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.canvas.VectorLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.VECTOR; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.canvas.VectorLayer} The layer renderer. + */ +ol.renderer.canvas.VectorLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.canvas.VectorLayer(/** @type {ol.layer.Vector} */ (layer)); +}; + + /** * @inheritDoc */ diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js index dd1bc4c949..280db28f13 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -1,16 +1,18 @@ goog.provide('ol.renderer.canvas.VectorTileLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.TileState'); goog.require('ol.dom'); goog.require('ol.extent'); +goog.require('ol.layer.VectorTileRenderType'); goog.require('ol.proj'); goog.require('ol.proj.Units'); -goog.require('ol.layer.VectorTileRenderType'); goog.require('ol.render.ReplayType'); goog.require('ol.render.canvas'); goog.require('ol.render.canvas.ReplayGroup'); goog.require('ol.render.replay'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.vector'); goog.require('ol.size'); @@ -21,6 +23,7 @@ goog.require('ol.transform'); * @constructor * @extends {ol.renderer.canvas.TileLayer} * @param {ol.layer.VectorTile} layer VectorTile layer. + * @api */ ol.renderer.canvas.VectorTileLayer = function(layer) { @@ -57,6 +60,28 @@ ol.renderer.canvas.VectorTileLayer = function(layer) { ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer); +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.canvas.VectorTileLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.CANVAS && layer.getType() === ol.LayerType.VECTOR_TILE; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.canvas.VectorTileLayer} The layer renderer. + */ +ol.renderer.canvas.VectorTileLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.canvas.VectorTileLayer(/** @type {ol.layer.VectorTile} */ (layer)); +}; + + /** * @const * @type {!Object.>} diff --git a/src/ol/renderer/layer.js b/src/ol/renderer/layer.js index 413e3f25bc..f966d5c978 100644 --- a/src/ol/renderer/layer.js +++ b/src/ol/renderer/layer.js @@ -147,7 +147,7 @@ ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSourc if (tileSource.canExpireCache()) { /** * @param {ol.source.Tile} tileSource Tile source. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {olx.FrameState} frameState Frame state. */ var postRenderFunction = function(tileSource, map, frameState) { diff --git a/src/ol/renderer/map.js b/src/ol/renderer/map.js index 78c40b09f7..4fa5386c79 100644 --- a/src/ol/renderer/map.js +++ b/src/ol/renderer/map.js @@ -7,6 +7,7 @@ goog.require('ol.events.EventType'); goog.require('ol.extent'); goog.require('ol.functions'); goog.require('ol.layer.Layer'); +goog.require('ol.plugins'); goog.require('ol.style'); goog.require('ol.transform'); @@ -16,7 +17,7 @@ goog.require('ol.transform'); * @abstract * @extends {ol.Disposable} * @param {Element} container Container. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @struct */ ol.renderer.Map = function(container, map) { @@ -26,7 +27,7 @@ ol.renderer.Map = function(container, map) { /** * @private - * @type {ol.Map} + * @type {ol.PluggableMap} */ this.map_ = map; @@ -77,7 +78,7 @@ ol.renderer.Map.prototype.disposeInternal = function() { /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {olx.FrameState} frameState Frame state. * @private */ @@ -206,12 +207,24 @@ ol.renderer.Map.prototype.getLayerRenderer = function(layer) { if (layerKey in this.layerRenderers_) { return this.layerRenderers_[layerKey]; } else { - var layerRenderer = layer.createRenderer(this); - this.layerRenderers_[layerKey] = layerRenderer; - this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer, - ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this); - - return layerRenderer; + var layerRendererPlugins = ol.plugins.getLayerRendererPlugins(); + var renderer; + var type = this.getType(); + for (var i = 0, ii = layerRendererPlugins.length; i < ii; ++i) { + var plugin = layerRendererPlugins[i]; + if (plugin.handles(type, layer)) { + renderer = plugin.create(this, layer); + break; + } + } + if (renderer) { + this.layerRenderers_[layerKey] = renderer; + this.layerRendererListeners_[layerKey] = ol.events.listen(renderer, + ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this); + } else { + throw new Error('Unable to create renderer for layer: ' + layer.getType()); + } + return renderer; } }; @@ -236,7 +249,7 @@ ol.renderer.Map.prototype.getLayerRenderers = function() { /** - * @return {ol.Map} Map. + * @return {ol.PluggableMap} Map. */ ol.renderer.Map.prototype.getMap = function() { return this.map_; @@ -245,7 +258,7 @@ ol.renderer.Map.prototype.getMap = function() { /** * @abstract - * @return {string} Type + * @return {ol.renderer.Type} Type */ ol.renderer.Map.prototype.getType = function() {}; @@ -283,7 +296,7 @@ ol.renderer.Map.prototype.renderFrame = ol.nullFunction; /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {olx.FrameState} frameState Frame state. * @private */ diff --git a/src/ol/renderer/webgl/imagelayer.js b/src/ol/renderer/webgl/imagelayer.js index bf87d2494b..0145865658 100644 --- a/src/ol/renderer/webgl/imagelayer.js +++ b/src/ol/renderer/webgl/imagelayer.js @@ -1,10 +1,12 @@ goog.provide('ol.renderer.webgl.ImageLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.ViewHint'); goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.functions'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.source.ImageVector'); goog.require('ol.transform'); @@ -19,6 +21,7 @@ if (ol.ENABLE_WEBGL) { * @extends {ol.renderer.webgl.Layer} * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. * @param {ol.layer.Image} imageLayer Tile layer. + * @api */ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { @@ -47,6 +50,31 @@ if (ol.ENABLE_WEBGL) { ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + /** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ + ol.renderer.webgl.ImageLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.IMAGE; + }; + + + /** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.ImageLayer} The layer renderer. + */ + ol.renderer.webgl.ImageLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.ImageLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Image} */ (layer) + ); + }; + + /** * @param {ol.ImageBase} image Image. * @private diff --git a/src/ol/renderer/webgl/map.js b/src/ol/renderer/webgl/map.js index f06ea93a1d..517f53f340 100644 --- a/src/ol/renderer/webgl/map.js +++ b/src/ol/renderer/webgl/map.js @@ -7,6 +7,7 @@ goog.require('ol.array'); goog.require('ol.css'); goog.require('ol.dom'); goog.require('ol.events'); +goog.require('ol.has'); goog.require('ol.layer.Layer'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); @@ -27,7 +28,8 @@ if (ol.ENABLE_WEBGL) { * @constructor * @extends {ol.renderer.Map} * @param {Element} container Container. - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. + * @api */ ol.renderer.webgl.Map = function(container, map) { ol.renderer.Map.call(this, container, map); @@ -131,7 +133,7 @@ if (ol.ENABLE_WEBGL) { /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {?olx.FrameState} frameState Frame state. * @return {boolean} false. * @this {ol.renderer.webgl.Map} @@ -162,6 +164,27 @@ if (ol.ENABLE_WEBGL) { ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map); + /** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @return {boolean} The renderer can render the layer. + */ + ol.renderer.webgl.Map['handles'] = function(type) { + return ol.has.WEBGL && type === ol.renderer.Type.WEBGL; + }; + + + /** + * Create the map renderer. + * @param {Element} container Container. + * @param {ol.PluggableMap} map Map. + * @return {ol.renderer.webgl.Map} The map renderer. + */ + ol.renderer.webgl.Map['create'] = function(container, map) { + return new ol.renderer.webgl.Map(container, map); + }; + + /** * @param {ol.Tile} tile Tile. * @param {ol.Size} tileSize Tile size. @@ -278,7 +301,7 @@ if (ol.ENABLE_WEBGL) { /** - * @param {ol.Map} map Map. + * @param {ol.PluggableMap} map Map. * @param {olx.FrameState} frameState Frame state. * @private */ diff --git a/src/ol/renderer/webgl/tilelayer.js b/src/ol/renderer/webgl/tilelayer.js index 8763ef785e..ce4620be0d 100644 --- a/src/ol/renderer/webgl/tilelayer.js +++ b/src/ol/renderer/webgl/tilelayer.js @@ -4,11 +4,13 @@ goog.provide('ol.renderer.webgl.TileLayer'); goog.require('ol'); -goog.require('ol.TileState'); +goog.require('ol.LayerType'); goog.require('ol.TileRange'); +goog.require('ol.TileState'); goog.require('ol.array'); goog.require('ol.extent'); goog.require('ol.math'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.tilelayershader'); goog.require('ol.size'); @@ -24,6 +26,7 @@ if (ol.ENABLE_WEBGL) { * @extends {ol.renderer.webgl.Layer} * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. * @param {ol.layer.Tile} tileLayer Tile layer. + * @api */ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { @@ -86,6 +89,31 @@ if (ol.ENABLE_WEBGL) { ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); + /** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ + ol.renderer.webgl.TileLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.TILE; + }; + + + /** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.TileLayer} The layer renderer. + */ + ol.renderer.webgl.TileLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.TileLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Tile} */ (layer) + ); + }; + + /** * @inheritDoc */ diff --git a/src/ol/renderer/webgl/vectorlayer.js b/src/ol/renderer/webgl/vectorlayer.js index ad901032d8..ec8ae1cd47 100644 --- a/src/ol/renderer/webgl/vectorlayer.js +++ b/src/ol/renderer/webgl/vectorlayer.js @@ -1,9 +1,11 @@ goog.provide('ol.renderer.webgl.VectorLayer'); goog.require('ol'); +goog.require('ol.LayerType'); goog.require('ol.ViewHint'); goog.require('ol.extent'); goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.renderer.Type'); goog.require('ol.renderer.vector'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.transform'); @@ -16,6 +18,7 @@ if (ol.ENABLE_WEBGL) { * @extends {ol.renderer.webgl.Layer} * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. * @param {ol.layer.Vector} vectorLayer Vector layer. + * @api */ ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { @@ -68,6 +71,31 @@ if (ol.ENABLE_WEBGL) { ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); + /** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ + ol.renderer.webgl.VectorLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.VECTOR; + }; + + + /** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.VectorLayer} The layer renderer. + */ + ol.renderer.webgl.VectorLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.VectorLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Vector} */ (layer) + ); + }; + + /** * @inheritDoc */ diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index 92e0a6cdbd..098c7f410e 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -365,13 +365,12 @@ 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., - * rendererConstructor: - * function(new: ol.renderer.Map, Element, ol.Map), + * mapRendererPlugin: olx.MapRendererPlugin, * values: Object.}} */ ol.MapOptionsInternal; @@ -405,7 +404,7 @@ ol.Pixel; /** - * @typedef {function(ol.Map, ?olx.FrameState): boolean} + * @typedef {function(ol.PluggableMap, ?olx.FrameState): boolean} */ ol.PostRenderFunction; @@ -415,7 +414,7 @@ ol.PostRenderFunction; * with the {@link ol.Map} as first and an optional {@link olx.FrameState} as * second argument. Return `true` to keep this function for the next frame, * `false` to remove it. - * @typedef {function(ol.Map, ?olx.FrameState): boolean} + * @typedef {function(ol.PluggableMap, ?olx.FrameState): boolean} */ ol.PreRenderFunction; diff --git a/test/test-extensions.js b/test/test-extensions.js index a5245eaddd..03122b1a51 100644 --- a/test/test-extensions.js +++ b/test/test-extensions.js @@ -445,7 +445,7 @@ /** * Assert that the given map resembles a reference image. * - * @param {ol.Map} map A map using the canvas renderer. + * @param {ol.PluggableMap} map A map using the canvas renderer. * @param {string} referenceImage Path to the reference image. * @param {number} tolerance The accepted mismatch tolerance. * @param {function} done A callback to indicate that the test is done.