OpenLayers OpenLayers

Ticket #1357: ordering-updated.patch

File ordering-updated.patch, 16.7 kB (added by tcoulter, 5 months ago)

Updated ordering patch that includes support for background graphics.

  • lib/OpenLayers/Renderer/Elements.js

    old new  
    55/** 
    66 * @requires OpenLayers/Renderer.js 
    77 */ 
     8/** 
     9 * Class: OpenLayers.ElementsIndexer 
     10 * This class takes care of figuring out which order elements should be 
     11 * placed in the DOM based on given indexing methods.  
     12 */ 
     13OpenLayers.ElementsIndexer = OpenLayers.Class({ 
     14    
     15    /** 
     16     * Property: maxZIndex 
     17     * {Integer} This is the largest-most z-index value for a node 
     18     * contained within the indexer. 
     19     */ 
     20    maxZIndex: null, 
     21     
     22    /** 
     23     * Property: order 
     24     * {Array<String>} This is an array of node id's stored in the 
     25     * order that they should show up on screen. Id's higher up in the 
     26     * array (higher array index) represent nodes with higher z-indeces. 
     27     */ 
     28    order: null,  
     29     
     30    /** 
     31     * Property: indices 
     32     * {Hash} This is a hash that maps node ids to their z-index value 
     33     * stored in the indexer. This is done to make finding a nodes z-index  
     34     * value O(1). 
     35     */ 
     36    indices: null, 
     37     
     38    /** 
     39     * Property: compare 
     40     * {Function} This is the function used to determine placement of 
     41     * of a new node within the indexer. If null, this defaults to to 
     42     * the Z_ORDER_DRAWING_ORDER comparison method. 
     43     */ 
     44    compare: null, 
     45    
     46    /** 
     47     * APIMethod: initialize 
     48     * Create a new indexer with  
     49     *  
     50     * Parameters: 
     51     * ordering - {Function} The canonical name of the indexing method to 
     52     * use. See OpenLayers.ElementsIndexer.Translations below. If null,  
     53     * this defaults to "z". 
     54     */ 
     55    initialize: function(ordering) { 
     56         
     57        var compareMethod = OpenLayers.ElementsIndexer.Translations[ordering]; 
     58         
     59        this.compare = compareMethod || OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER; 
     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 positioning for 
     68     * the node to be inserted, this method uses a binary search. This makes inserting 
     69     * O(log(n)).  
     70     *  
     71     * Parameters: 
     72     * newNode - The new node to be inserted. 
     73     */ 
     74    insert: function(newNode) { 
     75        var nodeId = this.getNodeId(newNode); 
     76         
     77        this.determineZIndex(newNode);        
    878 
     79        var leftIndex = -1; 
     80        var rightIndex = this.order.length; 
     81        var middle; 
     82 
     83        while (rightIndex - leftIndex > 1) { 
     84            middle = parseInt((leftIndex + rightIndex) / 2); 
     85             
     86            var nextId = this.order[middle]; 
     87            var next = OpenLayers.Util.getElement(nextId); 
     88             
     89            var placement = this.compare(this, newNode, next); 
     90             
     91            if (placement > 0) { 
     92                leftIndex = middle; 
     93            } else { 
     94                rightIndex = middle; 
     95            }  
     96        } 
     97         
     98        this.order.splice(rightIndex, 0, nodeId); 
     99        this.indices[nodeId] = this.getZIndex(newNode); 
     100         
     101        // Return the next item in the array -- the id of the node that should 
     102        // follow this one directly in the index order -- if any. 
     103        var nextIndex = rightIndex + 1; 
     104         
     105        var returnVal = null; 
     106        if (nextIndex > 0 && nextIndex < this.order.length) { 
     107            returnVal = this.order[nextIndex]; 
     108        } 
     109        return returnVal; 
     110    }, 
     111     
     112    remove: function(node) { 
     113        var nodeId = this.getNodeId(node); 
     114        var arrayIndex = this.order.indexOf(nodeId); 
     115        if (arrayIndex >= 0) { 
     116            // Remove it from the order array, as well as deleting the node 
     117            // from the indeces hash. 
     118            this.order.splice(arrayIndex, 1); 
     119            delete this.indices[nodeId]; 
     120             
     121            // Reset the maxium z-index based on the last item in the order array. 
     122            var lastId = this.order[this.order.length - 1]; 
     123            this.maxZIndex = this.indices[lastId]; 
     124        } 
     125    }, 
     126     
     127    exists: function(node) { 
     128        var nodeId = this.getNodeId(node); 
     129        return this.indices[nodeId] != null;   
     130    }, 
     131     
     132    /** 
     133     * APIMethod: getZIndex 
     134     * Get the z-index value for the current node from the node data itself. 
     135     */ 
     136    getZIndex: function(node) { 
     137        return node._style.graphicZIndex;   
     138    }, 
     139 
     140    /** 
     141     * APIMethod: getMaxZIndex 
     142     * Get the maximum z-index stored within the indexer. 
     143     */ 
     144    getMaxZIndex: function() { 
     145        return this.maxZIndex; 
     146    }, 
     147     
     148    /** 
     149     * APIMethod: setZIndex 
     150     * Set the z-index for the given node. 
     151     */ 
     152    setZIndex: function(node, zIndex) { 
     153        node._style.graphicZIndex = zIndex;   
     154    }, 
     155     
     156    /** 
     157     * Method: determineZIndex 
     158     * Determine the z-index for the current node if there isn't one,  
     159     * and set the maximum value if we've found a new maximum. 
     160     */ 
     161    determineZIndex: function(node) { 
     162        var zIndex = node._style.graphicZIndex; 
     163         
     164        // Everything must have a zIndex. If none is specified, 
     165        // this means the user *must* (hint: assumption) want this 
     166        // node to succomb to drawing order. To enforce drawing order 
     167        // over all indexing methods, we'll create a new z-index that's 
     168        // greater than any currently in the indexer. 
     169        if (zIndex == null) { 
     170            zIndex = this.maxZIndex; 
     171        } 
     172         
     173        if (zIndex > this.maxZIndex) { 
     174            this.maxZIndex = zIndex; 
     175        } 
     176         
     177        node._style.graphicZIndex = zIndex; 
     178    }, 
     179     
     180    /** 
     181     * Method: getNodeId 
     182     * Get the id of the node passed in. 
     183     */ 
     184    getNodeId: function(node) { 
     185        return node.id;   
     186    }, 
     187    
     188    CLASS_NAME: "OpenLayers.ElementsIndexer" 
     189}); 
     190 
     191 
     192// These are the compare methods for figuring out where a new 
     193// node should be placed within the indexer. These methods are very  
     194// similar to general sorting methods in that they return -1, 0, and 1  
     195// to specify the direction in which new nodes fall in the ordering. 
     196OpenLayers.ElementsIndexer.IndexingMethods = { 
     197    /** 
     198     * Method: Z_ORDER 
     199     * This compare method is used by other comparison methods. 
     200     * It can be used individually for ordering, but is not recommended, 
     201     * because it doesn't subscribe to drawing order. 
     202     */     
     203    Z_ORDER: function(indexer, newNode, next) { 
     204        var newZIndex = indexer.getZIndex(newNode); 
     205 
     206        var returnVal = 0; 
     207        if (next) { 
     208            var nextZIndex = indexer.getZIndex(next); 
     209            returnVal = newZIndex - nextZIndex;  
     210        } 
     211         
     212        return returnVal; 
     213    }, 
     214    /** 
     215     * API Method: Z_ORDER_DRAWING_ORDER 
     216     * This method orders nodes by their z-index, but does so in a way 
     217     * that, if there are other nodes with the same z-index, the newest drawn 
     218     * will be the front most within that z-index. This is the default 
     219     * indexing method. 
     220     */ 
     221    Z_ORDER_DRAWING_ORDER: function(indexer, newNode, next) { 
     222        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer, newNode, next); 
     223         
     224        // Make Z_ORDER subscribe to drawing order by pushing it above 
     225        // all of the other nodes with the same z-index. 
     226        if (next && returnVal == 0) { 
     227            returnVal = 1; 
     228        } 
     229         
     230        return returnVal; 
     231    }, 
     232    /** 
     233     * API Method: Z_ORDER_Y_ORDER 
     234     * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it 
     235     * best describes which ordering methods have precedence (though, the name would 
     236     * be too long). This method orders nodes by their z-index, but does so in a way 
     237     * that, if there are other nodes with the same z-index, the nodes with the 
     238     * lower y position will be "closer" than those with a higher y position.  
     239     * If two nodes have the exact same y position, however, then this method 
     240     * will revert to using drawing order to decide placement. 
     241     */ 
     242    Z_ORDER_Y_ORDER: function(indexer, newNode, next) { 
     243        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer, newNode, next); 
     244         
     245        if (next && returnVal == 0) { 
     246            var newLat = newNode._geometry.getBounds().bottom; 
     247            var nextLat = next._geometry.getBounds().bottom; 
     248             
     249            var result = nextLat - newLat; 
     250             
     251            if (result == 0) { 
     252                returnVal = 1; 
     253            } else { 
     254                returnVal = result; 
     255            } 
     256        } 
     257         
     258        return returnVal;        
     259    } 
     260} 
     261 
    9262/** 
     263 * Translate canonical names for ordering methods to their  
     264 * function equivalents.  
     265 */ 
     266OpenLayers.ElementsIndexer.Translations = { 
     267    "z" : OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER, 
     268    "y" : OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER    
     269} 
     270 
     271 
     272/** 
    10273 * Class: OpenLayers.Renderer.Elements 
    11274 * This is another virtual class in that it should never be instantiated by  
    12275 *  itself as a Renderer. It exists because there is *tons* of shared  
     
    42305    xmlns: null, 
    43306     
    44307    /** 
     308     * Property: Indexer 
     309     * An instance of OpenLayers.ElementsIndexer created upon 
     310     * initialization. 
     311     */ 
     312    indexer: null,  
     313     
     314    BACKGROUND_ID_SUFFIX: "_background", 
     315     
     316    /** 
    45317     * Property: minimumSymbolizer 
    46318     * {Object} 
    47319     */ 
     
    57329     *  
    58330     * Parameters: 
    59331     * containerID - {String} 
     332     * ordering - {String} The cononical name for the ordering method 
     333     * used (such as "z"). See OpenLayers.ElementsIndexer.Translations above.  
    60334     */ 
    61     initialize: function(containerID) { 
     335    initialize: function(containerID, ordering) { 
    62336        OpenLayers.Renderer.prototype.initialize.apply(this, arguments); 
    63337 
    64338        this.rendererRoot = this.createRenderRoot(); 
     
    66340         
    67341        this.rendererRoot.appendChild(this.root); 
    68342        this.container.appendChild(this.rendererRoot); 
     343         
     344        this.indexer = new OpenLayers.ElementsIndexer(ordering); 
    69345    }, 
    70346     
    71347    /** 
     
    135411            return; 
    136412        }; 
    137413 
     414        var id = geometry.id; 
     415         
     416        if (style.backgroundGraphic) { 
     417            this.redrawBackgroundNode(id, geometry, style, featureId); 
     418        } 
     419         
    138420        if (style.display != "none") { 
    139             //first we create the basic node and add it to the root 
    140             var nodeType = this.getNodeType(geometry, style); 
    141             var node = this.nodeFactory(geometry.id, nodeType); 
    142             node._featureId = featureId; 
    143             node._geometryClass = geometry.CLASS_NAME; 
    144             node._style = style; 
    145              
    146             //now actually draw the node, and style it 
    147             node = this.drawGeometryNode(node, geometry); 
    148              
    149             // append the node to root (but only if it's new) 
    150             if (node.parentNode != this.root) {  
    151                 this.root.appendChild(node);  
    152             } 
    153             this.postDraw(node); 
     421            this.redrawNode(id, geometry, style, featureId);  
    154422        } else { 
    155             node = OpenLayers.Util.getElement(geometry.id); 
     423            node = OpenLayers.Util.getElement(id); 
    156424            if (node) { 
    157425                node.parentNode.removeChild(node); 
    158426            } 
    159427        } 
    160428    }, 
     429     
     430    redrawNode: function(id, geometry, style, featureId) { 
     431        // Get the node if it's already on the map. 
     432        var currentNode = OpenLayers.Util.getElement(id); 
     433         
     434        // Create a new node, or use the current one if it's 
     435        // already there. 
     436        var newNode; 
     437        if (!currentNode) { 
     438            var nodeType = this.getNodeType(geometry, style); 
     439            newNode = this.createNode(nodeType, id); 
     440        } else { 
     441            newNode = currentNode; 
     442        } 
     443         
     444        // Set the data for the node, then draw it. 
     445        newNode._featureId = featureId; 
     446        newNode._geometry = geometry; 
     447        newNode._geometryClass = geometry.CLASS_NAME; 
     448        newNode._style = style; 
     449        newNode = this.drawGeometryNode(newNode, geometry, style); 
     450         
     451        // If the node is known to the indexer, remove it so we can 
     452        // recalculate where it should go. 
     453        if (this.indexer.exists(newNode)) { 
     454            this.indexer.remove(newNode); 
     455        } 
     456         
     457        // Insert the node into the indexer so it can show us where to place it. 
     458        // Note that this operation is O(log(n)). If there's a performance problem 
     459        // (when dragging, for instance) this is likely where it would be. 
     460        var nextNodeId = this.indexer.insert(newNode); 
    161461 
     462        // If the new node should be before another in the index 
     463        // order, insert the new node before the next; else, lets just 
     464        // append the new one on the end, making it the highest in the index order. 
     465        if (nextNodeId) { 
     466            var nextNode = OpenLayers.Util.getElement(nextNodeId); 
     467            this.root.insertBefore(newNode, nextNode); 
     468        } else { 
     469            this.root.appendChild(newNode); 
     470        } 
     471         
     472        this.postDraw(newNode);  
     473    }, 
     474     
     475    redrawBackgroundNode: function(id, geometry, style, featureId) { 
     476        var backgroundStyle = OpenLayers.Util.extend({}, style); 
     477         
     478        // Set regular style attributes to apply to the background styles. 
     479        backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic; 
     480        backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset; 
     481        backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset; 
     482        backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex; 
     483         
     484        // Erase background styles. 
     485        backgroundStyle.backgroundGraphic = null; 
     486        backgroundStyle.backgroundXOffset = null; 
     487        backgroundStyle.backgroundYOffset = null; 
     488        backgroundStyle.backgroundGraphicZIndex = null; 
     489         
     490        this.redrawNode(id + this.BACKGROUND_ID_SUFFIX, geometry, backgroundStyle, null); 
     491    }, 
     492 
    162493    /** 
    163494     * Method: drawGeometryNode 
    164495     * Given a node, draw a geometry on the specified layer. 
     
    337668                this.eraseGeometry(geometry.components[i]); 
    338669            } 
    339670        } else {     
    340             var element = OpenLayers.Util.getElement(geometry.id); 
     671         
     672            // TODO: Functionalize the next two blocks. 
     673            var elementId = geometry.id; 
     674            var element = OpenLayers.Util.getElement(elementId); 
    341675            if (element && element.parentNode) { 
     676                if (element.geometry) { 
     677                    element.geometry.destroy(); 
     678                    element.geometry = null; 
     679                } 
    342680                element.parentNode.removeChild(element); 
     681                 
     682                this.indexer.remove(element); 
    343683            } 
     684             
     685            var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX; 
     686            var backgroundElement = OpenLayers.Util.getElement(backgroundId); 
     687            if (backgroundElement && backgroundElement.parentNode) { 
     688                if (backgroundElement.geometry) { 
     689                    backgroundElement.geometry.destroy(); 
     690                    backgroundElement.geometry = null; 
     691                } 
     692                backgroundElement.parentNode.removeChild(backgroundElement); 
     693                 
     694                this.indexer.remove(backgroundElement); 
     695            } 
    344696        } 
    345     },     
    346  
     697    }, 
     698     
    347699    /**  
    348700     * Method: nodeFactory 
    349701     * Create new node of the specified type, with the (optional) specified id. 
  • lib/OpenLayers/Layer/Vector.js

    old new  
    130137     * {<OpenLayers.Renderer>} 
    131138     */ 
    132139    renderer: null, 
     140     
     141    /** 
     142     * APIProperty: ordering 
     143     * {String} The ordering method that the layer's renderer should use. 
     144     *  
     145     * Valid values are: 
     146     *      "z" - Use z-ordering. 
     147     *      "y" - Use y-ordering. 
     148     *  
     149     * This defaults to z-ordering if no ordering is specified. 
     150     */ 
     151    ordering: "z", 
    133152    
    134153    /**  
    135154     * APIProperty: geometryType 
     
    213233        for (var i = 0; i < this.renderers.length; i++) { 
    214234            var rendererClass = OpenLayers.Renderer[this.renderers[i]]; 
    215235            if (rendererClass && rendererClass.prototype.supported()) { 
    216                this.renderer = new rendererClass(this.div); 
    217                break; 
     236                this.renderer = new rendererClass(this.div, this.ordering); 
     237                break; 
    218238            }   
    219239        }   
    220240    },