1 | /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
---|
2 | * full list of contributors). Published under the Clear BSD license. |
---|
3 | * See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
---|
4 | * full text of the license. */ |
---|
5 | |
---|
6 | /** |
---|
7 | * @requires OpenLayers/Renderer.js |
---|
8 | */ |
---|
9 | |
---|
10 | /** |
---|
11 | * Class: OpenLayers.Renderer.Canvas |
---|
12 | * A renderer based on the 2D 'canvas' drawing element.element |
---|
13 | * |
---|
14 | * Inherits: |
---|
15 | * - <OpenLayers.Renderer> |
---|
16 | */ |
---|
17 | OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { |
---|
18 | |
---|
19 | /** |
---|
20 | * Property: canvas |
---|
21 | * {Canvas} The canvas context object. |
---|
22 | */ |
---|
23 | canvas: null, |
---|
24 | |
---|
25 | /** |
---|
26 | * Property: features |
---|
27 | * {Object} Internal object of feature/style pairs for use in redrawing the layer. |
---|
28 | */ |
---|
29 | features: null, |
---|
30 | |
---|
31 | /** |
---|
32 | * Constructor: OpenLayers.Renderer.Canvas |
---|
33 | * |
---|
34 | * Parameters: |
---|
35 | * containerID - {<String>} |
---|
36 | */ |
---|
37 | initialize: function(containerID) { |
---|
38 | OpenLayers.Renderer.prototype.initialize.apply(this, arguments); |
---|
39 | this.root = document.createElement("canvas"); |
---|
40 | this.container.appendChild(this.root); |
---|
41 | this.canvas = this.root.getContext("2d"); |
---|
42 | this.features = {}; |
---|
43 | }, |
---|
44 | |
---|
45 | /** |
---|
46 | * Method: eraseGeometry |
---|
47 | * Erase a geometry from the renderer. Because the Canvas renderer has |
---|
48 | * 'memory' of the features that it has drawn, we have to remove the |
---|
49 | * feature so it doesn't redraw. |
---|
50 | * |
---|
51 | * Parameters: |
---|
52 | * geometry - {<OpenLayers.Geometry>} |
---|
53 | * featureId - {String} |
---|
54 | */ |
---|
55 | eraseGeometry: function(geometry, featureId) { |
---|
56 | this.eraseFeatures(this.features[featureId][0]); |
---|
57 | }, |
---|
58 | |
---|
59 | /** |
---|
60 | * APIMethod: supported |
---|
61 | * |
---|
62 | * Returns: |
---|
63 | * {Boolean} Whether or not the browser supports the renderer class |
---|
64 | */ |
---|
65 | supported: function() { |
---|
66 | var canvas = document.createElement("canvas"); |
---|
67 | return !!canvas.getContext; |
---|
68 | }, |
---|
69 | |
---|
70 | /** |
---|
71 | * Method: setExtent |
---|
72 | * Set the visible part of the layer. |
---|
73 | * |
---|
74 | * Resolution has probably changed, so we nullify the resolution |
---|
75 | * cache (this.resolution), then redraw. |
---|
76 | * |
---|
77 | * Parameters: |
---|
78 | * extent - {<OpenLayers.Bounds>} |
---|
79 | */ |
---|
80 | setExtent: function(extent) { |
---|
81 | this.extent = extent.clone(); |
---|
82 | this.resolution = null; |
---|
83 | this.redraw(); |
---|
84 | }, |
---|
85 | |
---|
86 | /** |
---|
87 | * Method: setSize |
---|
88 | * Sets the size of the drawing surface. |
---|
89 | * |
---|
90 | * Once the size is updated, redraw the canvas. |
---|
91 | * |
---|
92 | * Parameters: |
---|
93 | * size - {<OpenLayers.Size>} |
---|
94 | */ |
---|
95 | setSize: function(size) { |
---|
96 | this.size = size.clone(); |
---|
97 | this.root.style.width = size.w + "px"; |
---|
98 | this.root.style.height = size.h + "px"; |
---|
99 | this.root.width = size.w; |
---|
100 | this.root.height = size.h; |
---|
101 | this.resolution = null; |
---|
102 | }, |
---|
103 | |
---|
104 | /** |
---|
105 | * Method: drawFeature |
---|
106 | * Draw the feature. Stores the feature in the features list, |
---|
107 | * then redraws the layer. |
---|
108 | * |
---|
109 | * Parameters: |
---|
110 | * feature - {<OpenLayers.Feature.Vector>} |
---|
111 | * style - {<Object>} |
---|
112 | */ |
---|
113 | drawFeature: function(feature, style) { |
---|
114 | style = style || feature.style; |
---|
115 | style = this.applyDefaultSymbolizer(style); |
---|
116 | |
---|
117 | this.features[feature.id] = [feature, style]; |
---|
118 | this.redraw(); |
---|
119 | }, |
---|
120 | |
---|
121 | |
---|
122 | /** |
---|
123 | * Method: drawGeometry |
---|
124 | * Used when looping (in redraw) over the features; draws |
---|
125 | * the canvas. |
---|
126 | * |
---|
127 | * Parameters: |
---|
128 | * geometry - {<OpenLayers.Geometry>} |
---|
129 | * style - {Object} |
---|
130 | */ |
---|
131 | drawGeometry: function(geometry, style) { |
---|
132 | var className = geometry.CLASS_NAME; |
---|
133 | if ((className == "OpenLayers.Geometry.Collection") || |
---|
134 | (className == "OpenLayers.Geometry.MultiPoint") || |
---|
135 | (className == "OpenLayers.Geometry.MultiLineString") || |
---|
136 | (className == "OpenLayers.Geometry.MultiPolygon")) { |
---|
137 | for (var i = 0; i < geometry.components.length; i++) { |
---|
138 | this.drawGeometry(geometry.components[i], style); |
---|
139 | } |
---|
140 | return; |
---|
141 | } |
---|
142 | switch (geometry.CLASS_NAME) { |
---|
143 | case "OpenLayers.Geometry.Point": |
---|
144 | this.drawPoint(geometry, style); |
---|
145 | break; |
---|
146 | case "OpenLayers.Geometry.LineString": |
---|
147 | this.drawLineString(geometry, style); |
---|
148 | break; |
---|
149 | case "OpenLayers.Geometry.LinearRing": |
---|
150 | this.drawLinearRing(geometry, style); |
---|
151 | break; |
---|
152 | case "OpenLayers.Geometry.Polygon": |
---|
153 | this.drawPolygon(geometry, style); |
---|
154 | break; |
---|
155 | default: |
---|
156 | break; |
---|
157 | } |
---|
158 | }, |
---|
159 | |
---|
160 | /** |
---|
161 | * Method: drawExternalGraphic |
---|
162 | * Called to draw External graphics. |
---|
163 | * |
---|
164 | * Parameters: |
---|
165 | * geometry - {<OpenLayers.Geometry>} |
---|
166 | * style - {Object} |
---|
167 | */ |
---|
168 | drawExternalGraphic: function(pt, style) { |
---|
169 | var img = new Image(); |
---|
170 | |
---|
171 | if(style.graphicTitle) { |
---|
172 | img.title=style.graphicTitle; |
---|
173 | } |
---|
174 | |
---|
175 | var width = style.graphicWidth || style.graphicHeight; |
---|
176 | var height = style.graphicHeight || style.graphicWidth; |
---|
177 | width = width ? width : style.pointRadius*2; |
---|
178 | height = height ? height : style.pointRadius*2; |
---|
179 | var xOffset = (style.graphicXOffset != undefined) ? |
---|
180 | style.graphicXOffset : -(0.5 * width); |
---|
181 | var yOffset = (style.graphicYOffset != undefined) ? |
---|
182 | style.graphicYOffset : -(0.5 * height); |
---|
183 | |
---|
184 | var context = { img: img, |
---|
185 | x: (pt[0]+xOffset), |
---|
186 | y: (pt[1]+yOffset), |
---|
187 | width: width, |
---|
188 | height: height, |
---|
189 | opacity: style.graphicOpacity || style.fillOpacity, |
---|
190 | canvas: this.canvas }; |
---|
191 | |
---|
192 | img.onload = OpenLayers.Function.bind( function() { |
---|
193 | this.canvas.globalAlpha = this.opacity; |
---|
194 | this.canvas.drawImage(this.img, this.x, |
---|
195 | this.y, this.width, this.height); |
---|
196 | }, context); |
---|
197 | img.src = style.externalGraphic; |
---|
198 | }, |
---|
199 | |
---|
200 | /** |
---|
201 | * Method: setCanvasStyle |
---|
202 | * Prepare the canvas for drawing by setting various global settings. |
---|
203 | * |
---|
204 | * Parameters: |
---|
205 | * type - {String} one of 'stroke', 'fill', or 'reset' |
---|
206 | * style - {Object} Symbolizer hash |
---|
207 | */ |
---|
208 | setCanvasStyle: function(type, style) { |
---|
209 | if (type == "fill") { |
---|
210 | this.canvas.globalAlpha = style['fillOpacity']; |
---|
211 | this.canvas.fillStyle = style['fillColor']; |
---|
212 | } else if (type == "stroke") { |
---|
213 | this.canvas.globalAlpha = style['strokeOpacity']; |
---|
214 | this.canvas.strokeStyle = style['strokeColor']; |
---|
215 | this.canvas.lineWidth = style['strokeWidth']; |
---|
216 | } else { |
---|
217 | this.canvas.globalAlpha = 0; |
---|
218 | this.canvas.lineWidth = 1; |
---|
219 | } |
---|
220 | }, |
---|
221 | |
---|
222 | /** |
---|
223 | * Method: drawPoint |
---|
224 | * This method is only called by the renderer itself. |
---|
225 | * |
---|
226 | * Parameters: |
---|
227 | * geometry - {<OpenLayers.Geometry>} |
---|
228 | * style - {Object} |
---|
229 | */ |
---|
230 | drawPoint: function(geometry, style) { |
---|
231 | if(style.graphic !== false) { |
---|
232 | var pt = this.getLocalXY(geometry); |
---|
233 | |
---|
234 | if (style.externalGraphic) { |
---|
235 | this.drawExternalGraphic(pt, style); |
---|
236 | } else { |
---|
237 | if(style.fill !== false) { |
---|
238 | this.setCanvasStyle("fill", style); |
---|
239 | this.canvas.beginPath(); |
---|
240 | this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true); |
---|
241 | this.canvas.fill(); |
---|
242 | } |
---|
243 | |
---|
244 | if(style.stroke !== false) { |
---|
245 | this.setCanvasStyle("stroke", style); |
---|
246 | this.canvas.beginPath(); |
---|
247 | this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true); |
---|
248 | this.canvas.stroke(); |
---|
249 | this.setCanvasStyle("reset"); |
---|
250 | } |
---|
251 | } |
---|
252 | } |
---|
253 | }, |
---|
254 | |
---|
255 | /** |
---|
256 | * Method: drawLineString |
---|
257 | * This method is only called by the renderer itself. |
---|
258 | * |
---|
259 | * Parameters: |
---|
260 | * geometry - {<OpenLayers.Geometry>} |
---|
261 | * style - {Object} |
---|
262 | */ |
---|
263 | drawLineString: function(geometry, style) { |
---|
264 | if(style.stroke !== false) { |
---|
265 | this.setCanvasStyle("stroke", style); |
---|
266 | this.canvas.beginPath(); |
---|
267 | var start = this.getLocalXY(geometry.components[0]); |
---|
268 | this.canvas.moveTo(start[0], start[1]); |
---|
269 | for(var i = 1; i < geometry.components.length; i++) { |
---|
270 | var pt = this.getLocalXY(geometry.components[i]); |
---|
271 | this.canvas.lineTo(pt[0], pt[1]); |
---|
272 | } |
---|
273 | this.canvas.stroke(); |
---|
274 | } |
---|
275 | this.setCanvasStyle("reset"); |
---|
276 | }, |
---|
277 | |
---|
278 | /** |
---|
279 | * Method: drawLinearRing |
---|
280 | * This method is only called by the renderer itself. |
---|
281 | * |
---|
282 | * Parameters: |
---|
283 | * geometry - {<OpenLayers.Geometry>} |
---|
284 | * style - {Object} |
---|
285 | */ |
---|
286 | drawLinearRing: function(geometry, style) { |
---|
287 | if(style.fill !== false) { |
---|
288 | this.setCanvasStyle("fill", style); |
---|
289 | this.canvas.beginPath(); |
---|
290 | var start = this.getLocalXY(geometry.components[0]); |
---|
291 | this.canvas.moveTo(start[0], start[1]); |
---|
292 | for(var i = 1; i < geometry.components.length - 1 ; i++) { |
---|
293 | var pt = this.getLocalXY(geometry.components[i]); |
---|
294 | this.canvas.lineTo(pt[0], pt[1]); |
---|
295 | } |
---|
296 | this.canvas.fill(); |
---|
297 | } |
---|
298 | |
---|
299 | if(style.stroke !== false) { |
---|
300 | this.setCanvasStyle("stroke", style); |
---|
301 | this.canvas.beginPath(); |
---|
302 | var start = this.getLocalXY(geometry.components[0]); |
---|
303 | this.canvas.moveTo(start[0], start[1]); |
---|
304 | for(var i = 1; i < geometry.components.length; i++) { |
---|
305 | var pt = this.getLocalXY(geometry.components[i]); |
---|
306 | this.canvas.lineTo(pt[0], pt[1]); |
---|
307 | } |
---|
308 | this.canvas.stroke(); |
---|
309 | } |
---|
310 | this.setCanvasStyle("reset"); |
---|
311 | }, |
---|
312 | |
---|
313 | /** |
---|
314 | * Method: drawPolygon |
---|
315 | * This method is only called by the renderer itself. |
---|
316 | * |
---|
317 | * Parameters: |
---|
318 | * geometry - {<OpenLayers.Geometry>} |
---|
319 | * style - {Object} |
---|
320 | */ |
---|
321 | drawPolygon: function(geometry, style) { |
---|
322 | this.drawLinearRing(geometry.components[0], style); |
---|
323 | for (var i = 1; i < geometry.components.length; i++) { |
---|
324 | this.drawLinearRing(geometry.components[i], { |
---|
325 | fillOpacity: 0, |
---|
326 | strokeWidth: 0, |
---|
327 | strokeOpacity: 0, |
---|
328 | strokeColor: '#000000', |
---|
329 | fillColor: '#000000'} |
---|
330 | ); // inner rings are 'empty' |
---|
331 | } |
---|
332 | }, |
---|
333 | |
---|
334 | /** |
---|
335 | * Method: drawText |
---|
336 | * This method is only called by the renderer itself. |
---|
337 | * |
---|
338 | * Parameters: |
---|
339 | * location - {<OpenLayers.Point>} |
---|
340 | * style - {Object} |
---|
341 | */ |
---|
342 | drawText: function(location, style) { |
---|
343 | style = OpenLayers.Util.extend({ |
---|
344 | fontColor: "#000000", |
---|
345 | labelAlign: "cm" |
---|
346 | }, style); |
---|
347 | var pt = this.getLocalXY(location); |
---|
348 | |
---|
349 | this.setCanvasStyle("reset"); |
---|
350 | this.canvas.fillStyle = style.fontColor; |
---|
351 | this.canvas.globalAlpha = style.fontOpacity || 1.0; |
---|
352 | var fontStyle = style.fontWeight + " " + style.fontSize + " " + style.fontFamily; |
---|
353 | if (this.canvas.fillText) { |
---|
354 | // HTML5 |
---|
355 | var labelAlign = |
---|
356 | OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] || |
---|
357 | "center"; |
---|
358 | this.canvas.font = fontStyle; |
---|
359 | this.canvas.textAlign = labelAlign; |
---|
360 | this.canvas.fillText(style.label, pt[0], pt[1]); |
---|
361 | } else if (this.canvas.mozDrawText) { |
---|
362 | // Mozilla pre-Gecko1.9.1 (<FF3.1) |
---|
363 | this.canvas.mozTextStyle = fontStyle; |
---|
364 | // No built-in text alignment, so we measure and adjust the position |
---|
365 | var len = this.canvas.mozMeasureText(style.label); |
---|
366 | switch(style.labelAlign[0]) { |
---|
367 | case "l": |
---|
368 | break; |
---|
369 | case "r": |
---|
370 | pt[0] -= len; |
---|
371 | break; |
---|
372 | case "c": |
---|
373 | default: |
---|
374 | pt[0] -= len / 2; |
---|
375 | } |
---|
376 | this.canvas.translate(pt[0], pt[1]); |
---|
377 | |
---|
378 | this.canvas.mozDrawText(style.label); |
---|
379 | this.canvas.translate(-1*pt[0], -1*pt[1]); |
---|
380 | } |
---|
381 | this.setCanvasStyle("reset"); |
---|
382 | }, |
---|
383 | |
---|
384 | /** |
---|
385 | * Method: getLocalXY |
---|
386 | * transform geographic xy into pixel xy |
---|
387 | * |
---|
388 | * Parameters: |
---|
389 | * point - {<OpenLayers.Geometry.Point>} |
---|
390 | */ |
---|
391 | getLocalXY: function(point) { |
---|
392 | var resolution = this.getResolution(); |
---|
393 | var extent = this.extent; |
---|
394 | var x = (point.x / resolution + (-extent.left / resolution)); |
---|
395 | var y = ((extent.top / resolution) - point.y / resolution); |
---|
396 | return [x, y]; |
---|
397 | }, |
---|
398 | |
---|
399 | /** |
---|
400 | * Method: clear |
---|
401 | * Clear all vectors from the renderer. |
---|
402 | */ |
---|
403 | clear: function() { |
---|
404 | this.canvas.clearRect(0, 0, this.root.width, this.root.height); |
---|
405 | this.features = {}; |
---|
406 | }, |
---|
407 | |
---|
408 | /** |
---|
409 | * Method: getFeatureIdFromEvent |
---|
410 | * Returns a feature id from an event on the renderer. |
---|
411 | * |
---|
412 | * Parameters: |
---|
413 | * evt - {<OpenLayers.Event>} |
---|
414 | * |
---|
415 | * Returns: |
---|
416 | * {String} A feature id or null. |
---|
417 | */ |
---|
418 | getFeatureIdFromEvent: function(evt) { |
---|
419 | var loc = this.map.getLonLatFromPixel(evt.xy); |
---|
420 | var resolution = this.getResolution(); |
---|
421 | var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5, |
---|
422 | loc.lat - resolution * 5, |
---|
423 | loc.lon + resolution * 5, |
---|
424 | loc.lat + resolution * 5); |
---|
425 | var geom = bounds.toGeometry(); |
---|
426 | for (var feat in this.features) { |
---|
427 | if (!this.features.hasOwnProperty(feat)) { continue; } |
---|
428 | if (this.features[feat][0].geometry.intersects(geom)) { |
---|
429 | return feat; |
---|
430 | } |
---|
431 | } |
---|
432 | return null; |
---|
433 | }, |
---|
434 | |
---|
435 | /** |
---|
436 | * Method: eraseFeatures |
---|
437 | * This is called by the layer to erase features; removes the feature from |
---|
438 | * the list, then redraws the layer. |
---|
439 | * |
---|
440 | * Parameters: |
---|
441 | * features - {Array(<OpenLayers.Feature.Vector>)} |
---|
442 | */ |
---|
443 | eraseFeatures: function(features) { |
---|
444 | if(!(features instanceof Array)) { |
---|
445 | features = [features]; |
---|
446 | } |
---|
447 | for(var i=0; i<features.length; ++i) { |
---|
448 | delete this.features[features[i].id]; |
---|
449 | } |
---|
450 | this.redraw(); |
---|
451 | }, |
---|
452 | |
---|
453 | /** |
---|
454 | * Method: redraw |
---|
455 | * The real 'meat' of the function: any time things have changed, |
---|
456 | * redraw() can be called to loop over all the data and (you guessed |
---|
457 | * it) redraw it. Unlike Elements-based Renderers, we can't interact |
---|
458 | * with things once they're drawn, to remove them, for example, so |
---|
459 | * instead we have to just clear everything and draw from scratch. |
---|
460 | */ |
---|
461 | redraw: function() { |
---|
462 | if (!this.locked) { |
---|
463 | this.canvas.clearRect(0, 0, this.root.width, this.root.height); |
---|
464 | var labelMap = []; |
---|
465 | var feature, style; |
---|
466 | for (var id in this.features) { |
---|
467 | if (!this.features.hasOwnProperty(id)) { continue; } |
---|
468 | feature = this.features[id][0]; |
---|
469 | style = this.features[id][1]; |
---|
470 | if (!feature.geometry) { continue; } |
---|
471 | this.drawGeometry(feature.geometry, style); |
---|
472 | if(style.label) { |
---|
473 | labelMap.push([feature, style]); |
---|
474 | } |
---|
475 | } |
---|
476 | var item; |
---|
477 | for (var i=0, len=labelMap.length; i<len; ++i) { |
---|
478 | item = labelMap[i]; |
---|
479 | this.drawText(item[0].geometry.getCentroid(), item[1]); |
---|
480 | } |
---|
481 | } |
---|
482 | }, |
---|
483 | |
---|
484 | CLASS_NAME: "OpenLayers.Renderer.Canvas" |
---|
485 | }); |
---|
486 | |
---|
487 | /** |
---|
488 | * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN |
---|
489 | * {Object} |
---|
490 | */ |
---|
491 | OpenLayers.Renderer.Canvas.LABEL_ALIGN = { |
---|
492 | "l": "left", |
---|
493 | "r": "right" |
---|
494 | }; |
---|