Merge pull request #149 from elemoine/singleimage

Add support for single image WMS
This commit is contained in:
Éric Lemoine
2013-02-17 14:19:29 -08:00
27 changed files with 1286 additions and 52 deletions

View File

@@ -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(
'&copy; ' +
@@ -50,7 +63,7 @@ var layers = new ol.Collection([
'National parks / geo.admin.ch</a>')],
params: {'LAYERS': 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung'},
projection: epsg21781,
extent: extent
resolutions: resolutions
})
})
]);

40
src/imageurlfunction.js Normal file
View File

@@ -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;
};

View File

@@ -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.<ol.Attribution>|undefined
@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.crossOrigin null|string|undefined
@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.extent ol.Extent|undefined
@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.params Object.<string,*>
@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.projection ol.Projection|undefined
@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.resolutions Array.<number>|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.<ol.Attribution>|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.<ol.Attribution>|undefined
@exportObjectLiteralProperty ol.source.TiledWMSOptions.params Object

View File

@@ -1,4 +1,5 @@
// FIXME handle date line wrap
// FIXME does not handle image sources
goog.provide('ol.control.Attribution');

View File

@@ -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.
*/

187
src/ol/image.js Normal file
View File

@@ -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.<number, Image>}
*/
this.imageByContext_ = {};
/**
* @private
* @type {Array.<number>}
*/
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;
};

View File

@@ -0,0 +1 @@
@exportClass ol.layer.ImageLayer ol.layer.LayerOptions

View File

@@ -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());
};

View File

@@ -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;
};

View File

@@ -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);
}
};

View File

@@ -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);

View File

@@ -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);
}
};

View File

@@ -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;
};

View File

@@ -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
*/

View File

@@ -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);
};

View File

@@ -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
*/

View File

@@ -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());

View File

@@ -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.
*/

View File

@@ -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.<ol.Attribution>|undefined),
* crossOrigin: (null|string|undefined),
* extent: (null|ol.Extent|undefined),
* projection: (ol.Projection|undefined),
* resolutions: (Array.<number>|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.<number>}
*/
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;

View File

@@ -0,0 +1 @@
@exportSymbol ol.source.SingleImageWMS

View File

@@ -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_;
};

View File

@@ -0,0 +1 @@
@exportSymbol ol.source.StaticImage

View File

@@ -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;
};
};

View File

@@ -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.

View File

@@ -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();
});
});
});

View File

@@ -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');

View File

@@ -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');