diff --git a/src/ol/renderer/dom/domlayerrenderer.js b/src/ol/renderer/dom/domlayerrenderer.js index e42c6f3fc1..8e582a6b4b 100644 --- a/src/ol/renderer/dom/domlayerrenderer.js +++ b/src/ol/renderer/dom/domlayerrenderer.js @@ -27,6 +27,13 @@ ol.renderer.dom.Layer = function(mapRenderer, layer, target) { goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer); +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + */ +ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction; + + /** * @return {!Element} Target. */ diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js index 0d2e72b47b..c512454543 100644 --- a/src/ol/renderer/dom/dommaprenderer.js +++ b/src/ol/renderer/dom/dommaprenderer.js @@ -10,10 +10,12 @@ goog.require('ol.RendererType'); goog.require('ol.css'); goog.require('ol.layer.Image'); goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); 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.source.State'); @@ -68,6 +70,9 @@ ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { layerRenderer = new ol.renderer.dom.ImageLayer(this, layer); } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { layerRenderer = new ol.renderer.dom.TileLayer(this, layer); + } else if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && + ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + layerRenderer = new ol.renderer.dom.VectorLayer(this, layer); } else { goog.asserts.fail(); return null; diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js new file mode 100644 index 0000000000..aec1bcf621 --- /dev/null +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -0,0 +1,325 @@ +goog.provide('ol.renderer.dom.VectorLayer'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.vec.Mat4'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +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.dom.Layer'); +goog.require('ol.renderer.vector'); +goog.require('ol.vec.Mat4'); + + + +/** + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.Vector} vectorLayer Vector layer. + */ +ol.renderer.dom.VectorLayer = function(mapRenderer, vectorLayer) { + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + + var target = this.context_.canvas; + // Bootstrap sets the style max-width: 100% for all images, which breaks + // prevents the image from being displayed in FireFox. Workaround by + // overriding the max-width style. + target.style.maxWidth = 'none'; + target.style.position = 'absolute'; + + goog.base(this, mapRenderer, vectorLayer, target); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumber(); + +}; +goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.dom.VectorLayer.prototype.composeFrame = + function(frameState, layerState) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); + + var viewState = frameState.viewState; + var viewRotation = viewState.rotation; + var pixelRatio = frameState.pixelRatio; + + var transform = ol.vec.Mat4.makeTransform2D(this.transform_, + pixelRatio * frameState.size[0] / 2, + pixelRatio * frameState.size[1] / 2, + pixelRatio / viewState.resolution, + -pixelRatio / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + + var context = this.context_; + + // Clear the canvas and set the correct size + context.canvas.width = frameState.size[0]; + context.canvas.height = frameState.size[1]; + + this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform); + + var replayGroup = this.replayGroup_; + + if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { + + context.globalAlpha = layerState.opacity; + replayGroup.replay( + context, frameState.extent, pixelRatio, transform, + viewRotation, frameState.skippedFeatureUids); + + this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform); + } + + this.dispatchEvent_(ol.render.EventType.POSTCOMPOSE, frameState, transform); +}; + + +/** + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number} transform Transform. + * @private + */ +ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = + function(type, frameState, transform) { + var context = this.context_; + var layer = this.getLayer(); + if (layer.hasListener(type)) { + 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); + layer.dispatchEvent(event); + render.flush(); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.replayGroup_)) { + return undefined; + } else { + var extent = frameState.extent; + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var layer = this.getLayer(); + /** @type {Object.} */ + var features = {}; + return this.replayGroup_.forEachGeometryAtPixel(extent, resolution, + rotation, coordinate, frameState.skippedFeatureUids, + /** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {Object} data Data. + * @return {?} Callback result. + */ + function(geometry, data) { + var feature = /** @type {ol.Feature} */ (data); + goog.asserts.assert(goog.isDef(feature)); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; + + +/** + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private + */ +ol.renderer.dom.VectorLayer.prototype.handleImageChange_ = + function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.dom.VectorLayer.prototype.prepareFrame = + function(frameState, layerState) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] || + frameState.viewHints[ol.ViewHint.INTERACTING])) { + return true; + } + + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + if (!goog.isDef(vectorLayerRenderOrder)) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, frameStateExtent)) { + return true; + } + + var extent = this.renderedExtent_; + var xBuffer = ol.extent.getWidth(frameStateExtent) / 4; + var yBuffer = ol.extent.getHeight(frameStateExtent) / 4; + extent[0] = frameStateExtent[0] - xBuffer; + extent[1] = frameStateExtent[1] - yBuffer; + extent[2] = frameStateExtent[2] + xBuffer; + extent[3] = frameStateExtent[3] + yBuffer; + + // FIXME dispose of old replayGroup in post render + goog.dispose(this.replayGroup_); + this.replayGroup_ = null; + + this.dirty_ = false; + + var replayGroup = + new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution); + vectorSource.loadFeatures(extent, resolution, projection); + var renderFeature = + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.dom.VectorLayer} + */ + function(feature) { + var styles; + if (goog.isDef(feature.getStyleFunction())) { + styles = feature.getStyleFunction().call(feature, resolution); + } else if (goog.isDef(vectorLayer.getStyleFunction())) { + styles = vectorLayer.getStyleFunction()(feature, resolution); + } + if (goog.isDefAndNotNull(styles)) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (!goog.isNull(vectorLayerRenderOrder)) { + /** @type {Array.} */ + var features = []; + vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + goog.array.sort(features, vectorLayerRenderOrder); + goog.array.forEach(features, renderFeature, this); + } else { + vectorSource.forEachFeatureInExtentAtResolution( + extent, resolution, renderFeature, this); + } + replayGroup.finish(); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.} styles Array of styles + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.dom.VectorLayer.prototype.renderFeature = + function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!goog.isDefAndNotNull(styles)) { + return false; + } + var i, ii, loading = false; + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + feature, this.handleImageChange_, this) || loading; + } + return loading; +};