diff --git a/examples/wms-custom-proj.js b/examples/wms-custom-proj.js index d389451935..fea7b563dd 100644 --- a/examples/wms-custom-proj.js +++ b/examples/wms-custom-proj.js @@ -10,7 +10,9 @@ goog.require('ol.Projection'); goog.require('ol.ProjectionUnits'); goog.require('ol.RendererHints'); goog.require('ol.View2D'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); +goog.require('ol.source.SingleImageWMS'); goog.require('ol.source.TiledWMS'); @@ -24,6 +26,17 @@ var epsg21781 = new ol.Projection('EPSG:21781', ol.ProjectionUnits.METERS, new ol.Extent(485869.5728, 76443.1884, 837076.5648, 299941.7864)); ol.Projection.addProjection(epsg21781); +// We give the single image source a set of resolutions. This prevents the +// source from requesting images of arbitrary resolutions. +var projectionExtent = epsg21781.getExtent(); +var maxResolution = Math.max( + projectionExtent.maxX - projectionExtent.minX, + projectionExtent.maxY - projectionExtent.minY) / 256; +var resolutions = new Array(10); +for (var i = 0; i < 10; ++i) { + resolutions[i] = maxResolution / Math.pow(2.0, i); +} + var extent = new ol.Extent(420000, 30000, 900000, 350000); var layers = new ol.Collection([ new ol.layer.TileLayer({ @@ -41,8 +54,8 @@ var layers = new ol.Collection([ extent: extent }) }), - new ol.layer.TileLayer({ - source: new ol.source.TiledWMS({ + new ol.layer.ImageLayer({ + source: new ol.source.SingleImageWMS({ url: 'http://wms.geo.admin.ch/', attributions: [new ol.Attribution( '© ' + @@ -50,7 +63,7 @@ var layers = new ol.Collection([ 'National parks / geo.admin.ch')], params: {'LAYERS': 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung'}, projection: epsg21781, - extent: extent + resolutions: resolutions }) }) ]); diff --git a/src/imageurlfunction.js b/src/imageurlfunction.js new file mode 100644 index 0000000000..bb328819eb --- /dev/null +++ b/src/imageurlfunction.js @@ -0,0 +1,40 @@ +goog.provide('ol.ImageUrlFunction'); +goog.provide('ol.ImageUrlFunctionType'); + +goog.require('ol.Extent'); +goog.require('ol.Size'); + + +/** + * @typedef {function(ol.Extent, ol.Size): (string|undefined)} + */ +ol.ImageUrlFunctionType; + + +/** + * @param {string} baseUrl Base URL (may have query data). + * @return {ol.ImageUrlFunctionType} Image URL function. + */ +ol.ImageUrlFunction.createBboxParam = function(baseUrl) { + return function(extent, size) { + // FIXME Projection dependant axis order. + var bboxValue = [ + extent.minX, extent.minY, extent.maxX, extent.maxY + ].join(','); + return goog.uri.utils.appendParams(baseUrl, + 'BBOX', bboxValue, + 'HEIGHT', size.height, + 'WIDTH', size.width); + }; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @return {string|undefined} Image URL. + */ +ol.ImageUrlFunction.nullImageUrlFunction = + function(extent, size) { + return undefined; +}; diff --git a/src/objectliterals.exports b/src/objectliterals.exports index cf5e830f3f..0e24207a11 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -92,10 +92,29 @@ @exportObjectLiteralProperty ol.source.DebugTileSourceOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.source.DebugTileSourceOptions.tileGrid ol.tilegrid.TileGrid|undefined +@exportObjectLiteral ol.source.SingleImageWMSOptions +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.attributions Array.|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.crossOrigin null|string|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.extent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.params Object. +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.projection ol.Projection|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.resolutions Array.|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.url string|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.version string|undefined + @exportObjectLiteral ol.source.StamenOptions @exportObjectLiteralProperty ol.source.StamenOptions.flavor string|undefined @exportObjectLiteralProperty ol.source.StamenOptions.provider string +@exportObjectLiteral ol.source.StaticImageOptions +@exportObjectLiteralProperty ol.source.StaticImageOptions.attributions Array.|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.crossOrigin null|string|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.extent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.imageExtent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.imageSize ol.Size|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.projection ol.Projection|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.url string|undefined + @exportObjectLiteral ol.source.TiledWMSOptions @exportObjectLiteralProperty ol.source.TiledWMSOptions.attributions Array.|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.params Object diff --git a/src/ol/control/attributioncontrol.js b/src/ol/control/attributioncontrol.js index d6f707ea56..731e2b9435 100644 --- a/src/ol/control/attributioncontrol.js +++ b/src/ol/control/attributioncontrol.js @@ -1,4 +1,5 @@ // FIXME handle date line wrap +// FIXME does not handle image sources goog.provide('ol.control.Attribution'); diff --git a/src/ol/extent.js b/src/ol/extent.js index 3d0ab215d6..3606dfc63c 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -46,17 +46,31 @@ ol.Extent.boundingExtent = function(var_args) { /** - * Checks if the given coordinate is contained or on the edge of the extent. + * Checks if the passed coordinate is contained or on the edge + * of the extent. * * @param {ol.Coordinate} coordinate Coordinate. * @return {boolean} Contains. */ -ol.Extent.prototype.contains = function(coordinate) { +ol.Extent.prototype.containsCoordinate = function(coordinate) { return this.minX <= coordinate.x && coordinate.x <= this.maxX && this.minY <= coordinate.y && coordinate.y <= this.maxY; }; +/** + * Checks if the passed extent is contained or on the edge of the + * extent. + * + * @param {ol.Extent} extent Extent. + * @return {boolean} Contains. + */ +ol.Extent.prototype.containsExtent = function(extent) { + return this.minX <= extent.minX && extent.maxX <= this.maxX && + this.minY <= extent.minY && extent.maxY <= this.maxY; +}; + + /** * @return {ol.Coordinate} Bottom left coordinate. */ diff --git a/src/ol/image.js b/src/ol/image.js new file mode 100644 index 0000000000..9f7cbe0c47 --- /dev/null +++ b/src/ol/image.js @@ -0,0 +1,187 @@ +goog.provide('ol.Image'); +goog.provide('ol.ImageState'); + +goog.require('goog.array'); +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.Extent'); + + +/** + * @enum {number} + */ +ol.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 +}; + + + +/** + * @constructor + * @extends {goog.events.EventTarget} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + */ +ol.Image = function(extent, resolution, src, crossOrigin) { + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.} + */ + this.imageListenerKeys_ = null; + + /** + * @protected + * @type {ol.ImageState} + */ + this.state = ol.ImageState.IDLE; +}; +goog.inherits(ol.Image, goog.events.EventTarget); + + +/** + * @protected + */ +ol.Image.prototype.dispatchChangeEvent = function() { + this.dispatchEvent(goog.events.EventType.CHANGE); +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.Image.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + */ +ol.Image.prototype.getImageElement = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * @return {number} Resolution. + */ +ol.Image.prototype.getResolution = function() { + return this.resolution_; +}; + + +/** + * @return {ol.ImageState} State. + */ +ol.Image.prototype.getState = function() { + return this.state; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.Image.prototype.handleImageError_ = function() { + this.state = ol.ImageState.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.Image.prototype.handleImageLoad_ = function() { + this.state = ol.ImageState.LOADED; + this.unlistenImage_(); + this.dispatchChangeEvent(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.Image.prototype.load = function() { + if (this.state == ol.ImageState.IDLE) { + this.state = ol.ImageState.LOADING; + goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.image_.src = this.src_; + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.Image.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; diff --git a/src/ol/layer/imagelayer.exports b/src/ol/layer/imagelayer.exports new file mode 100644 index 0000000000..f00c7ec5e5 --- /dev/null +++ b/src/ol/layer/imagelayer.exports @@ -0,0 +1 @@ +@exportClass ol.layer.ImageLayer ol.layer.LayerOptions diff --git a/src/ol/layer/imagelayer.js b/src/ol/layer/imagelayer.js new file mode 100644 index 0000000000..8b61efce0e --- /dev/null +++ b/src/ol/layer/imagelayer.js @@ -0,0 +1,24 @@ +goog.provide('ol.layer.ImageLayer'); + +goog.require('ol.layer.Layer'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.layer.Layer} + * @param {ol.layer.LayerOptions} layerOptions Layer options. + */ +ol.layer.ImageLayer = function(layerOptions) { + goog.base(this, layerOptions); +}; +goog.inherits(ol.layer.ImageLayer, ol.layer.Layer); + + +/** + * @return {ol.source.ImageSource} Single image source. + */ +ol.layer.ImageLayer.prototype.getImageSource = function() { + return /** @type {ol.source.ImageSource} */ (this.getSource()); +}; diff --git a/src/ol/rectangle.js b/src/ol/rectangle.js index d15f1d7a18..9d7a27af5e 100644 --- a/src/ol/rectangle.js +++ b/src/ol/rectangle.js @@ -41,6 +41,16 @@ ol.Rectangle = function(minX, minY, maxX, maxY) { }; +/** + * @param {ol.Rectangle} rectangle Rectangle. + * @return {boolean} Equals. + */ +ol.Rectangle.prototype.equals = function(rectangle) { + return this.minX == rectangle.minX && this.minY == rectangle.minY && + this.maxX == rectangle.maxX && this.maxY == rectangle.maxY; +}; + + /** * @param {ol.Rectangle} rectangle Rectangle. */ @@ -114,3 +124,16 @@ ol.Rectangle.prototype.normalize = function(coordinate) { ol.Rectangle.prototype.toString = function() { return '(' + [this.minX, this.minY, this.maxX, this.maxY].join(', ') + ')'; }; + + +/** + * @param {number} value Value. + */ +ol.Rectangle.prototype.scaleFromCenter = function(value) { + var deltaX = (this.getWidth() / 2.0) * (value - 1); + var deltaY = (this.getHeight() / 2.0) * (value - 1); + this.minX -= deltaX; + this.minY -= deltaY; + this.maxX += deltaX; + this.maxY += deltaY; +}; diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js new file mode 100644 index 0000000000..273f22d49f --- /dev/null +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -0,0 +1,115 @@ +goog.provide('ol.renderer.canvas.ImageLayer'); + +goog.require('goog.vec.Mat4'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Single image layer. + */ +ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) { + + goog.base(this, mapRenderer, imageLayer); + + /** + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumber(); + +}; +goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImage = function() { + return goog.isNull(this.image_) ? + null : this.image_.getImageElement(this); +}; + + +/** + * @return {ol.layer.ImageLayer} Single image layer. + */ +ol.renderer.canvas.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getTransform = function() { + return this.transform_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + image = imageSource.getImage(frameState.extent, viewResolution); + if (!goog.isNull(image)) { + var imageState = image.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image.load(); + } else if (imageState == ol.ImageState.LOADED) { + this.image_ = image; + } + } + } + + if (!goog.isNull(this.image_)) { + image = this.image_; + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var transform = this.transform_; + goog.vec.Mat4.makeIdentity(transform); + goog.vec.Mat4.translate(transform, + frameState.size.width / 2, frameState.size.height / 2, 0); + goog.vec.Mat4.rotateZ(transform, viewRotation); + goog.vec.Mat4.scale( + transform, + imageResolution / viewResolution, + imageResolution / viewResolution, + 1); + goog.vec.Mat4.translate( + transform, + (imageExtent.minX - viewCenter.x) / imageResolution, + (viewCenter.y - imageExtent.maxY) / imageResolution, + 0); + } +}; diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index bd0b3dbfe3..24ee38307f 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -7,8 +7,10 @@ goog.require('goog.dom'); goog.require('goog.style'); goog.require('goog.vec.Mat4'); goog.require('ol.Size'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.ImageLayer'); goog.require('ol.renderer.canvas.TileLayer'); @@ -59,7 +61,9 @@ goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map); * @inheritDoc */ ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { - if (layer instanceof ol.layer.TileLayer) { + if (layer instanceof ol.layer.ImageLayer) { + return new ol.renderer.canvas.ImageLayer(this, layer); + } else if (layer instanceof ol.layer.TileLayer) { return new ol.renderer.canvas.TileLayer(this, layer); } else { goog.asserts.assert(false); diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js new file mode 100644 index 0000000000..b3f5f9f224 --- /dev/null +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -0,0 +1,122 @@ +goog.provide('ol.renderer.dom.ImageLayer'); + +goog.require('goog.dom'); +goog.require('goog.vec.Mat4'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.dom.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Image layer. + */ +ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) { + var target = goog.dom.createElement(goog.dom.TagName.DIV); + target.className = 'ol-layer-image'; + target.style.position = 'absolute'; + + goog.base(this, mapRenderer, imageLayer, target); + + /** + * The last rendered image. + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {goog.vec.Mat4.AnyType} + */ + this.transform_ = goog.vec.Mat4.createNumberIdentity(); + +}; +goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer); + + +/** + * @return {ol.layer.ImageLayer} Image layer. + */ +ol.renderer.dom.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.dom.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image = this.image_; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + var image_ = imageSource.getImage(frameState.extent, viewResolution); + if (!goog.isNull(image_)) { + var imageState = image_.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image_.load(); + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + } + } + } + + if (!goog.isNull(image)) { + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var transform = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(transform); + goog.vec.Mat4.translate(transform, + frameState.size.width / 2, frameState.size.height / 2, 0); + goog.vec.Mat4.rotateZ(transform, viewRotation); + goog.vec.Mat4.scale( + transform, + imageResolution / viewResolution, + imageResolution / viewResolution, + 1); + goog.vec.Mat4.translate( + transform, + (imageExtent.minX - viewCenter.x) / imageResolution, + (viewCenter.y - imageExtent.maxY) / imageResolution, + 0); + if (image != this.image_) { + var imageElement = image.getImageElement(this); + imageElement.style.position = 'absolute'; + goog.dom.removeChildren(this.target); + goog.dom.appendChild(this.target, imageElement); + this.image_ = image; + } + this.setTransform(transform); + } + +}; + + +/** + * @param {goog.vec.Mat4.AnyType} transform Transform. + */ +ol.renderer.dom.ImageLayer.prototype.setTransform = function(transform) { + if (!goog.vec.Mat4.equals(transform, this.transform_)) { + ol.dom.transformElement2D(this.target, transform, 6); + goog.vec.Mat4.setFromArray(this.transform_, transform); + } +}; diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js index 18700d9b25..a833d5fd91 100644 --- a/src/ol/renderer/dom/dommaprenderer.js +++ b/src/ol/renderer/dom/dommaprenderer.js @@ -5,8 +5,10 @@ goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.style'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); +goog.require('ol.renderer.dom.ImageLayer'); goog.require('ol.renderer.dom.TileLayer'); @@ -57,14 +59,15 @@ ol.renderer.dom.Map.prototype.addLayer = function(layer) { * @inheritDoc */ ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { + var layerRenderer; if (layer instanceof ol.layer.TileLayer) { - var layerRenderer = new ol.renderer.dom.TileLayer(this, layer); - goog.dom.appendChild(this.layersPane_, layerRenderer.getTarget()); - return layerRenderer; - } else { - goog.asserts.assert(false); - return null; + layerRenderer = new ol.renderer.dom.TileLayer(this, layer); + } else if (layer instanceof ol.layer.ImageLayer) { + layerRenderer = new ol.renderer.dom.ImageLayer(this, layer); } + goog.asserts.assert(goog.isDef(layerRenderer)); + goog.dom.appendChild(this.layersPane_, layerRenderer.getTarget()); + return layerRenderer; }; diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index fab64889fc..bfe98600c4 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -3,6 +3,8 @@ goog.provide('ol.renderer.Layer'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('ol.FrameState'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); goog.require('ol.Object'); goog.require('ol.Tile'); goog.require('ol.TileCoord'); @@ -119,6 +121,19 @@ ol.renderer.Layer.prototype.handleLayerContrastChange = goog.nullFunction; ol.renderer.Layer.prototype.handleLayerHueChange = goog.nullFunction; +/** + * Handle changes in image state. + * @param {goog.events.Event} event Image change event. + * @protected + */ +ol.renderer.Layer.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.ImageState.LOADED) { + this.getMap().requestRenderFrame(); + } +}; + + /** * @protected */ diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js new file mode 100644 index 0000000000..6449a7c178 --- /dev/null +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -0,0 +1,234 @@ +goog.provide('ol.renderer.webgl.ImageLayer'); + +goog.require('goog.vec.Mat4'); +goog.require('ol.Coordinate'); +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.webgl.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Tile layer. + */ +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + goog.base(this, mapRenderer, imageLayer); + + /** + * The last rendered image. + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * The last rendered texture. + * @private + * @type {WebGLTexture} + */ + this.texture_ = null; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.texCoordMatrix_ = goog.vec.Mat4.createNumberIdentity(); + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.vertexCoordMatrix_ = goog.vec.Mat4.createNumber(); + +}; +goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + + +/** + * @private + * @param {ol.Image} image Image. + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImageElement(this); + var gl = this.getMapRenderer().getGL(); + + var texture = gl.createTexture(); + + gl.bindTexture(goog.webgl.TEXTURE_2D, texture); + gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, + goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement); + + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, + goog.webgl.CLAMP_TO_EDGE); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, + goog.webgl.CLAMP_TO_EDGE); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); + + return texture; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.disposeInternal = function() { + var mapRenderer = this.getMapRenderer(); + var gl = mapRenderer.getGL(); + if (!gl.isContextLost()) { + gl.deleteTexture(this.texture_); + } + goog.base(this, 'disposeInternal'); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getTexture = function() { + return this.texture_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getVertexCoordMatrix = function() { + return this.vertexCoordMatrix_; +}; + + +/** + * @return {ol.layer.ImageLayer} Tile layer. + */ +ol.renderer.webgl.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.handleWebGLContextLost = function() { + this.texture_ = null; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var gl = this.getMapRenderer().getGL(); + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image = this.image_; + var texture = this.texture_; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + var image_ = imageSource.getImage(frameState.extent, viewResolution); + if (!goog.isNull(image_)) { + var imageState = image_.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image_.load(); + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + texture = this.createTexture_(image_); + if (!goog.isNull(this.texture_)) { + frameState.postRenderFunctions.push( + goog.partial(function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }, gl, this.texture_)); + } + } + } + } + + if (!goog.isNull(image)) { + goog.asserts.assert(!goog.isNull(texture)); + + var canvas = this.getMapRenderer().getCanvas(); + + this.updateVertexCoordMatrix_(canvas.width, canvas.height, + viewCenter, viewResolution, viewRotation, image.getExtent()); + + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix_; + goog.vec.Mat4.makeIdentity(texCoordMatrix); + goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1); + goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0); + + this.image_ = image; + this.texture_ = texture; + } +}; + + +/** + * @private + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + */ +ol.renderer.webgl.ImageLayer.prototype.updateVertexCoordMatrix_ = + function(canvasWidth, canvasHeight, viewCenter, + viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + + var vertexCoordMatrix = this.vertexCoordMatrix_; + goog.vec.Mat4.makeIdentity(vertexCoordMatrix); + goog.vec.Mat4.scale(vertexCoordMatrix, + 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); + goog.vec.Mat4.rotateZ(vertexCoordMatrix, -viewRotation); + goog.vec.Mat4.translate(vertexCoordMatrix, + imageExtent.minX - viewCenter.x, + imageExtent.minY - viewCenter.y, + 0); + goog.vec.Mat4.scale(vertexCoordMatrix, + imageExtent.getWidth() / 2, imageExtent.getHeight() / 2, 1); + goog.vec.Mat4.translate(vertexCoordMatrix, 1, 1, 0); + +}; diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 238462410f..067a4e054c 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -97,6 +97,12 @@ ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = goog.abstractMethod; ol.renderer.webgl.Layer.prototype.getTexture = goog.abstractMethod; +/** + * @return {!goog.vec.Mat4.Number} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getVertexCoordMatrix = goog.abstractMethod; + + /** * @inheritDoc */ diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index f7a553f3f2..62b03fc489 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -14,9 +14,11 @@ goog.require('goog.webgl'); goog.require('ol.FrameState'); goog.require('ol.Size'); goog.require('ol.Tile'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.webgl.FragmentShader'); +goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.TileLayer'); goog.require('ol.renderer.webgl.VertexShader'); goog.require('ol.structs.LRUCache'); @@ -79,11 +81,12 @@ ol.renderer.webgl.map.shader.Vertex = function() { 'attribute vec2 aTexCoord;', '', 'uniform mat4 uTexCoordMatrix;', + 'uniform mat4 uVertexCoordMatrix;', '', 'varying vec2 vTexCoord;', '', 'void main(void) {', - ' gl_Position = vec4(aPosition, 0., 1.);', + ' gl_Position = uVertexCoordMatrix * vec4(aPosition, 0., 1.);', ' vTexCoord = (uTexCoordMatrix * vec4(aTexCoord, 0., 1.)).st;', '}' ].join('\n')); @@ -159,7 +162,8 @@ ol.renderer.webgl.Map = function(container, map) { * uColorMatrix: WebGLUniformLocation, * uOpacity: WebGLUniformLocation, * uTexture: WebGLUniformLocation, - * uTexCoordMatrix: WebGLUniformLocation}|null} + * uTexCoordMatrix: WebGLUniformLocation, + * uVertexCoordMatrix: WebGLUniformLocation}|null} */ this.locations_ = null; @@ -270,12 +274,15 @@ ol.renderer.webgl.Map.prototype.bindTileTexture = * @inheritDoc */ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { + var layerRenderer = null; if (layer instanceof ol.layer.TileLayer) { - return new ol.renderer.webgl.TileLayer(this, layer); + layerRenderer = new ol.renderer.webgl.TileLayer(this, layer); + } else if (layer instanceof ol.layer.ImageLayer) { + layerRenderer = new ol.renderer.webgl.ImageLayer(this, layer); } else { goog.asserts.assert(false); - return null; } + return layerRenderer; }; @@ -519,6 +526,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { aTexCoord: gl.getAttribLocation(program, 'aTexCoord'), uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), uTexCoordMatrix: gl.getUniformLocation(program, 'uTexCoordMatrix'), + uVertexCoordMatrix: gl.getUniformLocation(program, 'uVertexCoordMatrix'), uOpacity: gl.getUniformLocation(program, 'uOpacity'), uTexture: gl.getUniformLocation(program, 'uTexture') }; @@ -555,6 +563,9 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { gl.uniformMatrix4fv( this.locations_.uTexCoordMatrix, false, layerRenderer.getTexCoordMatrix()); + gl.uniformMatrix4fv( + this.locations_.uVertexCoordMatrix, false, + layerRenderer.getVertexCoordMatrix()); gl.uniformMatrix4fv( this.locations_.uColorMatrix, false, layerRenderer.getColorMatrix()); gl.uniform1f(this.locations_.uOpacity, layer.getOpacity()); diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 8f1dab302a..360f8f8d82 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -141,6 +141,12 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { */ this.texCoordMatrix_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.vertexCoordMatrix_ = goog.vec.Mat4.createNumberIdentity(); + /** * @private * @type {ol.TileRange} @@ -237,6 +243,14 @@ ol.renderer.webgl.TileLayer.prototype.getTexture = function() { }; +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.getVertexCoordMatrix = function() { + return this.vertexCoordMatrix_; +}; + + /** * @return {ol.layer.TileLayer} Tile layer. */ diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js new file mode 100644 index 0000000000..caad04b5e9 --- /dev/null +++ b/src/ol/source/imagesource.js @@ -0,0 +1,113 @@ +goog.provide('ol.source.ImageSource'); + +goog.require('goog.array'); +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunction'); +goog.require('ol.ImageUrlFunctionType'); +goog.require('ol.Projection'); +goog.require('ol.Size'); +goog.require('ol.array'); +goog.require('ol.source.Source'); + + +/** + * @typedef {{attributions: (Array.|undefined), + * crossOrigin: (null|string|undefined), + * extent: (null|ol.Extent|undefined), + * projection: (ol.Projection|undefined), + * resolutions: (Array.|undefined), + * imageUrlFunction: (ol.ImageUrlFunctionType| + * undefined)}} + */ +ol.source.ImageSourceOptions; + + + +/** + * @constructor + * @extends {ol.source.Source} + * @param {ol.source.ImageSourceOptions} options Single + * image source options. + */ +ol.source.ImageSource = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + projection: options.projection + }); + + /** + * @protected + * @type {ol.ImageUrlFunctionType} + */ + this.imageUrlFunction = + goog.isDef(options.imageUrlFunction) ? + options.imageUrlFunction : + ol.ImageUrlFunction.nullImageUrlFunction; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : 'anonymous'; + + /** + * @private + * @type {Array.} + */ + this.resolutions_ = goog.isDef(options.resolutions) ? + options.resolutions : null; + goog.asserts.assert(goog.isNull(this.resolutions_) || + goog.array.isSorted(this.resolutions_, + function(a, b) { + return b - a; + }, true)); + +}; +goog.inherits(ol.source.ImageSource, ol.source.Source); + + +/** + * @protected + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.Size} size Size. + * @return {ol.Image} Single image. + */ +ol.source.ImageSource.prototype.createImage = + function(extent, resolution, size) { + var image = null; + var imageUrl = this.imageUrlFunction(extent, size); + if (goog.isDef(imageUrl)) { + image = new ol.Image( + extent, resolution, imageUrl, this.crossOrigin_); + } + return image; +}; + + +/** + * @protected + * @param {number} resolution Resolution. + * @return {number} Resolution. + */ +ol.source.ImageSource.prototype.findNearestResolution = + function(resolution) { + if (!goog.isNull(this.resolutions_)) { + var idx = ol.array.linearFindNearest(this.resolutions_, resolution); + resolution = this.resolutions_[idx]; + } + return resolution; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {ol.Image} Single image. + */ +ol.source.ImageSource.prototype.getImage = goog.abstractMethod; diff --git a/src/ol/source/singleimagewms.exports b/src/ol/source/singleimagewms.exports new file mode 100644 index 0000000000..4d79ecdabc --- /dev/null +++ b/src/ol/source/singleimagewms.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.SingleImageWMS diff --git a/src/ol/source/singleimagewmssource.js b/src/ol/source/singleimagewmssource.js new file mode 100644 index 0000000000..3529678443 --- /dev/null +++ b/src/ol/source/singleimagewmssource.js @@ -0,0 +1,99 @@ +goog.provide('ol.source.SingleImageWMS'); + +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunction'); +goog.require('ol.Projection'); +goog.require('ol.Size'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.source.ImageSource} + * @param {ol.source.SingleImageWMSOptions} options Options. + */ +ol.source.SingleImageWMS = function(options) { + + var projection = ol.Projection.createProjection( + options.projection, 'EPSG:3857'); + var projectionExtent = projection.getExtent(); + + var extent = goog.isDef(options.extent) ? + options.extent : projectionExtent; + + var version = goog.isDef(options.version) ? + options.version : '1.3'; + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': version, + 'REQUEST': 'GetMap', + 'STYLES': '', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + baseParams[version >= '1.3' ? 'CRS' : 'SRS'] = projection.getCode(); + goog.object.extend(baseParams, options.params); + + var imageUrlFunction; + if (options.url) { + var url = goog.uri.utils.appendParamsFromMap( + options.url, baseParams); + imageUrlFunction = ol.ImageUrlFunction.createBboxParam(url); + } else { + imageUrlFunction = + ol.ImageUrlFunction.nullImageUrlFunction; + } + + goog.base(this, { + attributions: options.attributions, + crossOrigin: options.crossOrigin, + extent: extent, + projection: projection, + resolutions: options.resolutions, + imageUrlFunction: imageUrlFunction + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * FIXME configurable? + * @private + * @type {number} + */ + this.ratio_ = 1.5; + +}; +goog.inherits(ol.source.SingleImageWMS, ol.source.ImageSource); + + +/** + * @inheritDoc + */ +ol.source.SingleImageWMS.prototype.getImage = + function(extent, resolution) { + resolution = this.findNearestResolution(resolution); + + var image = this.image_; + if (!goog.isNull(image) && + image.getResolution() == resolution && + image.getExtent().containsExtent(extent)) { + return image; + } + + extent = new ol.Extent(extent.minX, extent.minY, + extent.maxX, extent.maxY); + extent.scaleFromCenter(this.ratio_); + var width = extent.getWidth() / resolution; + var height = extent.getHeight() / resolution; + var size = new ol.Size(width, height); + + this.image_ = this.createImage(extent, resolution, size); + return this.image_; +}; diff --git a/src/ol/source/staticimage.exports b/src/ol/source/staticimage.exports new file mode 100644 index 0000000000..3f7b2c98a5 --- /dev/null +++ b/src/ol/source/staticimage.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.StaticImage diff --git a/src/ol/source/staticimagesource.js b/src/ol/source/staticimagesource.js new file mode 100644 index 0000000000..eafa205eb1 --- /dev/null +++ b/src/ol/source/staticimagesource.js @@ -0,0 +1,61 @@ +goog.provide('ol.source.StaticImage'); + +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunctionType'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.source.ImageSource} + * @param {ol.source.StaticImageOptions} options Options. + */ +ol.source.StaticImage = function(options) { + + var imageFunction = ol.source.StaticImage.createImageFunction( + options.url); + + var imageExtent = options.imageExtent; + var imageSize = options.imageSize; + var imageResolution = imageExtent.getHeight() / imageSize.height; + + goog.base(this, { + attributions: options.attributions, + crossOrigin: options.crossOrigin, + extent: options.extent, + projection: options.projection, + imageUrlFunction: imageFunction, + resolutions: [imageResolution] + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = this.createImage(imageExtent, imageResolution, imageSize); + +}; +goog.inherits(ol.source.StaticImage, ol.source.ImageSource); + + +/** + * @inheritDoc + */ +ol.source.StaticImage.prototype.getImage = function(extent, resolution) { + if (extent.intersects(this.image_.getExtent())) { + return this.image_; + } + return null; +}; + + +/** + * @param {string|undefined} url URL. + * @return {ol.ImageUrlFunctionType} Function. + */ +ol.source.StaticImage.createImageFunction = function(url) { + return function(extent, size) { + return url; + }; +}; diff --git a/src/ol/tilerange.js b/src/ol/tilerange.js index 7c402f00cb..e1baa169f8 100644 --- a/src/ol/tilerange.js +++ b/src/ol/tilerange.js @@ -61,16 +61,6 @@ ol.TileRange.prototype.containsTileRange = function(tileRange) { }; -/** - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} Equals. - */ -ol.TileRange.prototype.equals = function(tileRange) { - return this.minX == tileRange.minX && tileRange.maxX == this.maxX && - this.minY == tileRange.minY && tileRange.minY == this.minY; -}; - - /** * @inheritDoc * @return {number} Height. diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index 2a2d98e9f3..de00b63e18 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -2,42 +2,67 @@ goog.provide('ol.test.Extent'); describe('ol.Extent', function() { - describe('contains', function() { + describe('containsCoordinate', function() { describe('positive', function() { it('returns true', function() { var extent = new ol.Extent(1, 2, 3, 4); - expect(extent.contains(new ol.Coordinate(1, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(1, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(1, 4))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 4))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 4))).toBeTruthy(); }); }); describe('negative', function() { it('returns false', function() { var extent = new ol.Extent(1, 2, 3, 4); - expect(extent.contains(new ol.Coordinate(0, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 2))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 3))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 4))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(1, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(1, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(2, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(2, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(3, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(3, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 2))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 3))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 4))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 2))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 3))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 4))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 2))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 3))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 4))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 5))).toBeFalsy(); }); }); }); diff --git a/test/spec/ol/rectangle.test.js b/test/spec/ol/rectangle.test.js index 55d8882e54..fe7ee9a1c3 100644 --- a/test/spec/ol/rectangle.test.js +++ b/test/spec/ol/rectangle.test.js @@ -98,6 +98,17 @@ describe('ol.Rectangle', function() { }); }); + describe('scaleFromCenter', function() { + it('scales the extent from its center', function() { + var rectangle = new ol.Rectangle(1, 1, 3, 3); + rectangle.scaleFromCenter(2); + expect(rectangle.minX).toEqual(0); + expect(rectangle.minY).toEqual(0); + expect(rectangle.maxX).toEqual(4); + expect(rectangle.maxY).toEqual(4); + }); + }); + }); goog.require('ol.Coordinate'); diff --git a/test/spec/ol/renderer/webgl/imagelayer.test.js b/test/spec/ol/renderer/webgl/imagelayer.test.js new file mode 100644 index 0000000000..62f3038827 --- /dev/null +++ b/test/spec/ol/renderer/webgl/imagelayer.test.js @@ -0,0 +1,87 @@ +goog.provide('ol.test.renderer.webgl.ImageLayer'); + +describe('ol.renderer.webgl.ImageLayer', function() { + describe('updateVertexCoordMatrix_', function() { + var map; + var renderer; + var canvasWidth; + var canvasHeight; + var viewExtent; + var viewResolution; + var viewRotation; + var imageExtent; + + beforeEach(function() { + map = new ol.Map({ + target: 'map' + }); + var layer = new ol.layer.ImageLayer({ + source: new ol.source.ImageSource({ + extent: new ol.Extent(0, 0, 1, 1) + }) + }); + renderer = new ol.renderer.webgl.ImageLayer(map.getRenderer(), layer); + + // input params + canvasWidth = 512; + canvasHeight = 256; + viewResolution = 10; + viewRotation = 0; + viewCenter = new ol.Coordinate(7680, 3840); + // view extent is 512O, 2560, 10240, 5120 + + // image size is 1024, 768 + // image resolution is 10 + imageExtent = new ol.Extent(0, 0, 10240, 7680); + }); + + afterEach(function() { + map.dispose(); + }); + + it('produces a correct matrix', function() { + + renderer.updateVertexCoordMatrix_(canvasWidth, canvasHeight, + viewCenter, viewResolution, viewRotation, imageExtent); + var matrix = renderer.getVertexCoordMatrix(); + + var input; + var output = goog.vec.Vec4.createNumber(); + + input = goog.vec.Vec4.createFromValues(-1, -1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-3); + expect(output[1]).toEqual(-3); + + input = goog.vec.Vec4.createFromValues(1, -1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(1); + expect(output[1]).toEqual(-3); + + input = goog.vec.Vec4.createFromValues(-1, 1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-3); + expect(output[1]).toEqual(3); + + input = goog.vec.Vec4.createFromValues(1, 1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(1); + expect(output[1]).toEqual(3); + + input = goog.vec.Vec4.createFromValues(0, 0, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-1); + expect(output[1]).toEqual(0); + }); + }); +}); + +goog.require('goog.vec.Mat4'); +goog.require('goog.vec.Vec4'); +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.Map'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.source.ImageSource'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.webgl.ImageLayer');