diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 922136574f..ce1a625f3e 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -7,10 +7,13 @@ goog.require('ol.Coordinate'); goog.require('ol.Extent'); goog.require('ol.ImageBase'); goog.require('ol.ViewHint'); +goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.layer.Image'); goog.require('ol.proj'); goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Context'); @@ -32,6 +35,18 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { */ this.image_ = null; + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.hitTransformationMatrix_ = null; + }; goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); @@ -143,6 +158,7 @@ ol.renderer.webgl.ImageLayer.prototype.prepareFrame = this.updateProjectionMatrix_(canvas.width, canvas.height, viewCenter, viewResolution, viewRotation, image.getExtent()); + this.hitTransformationMatrix_ = null; // Translate and scale to flip the Y coord. var texCoordMatrix = this.texCoordMatrix; @@ -204,3 +220,103 @@ ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtPixel = coordinate, frameState, goog.functions.TRUE, this); return goog.isDef(hasFeature); }; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.image_) || goog.isNull(this.image_.getImage())) { + return undefined; + } + + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var hasFeature = this.forEachFeatureAtPixel( + coordinate, frameState, goog.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; + + if (goog.isNull(this.hitTransformationMatrix_)) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); + } + + var pixelOnMap = this.getMap().getPixelFromCoordinate(coordinate); + var pixelOnFrameBuffer = [0, 0]; + ol.vec.Mat4.multVec2( + this.hitTransformationMatrix_, pixelOnMap, pixelOnFrameBuffer); + + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check + return undefined; + } + + if (goog.isNull(this.hitCanvasContext_)) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } +}; + + +/** + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize + * @param {ol.Size} imageSize + * @return {goog.vec.Mat4.Number} + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = + function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(mapCoordMatrix); + goog.vec.Mat4.translate(mapCoordMatrix, -1, -1, 0); + goog.vec.Mat4.scale(mapCoordMatrix, 2 / mapSize[0], 2 / mapSize[1], 1); + goog.vec.Mat4.translate(mapCoordMatrix, 0, mapSize[1], 0); + goog.vec.Mat4.scale(mapCoordMatrix, 1, -1, 1); + + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.projectionMatrix, projectionMatrixInv); + + // the third matrix scales to the image dimensions and flips the y-axis again + var imageCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(imageCoordMatrix); + goog.vec.Mat4.translate(imageCoordMatrix, 0, imageSize[1], 0); + goog.vec.Mat4.scale(imageCoordMatrix, 1, -1, 1); + goog.vec.Mat4.scale(imageCoordMatrix, imageSize[0] / 2, imageSize[1] / 2, 1); + goog.vec.Mat4.translate(imageCoordMatrix, 1, 1, 0); + + var transformMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.multMat( + imageCoordMatrix, projectionMatrixInv, transformMatrix); + goog.vec.Mat4.multMat( + transformMatrix, mapCoordMatrix, transformMatrix); + + return transformMatrix; +}; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index c5985c194b..a9c4205659 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -648,6 +648,57 @@ ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = + function(coordinate, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + + var context = this.getContext(); + var viewState = frameState.viewState; + var result; + + // do the hit-detection for the overlays first + if (!goog.isNull(this.replayGroup)) { + // use default color values + var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; + + var hasFeature = this.replayGroup.hasFeatureAtPixel(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, + coordinate); + if (hasFeature) { + result = callback.call(thisArg, null); + if (result) { + return result; + } + } + } + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + coordinate, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + /** * @private * @const diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 68ceed8d0d..455fce7427 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -17,6 +17,7 @@ goog.require('ol.math'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.tilelayer.shader'); goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Buffer'); @@ -326,3 +327,39 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = return true; }; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.framebuffer)) { + return undefined; + } + var mapSize = this.getMap().getSize(); + + var pixelOnMap = this.getMap().getPixelFromCoordinate(coordinate); + var pixelOnMapScaled = [ + pixelOnMap[0] / mapSize[0], + (mapSize[1] - pixelOnMap[1]) / mapSize[1]]; + + var pixelOnFrameBufferScaled = [0, 0]; + ol.vec.Mat4.multVec2( + this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + + var gl = this.getWebGLMapRenderer().getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } +}; diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index f2a00be9e0..904f496e95 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -162,6 +162,21 @@ ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = + function(coordinate, frameState, callback, thisArg) { + var hasFeature = this.hasFeatureAtPixel(coordinate, frameState); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } +}; + + /** * Handle changes in image style state. * @param {goog.events.Event} event Image style change event.