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.ElementsIndexer
11 * This class takes care of figuring out which order elements should be
12 * placed in the DOM based on given indexing methods.
14 OpenLayers.ElementsIndexer = OpenLayers.Class({
18 * {Integer} This is the largest-most z-index value for a node
19 * contained within the indexer.
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.
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
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.
48 * APIMethod: initialize
49 * Create a new indexer with
52 * yOrdering - {Boolean} Whether to use y-ordering.
54 initialize: function(yOrdering) {
56 this.compare = yOrdering ?
57 OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
58 OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
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)).
72 * newNode - {DOMElement} The new node to be inserted.
75 * {DOMElement} the node before which we should insert our newNode, or
76 * null if newNode can just be appended.
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)) {
85 var nodeId = newNode.id;
87 this.determineZIndex(newNode);
90 var rightIndex = this.order.length;
93 while (rightIndex - leftIndex > 1) {
94 middle = parseInt((leftIndex + rightIndex) / 2);
96 var placement = this.compare(this, newNode,
97 OpenLayers.Util.getElement(this.order[middle]));
106 this.order.splice(rightIndex, 0, nodeId);
107 this.indices[nodeId] = this.getZIndex(newNode);
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);
119 * node - {DOMElement} The node to be removed.
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];
130 // Reset the maxium z-index based on the last item in the
132 if (this.order.length > 0) {
133 var lastId = this.order[this.order.length - 1];
134 this.maxZIndex = this.indices[lastId];
154 * node- {DOMElement} The node to test for existence.
157 * {Boolean} Whether or not the node exists in the indexer?
159 exists: function(node) {
160 return (this.indices[node.id] != null);
164 * APIMethod: getZIndex
165 * Get the z-index value for the current node from the node data itself.
168 * node - {DOMElement} The node whose z-index to get.
171 * {Integer} The z-index value for the specified node (from the node
174 getZIndex: function(node) {
175 return node._style.graphicZIndex;
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.
184 * node - {DOMElement}
186 determineZIndex: function(node) {
187 var zIndex = node._style.graphicZIndex;
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;
203 * APIMethod: getNextElement
204 * Get the next element in the order stack.
207 * index - {Integer} The index of the current node in this.order.
210 * {DOMElement} the node following the index passed in, or
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);
226 CLASS_NAME: "OpenLayers.ElementsIndexer"
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.
236 OpenLayers.ElementsIndexer.IndexingMethods = {
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.
245 * indexer - {<OpenLayers.ElementsIndexer>}
246 * newNode - {DOMElement}
247 * nextNode - {DOMElement}
252 Z_ORDER: function(indexer, newNode, nextNode) {
253 var newZIndex = indexer.getZIndex(newNode);
257 var nextZIndex = indexer.getZIndex(nextNode);
258 returnVal = newZIndex - nextZIndex;
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.
272 * indexer - {<OpenLayers.ElementsIndexer>}
273 * newNode - {DOMElement}
274 * nextNode - {DOMElement}
279 Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
280 var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
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) {
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.
307 * indexer - {<OpenLayers.ElementsIndexer>}
308 * newNode - {DOMElement}
309 * nextNode - {DOMElement}
314 Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
315 var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
321 if (nextNode && returnVal == 0) {
322 var newLat = newNode._geometry.getBounds().bottom;
323 var nextLat = nextNode._geometry.getBounds().bottom;
325 var result = nextLat - newLat;
326 returnVal = (result ==0) ? 1 : result;
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.
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.
346 * - <OpenLayers.Renderer>
348 OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
351 * Property: rendererRoot
363 * Property: vectorRoot
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.
389 * Constant: BACKGROUND_ID_SUFFIX
392 BACKGROUND_ID_SUFFIX: "_background",
395 * Constant: BACKGROUND_ID_SUFFIX
398 LABEL_ID_SUFFIX: "_label",
401 * Property: minimumSymbolizer
405 strokeLinecap: "round",
407 strokeDashstyle: "solid",
413 * Constructor: OpenLayers.Renderer.Elements
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.
422 initialize: function(containerID, options) {
423 OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
425 this.rendererRoot = this.createRenderRoot();
426 this.root = this.createRoot("_root");
427 this.vectorRoot = this.createRoot("_vroot");
428 this.textRoot = this.createRoot("_troot");
430 this.root.appendChild(this.vectorRoot);
431 this.root.appendChild(this.textRoot);
433 this.rendererRoot.appendChild(this.root);
434 this.container.appendChild(this.rendererRoot);
436 if(options && (options.zIndexing || options.yOrdering)) {
437 this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
444 destroy: function() {
448 this.rendererRoot = null;
452 OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
457 * Remove all the elements from the root
460 if (this.vectorRoot) {
461 while (this.vectorRoot.childNodes.length > 0) {
462 this.vectorRoot.removeChild(this.vectorRoot.firstChild);
466 while (this.textRoot.childNodes.length > 0) {
467 this.textRoot.removeChild(this.textRoot.firstChild);
471 this.indexer.clear();
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.
485 * geometry - {<OpenLayers.Geometry>}
489 * {String} The corresponding node type for the specified geometry
491 getNodeType: function(geometry, style) { },
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.
500 * geometry - {<OpenLayers.Geometry>}
502 * featureId - {String}
505 * {Boolean} true if the geometry has been drawn completely; null if
506 * incomplete; false otherwise
508 drawGeometry: function(geometry, style, featureId) {
509 var className = geometry.CLASS_NAME;
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;
523 if (style.display != "none") {
524 if (style.backgroundGraphic) {
525 this.redrawBackgroundNode(geometry.id, geometry, style,
528 rendered = this.redrawNode(geometry.id, geometry, style,
531 if (rendered == false) {
532 var node = document.getElementById(geometry.id);
534 if (node._style.backgroundGraphic) {
535 node.parentNode.removeChild(document.getElementById(
536 geometry.id + this.BACKGROUND_ID_SUFFIX));
538 node.parentNode.removeChild(node);
549 * geometry - {<OpenLayers.Geometry>}
551 * featureId - {String}
554 * {Boolean} true if the complete geometry could be drawn, null if parts of
555 * the geometry could not be drawn, false otherwise
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));
561 // Set the data for the node, then draw it.
562 node._featureId = featureId;
563 node._geometry = geometry;
564 node._geometryClass = geometry.CLASS_NAME;
567 var drawResult = this.drawGeometryNode(node, geometry, style);
568 if(drawResult === false) {
572 node = drawResult.node;
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.
579 var insert = this.indexer.insert(node);
581 this.vectorRoot.insertBefore(node, insert);
583 this.vectorRoot.appendChild(node);
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);
595 return drawResult.complete;
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.
610 * geometry - {<OpenLayers.Geometry>}
612 * featureId - {String}
615 * {Boolean} true if the complete geometry could be drawn, null if parts of
616 * the geometry could not be drawn, false otherwise
618 redrawBackgroundNode: function(id, geometry, style, featureId) {
619 var backgroundStyle = OpenLayers.Util.extend({}, style);
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;
629 // Erase background styles.
630 backgroundStyle.backgroundGraphic = null;
631 backgroundStyle.backgroundXOffset = null;
632 backgroundStyle.backgroundYOffset = null;
633 backgroundStyle.backgroundGraphicZIndex = null;
635 return this.redrawNode(
636 id + this.BACKGROUND_ID_SUFFIX,
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.
650 * node - {DOMElement}
651 * geometry - {<OpenLayers.Geometry>}
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
659 drawGeometryNode: function(node, geometry, style) {
660 style = style || node._style;
661 OpenLayers.Util.applyDefaults(style, this.minimumSymbolizer);
664 'isFilled': style.fill === undefined ?
667 'isStroked': style.stroke === undefined ?
668 !!style.strokeWidth :
672 switch (geometry.CLASS_NAME) {
673 case "OpenLayers.Geometry.Point":
674 if(style.graphic === false) {
675 options.isFilled = false;
676 options.isStroked = false;
678 drawn = this.drawPoint(node, geometry);
680 case "OpenLayers.Geometry.LineString":
681 options.isFilled = false;
682 drawn = this.drawLineString(node, geometry);
684 case "OpenLayers.Geometry.LinearRing":
685 drawn = this.drawLinearRing(node, geometry);
687 case "OpenLayers.Geometry.Polygon":
688 drawn = this.drawPolygon(node, geometry);
690 case "OpenLayers.Geometry.Surface":
691 drawn = this.drawSurface(node, geometry);
693 case "OpenLayers.Geometry.Rectangle":
694 drawn = this.drawRectangle(node, geometry);
701 node._options = options;
705 if (drawn != false) {
707 node: this.setStyle(node, style, options, geometry),
717 * Things that have do be done after the geometry node is appended
718 * to its parent node. To be overridden by subclasses.
721 * node - {DOMElement}
723 postDraw: function(node) {},
727 * Virtual function for drawing Point Geometry.
728 * Should be implemented by subclasses.
729 * This method is only called by the renderer itself.
732 * node - {DOMElement}
733 * geometry - {<OpenLayers.Geometry>}
736 * {DOMElement} or false if the renderer could not draw the point
738 drawPoint: function(node, geometry) {},
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.
747 * node - {DOMElement}
748 * geometry - {<OpenLayers.Geometry>}
751 * {DOMElement} or null if the renderer could not draw all components of
752 * the linestring, or false if nothing could be drawn
754 drawLineString: function(node, geometry) {},
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.
763 * node - {DOMElement}
764 * geometry - {<OpenLayers.Geometry>}
767 * {DOMElement} or null if the renderer could not draw all components
768 * of the linear ring, or false if nothing could be drawn
770 drawLinearRing: function(node, geometry) {},
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.
779 * node - {DOMElement}
780 * geometry - {<OpenLayers.Geometry>}
783 * {DOMElement} or null if the renderer could not draw all components
784 * of the polygon, or false if nothing could be drawn
786 drawPolygon: function(node, geometry) {},
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.
795 * node - {DOMElement}
796 * geometry - {<OpenLayers.Geometry>}
799 * {DOMElement} or false if the renderer could not draw the rectangle
801 drawRectangle: function(node, geometry) {},
805 * Virtual function for drawing Circle Geometry.
806 * Should be implemented by subclasses.
807 * This method is only called by the renderer itself.
810 * node - {DOMElement}
811 * geometry - {<OpenLayers.Geometry>}
814 * {DOMElement} or false if the renderer could not draw the circle
816 drawCircle: function(node, geometry) {},
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.
825 * node - {DOMElement}
826 * geometry - {<OpenLayers.Geometry>}
829 * {DOMElement} or false if the renderer could not draw the surface
831 drawSurface: function(node, geometry) {},
838 * featureId - {String}
840 removeText: function(featureId) {
841 var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
843 this.textRoot.removeChild(label);
848 * Method: getFeatureIdFromEvent
851 * evt - {Object} An <OpenLayers.Event> object
854 * {<OpenLayers.Geometry>} A geometry from an event that
855 * happened on a layer.
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;
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
873 * geometry - {<OpenLayers.Geometry>}
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]);
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;
890 element.parentNode.removeChild(element);
893 this.indexer.remove(element);
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);
910 * Method: nodeFactory
911 * Create new node of the specified type, with the (optional) specified id.
913 * If node already exists with same ID and a different type, we remove it
914 * and then call ourselves again to recreate it.
918 * type - {String} type Kind of node to draw.
921 * {DOMElement} A new node of the given type and id.
923 nodeFactory: function(id, type) {
924 var node = OpenLayers.Util.getElement(id);
926 if (!this.nodeTypeCompare(node, type)) {
927 node.parentNode.removeChild(node);
928 node = this.nodeFactory(id, type);
931 node = this.createNode(type, id);
937 * Method: nodeTypeCompare
940 * node - {DOMElement}
941 * type - {String} Kind of node
944 * {Boolean} Whether or not the specified node is of the specified type
945 * This function must be overridden by subclasses.
947 nodeTypeCompare: function(node, type) {},
953 * type - {String} Kind of node to draw.
954 * id - {String} Id for node.
957 * {DOMElement} A new node of the given type and id.
958 * This function must be overridden by subclasses.
960 createNode: function(type, id) {},
964 * moves this renderer's root to a different renderer.
967 * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
969 moveRoot: function(renderer) {
970 var root = this.root;
971 if(renderer.root.parentNode == this.rendererRoot) {
972 root = renderer.root;
974 root.parentNode.removeChild(root);
975 renderer.rendererRoot.appendChild(root);
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.
985 * {String} the id of the output layer.
987 getRenderLayerId: function() {
988 return this.root.parentNode.parentNode.id;
992 * Method: isComplexSymbol
993 * Determines if a symbol cannot be rendered using drawCircle
996 * graphicName - {String}
999 * {Boolean} true if the symbol is complex, false if not
1001 isComplexSymbol: function(graphicName) {
1002 return (graphicName != "circle") && !!graphicName;
1005 CLASS_NAME: "OpenLayers.Renderer.Elements"
1010 * Constant: OpenLayers.Renderer.symbol
1011 * Coordinate arrays for well known (named) symbols.
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,
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]