1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Renderer.js
10 * Class: OpenLayers.Renderer.Canvas
11 * A renderer based on the 2D 'canvas' drawing element.element
14 * - <OpenLayers.Renderer>
16 OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
20 * {Canvas} The canvas context object.
26 * {Object} Internal object of feature/style pairs for use in redrawing the layer.
31 * Property: geometryMap
32 * {Object} Geometry -> Feature lookup table. Used by eraseGeometry to
33 * lookup features to remove from our internal table (this.features)
39 * Constructor: OpenLayers.Renderer.Canvas
42 * containerID - {<String>}
44 initialize: function(containerID) {
45 OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
46 this.root = document.createElement("canvas");
47 this.container.appendChild(this.root);
48 this.canvas = this.root.getContext("2d");
50 this.geometryMap = {};
54 * Method: eraseGeometry
55 * Erase a geometry from the renderer. Because the Canvas renderer has
56 * 'memory' of the features that it has drawn, we have to remove the
57 * feature so it doesn't redraw.
60 * geometry - {<OpenLayers.Geometry>}
62 eraseGeometry: function(geometry) {
63 this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);
67 * APIMethod: supported
70 * {Boolean} Whether or not the browser supports the renderer class
72 supported: function() {
73 var canvas = document.createElement("canvas");
74 return !!canvas.getContext;
79 * Set the visible part of the layer.
81 * Resolution has probably changed, so we nullify the resolution
82 * cache (this.resolution), then redraw.
85 * extent - {<OpenLayers.Bounds>}
87 setExtent: function(extent) {
88 this.extent = extent.clone();
89 this.resolution = null;
95 * Sets the size of the drawing surface.
97 * Once the size is updated, redraw the canvas.
100 * size - {<OpenLayers.Size>}
102 setSize: function(size) {
103 this.size = size.clone();
104 this.root.style.width = size.w + "px";
105 this.root.style.height = size.h + "px";
106 this.root.width = size.w;
107 this.root.height = size.h;
108 this.resolution = null;
112 * Method: drawFeature
113 * Draw the feature. Stores the feature in the features list,
114 * then redraws the layer.
117 * feature - {<OpenLayers.Feature.Vector>}
120 drawFeature: function(feature, style) {
122 style = feature.style;
124 style = OpenLayers.Util.extend({
125 'fillColor': '#000000',
126 'strokeColor': '#000000',
131 this.features[feature.id] = [feature, style];
132 if (feature.geometry) {
133 this.geometryMap[feature.geometry.id] = feature.id;
140 * Method: drawGeometry
141 * Used when looping (in redraw) over the features; draws
145 * geometry - {<OpenLayers.Geometry>}
148 drawGeometry: function(geometry, style) {
149 var className = geometry.CLASS_NAME;
150 if ((className == "OpenLayers.Geometry.Collection") ||
151 (className == "OpenLayers.Geometry.MultiPoint") ||
152 (className == "OpenLayers.Geometry.MultiLineString") ||
153 (className == "OpenLayers.Geometry.MultiPolygon")) {
154 for (var i = 0; i < geometry.components.length; i++) {
155 this.drawGeometry(geometry.components[i], style);
159 switch (geometry.CLASS_NAME) {
160 case "OpenLayers.Geometry.Point":
161 this.drawPoint(geometry, style);
163 case "OpenLayers.Geometry.LineString":
164 this.drawLineString(geometry, style);
166 case "OpenLayers.Geometry.LinearRing":
167 this.drawLinearRing(geometry, style);
169 case "OpenLayers.Geometry.Polygon":
170 this.drawPolygon(geometry, style);
178 * Method: drawExternalGraphic
179 * Called to draw External graphics.
182 * geometry - {<OpenLayers.Geometry>}
185 drawExternalGraphic: function(pt, style) {
186 var img = new Image();
187 img.src = style.externalGraphic;
189 if(style.graphicTitle) {
190 img.title=style.graphicTitle;
193 var width = style.graphicWidth || style.graphicHeight;
194 var height = style.graphicHeight || style.graphicWidth;
195 width = width ? width : style.pointRadius*2;
196 height = height ? height : style.pointRadius*2;
197 var xOffset = (style.graphicXOffset != undefined) ?
198 style.graphicXOffset : -(0.5 * width);
199 var yOffset = (style.graphicYOffset != undefined) ?
200 style.graphicYOffset : -(0.5 * height);
201 var opacity = style.graphicOpacity || style.fillOpacity;
203 var context = { img: img,
208 canvas: this.canvas };
210 img.onload = OpenLayers.Function.bind( function() {
211 this.canvas.drawImage(this.img, this.x,
212 this.y, this.width, this.height);
217 * Method: setCanvasStyle
218 * Prepare the canvas for drawing by setting various global settings.
221 * type - {String} one of 'stroke', 'fill', or 'reset'
222 * style - {Object} Symbolizer hash
224 setCanvasStyle: function(type, style) {
225 if (type == "fill") {
226 this.canvas.globalAlpha = style['fillOpacity'];
227 this.canvas.fillStyle = style['fillColor'];
228 } else if (type == "stroke") {
229 this.canvas.globalAlpha = style['strokeOpacity'];
230 this.canvas.strokeStyle = style['strokeColor'];
231 this.canvas.lineWidth = style['strokeWidth'];
233 this.canvas.globalAlpha = 0;
234 this.canvas.lineWidth = 1;
240 * This method is only called by the renderer itself.
243 * geometry - {<OpenLayers.Geometry>}
246 drawPoint: function(geometry, style) {
247 if(style.graphic !== false) {
248 var pt = this.getLocalXY(geometry);
250 if (style.externalGraphic) {
251 this.drawExternalGraphic(pt, style);
253 if(style.fill !== false) {
254 this.setCanvasStyle("fill", style);
255 this.canvas.beginPath();
256 this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
260 if(style.stroke !== false) {
261 this.setCanvasStyle("stroke", style);
262 this.canvas.beginPath();
263 this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
264 this.canvas.stroke();
265 this.setCanvasStyle("reset");
272 * Method: drawLineString
273 * This method is only called by the renderer itself.
276 * geometry - {<OpenLayers.Geometry>}
279 drawLineString: function(geometry, style) {
280 if(style.stroke !== false) {
281 this.setCanvasStyle("stroke", style);
282 this.canvas.beginPath();
283 var start = this.getLocalXY(geometry.components[0]);
284 this.canvas.moveTo(start[0], start[1]);
285 for(var i = 1; i < geometry.components.length; i++) {
286 var pt = this.getLocalXY(geometry.components[i]);
287 this.canvas.lineTo(pt[0], pt[1]);
289 this.canvas.stroke();
291 this.setCanvasStyle("reset");
295 * Method: drawLinearRing
296 * This method is only called by the renderer itself.
299 * geometry - {<OpenLayers.Geometry>}
302 drawLinearRing: function(geometry, style) {
303 if(style.fill !== false) {
304 this.setCanvasStyle("fill", style);
305 this.canvas.beginPath();
306 var start = this.getLocalXY(geometry.components[0]);
307 this.canvas.moveTo(start[0], start[1]);
308 for(var i = 1; i < geometry.components.length - 1 ; i++) {
309 var pt = this.getLocalXY(geometry.components[i]);
310 this.canvas.lineTo(pt[0], pt[1]);
315 if(style.stroke !== false) {
316 var oldWidth = this.canvas.lineWidth;
317 this.setCanvasStyle("stroke", style);
318 this.canvas.beginPath();
319 var start = this.getLocalXY(geometry.components[0]);
320 this.canvas.moveTo(start[0], start[1]);
321 for(var i = 1; i < geometry.components.length; i++) {
322 var pt = this.getLocalXY(geometry.components[i]);
323 this.canvas.lineTo(pt[0], pt[1]);
325 this.canvas.stroke();
327 this.setCanvasStyle("reset");
331 * Method: drawPolygon
332 * This method is only called by the renderer itself.
335 * geometry - {<OpenLayers.Geometry>}
338 drawPolygon: function(geometry, style) {
339 this.drawLinearRing(geometry.components[0], style);
340 for (var i = 1; i < geometry.components.length; i++) {
341 this.drawLinearRing(geometry.components[i], {
345 strokeColor: '#000000',
346 fillColor: '#000000'}
347 ); // inner rings are 'empty'
353 * This method is only called by the renderer itself.
356 * location - {<OpenLayers.Point>}
359 drawText: function(location, style) {
360 style = OpenLayers.Util.extend({
361 fontColor: "#000000",
364 var pt = this.getLocalXY(location);
366 this.setCanvasStyle("reset");
367 this.canvas.fillStyle = style.fontColor;
368 this.canvas.globalAlpha = 1;
369 var fontStyle = style.fontWeight + " " + style.fontSize + " " + style.fontFamily;
370 if (this.canvas.fillText) {
373 OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
375 this.canvas.font = fontStyle;
376 this.canvas.textAlign = labelAlign;
377 this.canvas.fillText(style.label, pt[0], pt[1]);
378 } else if (this.canvas.mozDrawText) {
379 // Mozilla pre-Gecko1.9.1 (<FF3.1)
380 this.canvas.mozTextStyle = fontStyle;
381 // No built-in text alignment, so we measure and adjust the position
382 var len = this.canvas.mozMeasureText(style.label);
383 switch(style.labelAlign[0]) {
393 this.canvas.translate(pt[0], pt[1]);
395 this.canvas.mozDrawText(style.label);
396 this.canvas.translate(-1*pt[0], -1*pt[1]);
398 this.setCanvasStyle("reset");
403 * transform geographic xy into pixel xy
406 * point - {<OpenLayers.Geometry.Point>}
408 getLocalXY: function(point) {
409 var resolution = this.getResolution();
410 var extent = this.extent;
411 var x = (point.x / resolution + (-extent.left / resolution));
412 var y = ((extent.top / resolution) - point.y / resolution);
418 * Clear all vectors from the renderer.
422 this.canvas.clearRect(0, 0, this.root.width, this.root.height);
426 * Method: getFeatureIdFromEvent
427 * Returns a feature id from an event on the renderer.
430 * evt - {<OpenLayers.Event>}
433 * {String} A feature id or null.
435 getFeatureIdFromEvent: function(evt) {
436 var loc = this.map.getLonLatFromPixel(evt.xy);
437 var resolution = this.getResolution();
438 var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5,
439 loc.lat - resolution * 5,
440 loc.lon + resolution * 5,
441 loc.lat + resolution * 5);
442 var geom = bounds.toGeometry();
443 for (var feat in this.features) {
444 if (!this.features.hasOwnProperty(feat)) { continue; }
445 if (this.features[feat][0].geometry.intersects(geom)) {
453 * Method: eraseFeatures
454 * This is called by the layer to erase features; removes the feature from
455 * the list, then redraws the layer.
458 * features - {Array(<OpenLayers.Feature.Vector>)}
460 eraseFeatures: function(features) {
461 if(!(features instanceof Array)) {
462 features = [features];
464 for(var i=0; i<features.length; ++i) {
465 delete this.features[features[i].id];
472 * The real 'meat' of the function: any time things have changed,
473 * redraw() can be called to loop over all the data and (you guessed
474 * it) redraw it. Unlike Elements-based Renderers, we can't interact
475 * with things once they're drawn, to remove them, for example, so
476 * instead we have to just clear everything and draw from scratch.
483 for (var id in this.features) {
484 if (!this.features.hasOwnProperty(id)) { continue; }
485 feature = this.features[id][0];
486 style = this.features[id][1];
487 if (!feature.geometry) { continue; }
488 this.drawGeometry(feature.geometry, style);
490 labelMap.push([feature, style]);
494 for (var i=0; len=labelMap.length, i<len; ++i) {
496 this.drawText(item[0].geometry.getCentroid(), item[1]);
501 CLASS_NAME: "OpenLayers.Renderer.Canvas"
505 * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
508 OpenLayers.Renderer.Canvas.LABEL_ALIGN = {