diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index b8c7700e6c..ed43c96a2f 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -638,6 +638,20 @@ * @property {ol.source.State|undefined} state Source state. */ +/** + * @typedef {Object} olx.source.ImageVectorOptions + * @property {Array.|undefined} attributions Attributions. + * @property {ol.Extent|undefined} extent Extent. + * @property {string|undefined} logo Logo. + * @property {ol.proj.ProjectionLike} projection Projection. + * @property {number|undefined} ratio Ratio. 1 means canvases are the size + * of the map viewport, 2 means twice the size of the map viewport, and so + * on. + * @property {Array.|undefined} resolutions Resolutions. If specified, + * new canvases will be created for these resolutions only. + * @property {ol.source.Vector} source Vector source. + */ + /** * @typedef {Object} olx.source.ImageWMSOptions * @property {Array.|undefined} attributions Attributions. diff --git a/src/ol/source/imagevectorsource.exports b/src/ol/source/imagevectorsource.exports new file mode 100644 index 0000000000..17ff190f77 --- /dev/null +++ b/src/ol/source/imagevectorsource.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.ImageVector diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js new file mode 100644 index 0000000000..34b70ec949 --- /dev/null +++ b/src/ol/source/imagevectorsource.js @@ -0,0 +1,213 @@ +goog.provide('ol.source.ImageVector'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.vec.Mat4'); +goog.require('ol.extent'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.source.Vector'); +goog.require('ol.style.ImageState'); +goog.require('ol.vec.Mat4'); + + + +/** + * @constructor + * @extends {ol.source.ImageCanvas} + * @param {olx.source.ImageVectorOptions} options Options. + */ +ol.source.ImageVector = function(options) { + + /** + * @private + * @type {ol.source.Vector} + */ + this.source_ = options.source; + + /** + * @private + * @type {ol.feature.StyleFunction} + */ + this.styleFunction_ = options.styleFunction; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumber(); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvasElement_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = /** @type {CanvasRenderingContext2D} */ + (this.canvasElement_.getContext('2d')); + + /** + * @private + * @type {ol.Size} + */ + this.canvasSize_ = [0, 0]; + + goog.base(this, { + attributions: options.attributions, + canvasFunction: goog.bind(this.canvasFunctionInternal_, this), + extent: options.extent, + logo: options.logo, + projection: options.projection, + ratio: options.ratio, + resolutions: options.resolutions, + state: this.source_.getState() + }); + + goog.events.listen(this.source_, goog.events.EventType.CHANGE, + this.handleSourceChange_, undefined, this); + +}; +goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas); + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection; + * @return {HTMLCanvasElement} Canvas element. + * @private + */ +ol.source.ImageVector.prototype.canvasFunctionInternal_ = + function(extent, resolution, pixelRatio, size, projection) { + + var tolerance = resolution / (2 * pixelRatio); + var replayGroup = new ol.render.canvas.ReplayGroup( + pixelRatio, tolerance); + + var loading = false; + this.source_.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + loading = loading || + this.renderFeature_(feature, resolution, pixelRatio, replayGroup); + }, this); + replayGroup.finish(); + + if (loading) { + return null; + } + + if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) { + this.canvasElement_.width = size[0]; + this.canvasElement_.height = size[1]; + this.canvasSize_[0] = size[0]; + this.canvasSize_[1] = size[1]; + } else { + this.canvasContext_.clearRect(0, 0, size[0], size[1]); + } + + var transform = this.getTransform_(ol.extent.getCenter(extent), + resolution, pixelRatio, size); + replayGroup.replay(this.canvasContext_, extent, transform, + goog.functions.TRUE); + + return this.canvasElement_; +}; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @return {!goog.vec.Mat4.Number} Transform. + * @private + */ +ol.source.ImageVector.prototype.getTransform_ = + function(center, resolution, pixelRatio, size) { + return ol.vec.Mat4.makeTransform2D(this.transform_, + size[0] / 2, size[1] / 2, + pixelRatio / resolution, -pixelRatio / resolution, + 0, + -center[0], -center[1]); +}; + + +/** + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private + */ +ol.source.ImageVector.prototype.handleImageStyleChange_ = + function(event) { + var imageStyle = /** @type {ol.style.Image} */ (event.target); + if (imageStyle.getImageState() == ol.style.ImageState.LOADED) { + this.dispatchChangeEvent(); + } +}; + + +/** + * @private + */ +ol.source.ImageVector.prototype.handleSourceChange_ = function() { + // setState will trigger a CHANGE event, so we always rely + // change events by calling setState. + this.setState(this.source_.getState()); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + * @private + */ +ol.source.ImageVector.prototype.renderFeature_ = + function(feature, resolution, pixelRatio, replayGroup) { + var loading = false; + var styles = this.styleFunction_(feature, resolution); + if (!goog.isDefAndNotNull(styles)) { + return false; + } + // simplify to a tolerance of half a device pixel + var squaredTolerance = + resolution * resolution / (4 * pixelRatio * pixelRatio); + var i, ii, style, imageStyle, imageState; + for (i = 0, ii = styles.length; i < ii; ++i) { + style = styles[i]; + imageStyle = style.getImage(); + if (!goog.isNull(imageStyle)) { + if (imageStyle.getImageState() == ol.style.ImageState.IDLE) { + goog.events.listenOnce(imageStyle, goog.events.EventType.CHANGE, + this.handleImageStyleChange_, false, this); + imageStyle.load(); + } else if (imageStyle.getImageState() == ol.style.ImageState.LOADED) { + ol.renderer.vector.renderFeature( + replayGroup, feature, style, squaredTolerance, feature); + } + goog.asserts.assert( + imageStyle.getImageState() != ol.style.ImageState.IDLE); + loading = imageStyle.getImageState() == ol.style.ImageState.LOADING; + } else { + ol.renderer.vector.renderFeature( + replayGroup, feature, style, squaredTolerance, feature); + } + } + return loading; +};