diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Renderer/Canvas.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Renderer/Canvas.js | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js b/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js new file mode 100644 index 0000000..61a327c --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js @@ -0,0 +1,906 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Renderer.js + */ + +/** + * Class: OpenLayers.Renderer.Canvas + * A renderer based on the 2D 'canvas' drawing element. + * + * Inherits: + * - <OpenLayers.Renderer> + */ +OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { + + /** + * APIProperty: hitDetection + * {Boolean} Allow for hit detection of features. Default is true. + */ + hitDetection: true, + + /** + * Property: hitOverflow + * {Number} The method for converting feature identifiers to color values + * supports 16777215 sequential values. Two features cannot be + * predictably detected if their identifiers differ by more than this + * value. The hitOverflow allows for bigger numbers (but the + * difference in values is still limited). + */ + hitOverflow: 0, + + /** + * Property: canvas + * {Canvas} The canvas context object. + */ + canvas: null, + + /** + * Property: features + * {Object} Internal object of feature/style pairs for use in redrawing the layer. + */ + features: null, + + /** + * Property: pendingRedraw + * {Boolean} The renderer needs a redraw call to render features added while + * the renderer was locked. + */ + pendingRedraw: false, + + /** + * Property: cachedSymbolBounds + * {Object} Internal cache of calculated symbol extents. + */ + cachedSymbolBounds: {}, + + /** + * Constructor: OpenLayers.Renderer.Canvas + * + * Parameters: + * containerID - {<String>} + * options - {Object} Optional properties to be set on the renderer. + */ + initialize: function(containerID, options) { + OpenLayers.Renderer.prototype.initialize.apply(this, arguments); + this.root = document.createElement("canvas"); + this.container.appendChild(this.root); + this.canvas = this.root.getContext("2d"); + this.features = {}; + if (this.hitDetection) { + this.hitCanvas = document.createElement("canvas"); + this.hitContext = this.hitCanvas.getContext("2d"); + } + }, + + /** + * Method: setExtent + * Set the visible part of the layer. + * + * Parameters: + * extent - {<OpenLayers.Bounds>} + * resolutionChanged - {Boolean} + * + * Returns: + * {Boolean} true to notify the layer that the new extent does not exceed + * the coordinate range, and the features will not need to be redrawn. + * False otherwise. + */ + setExtent: function() { + OpenLayers.Renderer.prototype.setExtent.apply(this, arguments); + // always redraw features + return false; + }, + + /** + * Method: eraseGeometry + * Erase a geometry from the renderer. Because the Canvas renderer has + * 'memory' of the features that it has drawn, we have to remove the + * feature so it doesn't redraw. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * featureId - {String} + */ + eraseGeometry: function(geometry, featureId) { + this.eraseFeatures(this.features[featureId][0]); + }, + + /** + * APIMethod: supported + * + * Returns: + * {Boolean} Whether or not the browser supports the renderer class + */ + supported: function() { + return OpenLayers.CANVAS_SUPPORTED; + }, + + /** + * Method: setSize + * Sets the size of the drawing surface. + * + * Once the size is updated, redraw the canvas. + * + * Parameters: + * size - {<OpenLayers.Size>} + */ + setSize: function(size) { + this.size = size.clone(); + var root = this.root; + root.style.width = size.w + "px"; + root.style.height = size.h + "px"; + root.width = size.w; + root.height = size.h; + this.resolution = null; + if (this.hitDetection) { + var hitCanvas = this.hitCanvas; + hitCanvas.style.width = size.w + "px"; + hitCanvas.style.height = size.h + "px"; + hitCanvas.width = size.w; + hitCanvas.height = size.h; + } + }, + + /** + * Method: drawFeature + * Draw the feature. Stores the feature in the features list, + * then redraws the layer. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} + * style - {<Object>} + * + * Returns: + * {Boolean} The feature has been drawn completely. If the feature has no + * geometry, undefined will be returned. If the feature is not rendered + * for other reasons, false will be returned. + */ + drawFeature: function(feature, style) { + var rendered; + if (feature.geometry) { + style = this.applyDefaultSymbolizer(style || feature.style); + // don't render if display none or feature outside extent + var bounds = feature.geometry.getBounds(); + + var worldBounds; + if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { + worldBounds = this.map.getMaxExtent(); + } + + var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds}); + + rendered = (style.display !== "none") && !!bounds && intersects; + if (rendered) { + // keep track of what we have rendered for redraw + this.features[feature.id] = [feature, style]; + } + else { + // remove from features tracked for redraw + delete(this.features[feature.id]); + } + this.pendingRedraw = true; + } + if (this.pendingRedraw && !this.locked) { + this.redraw(); + this.pendingRedraw = false; + } + return rendered; + }, + + /** + * Method: drawGeometry + * Used when looping (in redraw) over the features; draws + * the canvas. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + */ + drawGeometry: function(geometry, style, featureId) { + var className = geometry.CLASS_NAME; + if ((className == "OpenLayers.Geometry.Collection") || + (className == "OpenLayers.Geometry.MultiPoint") || + (className == "OpenLayers.Geometry.MultiLineString") || + (className == "OpenLayers.Geometry.MultiPolygon")) { + for (var i = 0; i < geometry.components.length; i++) { + this.drawGeometry(geometry.components[i], style, featureId); + } + return; + } + switch (geometry.CLASS_NAME) { + case "OpenLayers.Geometry.Point": + this.drawPoint(geometry, style, featureId); + break; + case "OpenLayers.Geometry.LineString": + this.drawLineString(geometry, style, featureId); + break; + case "OpenLayers.Geometry.LinearRing": + this.drawLinearRing(geometry, style, featureId); + break; + case "OpenLayers.Geometry.Polygon": + this.drawPolygon(geometry, style, featureId); + break; + default: + break; + } + }, + + /** + * Method: drawExternalGraphic + * Called to draw External graphics. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawExternalGraphic: function(geometry, style, featureId) { + var img = new Image(); + + var title = style.title || style.graphicTitle; + if (title) { + img.title = title; + } + + var width = style.graphicWidth || style.graphicHeight; + var height = style.graphicHeight || style.graphicWidth; + width = width ? width : style.pointRadius * 2; + height = height ? height : style.pointRadius * 2; + var xOffset = (style.graphicXOffset != undefined) ? + style.graphicXOffset : -(0.5 * width); + var yOffset = (style.graphicYOffset != undefined) ? + style.graphicYOffset : -(0.5 * height); + + var opacity = style.graphicOpacity || style.fillOpacity; + + var onLoad = function() { + if(!this.features[featureId]) { + return; + } + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + if(!isNaN(p0) && !isNaN(p1)) { + var x = (p0 + xOffset) | 0; + var y = (p1 + yOffset) | 0; + var canvas = this.canvas; + canvas.globalAlpha = opacity; + var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor || + (OpenLayers.Renderer.Canvas.drawImageScaleFactor = + /android 2.1/.test(navigator.userAgent.toLowerCase()) ? + // 320 is the screen width of the G1 phone, for + // which drawImage works out of the box. + 320 / window.screen.width : 1 + ); + canvas.drawImage( + img, x*factor, y*factor, width*factor, height*factor + ); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId); + this.hitContext.fillRect(x, y, width, height); + } + } + }; + + img.onload = OpenLayers.Function.bind(onLoad, this); + img.src = style.externalGraphic; + }, + + /** + * Method: drawNamedSymbol + * Called to draw Well Known Graphic Symbol Name. + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawNamedSymbol: function(geometry, style, featureId) { + var x, y, cx, cy, i, symbolBounds, scaling, angle; + var unscaledStrokeWidth; + var deg2rad = Math.PI / 180.0; + + var symbol = OpenLayers.Renderer.symbol[style.graphicName]; + + if (!symbol) { + throw new Error(style.graphicName + ' is not a valid symbol name'); + } + + if (!symbol.length || symbol.length < 2) return; + + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + + if (isNaN(p0) || isNaN(p1)) return; + + // Use rounded line caps + this.canvas.lineCap = "round"; + this.canvas.lineJoin = "round"; + + if (this.hitDetection) { + this.hitContext.lineCap = "round"; + this.hitContext.lineJoin = "round"; + } + + // Scale and rotate symbols, using precalculated bounds whenever possible. + if (style.graphicName in this.cachedSymbolBounds) { + symbolBounds = this.cachedSymbolBounds[style.graphicName]; + } else { + symbolBounds = new OpenLayers.Bounds(); + for(i = 0; i < symbol.length; i+=2) { + symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1])); + } + this.cachedSymbolBounds[style.graphicName] = symbolBounds; + } + + // Push symbol scaling, translation and rotation onto the transformation stack in reverse order. + // Don't forget to apply all canvas transformations to the hitContext canvas as well(!) + this.canvas.save(); + if (this.hitDetection) { this.hitContext.save(); } + + // Step 3: place symbol at the desired location + this.canvas.translate(p0,p1); + if (this.hitDetection) { this.hitContext.translate(p0,p1); } + + // Step 2a. rotate the symbol if necessary + angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined. + if (!isNaN(angle)) { + this.canvas.rotate(angle); + if (this.hitDetection) { this.hitContext.rotate(angle); } + } + + // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension. + scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight()); + this.canvas.scale(scaling,scaling); + if (this.hitDetection) { this.hitContext.scale(scaling,scaling); } + + // Step 1: center the symbol at the origin + cx = symbolBounds.getCenterLonLat().lon; + cy = symbolBounds.getCenterLonLat().lat; + this.canvas.translate(-cx,-cy); + if (this.hitDetection) { this.hitContext.translate(-cx,-cy); } + + // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!) + // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore. + unscaledStrokeWidth = style.strokeWidth; + style.strokeWidth = unscaledStrokeWidth / scaling; + + if (style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + for (i=0; i<symbol.length; i=i+2) { + x = symbol[i]; + y = symbol[i+1]; + if (i == 0) this.canvas.moveTo(x,y); + this.canvas.lineTo(x,y); + } + this.canvas.closePath(); + this.canvas.fill(); + + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId, style); + this.hitContext.beginPath(); + for (i=0; i<symbol.length; i=i+2) { + x = symbol[i]; + y = symbol[i+1]; + if (i == 0) this.canvas.moveTo(x,y); + this.hitContext.lineTo(x,y); + } + this.hitContext.closePath(); + this.hitContext.fill(); + } + } + + if (style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + for (i=0; i<symbol.length; i=i+2) { + x = symbol[i]; + y = symbol[i+1]; + if (i == 0) this.canvas.moveTo(x,y); + this.canvas.lineTo(x,y); + } + this.canvas.closePath(); + this.canvas.stroke(); + + + if (this.hitDetection) { + this.setHitContextStyle("stroke", featureId, style, scaling); + this.hitContext.beginPath(); + for (i=0; i<symbol.length; i=i+2) { + x = symbol[i]; + y = symbol[i+1]; + if (i == 0) this.hitContext.moveTo(x,y); + this.hitContext.lineTo(x,y); + } + this.hitContext.closePath(); + this.hitContext.stroke(); + } + + } + + style.strokeWidth = unscaledStrokeWidth; + this.canvas.restore(); + if (this.hitDetection) { this.hitContext.restore(); } + this.setCanvasStyle("reset"); + }, + + /** + * Method: setCanvasStyle + * Prepare the canvas for drawing by setting various global settings. + * + * Parameters: + * type - {String} one of 'stroke', 'fill', or 'reset' + * style - {Object} Symbolizer hash + */ + setCanvasStyle: function(type, style) { + if (type === "fill") { + this.canvas.globalAlpha = style['fillOpacity']; + this.canvas.fillStyle = style['fillColor']; + } else if (type === "stroke") { + this.canvas.globalAlpha = style['strokeOpacity']; + this.canvas.strokeStyle = style['strokeColor']; + this.canvas.lineWidth = style['strokeWidth']; + } else { + this.canvas.globalAlpha = 0; + this.canvas.lineWidth = 1; + } + }, + + /** + * Method: featureIdToHex + * Convert a feature ID string into an RGB hex string. + * + * Parameters: + * featureId - {String} Feature id + * + * Returns: + * {String} RGB hex string. + */ + featureIdToHex: function(featureId) { + var id = Number(featureId.split("_").pop()) + 1; // zero for no feature + if (id >= 16777216) { + this.hitOverflow = id - 16777215; + id = id % 16777216 + 1; + } + var hex = "000000" + id.toString(16); + var len = hex.length; + hex = "#" + hex.substring(len-6, len); + return hex; + }, + + /** + * Method: setHitContextStyle + * Prepare the hit canvas for drawing by setting various global settings. + * + * Parameters: + * type - {String} one of 'stroke', 'fill', or 'reset' + * featureId - {String} The feature id. + * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer. + */ + setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) { + var hex = this.featureIdToHex(featureId); + if (type == "fill") { + this.hitContext.globalAlpha = 1.0; + this.hitContext.fillStyle = hex; + } else if (type == "stroke") { + this.hitContext.globalAlpha = 1.0; + this.hitContext.strokeStyle = hex; + // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol + // on a transformed canvas, so the antialias width bump has to scale as well. + if (typeof strokeScaling === "undefined") { + this.hitContext.lineWidth = symbolizer.strokeWidth + 2; + } else { + if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; } + } + } else { + this.hitContext.globalAlpha = 0; + this.hitContext.lineWidth = 1; + } + }, + + /** + * Method: drawPoint + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawPoint: function(geometry, style, featureId) { + if(style.graphic !== false) { + if(style.externalGraphic) { + this.drawExternalGraphic(geometry, style, featureId); + } else if (style.graphicName && (style.graphicName != "circle")) { + this.drawNamedSymbol(geometry, style, featureId); + } else { + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + if(!isNaN(p0) && !isNaN(p1)) { + var twoPi = Math.PI*2; + var radius = style.pointRadius; + if(style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + this.canvas.arc(p0, p1, radius, 0, twoPi, true); + this.canvas.fill(); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId, style); + this.hitContext.beginPath(); + this.hitContext.arc(p0, p1, radius, 0, twoPi, true); + this.hitContext.fill(); + } + } + + if(style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + this.canvas.arc(p0, p1, radius, 0, twoPi, true); + this.canvas.stroke(); + if (this.hitDetection) { + this.setHitContextStyle("stroke", featureId, style); + this.hitContext.beginPath(); + this.hitContext.arc(p0, p1, radius, 0, twoPi, true); + this.hitContext.stroke(); + } + this.setCanvasStyle("reset"); + } + } + } + } + }, + + /** + * Method: drawLineString + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawLineString: function(geometry, style, featureId) { + style = OpenLayers.Util.applyDefaults({fill: false}, style); + this.drawLinearRing(geometry, style, featureId); + }, + + /** + * Method: drawLinearRing + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawLinearRing: function(geometry, style, featureId) { + if (style.fill !== false) { + this.setCanvasStyle("fill", style); + this.renderPath(this.canvas, geometry, style, featureId, "fill"); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId, style); + this.renderPath(this.hitContext, geometry, style, featureId, "fill"); + } + } + if (style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.renderPath(this.canvas, geometry, style, featureId, "stroke"); + if (this.hitDetection) { + this.setHitContextStyle("stroke", featureId, style); + this.renderPath(this.hitContext, geometry, style, featureId, "stroke"); + } + } + this.setCanvasStyle("reset"); + }, + + /** + * Method: renderPath + * Render a path with stroke and optional fill. + */ + renderPath: function(context, geometry, style, featureId, type) { + var components = geometry.components; + var len = components.length; + context.beginPath(); + var start = this.getLocalXY(components[0]); + var x = start[0]; + var y = start[1]; + if (!isNaN(x) && !isNaN(y)) { + context.moveTo(start[0], start[1]); + for (var i=1; i<len; ++i) { + var pt = this.getLocalXY(components[i]); + context.lineTo(pt[0], pt[1]); + } + if (type === "fill") { + context.fill(); + } else { + context.stroke(); + } + } + }, + + /** + * Method: drawPolygon + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {<OpenLayers.Geometry>} + * style - {Object} + * featureId - {String} + */ + drawPolygon: function(geometry, style, featureId) { + var components = geometry.components; + var len = components.length; + this.drawLinearRing(components[0], style, featureId); + // erase inner rings + for (var i=1; i<len; ++i) { + /** + * Note that this is overly agressive. Here we punch holes through + * all previously rendered features on the same canvas. A better + * solution for polygons with interior rings would be to draw the + * polygon on a sketch canvas first. We could erase all holes + * there and then copy the drawing to the layer canvas. + * TODO: http://trac.osgeo.org/openlayers/ticket/3130 + */ + this.canvas.globalCompositeOperation = "destination-out"; + if (this.hitDetection) { + this.hitContext.globalCompositeOperation = "destination-out"; + } + this.drawLinearRing( + components[i], + OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style), + featureId + ); + this.canvas.globalCompositeOperation = "source-over"; + if (this.hitDetection) { + this.hitContext.globalCompositeOperation = "source-over"; + } + this.drawLinearRing( + components[i], + OpenLayers.Util.applyDefaults({fill: false}, style), + featureId + ); + } + }, + + /** + * Method: drawText + * This method is only called by the renderer itself. + * + * Parameters: + * location - {<OpenLayers.Point>} + * style - {Object} + */ + drawText: function(location, style) { + var pt = this.getLocalXY(location); + + this.setCanvasStyle("reset"); + this.canvas.fillStyle = style.fontColor; + this.canvas.globalAlpha = style.fontOpacity || 1.0; + var fontStyle = [style.fontStyle ? style.fontStyle : "normal", + "normal", // "font-variant" not supported + style.fontWeight ? style.fontWeight : "normal", + style.fontSize ? style.fontSize : "1em", + style.fontFamily ? style.fontFamily : "sans-serif"].join(" "); + var labelRows = style.label.split('\n'); + var numRows = labelRows.length; + if (this.canvas.fillText) { + // HTML5 + this.canvas.font = fontStyle; + this.canvas.textAlign = + OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] || + "center"; + this.canvas.textBaseline = + OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] || + "middle"; + var vfactor = + OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]]; + if (vfactor == null) { + vfactor = -.5; + } + var lineHeight = + this.canvas.measureText('Mg').height || + this.canvas.measureText('xx').width; + pt[1] += lineHeight*vfactor*(numRows-1); + for (var i = 0; i < numRows; i++) { + if (style.labelOutlineWidth) { + this.canvas.save(); + this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0; + this.canvas.strokeStyle = style.labelOutlineColor; + this.canvas.lineWidth = style.labelOutlineWidth; + this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1); + this.canvas.restore(); + } + this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i)); + } + } else if (this.canvas.mozDrawText) { + // Mozilla pre-Gecko1.9.1 (<FF3.1) + this.canvas.mozTextStyle = fontStyle; + // No built-in text alignment, so we measure and adjust the position + var hfactor = + OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]]; + if (hfactor == null) { + hfactor = -.5; + } + var vfactor = + OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]]; + if (vfactor == null) { + vfactor = -.5; + } + var lineHeight = this.canvas.mozMeasureText('xx'); + pt[1] += lineHeight*(1 + (vfactor*numRows)); + for (var i = 0; i < numRows; i++) { + var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i])); + var y = pt[1] + (i*lineHeight); + this.canvas.translate(x, y); + this.canvas.mozDrawText(labelRows[i]); + this.canvas.translate(-x, -y); + } + } + this.setCanvasStyle("reset"); + }, + + /** + * Method: getLocalXY + * transform geographic xy into pixel xy + * + * Parameters: + * point - {<OpenLayers.Geometry.Point>} + */ + getLocalXY: function(point) { + var resolution = this.getResolution(); + var extent = this.extent; + var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution)); + var y = ((extent.top / resolution) - point.y / resolution); + return [x, y]; + }, + + /** + * Method: clear + * Clear all vectors from the renderer. + */ + clear: function() { + var height = this.root.height; + var width = this.root.width; + this.canvas.clearRect(0, 0, width, height); + this.features = {}; + if (this.hitDetection) { + this.hitContext.clearRect(0, 0, width, height); + } + }, + + /** + * Method: getFeatureIdFromEvent + * Returns a feature id from an event on the renderer. + * + * Parameters: + * evt - {<OpenLayers.Event>} + * + * Returns: + * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a + * feature instead of a feature id to avoid an unnecessary lookup on the + * layer. + */ + getFeatureIdFromEvent: function(evt) { + var featureId, feature; + + if (this.hitDetection && this.root.style.display !== "none") { + // this dragging check should go in the feature handler + if (!this.map.dragging) { + var xy = evt.xy; + var x = xy.x | 0; + var y = xy.y | 0; + var data = this.hitContext.getImageData(x, y, 1, 1).data; + if (data[3] === 255) { // antialiased + var id = data[2] + (256 * (data[1] + (256 * data[0]))); + if (id) { + featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow); + try { + feature = this.features[featureId][0]; + } catch(err) { + // Because of antialiasing on the canvas, when the hit location is at a point where the edge of + // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results. + // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it. + } + } + } + } + } + return feature; + }, + + /** + * Method: eraseFeatures + * This is called by the layer to erase features; removes the feature from + * the list, then redraws the layer. + * + * Parameters: + * features - {Array(<OpenLayers.Feature.Vector>)} + */ + eraseFeatures: function(features) { + if(!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + for(var i=0; i<features.length; ++i) { + delete this.features[features[i].id]; + } + this.redraw(); + }, + + /** + * Method: redraw + * The real 'meat' of the function: any time things have changed, + * redraw() can be called to loop over all the data and (you guessed + * it) redraw it. Unlike Elements-based Renderers, we can't interact + * with things once they're drawn, to remove them, for example, so + * instead we have to just clear everything and draw from scratch. + */ + redraw: function() { + if (!this.locked) { + var height = this.root.height; + var width = this.root.width; + this.canvas.clearRect(0, 0, width, height); + if (this.hitDetection) { + this.hitContext.clearRect(0, 0, width, height); + } + var labelMap = []; + var feature, geometry, style; + var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent(); + for (var id in this.features) { + if (!this.features.hasOwnProperty(id)) { continue; } + feature = this.features[id][0]; + geometry = feature.geometry; + this.calculateFeatureDx(geometry.getBounds(), worldBounds); + style = this.features[id][1]; + this.drawGeometry(geometry, style, feature.id); + if(style.label) { + labelMap.push([feature, style]); + } + } + var item; + for (var i=0, len=labelMap.length; i<len; ++i) { + item = labelMap[i]; + this.drawText(item[0].geometry.getCentroid(), item[1]); + } + } + }, + + CLASS_NAME: "OpenLayers.Renderer.Canvas" +}); + +/** + * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN + * {Object} + */ +OpenLayers.Renderer.Canvas.LABEL_ALIGN = { + "l": "left", + "r": "right", + "t": "top", + "b": "bottom" +}; + +/** + * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR + * {Object} + */ +OpenLayers.Renderer.Canvas.LABEL_FACTOR = { + "l": 0, + "r": -1, + "t": 0, + "b": -1 +}; + +/** + * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor + * {Number} Scale factor to apply to the canvas drawImage arguments. This + * is always 1 except for Android 2.1 devices, to work around + * http://code.google.com/p/android/issues/detail?id=5141. + */ +OpenLayers.Renderer.Canvas.drawImageScaleFactor = null; |