]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Renderer/Elements.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Renderer / Elements.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.ElementsIndexer
11  * This class takes care of figuring out which order elements should be
12  *     placed in the DOM based on given indexing methods. 
13  */
14 OpenLayers.ElementsIndexer = OpenLayers.Class({
15    
16     /**
17      * Property: maxZIndex
18      * {Integer} This is the largest-most z-index value for a node
19      *     contained within the indexer.
20      */
21     maxZIndex: null,
22     
23     /**
24      * Property: order
25      * {Array<String>} This is an array of node id's stored in the
26      *     order that they should show up on screen. Id's higher up in the
27      *     array (higher array index) represent nodes with higher z-indeces.
28      */
29     order: null, 
30     
31     /**
32      * Property: indices
33      * {Object} This is a hash that maps node ids to their z-index value
34      *     stored in the indexer. This is done to make finding a nodes z-index 
35      *     value O(1).
36      */
37     indices: null,
38     
39     /**
40      * Property: compare
41      * {Function} This is the function used to determine placement of
42      *     of a new node within the indexer. If null, this defaults to to
43      *     the Z_ORDER_DRAWING_ORDER comparison method.
44      */
45     compare: null,
46     
47     /**
48      * APIMethod: initialize
49      * Create a new indexer with 
50      * 
51      * Parameters:
52      * yOrdering - {Boolean} Whether to use y-ordering.
53      */
54     initialize: function(yOrdering) {
55
56         this.compare = yOrdering ? 
57             OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
58             OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
59             
60         this.order = [];
61         this.indices = {};
62         this.maxZIndex = 0;
63     },
64     
65     /**
66      * APIMethod: insert
67      * Insert a new node into the indexer. In order to find the correct 
68      *     positioning for the node to be inserted, this method uses a binary 
69      *     search. This makes inserting O(log(n)). 
70      * 
71      * Parameters:
72      * newNode - {DOMElement} The new node to be inserted.
73      * 
74      * Returns
75      * {DOMElement} the node before which we should insert our newNode, or
76      *     null if newNode can just be appended.
77      */
78     insert: function(newNode) {
79         // If the node is known to the indexer, remove it so we can
80         // recalculate where it should go.
81         if (this.exists(newNode)) {
82             this.remove(newNode);
83         }
84         
85         var nodeId = newNode.id;
86         
87         this.determineZIndex(newNode);       
88
89         var leftIndex = -1;
90         var rightIndex = this.order.length;
91         var middle;
92
93         while (rightIndex - leftIndex > 1) {
94             middle = parseInt((leftIndex + rightIndex) / 2);
95             
96             var placement = this.compare(this, newNode,
97                 OpenLayers.Util.getElement(this.order[middle]));
98             
99             if (placement > 0) {
100                 leftIndex = middle;
101             } else {
102                 rightIndex = middle;
103             } 
104         }
105         
106         this.order.splice(rightIndex, 0, nodeId);
107         this.indices[nodeId] = this.getZIndex(newNode);
108         
109         // If the new node should be before another in the index
110         // order, return the node before which we have to insert the new one;
111         // else, return null to indicate that the new node can be appended.
112         return this.getNextElement(rightIndex);
113     },
114     
115     /**
116      * APIMethod: remove
117      * 
118      * Parameters:
119      * node - {DOMElement} The node to be removed.
120      */
121     remove: function(node) {
122         var nodeId = node.id;
123         var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
124         if (arrayIndex >= 0) {
125             // Remove it from the order array, as well as deleting the node
126             // from the indeces hash.
127             this.order.splice(arrayIndex, 1);
128             delete this.indices[nodeId];
129             
130             // Reset the maxium z-index based on the last item in the 
131             // order array.
132             if (this.order.length > 0) {
133                 var lastId = this.order[this.order.length - 1];
134                 this.maxZIndex = this.indices[lastId];
135             } else {
136                 this.maxZIndex = 0;
137             }
138         }
139     },
140     
141     /**
142      * APIMethod: clear
143      */
144     clear: function() {
145         this.order = [];
146         this.indices = {};
147         this.maxZIndex = 0;
148     },
149     
150     /**
151      * APIMethod: exists
152      *
153      * Parameters:
154      * node- {DOMElement} The node to test for existence.
155      *
156      * Returns:
157      * {Boolean} Whether or not the node exists in the indexer?
158      */
159     exists: function(node) {
160         return (this.indices[node.id] != null);
161     },
162
163     /**
164      * APIMethod: getZIndex
165      * Get the z-index value for the current node from the node data itself.
166      * 
167      * Parameters:
168      * node - {DOMElement} The node whose z-index to get.
169      * 
170      * Returns:
171      * {Integer} The z-index value for the specified node (from the node 
172      *     data itself).
173      */
174     getZIndex: function(node) {
175         return node._style.graphicZIndex;  
176     },
177     
178     /**
179      * Method: determineZIndex
180      * Determine the z-index for the current node if there isn't one, 
181      *     and set the maximum value if we've found a new maximum.
182      * 
183      * Parameters:
184      * node - {DOMElement} 
185      */
186     determineZIndex: function(node) {
187         var zIndex = node._style.graphicZIndex;
188         
189         // Everything must have a zIndex. If none is specified,
190         // this means the user *must* (hint: assumption) want this
191         // node to succomb to drawing order. To enforce drawing order
192         // over all indexing methods, we'll create a new z-index that's
193         // greater than any currently in the indexer.
194         if (zIndex == null) {
195             zIndex = this.maxZIndex;
196             node._style.graphicZIndex = zIndex; 
197         } else if (zIndex > this.maxZIndex) {
198             this.maxZIndex = zIndex;
199         }
200     },
201         
202     /**
203      * APIMethod: getNextElement
204      * Get the next element in the order stack.
205      * 
206      * Parameters:
207      * index - {Integer} The index of the current node in this.order.
208      * 
209      * Returns:
210      * {DOMElement} the node following the index passed in, or
211      *     null.
212      */
213     getNextElement: function(index) {
214                 var nextIndex = index + 1;
215         if (nextIndex < this.order.length){
216                         var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
217                         if (nextElement == undefined){
218                           nextElement = this.getNextElement(nextIndex);
219                         }
220                         return nextElement;
221                 } else {
222                         return null;
223                 } 
224     },  
225     
226     CLASS_NAME: "OpenLayers.ElementsIndexer"
227 });
228
229 /**
230  * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
231  * These are the compare methods for figuring out where a new node should be 
232  *     placed within the indexer. These methods are very similar to general 
233  *     sorting methods in that they return -1, 0, and 1 to specify the 
234  *     direction in which new nodes fall in the ordering.
235  */
236 OpenLayers.ElementsIndexer.IndexingMethods = {
237     
238     /**
239      * Method: Z_ORDER
240      * This compare method is used by other comparison methods.
241      *     It can be used individually for ordering, but is not recommended,
242      *     because it doesn't subscribe to drawing order.
243      * 
244      * Parameters:
245      * indexer - {<OpenLayers.ElementsIndexer>}
246      * newNode - {DOMElement}
247      * nextNode - {DOMElement}
248      * 
249      * Returns:
250      * {Integer}
251      */
252     Z_ORDER: function(indexer, newNode, nextNode) {
253         var newZIndex = indexer.getZIndex(newNode);
254
255         var returnVal = 0;
256         if (nextNode) {
257             var nextZIndex = indexer.getZIndex(nextNode);
258             returnVal = newZIndex - nextZIndex; 
259         }
260         
261         return returnVal;
262     },
263
264     /**
265      * APIMethod: Z_ORDER_DRAWING_ORDER
266      * This method orders nodes by their z-index, but does so in a way
267      *     that, if there are other nodes with the same z-index, the newest 
268      *     drawn will be the front most within that z-index. This is the 
269      *     default indexing method.
270      * 
271      * Parameters:
272      * indexer - {<OpenLayers.ElementsIndexer>}
273      * newNode - {DOMElement}
274      * nextNode - {DOMElement}
275      * 
276      * Returns:
277      * {Integer}
278      */
279     Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
280         var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
281             indexer, 
282             newNode, 
283             nextNode
284         );
285         
286         // Make Z_ORDER subscribe to drawing order by pushing it above
287         // all of the other nodes with the same z-index.
288         if (nextNode && returnVal == 0) {
289             returnVal = 1;
290         }
291         
292         return returnVal;
293     },
294
295     /**
296      * APIMethod: Z_ORDER_Y_ORDER
297      * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
298      *     best describes which ordering methods have precedence (though, the 
299      *     name would be too long). This method orders nodes by their z-index, 
300      *     but does so in a way that, if there are other nodes with the same 
301      *     z-index, the nodes with the lower y position will be "closer" than 
302      *     those with a higher y position. If two nodes have the exact same y 
303      *     position, however, then this method will revert to using drawing  
304      *     order to decide placement.
305      * 
306      * Parameters:
307      * indexer - {<OpenLayers.ElementsIndexer>}
308      * newNode - {DOMElement}
309      * nextNode - {DOMElement}
310      * 
311      * Returns:
312      * {Integer}
313      */
314     Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
315         var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
316             indexer, 
317             newNode, 
318             nextNode
319         );
320         
321         if (nextNode && returnVal == 0) {
322             var newLat = newNode._geometry.getBounds().bottom;
323             var nextLat = nextNode._geometry.getBounds().bottom;
324             
325             var result = nextLat - newLat;
326             returnVal = (result ==0) ? 1 : result;
327         }
328         
329         return returnVal;       
330     }
331 };
332
333 /**
334  * Class: OpenLayers.Renderer.Elements
335  * This is another virtual class in that it should never be instantiated by 
336  *  itself as a Renderer. It exists because there is *tons* of shared 
337  *  functionality between different vector libraries which use nodes/elements
338  *  as a base for rendering vectors. 
339  * 
340  * The highlevel bits of code that are implemented here are the adding and 
341  *  removing of geometries, which is essentially the same for any 
342  *  element-based renderer. The details of creating each node and drawing the
343  *  paths are of course different, but the machinery is the same. 
344  * 
345  * Inherits:
346  *  - <OpenLayers.Renderer>
347  */
348 OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
349
350     /**
351      * Property: rendererRoot
352      * {DOMElement}
353      */
354     rendererRoot: null,
355     
356     /**
357      * Property: root
358      * {DOMElement}
359      */
360     root: null,
361     
362     /**
363      * Property: vectorRoot
364      * {DOMElement}
365      */
366     vectorRoot: null,
367
368     /**
369      * Property: textRoot
370      * {DOMElement}
371      */
372     textRoot: null,
373
374     /**
375      * Property: xmlns
376      * {String}
377      */    
378     xmlns: null,
379     
380     /**
381      * Property: Indexer
382      * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer 
383      *     created upon initialization if the zIndexing or yOrdering options
384      *     passed to this renderer's constructor are set to true.
385      */
386     indexer: null, 
387     
388     /**
389      * Constant: BACKGROUND_ID_SUFFIX
390      * {String}
391      */
392     BACKGROUND_ID_SUFFIX: "_background",
393     
394     /**
395      * Constant: BACKGROUND_ID_SUFFIX
396      * {String}
397      */
398     LABEL_ID_SUFFIX: "_label",
399     
400     /**
401      * Property: minimumSymbolizer
402      * {Object}
403      */
404     minimumSymbolizer: {
405         strokeLinecap: "round",
406         strokeOpacity: 1,
407         strokeDashstyle: "solid",
408         fillOpacity: 1,
409         pointRadius: 0
410     },
411     
412     /**
413      * Constructor: OpenLayers.Renderer.Elements
414      * 
415      * Parameters:
416      * containerID - {String}
417      * options - {Object} options for this renderer. Supported options are:
418      *     * yOrdering - {Boolean} Whether to use y-ordering
419      *     * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
420      *         if yOrdering is set to true.
421      */
422     initialize: function(containerID, options) {
423         OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
424
425         this.rendererRoot = this.createRenderRoot();
426         this.root = this.createRoot("_root");
427         this.vectorRoot = this.createRoot("_vroot");
428         this.textRoot = this.createRoot("_troot");
429         
430         this.root.appendChild(this.vectorRoot);
431         this.root.appendChild(this.textRoot);
432         
433         this.rendererRoot.appendChild(this.root);
434         this.container.appendChild(this.rendererRoot);
435         
436         if(options && (options.zIndexing || options.yOrdering)) {
437             this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
438         }
439     },
440     
441     /**
442      * Method: destroy
443      */
444     destroy: function() {
445
446         this.clear(); 
447
448         this.rendererRoot = null;
449         this.root = null;
450         this.xmlns = null;
451
452         OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
453     },
454     
455     /**
456      * Method: clear
457      * Remove all the elements from the root
458      */    
459     clear: function() {
460         if (this.vectorRoot) {
461             while (this.vectorRoot.childNodes.length > 0) {
462                 this.vectorRoot.removeChild(this.vectorRoot.firstChild);
463             }
464         }
465         if (this.textRoot) {
466             while (this.textRoot.childNodes.length > 0) {
467                 this.textRoot.removeChild(this.textRoot.firstChild);
468             }
469         }
470         if (this.indexer) {
471             this.indexer.clear();
472         }
473     },
474
475     /** 
476      * Method: getNodeType
477      * This function is in charge of asking the specific renderer which type
478      *     of node to create for the given geometry and style. All geometries
479      *     in an Elements-based renderer consist of one node and some
480      *     attributes. We have the nodeFactory() function which creates a node
481      *     for us, but it takes a 'type' as input, and that is precisely what
482      *     this function tells us.  
483      *  
484      * Parameters:
485      * geometry - {<OpenLayers.Geometry>}
486      * style - {Object}
487      * 
488      * Returns:
489      * {String} The corresponding node type for the specified geometry
490      */
491     getNodeType: function(geometry, style) { },
492
493     /** 
494      * Method: drawGeometry 
495      * Draw the geometry, creating new nodes, setting paths, setting style,
496      *     setting featureId on the node.  This method should only be called
497      *     by the renderer itself.
498      *
499      * Parameters:
500      * geometry - {<OpenLayers.Geometry>}
501      * style - {Object}
502      * featureId - {String}
503      * 
504      * Returns:
505      * {Boolean} true if the geometry has been drawn completely; null if
506      *     incomplete; false otherwise
507      */
508     drawGeometry: function(geometry, style, featureId) {
509         var className = geometry.CLASS_NAME;
510         var rendered = true;
511         if ((className == "OpenLayers.Geometry.Collection") ||
512             (className == "OpenLayers.Geometry.MultiPoint") ||
513             (className == "OpenLayers.Geometry.MultiLineString") ||
514             (className == "OpenLayers.Geometry.MultiPolygon")) {
515             for (var i = 0, len=geometry.components.length; i<len; i++) {
516                 rendered = this.drawGeometry(
517                     geometry.components[i], style, featureId) && rendered;
518             }
519             return rendered;
520         };
521
522         rendered = false;
523         if (style.display != "none") {
524             if (style.backgroundGraphic) {
525                 this.redrawBackgroundNode(geometry.id, geometry, style,
526                     featureId);
527             }
528             rendered = this.redrawNode(geometry.id, geometry, style,
529                 featureId);
530         }
531         if (rendered == false) {
532             var node = document.getElementById(geometry.id);
533             if (node) {
534                 if (node._style.backgroundGraphic) {
535                     node.parentNode.removeChild(document.getElementById(
536                         geometry.id + this.BACKGROUND_ID_SUFFIX));
537                 }
538                 node.parentNode.removeChild(node);
539             }
540         }
541         return rendered;
542     },
543     
544     /**
545      * Method: redrawNode
546      * 
547      * Parameters:
548      * id - {String}
549      * geometry - {<OpenLayers.Geometry>}
550      * style - {Object}
551      * featureId - {String}
552      * 
553      * Returns:
554      * {Boolean} true if the complete geometry could be drawn, null if parts of
555      *     the geometry could not be drawn, false otherwise
556      */
557     redrawNode: function(id, geometry, style, featureId) {
558         // Get the node if it's already on the map.
559         var node = this.nodeFactory(id, this.getNodeType(geometry, style));
560         
561         // Set the data for the node, then draw it.
562         node._featureId = featureId;
563         node._geometry = geometry;
564         node._geometryClass = geometry.CLASS_NAME;
565         node._style = style;
566
567         var drawResult = this.drawGeometryNode(node, geometry, style);
568         if(drawResult === false) {
569             return false;
570         }
571          
572         node = drawResult.node;
573         
574         // Insert the node into the indexer so it can show us where to
575         // place it. Note that this operation is O(log(n)). If there's a
576         // performance problem (when dragging, for instance) this is
577         // likely where it would be.
578         if (this.indexer) {
579             var insert = this.indexer.insert(node);
580             if (insert) {
581                 this.vectorRoot.insertBefore(node, insert);
582             } else {
583                 this.vectorRoot.appendChild(node);
584             }
585         } else {
586             // if there's no indexer, simply append the node to root,
587             // but only if the node is a new one
588             if (node.parentNode !== this.vectorRoot){ 
589                 this.vectorRoot.appendChild(node);
590             }
591         }
592         
593         this.postDraw(node);
594         
595         return drawResult.complete;
596     },
597     
598     /**
599      * Method: redrawBackgroundNode
600      * Redraws the node using special 'background' style properties. Basically
601      *     just calls redrawNode(), but instead of directly using the 
602      *     'externalGraphic', 'graphicXOffset', 'graphicYOffset', and 
603      *     'graphicZIndex' properties directly from the specified 'style' 
604      *     parameter, we create a new style object and set those properties 
605      *     from the corresponding 'background'-prefixed properties from 
606      *     specified 'style' parameter.
607      * 
608      * Parameters:
609      * id - {String}
610      * geometry - {<OpenLayers.Geometry>}
611      * style - {Object}
612      * featureId - {String}
613      * 
614      * Returns:
615      * {Boolean} true if the complete geometry could be drawn, null if parts of
616      *     the geometry could not be drawn, false otherwise
617      */
618     redrawBackgroundNode: function(id, geometry, style, featureId) {
619         var backgroundStyle = OpenLayers.Util.extend({}, style);
620         
621         // Set regular style attributes to apply to the background styles.
622         backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
623         backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
624         backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
625         backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
626         backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
627         backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
628         
629         // Erase background styles.
630         backgroundStyle.backgroundGraphic = null;
631         backgroundStyle.backgroundXOffset = null;
632         backgroundStyle.backgroundYOffset = null;
633         backgroundStyle.backgroundGraphicZIndex = null;
634         
635         return this.redrawNode(
636             id + this.BACKGROUND_ID_SUFFIX, 
637             geometry, 
638             backgroundStyle, 
639             null
640         );
641     },
642
643     /**
644      * Method: drawGeometryNode
645      * Given a node, draw a geometry on the specified layer.
646      *     node and geometry are required arguments, style is optional.
647      *     This method is only called by the render itself.
648      *
649      * Parameters:
650      * node - {DOMElement}
651      * geometry - {<OpenLayers.Geometry>}
652      * style - {Object}
653      * 
654      * Returns:
655      * {Object} a hash with properties "node" (the drawn node) and "complete"
656      *     (null if parts of the geometry could not be drawn, false if nothing
657      *     could be drawn)
658      */
659     drawGeometryNode: function(node, geometry, style) {
660         style = style || node._style;
661         OpenLayers.Util.applyDefaults(style, this.minimumSymbolizer);
662
663         var options = {
664             'isFilled': style.fill === undefined ?
665                 true :
666                 style.fill,
667             'isStroked': style.stroke === undefined ?
668                 !!style.strokeWidth :
669                 style.stroke
670         };
671         var drawn;
672         switch (geometry.CLASS_NAME) {
673             case "OpenLayers.Geometry.Point":
674                 if(style.graphic === false) {
675                     options.isFilled = false;
676                     options.isStroked = false;
677                 }
678                 drawn = this.drawPoint(node, geometry);
679                 break;
680             case "OpenLayers.Geometry.LineString":
681                 options.isFilled = false;
682                 drawn = this.drawLineString(node, geometry);
683                 break;
684             case "OpenLayers.Geometry.LinearRing":
685                 drawn = this.drawLinearRing(node, geometry);
686                 break;
687             case "OpenLayers.Geometry.Polygon":
688                 drawn = this.drawPolygon(node, geometry);
689                 break;
690             case "OpenLayers.Geometry.Surface":
691                 drawn = this.drawSurface(node, geometry);
692                 break;
693             case "OpenLayers.Geometry.Rectangle":
694                 drawn = this.drawRectangle(node, geometry);
695                 break;
696             default:
697                 break;
698         }
699
700         node._style = style; 
701         node._options = options; 
702
703         //set style
704         //TBD simplify this
705         if (drawn != false) {
706             return {
707                 node: this.setStyle(node, style, options, geometry),
708                 complete: drawn
709             };
710         } else {
711             return false;
712         }
713     },
714     
715     /**
716      * Method: postDraw
717      * Things that have do be done after the geometry node is appended
718      *     to its parent node. To be overridden by subclasses.
719      * 
720      * Parameters:
721      * node - {DOMElement}
722      */
723     postDraw: function(node) {},
724     
725     /**
726      * Method: drawPoint
727      * Virtual function for drawing Point Geometry. 
728      *     Should be implemented by subclasses.
729      *     This method is only called by the renderer itself.
730      * 
731      * Parameters: 
732      * node - {DOMElement}
733      * geometry - {<OpenLayers.Geometry>}
734      * 
735      * Returns:
736      * {DOMElement} or false if the renderer could not draw the point
737      */ 
738     drawPoint: function(node, geometry) {},
739
740     /**
741      * Method: drawLineString
742      * Virtual function for drawing LineString Geometry. 
743      *     Should be implemented by subclasses.
744      *     This method is only called by the renderer itself.
745      * 
746      * Parameters: 
747      * node - {DOMElement}
748      * geometry - {<OpenLayers.Geometry>}
749      * 
750      * Returns:
751      * {DOMElement} or null if the renderer could not draw all components of
752      *     the linestring, or false if nothing could be drawn
753      */ 
754     drawLineString: function(node, geometry) {},
755
756     /**
757      * Method: drawLinearRing
758      * Virtual function for drawing LinearRing Geometry. 
759      *     Should be implemented by subclasses.
760      *     This method is only called by the renderer itself.
761      * 
762      * Parameters: 
763      * node - {DOMElement}
764      * geometry - {<OpenLayers.Geometry>}
765      * 
766      * Returns:
767      * {DOMElement} or null if the renderer could not draw all components
768      *     of the linear ring, or false if nothing could be drawn
769      */ 
770     drawLinearRing: function(node, geometry) {},
771
772     /**
773      * Method: drawPolygon
774      * Virtual function for drawing Polygon Geometry. 
775      *    Should be implemented by subclasses.
776      *    This method is only called by the renderer itself.
777      * 
778      * Parameters: 
779      * node - {DOMElement}
780      * geometry - {<OpenLayers.Geometry>}
781      * 
782      * Returns:
783      * {DOMElement} or null if the renderer could not draw all components
784      *     of the polygon, or false if nothing could be drawn
785      */ 
786     drawPolygon: function(node, geometry) {},
787
788     /**
789      * Method: drawRectangle
790      * Virtual function for drawing Rectangle Geometry. 
791      *     Should be implemented by subclasses.
792      *     This method is only called by the renderer itself.
793      * 
794      * Parameters: 
795      * node - {DOMElement}
796      * geometry - {<OpenLayers.Geometry>}
797      * 
798      * Returns:
799      * {DOMElement} or false if the renderer could not draw the rectangle
800      */ 
801     drawRectangle: function(node, geometry) {},
802
803     /**
804      * Method: drawCircle
805      * Virtual function for drawing Circle Geometry. 
806      *     Should be implemented by subclasses.
807      *     This method is only called by the renderer itself.
808      * 
809      * Parameters: 
810      * node - {DOMElement}
811      * geometry - {<OpenLayers.Geometry>}
812      * 
813      * Returns:
814      * {DOMElement} or false if the renderer could not draw the circle
815      */ 
816     drawCircle: function(node, geometry) {},
817
818     /**
819      * Method: drawSurface
820      * Virtual function for drawing Surface Geometry. 
821      *     Should be implemented by subclasses.
822      *     This method is only called by the renderer itself.
823      * 
824      * Parameters: 
825      * node - {DOMElement}
826      * geometry - {<OpenLayers.Geometry>}
827      * 
828      * Returns:
829      * {DOMElement} or false if the renderer could not draw the surface
830      */ 
831     drawSurface: function(node, geometry) {},
832
833     /**
834      * Method: removeText
835      * Removes a label
836      * 
837      * Parameters:
838      * featureId - {String}
839      */
840     removeText: function(featureId) {
841         var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
842         if (label) {
843             this.textRoot.removeChild(label);
844         }
845     },
846
847     /**
848      * Method: getFeatureIdFromEvent
849      * 
850      * Parameters:
851      * evt - {Object} An <OpenLayers.Event> object
852      *
853      * Returns:
854      * {<OpenLayers.Geometry>} A geometry from an event that 
855      *     happened on a layer.
856      */
857     getFeatureIdFromEvent: function(evt) {
858         var target = evt.target;
859         var useElement = target && target.correspondingUseElement;
860         var node = useElement ? useElement : (target || evt.srcElement);
861         var featureId = node._featureId;
862         return featureId;
863     },
864
865     /** 
866      * Method: eraseGeometry
867      * Erase a geometry from the renderer. In the case of a multi-geometry, 
868      *     we cycle through and recurse on ourselves. Otherwise, we look for a 
869      *     node with the geometry.id, destroy its geometry, and remove it from
870      *     the DOM.
871      * 
872      * Parameters:
873      * geometry - {<OpenLayers.Geometry>}
874      */
875     eraseGeometry: function(geometry) {
876         if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
877             (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
878             (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
879             (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
880             for (var i=0, len=geometry.components.length; i<len; i++) {
881                 this.eraseGeometry(geometry.components[i]);
882             }
883         } else {    
884             var element = OpenLayers.Util.getElement(geometry.id);
885             if (element && element.parentNode) {
886                 if (element.geometry) {
887                     element.geometry.destroy();
888                     element.geometry = null;
889                 }
890                 element.parentNode.removeChild(element);
891
892                 if (this.indexer) {
893                     this.indexer.remove(element);
894                 }
895                 
896                 if (element._style.backgroundGraphic) {
897                     var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
898                     var bElem = OpenLayers.Util.getElement(backgroundId);
899                     if (bElem && bElem.parentNode) {
900                         // No need to destroy the geometry since the element and the background
901                         // node share the same geometry.
902                         bElem.parentNode.removeChild(bElem);
903                     }
904                 }
905             }
906         }
907     },
908
909     /** 
910      * Method: nodeFactory
911      * Create new node of the specified type, with the (optional) specified id.
912      * 
913      * If node already exists with same ID and a different type, we remove it
914      *     and then call ourselves again to recreate it.
915      * 
916      * Parameters:
917      * id - {String}
918      * type - {String} type Kind of node to draw.
919      * 
920      * Returns:
921      * {DOMElement} A new node of the given type and id.
922      */
923     nodeFactory: function(id, type) {
924         var node = OpenLayers.Util.getElement(id);
925         if (node) {
926             if (!this.nodeTypeCompare(node, type)) {
927                 node.parentNode.removeChild(node);
928                 node = this.nodeFactory(id, type);
929             }
930         } else {
931             node = this.createNode(type, id);
932         }
933         return node;
934     },
935     
936     /** 
937      * Method: nodeTypeCompare
938      * 
939      * Parameters:
940      * node - {DOMElement}
941      * type - {String} Kind of node
942      * 
943      * Returns:
944      * {Boolean} Whether or not the specified node is of the specified type
945      *     This function must be overridden by subclasses.
946      */
947     nodeTypeCompare: function(node, type) {},
948     
949     /** 
950      * Method: createNode
951      * 
952      * Parameters:
953      * type - {String} Kind of node to draw.
954      * id - {String} Id for node.
955      * 
956      * Returns:
957      * {DOMElement} A new node of the given type and id.
958      *     This function must be overridden by subclasses.
959      */
960     createNode: function(type, id) {},
961
962     /**
963      * Method: moveRoot
964      * moves this renderer's root to a different renderer.
965      * 
966      * Parameters:
967      * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
968      */
969     moveRoot: function(renderer) {
970         var root = this.root;
971         if(renderer.root.parentNode == this.rendererRoot) {
972             root = renderer.root;
973         }
974         root.parentNode.removeChild(root);
975         renderer.rendererRoot.appendChild(root);
976     },
977     
978     /**
979      * Method: getRenderLayerId
980      * Gets the layer that this renderer's output appears on. If moveRoot was
981      * used, this will be different from the id of the layer containing the
982      * features rendered by this renderer.
983      * 
984      * Returns:
985      * {String} the id of the output layer.
986      */
987     getRenderLayerId: function() {
988         return this.root.parentNode.parentNode.id;
989     },
990     
991     /**
992      * Method: isComplexSymbol
993      * Determines if a symbol cannot be rendered using drawCircle
994      * 
995      * Parameters:
996      * graphicName - {String}
997      * 
998      * Returns
999      * {Boolean} true if the symbol is complex, false if not
1000      */
1001     isComplexSymbol: function(graphicName) {
1002         return (graphicName != "circle") && !!graphicName;
1003     },
1004
1005     CLASS_NAME: "OpenLayers.Renderer.Elements"
1006 });
1007
1008
1009 /**
1010  * Constant: OpenLayers.Renderer.symbol
1011  * Coordinate arrays for well known (named) symbols.
1012  */
1013 OpenLayers.Renderer.symbol = {
1014     "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
1015             303,215, 231,161, 321,161, 350,75],
1016     "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
1017             4,0],
1018     "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
1019     "square": [0,0, 0,1, 1,1, 1,0, 0,0],
1020     "triangle": [0,10, 10,10, 5,0, 0,10]
1021 };