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

141
examples/marker-shadow.html Normal file
View File

@@ -0,0 +1,141 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
<link rel="stylesheet" href="style.css" type="text/css" />
<style type="text/css">
.smallmap {
width: 300px;
}
.docs {
padding: 0px 5px;
}
td {
vertical-align: top;
}
</style>
<script src="../lib/OpenLayers.js" type="text/javascript"></script>
<script type="text/javascript">
var SHADOW_Z_INDEX = 10;
var MARKER_Z_INDEX = 11;
var DIAMETER = 200;
var NUMBER_OF_FEATURES = 15;
var map, layer;
function init() {
map = new OpenLayers.Map("map");
layer = new OpenLayers.Layer.Vector(
"Marker Drop Shadows",
{
styleMap: new OpenLayers.StyleMap({
// Set the external graphic and background graphic images.
externalGraphic: "../img/marker-gold.png",
backgroundGraphic: "./marker_shadow.png",
// Makes sure the background graphic is placed correctly relative
// to the external graphic.
backgroundXOffset: 0,
backgroundYOffset: -7,
// Set the z-indexes of both graphics to make sure the background
// graphics stay in the background (shadows on top of markers looks
// odd; let's not do that).
graphicZIndex: MARKER_Z_INDEX,
backgroundGraphicZIndex: SHADOW_Z_INDEX,
pointRadius: 10
}),
isBaseLayer: true,
yOrdering: true
}
);
map.addLayers([layer]);
// Add a drag feature control to move features around.
var dragFeature = new OpenLayers.Control.DragFeature(layer);
map.addControl(dragFeature);
dragFeature.activate();
map.zoomToMaxExtent();
drawFeatures();
}
function drawFeatures() {
layer.removeFeatures(layer.features);
// Create features at random around the center.
var center = map.getViewPortPxFromLonLat(map.getCenter());
// Add the ordering features. These are the gold ones that all have the same z-index
// and succomb to y-ordering.
var features = [];
for (var index = 0; index < NUMBER_OF_FEATURES; index++) {
// Calculate a random x/y. Subtract half the diameter to make some
// features negative.
var x = (parseInt(Math.random() * DIAMETER)) - (DIAMETER / 2);
var y = (parseInt(Math.random() * DIAMETER)) - (DIAMETER / 2);
var pixel = new OpenLayers.Pixel(center.x + x, center.y + y);
var lonLat = map.getLonLatFromViewPortPx(pixel);
features.push(
new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
)
);
}
layer.addFeatures(features);
}
</script>
</head>
<body onload="init()">
<h1 id="title">Marker Shadows using Background Graphics/Z-Indexes</h1>
<div id="tags">
</div>
<p id="shortdesc">
This example shows off marker shadows using background graphics and z-indexes. Move the features around to show the shadows' interaction.
</p>
<br>
<table>
<tr>
<td>
<div id="map" class="smallmap"></div>
</td>
<td>
<div class="docs">
The features in this map were generated at random. Each of these features have a <i>backgroundGraphic</i> property set in the style map to add a shadow image. Note that the background graphics are not duplicated features with a different style.
<br><br>
The shadows were set to have a different z-index than the markers themselves, using the <i>backgroundGraphicZIndex</i> property. This makes sure all shadows stay behind the markers, keeping a clean look. The shadows were also placed nicely relative to the external graphic using the <i>backgroundXOffset</i> and <i>backgroundYOffset</i> property.
<br><br>
Y-ordering on the layer is enabled. See the <a href="./ordering.html">ordering example</a>.
</div>
</td>
</tr>
<tr>
<td>
<button onclick="drawFeatures()">Redraw Features</button>
</td>
</tr>
</table>
</body>
</html>

BIN
examples/marker_shadow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

203
examples/ordering.html Normal file
View File

