diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 1963f7c7d5..32b6831867 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -633,6 +633,10 @@ * @typedef {Object} olx.layer.VectorOptions * @property {number|undefined} brightness Brightness. * @property {number|undefined} contrast Contrast. + * @property {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder Render order. + * Function to be used when sorting features before rendering. By default + * features are drawn in the order that they are created. Use `null` to + * avoid the sort, but get an undefined draw order. * @property {number|undefined} hue Hue. * @property {number|undefined} minResolution The minimum resolution * (inclusive) at which this layer will be visible. diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 4e55b7272f..5b457dff48 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -6,8 +6,17 @@ goog.require('ol.feature'); goog.require('ol.layer.Layer'); +/** + * @enum {string} + */ +ol.layer.VectorProperty = { + RENDER_ORDER: 'renderOrder' +}; + + /** + * * @constructor * @extends {ol.layer.Layer} * @fires {@link ol.render.Event} ol.render.Event @@ -46,6 +55,16 @@ ol.layer.Vector = function(opt_options) { goog.inherits(ol.layer.Vector, ol.layer.Layer); +/** + * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render + * order. + */ +ol.layer.Vector.prototype.getRenderOrder = function() { + return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ ( + this.get(ol.layer.VectorProperty.RENDER_ORDER)); +}; + + /** * Get the style for features. This returns whatever was passed to the `style` * option at construction or to the `setStyle` method. @@ -67,6 +86,15 @@ ol.layer.Vector.prototype.getStyleFunction = function() { }; +/** + * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder + * Render order. + */ +ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) { + this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder); +}; + + /** * 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 diff --git a/src/ol/object.js b/src/ol/object.js index a786c8205b..8894e75241 100644 --- a/src/ol/object.js +++ b/src/ol/object.js @@ -48,6 +48,12 @@ ol.ObjectEventType = { ol.ObjectEvent = function(type, key) { goog.base(this, type); + // Call goog.getUid to ensure that the order of objects' ids is the same as + // the order in which they were created. This also helps to ensure that + // object properties are always added in the same order, which helps many + // JavaScript engines generate faster code. + goog.getUid(this); + /** * The name of the property whose value is changing. * @type {string} diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 37eeaffe8d..3460e20632 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -1,5 +1,6 @@ goog.provide('ol.renderer.canvas.VectorLayer'); +goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); @@ -50,6 +51,12 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, vectorLayer) { */ this.renderedExtent_ = ol.extent.createEmpty(); + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + /** * @private * @type {ol.render.canvas.ReplayGroup} @@ -174,10 +181,23 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = var frameStateResolution = frameState.view2DState.resolution; var pixelRatio = frameState.pixelRatio; var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + if (!goog.isDef(vectorLayerRenderOrder)) { + vectorLayerRenderOrder = + /** + * @param {ol.Feature} feature1 Feature 1. + * @param {ol.Feature} feature2 Feature 2. + * @return {number} Order. + */ + function(feature1, feature2) { + return goog.getUid(feature1) - goog.getUid(feature2); + }; + } if (!this.dirty_ && this.renderedResolution_ == frameStateResolution && this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && ol.extent.containsExtent(this.renderedExtent_, frameStateExtent)) { return; } @@ -203,20 +223,29 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = var tolerance = frameStateResolution / (2 * pixelRatio); var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent, frameStateResolution); - vectorSource.forEachFeatureInExtent(extent, + var renderFeature = /** * @param {ol.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorLayer} */ function(feature) { - var dirty = - this.renderFeature(feature, frameStateResolution, pixelRatio, - styleFunction, replayGroup); - this.dirty_ = this.dirty_ || dirty; - }, this); + goog.asserts.assert(goog.isDef(styleFunction)); + var dirty = this.renderFeature( + feature, frameStateResolution, pixelRatio, styleFunction, replayGroup); + this.dirty_ = this.dirty_ || dirty; + }; + if (goog.isDef(vectorLayerRenderOrder)) { + var features = vectorSource.getFeaturesInExtent(extent); + goog.array.sort(features, vectorLayerRenderOrder); + goog.array.forEach(features, renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } replayGroup.finish(); this.renderedResolution_ = frameStateResolution; this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; this.replayGroup_ = replayGroup; };