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.