]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Renderer/Canvas.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Renderer / Canvas.js
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. */
4
5 /**
6  * @requires OpenLayers/Renderer.js
7  */
8
9 /**
10  * Class: OpenLayers.Renderer.Canvas 
11  * A renderer based on the 2D 'canvas' drawing element.element
12  * 
13  * Inherits:
14  *  - <OpenLayers.Renderer>
15  */
16 OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
17
18     /**
19      * Property: canvas
20      * {Canvas} The canvas context object.
21      */
22     canvas: null, 
23     
24     /**
25      * Property: features
26      * {Object} Internal object of feature/style pairs for use in redrawing the layer.
27      */
28     features: null, 
29    
30     /**
31      * Property: geometryMap
32      * {Object} Geometry -> Feature lookup table. Used by eraseGeometry to
33      *     lookup features to remove from our internal table (this.features)
34      *     when erasing geoms.
35      */
36     geometryMap: null,
37  
38     /**
39      * Constructor: OpenLayers.Renderer.Canvas
40      *
41      * Parameters:
42      * containerID - {<String>} 
43      */
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");
49         this.features = {};
50         this.geometryMap = {};
51     },
52     
53     /** 
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.   
58      * 
59      * Parameters:
60      * geometry - {<OpenLayers.Geometry>}
61      */
62     eraseGeometry: function(geometry) {
63         this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);
64     },
65
66     /**
67      * APIMethod: supported
68      * 
69      * Returns:
70      * {Boolean} Whether or not the browser supports the renderer class
71      */
72     supported: function() {
73         var canvas = document.createElement("canvas");
74         return !!canvas.getContext;
75     },    
76     
77     /**
78      * Method: setExtent
79      * Set the visible part of the layer.
80      *
81      * Resolution has probably changed, so we nullify the resolution 
82      * cache (this.resolution), then redraw. 
83      *
84      * Parameters:
85      * extent - {<OpenLayers.Bounds>} 
86      */
87     setExtent: function(extent) {
88         this.extent = extent.clone();
89         this.resolution = null;
90         this.redraw();
91     },
92     
93     /**
94      * Method: setSize
95      * Sets the size of the drawing surface.
96      *
97      * Once the size is updated, redraw the canvas.
98      *
99      * Parameters:
100      * size - {<OpenLayers.Size>} 
101      */
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;
109     },
110     
111     /**
112      * Method: drawFeature
113      * Draw the feature. Stores the feature in the features list,
114      * then redraws the layer. 
115      *
116      * Parameters:
117      * feature - {<OpenLayers.Feature.Vector>} 
118      * style - {<Object>} 
119      */
120     drawFeature: function(feature, style) {
121         if(style == null) {
122             style = feature.style;
123         }
124         style = OpenLayers.Util.extend({
125           'fillColor': '#000000',
126           'strokeColor': '#000000',
127           'strokeWidth': 2,
128           'fillOpacity': 1,
129           'strokeOpacity': 1
130         }, style);  
131         this.features[feature.id] = [feature, style]; 
132         if (feature.geometry) { 
133             this.geometryMap[feature.geometry.id] = feature.id;
134         }    
135         this.redraw();
136     },
137
138
139     /** 
140      * Method: drawGeometry
141      * Used when looping (in redraw) over the features; draws
142      * the canvas. 
143      *
144      * Parameters:
145      * geometry - {<OpenLayers.Geometry>} 
146      * style - {Object} 
147      */
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);
156             }
157             return;
158         };
159         switch (geometry.CLASS_NAME) {
160             case "OpenLayers.Geometry.Point":
161                 this.drawPoint(geometry, style);
162                 break;
163             case "OpenLayers.Geometry.LineString":
164                 this.drawLineString(geometry, style);
165                 break;
166             case "OpenLayers.Geometry.LinearRing":
167                 this.drawLinearRing(geometry, style);
168                 break;
169             case "OpenLayers.Geometry.Polygon":
170                 this.drawPolygon(geometry, style);
171                 break;
172             default:
173                 break;
174         }
175     },
176
177     /**
178      * Method: drawExternalGraphic
179      * Called to draw External graphics. 
180      * 
181      * Parameters: 
182      * geometry - {<OpenLayers.Geometry>}
183      * style    - {Object}
184      */ 
185     drawExternalGraphic: function(pt, style) {
186        var img = new Image();
187        img.src = style.externalGraphic;
188        
189        if(style.graphicTitle) {
190            img.title=style.graphicTitle;           
191        }
192
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;
202        
203        var context = { img: img, 
204                        x: (pt[0]+xOffset), 
205                        y: (pt[1]+yOffset), 
206                        width: width, 
207                        height: height, 
208                        canvas: this.canvas };
209
210        img.onload = OpenLayers.Function.bind( function() {
211            this.canvas.drawImage(this.img, this.x, 
212                                  this.y, this.width, this.height);
213        }, context);   
214     },
215
216     /**
217      * Method: setCanvasStyle
218      * Prepare the canvas for drawing by setting various global settings.
219      *
220      * Parameters:
221      * type - {String} one of 'stroke', 'fill', or 'reset'
222      * style - {Object} Symbolizer hash
223      */
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'];
232         } else {
233             this.canvas.globalAlpha = 0;
234             this.canvas.lineWidth = 1;
235         }
236     },
237
238     /**
239      * Method: drawPoint
240      * This method is only called by the renderer itself.
241      * 
242      * Parameters: 
243      * geometry - {<OpenLayers.Geometry>}
244      * style    - {Object}
245      */ 
246     drawPoint: function(geometry, style) {
247         if(style.graphic !== false) {
248             var pt = this.getLocalXY(geometry);
249             
250             if (style.externalGraphic) {
251                 this.drawExternalGraphic(pt, style);
252             } else {
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);
257                     this.canvas.fill();
258                 }
259                 
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");
266                 }
267             }
268         }
269     },
270
271     /**
272      * Method: drawLineString
273      * This method is only called by the renderer itself.
274      * 
275      * Parameters: 
276      * geometry - {<OpenLayers.Geometry>}
277      * style    - {Object}
278      */ 
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]);
288             }
289             this.canvas.stroke();
290         }
291         this.setCanvasStyle("reset");
292     },    
293     
294     /**
295      * Method: drawLinearRing
296      * This method is only called by the renderer itself.
297      * 
298      * Parameters: 
299      * geometry - {<OpenLayers.Geometry>}
300      * style    - {Object}
301      */ 
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]);
311             }
312             this.canvas.fill();
313         }
314         
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]);
324             }
325             this.canvas.stroke();
326         }
327         this.setCanvasStyle("reset");
328     },    
329     
330     /**
331      * Method: drawPolygon
332      * This method is only called by the renderer itself.
333      * 
334      * Parameters: 
335      * geometry - {<OpenLayers.Geometry>}
336      * style    - {Object}
337      */ 
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], {
342                 fillOpacity: 0, 
343                 strokeWidth: 0, 
344                 strokeOpacity: 0, 
345                 strokeColor: '#000000', 
346                 fillColor: '#000000'}
347             ); // inner rings are 'empty'  
348         }
349     },
350     
351     /**
352      * Method: drawText
353      * This method is only called by the renderer itself.
354      *
355      * Parameters:
356      * location - {<OpenLayers.Point>}
357      * style    - {Object}
358      */
359     drawText: function(location, style) {
360         style = OpenLayers.Util.extend({
361             fontColor: "#000000",
362             labelAlign: "cm"
363         }, style);
364         var pt = this.getLocalXY(location);
365         
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) {
371             // HTML5
372             var labelAlign =
373                 OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
374                 "middle";
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]) {
384                 case "l":
385                     break;
386                 case "r":
387                     pt[0] -= len;
388                     break;
389                 case "c":
390                 default:
391                     pt[0] -= len / 2;
392             }
393             this.canvas.translate(pt[0], pt[1]);
394             
395             this.canvas.mozDrawText(style.label);
396             this.canvas.translate(-1*pt[0], -1*pt[1]);
397         }
398         this.setCanvasStyle("reset");
399     },
400
401     /**
402      * Method: getLocalXY
403      * transform geographic xy into pixel xy
404      *
405      * Parameters: 
406      * point - {<OpenLayers.Geometry.Point>}
407      */
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);
413         return [x, y];
414     },
415         
416     /**
417      * Method: clear
418      * Clear all vectors from the renderer.
419      * virtual function.
420      */    
421     clear: function() {
422         this.canvas.clearRect(0, 0, this.root.width, this.root.height);
423     },
424
425     /**
426      * Method: getFeatureIdFromEvent
427      * Returns a feature id from an event on the renderer.  
428      * 
429      * Parameters:
430      * evt - {<OpenLayers.Event>} 
431      *
432      * Returns:
433      * {String} A feature id or null.
434      */
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)) {
446                 return feat;
447             }
448         }   
449         return null;
450     },
451     
452     /**
453      * Method: eraseFeatures 
454      * This is called by the layer to erase features; removes the feature from
455      *     the list, then redraws the layer.
456      * 
457      * Parameters:
458      * features - {Array(<OpenLayers.Feature.Vector>)} 
459      */
460     eraseFeatures: function(features) {
461         if(!(features instanceof Array)) {
462             features = [features];
463         }
464         for(var i=0; i<features.length; ++i) {
465             delete this.features[features[i].id];
466         }
467         this.redraw();
468     },
469
470     /**
471      * Method: redraw
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.
477      */
478     redraw: function() {
479         if (!this.locked) {
480             this.clear();
481             var labelMap = [];
482             var feature, style;
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);
489                 if(style.label) {
490                     labelMap.push([feature, style]);
491                 }
492             }
493             var item;
494             for (var i=0; len=labelMap.length, i<len; ++i) {
495                 item = labelMap[i];
496                 this.drawText(item[0].geometry.getCentroid(), item[1]);
497             }
498         }    
499     },
500
501     CLASS_NAME: "OpenLayers.Renderer.Canvas"
502 });
503
504 /**
505  * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
506  * {Object}
507  */
508 OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
509     "l": "left",
510     "r": "right"
511 };