diff --git a/src/ol/layer/vectorlayer.exports b/src/ol/layer/vectorlayer.exports index 669da99ad1..fa79c6b730 100644 --- a/src/ol/layer/vectorlayer.exports +++ b/src/ol/layer/vectorlayer.exports @@ -1,4 +1,3 @@ @exportClass ol.layer.Vector ol.layer.VectorLayerOptions @exportProperty ol.layer.Vector.prototype.addFeatures -@exportProperty ol.layer.Vector.prototype.parseFeatures diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 264c4c9fa4..fdef16b75a 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -279,34 +279,26 @@ ol.layer.Vector.prototype.getVectorSource = function() { /** - * @param {ol.expr.Expression=} opt_expr Expression for filtering. - * @return {Array.} Array of features. - */ -ol.layer.Vector.prototype.getFeatures = function(opt_expr) { - return goog.object.getValues( - this.featureCache_.getFeaturesObject(opt_expr)); -}; - - -/** - * @param {ol.expr.Expression=} opt_expr Expression for filtering. - * @return {Object.} Features. - */ -ol.layer.Vector.prototype.getFeaturesObject = function(opt_expr) { - return this.featureCache_.getFeaturesObject(opt_expr); -}; - - -/** - * Get all features whose bounding box intersects the provided extent. + * Get all features whose bounding box intersects the provided extent. This + * method is intended for being called by the renderer. When null is returned, + * the renderer should not waste time rendering, and `opt_callback` is + * usually a function that requests a renderFrame, which will be called as soon + * as the data for `extent` is available. * * @param {ol.Extent} extent Bounding extent. + * @param {ol.Projection} projection Target projection. * @param {ol.geom.GeometryType=} opt_type Optional geometry type. - * @return {Object.} Features. + * @param {Function=} opt_callback Callback to call when data is parsed. + * @return {Object.} Features or null if source is loading + * data for `extent`. */ ol.layer.Vector.prototype.getFeaturesObjectForExtent = function(extent, - opt_type) { - return this.featureCache_.getFeaturesObjectForExtent(extent, opt_type); + projection, opt_type, opt_callback) { + var source = this.getSource(); + return source.prepareFeatures(this, extent, projection, opt_callback) == + ol.source.VectorLoadState.LOADING ? + null : + this.featureCache_.getFeaturesObjectForExtent(extent, opt_type); }; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 343d783f72..a211bd4786 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -228,9 +228,12 @@ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = * @param {function(Array., ol.layer.Layer)} success Callback for * successful queries. The passed arguments are the resulting features * and the layer. + * @param {function(Object)=} opt_error Callback for + * unsuccessful queries. */ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = - function(pixel, success) { + function(pixel, success, opt_error) { + // TODO What do we want to pass to the error callback? var map = this.getMap(); var result = []; @@ -247,7 +250,15 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = var locationMin = [location[0] - halfMaxWidth, location[1] - halfMaxHeight]; var locationMax = [location[0] + halfMaxWidth, location[1] + halfMaxHeight]; var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]); - var candidates = layer.getFeaturesObjectForExtent(locationBbox); + var candidates = layer.getFeaturesObjectForExtent(locationBbox, + map.getView().getView2D().getProjection()); + if (goog.isNull(candidates)) { + // data is not loaded + if (goog.isDef(opt_error)) { + goog.global.setTimeout(function() { opt_error({}); }, 0); + } + return; + } var candidate, geom, type, symbolBounds, symbolSize, halfWidth, halfHeight, coordinates, j; @@ -446,6 +457,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = dirty = false, i, type, tileExtent, groups, group, j, numGroups, featuresObject, tileHasFeatures; + fetchTileData: for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { tileCoord = new ol.TileCoord(0, x, y); @@ -464,7 +476,12 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = if (!goog.isDef(featuresToRender[type])) { featuresToRender[type] = {}; } - featuresObject = layer.getFeaturesObjectForExtent(tileExtent, type); + featuresObject = layer.getFeaturesObjectForExtent(tileExtent, + projection, type, this.requestMapRenderFrame_); + if (goog.isNull(featuresObject)) { + deferred = true; + break fetchTileData; + } tileHasFeatures = tileHasFeatures || !goog.object.isEmpty(featuresObject); goog.object.extend(featuresToRender[type], featuresObject); diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index 1525617d6a..8c6c9f06e1 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -9,11 +9,12 @@ describe('ol.layer.Vector', function() { source: new ol.source.Vector({}) }); layer.addFeatures([new ol.Feature(), new ol.Feature()]); - expect(layer.getFeatures().length).to.eql(2); + expect(goog.object.getCount(layer.featureCache_.getFeaturesObject())) + .to.eql(2); }); }); - describe('#getFeatures()', function() { + describe('ol.layer.FeatureCache#getFeaturesObject()', function() { var layer, features; @@ -55,18 +56,18 @@ describe('ol.layer.Vector', function() { it('can filter by geometry type using its GeometryType index', function() { sinon.spy(geomFilter, 'evaluate'); - var lineStrings = layer.getFeatures(geomFilter); + var lineStrings = layer.featureCache_.getFeaturesObject(geomFilter); expect(geomFilter.evaluate).to.not.be.called(); - expect(lineStrings.length).to.eql(4); - expect(lineStrings).to.contain(features[4]); + expect(goog.object.getCount(lineStrings)).to.eql(4); + expect(goog.object.getValues(lineStrings)).to.contain(features[4]); }); it('can filter by extent using its RTree', function() { sinon.spy(extentFilter, 'evaluate'); - var subset = layer.getFeatures(extentFilter); + var subset = layer.featureCache_.getFeaturesObject(extentFilter); expect(extentFilter.evaluate).to.not.be.called(); - expect(subset.length).to.eql(4); - expect(subset).not.to.contain(features[7]); + expect(goog.object.getCount(subset)).to.eql(4); + expect(goog.object.getValues(subset)).not.to.contain(features[7]); }); it('can filter by extent and geometry type using its index', function() { @@ -76,21 +77,21 @@ describe('ol.layer.Vector', function() { ol.expr.LogicalOp.AND, extentFilter, geomFilter); sinon.spy(filter1, 'evaluate'); sinon.spy(filter2, 'evaluate'); - var subset1 = layer.getFeatures(filter1); - var subset2 = layer.getFeatures(filter2); + var subset1 = layer.featureCache_.getFeaturesObject(filter1); + var subset2 = layer.featureCache_.getFeaturesObject(filter2); expect(filter1.evaluate).to.not.be.called(); expect(filter2.evaluate).to.not.be.called(); - expect(subset1.length).to.eql(0); - expect(subset2.length).to.eql(0); + expect(goog.object.getCount(subset1)).to.eql(0); + expect(goog.object.getCount(subset2)).to.eql(0); }); it('can handle query using the filter\'s evaluate function', function() { var filter = new ol.expr.Logical( ol.expr.LogicalOp.OR, geomFilter, extentFilter); sinon.spy(filter, 'evaluate'); - var subset = layer.getFeatures(filter); + var subset = layer.featureCache_.getFeaturesObject(filter); expect(filter.evaluate).to.be.called(); - expect(subset.length).to.eql(8); + expect(goog.object.getCount(subset)).to.eql(8); }); }); @@ -177,6 +178,7 @@ describe('ol.layer.Vector', function() { }); goog.require('goog.dispose'); +goog.require('goog.object'); goog.require('ol.Feature'); goog.require('ol.expr'); goog.require('ol.expr.Logical');