summaryrefslogtreecommitdiff
path: root/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js
diff options
context:
space:
mode:
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Renderer/Canvas.js')
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer/Canvas.js906
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;