ordering <exclamation point/>. you can now gracefully z-order your vectors... and you can even 'yOrder' them and add background images, making for a nice 3dish look. be sure to check out the two new example html's: marker-shadow.html and ordering.html. Big thanks to tcoulter (funkyc) for a prolonged effort with this patch. It has come a long way and now what a beautiful finish. (Closes #1357)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@7652 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
euzuro
2008-08-01 00:17:59 +00:00
parent d4a62c630a
commit 8b98f5acc3
6 changed files with 996 additions and 162 deletions
+10 -3
View File
@@ -152,6 +152,13 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
* {<OpenLayers.Renderer>}
*/
renderer: null,
/**
* APIProperty: yOrdering
* {String} Whether or not externalGraphic y-ordering is enabled on this
* layer. Default is false.
*/
yOrdering: false,
/**
* APIProperty: geometryType
@@ -252,8 +259,8 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
for (var i=0, len=this.renderers.length; i<this.renderers.length; i++) {
var rendererClass = OpenLayers.Renderer[this.renderers[i]];
if (rendererClass && rendererClass.prototype.supported()) {
this.renderer = new rendererClass(this.div);
break;
this.renderer = new rendererClass(this.div, this.yOrdering);
break;
}
}
},
@@ -332,7 +339,7 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
this.renderer.setExtent(extent);
this.renderer.root.style.visibility = "visible";
// Force a reflow on gecko based browsers to prevent jump/flicker.
// This seems to happen on only certain configurations; it was originally
// noticed in FF 2.0 and Linux.
+440 -36
View File
@@ -6,6 +6,292 @@
* @requires OpenLayers/Renderer.js
*/
/**
* Class: OpenLayers.ElementsIndexer
* This class takes care of figuring out which order elements should be
* placed in the DOM based on given indexing methods.
*/
OpenLayers.ElementsIndexer = OpenLayers.Class({
/**
* Property: maxZIndex
* {Integer} This is the largest-most z-index value for a node
* contained within the indexer.
*/
maxZIndex: null,
/**
* Property: order
* {Array<String>} This is an array of node id's stored in the
* order that they should show up on screen. Id's higher up in the
* array (higher array index) represent nodes with higher z-indeces.
*/
order: null,
/**
* Property: indices
* {Object} This is a hash that maps node ids to their z-index value
* stored in the indexer. This is done to make finding a nodes z-index
* value O(1).
*/
indices: null,
/**
* Property: compare
* {Function} This is the function used to determine placement of
* of a new node within the indexer. If null, this defaults to to
* the Z_ORDER_DRAWING_ORDER comparison method.
*/
compare: null,
/**
* APIMethod: initialize
* Create a new indexer with
*
* Parameters:
* yOrdering - {Boolean} Whether to use y-ordering.
*/
initialize: function(yOrdering) {
this.compare = yOrdering ?
OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
this.order = [];
this.indices = {};
this.maxZIndex = 0;
},
/**
* APIMethod: insert
* Insert a new node into the indexer. In order to find the correct
* positioning for the node to be inserted, this method uses a binary
* search. This makes inserting O(log(n)).
*
* Parameters:
* newNode - {DOMElement} The new node to be inserted.
* root - {DOMElement} The root node from which to insert the new node.
*/
insert: function(newNode, root) {
var nodeId = newNode.id;
this.determineZIndex(newNode);
var leftIndex = -1;
var rightIndex = this.order.length;
var middle;
while (rightIndex - leftIndex > 1) {
middle = parseInt((leftIndex + rightIndex) / 2);
var nextId = this.order[middle];
var nextNode = OpenLayers.Util.getElement(nextId);
var placement = this.compare(this, newNode, nextNode);
if (placement > 0) {
leftIndex = middle;
} else {
rightIndex = middle;
}
}
this.order.splice(rightIndex, 0, nodeId);
this.indices[nodeId] = this.getZIndex(newNode);
// If the new node should be before another in the index
// order, insert the new node before the next; else, lets just
// append the new one on the end, making it the highest in the index order.
var nextIndex = rightIndex + 1;
if (nextIndex < this.order.length) {
var nextNode = OpenLayers.Util.getElement(this.order[nextIndex]);
root.insertBefore(newNode, nextNode);
} else {
root.appendChild(newNode);
}
},
/**
* APIMethod: remove
*
* Parameters:
* node - {DOMElement} The node to be removed.
*/
remove: function(node) {
var nodeId = node.id;
var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
if (arrayIndex >= 0) {
// Remove it from the order array, as well as deleting the node
// from the indeces hash.
this.order.splice(arrayIndex, 1);
delete this.indices[nodeId];
// Reset the maxium z-index based on the last item in the
// order array.
var lastId = this.order[this.order.length - 1];
this.maxZIndex = this.indices[lastId];
}
},
/**
* APIMethod: exists
*
* Parameters:
* node- {DOMElement} The node to test for existence.
*
* Returns:
* {Boolean} Whether or not the node exists in the indexer?
*/
exists: function(node) {
return (this.indices[node.id] != null);
},
/**
* APIMethod: getZIndex
* Get the z-index value for the current node from the node data itself.
*
* Parameters:
* node - {DOMElement} The node whose z-index to get.
*
* Returns:
* {Integer} The z-index value for the specified node (from the node
* data itself).
*/
getZIndex: function(node) {
return node._style.graphicZIndex;
},
/**
* Method: determineZIndex
* Determine the z-index for the current node if there isn't one,
* and set the maximum value if we've found a new maximum.
*
* Parameters:
* node - {DOMElement}
*/
determineZIndex: function(node) {
var zIndex = node._style.graphicZIndex;
// Everything must have a zIndex. If none is specified,
// this means the user *must* (hint: assumption) want this
// node to succomb to drawing order. To enforce drawing order
// over all indexing methods, we'll create a new z-index that's
// greater than any currently in the indexer.
if (zIndex == null) {
zIndex = this.maxZIndex;
node._style.graphicZIndex = zIndex;
} else if (zIndex > this.maxZIndex) {
this.maxZIndex = zIndex;
}
},
CLASS_NAME: "OpenLayers.ElementsIndexer"
});
/**
* Namespace: OpenLayers.ElementsIndexer.IndexingMethods
* These are the compare methods for figuring out where a new node should be
* placed within the indexer. These methods are very similar to general
* sorting methods in that they return -1, 0, and 1 to specify the
* direction in which new nodes fall in the ordering.
*/
OpenLayers.ElementsIndexer.IndexingMethods = {
/**
* Method: Z_ORDER
* This compare method is used by other comparison methods.
* It can be used individually for ordering, but is not recommended,
* because it doesn't subscribe to drawing order.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER: function(indexer, newNode, nextNode) {
var newZIndex = indexer.getZIndex(newNode);
var returnVal = 0;
if (nextNode) {
var nextZIndex = indexer.getZIndex(nextNode);
returnVal = newZIndex - nextZIndex;
}
return returnVal;
},
/**
* APIMethod: Z_ORDER_DRAWING_ORDER
* This method orders nodes by their z-index, but does so in a way
* that, if there are other nodes with the same z-index, the newest
* drawn will be the front most within that z-index. This is the
* default indexing method.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
indexer,
newNode,
nextNode
);
// Make Z_ORDER subscribe to drawing order by pushing it above
// all of the other nodes with the same z-index.
if (nextNode && returnVal == 0) {
returnVal = 1;
}
return returnVal;
},
/**
* APIMethod: Z_ORDER_Y_ORDER
* This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
* best describes which ordering methods have precedence (though, the
* name would be too long). This method orders nodes by their z-index,
* but does so in a way that, if there are other nodes with the same
* z-index, the nodes with the lower y position will be "closer" than
* those with a higher y position. If two nodes have the exact same y
* position, however, then this method will revert to using drawing
* order to decide placement.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
indexer,
newNode,
nextNode
);
if (nextNode && returnVal == 0) {
var newLat = newNode._geometry.getBounds().bottom;
var nextLat = nextNode._geometry.getBounds().bottom;
var result = nextLat - newLat;
returnVal = (result ==0) ? 1 : result;
}
return returnVal;
}
};
/**
* Class: OpenLayers.Renderer.Elements
* This is another virtual class in that it should never be instantiated by
@@ -41,6 +327,19 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
*/
xmlns: null,
/**
* Property: Indexer
* {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
* created upon initialization.
*/
indexer: null,
/**
* Constant: BACKGROUND_ID_SUFFIX
* {String}
*/
BACKGROUND_ID_SUFFIX: "_background",
/**
* Property: minimumSymbolizer
* {Object}
@@ -57,8 +356,9 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
*
* Parameters:
* containerID - {String}
* yOrdering - {Boolean} Whether or not y-ordering is enabled.
*/
initialize: function(containerID) {
initialize: function(containerID, yOrdering) {
OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
this.rendererRoot = this.createRenderRoot();
@@ -66,6 +366,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
this.rendererRoot.appendChild(this.root);
this.container.appendChild(this.rendererRoot);
this.indexer = new OpenLayers.ElementsIndexer(yOrdering);
},
/**
@@ -135,22 +437,13 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
return;
};
//
if (style.backgroundGraphic) {
this.redrawBackgroundNode(geometry.id, geometry, style, featureId);
}
if (style.display != "none") {
//first we create the basic node and add it to the root
var nodeType = this.getNodeType(geometry, style);
var node = this.nodeFactory(geometry.id, nodeType);
node._featureId = featureId;
node._geometryClass = geometry.CLASS_NAME;
node._style = style;
//now actually draw the node, and style it
node = this.drawGeometryNode(node, geometry);
// append the node to root (but only if it's new)
if (node.parentNode != this.root) {
this.root.appendChild(node);
}
this.postDraw(node);
this.redrawNode(geometry.id, geometry, style, featureId);
} else {
node = OpenLayers.Util.getElement(geometry.id);
if (node) {
@@ -158,6 +451,90 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
}
}
},
/**
* Method: redrawNode
*
* Parameters:
* id - {String}
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
redrawNode: function(id, geometry, style, featureId) {
// Get the node if it's already on the map.
var currentNode = OpenLayers.Util.getElement(id);
// Create a new node, or use the current one if it's
// already there.
var newNode;
if (!currentNode) {
var nodeType = this.getNodeType(geometry, style);
newNode = this.createNode(nodeType, id);
} else {
newNode = currentNode;
}
// Set the data for the node, then draw it.
newNode._featureId = featureId;
newNode._geometry = geometry;
newNode._geometryClass = geometry.CLASS_NAME;
newNode._style = style;
newNode = this.drawGeometryNode(newNode, geometry, style);
// If the node is known to the indexer, remove it so we can
// recalculate where it should go.
if (this.indexer.exists(newNode)) {
this.indexer.remove(newNode);
}
// Insert the node into the indexer so it can show us where to place it.
// Note that this operation is O(log(n)). If there's a performance
// problem (when dragging, for instance) this is likely where it
// would be.
this.indexer.insert(newNode, this.root);
this.postDraw(newNode);
},
/**
* Method: redrawBackgroundNode
* Redraws the node using special 'background' style properties. Basically
* just calls redrawNode(), but instead of directly using the
* 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
* 'graphicZIndex' properties directly from the specified 'style'
* parameter, we create a new style object and set those properties
* from the corresponding 'background'-prefixed properties from
* specified 'style' parameter.
*
* Parameters:
* id - {String}
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
redrawBackgroundNode: function(id, geometry, style, featureId) {
var backgroundStyle = OpenLayers.Util.extend({}, style);
// Set regular style attributes to apply to the background styles.
backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
// Erase background styles.
backgroundStyle.backgroundGraphic = null;
backgroundStyle.backgroundXOffset = null;
backgroundStyle.backgroundYOffset = null;
backgroundStyle.backgroundGraphicZIndex = null;
this.redrawNode(
id + this.BACKGROUND_ID_SUFFIX,
geometry,
backgroundStyle,
null
);
},
/**
* Method: drawGeometryNode
@@ -213,7 +590,7 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: postDraw
* Things that have do be done after the geometry node is appended
* to its parent node. To be overridden by subclasses.
* to its parent node. To be overridden by subclasses.
*
* Parameters:
* node - {DOMElement}
@@ -223,8 +600,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawPoint
* Virtual function for drawing Point Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -235,8 +612,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawLineString
* Virtual function for drawing LineString Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -247,8 +624,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawLinearRing
* Virtual function for drawing LinearRing Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -259,8 +636,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawPolygon
* Virtual function for drawing Polygon Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -271,8 +648,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawRectangle
* Virtual function for drawing Rectangle Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -283,8 +660,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawCircle
* Virtual function for drawing Circle Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -295,8 +672,8 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Method: drawSurface
* Virtual function for drawing Surface Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
@@ -312,7 +689,7 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
*
* Returns:
* {<OpenLayers.Geometry>} A geometry from an event that
* happened on a layer
* happened on a layer.
*/
getFeatureIdFromEvent: function(evt) {
var node = evt.target || evt.srcElement;
@@ -340,10 +717,24 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
} else {
var element = OpenLayers.Util.getElement(geometry.id);
if (element && element.parentNode) {
if (element.geometry) {
element.geometry.destroy();
element.geometry = null;
}
element.parentNode.removeChild(element);
this.indexer.remove(element);
var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
var bElem = OpenLayers.Util.getElement(backgroundId);
if (bElem && bElem.parentNode) {
// No need to destroy the geometry since the element and the background
// node share the same geometry.
bElem.parentNode.removeChild(bElem);
}
}
}
},
},
/**
* Method: nodeFactory
@@ -354,10 +745,10 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
*
* Parameters:
* id - {String}
* type - {String} type Kind of node to draw
* type - {String} type Kind of node to draw.
*
* Returns:
* {DOMElement} A new node of the given type and id
* {DOMElement} A new node of the given type and id.
*/
nodeFactory: function(id, type) {
var node = OpenLayers.Util.getElement(id);
@@ -372,6 +763,19 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
return node;
},
/**
* Method: createNode
*
* Parameters:
* type - {String} Kind of node to draw.
* id - {String} Id for node.
*
* Returns:
* {DOMElement} A new node of the given type and id.
* This function must be overridden by subclasses.
*/
createNode: function(type, id) {},
/**
* Method: isComplexSymbol
* Determines if a symbol cannot be rendered using drawCircle
@@ -385,7 +789,7 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
isComplexSymbol: function(graphicName) {
return (graphicName != "circle") && !!graphicName;
},
CLASS_NAME: "OpenLayers.Renderer.Elements"
});