diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 2c6b08768d..15c957ec08 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -1,5 +1,39 @@ ## Upgrade notes +### v3.7.0 + +#### Removal of `ol.FeatureOverlay` + +Instead of an `ol.FeatureOverlay`, we now use an `ol.layer.Vector` with an +`ol.source.Vector`. If you previously had: +```js +var featureOverlay = new ol.FeatureOverlay({ + map: map, + style: overlayStyle +}); +featureOverlay.addFeature(feature); +featureOverlay.removeFeature(feature); +var collection = featureOverlay.getFeatures(); +``` +you will have to change this to: +```js +var collection = new ol.Collection(); +var featureOverlay = new ol.layer.Vector({ + map: map, + style: overlayStyle, + source: new ol.source.Vector({ + features: collection, + useSpatialIndex: false // optional, might improve performance + }); +}); +featureOverlay.getSource().addFeature(feature); +featureOverlay.getSource().removeFeature(feature); +``` + +With the removal of `ol.FeatureOverlay`, `zIndex` symbolizer properties of overlays are no longer stacked per map, but per layer/overlay. If you previously had multiple feature overlays where you controlled the rendering order of features by using `zIndex` symbolizer properties, you can now achieve the same rendering order only if all overlay features are on the same layer. + +Note that `ol.FeatureOverlay#getFeatures()` returned an `{ol.Collection.}`, whereas `ol.source.Vector#getFeatures()` returns an `{Array.}`. + ### v3.6.0 #### `ol.interaction.Draw` changes diff --git a/examples/draw-and-modify-features.js b/examples/draw-and-modify-features.js index ae9d56a018..8e79524424 100644 --- a/examples/draw-and-modify-features.js +++ b/examples/draw-and-modify-features.js @@ -1,11 +1,13 @@ -goog.require('ol.FeatureOverlay'); +goog.require('ol.Collection'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.events.condition'); goog.require('ol.interaction.Draw'); goog.require('ol.interaction.Modify'); goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); goog.require('ol.source.MapQuest'); +goog.require('ol.source.Vector'); goog.require('ol.style.Circle'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); @@ -24,11 +26,9 @@ var map = new ol.Map({ }) }); -// The features are not added to a regular vector layer/source, -// but to a feature overlay which holds a collection of features. -// This collection is passed to the modify and also the draw -// interaction, so that both can add or modify features. -var featureOverlay = new ol.FeatureOverlay({ +var features = new ol.Collection(); +var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector({features: features}), style: new ol.style.Style({ fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.2)' @@ -48,7 +48,7 @@ var featureOverlay = new ol.FeatureOverlay({ featureOverlay.setMap(map); var modify = new ol.interaction.Modify({ - features: featureOverlay.getFeatures(), + features: features, // the SHIFT key must be pressed to delete vertices, so // that new vertices can be drawn at the same position // of existing vertices @@ -62,7 +62,7 @@ map.addInteraction(modify); var draw; // global so we can remove it later function addInteraction() { draw = new ol.interaction.Draw({ - features: featureOverlay.getFeatures(), + features: features, type: /** @type {ol.geom.GeometryType} */ (typeSelect.value) }); map.addInteraction(draw); diff --git a/examples/geolocation.js b/examples/geolocation.js index b6b3f70a41..a35eacd2b2 100644 --- a/examples/geolocation.js +++ b/examples/geolocation.js @@ -1,12 +1,13 @@ goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.Geolocation'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.control'); goog.require('ol.geom.Point'); goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); goog.require('ol.source.OSM'); +goog.require('ol.source.Vector'); goog.require('ol.style.Circle'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); @@ -85,7 +86,9 @@ geolocation.on('change:position', function() { new ol.geom.Point(coordinates) : null); }); -var featuresOverlay = new ol.FeatureOverlay({ +var featuresOverlay = new ol.layer.Vector({ map: map, - features: [accuracyFeature, positionFeature] + source: new ol.source.Vector({ + features: [accuracyFeature, positionFeature] + }) }); diff --git a/examples/icon-sprite-webgl.js b/examples/icon-sprite-webgl.js index ffda56a325..7f65cb3e27 100644 --- a/examples/icon-sprite-webgl.js +++ b/examples/icon-sprite-webgl.js @@ -1,5 +1,4 @@ goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.geom.Point'); @@ -102,12 +101,14 @@ for (i = 0; i < featureCount; i += 30) { overlayFeatures.push(clone); } -var featureOverlay = new ol.FeatureOverlay({ +var featureOverlay = new ol.layer.Vector({ map: map, + source: new ol.source.Vector({ + features: overlayFeatures + }), style: new ol.style.Style({ image: icons[iconCount - 1] - }), - features: overlayFeatures + }) }); map.on('click', function(evt) { diff --git a/examples/igc.js b/examples/igc.js index 43020959ef..5ebd160ec6 100644 --- a/examples/igc.js +++ b/examples/igc.js @@ -1,6 +1,5 @@ goog.require('ol.Attribution'); goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.control'); @@ -180,7 +179,8 @@ map.on('postcompose', function(evt) { } }); -var featureOverlay = new ol.FeatureOverlay({ +var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector(), map: map, style: new ol.style.Style({ image: new ol.style.Circle({ @@ -203,7 +203,7 @@ document.getElementById('time').addEventListener('input', function() { if (highlight === undefined) { highlight = new ol.Feature(new ol.geom.Point(coordinate)); feature.set('highlight', highlight); - featureOverlay.addFeature(highlight); + featureOverlay.getSource().addFeature(highlight); } else { highlight.getGeometry().setCoordinates(coordinate); } diff --git a/examples/image-vector-layer.js b/examples/image-vector-layer.js index 6e7eaf352b..47111dfff8 100644 --- a/examples/image-vector-layer.js +++ b/examples/image-vector-layer.js @@ -1,9 +1,9 @@ -goog.require('ol.FeatureOverlay'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.format.GeoJSON'); goog.require('ol.layer.Image'); goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); goog.require('ol.source.ImageVector'); goog.require('ol.source.MapQuest'); goog.require('ol.source.Vector'); @@ -42,7 +42,8 @@ var map = new ol.Map({ }) }); -var featureOverlay = new ol.FeatureOverlay({ +var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector(), map: map, style: new ol.style.Style({ stroke: new ol.style.Stroke({ @@ -71,10 +72,10 @@ var displayFeatureInfo = function(pixel) { if (feature !== highlight) { if (highlight) { - featureOverlay.removeFeature(highlight); + featureOverlay.getSource().removeFeature(highlight); } if (feature) { - featureOverlay.addFeature(feature); + featureOverlay.getSource().addFeature(feature); } highlight = feature; } diff --git a/examples/modify-features.js b/examples/modify-features.js index 25c6ee18dc..903cf3bc40 100644 --- a/examples/modify-features.js +++ b/examples/modify-features.js @@ -24,7 +24,9 @@ var vector = new ol.layer.Vector({ }) }); -var select = new ol.interaction.Select(); +var select = new ol.interaction.Select({ + wrapX: false +}); var modify = new ol.interaction.Modify({ features: select.getFeatures() diff --git a/examples/vector-layer.js b/examples/vector-layer.js index 25faeca41f..3b7907c5b3 100644 --- a/examples/vector-layer.js +++ b/examples/vector-layer.js @@ -1,4 +1,3 @@ -goog.require('ol.FeatureOverlay'); goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.format.GeoJSON'); @@ -60,7 +59,8 @@ var map = new ol.Map({ var highlightStyleCache = {}; -var featureOverlay = new ol.FeatureOverlay({ +var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector(), map: map, style: function(feature, resolution) { var text = resolution < 5000 ? feature.get('name') : ''; @@ -106,10 +106,10 @@ var displayFeatureInfo = function(pixel) { if (feature !== highlight) { if (highlight) { - featureOverlay.removeFeature(highlight); + featureOverlay.getSource().removeFeature(highlight); } if (feature) { - featureOverlay.addFeature(feature); + featureOverlay.getSource().addFeature(feature); } highlight = feature; } diff --git a/externs/olx.js b/externs/olx.js index 05106437cc..40dd5e67a5 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -2375,7 +2375,8 @@ olx.interaction.DragZoomOptions.prototype.style; * geometryFunction: (ol.interaction.DrawGeometryFunctionType|undefined), * geometryName: (string|undefined), * condition: (ol.events.ConditionType|undefined), - * freehandCondition: (ol.events.ConditionType|undefined)}} + * freehandCondition: (ol.events.ConditionType|undefined), + * wrapX: (boolean|undefined)}} * @api */ olx.interaction.DrawOptions; @@ -2479,6 +2480,14 @@ olx.interaction.DrawOptions.prototype.condition; olx.interaction.DrawOptions.prototype.freehandCondition; +/** + * Wrap the world horizontally on the sketch overlay. Default is `false`. + * @type {boolean|undefined} + * @api + */ +olx.interaction.DrawOptions.prototype.wrapX; + + /** * @typedef {{condition: (ol.events.ConditionType|undefined), * duration: (number|undefined), @@ -2554,7 +2563,8 @@ olx.interaction.KeyboardZoomOptions.prototype.delta; * @typedef {{deleteCondition: (ol.events.ConditionType|undefined), * pixelTolerance: (number|undefined), * style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined), - * features: ol.Collection.}} + * features: ol.Collection., + * wrapX: (boolean|undefined)}} * @api */ olx.interaction.ModifyOptions; @@ -2596,6 +2606,14 @@ olx.interaction.ModifyOptions.prototype.style; olx.interaction.ModifyOptions.prototype.features; +/** + * Wrap the world horizontally on the sketch overlay. Default is `false`. + * @type {boolean|undefined} + * @api + */ +olx.interaction.ModifyOptions.prototype.wrapX; + + /** * @typedef {{duration: (number|undefined)}} * @api @@ -2717,7 +2735,8 @@ olx.interaction.PointerOptions.prototype.handleUpEvent; * removeCondition: (ol.events.ConditionType|undefined), * toggleCondition: (ol.events.ConditionType|undefined), * multi: (boolean|undefined), - * filter: (ol.interaction.SelectFilterFunction|undefined)}} + * filter: (ol.interaction.SelectFilterFunction|undefined), + * wrapX: (boolean|undefined)}} * @api */ olx.interaction.SelectOptions; @@ -2812,6 +2831,14 @@ olx.interaction.SelectOptions.prototype.multi; olx.interaction.SelectOptions.prototype.filter; +/** + * Wrap the world horizontally on the selection overlay. Default is `true`. + * @type {boolean|undefined} + * @api + */ +olx.interaction.SelectOptions.prototype.wrapX; + + /** * Options for snap * @typedef {{ @@ -3291,6 +3318,7 @@ olx.layer.HeatmapOptions.prototype.visible; * hue: (number|undefined), * opacity: (number|undefined), * saturation: (number|undefined), + * map: (ol.Map|undefined), * source: (ol.source.Image|undefined), * visible: (boolean|undefined), * extent: (ol.Extent|undefined), @@ -3349,6 +3377,17 @@ olx.layer.ImageOptions.prototype.saturation; olx.layer.ImageOptions.prototype.source; +/** + * Sets the layer as overlay on a map. The map will not manage this layer in its + * 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} + * @api + */ +olx.layer.ImageOptions.prototype.map; + + /** * Visibility. Default is `true` (visible). * @type {boolean|undefined} @@ -3390,6 +3429,7 @@ olx.layer.ImageOptions.prototype.maxResolution; * preload: (number|undefined), * saturation: (number|undefined), * source: (ol.source.Tile|undefined), + * map: (ol.Map|undefined), * visible: (boolean|undefined), * extent: (ol.Extent|undefined), * minResolution: (number|undefined), @@ -3457,6 +3497,17 @@ olx.layer.TileOptions.prototype.saturation; olx.layer.TileOptions.prototype.source; +/** + * Sets the layer as overlay on a map. The map will not manage this layer in its + * 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} + * @api + */ +olx.layer.TileOptions.prototype.map; + + /** * Visibility. Default is `true` (visible). * @type {boolean|undefined} @@ -3509,6 +3560,7 @@ olx.layer.TileOptions.prototype.useInterimTilesOnError; * renderBuffer: (number|undefined), * saturation: (number|undefined), * source: (ol.source.Vector|undefined), + * map: (ol.Map|undefined), * style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined), * updateWhileAnimating: (boolean|undefined), * updateWhileInteracting: (boolean|undefined), @@ -3552,6 +3604,17 @@ olx.layer.VectorOptions.prototype.renderOrder; olx.layer.VectorOptions.prototype.hue; +/** + * Sets the layer as overlay on a map. The map will not manage this layer in its + * 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} + * @api + */ +olx.layer.VectorOptions.prototype.map; + + /** * The bounding extent for layer rendering. The layer will not be rendered * outside of this extent. @@ -4986,12 +5049,13 @@ olx.source.TileWMSOptions.prototype.wrapX; /** * @typedef {{attributions: (Array.|undefined), - * features: (Array.|undefined), + * features: (Array.|ol.Collection.|undefined), * format: (ol.format.Feature|undefined), * loader: (ol.FeatureLoader|undefined), * logo: (string|olx.LogoOptions|undefined), * strategy: (ol.LoadingStrategy|undefined), * url: (string|undefined), + * useSpatialIndex: (boolean|undefined), * wrapX: (boolean|undefined)}} * @api */ @@ -5007,8 +5071,9 @@ olx.source.VectorOptions.prototype.attributions; /** - * Features. - * @type {Array.|undefined} + * Features. If provided as {@link ol.Collection}, the features in the source + * and the collection will stay in sync. + * @type {Array.|ol.Collection.|undefined} * @api stable */ olx.source.VectorOptions.prototype.features; @@ -5061,6 +5126,29 @@ olx.source.VectorOptions.prototype.strategy; olx.source.VectorOptions.prototype.url; +/** + * By default, an RTree is used as spatial index. When features are removed and + * added frequently, and the total number of features is low, setting this to + * `false` may improve performance. + * + * Note that + * {@link ol.source.Vector#getFeaturesInExtent}, + * {@link ol.source.Vector#getClosestFeatureToCoordinate} and + * {@link ol.source.Vector#getExtent} cannot be used when `useSpatialIndex` is + * set to `false`, and {@link ol.source.Vector#forEachFeatureInExtent} will loop + * through all features. + * + * When set to `false`, the features will be maintained in an + * {@link ol.Collection}, which can be retrieved through + * {@link ol.source.Vector#getFeaturesCollection}. + * + * The default is `true`. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorOptions.prototype.useSpatialIndex; + + /** * Wrap the world horizontally. Default is `true`. For vector editing across the * -180° and 180° meridians to work properly, this should be set to `false`. The diff --git a/src/ol/featureoverlay.js b/src/ol/featureoverlay.js deleted file mode 100644 index 5a555a0973..0000000000 --- a/src/ol/featureoverlay.js +++ /dev/null @@ -1,326 +0,0 @@ -goog.provide('ol.FeatureOverlay'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.Collection'); -goog.require('ol.CollectionEventType'); -goog.require('ol.Feature'); -goog.require('ol.render.EventType'); -goog.require('ol.renderer.vector'); -goog.require('ol.style.Style'); - - - -/** - * @classdesc - * A mechanism for changing the style of a small number of features on a - * temporary basis, for example highlighting. This is necessary with the Canvas - * renderer, where, unlike in SVG, features cannot be individually referenced. - * See examples/vector-layers for an example: create a FeatureOverlay with a - * different style, copy the feature(s) you want rendered in this different - * style into it, and then remove them again when you're finished. - * - * @constructor - * @param {olx.FeatureOverlayOptions=} opt_options Options. - * @api - */ -ol.FeatureOverlay = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.Collection.} - */ - this.features_ = null; - - /** - * @private - * @type {Array.} - */ - this.featuresListenerKeys_ = null; - - /** - * @private - * @type {Object.} - */ - this.featureChangeListenerKeys_ = null; - - /** - * @private - * @type {ol.Map} - */ - this.map_ = null; - - /** - * @private - * @type {goog.events.Key} - */ - this.postComposeListenerKey_ = null; - - /** - * @private - * @type {ol.style.Style|Array.|ol.style.StyleFunction} - */ - this.style_ = null; - - /** - * @private - * @type {ol.style.StyleFunction|undefined} - */ - this.styleFunction_ = undefined; - - this.setStyle(goog.isDef(options.style) ? - options.style : ol.style.defaultStyleFunction); - - if (goog.isDef(options.features)) { - if (goog.isArray(options.features)) { - this.setFeatures(new ol.Collection(options.features.slice())); - } else { - goog.asserts.assertInstanceof(options.features, ol.Collection, - 'options.features should be an ol.Collection'); - this.setFeatures(options.features); - } - } else { - this.setFeatures(new ol.Collection()); - } - - if (goog.isDef(options.map)) { - this.setMap(options.map); - } - -}; - - -/** - * Add a feature to the overlay. - * @param {ol.Feature} feature Feature. - * @api - */ -ol.FeatureOverlay.prototype.addFeature = function(feature) { - this.features_.push(feature); -}; - - -/** - * Get the features on the overlay. - * @return {ol.Collection.} Features collection. - * @api - */ -ol.FeatureOverlay.prototype.getFeatures = function() { - return this.features_; -}; - - -/** - * Get the map associated with the overlay. - * @return {?ol.Map} The map with which this feature overlay is associated. - * @api - */ -ol.FeatureOverlay.prototype.getMap = function() { - return this.map_; -}; - - -/** - * @private - */ -ol.FeatureOverlay.prototype.handleFeatureChange_ = function() { - this.render_(); -}; - - -/** - * @private - * @param {ol.CollectionEvent} collectionEvent Collection event. - */ -ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) { - goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_), - 'this.featureChangeListenerKeys_ should not be null'); - var feature = /** @type {ol.Feature} */ (collectionEvent.element); - this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = - goog.events.listen(feature, goog.events.EventType.CHANGE, - this.handleFeatureChange_, false, this); - this.render_(); -}; - - -/** - * @private - * @param {ol.CollectionEvent} collectionEvent Collection event. - */ -ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) { - goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_), - 'this.featureChangeListenerKeys_ should not be null'); - var feature = /** @type {ol.Feature} */ (collectionEvent.element); - var key = goog.getUid(feature).toString(); - goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]); - delete this.featureChangeListenerKeys_[key]; - this.render_(); -}; - - -/** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. - * @private - */ -ol.FeatureOverlay.prototype.handleImageChange_ = function(event) { - this.render_(); -}; - - -/** - * @param {ol.render.Event} event Event. - * @private - */ -ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) { - if (goog.isNull(this.features_)) { - return; - } - var styleFunction = this.styleFunction_; - if (!goog.isDef(styleFunction)) { - styleFunction = ol.style.defaultStyleFunction; - } - var replayGroup = /** @type {ol.render.IReplayGroup} */ - (event.replayGroup); - goog.asserts.assert(goog.isDef(replayGroup), - 'replayGroup should be defined'); - var frameState = event.frameState; - var pixelRatio = frameState.pixelRatio; - var resolution = frameState.viewState.resolution; - var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution, - pixelRatio); - var i, ii, styles, featureStyleFunction; - this.features_.forEach(function(feature) { - featureStyleFunction = feature.getStyleFunction(); - styles = goog.isDef(featureStyleFunction) ? - featureStyleFunction.call(feature, resolution) : - styleFunction(feature, resolution); - - if (!goog.isDefAndNotNull(styles)) { - return; - } - ii = styles.length; - for (i = 0; i < ii; ++i) { - ol.renderer.vector.renderFeature(replayGroup, feature, styles[i], - squaredTolerance, this.handleImageChange_, this); - } - }, this); -}; - - -/** - * Remove a feature from the overlay. - * @param {ol.Feature} feature The feature to be removed. - * @api - */ -ol.FeatureOverlay.prototype.removeFeature = function(feature) { - this.features_.remove(feature); -}; - - -/** - * @private - */ -ol.FeatureOverlay.prototype.render_ = function() { - if (!goog.isNull(this.map_)) { - this.map_.render(); - } -}; - - -/** - * Set the features for the overlay. - * @param {ol.Collection.} features Features collection. - * @api - */ -ol.FeatureOverlay.prototype.setFeatures = function(features) { - if (!goog.isNull(this.featuresListenerKeys_)) { - goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey); - this.featuresListenerKeys_ = null; - } - if (!goog.isNull(this.featureChangeListenerKeys_)) { - goog.array.forEach( - goog.object.getValues(this.featureChangeListenerKeys_), - goog.events.unlistenByKey); - this.featureChangeListenerKeys_ = null; - } - this.features_ = features; - if (!goog.isNull(features)) { - this.featuresListenerKeys_ = [ - goog.events.listen(features, ol.CollectionEventType.ADD, - this.handleFeaturesAdd_, false, this), - goog.events.listen(features, ol.CollectionEventType.REMOVE, - this.handleFeaturesRemove_, false, this) - ]; - this.featureChangeListenerKeys_ = {}; - features.forEach(function(feature) { - this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = - goog.events.listen(feature, goog.events.EventType.CHANGE, - this.handleFeatureChange_, false, this); - }, this); - } - this.render_(); -}; - - -/** - * Set the map for the overlay. - * @param {ol.Map} map Map. - * @api - */ -ol.FeatureOverlay.prototype.setMap = function(map) { - if (!goog.isNull(this.postComposeListenerKey_)) { - goog.events.unlistenByKey(this.postComposeListenerKey_); - this.postComposeListenerKey_ = null; - } - this.render_(); - this.map_ = map; - if (!goog.isNull(map)) { - this.postComposeListenerKey_ = goog.events.listen( - map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false, - this); - map.render(); - } -}; - - -/** - * Set the style for features. This can be a single style object, an array - * of styles, or a function that takes a feature and resolution and returns - * an array of styles. - * @param {ol.style.Style|Array.|ol.style.StyleFunction} style - * Overlay style. - * @api - */ -ol.FeatureOverlay.prototype.setStyle = function(style) { - this.style_ = style; - this.styleFunction_ = ol.style.createStyleFunction(style); - this.render_(); -}; - - -/** - * Get the style for features. This returns whatever was passed to the `style` - * option at construction or to the `setStyle` method. - * @return {ol.style.Style|Array.|ol.style.StyleFunction} - * Overlay style. - * @api - */ -ol.FeatureOverlay.prototype.getStyle = function() { - return this.style_; -}; - - -/** - * Get the style function. - * @return {ol.style.StyleFunction|undefined} Style function. - * @api - */ -ol.FeatureOverlay.prototype.getStyleFunction = function() { - return this.styleFunction_; -}; diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index 1bdd603643..088da09a0a 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -10,7 +10,6 @@ goog.require('goog.events.Event'); goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.MapBrowserEvent'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.Object'); @@ -27,6 +26,7 @@ goog.require('ol.geom.Polygon'); goog.require('ol.geom.SimpleGeometry'); goog.require('ol.interaction.InteractionProperty'); goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); goog.require('ol.source.Vector'); goog.require('ol.style.Style'); @@ -269,10 +269,14 @@ ol.interaction.Draw = function(options) { /** * Draw overlay where our sketch features are drawn. - * @type {ol.FeatureOverlay} + * @type {ol.layer.Vector} * @private */ - this.overlay_ = new ol.FeatureOverlay({ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : false + }), style: goog.isDef(options.style) ? options.style : ol.interaction.Draw.getDefaultStyleFunction() }); @@ -674,7 +678,7 @@ ol.interaction.Draw.prototype.abortDrawing_ = function() { this.sketchFeature_ = null; this.sketchPoint_ = null; this.sketchLine_ = null; - this.overlay_.getFeatures().clear(); + this.overlay_.getSource().clear(true); } return sketchFeature; }; @@ -701,7 +705,9 @@ ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { if (!goog.isNull(this.sketchPoint_)) { sketchFeatures.push(this.sketchPoint_); } - this.overlay_.setFeatures(new ol.Collection(sketchFeatures)); + var overlaySource = this.overlay_.getSource(); + overlaySource.clear(true); + overlaySource.addFeatures(sketchFeatures); }; diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index 60322e6dcc..ad6e34993d 100644 --- a/src/ol/interaction/modifyinteraction.js +++ b/src/ol/interaction/modifyinteraction.js @@ -7,7 +7,6 @@ goog.require('goog.functions'); goog.require('ol.Collection'); goog.require('ol.CollectionEventType'); goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.ViewHint'); goog.require('ol.coordinate'); @@ -21,6 +20,8 @@ goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); goog.require('ol.structs.RBush'); goog.require('ol.style.Style'); @@ -112,10 +113,14 @@ ol.interaction.Modify = function(options) { /** * Draw overlay where are sketch features are drawn. - * @type {ol.FeatureOverlay} + * @type {ol.layer.Vector} * @private */ - this.overlay_ = new ol.FeatureOverlay({ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : false + }), style: goog.isDef(options.style) ? options.style : ol.interaction.Modify.getDefaultStyleFunction() }); @@ -208,7 +213,7 @@ ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) { // There remains only vertexFeature… if (!goog.isNull(this.vertexFeature_) && this.features_.getLength() === 0) { - this.overlay_.removeFeature(this.vertexFeature_); + this.overlay_.getSource().removeFeature(this.vertexFeature_); this.vertexFeature_ = null; } }; @@ -383,7 +388,7 @@ ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = if (goog.isNull(vertexFeature)) { vertexFeature = new ol.Feature(new ol.geom.Point(coordinates)); this.vertexFeature_ = vertexFeature; - this.overlay_.addFeature(vertexFeature); + this.overlay_.getSource().addFeature(vertexFeature); } else { var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); geometry.setCoordinates(coordinates); @@ -630,7 +635,7 @@ ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { } } if (!goog.isNull(this.vertexFeature_)) { - this.overlay_.removeFeature(this.vertexFeature_); + this.overlay_.getSource().removeFeature(this.vertexFeature_); this.vertexFeature_ = null; } }; @@ -800,7 +805,7 @@ ol.interaction.Modify.prototype.removeVertex_ = function() { newSegmentData); this.updateSegmentIndices_(geometry, index, segmentData.depth, -1); - this.overlay_.removeFeature(this.vertexFeature_); + this.overlay_.getSource().removeFeature(this.vertexFeature_); this.vertexFeature_ = null; } } diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 58388f0402..1f47a75353 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -8,10 +8,11 @@ goog.require('goog.events.Event'); goog.require('goog.functions'); goog.require('ol.CollectionEventType'); goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); goog.require('ol.events.condition'); goog.require('ol.geom.GeometryType'); goog.require('ol.interaction.Interaction'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); goog.require('ol.style.Style'); @@ -82,7 +83,7 @@ goog.inherits(ol.SelectEvent, goog.events.Event); /** * @classdesc - * Handles selection of vector data. A {@link ol.FeatureOverlay} is maintained + * Handles selection of vector data. An {@link ol.source.Vector} is maintained * internally to store the selected feature(s). Which features are selected is * determined by the `condition` option, and optionally the `toggle` or * `add`/`remove` options. @@ -169,14 +170,18 @@ ol.interaction.Select = function(opt_options) { /** * @private - * @type {ol.FeatureOverlay} + * @type {ol.layer.Vector} */ - this.featureOverlay_ = new ol.FeatureOverlay({ + this.featureOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: options.wrapX + }), style: goog.isDef(options.style) ? options.style : ol.interaction.Select.getDefaultStyleFunction() }); - var features = this.featureOverlay_.getFeatures(); + var features = this.featureOverlay_.getSource().getFeaturesCollection(); goog.events.listen(features, ol.CollectionEventType.ADD, this.addFeature_, false, this); goog.events.listen(features, ol.CollectionEventType.REMOVE, @@ -192,7 +197,7 @@ goog.inherits(ol.interaction.Select, ol.interaction.Interaction); * @api stable */ ol.interaction.Select.prototype.getFeatures = function() { - return this.featureOverlay_.getFeatures(); + return this.featureOverlay_.getSource().getFeaturesCollection(); }; @@ -213,7 +218,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { var toggle = this.toggleCondition_(mapBrowserEvent); var set = !add && !remove && !toggle; var map = mapBrowserEvent.map; - var features = this.featureOverlay_.getFeatures(); + var features = this.featureOverlay_.getSource().getFeaturesCollection(); var /** @type {Array.} */ deselected = []; var /** @type {Array.} */ selected = []; var change = false; @@ -290,7 +295,8 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { */ ol.interaction.Select.prototype.setMap = function(map) { var currentMap = this.getMap(); - var selectedFeatures = this.featureOverlay_.getFeatures(); + var selectedFeatures = + this.featureOverlay_.getSource().getFeaturesCollection(); if (!goog.isNull(currentMap)) { selectedFeatures.forEach(currentMap.unskipFeature, currentMap); } diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index 989cf70389..13f3a565ed 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -6,6 +6,7 @@ goog.require('goog.object'); goog.require('ol.Object'); goog.require('ol.layer.Base'); goog.require('ol.layer.LayerProperty'); +goog.require('ol.render.EventType'); goog.require('ol.source.State'); @@ -32,12 +33,28 @@ ol.layer.Layer = function(options) { goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + /** + * @private + * @type {goog.events.Key} + */ + this.mapPrecomposeKey_ = null; + + /** + * @private + * @type {goog.events.Key} + */ + this.mapRenderKey_ = null; + /** * @private * @type {goog.events.Key} */ this.sourceChangeKey_ = null; + if (goog.isDef(options.map)) { + this.setMap(options.map); + } + goog.events.listen(this, ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE), this.handleSourcePropertyChange_, false, this); @@ -129,6 +146,33 @@ ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { }; +/** + * Sets the layer to be rendered on a map. The map will not manage this layer in + * its layers collection, and the layer will be rendered on top. This is useful + * for temporary layers. To remove an unmanaged layer from the map, use + * `#setMap(null)`. 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. + * @api + */ +ol.layer.Layer.prototype.setMap = function(map) { + goog.events.unlistenByKey(this.mapPrecomposeKey_); + this.changed(); + goog.events.unlistenByKey(this.mapRenderKey_); + if (!goog.isNull(map)) { + this.mapPrecomposeKey_ = goog.events.listen( + map, ol.render.EventType.PRECOMPOSE, function(evt) { + var layerState = this.getLayerState(); + layerState.unmanaged = true; + evt.frameState.layerStatesArray.push(layerState); + evt.frameState.layerStates[goog.getUid(this)] = layerState; + }, false, this); + this.mapRenderKey_ = goog.events.listen( + this, goog.events.EventType.CHANGE, map.render, false, map); + } +}; + + /** * Set the layer source. * @param {ol.source.Source} source The layer source. diff --git a/src/ol/map.js b/src/ol/map.js index 8e3b677aa1..5b1fb9e37f 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -578,10 +578,8 @@ ol.Map.prototype.disposeInternal = function() { * @param {function(this: S, ol.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} at the pixel, the second is - * the {@link ol.layer.Layer layer} of the feature. If the detected feature - * is not on a layer, but on a {@link ol.FeatureOverlay}, then the second - * argument to this function will be `null`. To stop detection, callback - * functions can return a truthy value. + * the {@link ol.layer.Layer layer} of the feature. 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 @@ -619,10 +617,8 @@ ol.Map.prototype.forEachFeatureAtPixel = * @param {ol.Pixel} pixel Pixel. * @param {function(this: S, ol.layer.Layer): T} callback Layer * callback. Will receive one argument, the {@link ol.layer.Layer layer} - * that contains the color pixel. If the detected color value is not from a - * layer, but from a {@link ol.FeatureOverlay}, then the argument to this - * function will be `null`. To stop detection, callback functions can return - * a truthy value. + * that contains the color pixel. 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 diff --git a/src/ol/render/canvas/canvasimmediate.js b/src/ol/render/canvas/canvasimmediate.js index 78306cbd5f..ce0ed0e96e 100644 --- a/src/ol/render/canvas/canvasimmediate.js +++ b/src/ol/render/canvas/canvasimmediate.js @@ -471,8 +471,8 @@ ol.render.canvas.Immediate.prototype.drawCircleGeometry = * Render a feature into the canvas. In order to respect the zIndex of the * style this method draws asynchronously and thus *after* calls to * drawXxxxGeometry have been finished, effectively drawing the feature - * *on top* of everything else. You probably should be using - * {@link ol.FeatureOverlay} instead of calling this method directly. + * *on top* of everything else. You probably should be using an + * {@link ol.layer.Vector} instead of calling this method directly. * * @param {ol.Feature} feature Feature. * @param {ol.style.Style} style Style. diff --git a/src/ol/render/renderevent.js b/src/ol/render/renderevent.js index 6fc57b63bc..6d8810ae7e 100644 --- a/src/ol/render/renderevent.js +++ b/src/ol/render/renderevent.js @@ -35,14 +35,13 @@ ol.render.EventType = { * @param {ol.render.EventType} type Type. * @param {Object=} opt_target Target. * @param {ol.render.VectorContext=} opt_vectorContext Vector context. - * @param {ol.render.IReplayGroup=} opt_replayGroup Replay group. * @param {olx.FrameState=} opt_frameState Frame state. * @param {?CanvasRenderingContext2D=} opt_context Context. * @param {?ol.webgl.Context=} opt_glContext WebGL Context. */ ol.render.Event = function( - type, opt_target, opt_vectorContext, opt_replayGroup, opt_frameState, - opt_context, opt_glContext) { + type, opt_target, opt_vectorContext, opt_frameState, opt_context, + opt_glContext) { goog.base(this, type, opt_target); @@ -53,11 +52,6 @@ ol.render.Event = function( */ this.vectorContext = opt_vectorContext; - /** - * @type {ol.render.IReplayGroup|undefined} - */ - this.replayGroup = opt_replayGroup; - /** * @type {olx.FrameState|undefined} * @api diff --git a/src/ol/renderer/canvas/canvaslayerrenderer.js b/src/ol/renderer/canvas/canvaslayerrenderer.js index ac8b9a112a..7e85d22355 100644 --- a/src/ol/renderer/canvas/canvaslayerrenderer.js +++ b/src/ol/renderer/canvas/canvaslayerrenderer.js @@ -131,8 +131,8 @@ ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = var render = new ol.render.canvas.Immediate( context, frameState.pixelRatio, frameState.extent, transform, frameState.viewState.rotation); - var composeEvent = new ol.render.Event(type, layer, render, null, - frameState, context, null); + var composeEvent = new ol.render.Event(type, layer, render, frameState, + context, null); layer.dispatchEvent(composeEvent); render.flush(); } diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index 6a1c38e691..609dc09c02 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -10,7 +10,6 @@ goog.require('ol'); goog.require('ol.RendererType'); goog.require('ol.css'); goog.require('ol.dom'); -goog.require('ol.extent'); goog.require('ol.layer.Image'); goog.require('ol.layer.Layer'); goog.require('ol.layer.Tile'); @@ -18,13 +17,11 @@ goog.require('ol.layer.Vector'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); goog.require('ol.render.canvas.Immediate'); -goog.require('ol.render.canvas.ReplayGroup'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.canvas.ImageLayer'); goog.require('ol.renderer.canvas.Layer'); goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.canvas.VectorLayer'); -goog.require('ol.renderer.vector'); goog.require('ol.source.State'); goog.require('ol.vec.Mat4'); @@ -103,55 +100,27 @@ ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = var extent = frameState.extent; var pixelRatio = frameState.pixelRatio; var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; var rotation = viewState.rotation; - var offsetX = 0; - if (projection.canWrapX()) { - var projectionExtent = projection.getExtent(); - var worldWidth = ol.extent.getWidth(projectionExtent); - var x = frameState.focus[0]; - if (x < projectionExtent[0] || x > projectionExtent[2]) { - var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); - offsetX = worldWidth * worldsAway; - extent = [ - extent[0] + offsetX, extent[1], - extent[2] + offsetX, extent[3] - ]; - } - } - - var transform = this.getTransform(frameState, offsetX); - - var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); - var replayGroup = new ol.render.canvas.ReplayGroup( - tolerance, extent, resolution); + var transform = this.getTransform(frameState); var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, extent, transform, rotation); var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, context, null); + frameState, context, null); map.dispatchEvent(composeEvent); - replayGroup.finish(); - if (!replayGroup.isEmpty()) { - replayGroup.replay(context, pixelRatio, transform, rotation, {}); - - } vectorContext.flush(); - this.replayGroup = replayGroup; } }; /** * @param {olx.FrameState} frameState Frame state. - * @param {number} offsetX Offset on the x-axis in view coordinates. * @protected * @return {!goog.vec.Mat4.Number} Transform. */ -ol.renderer.canvas.Map.prototype.getTransform = function(frameState, offsetX) { +ol.renderer.canvas.Map.prototype.getTransform = function(frameState) { var pixelRatio = frameState.pixelRatio; var viewState = frameState.viewState; var resolution = viewState.resolution; @@ -159,8 +128,7 @@ ol.renderer.canvas.Map.prototype.getTransform = function(frameState, offsetX) { this.canvas_.width / 2, this.canvas_.height / 2, pixelRatio / resolution, -pixelRatio / resolution, -viewState.rotation, - -viewState.center[0] - offsetX, - -viewState.center[1]); + -viewState.center[0], -viewState.center[1]); }; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 45a83ea653..e9a1c4b5b2 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -77,9 +77,9 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { var extent = frameState.extent; - var focus = frameState.focus; var pixelRatio = frameState.pixelRatio; - var skippedFeatureUids = frameState.skippedFeatureUids; + var skippedFeatureUids = layerState.unmanaged ? + {} : frameState.skippedFeatureUids; var viewState = frameState.viewState; var projection = viewState.projection; var rotation = viewState.rotation; @@ -108,20 +108,11 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = // see http://jsperf.com/context-save-restore-versus-variable var alpha = replayContext.globalAlpha; replayContext.globalAlpha = layerState.opacity; - var noSkip = {}; - var focusX = focus[0]; + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); if (vectorSource.getWrapX() && projection.canWrapX() && !ol.extent.containsExtent(projectionExtent, extent)) { - var projLeft = projectionExtent[0]; - var projRight = projectionExtent[2]; - // A feature from skippedFeatureUids will only be skipped in the world - // that has the frameState's focus, because this is where a feature - // overlay for highlighting or selection would render the skipped - // feature. - replayGroup.replay(replayContext, pixelRatio, transform, rotation, - projLeft <= focusX && focusX <= projRight ? - skippedFeatureUids : noSkip); var startX = extent[0]; var worldWidth = ol.extent.getWidth(projectionExtent); var world = 0; @@ -131,8 +122,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); replayGroup.replay(replayContext, pixelRatio, transform, rotation, - projLeft + offsetX <= focusX && focusX <= projRight + offsetX ? - skippedFeatureUids : noSkip); + skippedFeatureUids); startX += worldWidth; } world = 0; @@ -142,13 +132,9 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); replayGroup.replay(replayContext, pixelRatio, transform, rotation, - projLeft + offsetX <= focusX && focusX <= projRight + offsetX ? - skippedFeatureUids : noSkip); + skippedFeatureUids); startX -= worldWidth; } - } else { - replayGroup.replay( - replayContext, pixelRatio, transform, rotation, skippedFeatureUids); } if (replayContext != context) { diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js index ea2cb5ee6e..bbe8e4e9ca 100644 --- a/src/ol/renderer/dom/dommaprenderer.js +++ b/src/ol/renderer/dom/dommaprenderer.js @@ -20,13 +20,11 @@ goog.require('ol.layer.Vector'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); goog.require('ol.render.canvas.Immediate'); -goog.require('ol.render.canvas.ReplayGroup'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.dom.ImageLayer'); goog.require('ol.renderer.dom.Layer'); goog.require('ol.renderer.dom.TileLayer'); goog.require('ol.renderer.dom.VectorLayer'); -goog.require('ol.renderer.vector'); goog.require('ol.source.State'); goog.require('ol.vec.Mat4'); @@ -139,7 +137,6 @@ ol.renderer.dom.Map.prototype.dispatchComposeEvent_ = var extent = frameState.extent; var pixelRatio = frameState.pixelRatio; var viewState = frameState.viewState; - var resolution = viewState.resolution; var rotation = viewState.rotation; var context = this.context_; var canvas = context.canvas; @@ -153,18 +150,10 @@ ol.renderer.dom.Map.prototype.dispatchComposeEvent_ = -viewState.center[0], -viewState.center[1]); var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, extent, this.transform_, rotation); - var replayGroup = new ol.render.canvas.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, context, null); + frameState, context, null); map.dispatchEvent(composeEvent); - replayGroup.finish(); - if (!replayGroup.isEmpty()) { - replayGroup.replay(context, pixelRatio, this.transform_, rotation, {}); - } vectorContext.flush(); - this.replayGroup = replayGroup; } }; diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index 9cf5f32dbd..a59873f645 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -142,7 +142,7 @@ ol.renderer.dom.VectorLayer.prototype.composeFrame = context.globalAlpha = layerState.opacity; replayGroup.replay(context, pixelRatio, transform, viewRotation, - frameState.skippedFeatureUids); + layerState.unmanaged ? {} : frameState.skippedFeatureUids); this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform); } @@ -165,8 +165,8 @@ ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = var render = new ol.render.canvas.Immediate( context, frameState.pixelRatio, frameState.extent, transform, frameState.viewState.rotation); - var event = new ol.render.Event(type, layer, render, null, - frameState, context, null); + var event = new ol.render.Event(type, layer, render, frameState, + context, null); layer.dispatchEvent(event); render.flush(); } diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index 666ae642b0..9d5344ee22 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -48,12 +48,6 @@ ol.renderer.Map = function(container, map) { */ this.map_ = map; - /** - * @protected - * @type {ol.render.IReplayGroup} - */ - this.replayGroup = null; - /** * @private * @type {Object.} @@ -137,7 +131,6 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate = var result; var viewState = frameState.viewState; var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; /** @type {Object.} */ var features = {}; @@ -168,13 +161,6 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate = } } - if (!goog.isNull(this.replayGroup)) { - result = this.replayGroup.forEachFeatureAtCoordinate(translatedCoordinate, - viewResolution, viewRotation, {}, forEachFeatureAtCoordinate); - if (result) { - return result; - } - } var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; @@ -216,20 +202,7 @@ ol.renderer.Map.prototype.forEachLayerAtPixel = var result; var viewState = frameState.viewState; var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; - if (!goog.isNull(this.replayGroup)) { - var coordinate = this.getMap().getCoordinateFromPixel(pixel); - var hasFeature = this.replayGroup.forEachFeatureAtCoordinate(coordinate, - viewResolution, viewRotation, {}, goog.functions.TRUE); - - if (hasFeature) { - result = callback.call(thisArg, null); - if (result) { - return result; - } - } - } var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 45b66f2512..30b37343e9 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -247,7 +247,7 @@ ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = var render = new ol.render.webgl.Immediate( context, center, resolution, rotation, size, extent, pixelRatio); var composeEvent = new ol.render.Event( - type, layer, render, null, frameState, null, context); + type, layer, render, frameState, null, context); layer.dispatchEvent(composeEvent); } }; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index c90f09536a..a62a8f0348 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -24,9 +24,7 @@ goog.require('ol.layer.Vector'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); goog.require('ol.render.webgl.Immediate'); -goog.require('ol.render.webgl.ReplayGroup'); goog.require('ol.renderer.Map'); -goog.require('ol.renderer.vector'); goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.TileLayer'); @@ -287,27 +285,14 @@ ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = var resolution = viewState.resolution; var center = viewState.center; var rotation = viewState.rotation; - var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); var vectorContext = new ol.render.webgl.Immediate(context, center, resolution, rotation, size, extent, pixelRatio); - var replayGroup = new ol.render.webgl.ReplayGroup(tolerance, extent); var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, null, context); + frameState, null, context); map.dispatchEvent(composeEvent); - replayGroup.finish(context); - if (!replayGroup.isEmpty()) { - // use default color values - var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - replayGroup.replay(context, center, resolution, rotation, size, - pixelRatio, d.opacity, d.brightness, d.contrast, - d.hue, d.saturation, {}); - } - replayGroup.getDeleteResourcesFunction(context)(); - vectorContext.flush(); - this.replayGroup = replayGroup; } }; @@ -561,37 +546,8 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = return false; } - var context = this.getContext(); var viewState = frameState.viewState; - // do the hit-detection for the overlays first - if (!goog.isNull(this.replayGroup)) { - /** @type {Object.} */ - var features = {}; - - // use default color values - var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - - result = this.replayGroup.forEachFeatureAtCoordinate(coordinate, - context, viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, - d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - goog.asserts.assert(goog.isDef(feature), 'received a feature'); - var key = goog.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, null); - } - }); - if (result) { - return result; - } - } var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; @@ -623,22 +579,8 @@ ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = return false; } - var context = this.getContext(); var viewState = frameState.viewState; - // do the hit-detection for the overlays first - if (!goog.isNull(this.replayGroup)) { - // use default color values - var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - - hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate, - context, viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, - d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}); - if (hasFeature) { - return true; - } - } var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; @@ -669,27 +611,9 @@ ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = return false; } - var context = this.getContext(); var viewState = frameState.viewState; var result; - // do the hit-detection for the overlays first - if (!goog.isNull(this.replayGroup)) { - // use default color values - var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - var coordinate = this.getMap().getCoordinateFromPixel(pixel); - - var hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate, - context, viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, - d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}); - if (hasFeature) { - result = callback.call(thisArg, null); - if (result) { - return result; - } - } - } var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index f12022d915..6195623530 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -83,7 +83,8 @@ ol.renderer.webgl.VectorLayer.prototype.composeFrame = viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, layerState.opacity, layerState.brightness, layerState.contrast, layerState.hue, - layerState.saturation, frameState.skippedFeatureUids); + layerState.saturation, + layerState.unmanaged ? {} : frameState.skippedFeatureUids); } }; diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 42f945f9d7..96a3c5f4b1 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -1,5 +1,4 @@ // FIXME bulk feature upload - suppress events -// FIXME put features in an ol.Collection // FIXME make change-detection more refined (notably, geometry hint) goog.provide('ol.source.Vector'); @@ -12,7 +11,10 @@ goog.require('goog.events'); goog.require('goog.events.Event'); goog.require('goog.events.EventType'); goog.require('goog.object'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEventType'); goog.require('ol.Extent'); +goog.require('ol.Feature'); goog.require('ol.FeatureLoader'); goog.require('ol.LoadingStrategy'); goog.require('ol.ObjectEventType'); @@ -105,11 +107,14 @@ ol.source.Vector = function(opt_options) { this.strategy_ = goog.isDef(options.strategy) ? options.strategy : ol.loadingstrategy.all; + var useSpatialIndex = + goog.isDef(options.useSpatialIndex) ? options.useSpatialIndex : true; + /** * @private * @type {ol.structs.RBush.} */ - this.featuresRtree_ = new ol.structs.RBush(); + this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null; /** * @private @@ -143,8 +148,27 @@ ol.source.Vector = function(opt_options) { */ this.featureChangeKeys_ = {}; - if (goog.isDef(options.features)) { - this.addFeaturesInternal(options.features); + /** + * @private + * @type {ol.Collection.} + */ + this.featuresCollection_ = null; + + var collection, features; + if (options.features instanceof ol.Collection) { + collection = options.features; + features = collection.getArray(); + } else if (goog.isArray(options.features)) { + features = options.features; + } + if (!useSpatialIndex && !goog.isDef(collection)) { + collection = new ol.Collection(features); + } + if (goog.isDef(features)) { + this.addFeaturesInternal(features); + } + if (goog.isDef(collection)) { + this.bindFeaturesCollection_(collection); } }; @@ -181,7 +205,9 @@ ol.source.Vector.prototype.addFeatureInternal = function(feature) { var geometry = feature.getGeometry(); if (goog.isDefAndNotNull(geometry)) { var extent = geometry.getExtent(); - this.featuresRtree_.insert(extent, feature); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.insert(extent, feature); + } } else { this.nullGeometryFeatures_[featureKey] = feature; } @@ -280,7 +306,9 @@ ol.source.Vector.prototype.addFeaturesInternal = function(features) { this.nullGeometryFeatures_[featureKey] = feature; } } - this.featuresRtree_.load(extents, geometryFeatures); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.load(extents, geometryFeatures); + } for (i = 0, length = newFeatures.length; i < length; i++) { this.dispatchEvent(new ol.source.VectorEvent( @@ -289,6 +317,54 @@ ol.source.Vector.prototype.addFeaturesInternal = function(features) { }; +/** + * @param {!ol.Collection.} collection Collection. + * @private + */ +ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) { + goog.asserts.assert(goog.isNull(this.featuresCollection_), + 'bindFeaturesCollection can only be called once'); + var modifyingCollection = false; + goog.events.listen(this, ol.source.VectorEventType.ADDFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.push(evt.feature); + modifyingCollection = false; + } + }); + goog.events.listen(this, ol.source.VectorEventType.REMOVEFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.remove(evt.feature); + modifyingCollection = false; + } + }); + goog.events.listen(collection, ol.CollectionEventType.ADD, + function(evt) { + if (!modifyingCollection) { + var feature = evt.element; + goog.asserts.assertInstanceof(feature, ol.Feature); + modifyingCollection = true; + this.addFeature(feature); + modifyingCollection = false; + } + }, false, this); + goog.events.listen(collection, ol.CollectionEventType.REMOVE, + function(evt) { + if (!modifyingCollection) { + var feature = evt.element; + goog.asserts.assertInstanceof(feature, ol.Feature); + modifyingCollection = true; + this.removeFeature(feature); + modifyingCollection = false; + } + }, false, this); + this.featuresCollection_ = collection; +}; + + /** * Remove all features from the source. * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events. @@ -296,26 +372,34 @@ ol.source.Vector.prototype.addFeaturesInternal = function(features) { */ ol.source.Vector.prototype.clear = function(opt_fast) { if (opt_fast) { - for (var featureId in this.featureChangeKeys_) { - var keys = this.featureChangeKeys_[featureId]; - goog.array.forEach(keys, goog.events.unlistenByKey); + if (goog.isNull(this.featuresCollection_)) { + for (var featureId in this.featureChangeKeys_) { + var keys = this.featureChangeKeys_[featureId]; + goog.array.forEach(keys, goog.events.unlistenByKey); + } + this.featureChangeKeys_ = {}; + this.idIndex_ = {}; + this.undefIdIndex_ = {}; + } else { + this.featuresCollection_.clear(); } - this.featureChangeKeys_ = {}; - this.idIndex_ = {}; - this.undefIdIndex_ = {}; } else { var rmFeatureInternal = this.removeFeatureInternal; - this.featuresRtree_.forEach(rmFeatureInternal, this); - goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this); - goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_), - 'featureChangeKeys is an empty object now'); - goog.asserts.assert(goog.object.isEmpty(this.idIndex_), - 'idIndex is an empty object now'); - goog.asserts.assert(goog.object.isEmpty(this.undefIdIndex_), - 'undefIdIndex is an empty object now'); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.forEach(rmFeatureInternal, this); + goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this); + } } + goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_), + 'featureChangeKeys is an empty object now'); + goog.asserts.assert(goog.object.isEmpty(this.idIndex_), + 'idIndex is an empty object now'); + goog.asserts.assert(goog.object.isEmpty(this.undefIdIndex_), + 'undefIdIndex is an empty object now'); - this.featuresRtree_.clear(); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.clear(); + } this.loadedExtentsRtree_.clear(); this.nullGeometryFeatures_ = {}; @@ -338,7 +422,11 @@ ol.source.Vector.prototype.clear = function(opt_fast) { * @api stable */ ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { - return this.featuresRtree_.forEach(callback, opt_this); + if (!goog.isNull(this.featuresRtree_)) { + return this.featuresRtree_.forEach(callback, opt_this); + } else if (!goog.isNull(this.featuresCollection_)) { + return this.featuresCollection_.forEach(callback, opt_this); + } }; @@ -381,6 +469,9 @@ ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = * the {@link ol.source.Vector#forEachFeatureIntersectingExtent * source.forEachFeatureIntersectingExtent()} method instead. * + * When `useSpatialIndex` is set to false, this method will loop through all + * features, equivalent to {@link ol.source.Vector#forEachFeature}. + * * @param {ol.Extent} extent Extent. * @param {function(this: T, ol.Feature): S} callback Called with each feature * whose bounding box intersects the provided extent. @@ -391,7 +482,11 @@ ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = */ ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) { - return this.featuresRtree_.forEachInExtent(extent, callback, opt_this); + if (!goog.isNull(this.featuresRtree_)) { + return this.featuresRtree_.forEachInExtent(extent, callback, opt_this); + } else if (!goog.isNull(this.featuresCollection_)) { + return this.featuresCollection_.forEach(callback, opt_this); + } }; @@ -448,17 +543,36 @@ ol.source.Vector.prototype.forEachFeatureIntersectingExtent = }; +/** + * Get the features collection associated with this source. Will be `null` + * unless the source was configured with `useSpatialIndex` set to `false`, or + * with an {@link ol.Collection} as `features`. + * @return {ol.Collection.} + * @api + */ +ol.source.Vector.prototype.getFeaturesCollection = function() { + return this.featuresCollection_; +}; + + /** * Get all features on the source. * @return {Array.} Features. * @api stable */ ol.source.Vector.prototype.getFeatures = function() { - var features = this.featuresRtree_.getAll(); - if (!goog.object.isEmpty(this.nullGeometryFeatures_)) { - goog.array.extend( - features, goog.object.getValues(this.nullGeometryFeatures_)); + var features; + if (!goog.isNull(this.featuresCollection_)) { + features = this.featuresCollection_.getArray(); + } else if (!goog.isNull(this.featuresRtree_)) { + features = this.featuresRtree_.getAll(); + if (!goog.object.isEmpty(this.nullGeometryFeatures_)) { + goog.array.extend( + features, goog.object.getValues(this.nullGeometryFeatures_)); + } } + goog.asserts.assert(goog.isDef(features), + 'Neither featuresRtree_ nor featuresCollection_ are available'); return features; }; @@ -482,17 +596,25 @@ ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { * Get all features in the provided extent. Note that this returns all features * whose bounding boxes intersect the given extent (so it may include features * whose geometries do not intersect the extent). + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. * @param {ol.Extent} extent Extent. * @return {Array.} Features. * @api */ ol.source.Vector.prototype.getFeaturesInExtent = function(extent) { + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getFeaturesInExtent does not work when useSpatialIndex is set to false'); return this.featuresRtree_.getInExtent(extent); }; /** * Get the closest feature to the provided coordinate. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. * @param {ol.Coordinate} coordinate Coordinate. * @return {ol.Feature} Closest feature. * @api stable @@ -512,6 +634,9 @@ ol.source.Vector.prototype.getClosestFeatureToCoordinate = var closestPoint = [NaN, NaN]; var minSquaredDistance = Infinity; var extent = [-Infinity, -Infinity, Infinity, Infinity]; + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' + + 'to false'); this.featuresRtree_.forEachInExtent(extent, /** * @param {ol.Feature} feature Feature. @@ -542,10 +667,15 @@ ol.source.Vector.prototype.getClosestFeatureToCoordinate = /** * Get the extent of the features currently in the source. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. * @return {ol.Extent} Extent. * @api stable */ ol.source.Vector.prototype.getExtent = function() { + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getExtent does not work when useSpatialIndex is set to false'); return this.featuresRtree_.getExtent(); }; @@ -575,16 +705,22 @@ ol.source.Vector.prototype.handleFeatureChange_ = function(event) { var geometry = feature.getGeometry(); if (!goog.isDefAndNotNull(geometry)) { if (!(featureKey in this.nullGeometryFeatures_)) { - this.featuresRtree_.remove(feature); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.remove(feature); + } this.nullGeometryFeatures_[featureKey] = feature; } } else { var extent = geometry.getExtent(); if (featureKey in this.nullGeometryFeatures_) { delete this.nullGeometryFeatures_[featureKey]; - this.featuresRtree_.insert(extent, feature); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.insert(extent, feature); + } } else { - this.featuresRtree_.update(extent, feature); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.update(extent, feature); + } } } var id = feature.getId(); @@ -668,7 +804,9 @@ ol.source.Vector.prototype.removeFeature = function(feature) { if (featureKey in this.nullGeometryFeatures_) { delete this.nullGeometryFeatures_[featureKey]; } else { - this.featuresRtree_.remove(feature); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.remove(feature); + } } this.removeFeatureInternal(feature); this.changed(); diff --git a/test/spec/ol/featureoverlay.test.js b/test/spec/ol/featureoverlay.test.js deleted file mode 100644 index 78fc78fbc3..0000000000 --- a/test/spec/ol/featureoverlay.test.js +++ /dev/null @@ -1,42 +0,0 @@ -goog.provide('ol.test.FeatureOverlay'); - -describe('ol.FeatureOverlay', function() { - - describe('constructor', function() { - - it('creates an new feature overlay', function() { - var featureOverlay = new ol.FeatureOverlay(); - expect(featureOverlay).to.be.a(ol.FeatureOverlay); - }); - - it('takes features', function() { - var featureOverlay = new ol.FeatureOverlay({ - features: [new ol.Feature(new ol.geom.Point([0, 0]))] - }); - expect(featureOverlay.getFeatures().getLength()).to.be(1); - }); - - it('takes a style', function() { - var style = [new ol.style.Style()]; - var featureOverlay = new ol.FeatureOverlay({ - style: [new ol.style.Style()] - }); - expect(featureOverlay.getStyle()).to.eql(style); - expect(featureOverlay.getStyleFunction()()).to.eql(style); - }); - - it('takes a map', function() { - var map = new ol.Map({}); - var featureOverlay = new ol.FeatureOverlay({ - map: map - }); - expect(featureOverlay.getMap()).to.eql(map); - }); - }); -}); - -goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); -goog.require('ol.Map'); -goog.require('ol.geom.Point'); -goog.require('ol.style.Style'); diff --git a/test/spec/ol/interaction/drawinteraction.test.js b/test/spec/ol/interaction/drawinteraction.test.js index 91c7df892b..140104c6a8 100644 --- a/test/spec/ol/interaction/drawinteraction.test.js +++ b/test/spec/ol/interaction/drawinteraction.test.js @@ -619,17 +619,19 @@ describe('ol.interaction.Draw', function() { describe('#setActive(false)', function() { it('unsets the map from the feature overlay', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); interaction.setActive(false); - expect(interaction.overlay_.map_).to.be(null); + expect(spy.getCall(0).args[0]).to.be(null); }); it('aborts the drawing', function() { interaction.setActive(false); expect(interaction.sketchFeature_).to.be(null); }); it('fires change:active', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); var listenerSpy = sinon.spy(function() { // test that the interaction's change:active listener is called first - expect(interaction.overlay_.map_).to.be(null); + expect(spy.getCall(0).args[0]).to.be(null); }); interaction.on('change:active', listenerSpy); interaction.setActive(false); @@ -642,13 +644,15 @@ describe('ol.interaction.Draw', function() { interaction.setActive(false); }); it('sets the map into the feature overlay', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); interaction.setActive(true); - expect(interaction.overlay_.map_).to.be(map); + expect(spy.getCall(0).args[0]).to.be(map); }); it('fires change:active', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); var listenerSpy = sinon.spy(function() { // test that the interaction's change:active listener is called first - expect(interaction.overlay_.map_).not.to.be(null); + expect(spy.getCall(0).args[0]).to.be(map); }); interaction.on('change:active', listenerSpy); interaction.setActive(true); @@ -682,8 +686,9 @@ describe('ol.interaction.Draw', function() { }); describe('#setMap(null) when interaction is active', function() { it('unsets the map from the feature overlay', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); interaction.setMap(null); - expect(interaction.overlay_.map_).to.be(null); + expect(spy.getCall(0).args[0]).to.be(null); }); it('aborts the drawing', function() { interaction.setMap(null); @@ -695,15 +700,17 @@ describe('ol.interaction.Draw', function() { describe('#setMap(map)', function() { describe('#setMap(map) when interaction is active', function() { it('sets the map into the feature overlay', function() { + var spy = sinon.spy(interaction.overlay_, 'setMap'); interaction.setMap(map); - expect(interaction.overlay_.map_).to.be(map); + expect(spy.getCall(0).args[0]).to.be(map); }); }); describe('#setMap(map) when interaction is not active', function() { it('does not set the map into the feature overlay', function() { interaction.setActive(false); + var spy = sinon.spy(interaction.overlay_, 'setMap'); interaction.setMap(map); - expect(interaction.overlay_.map_).to.be(null); + expect(spy.getCall(0).args[0]).to.be(null); }); }); diff --git a/test/spec/ol/interaction/selectinteraction.test.js b/test/spec/ol/interaction/selectinteraction.test.js index 101c87a783..50a05ac798 100644 --- a/test/spec/ol/interaction/selectinteraction.test.js +++ b/test/spec/ol/interaction/selectinteraction.test.js @@ -219,15 +219,8 @@ describe('ol.interaction.Select', function() { beforeEach(function() { interaction.setActive(false); }); - it('sets the map into the feature overlay', function() { - interaction.setActive(true); - expect(interaction.featureOverlay_.map_).to.be(map); - }); it('fires change:active', function() { - var listenerSpy = sinon.spy(function() { - // test that the interaction's change:active listener is called first - expect(interaction.featureOverlay_.map_).not.to.be(null); - }); + var listenerSpy = sinon.spy(); interaction.on('change:active', listenerSpy); interaction.setActive(true); expect(listenerSpy.callCount).to.be(1); @@ -253,8 +246,9 @@ describe('ol.interaction.Select', function() { }); describe('#setMap(null) when interaction is active', function() { it('unsets the map from the feature overlay', function() { + var spy = sinon.spy(interaction.featureOverlay_, 'setMap'); interaction.setMap(null); - expect(interaction.featureOverlay_.map_).to.be(null); + expect(spy.getCall(0).args[0]).to.be(null); }); }); }); @@ -262,8 +256,9 @@ describe('ol.interaction.Select', function() { describe('#setMap(map)', function() { describe('#setMap(map) when interaction is active', function() { it('sets the map into the feature overlay', function() { + var spy = sinon.spy(interaction.featureOverlay_, 'setMap'); interaction.setMap(map); - expect(interaction.featureOverlay_.map_).to.be(map); + expect(spy.getCall(0).args[0]).to.be(map); }); }); }); diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js index 3ab583d9d6..c9e63884cb 100644 --- a/test/spec/ol/layer/layer.test.js +++ b/test/spec/ol/layer/layer.test.js @@ -573,11 +573,41 @@ describe('ol.layer.Layer', function() { }); + describe('As overlay', function() { + + it('overlays the layer on the map', function() { + var map = new ol.Map({}); + var layer = new ol.layer.Layer({ + map: map + }); + var frameState = { + layerStatesArray: [], + layerStates: {} + }; + map.dispatchEvent(new ol.render.Event('precompose', map, null, + frameState, null, null)); + expect(frameState.layerStatesArray.length).to.be(1); + var layerState = frameState.layerStatesArray[0]; + expect(layerState.layer).to.equal(layer); + expect(frameState.layerStates[goog.getUid(layer)]).to.equal(layerState); + frameState.layerStatesArray = []; + frameState.layerStates = {}; + + layer.setMap(null); + map.dispatchEvent(new ol.render.Event('precompose', map, null, + frameState, null, null)); + expect(frameState.layerStatesArray.length).to.be(0); + }); + + }); + }); goog.require('goog.dispose'); +goog.require('ol.Map'); goog.require('ol.ObjectEventType'); goog.require('ol.layer.Layer'); goog.require('ol.proj'); +goog.require('ol.render.Event'); goog.require('ol.source.Source'); goog.require('ol.source.State'); diff --git a/test/spec/ol/renderer/canvas/canvasmaprenderer.test.js b/test/spec/ol/renderer/canvas/canvasmaprenderer.test.js index f5209f1ce6..b46ad7579d 100644 --- a/test/spec/ol/renderer/canvas/canvasmaprenderer.test.js +++ b/test/spec/ol/renderer/canvas/canvasmaprenderer.test.js @@ -31,52 +31,6 @@ describe('ol.renderer.canvas.Map', function() { renderer.layerRenderers_[goog.getUid(layer)] = layerRenderer; }); - it('uses correct extent and offset on wrapped worlds', function() { - var spy = sinon.spy(renderer, 'getTransform'); - var proj = new ol.proj.Projection({ - code: 'foo', - extent: [-180, -90, 180, 90], - global: true - }); - var frameState = { - coordinateToPixelMatrix: map.coordinateToPixelMatrix_, - pixelToCoordinateMatrix: map.pixelToCoordinateMatrix_, - pixelRatio: 1, - size: [100, 100], - skippedFeatureUids: {}, - extent: proj.getExtent(), - viewState: { - center: [0, 0], - projection: proj, - resolution: 1, - rotation: 0 - }, - layerStates: {}, - layerStatesArray: [{ - layer: layer, - sourceState: 'ready', - visible: true, - minResolution: 1, - maxResolution: 2 - }], - postRenderFunctions: [] - }; - frameState.focus = [0, 0]; - // focus is on real world - renderer.renderFrame(frameState); - expect(spy.getCall(0).args[1]).to.be(0); - expect(renderer.replayGroup.maxExtent_).to.eql([-180, -90, 180, 90]); - frameState.focus = [-200, 0]; - // focus is one world left of the real world - renderer.renderFrame(frameState); - expect(spy.getCall(1).args[1]).to.be(360); - expect(renderer.replayGroup.maxExtent_).to.eql([180, -90, 540, 90]); - frameState.focus = [200, 0]; - // focus is one world right of the real world - renderer.renderFrame(frameState); - expect(spy.getCall(2).args[1]).to.be(-360); - expect(renderer.replayGroup.maxExtent_).to.eql([-540, -90, -180, 90]); - }); }); }); @@ -84,7 +38,6 @@ describe('ol.renderer.canvas.Map', function() { goog.require('ol.layer.Vector'); goog.require('ol.Map'); -goog.require('ol.proj.Projection'); goog.require('ol.renderer.canvas.Layer'); goog.require('ol.renderer.canvas.Map'); goog.require('ol.source.Vector'); diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index df782e7a67..8b7c6fb6e0 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -427,10 +427,77 @@ describe('ol.source.Vector', function() { }); }); + describe('with useSpatialIndex set to false', function() { + var source; + beforeEach(function() { + source = new ol.source.Vector({useSpatialIndex: false}); + }); + + it('returns a features collection', function() { + expect(source.getFeaturesCollection()).to.be.a(ol.Collection); + }); + + it('#forEachFeatureInExtent loops through all features', function() { + source.addFeatures([new ol.Feature(), new ol.Feature()]); + var spy = sinon.spy(); + source.forEachFeatureInExtent([0, 0, 0, 0], spy); + expect(spy.callCount).to.be(2); + }); + + }); + + describe('with a collection of features', function() { + var collection, source; + beforeEach(function() { + collection = new ol.Collection(); + source = new ol.source.Vector({ + features: collection + }); + }); + + it('#getFeaturesCollection returns the configured collection', function() { + expect(source.getFeaturesCollection()).to.equal(collection); + }); + + it('adding/removing features keeps the collection in sync', function() { + var feature = new ol.Feature(); + source.addFeature(feature); + expect(collection.getLength()).to.be(1); + source.removeFeature(feature); + expect(collection.getLength()).to.be(0); + }); + + it('#clear() features keeps the collection in sync', function() { + var feature = new ol.Feature(); + source.addFeatures([feature]); + expect(collection.getLength()).to.be(1); + source.clear(); + expect(collection.getLength()).to.be(0); + source.addFeatures([feature]); + expect(collection.getLength()).to.be(1); + source.clear(true); + expect(collection.getLength()).to.be(0); + }); + + it('keeps the source\'s features in sync with the collection', function() { + var feature = new ol.Feature(); + collection.push(feature); + expect(source.getFeatures().length).to.be(1); + collection.remove(feature); + expect(source.getFeatures().length).to.be(0); + collection.extend([feature]); + expect(source.getFeatures().length).to.be(1); + collection.clear(); + expect(source.getFeatures().length).to.be(0); + }); + + }); + }); goog.require('goog.events'); +goog.require('ol.Collection'); goog.require('ol.Feature'); goog.require('ol.geom.Point'); goog.require('ol.proj');