This is required for IFrame tiles, so we don't set the opacity on the event pane. To make this work with vector layers, the defs node needs to be inserted before the root group element instead of after it.
1005 lines
34 KiB
JavaScript
1005 lines
34 KiB
JavaScript
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
|
|
* full list of contributors). Published under the 2-clause BSD license.
|
|
* See license.txt in the OpenLayers distribution or repository for the
|
|
* full text of the license. */
|
|
|
|
/**
|
|
* @requires OpenLayers/Renderer/Elements.js
|
|
*/
|
|
|
|
/**
|
|
* Class: OpenLayers.Renderer.SVG
|
|
*
|
|
* Inherits:
|
|
* - <OpenLayers.Renderer.Elements>
|
|
*/
|
|
OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
|
|
|
|
/**
|
|
* Property: xmlns
|
|
* {String}
|
|
*/
|
|
xmlns: "http://www.w3.org/2000/svg",
|
|
|
|
/**
|
|
* Property: xlinkns
|
|
* {String}
|
|
*/
|
|
xlinkns: "http://www.w3.org/1999/xlink",
|
|
|
|
/**
|
|
* Constant: MAX_PIXEL
|
|
* {Integer} Firefox has a limitation where values larger or smaller than
|
|
* about 15000 in an SVG document lock the browser up. This
|
|
* works around it.
|
|
*/
|
|
MAX_PIXEL: 15000,
|
|
|
|
/**
|
|
* Property: translationParameters
|
|
* {Object} Hash with "x" and "y" properties
|
|
*/
|
|
translationParameters: null,
|
|
|
|
/**
|
|
* Property: symbolMetrics
|
|
* {Object} Cache for symbol metrics according to their svg coordinate
|
|
* space. This is an object keyed by the symbol's id, and values are
|
|
* an array of [width, centerX, centerY].
|
|
*/
|
|
symbolMetrics: null,
|
|
|
|
/**
|
|
* Constructor: OpenLayers.Renderer.SVG
|
|
*
|
|
* Parameters:
|
|
* containerID - {String}
|
|
*/
|
|
initialize: function(containerID) {
|
|
if (!this.supported()) {
|
|
return;
|
|
}
|
|
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
|
|
arguments);
|
|
this.translationParameters = {x: 0, y: 0};
|
|
|
|
this.symbolMetrics = {};
|
|
},
|
|
|
|
/**
|
|
* APIMethod: supported
|
|
*
|
|
* Returns:
|
|
* {Boolean} Whether or not the browser supports the SVG renderer
|
|
*/
|
|
supported: function() {
|
|
var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
|
|
return (document.implementation &&
|
|
(document.implementation.hasFeature("org.w3c.svg", "1.0") ||
|
|
document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
|
|
document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
|
|
},
|
|
|
|
/**
|
|
* Method: inValidRange
|
|
* See #669 for more information
|
|
*
|
|
* Parameters:
|
|
* x - {Integer}
|
|
* y - {Integer}
|
|
* xyOnly - {Boolean} whether or not to just check for x and y, which means
|
|
* to not take the current translation parameters into account if true.
|
|
*
|
|
* Returns:
|
|
* {Boolean} Whether or not the 'x' and 'y' coordinates are in the
|
|
* valid range.
|
|
*/
|
|
inValidRange: function(x, y, xyOnly) {
|
|
var left = x + (xyOnly ? 0 : this.translationParameters.x);
|
|
var top = y + (xyOnly ? 0 : this.translationParameters.y);
|
|
return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
|
|
top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
|
|
},
|
|
|
|
/**
|
|
* Method: setExtent
|
|
*
|
|
* 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.Elements.prototype.setExtent.apply(this, arguments);
|
|
|
|
var resolution = this.getResolution(),
|
|
left = -extent.left / resolution,
|
|
top = extent.top / resolution;
|
|
|
|
// If the resolution has changed, start over changing the corner, because
|
|
// the features will redraw.
|
|
if (resolutionChanged) {
|
|
this.left = left;
|
|
this.top = top;
|
|
// Set the viewbox
|
|
var extentString = "0 0 " + this.size.w + " " + this.size.h;
|
|
|
|
this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
|
|
this.translate(this.xOffset, 0);
|
|
return true;
|
|
} else {
|
|
var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
|
|
if (!inRange) {
|
|
// recenter the coordinate system
|
|
this.setExtent(extent, true);
|
|
}
|
|
return coordSysUnchanged && inRange;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: translate
|
|
* Transforms the SVG coordinate system
|
|
*
|
|
* Parameters:
|
|
* x - {Float}
|
|
* y - {Float}
|
|
*
|
|
* Returns:
|
|
* {Boolean} true if the translation parameters are in the valid coordinates
|
|
* range, false otherwise.
|
|
*/
|
|
translate: function(x, y) {
|
|
if (!this.inValidRange(x, y, true)) {
|
|
return false;
|
|
} else {
|
|
var transformString = "";
|
|
if (x || y) {
|
|
transformString = "translate(" + x + "," + y + ")";
|
|
}
|
|
this.root.setAttributeNS(null, "transform", transformString);
|
|
this.translationParameters = {x: x, y: y};
|
|
return true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: setSize
|
|
* Sets the size of the drawing surface.
|
|
*
|
|
* Parameters:
|
|
* size - {<OpenLayers.Size>} The size of the drawing surface
|
|
*/
|
|
setSize: function(size) {
|
|
OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
|
|
|
|
this.rendererRoot.setAttributeNS(null, "width", this.size.w);
|
|
this.rendererRoot.setAttributeNS(null, "height", this.size.h);
|
|
},
|
|
|
|
/**
|
|
* Method: getNodeType
|
|
*
|
|
* Parameters:
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* style - {Object}
|
|
*
|
|
* Returns:
|
|
* {String} The corresponding node type for the specified geometry
|
|
*/
|
|
getNodeType: function(geometry, style) {
|
|
var nodeType = null;
|
|
switch (geometry.CLASS_NAME) {
|
|
case "OpenLayers.Geometry.Point":
|
|
if (style.externalGraphic) {
|
|
nodeType = "image";
|
|
} else if (this.isComplexSymbol(style.graphicName)) {
|
|
nodeType = "svg";
|
|
} else {
|
|
nodeType = "circle";
|
|
}
|
|
break;
|
|
case "OpenLayers.Geometry.Rectangle":
|
|
nodeType = "rect";
|
|
break;
|
|
case "OpenLayers.Geometry.LineString":
|
|
nodeType = "polyline";
|
|
break;
|
|
case "OpenLayers.Geometry.LinearRing":
|
|
nodeType = "polygon";
|
|
break;
|
|
case "OpenLayers.Geometry.Polygon":
|
|
case "OpenLayers.Geometry.Curve":
|
|
nodeType = "path";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return nodeType;
|
|
},
|
|
|
|
/**
|
|
* Method: setStyle
|
|
* Use to set all the style attributes to a SVG node.
|
|
*
|
|
* Takes care to adjust stroke width and point radius to be
|
|
* resolution-relative
|
|
*
|
|
* Parameters:
|
|
* node - {SVGDomElement} An SVG element to decorate
|
|
* style - {Object}
|
|
* options - {Object} Currently supported options include
|
|
* 'isFilled' {Boolean} and
|
|
* 'isStroked' {Boolean}
|
|
*/
|
|
setStyle: function(node, style, options) {
|
|
style = style || node._style;
|
|
options = options || node._options;
|
|
var r = parseFloat(node.getAttributeNS(null, "r"));
|
|
var widthFactor = 1;
|
|
var pos;
|
|
if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
|
|
node.style.visibility = "";
|
|
if (style.graphic === false) {
|
|
node.style.visibility = "hidden";
|
|
} else if (style.externalGraphic) {
|
|
pos = this.getPosition(node);
|
|
|
|
if (style.graphicTitle) {
|
|
node.setAttributeNS(null, "title", style.graphicTitle);
|
|
//Standards-conformant SVG
|
|
// Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
|
|
var titleNode = node.getElementsByTagName("title");
|
|
if (titleNode.length > 0) {
|
|
titleNode[0].firstChild.textContent = style.graphicTitle;
|
|
} else {
|
|
var label = this.nodeFactory(null, "title");
|
|
label.textContent = style.graphicTitle;
|
|
node.appendChild(label);
|
|
}
|
|
}
|
|
if (style.graphicWidth && style.graphicHeight) {
|
|
node.setAttributeNS(null, "preserveAspectRatio", "none");
|
|
}
|
|
var width = style.graphicWidth || style.graphicHeight;
|
|
var height = style.graphicHeight || style.graphicWidth;
|
|
width = width ? width : style.pointRadius*2;
|
|
height = height ? height : style.pointRadius*2;
|
|
var xOffset = (style.graphicXOffset != undefined) ?
|
|
style.graphicXOffset : -(0.5 * width);
|
|
var yOffset = (style.graphicYOffset != undefined) ?
|
|
style.graphicYOffset : -(0.5 * height);
|
|
|
|
var opacity = style.graphicOpacity || style.fillOpacity;
|
|
|
|
node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
|
|
node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
|
|
node.setAttributeNS(null, "width", width);
|
|
node.setAttributeNS(null, "height", height);
|
|
node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
|
|
node.setAttributeNS(null, "style", "opacity: "+opacity);
|
|
node.onclick = OpenLayers.Renderer.SVG.preventDefault;
|
|
} else if (this.isComplexSymbol(style.graphicName)) {
|
|
// the symbol viewBox is three times as large as the symbol
|
|
var offset = style.pointRadius * 3;
|
|
var size = offset * 2;
|
|
var src = this.importSymbol(style.graphicName);
|
|
pos = this.getPosition(node);
|
|
widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
|
|
|
|
// remove the node from the dom before we modify it. This
|
|
// prevents various rendering issues in Safari and FF
|
|
var parent = node.parentNode;
|
|
var nextSibling = node.nextSibling;
|
|
if(parent) {
|
|
parent.removeChild(node);
|
|
}
|
|
|
|
// The more appropriate way to implement this would be use/defs,
|
|
// but due to various issues in several browsers, it is safer to
|
|
// copy the symbols instead of referencing them.
|
|
// See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
|
|
// and this email thread
|
|
// http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
|
|
node.firstChild && node.removeChild(node.firstChild);
|
|
node.appendChild(src.firstChild.cloneNode(true));
|
|
node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
|
|
|
|
node.setAttributeNS(null, "width", size);
|
|
node.setAttributeNS(null, "height", size);
|
|
node.setAttributeNS(null, "x", pos.x - offset);
|
|
node.setAttributeNS(null, "y", pos.y - offset);
|
|
|
|
// now that the node has all its new properties, insert it
|
|
// back into the dom where it was
|
|
if(nextSibling) {
|
|
parent.insertBefore(node, nextSibling);
|
|
} else if(parent) {
|
|
parent.appendChild(node);
|
|
}
|
|
} else {
|
|
node.setAttributeNS(null, "r", style.pointRadius);
|
|
}
|
|
|
|
var rotation = style.rotation;
|
|
|
|
if ((rotation !== undefined || node._rotation !== undefined) && pos) {
|
|
node._rotation = rotation;
|
|
rotation |= 0;
|
|
if (node.nodeName !== "svg") {
|
|
node.setAttributeNS(null, "transform",
|
|
"rotate(" + rotation + " " + pos.x + " " +
|
|
pos.y + ")");
|
|
} else {
|
|
var metrics = this.symbolMetrics[src.id];
|
|
node.firstChild.setAttributeNS(null, "transform", "rotate("
|
|
+ rotation + " "
|
|
+ metrics[1] + " "
|
|
+ metrics[2] + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.isFilled) {
|
|
node.setAttributeNS(null, "fill", style.fillColor);
|
|
node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
|
|
} else {
|
|
node.setAttributeNS(null, "fill", "none");
|
|
}
|
|
|
|
if (options.isStroked) {
|
|
node.setAttributeNS(null, "stroke", style.strokeColor);
|
|
node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
|
|
node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
|
|
node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
|
|
// Hard-coded linejoin for now, to make it look the same as in VML.
|
|
// There is no strokeLinejoin property yet for symbolizers.
|
|
node.setAttributeNS(null, "stroke-linejoin", "round");
|
|
style.strokeDashstyle && node.setAttributeNS(null,
|
|
"stroke-dasharray", this.dashStyle(style, widthFactor));
|
|
} else {
|
|
node.setAttributeNS(null, "stroke", "none");
|
|
}
|
|
|
|
if (style.pointerEvents) {
|
|
node.setAttributeNS(null, "pointer-events", style.pointerEvents);
|
|
}
|
|
|
|
if (style.cursor != null) {
|
|
node.setAttributeNS(null, "cursor", style.cursor);
|
|
}
|
|
|
|
return node;
|
|
},
|
|
|
|
/**
|
|
* Method: dashStyle
|
|
*
|
|
* Parameters:
|
|
* style - {Object}
|
|
* widthFactor - {Number}
|
|
*
|
|
* Returns:
|
|
* {String} A SVG compliant 'stroke-dasharray' value
|
|
*/
|
|
dashStyle: function(style, widthFactor) {
|
|
var w = style.strokeWidth * widthFactor;
|
|
var str = style.strokeDashstyle;
|
|
switch (str) {
|
|
case 'solid':
|
|
return 'none';
|
|
case 'dot':
|
|
return [1, 4 * w].join();
|
|
case 'dash':
|
|
return [4 * w, 4 * w].join();
|
|
case 'dashdot':
|
|
return [4 * w, 4 * w, 1, 4 * w].join();
|
|
case 'longdash':
|
|
return [8 * w, 4 * w].join();
|
|
case 'longdashdot':
|
|
return [8 * w, 4 * w, 1, 4 * w].join();
|
|
default:
|
|
return OpenLayers.String.trim(str).replace(/\s+/g, ",");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
createNode: function(type, id) {
|
|
var node = document.createElementNS(this.xmlns, type);
|
|
if (id) {
|
|
node.setAttributeNS(null, "id", id);
|
|
}
|
|
return node;
|
|
},
|
|
|
|
/**
|
|
* Method: nodeTypeCompare
|
|
*
|
|
* Parameters:
|
|
* node - {SVGDomElement} An SVG element
|
|
* type - {String} Kind of node
|
|
*
|
|
* Returns:
|
|
* {Boolean} Whether or not the specified node is of the specified type
|
|
*/
|
|
nodeTypeCompare: function(node, type) {
|
|
return (type == node.nodeName);
|
|
},
|
|
|
|
/**
|
|
* Method: createRenderRoot
|
|
*
|
|
* Returns:
|
|
* {DOMElement} The specific render engine's root element
|
|
*/
|
|
createRenderRoot: function() {
|
|
return this.nodeFactory(this.container.id + "_svgRoot", "svg");
|
|
},
|
|
|
|
/**
|
|
* Method: createRoot
|
|
*
|
|
* Parameters:
|
|
* suffix - {String} suffix to append to the id
|
|
*
|
|
* Returns:
|
|
* {DOMElement}
|
|
*/
|
|
createRoot: function(suffix) {
|
|
return this.nodeFactory(this.container.id + suffix, "g");
|
|
},
|
|
|
|
/**
|
|
* Method: createDefs
|
|
*
|
|
* Returns:
|
|
* {DOMElement} The element to which we'll add the symbol definitions
|
|
*/
|
|
createDefs: function() {
|
|
var defs = this.nodeFactory(this.container.id + "_defs", "defs");
|
|
this.rendererRoot.insertBefore(defs, this.root);
|
|
return defs;
|
|
},
|
|
|
|
/**************************************
|
|
* *
|
|
* GEOMETRY DRAWING FUNCTIONS *
|
|
* *
|
|
**************************************/
|
|
|
|
/**
|
|
* Method: drawPoint
|
|
* 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) {
|
|
return this.drawCircle(node, geometry, 1);
|
|
},
|
|
|
|
/**
|
|
* Method: drawCircle
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
* geometry - {<OpenLayers.Geometry>}
|
|
* radius - {Float}
|
|
*
|
|
* Returns:
|
|
* {DOMElement} or false if the renderer could not draw the circle
|
|
*/
|
|
drawCircle: function(node, geometry, radius) {
|
|
var resolution = this.getResolution();
|
|
var x = ((geometry.x - this.featureDx) / resolution + this.left);
|
|
var y = (this.top - geometry.y / resolution);
|
|
|
|
if (this.inValidRange(x, y)) {
|
|
node.setAttributeNS(null, "cx", x);
|
|
node.setAttributeNS(null, "cy", y);
|
|
node.setAttributeNS(null, "r", radius);
|
|
return node;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Method: drawLineString
|
|
* 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) {
|
|
var componentsResult = this.getComponentsString(geometry.components);
|
|
if (componentsResult.path) {
|
|
node.setAttributeNS(null, "points", componentsResult.path);
|
|
return (componentsResult.complete ? node : null);
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: drawLinearRing
|
|
* 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) {
|
|
var componentsResult = this.getComponentsString(geometry.components);
|
|
if (componentsResult.path) {
|
|
node.setAttributeNS(null, "points", componentsResult.path);
|
|
return (componentsResult.complete ? node : null);
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: drawPolygon
|
|
* 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) {
|
|
var d = "";
|
|
var draw = true;
|
|
var complete = true;
|
|
var linearRingResult, path;
|
|
for (var j=0, len=geometry.components.length; j<len; j++) {
|
|
d += " M";
|
|
linearRingResult = this.getComponentsString(
|
|
geometry.components[j].components, " ");
|
|
path = linearRingResult.path;
|
|
if (path) {
|
|
d += " " + path;
|
|
complete = linearRingResult.complete && complete;
|
|
} else {
|
|
draw = false;
|
|
}
|
|
}
|
|
d += " z";
|
|
if (draw) {
|
|
node.setAttributeNS(null, "d", d);
|
|
node.setAttributeNS(null, "fill-rule", "evenodd");
|
|
return complete ? node : null;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: drawRectangle
|
|
* 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) {
|
|
var resolution = this.getResolution();
|
|
var x = ((geometry.x - this.featureDx) / resolution + this.left);
|
|
var y = (this.top - geometry.y / resolution);
|
|
|
|
if (this.inValidRange(x, y)) {
|
|
node.setAttributeNS(null, "x", x);
|
|
node.setAttributeNS(null, "y", y);
|
|
node.setAttributeNS(null, "width", geometry.width / resolution);
|
|
node.setAttributeNS(null, "height", geometry.height / resolution);
|
|
return node;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: drawText
|
|
* This method is only called by the renderer itself.
|
|
*
|
|
* Parameters:
|
|
* featureId - {String}
|
|
* style -
|
|
* location - {<OpenLayers.Geometry.Point>}
|
|
*/
|
|
drawText: function(featureId, style, location) {
|
|
var drawOutline = (!!style.labelOutlineWidth);
|
|
// First draw text in halo color and size and overlay the
|
|
// normal text afterwards
|
|
if (drawOutline) {
|
|
var outlineStyle = OpenLayers.Util.extend({}, style);
|
|
outlineStyle.fontColor = outlineStyle.labelOutlineColor;
|
|
outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
|
|
outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
|
|
delete outlineStyle.labelOutlineWidth;
|
|
this.drawText(featureId, outlineStyle, location);
|
|
}
|
|
|
|
var resolution = this.getResolution();
|
|
|
|
var x = ((location.x - this.featureDx) / resolution + this.left);
|
|
var y = (location.y / resolution - this.top);
|
|
|
|
var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
|
|
var label = this.nodeFactory(featureId + suffix, "text");
|
|
|
|
label.setAttributeNS(null, "x", x);
|
|
label.setAttributeNS(null, "y", -y);
|
|
|
|
if (style.fontColor) {
|
|
label.setAttributeNS(null, "fill", style.fontColor);
|
|
}
|
|
if (style.fontStrokeColor) {
|
|
label.setAttributeNS(null, "stroke", style.fontStrokeColor);
|
|
}
|
|
if (style.fontStrokeWidth) {
|
|
label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
|
|
}
|
|
if (style.fontOpacity) {
|
|
label.setAttributeNS(null, "opacity", style.fontOpacity);
|
|
}
|
|
if (style.fontFamily) {
|
|
label.setAttributeNS(null, "font-family", style.fontFamily);
|
|
}
|
|
if (style.fontSize) {
|
|
label.setAttributeNS(null, "font-size", style.fontSize);
|
|
}
|
|
if (style.fontWeight) {
|
|
label.setAttributeNS(null, "font-weight", style.fontWeight);
|
|
}
|
|
if (style.fontStyle) {
|
|
label.setAttributeNS(null, "font-style", style.fontStyle);
|
|
}
|
|
if (style.labelSelect === true) {
|
|
label.setAttributeNS(null, "pointer-events", "visible");
|
|
label._featureId = featureId;
|
|
} else {
|
|
label.setAttributeNS(null, "pointer-events", "none");
|
|
}
|
|
var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
|
|
label.setAttributeNS(null, "text-anchor",
|
|
OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
|
|
|
|
if (OpenLayers.IS_GECKO === true) {
|
|
label.setAttributeNS(null, "dominant-baseline",
|
|
OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
|
|
}
|
|
|
|
var labelRows = style.label.split('\n');
|
|
var numRows = labelRows.length;
|
|
while (label.childNodes.length > numRows) {
|
|
label.removeChild(label.lastChild);
|
|
}
|
|
for (var i = 0; i < numRows; i++) {
|
|
var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
|
|
if (style.labelSelect === true) {
|
|
tspan._featureId = featureId;
|
|
tspan._geometry = location;
|
|
tspan._geometryClass = location.CLASS_NAME;
|
|
}
|
|
if (OpenLayers.IS_GECKO === false) {
|
|
tspan.setAttributeNS(null, "baseline-shift",
|
|
OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
|
|
}
|
|
tspan.setAttribute("x", x);
|
|
if (i == 0) {
|
|
var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
|
|
if (vfactor == null) {
|
|
vfactor = -.5;
|
|
}
|
|
tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
|
|
} else {
|
|
tspan.setAttribute("dy", "1em");
|
|
}
|
|
tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
|
|
if (!tspan.parentNode) {
|
|
label.appendChild(tspan);
|
|
}
|
|
}
|
|
|
|
if (!label.parentNode) {
|
|
this.textRoot.appendChild(label);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: getComponentString
|
|
*
|
|
* Parameters:
|
|
* components - {Array(<OpenLayers.Geometry.Point>)} Array of points
|
|
* separator - {String} character between coordinate pairs. Defaults to ","
|
|
*
|
|
* Returns:
|
|
* {Object} hash with properties "path" (the string created from the
|
|
* components and "complete" (false if the renderer was unable to
|
|
* draw all components)
|
|
*/
|
|
getComponentsString: function(components, separator) {
|
|
var renderCmp = [];
|
|
var complete = true;
|
|
var len = components.length;
|
|
var strings = [];
|
|
var str, component;
|
|
for(var i=0; i<len; i++) {
|
|
component = components[i];
|
|
renderCmp.push(component);
|
|
str = this.getShortString(component);
|
|
if (str) {
|
|
strings.push(str);
|
|
} else {
|
|
// The current component is outside the valid range. Let's
|
|
// see if the previous or next component is inside the range.
|
|
// If so, add the coordinate of the intersection with the
|
|
// valid range bounds.
|
|
if (i > 0) {
|
|
if (this.getShortString(components[i - 1])) {
|
|
strings.push(this.clipLine(components[i],
|
|
components[i-1]));
|
|
}
|
|
}
|
|
if (i < len - 1) {
|
|
if (this.getShortString(components[i + 1])) {
|
|
strings.push(this.clipLine(components[i],
|
|
components[i+1]));
|
|
}
|
|
}
|
|
complete = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
path: strings.join(separator || ","),
|
|
complete: complete
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Method: clipLine
|
|
* Given two points (one inside the valid range, and one outside),
|
|
* clips the line betweeen the two points so that the new points are both
|
|
* inside the valid range.
|
|
*
|
|
* Parameters:
|
|
* badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
|
|
* invalid point
|
|
* goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
|
|
* valid point
|
|
* Returns
|
|
* {String} the SVG coordinate pair of the clipped point (like
|
|
* getShortString), or an empty string if both passed componets are at
|
|
* the same point.
|
|
*/
|
|
clipLine: function(badComponent, goodComponent) {
|
|
if (goodComponent.equals(badComponent)) {
|
|
return "";
|
|
}
|
|
var resolution = this.getResolution();
|
|
var maxX = this.MAX_PIXEL - this.translationParameters.x;
|
|
var maxY = this.MAX_PIXEL - this.translationParameters.y;
|
|
var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
|
|
var y1 = this.top - goodComponent.y / resolution;
|
|
var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
|
|
var y2 = this.top - badComponent.y / resolution;
|
|
var k;
|
|
if (x2 < -maxX || x2 > maxX) {
|
|
k = (y2 - y1) / (x2 - x1);
|
|
x2 = x2 < 0 ? -maxX : maxX;
|
|
y2 = y1 + (x2 - x1) * k;
|
|
}
|
|
if (y2 < -maxY || y2 > maxY) {
|
|
k = (x2 - x1) / (y2 - y1);
|
|
y2 = y2 < 0 ? -maxY : maxY;
|
|
x2 = x1 + (y2 - y1) * k;
|
|
}
|
|
return x2 + "," + y2;
|
|
},
|
|
|
|
/**
|
|
* Method: getShortString
|
|
*
|
|
* Parameters:
|
|
* point - {<OpenLayers.Geometry.Point>}
|
|
*
|
|
* Returns:
|
|
* {String} or false if point is outside the valid range
|
|
*/
|
|
getShortString: function(point) {
|
|
var resolution = this.getResolution();
|
|
var x = ((point.x - this.featureDx) / resolution + this.left);
|
|
var y = (this.top - point.y / resolution);
|
|
|
|
if (this.inValidRange(x, y)) {
|
|
return x + "," + y;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: getPosition
|
|
* Finds the position of an svg node.
|
|
*
|
|
* Parameters:
|
|
* node - {DOMElement}
|
|
*
|
|
* Returns:
|
|
* {Object} hash with x and y properties, representing the coordinates
|
|
* within the svg coordinate system
|
|
*/
|
|
getPosition: function(node) {
|
|
return({
|
|
x: parseFloat(node.getAttributeNS(null, "cx")),
|
|
y: parseFloat(node.getAttributeNS(null, "cy"))
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Method: importSymbol
|
|
* add a new symbol definition from the rendererer's symbol hash
|
|
*
|
|
* Parameters:
|
|
* graphicName - {String} name of the symbol to import
|
|
*
|
|
* Returns:
|
|
* {DOMElement} - the imported symbol
|
|
*/
|
|
importSymbol: function (graphicName) {
|
|
if (!this.defs) {
|
|
// create svg defs tag
|
|
this.defs = this.createDefs();
|
|
}
|
|
var id = this.container.id + "-" + graphicName;
|
|
|
|
// check if symbol already exists in the defs
|
|
var existing = document.getElementById(id);
|
|
if (existing != null) {
|
|
return existing;
|
|
}
|
|
|
|
var symbol = OpenLayers.Renderer.symbol[graphicName];
|
|
if (!symbol) {
|
|
throw new Error(graphicName + ' is not a valid symbol name');
|
|
}
|
|
|
|
var symbolNode = this.nodeFactory(id, "symbol");
|
|
var node = this.nodeFactory(null, "polygon");
|
|
symbolNode.appendChild(node);
|
|
var symbolExtent = new OpenLayers.Bounds(
|
|
Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
|
|
|
|
var points = [];
|
|
var x,y;
|
|
for (var i=0; i<symbol.length; i=i+2) {
|
|
x = symbol[i];
|
|
y = symbol[i+1];
|
|
symbolExtent.left = Math.min(symbolExtent.left, x);
|
|
symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
|
|
symbolExtent.right = Math.max(symbolExtent.right, x);
|
|
symbolExtent.top = Math.max(symbolExtent.top, y);
|
|
points.push(x, ",", y);
|
|
}
|
|
|
|
node.setAttributeNS(null, "points", points.join(" "));
|
|
|
|
var width = symbolExtent.getWidth();
|
|
var height = symbolExtent.getHeight();
|
|
// create a viewBox three times as large as the symbol itself,
|
|
// to allow for strokeWidth being displayed correctly at the corners.
|
|
var viewBox = [symbolExtent.left - width,
|
|
symbolExtent.bottom - height, width * 3, height * 3];
|
|
symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
|
|
this.symbolMetrics[id] = [
|
|
Math.max(width, height),
|
|
symbolExtent.getCenterLonLat().lon,
|
|
symbolExtent.getCenterLonLat().lat
|
|
];
|
|
|
|
this.defs.appendChild(symbolNode);
|
|
return symbolNode;
|
|
},
|
|
|
|
/**
|
|
* Method: getFeatureIdFromEvent
|
|
*
|
|
* Parameters:
|
|
* evt - {Object} An <OpenLayers.Event> object
|
|
*
|
|
* Returns:
|
|
* {String} A feature id or undefined.
|
|
*/
|
|
getFeatureIdFromEvent: function(evt) {
|
|
var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
|
|
if(!featureId) {
|
|
var target = evt.target;
|
|
featureId = target.parentNode && target != this.rendererRoot ?
|
|
target.parentNode._featureId : undefined;
|
|
}
|
|
return featureId;
|
|
},
|
|
|
|
CLASS_NAME: "OpenLayers.Renderer.SVG"
|
|
});
|
|
|
|
/**
|
|
* Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
|
|
* {Object}
|
|
*/
|
|
OpenLayers.Renderer.SVG.LABEL_ALIGN = {
|
|
"l": "start",
|
|
"r": "end",
|
|
"b": "bottom",
|
|
"t": "hanging"
|
|
};
|
|
|
|
/**
|
|
* Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
|
|
* {Object}
|
|
*/
|
|
OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
|
|
// according to
|
|
// http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
|
|
// a baseline-shift of -70% shifts the text exactly from the
|
|
// bottom to the top of the baseline, so -35% moves the text to
|
|
// the center of the baseline.
|
|
"t": "-70%",
|
|
"b": "0"
|
|
};
|
|
|
|
/**
|
|
* Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
|
|
* {Object}
|
|
*/
|
|
OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
|
|
"t": 0,
|
|
"b": -1
|
|
};
|
|
|
|
/**
|
|
* Function: OpenLayers.Renderer.SVG.preventDefault
|
|
* Used to prevent default events (especially opening images in a new tab on
|
|
* ctrl-click) from being executed for externalGraphic symbols
|
|
*/
|
|
OpenLayers.Renderer.SVG.preventDefault = function(e) {
|
|
e.preventDefault && e.preventDefault();
|
|
};
|