diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Renderer/SVG.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Renderer/SVG.js | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/SVG.js b/misc/openlayers/lib/OpenLayers/Renderer/SVG.js new file mode 100644 index 0000000..263aac0 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Renderer/SVG.js @@ -0,0 +1,1012 @@ +/* Copyright (c) 2006-2013 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 title = style.title || style.graphicTitle; + if (title) { + node.setAttributeNS(null, "title", title); + //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 = title; + } else { + var label = this.nodeFactory(null, "title"); + label.textContent = title; + node.appendChild(label); + } + } + + 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.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, "xlink:href", style.externalGraphic); + node.setAttributeNS(null, "style", "opacity: "+opacity); + node.onclick = OpenLayers.Event.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() { + var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg"); + svg.style.display = "block"; + return 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.appendChild(defs); + 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; + if (style.labelOutlineOpacity) { + outlineStyle.fontOpacity = style.labelOutlineOpacity; + } + 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 + * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead. + * 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) { + OpenLayers.Event.preventDefault(e); +}; |