From f186ed3deb9eebf2a31dddcbf800ef332799aeca Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 3 Jun 2015 12:05:49 +0200 Subject: [PATCH 1/5] Alternatively manage features in an ol.Collection ol.layer.Vector can now manage both an RTree and a Collection of features. The new useSpatialIndex option allows to opt out of RTree management, and the new ol.Collection type of the features option allows to opt in for Collection management. --- externs/olx.js | 31 +++- src/ol/source/vectorsource.js | 172 ++++++++++++++++++++--- test/spec/ol/source/vectorsource.test.js | 59 ++++++++ 3 files changed, 240 insertions(+), 22 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 9700718064..840d4f2434 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4977,12 +4977,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 */ @@ -4998,8 +4999,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; @@ -5052,6 +5054,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/source/vectorsource.js b/src/ol/source/vectorsource.js index 42f945f9d7..60976af89b 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. @@ -305,8 +381,10 @@ ol.source.Vector.prototype.clear = function(opt_fast) { this.undefIdIndex_ = {}; } else { var rmFeatureInternal = this.removeFeatureInternal; - this.featuresRtree_.forEach(rmFeatureInternal, this); - goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this); + 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_), @@ -315,7 +393,9 @@ ol.source.Vector.prototype.clear = function(opt_fast) { 'undefIdIndex is an empty object now'); } - this.featuresRtree_.clear(); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.clear(); + } this.loadedExtentsRtree_.clear(); this.nullGeometryFeatures_ = {}; @@ -338,7 +418,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 +465,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 +478,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 +539,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 +592,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 +630,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 +663,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 +701,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 +800,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/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index df782e7a67..74bad15d13 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -427,10 +427,69 @@ 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('keeps the collection in sync with the source\'s features', function() { + var feature = new ol.Feature(); + source.addFeature(feature); + expect(collection.getLength()).to.be(1); + source.removeFeature(feature); + expect(collection.getLength()).to.be(0); + source.addFeatures([feature]); + expect(collection.getLength()).to.be(1); + source.clear(); + 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'); From 54da473991eea31bdce904d879a23d29b10b762a Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 3 Jun 2015 12:10:39 +0200 Subject: [PATCH 2/5] Allow layers that are not managed by the map When a layer is configured with a map, it will be added on top of other layers, and not be managed in the map's features collection. The layerState will have an 'unmanaged' flag for such layers. For vector layers, this flag is used to not skip any features. --- externs/olx.js | 36 ++++++++++++++++ src/ol/layer/layer.js | 43 +++++++++++++++++++ .../canvas/canvasvectorlayerrenderer.js | 3 +- src/ol/renderer/dom/domvectorlayerrenderer.js | 2 +- .../webgl/webglvectorlayerrenderer.js | 3 +- test/spec/ol/layer/layer.test.js | 23 ++++++++++ 6 files changed, 107 insertions(+), 3 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 840d4f2434..aae7de390a 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -3282,6 +3282,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), @@ -3340,6 +3341,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} @@ -3381,6 +3393,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), @@ -3448,6 +3461,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} @@ -3500,6 +3524,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), @@ -3543,6 +3568,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. diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index 989cf70389..d9e9eca0a8 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,32 @@ 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 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/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 45a83ea653..6cebf93ab7 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -79,7 +79,8 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = 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; diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index 9cf5f32dbd..79c1bfb776 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); } 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/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js index 3ab583d9d6..b5899b034c 100644 --- a/test/spec/ol/layer/layer.test.js +++ b/test/spec/ol/layer/layer.test.js @@ -573,11 +573,34 @@ 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)); + var layerState = frameState.layerStatesArray[0]; + expect(frameState.layerStatesArray.length).to.be(1); + expect(layerState.layer).to.equal(layer); + expect(frameState.layerStates[goog.getUid(layer)]).to.equal(layerState); + }); + + }); + }); 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'); From 53d5d8c1d9a050ff99755703b16239a6e48a4750 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 9 Jun 2015 13:25:51 +0200 Subject: [PATCH 3/5] Get rid of ol.FeatureOverlay This also introduces a wrapX option to the Draw, Modify and Select interaction. --- changelog/upgrade-notes.md | 34 ++ examples/draw-and-modify-features.js | 16 +- examples/geolocation.js | 9 +- examples/icon-sprite-webgl.js | 9 +- examples/igc.js | 6 +- examples/image-vector-layer.js | 9 +- examples/modify-features.js | 4 +- examples/vector-layer.js | 8 +- externs/olx.js | 33 +- src/ol/featureoverlay.js | 326 ------------------ src/ol/interaction/drawinteraction.js | 16 +- src/ol/interaction/modifyinteraction.js | 19 +- src/ol/interaction/selectinteraction.js | 22 +- src/ol/map.js | 12 +- src/ol/render/canvas/canvasimmediate.js | 4 +- src/ol/render/renderevent.js | 10 +- src/ol/renderer/canvas/canvaslayerrenderer.js | 4 +- src/ol/renderer/canvas/canvasmaprenderer.js | 40 +-- .../canvas/canvasvectorlayerrenderer.js | 23 +- src/ol/renderer/dom/dommaprenderer.js | 13 +- src/ol/renderer/dom/domvectorlayerrenderer.js | 4 +- src/ol/renderer/maprenderer.js | 27 -- src/ol/renderer/webgl/webgllayerrenderer.js | 2 +- src/ol/renderer/webgl/webglmaprenderer.js | 78 +---- test/spec/ol/featureoverlay.test.js | 42 --- .../ol/interaction/drawinteraction.test.js | 21 +- .../ol/interaction/selectinteraction.test.js | 15 +- .../renderer/canvas/canvasmaprenderer.test.js | 47 --- 28 files changed, 177 insertions(+), 676 deletions(-) delete mode 100644 src/ol/featureoverlay.js delete mode 100644 test/spec/ol/featureoverlay.test.js 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 aae7de390a..d6a597db02 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -2366,7 +2366,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; @@ -2470,6 +2471,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), @@ -2545,7 +2554,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; @@ -2587,6 +2597,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 @@ -2708,7 +2726,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; @@ -2803,6 +2822,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 {{ 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 1a0cfe6a03..da5e210aad 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(); } 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(); + overlaySource.addFeatures(sketchFeatures); }; diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index 66f3b5ed28..b0276b2195 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 161eff39ae..2f4e35c71e 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'); @@ -73,7 +74,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. @@ -160,14 +161,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, @@ -183,7 +188,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(); }; @@ -204,7 +209,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; @@ -280,7 +285,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/map.js b/src/ol/map.js index 013c4901e7..13806ef880 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -577,10 +577,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 @@ -618,10 +616,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 6cebf93ab7..e9a1c4b5b2 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -77,7 +77,6 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { var extent = frameState.extent; - var focus = frameState.focus; var pixelRatio = frameState.pixelRatio; var skippedFeatureUids = layerState.unmanaged ? {} : frameState.skippedFeatureUids; @@ -109,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; @@ -132,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; @@ -143,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 79c1bfb776..a59873f645 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -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/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/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'); From 57e1dda5f1eda162a1f0299964b66212890358c0 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 9 Jun 2015 13:26:12 +0200 Subject: [PATCH 4/5] Make sure that #clear() keeps the collection in sync --- src/ol/interaction/drawinteraction.js | 4 ++-- src/ol/source/vectorsource.js | 28 ++++++++++++++---------- test/spec/ol/source/vectorsource.test.js | 10 ++++++++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index da5e210aad..87da838d99 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -678,7 +678,7 @@ ol.interaction.Draw.prototype.abortDrawing_ = function() { this.sketchFeature_ = null; this.sketchPoint_ = null; this.sketchLine_ = null; - this.overlay_.getSource().clear(); + this.overlay_.getSource().clear(true); } return sketchFeature; }; @@ -706,7 +706,7 @@ ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { sketchFeatures.push(this.sketchPoint_); } var overlaySource = this.overlay_.getSource(); - overlaySource.clear(); + overlaySource.clear(true); overlaySource.addFeatures(sketchFeatures); }; diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 60976af89b..96a3c5f4b1 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -372,26 +372,30 @@ ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) { */ 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; 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'); } + 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_.clear(); diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index 74bad15d13..8b7c6fb6e0 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -459,16 +459,24 @@ describe('ol.source.Vector', function() { expect(source.getFeaturesCollection()).to.equal(collection); }); - it('keeps the collection in sync with the source\'s features', function() { + 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() { From 9acd65270a00892a9863ae1add6dfc8133f161f8 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 9 Jun 2015 13:39:33 +0200 Subject: [PATCH 5/5] Make clear how to remove an unmanaged layer from a map --- src/ol/layer/layer.js | 5 +++-- test/spec/ol/layer/layer.test.js | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index d9e9eca0a8..13f3a565ed 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -149,8 +149,9 @@ 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 add the layer to a map and have it managed by the - * map, use {@link ol.Map#addLayer} instead. + * 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 */ diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js index b5899b034c..c9e63884cb 100644 --- a/test/spec/ol/layer/layer.test.js +++ b/test/spec/ol/layer/layer.test.js @@ -586,10 +586,17 @@ describe('ol.layer.Layer', function() { }; map.dispatchEvent(new ol.render.Event('precompose', map, null, frameState, null, null)); - var layerState = frameState.layerStatesArray[0]; 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); }); });