@@ -0,0 +1,203 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
<link rel="stylesheet" href="style.css" type="text/css" />
<style type="text/css">
.smallmap {
width: 300px;
}
.docs {
padding: 0px 5px;
}
td {
vertical-align: top;
}
</style>
<script src="../lib/OpenLayers.js" type="text/javascript"></script>
<script type="text/javascript">
var GOLD_Z_INDEX = 15;
var FIRST_RED_Z_INDEX = 10;
var SECOND_RED_Z_INDEX = 11;
var RADIUS_FROM_CENTER = 40;
var POINT_DISTANCE = 10;
function initYOrderMap() {
var map = new OpenLayers.Map("yorder");
var layer = new OpenLayers.Layer.Vector(
"Y-Order",
{
styleMap: new OpenLayers.StyleMap({
externalGraphic: "../img/marker-gold.png",
pointRadius: 10,
graphicZIndex: GOLD_Z_INDEX
}),
isBaseLayer: true,
yOrdering: true
}
);
map.addLayers([layer]);
map.zoomToMaxExtent();
// Add features to the layers to show off z-index/y-ordering.
// We do this after adding the layer so we can work in pixels.
var center = map.getViewPortPxFromLonLat(map.getCenter());
var top = new OpenLayers.Pixel(center.x, center.y - RADIUS_FROM_CENTER);
var bottom = new OpenLayers.Pixel(center.x, center.y + RADIUS_FROM_CENTER);
var left = new OpenLayers.Pixel(center.x - RADIUS_FROM_CENTER, center.y - POINT_DISTANCE / 2);
var right = new OpenLayers.Pixel(center.x + RADIUS_FROM_CENTER, center.y - POINT_DISTANCE / 2);
// Add the ordering features. These are the gold ones that all have the same z-index
// and succomb to y-ordering.
var orderingFeatures = [];
// Note: We use > here on purpose (instead of >= ), as well as subtracting the
// the POINT_DISTANCE in the beginning of the loop (as opposed to the end).
// This is purely for symmetry. Also note that the gold features are drawn
// from bottom to top so as to quickly signal whether or not y-ordering is working.
while (bottom.y > top.y) {
bottom.y -= POINT_DISTANCE;
var lonLat = map.getLonLatFromViewPortPx(bottom);
orderingFeatures.push(
new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
)
);
}
layer.addFeatures(orderingFeatures);
// Add the z-index features. Technically, these features succomb to y-ordering
// as well; however, since they have different z-indexes, the z-indexes take
// precedence.
var indexFeatures = [];
var useFirst = true;
while (left.x <= right.x) {
var lonLat = map.getLonLatFromViewPortPx(left);
var point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
);
// This is where the magic happens. We override the style on the layer
// to give our own style with alternativing z-indexes.
point.style = {
graphicZIndex: useFirst ? FIRST_RED_Z_INDEX : SECOND_RED_Z_INDEX,
externalGraphic: "../img/marker.png",
pointRadius: 10
}
indexFeatures.push(
point
);
left.x += POINT_DISTANCE;
useFirst = !useFirst;
}
layer.addFeatures(indexFeatures);
}
function initDrawingOrderMap() {
var map = new OpenLayers.Map("drawingorder");
var layer = new OpenLayers.Layer.Vector(
"Drawing Order",
{
// Note there's no z-index set, and yOrdering is left
// to its default.
styleMap: new OpenLayers.StyleMap({
externalGraphic: "../img/marker-green.png",
pointRadius: 10
}),
isBaseLayer: true
}
);
map.addLayers([layer]);
map.zoomToMaxExtent();
// Add features to the layers to show off z-index/y-ordering.
// We do this after adding the layer so we can work in pixels.
var center = map.getViewPortPxFromLonLat(map.getCenter());
var top = new OpenLayers.Pixel(center.x, center.y - RADIUS_FROM_CENTER);
var bottom = new OpenLayers.Pixel(center.x, center.y + RADIUS_FROM_CENTER);
var left = new OpenLayers.Pixel(center.x - RADIUS_FROM_CENTER, center.y);
var right = new OpenLayers.Pixel(center.x + RADIUS_FROM_CENTER, center.y);
// Add the ordering features. These are the gold ones that all have the same z-index
// and succomb to y-ordering.
var orderingFeatures = [];
while (bottom.y > top.y && left.x < right.x) {
var bottomLonLat = map.getLonLatFromViewPortPx(bottom);
var leftLonLat = map.getLonLatFromViewPortPx(left);
orderingFeatures.push(
new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(leftLonLat.lon, bottomLonLat.lat)
)
);
bottom.y -= POINT_DISTANCE / 2; // Divide by 2 for better visual.
left.x += POINT_DISTANCE / 2;
}
layer.addFeatures(orderingFeatures);
}
function init(){
initYOrderMap();
initDrawingOrderMap();
};
</script>
</head>
<body onload="init()">
<h1 id="title">Z-Index/Y-Order Example</h1>
<div id="tags">
</div>
<p id="shortdesc">
This example shows the use of z-indexing and y-ordering of external graphics. Zoom in and out to see this behavior.
</p>
<h3>Z-Index (with Y-Ordering enabled)</h3>
<table>
<tr>
<td>
<div id="yorder" class="smallmap"></div>
</td>
<td>
<div class="docs">
In this map, the gold features all have the same z-index, and the red features have alternating z-indeces. The gold features' z-index is greater than the red features' z-indeces, which is why gold features look to be drawn on top of the red features. Since each gold feature has the same z-index, gold features succomb to y-ordering: this is where features that seem closest to the viewer (lower lattitude) show up above those that seem farther away (higher lattitude).
<br><br>
All vector layers have z-indexing enabled by default, but are not enabled with y-ordering. You can enable y-ordering by passing the parameter <i>yOrdering: true</i> in the vector layer's options hash. For all configurations, if features have the same z-index -- and if y-ordering is enabled: the same lattitude -- those features will succomb to drawing order, where the last feature to be drawn will appear above the rest.
</div>
</td>
</tr>
</table>
<br>
<h3>Drawing Order (no Z-Indexes set, and Y-Ordering disabled)</h3>
<table>
<tr>
<td>
<div id="drawingorder" class="smallmap"></div>
</td>
<td>
<div class="docs">
In this map, features are not given z-indexes, and the layer's <i>yOrdering</i> parameter is set to the default (false). This configuration makes features succomb to drawing order instead of z-index order or y-order.
<br><br>
The features in this map were drawn from left to right and bottom to top, diagonally, to show that y-ordering is not enabled.
</div>
</td>
</tr>
</table>
</body>
</html>

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.

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"
});

