--- a/labs/openlayers/lib/OpenLayers/Renderer/Canvas.js +++ b/labs/openlayers/lib/OpenLayers/Renderer/Canvas.js @@ -1,1 +1,495 @@ - +/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Renderer.js + */ + +/** + * Class: OpenLayers.Renderer.Canvas + * A renderer based on the 2D 'canvas' drawing element.element + * + * Inherits: + * - + */ +OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { + + /** + * 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, + + /** + * Constructor: OpenLayers.Renderer.Canvas + * + * Parameters: + * containerID - {} + */ + initialize: function(containerID) { + 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 = {}; + }, + + /** + * 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 - {} + * 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() { + var canvas = document.createElement("canvas"); + return !!canvas.getContext; + }, + + /** + * Method: setExtent + * Set the visible part of the layer. + * + * Resolution has probably changed, so we nullify the resolution + * cache (this.resolution), then redraw. + * + * Parameters: + * extent - {} + */ + setExtent: function(extent) { + this.extent = extent.clone(); + this.resolution = null; + this.redraw(); + }, + + /** + * Method: setSize + * Sets the size of the drawing surface. + * + * Once the size is updated, redraw the canvas. + * + * Parameters: + * size - {} + */ + setSize: function(size) { + this.size = size.clone(); + this.root.style.width = size.w + "px"; + this.root.style.height = size.h + "px"; + this.root.width = size.w; + this.root.height = size.h; + this.resolution = null; + }, + + /** + * Method: drawFeature + * Draw the feature. Stores the feature in the features list, + * then redraws the layer. + * + * Parameters: + * feature - {} + * style - {} + */ + drawFeature: function(feature, style) { + style = style || feature.style; + style = this.applyDefaultSymbolizer(style); + + this.features[feature.id] = [feature, style]; + this.redraw(); + }, + + + /** + * Method: drawGeometry + * Used when looping (in redraw) over the features; draws + * the canvas. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawGeometry: function(geometry, style) { + 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); + } + return; + } + switch (geometry.CLASS_NAME) { + case "OpenLayers.Geometry.Point": + this.drawPoint(geometry, style); + break; + case "OpenLayers.Geometry.LineString": + this.drawLineString(geometry, style); + break; + case "OpenLayers.Geometry.LinearRing": + this.drawLinearRing(geometry, style); + break; + case "OpenLayers.Geometry.Polygon": + this.drawPolygon(geometry, style); + break; + default: + break; + } + }, + + /** + * Method: drawExternalGraphic + * Called to draw External graphics. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawExternalGraphic: function(pt, style) { + var img = new Image(); + + if(style.graphicTitle) { + img.title=style.graphicTitle; + } + + 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 context = { img: img, + x: (pt[0]+xOffset), + y: (pt[1]+yOffset), + width: width, + height: height, + opacity: style.graphicOpacity || style.fillOpacity, + canvas: this.canvas }; + + img.onload = OpenLayers.Function.bind( function() { + this.canvas.globalAlpha = this.opacity; + this.canvas.drawImage(this.img, this.x, + this.y, this.width, this.height); + }, context); + img.src = style.externalGraphic; + }, + + /** + * 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: drawPoint + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawPoint: function(geometry, style) { + if(style.graphic !== false) { + var pt = this.getLocalXY(geometry); + + if (style.externalGraphic) { + this.drawExternalGraphic(pt, style); + } else { + if(style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true); + this.canvas.fill(); + } + + if(style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true); + this.canvas.stroke(); + this.setCanvasStyle("reset"); + } + } + } + }, + + /** + * Method: drawLineString + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawLineString: function(geometry, style) { + if(style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + var start = this.getLocalXY(geometry.components[0]); + this.canvas.moveTo(start[0], start[1]); + for(var i = 1; i < geometry.components.length; i++) { + var pt = this.getLocalXY(geometry.components[i]); + this.canvas.lineTo(pt[0], pt[1]); + } + this.canvas.stroke(); + } + this.setCanvasStyle("reset"); + }, + + /** + * Method: drawLinearRing + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawLinearRing: function(geometry, style) { + if(style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + var start = this.getLocalXY(geometry.components[0]); + this.canvas.moveTo(start[0], start[1]); + for(var i = 1; i < geometry.components.length - 1 ; i++) { + var pt = this.getLocalXY(geometry.components[i]); + this.canvas.lineTo(pt[0], pt[1]); + } + this.canvas.fill(); + } + + if(style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + var start = this.getLocalXY(geometry.components[0]); + this.canvas.moveTo(start[0], start[1]); + for(var i = 1; i < geometry.components.length; i++) { + var pt = this.getLocalXY(geometry.components[i]); + this.canvas.lineTo(pt[0], pt[1]); + } + this.canvas.stroke(); + } + this.setCanvasStyle("reset"); + }, + + /** + * Method: drawPolygon + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawPolygon: function(geometry, style) { + this.drawLinearRing(geometry.components[0], style); + for (var i = 1; i < geometry.components.length; i++) { + this.drawLinearRing(geometry.components[i], { + fillOpacity: 0, + strokeWidth: 0, + strokeOpacity: 0, + strokeColor: '#000000', + fillColor: '#000000'} + ); // inner rings are 'empty' + } + }, + + /** + * Method: drawText + * This method is only called by the renderer itself. + * + * Parameters: + * location - {} + * style - {Object} + */ + drawText: function(location, style) { + style = OpenLayers.Util.extend({ + fontColor: "#000000", + labelAlign: "cm" + }, style); + var pt = this.getLocalXY(location); + + this.setCanvasStyle("reset"); + this.canvas.fillStyle = style.fontColor; + this.canvas.globalAlpha = style.fontOpacity || 1.0; + var fontStyle = style.fontWeight + " " + style.fontSize + " " + style.fontFamily; + if (this.canvas.fillText) { + // HTML5 + var labelAlign = + OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] || + "center"; + this.canvas.font = fontStyle; + this.canvas.textAlign = labelAlign; + this.canvas.fillText(style.label, pt[0], pt[1]); + } else if (this.canvas.mozDrawText) { + // Mozilla pre-Gecko1.9.1 (} + */ + getLocalXY: function(point) { + var resolution = this.getResolution(); + var extent = this.extent; + var x = (point.x / 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() { + this.canvas.clearRect(0, 0, this.root.width, this.root.height); + this.features = {}; + }, + + /** + * Method: getFeatureIdFromEvent + * Returns a feature id from an event on the renderer. + * + * Parameters: + * evt - {} + * + * Returns: + * {String} A feature id or null. + */ + getFeatureIdFromEvent: function(evt) { + var loc = this.map.getLonLatFromPixel(evt.xy); + var resolution = this.getResolution(); + var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5, + loc.lat - resolution * 5, + loc.lon + resolution * 5, + loc.lat + resolution * 5); + var geom = bounds.toGeometry(); + for (var feat in this.features) { + if (!this.features.hasOwnProperty(feat)) { continue; } + if (this.features[feat][0].geometry.intersects(geom)) { + return feat; + } + } + return null; + }, + + /** + * Method: eraseFeatures + * This is called by the layer to erase features; removes the feature from + * the list, then redraws the layer. + * + * Parameters: + * features - {Array()} + */ + eraseFeatures: function(features) { + if(!(features instanceof Array)) { + features = [features]; + } + for(var i=0; i