This is mostly done to make SVG and VML tests pass again, but it may also help existing applications that override setExtent or drawFeature without calling the superclass methods properly.
1075 lines
34 KiB
JavaScript
1075 lines
34 KiB
JavaScript
/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
|
|
* full list of contributors). Published under the Clear BSD license.
|
|
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
|
* full text of the license. */
|
|
|
|
/**
|
|
* @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.clear();
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Returns
|
|
* {DOMElement} the node before which we should insert our newNode, or
|
|
* null if newNode can just be appended.
|
|
*/
|
|
insert: function(newNode) {
|
|
// If the node is known to the indexer, remove it so we can
|
|
// recalculate where it should go.
|
|
if (this.exists(newNode)) {
|
|
this.remove(newNode);
|
|
}
|
|
|
|
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 placement = this.compare(this, newNode,
|
|
OpenLayers.Util.getElement(this.order[middle]));
|
|
|
|
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, return the node before which we have to insert the new one;
|
|
// else, return null to indicate that the new node can be appended.
|
|
return this.getNextElement(rightIndex);
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
if (this.order.length > 0) {
|
|
var lastId = this.order[this.order.length - 1];
|
|
this.maxZIndex = this.indices[lastId];
|
|
} else {
|
|
this.maxZIndex = 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: clear
|
|
*/
|
|
clear: function() {
|
|
this.order = [];
|
|
this.indices = {};
|
|
this.maxZIndex = 0;
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: getNextElement
|
|
* Get the next element in the order stack.
|
|
*
|
|
* Parameters:
|
|
* index - {Integer} The index of the current node in this.order.
|
|
*
|
|
* Returns:
|
|
* {DOMElement} the node following the index passed in, or
|
|
* null.
|
|
*/
|
|
getNextElement: function(index) {
|
|
var nextIndex = index + 1;
|
|
if (nextIndex < this.order.length) {
|
|
var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
|
|
if (nextElement == undefined) {
|
|
nextElement = this.getNextElement(nextIndex);
|
|
}
|
|
return nextElement;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
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 result = nextNode._boundsBottom - newNode._boundsBottom;
|
|
returnVal = (result === 0) ? 1 : result;
|
|
}
|
|
|
|
return returnVal;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Class: OpenLayers.Renderer.Elements
|
|
* This is another virtual class in that it should never be instantiated by
|
|
* itself as a Renderer. It exists because there is *tons* of shared
|
|
* functionality between different vector libraries which use nodes/elements
|
|
* as a base for rendering vectors.
|
|
*
|
|
* The highlevel bits of code that are implemented here are the adding and
|
|
* removing of geometries, which is essentially the same for any
|
|
* element-based renderer. The details of creating each node and drawing the
|
|
* paths are of course different, but the machinery is the same.
|
|
*
|
|
* Inherits:
|
|
* - <OpenLayers.Renderer>
|
|
*/
|
|
OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
|
|
|
|
/**
|
|
* Property: rendererRoot
|
|
* {DOMElement}
|
|
*/
|
|
rendererRoot: null,
|
|
|
|
/**
|
|
* Property: root
|
|
* {DOMElement}
|
|
*/
|
|
root: null,
|
|
|
|
/**
|
|
* Property: vectorRoot
|
|
* {DOMElement}
|
|
*/
|
|
vectorRoot: null,
|
|
|
|
/**
|
|
* Property: textRoot
|
|
* {DOMElement}
|
|
*/
|
|
textRoot: null,
|
|
|
|
/**
|
|
* Property: xmlns
|
|
* {String}
|
|
*/
|
|
xmlns: null,
|
|
|
|
/**
|
|
* Property: xOffset
|
|
* {Number} Offset to apply to the renderer viewport translation in x
|
|
* direction. If the renderer extent's center is on the right of the
|
|
* dateline (i.e. exceeds the world bounds), we shift the viewport to the
|
|
* left by one world width. This avoids that features disappear from the
|
|
* map viewport. Because our dateline handling logic in other places
|
|
* ensures that extents crossing the dateline always have a center
|
|
* exceeding the world bounds on the left, we need this offset to make sure
|
|
* that the same is true for the renderer extent in pixel space as well.
|
|
*/
|
|
xOffset: 0,
|
|
|
|
/**
|
|
* Property: rightOfDateLine
|
|
* {Boolean} Keeps track of the location of the map extent relative to the
|
|
* date line. The <setExtent> method compares this value (which is the one
|
|
* from the previous <setExtent> call) with the current position of the map
|
|
* extent relative to the date line and updates the xOffset when the extent
|
|
* has moved from one side of the date line to the other.
|
|
*/
|
|
|
|
/**
|
|
* Property: Indexer
|
|
* {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
|
|
* created upon initialization if the zIndexing or yOrdering options
|
|
* passed to this renderer's constructor are set to true.
|
|
*/
|
|
indexer: null,
|
|
|
|
/**
|
|
* Constant: BACKGROUND_ID_SUFFIX
|
|
* {String}
|
|
*/
|
|
BACKGROUND_ID_SUFFIX: "_background",
|
|
|
|
/**
|
|
* Constant: LABEL_ID_SUFFIX
|
|
* {String}
|
|
*/
|
|
LABEL_ID_SUFFIX: "_label",
|
|
|
|
/**
|
|
* Constructor: OpenLayers.Renderer.Elements
|
|
*
|
|
* Parameters:
|
|
* containerID - {String}
|
|
* options - {Object} options for this renderer. Supported options are:
|
|
* * yOrdering - {Boolean} Whether to use y-ordering
|
|
* * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
|
|
* if yOrdering is set to true.
|
|
*/
|
|
initialize: function(containerID, options) {
|
|
OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
|
|
|
|
this.rendererRoot = this.createRenderRoot();
|
|
this.root = this.createRoot("_root");
|
|
this.vectorRoot = this.createRoot("_vroot");
|
|
this.textRoot = this.createRoot("_troot");
|
|
|
|
this.root.appendChild(this.vectorRoot);
|
|
this.root.appendChild(this.textRoot);
|
|
|
|
this.rendererRoot.appendChild(this.root);
|
|
this.container.appendChild(this.rendererRoot);
|
|
|
|
if(options && (options.zIndexing || options.yOrdering)) {
|
|
this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: destroy
|
|
*/
|
|
destroy: function() {
|
|
|
|
this.clear();
|
|
|
|
this.rendererRoot = null;
|
|
this.root = null;
|
|
this.xmlns = null;
|
|
|
|
OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Method: clear
|
|
* Remove all the elements from the root
|
|
*/
|
|
clear: function() {
|
|
var child;
|
|
var root = this.vectorRoot;
|
|
if (root) {
|
|
while (child = root.firstChild) {
|
|
root.removeChild(child);
|
|
}
|
|
}
|
|
root = this.textRoot;
|
|
if (root) {
|
|
while (child = root.firstChild) {
|
|
root.removeChild(child);
|
|
}
|
|
}
|
|
if (this.indexer) {
|
|
this.indexer.clear();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: setExtent
|
|
* Set the visible part of the layer.
|
|
*
|
|
* Parameters:
|
|
* extent - {<OpenLayers.Bounds>}
|
|
* resolutionChanged - {Boolean}
|
|
*
|
|
* Returns:
|
|
* {Boolean} true to notify the layer that the new extent does not exceed
|
|
* the coordinate range, and the features will not need to be redrawn.
|
|
* False otherwise.
|
|
*/
|
|
setExtent: function(extent, resolutionChanged) {
|
|
var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
|
|
var resolution = this.getResolution();
|
|
if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
|
|
coordSysUnchanged = this.featureDx === 0;
|
|
var rightOfDateLine,
|
|
world = this.map.getMaxExtent();
|
|
if (world.right > extent.left && world.right < extent.right) {
|
|
rightOfDateLine = true;
|
|
} else if (world.left > extent.left && world.left < extent.right) {
|
|
rightOfDateLine = false;
|
|
}
|
|
if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
|
|
coordSysUnchanged = false;
|
|
this.xOffset = rightOfDateLine === true ?
|
|
world.getWidth() / resolution : 0;
|
|
}
|
|
this.rightOfDateLine = rightOfDateLine;
|
|
}
|
|
return coordSysUnchanged;
|
|
},
|
|
|
|
/**
|
|
* Method: getNodeType
|
|
* This function is in charge of asking the specific renderer which type
|
|
* of node to create for the given geometry and style. All geometries
|
|
* in an Elements-based renderer consist of one node and some
|
|
* attributes. We have the nodeFactory() function which creates a node
|
|
* for us, but it takes a 'type' as input, and that is precisely what
|
|
* this function tells us.
|
|
*
|
|
* Parameters:
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* style - {Object}
|
|
*
|
|
* Returns:
|
|
* {String} The corresponding node type for the specified geometry
|
|
*/
|
|
getNodeType: function(geometry, style) { },
|
|
|
|
/**
|
|
* Method: drawGeometry
|
|
* Draw the geometry, creating new nodes, setting paths, setting style,
|
|
* setting featureId on the node. This method should only be called
|
|
* by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* style - {Object}
|
|
* featureId - {String}
|
|
*
|
|
* Returns:
|
|
* {Boolean} true if the geometry has been drawn completely; null if
|
|
* incomplete; false otherwise
|
|
*/
|
|
drawGeometry: function(geometry, style, featureId) {
|
|
var className = geometry.CLASS_NAME;
|
|
var rendered = true;
|
|
if ((className == "OpenLayers.Geometry.Collection") ||
|
|
(className == "OpenLayers.Geometry.MultiPoint") ||
|
|
(className == "OpenLayers.Geometry.MultiLineString") ||
|
|
(className == "OpenLayers.Geometry.MultiPolygon")) {
|
|
for (var i = 0, len=geometry.components.length; i<len; i++) {
|
|
rendered = this.drawGeometry(
|
|
geometry.components[i], style, featureId) && rendered;
|
|
}
|
|
return rendered;
|
|
};
|
|
|
|
rendered = false;
|
|
var removeBackground = false;
|
|
if (style.display != "none") {
|
|
if (style.backgroundGraphic) {
|
|
this.redrawBackgroundNode(geometry.id, geometry, style,
|
|
featureId);
|
|
} else {
|
|
removeBackground = true;
|
|
}
|
|
rendered = this.redrawNode(geometry.id, geometry, style,
|
|
featureId);
|
|
}
|
|
if (rendered == false) {
|
|
var node = document.getElementById(geometry.id);
|
|
if (node) {
|
|
if (node._style.backgroundGraphic) {
|
|
removeBackground = true;
|
|
}
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
if (removeBackground) {
|
|
var node = document.getElementById(
|
|
geometry.id + this.BACKGROUND_ID_SUFFIX);
|
|
if (node) {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
return rendered;
|
|
},
|
|
|
|
/**
|
|
* Method: redrawNode
|
|
*
|
|
* Parameters:
|
|
* id - {String}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* style - {Object}
|
|
* featureId - {String}
|
|
*
|
|
* Returns:
|
|
* {Boolean} true if the complete geometry could be drawn, null if parts of
|
|
* the geometry could not be drawn, false otherwise
|
|
*/
|
|
redrawNode: function(id, geometry, style, featureId) {
|
|
style = this.applyDefaultSymbolizer(style);
|
|
// Get the node if it's already on the map.
|
|
var node = this.nodeFactory(id, this.getNodeType(geometry, style));
|
|
|
|
// Set the data for the node, then draw it.
|
|
node._featureId = featureId;
|
|
node._boundsBottom = geometry.getBounds().bottom;
|
|
node._geometryClass = geometry.CLASS_NAME;
|
|
node._style = style;
|
|
|
|
var drawResult = this.drawGeometryNode(node, geometry, style);
|
|
if(drawResult === false) {
|
|
return false;
|
|
}
|
|
|
|
node = drawResult.node;
|
|
|
|
// 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.
|
|
if (this.indexer) {
|
|
var insert = this.indexer.insert(node);
|
|
if (insert) {
|
|
this.vectorRoot.insertBefore(node, insert);
|
|
} else {
|
|
this.vectorRoot.appendChild(node);
|
|
}
|
|
} else {
|
|
// if there's no indexer, simply append the node to root,
|
|
// but only if the node is a new one
|
|
if (node.parentNode !== this.vectorRoot){
|
|
this.vectorRoot.appendChild(node);
|
|
}
|
|
}
|
|
|
|
this.postDraw(node);
|
|
|
|
return drawResult.complete;
|
|
},
|
|
|
|
/**
|
|
* 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}
|
|
*
|
|
* Returns:
|
|
* {Boolean} true if the complete geometry could be drawn, null if parts of
|
|
* the geometry could not be drawn, false otherwise
|
|
*/
|
|
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;
|
|
backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
|
|
backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
|
|
|
|
// Erase background styles.
|
|
backgroundStyle.backgroundGraphic = null;
|
|
backgroundStyle.backgroundXOffset = null;
|
|
backgroundStyle.backgroundYOffset = null;
|
|
backgroundStyle.backgroundGraphicZIndex = null;
|
|
|
|
return this.redrawNode(
|
|
id + this.BACKGROUND_ID_SUFFIX,
|
|
geometry,
|
|
backgroundStyle,
|
|
null
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Method: drawGeometryNode
|
|
* Given a node, draw a geometry on the specified layer.
|
|
* node and geometry are required arguments, style is optional.
|
|
* This method is only called by the render itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* style - {Object}
|
|
*
|
|
* Returns:
|
|
* {Object} a hash with properties "node" (the drawn node) and "complete"
|
|
* (null if parts of the geometry could not be drawn, false if nothing
|
|
* could be drawn)
|
|
*/
|
|
drawGeometryNode: function(node, geometry, style) {
|
|
style = style || node._style;
|
|
|
|
var options = {
|
|
'isFilled': style.fill === undefined ?
|
|
true :
|
|
style.fill,
|
|
'isStroked': style.stroke === undefined ?
|
|
!!style.strokeWidth :
|
|
style.stroke
|
|
};
|
|
var drawn;
|
|
switch (geometry.CLASS_NAME) {
|
|
case "OpenLayers.Geometry.Point":
|
|
if(style.graphic === false) {
|
|
options.isFilled = false;
|
|
options.isStroked = false;
|
|
}
|
|
drawn = this.drawPoint(node, geometry);
|
|
break;
|
|
case "OpenLayers.Geometry.LineString":
|
|
options.isFilled = false;
|
|
drawn = this.drawLineString(node, geometry);
|
|
break;
|
|
case "OpenLayers.Geometry.LinearRing":
|
|
drawn = this.drawLinearRing(node, geometry);
|
|
break;
|
|
case "OpenLayers.Geometry.Polygon":
|
|
drawn = this.drawPolygon(node, geometry);
|
|
break;
|
|
case "OpenLayers.Geometry.Surface":
|
|
drawn = this.drawSurface(node, geometry);
|
|
break;
|
|
case "OpenLayers.Geometry.Rectangle":
|
|
drawn = this.drawRectangle(node, geometry);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
node._options = options;
|
|
|
|
//set style
|
|
//TBD simplify this
|
|
if (drawn != false) {
|
|
return {
|
|
node: this.setStyle(node, style, options, geometry),
|
|
complete: drawn
|
|
};
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: postDraw
|
|
* Things that have do be done after the geometry node is appended
|
|
* to its parent node. To be overridden by subclasses.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
*/
|
|
postDraw: function(node) {},
|
|
|
|
/**
|
|
* Method: drawPoint
|
|
* Virtual function for drawing Point Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or false if the renderer could not draw the point
|
|
*/
|
|
drawPoint: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawLineString
|
|
* Virtual function for drawing LineString Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or null if the renderer could not draw all components of
|
|
* the linestring, or false if nothing could be drawn
|
|
*/
|
|
drawLineString: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawLinearRing
|
|
* Virtual function for drawing LinearRing Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or null if the renderer could not draw all components
|
|
* of the linear ring, or false if nothing could be drawn
|
|
*/
|
|
drawLinearRing: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawPolygon
|
|
* Virtual function for drawing Polygon Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or null if the renderer could not draw all components
|
|
* of the polygon, or false if nothing could be drawn
|
|
*/
|
|
drawPolygon: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawRectangle
|
|
* Virtual function for drawing Rectangle Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or false if the renderer could not draw the rectangle
|
|
*/
|
|
drawRectangle: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawCircle
|
|
* Virtual function for drawing Circle Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or false if the renderer could not draw the circle
|
|
*/
|
|
drawCircle: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: drawSurface
|
|
* Virtual function for drawing Surface Geometry.
|
|
* Should be implemented by subclasses.
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or false if the renderer could not draw the surface
|
|
*/
|
|
drawSurface: function(node, geometry) {},
|
|
|
|
/**
|
|
* Method: removeText
|
|
* Removes a label
|
|
*
|
|
* Parameters:
|
|
* featureId - {String}
|
|
*/
|
|
removeText: function(featureId) {
|
|
var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
|
|
if (label) {
|
|
this.textRoot.removeChild(label);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: getFeatureIdFromEvent
|
|
*
|
|
* Parameters:
|
|
* evt - {Object} An <OpenLayers.Event> object
|
|
*
|
|
* Returns:
|
|
* {<OpenLayers.Geometry>} A geometry from an event that
|
|
* happened on a layer.
|
|
*/
|
|
getFeatureIdFromEvent: function(evt) {
|
|
var target = evt.target;
|
|
var useElement = target && target.correspondingUseElement;
|
|
var node = useElement ? useElement : (target || evt.srcElement);
|
|
var featureId = node._featureId;
|
|
return featureId;
|
|
},
|
|
|
|
/**
|
|
* Method: eraseGeometry
|
|
* Erase a geometry from the renderer. In the case of a multi-geometry,
|
|
* we cycle through and recurse on ourselves. Otherwise, we look for a
|
|
* node with the geometry.id, destroy its geometry, and remove it from
|
|
* the DOM.
|
|
*
|
|
* Parameters:
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* featureId - {String}
|
|
*/
|
|
eraseGeometry: function(geometry, featureId) {
|
|
if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
|
|
(geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
|
|
(geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
|
|
(geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
|
|
for (var i=0, len=geometry.components.length; i<len; i++) {
|
|
this.eraseGeometry(geometry.components[i], featureId);
|
|
}
|
|
} 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);
|
|
|
|
if (this.indexer) {
|
|
this.indexer.remove(element);
|
|
}
|
|
|
|
if (element._style.backgroundGraphic) {
|
|
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
|
|
* Create new node of the specified type, with the (optional) specified id.
|
|
*
|
|
* If node already exists with same ID and a different type, we remove it
|
|
* and then call ourselves again to recreate it.
|
|
*
|
|
* Parameters:
|
|
* id - {String}
|
|
* type - {String} type Kind of node to draw.
|
|
*
|
|
* Returns:
|
|
* {DOMElement} A new node of the given type and id.
|
|
*/
|
|
nodeFactory: function(id, type) {
|
|
var node = OpenLayers.Util.getElement(id);
|
|
if (node) {
|
|
if (!this.nodeTypeCompare(node, type)) {
|
|
node.parentNode.removeChild(node);
|
|
node = this.nodeFactory(id, type);
|
|
}
|
|
} else {
|
|
node = this.createNode(type, id);
|
|
}
|
|
return node;
|
|
},
|
|
|
|
/**
|
|
* Method: nodeTypeCompare
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* type - {String} Kind of node
|
|
*
|
|
* Returns:
|
|
* {Boolean} Whether or not the specified node is of the specified type
|
|
* This function must be overridden by subclasses.
|
|
*/
|
|
nodeTypeCompare: function(node, type) {},
|
|
|
|
/**
|
|
* 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: moveRoot
|
|
* moves this renderer's root to a different renderer.
|
|
*
|
|
* Parameters:
|
|
* renderer - {<OpenLayers.Renderer>} target renderer for the moved root
|
|
*/
|
|
moveRoot: function(renderer) {
|
|
var root = this.root;
|
|
if(renderer.root.parentNode == this.rendererRoot) {
|
|
root = renderer.root;
|
|
}
|
|
root.parentNode.removeChild(root);
|
|
renderer.rendererRoot.appendChild(root);
|
|
},
|
|
|
|
/**
|
|
* Method: getRenderLayerId
|
|
* Gets the layer that this renderer's output appears on. If moveRoot was
|
|
* used, this will be different from the id of the layer containing the
|
|
* features rendered by this renderer.
|
|
*
|
|
* Returns:
|
|
* {String} the id of the output layer.
|
|
*/
|
|
getRenderLayerId: function() {
|
|
return this.root.parentNode.parentNode.id;
|
|
},
|
|
|
|
/**
|
|
* Method: isComplexSymbol
|
|
* Determines if a symbol cannot be rendered using drawCircle
|
|
*
|
|
* Parameters:
|
|
* graphicName - {String}
|
|
*
|
|
* Returns
|
|
* {Boolean} true if the symbol is complex, false if not
|
|
*/
|
|
isComplexSymbol: function(graphicName) {
|
|
return (graphicName != "circle") && !!graphicName;
|
|
},
|
|
|
|
CLASS_NAME: "OpenLayers.Renderer.Elements"
|
|
});
|
|
|
|
|
|
/**
|
|
* Constant: OpenLayers.Renderer.symbol
|
|
* Coordinate arrays for well known (named) symbols.
|
|
*/
|
|
OpenLayers.Renderer.symbol = {
|
|
"star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
|
|
303,215, 231,161, 321,161, 350,75],
|
|
"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,
|
|
4,0],
|
|
"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],
|
|
"square": [0,0, 0,1, 1,1, 1,0, 0,0],
|
|
"triangle": [0,10, 10,10, 5,0, 0,10]
|
|
};
|