View File

@@ -3,12 +3,9 @@
<script src="../../lib/OpenLayers.js"></script>
<script type="text/javascript">
function test_Elements_constructor(t) {
t.plan(5);
var el = document.createElement('div');
document.body.appendChild(el);
el.id = "foo";
function setUp() {
// Stub out functions that are meant to be overridden by
// subclasses.
OpenLayers.Renderer.Elements.prototype._createRenderRoot =
OpenLayers.Renderer.Elements.prototype.createRenderRoot;
@@ -25,69 +22,97 @@
return root;
};
var r = new OpenLayers.Renderer.Elements("foo");
OpenLayers.Renderer.Elements.prototype._createNode =
OpenLayers.Renderer.Elements.prototype.createNode;
t.ok(r instanceof OpenLayers.Renderer.Elements, "new OpenLayers.Renderer.Elements returns Elements object" );
t.ok(r.rendererRoot != null, "elements rendererRoot is not null");
t.ok(r.root != null, "elements root is not null");
OpenLayers.Renderer.Elements.prototype.createNode = function() {
return document.createElement("div");
};
}
// Create a new Elements renderer based on an id and an ordering
// type. For these tests, both of these parameters are optional.
function create_renderer(id, ordering) {
t.ok(r.root.parentNode == rendererRoot, "elements root is correctly appended to rendererRoot");
t.ok(r.rendererRoot.parentNode == el, "elements rendererRoot is correctly appended to container");
rendererRoot = null;
if (id == null) {
var el = document.createElement('div');
document.body.appendChild(el);
el.id = OpenLayers.Util.createUniqueID();
id = el.id;
}
return new OpenLayers.Renderer.Elements(id, ordering);
}
// Cleanup stubs made in the function above.
function tearDown() {
OpenLayers.Renderer.Elements.prototype.createRenderRoot =
OpenLayers.Renderer.Elements.prototype._createRenderRoot;
OpenLayers.Renderer.Elements.prototype.createRoot =
OpenLayers.Renderer.Elements.prototype._createRoot;
OpenLayers.Renderer.Elements.prototype.createNode =
OpenLayers.Renderer.Elements.prototype._createNode;
}
function test_Elements_constructor(t) {
t.plan(6);
setUp();
var r = create_renderer();
t.ok(r instanceof OpenLayers.Renderer.Elements, "new OpenLayers.Renderer.Elements returns Elements object" );
t.ok(r.rendererRoot != null, "elements rendererRoot is not null");
t.ok(r.root != null, "elements root is not null");
t.ok(r.indexer != null, "indexer is not null.");
t.ok(r.root.parentNode == r.rendererRoot, "elements root is correctly appended to rendererRoot");
t.ok(r.rendererRoot.parentNode == r.container, "elements rendererRoot is correctly appended to container");
tearDown();
}
function test_Elements_destroy(t) {
t.plan(5);
OpenLayers.Renderer.Elements.prototype._initialize =
OpenLayers.Renderer.Elements.prototype.initialize;
OpenLayers.Renderer.Elements.prototype.initialize = function() {}
var g_Clear = false, g_Destroy = false;
var elems = {
'clear': function() {
t.ok(true, "clear called");
},
'rendererRoot': {},
'root': {},
'xmlns': {}
};
OpenLayers.Renderer.prototype._destroy =
OpenLayers.Renderer.prototype.destroy;
var args = [{}, {}, {}];
OpenLayers.Renderer.prototype.destroy = function() {
g_Destroy = true;
t.ok((arguments[0] == args[0]) &&
(arguments[1] == args[1]) &&
(arguments[2] == args[2]), "correct arguments passed to OpenLayers.Renderer.destroy()");
};
var r = new OpenLayers.Renderer.SVG(document.body);
r.clear = function() {
g_Clear = true;
};
r.rendererRoot = 'foo';
r.root = 'bar';
r.xmlns = 'dude';
r.destroy();
t.eq(g_Clear, true, "OpenLayers.Renderer.Elements.clear() called");
t.eq(r.rendererRoot, null, "rendererRoot nullified");
t.eq(r.root, null, "root nullified");
t.eq(r.xmlns, null, "xmlns nullified");
t.eq(g_Destroy, true, "OpenLayers.Renderer.destroy() called");
OpenLayers.Renderer.Elements.prototype.initialize =
OpenLayers.Renderer.Elements.prototype._initialize;
OpenLayers.Renderer.Elements.prototype.destroy.apply(elems, args);
t.ok(elems.rendererRoot == null, "rendererRoot nullified");
t.ok(elems.root == null, "root nullified");
t.ok(elems.xmlns == null, "xmlns nullified");
OpenLayers.Renderer.prototype.destroy =
OpenLayers.Renderer.prototype._destroy;
}
function test_Elements_clear(t) {
t.plan(1);
OpenLayers.Renderer.Elements.prototype._initialize =
OpenLayers.Renderer.Elements.prototype.initialize;
setUp();
OpenLayers.Renderer.Elements.prototype.initialize = function() {}
var r = new OpenLayers.Renderer.Elements();
var r = create_renderer();
var element = document.createElement("div");
r.root = element;
@@ -98,19 +123,15 @@
t.ok(r.root.childNodes.length == 0, "root is correctly cleared");
OpenLayers.Renderer.Elements.prototype.initialize =
OpenLayers.Renderer.Elements.prototype._initialize;
tearDown();
}
function test_Elements_drawGeometry(t) {
t.plan(5);
OpenLayers.Renderer.Elements.prototype._initialize =
OpenLayers.Renderer.Elements.prototype.initialize;
OpenLayers.Renderer.Elements.prototype.initialize = function() {};
var r = new OpenLayers.Renderer.Elements();
setUp();
var r = create_renderer();
var element = document.createElement("div");
r.root = element;
@@ -147,21 +168,16 @@
var style = {'display':'none'};
r.drawGeometry(geometry, style, featureId);
t.ok(g_Node.parentNode != r.root, "node is correctly removed");
OpenLayers.Util.getElement = _getElement;
OpenLayers.Renderer.Elements.prototype.initialize =
OpenLayers.Renderer.Elements.prototype._initialize;
tearDown();
}
function test_Elements_drawGeometry_2(t) {
t.plan(9);
OpenLayers.Renderer.Elements.prototype._initialize =
OpenLayers.Renderer.Elements.prototype.initialize;
OpenLayers.Renderer.Elements.prototype.initialize = function() {};
var r = new OpenLayers.Renderer.Elements();
setUp();
var r = create_renderer();
var element = document.createElement("div");
r.root = element;
@@ -281,9 +297,8 @@
style = true;
r.drawGeometry(geometry, style);
t.ok(properDraw, "drawGeometry called drawPolygon when passed a multi-polygon");
OpenLayers.Renderer.Elements.prototype.initialize =
OpenLayers.Renderer.Elements.prototype._initialize;
tearDown();
}
function test_Elements_getfeatureidfromevent(t) {
@@ -308,70 +323,134 @@
}
function test_Elements_erasegeometry(t) {
t.plan(5);
var el = document.createElement('div');
document.body.appendChild(el);
el.id = 'bar';
var geometry = {
id: 'bar'
t.plan(15);
var elements = {
'eraseGeometry': function(geometry) {
gErased.push(geometry);
}
};
OpenLayers.Renderer.Elements.prototype.eraseGeometry(geometry);
t.ok(el.parentNode != document.body, "element correctly removed");
var el = document.createElement('div');
document.body.appendChild(el);
el.id = 'bar';
var geometry = {
CLASS_NAME: "OpenLayers.Geometry.MultiPoint",
components: [{
id: 'bar'
}]
'components': [{}, {}, {}]
};
//multipoint
geometry.CLASS_NAME = "OpenLayers.Geometry.MultiPoint";
gErased = [];
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gErased[0] == geometry.components[0]) &&
(gErased[1] == geometry.components[1]) &&
(gErased[2] == geometry.components[2]), "multipoint all components of geometry correctly erased.");
//multilinestring
geometry.CLASS_NAME = "OpenLayers.Geometry.MultiLineString";
gErased = [];
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gErased[0] == geometry.components[0]) &&
(gErased[1] == geometry.components[1]) &&
(gErased[2] == geometry.components[2]), "multilinestring all components of geometry correctly erased.");
//multipolygon
geometry.CLASS_NAME = "OpenLayers.Geometry.MultiPolygon";
gErased = [];
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gErased[0] == geometry.components[0]) &&
(gErased[1] == geometry.components[1]) &&
(gErased[2] == geometry.components[2]), "multipolygon all components of geometry correctly erased.");
//collection
geometry.CLASS_NAME = "OpenLayers.Geometry.Collection";
gErased = [];
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gErased[0] == geometry.components[0]) &&
(gErased[1] == geometry.components[1]) &&
(gErased[2] == geometry.components[2]), "collection all components of geometry correctly erased.");
// OTHERS
//
geometry.CLASS_NAME = {};
gElement = null;
gBackElement = null;
OpenLayers.Util._getElement = OpenLayers.Util.getElement;
OpenLayers.Util.getElement = function(id) {
var retVal = null;
if (id != null) {
var hasBack = (id.indexOf(elements.BACKGROUND_ID_SUFFIX) != -1);
retVal = hasBack ? gBackElement : gElement;
}
return retVal;
};
OpenLayers.Renderer.Elements.prototype.eraseGeometry(geometry);
t.ok(el.parentNode != document.body, "geometry components correctly removed when passed a multipoint");
var el = document.createElement('div');
document.body.appendChild(el);
el.id = 'bar';
var geometry = {
CLASS_NAME: "OpenLayers.Geometry.MultiLineString",
components: [{
id: 'bar'
}]
//element null
geometry.id = null;
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
// (no tests here, just make sure it doesn't bomb)
//element.parentNode null
elements.BACKGROUND_ID_SUFFIX = 'BLAHBLAHBLAH';
geometry.id = "foo";
gElement = {};
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
// (no tests here, just make sure it doesn't bomb)
//valid element.parentNode, element.geometry
elements.indexer = {
'remove': function(elem) {
gIndexerRemoved = elem;
}
};
OpenLayers.Renderer.Elements.prototype.eraseGeometry(geometry);
t.ok(el.parentNode != document.body, "geometry components correctly removed when passed a multilinestring");
var el = document.createElement('div');
document.body.appendChild(el);
el.id = 'bar';
var geometry = {
CLASS_NAME: "OpenLayers.Geometry.MultiPolygon",
components: [{
id: 'bar'
}]
gElement = {
'geometry': {
'destroy': function() {
t.ok(true, "geometry destroyed");
}
},
'parentNode': {
'removeChild': function(elem) {
gElemRemoved = elem;
}
}
};
OpenLayers.Renderer.Elements.prototype.eraseGeometry(geometry);
t.ok(el.parentNode != document.body, "geometry components correctly removed when passed a multipolygon");
var el = document.createElement('div');
document.body.appendChild(el);
el.id = 'bar';
var geometry = {
CLASS_NAME: "OpenLayers.Geometry.Collection",
components: [{
id: 'bar'
}]
gBackElement = {
'parentNode': {
'removeChild': function(elem) {
gBackRemoved = elem;
}
}
};
OpenLayers.Renderer.Elements.prototype.eraseGeometry(geometry);
t.ok(el.parentNode != document.body, "geometry components correctly removed when passed a collection");
}
gElemRemoved = gBackRemoved = gIndexerRemoved = null;
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gElement.geometry == null), "all normal: element's 'geometry' property nullified");
t.ok( (gElemRemoved == gElement), "all normal: main element properly removed from parent node");
t.ok( (gBackRemoved == gBackElement), "all normal: back element properly removed from parent node");
t.ok( (gIndexerRemoved == gElement), "all normal: main element properly removed from the indexer");
//valid element.parentNode, no element.geometry, no bElem
gBackElement = null;
gElement.geometry = null;
gElemRemoved = gBackRemoved = gIndexerRemoved = null;
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gElemRemoved == gElement), "no bElem: main element properly removed from parent node");
t.ok( (gBackRemoved == null), "no bElem: back element not tried to remove from parent node when it doesn't exist");
t.ok( (gIndexerRemoved == gElement), "no bElem: main element properly removed from the indexer");
//valid element.parentNode, no element.geometry, valid bElem, no bElem.parentNode
gBackElement = {};
gElemRemoved = gBackRemoved = gIndexerRemoved = null;
OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
t.ok( (gElemRemoved == gElement), "no bElem.parentNode: main element properly removed from parent node");
t.ok( (gBackRemoved == null), "no bElem.parentNode: back element not tried to remove from parent node when it has no parent node");
t.ok( (gIndexerRemoved == gElement), "no bElem.parentNode: main element properly removed from the indexer");
OpenLayers.Util.getElement = OpenLayers.Util._getElement;
}
</script>
</head>