diff --git a/examples/marker-shadow.html b/examples/marker-shadow.html index 6f7f0cb74a..0afd51a518 100644 --- a/examples/marker-shadow.html +++ b/examples/marker-shadow.html @@ -33,6 +33,10 @@ function init() { map = new OpenLayers.Map("map"); + // allow testing of specific renderers via "?renderer=Canvas", etc + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + layer = new OpenLayers.Layer.Vector( "Marker Drop Shadows", { @@ -55,7 +59,8 @@ pointRadius: 10 }), isBaseLayer: true, - rendererOptions: {yOrdering: true} + rendererOptions: {yOrdering: true}, + renderers: renderer } ); diff --git a/examples/ordering.html b/examples/ordering.html index ec9c92ef2a..d69d6696cf 100644 --- a/examples/ordering.html +++ b/examples/ordering.html @@ -32,6 +32,10 @@ function initYOrderMap() { var map = new OpenLayers.Map("yorder"); + // allow testing of specific renderers via "?renderer=Canvas", etc + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + var layer = new OpenLayers.Layer.Vector( "Y-Order", { @@ -41,7 +45,8 @@ graphicZIndex: GOLD_Z_INDEX }), isBaseLayer: true, - rendererOptions: {yOrdering: true} + rendererOptions: {yOrdering: true}, + renderers: renderer } ); diff --git a/examples/snap-split.html b/examples/snap-split.html index d45d94c5b1..27c03e5827 100644 --- a/examples/snap-split.html +++ b/examples/snap-split.html @@ -92,6 +92,10 @@ }) }); + // allow testing of specific renderers via "?renderer=Canvas", etc + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + // create three vector layers vectors = new OpenLayers.Layer.Vector("Lines", { isBaseLayer: true, @@ -103,7 +107,8 @@ styleMap: styles, maxExtent: new OpenLayers.Bounds( 1549471.9221, 6403610.94, 1550001.32545, 6404015.8 - ) + ), + renderers: renderer }); map.addLayer(vectors); diff --git a/examples/vector-features.html b/examples/vector-features.html index 9954ba5d5e..d647bea483 100644 --- a/examples/vector-features.html +++ b/examples/vector-features.html @@ -16,6 +16,10 @@ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} ); map.addLayer(layer); + // allow testing of specific renderers via "?renderer=Canvas", etc + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + /* * Layer style */ @@ -68,7 +72,10 @@ // graphicTitle only works in Firefox and Internet Explorer style_mark.graphicTitle = "this is a test tooltip"; - var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {style: layer_style}); + var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", { + style: layer_style, + renderers: renderer + }); // create a point feature var point = new OpenLayers.Geometry.Point(-111.04, 45.68); diff --git a/examples/wfs-states.js b/examples/wfs-states.js index e8f183f538..7f31513a33 100644 --- a/examples/wfs-states.js +++ b/examples/wfs-states.js @@ -10,13 +10,18 @@ function init() { ); map.addLayer(base); + // allow testing of specific renderers via "?renderer=Canvas", etc + var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; + renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + var wfs = new OpenLayers.Layer.Vector("States", { strategies: [new OpenLayers.Strategy.BBOX()], protocol: new OpenLayers.Protocol.WFS({ url: "http://demo.opengeo.org/geoserver/wfs", featureType: "states", featureNS: "http://www.openplans.org/topp" - }) + }), + renderers: renderer }); map.addLayer(wfs); diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 99c79fdb2f..9783430dc1 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -223,7 +223,9 @@ "OpenLayers/Geometry/Surface.js", "OpenLayers/Renderer.js", "OpenLayers/Renderer/Elements.js", + "OpenLayers/Renderer/NG.js", "OpenLayers/Renderer/SVG.js", + "OpenLayers/Renderer/SVG2.js", "OpenLayers/Renderer/Canvas.js", "OpenLayers/Renderer/VML.js", "OpenLayers/Layer/Vector.js", diff --git a/lib/OpenLayers/Layer/Vector.js b/lib/OpenLayers/Layer/Vector.js index 633f5d1a8b..dbd00de9c2 100644 --- a/lib/OpenLayers/Layer/Vector.js +++ b/lib/OpenLayers/Layer/Vector.js @@ -472,34 +472,38 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { moveTo: function(bounds, zoomChanged, dragging) { OpenLayers.Layer.prototype.moveTo.apply(this, arguments); - var coordSysUnchanged = true; + var ng = (this.renderer instanceof OpenLayers.Renderer.NG); + if (ng) { + zoomChanged && this.renderer.updateDimensions(); + } else { + var coordSysUnchanged = true; - if (!dragging) { - this.renderer.root.style.visibility = "hidden"; + if (!dragging) { + this.renderer.root.style.visibility = "hidden"; - this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px"; - this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px"; - var extent = this.map.getExtent(); - coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged); + this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px"; + this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px"; + var extent = this.map.getExtent(); + coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged); - this.renderer.root.style.visibility = "visible"; + 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. - if (OpenLayers.IS_GECKO === true) { - this.div.scrollLeft = this.div.scrollLeft; - } + // 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. + if (OpenLayers.IS_GECKO === true) { + this.div.scrollLeft = this.div.scrollLeft; + } - if(!zoomChanged && coordSysUnchanged) { - for(var i in this.unrenderedFeatures) { - var feature = this.unrenderedFeatures[i]; - this.drawFeature(feature); + if(!zoomChanged && coordSysUnchanged) { + for(var i in this.unrenderedFeatures) { + var feature = this.unrenderedFeatures[i]; + this.drawFeature(feature); + } } } } - - if (!this.drawn || zoomChanged || !coordSysUnchanged) { + if (!this.drawn || (!ng && (zoomChanged || !coordSysUnchanged))) { this.drawn = true; var feature; for(var i=0, len=this.features.length; i + */ +OpenLayers.Renderer.NG = OpenLayers.Class(OpenLayers.Renderer.Elements, { + + /** + * Constant: labelNodeType + * {String} The node type for text label containers. To be defined by + * subclasses. + */ + labelNodeType: null, + + /** + * Constructor: OpenLayers.Renderer.NG + * + * 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.Elements.prototype.initialize.apply(this, arguments); + }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: updateDimensions + * To be extended by subclasses - here we set positioning related styles + * on HTML elements, subclasses have to do the same for renderer specific + * elements (e.g. viesBox, width and height of the rendererRoot) + */ + updateDimensions: function() { + var mapExtent = this.map.getExtent(); + var renderExtent = this.map.getMaxExtent(); + this.setExtent(renderExtent, true); + var res = this.getResolution(); + var div = this.rendererRoot.parentNode; + var layerLeft = parseFloat(div.parentNode.style.left); + var layerTop = parseFloat(div.parentNode.style.top); + div.style.left = ((renderExtent.left - mapExtent.left) / res - layerLeft) + "px"; + div.style.top = ((mapExtent.top - renderExtent.top) / res - layerTop) + "px"; + }, + + /** + * Method: resize + */ + setSize: function() { + this.map.getExtent() && this.updateDimensions(); + }, + + /** + * Method: drawFeature + * Draw the feature. The optional style argument can be used + * to override the feature's own style. This method should only + * be called from layer.drawFeature(). + * + * Parameters: + * feature - {} + * style - {} + * + * Returns: + * {Boolean} true if the feature has been drawn completely, false if not, + * undefined if the feature had no geometry + */ + drawFeature: function(feature, style) { + if(style == null) { + style = feature.style; + } + if (feature.geometry) { + var rendered = this.drawGeometry(feature.geometry, style, feature.id); + if(rendered !== false && style.label) { + var location = feature.geometry.getCentroid(); + this.drawText(feature.id, style, location); + } else { + this.removeText(feature.id); + } + return rendered; + } + }, + + /** + * Method: drawText + * Function for drawing text labels. + * This method is only called by the renderer itself. + * + * Parameters: + * featureId - {String|DOMElement} + * style - {Object} + * location - {}, will be modified inline + * + * Returns: + * {DOMElement} container holding the text label (to be populated by + * subclasses) + */ + drawText: function(featureId, style, location) { + var label; + if (typeof featureId !== "string") { + label = featureId; + } else { + label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, this.labelNodeType); + label._featureId = featureId; + } + label._style = style; + label._x = location.x; + label._y = location.y; + if(style.labelXOffset || style.labelYOffset) { + xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset; + yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset; + var res = this.getResolution(); + location.move(xOffset*res, yOffset*res); + } + + if(label.parentNode !== this.textRoot) { + this.textRoot.appendChild(label); + } + + return label; + }, + + CLASS_NAME: "OpenLayers.Renderer.NG" +}); \ No newline at end of file diff --git a/lib/OpenLayers/Renderer/SVG2.js b/lib/OpenLayers/Renderer/SVG2.js new file mode 100644 index 0000000000..a629b3faca --- /dev/null +++ b/lib/OpenLayers/Renderer/SVG2.js @@ -0,0 +1,769 @@ +/* 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/NG.js + */ + +/** + * Class: OpenLayers.Renderer.SVG2 + * + * Inherits from: + * - + */ +OpenLayers.Renderer.SVG2 = OpenLayers.Class(OpenLayers.Renderer.NG, { + + /** + * Property: xmlns + * {String} + */ + xmlns: "http://www.w3.org/2000/svg", + + /** + * Property: xlinkns + * {String} + */ + xlinkns: "http://www.w3.org/1999/xlink", + + /** + * 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 object with size, x and y properties. + */ + symbolMetrics: null, + + /** + * Constant: labelNodeType + * {String} The node type for text label containers. + */ + labelNodeType: "g", + + /** + * Constructor: OpenLayers.Renderer.SVG + * + * Parameters: + * containerID - {String} + */ + initialize: function(containerID) { + if (!this.supported()) { + return; + } + OpenLayers.Renderer.Elements.prototype.initialize.apply(this, + arguments); + + this.symbolMetrics = {}; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments); + }, + + /** + * 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: updateDimensions + */ + updateDimensions: function() { + OpenLayers.Renderer.NG.prototype.updateDimensions.apply(this, arguments); + + var res = this.getResolution(); + + var width = this.extent.getWidth(); + var height = this.extent.getHeight(); + + var extentString = [ + this.extent.left, + -this.extent.top, + width, + height + ].join(" "); + this.rendererRoot.setAttributeNS(null, "viewBox", extentString); + this.rendererRoot.setAttributeNS(null, "width", width / res); + this.rendererRoot.setAttributeNS(null, "height", height / res); + + // update styles for the new resolution + var nodes = this.vectorRoot.childNodes; + for (var i=0, len=nodes.length; i} + * 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": + case "OpenLayers.Geometry.Surface": + 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 resolution = this.getResolution(); + var r = node._radius; + var widthFactor = resolution; + if (node._geometryClass == "OpenLayers.Geometry.Point" && r) { + node.style.visibility = ""; + if (style.graphic === false) { + node.style.visibility = "hidden"; + } else if (style.externalGraphic) { + + if (style.graphicTitle) { + node.setAttributeNS(null, "title", style.graphicTitle); + //Standards-conformant SVG + 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; + width *= resolution; + height *= resolution; + + var xOffset = (style.graphicXOffset != undefined) ? + style.graphicXOffset * resolution : -(0.5 * width); + var yOffset = (style.graphicYOffset != undefined) ? + style.graphicYOffset * resolution : -(0.5 * height); + + var opacity = style.graphicOpacity || style.fillOpacity; + + node.setAttributeNS(null, "x", node._x + xOffset); + node.setAttributeNS(null, "y", node._y + yOffset); + 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.SVG2.preventDefault; + } else if (this.isComplexSymbol(style.graphicName)) { + // the symbol viewBox is three times as large as the symbol + var offset = style.pointRadius * 3 * resolution; + var size = offset * 2; + var id = this.importSymbol(style.graphicName); + widthFactor = this.symbolMetrics[id].size * 3 / size * resolution; + + // 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 + var src = document.getElementById(id); + 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", node._x - offset); + node.setAttributeNS(null, "y", node._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 * resolution); + } + + var rotation = style.rotation; + if (rotation !== undefined || node._rotation !== undefined) { + node._rotation = rotation; + rotation |= 0; + if (node.nodeName !== "svg") { + node.setAttributeNS(null, "transform", + ["rotate(", rotation, node._x, node._y, ")"].join(" ") + ); + } else { + var metrics = this.symbolMetrics[id]; + node.firstChild.setAttributeNS(null, "transform", + ["rotate(", rotation, metrics.x, metrics.y, ")"].join(" ") + ); + } + } + } + + 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 [widthFactor, 4 * w].join(); + case 'dash': + return [4 * w, 4 * w].join(); + case 'dashdot': + return [4 * w, 4 * w, widthFactor, 4 * w].join(); + case 'longdash': + return [8 * w, 4 * w].join(); + case 'longdashdot': + return [8 * w, 4 * w, widthFactor, 4 * w].join(); + default: + var parts = OpenLayers.String.trim(str).split(/\s+/g); + for (var i=0, ii=parts.length; i} + * + * 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 - {} + * radius - {Float} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the circle + */ + drawCircle: function(node, geometry, radius) { + var x = geometry.x; + var y = -geometry.y; + node.setAttributeNS(null, "cx", x); + node.setAttributeNS(null, "cy", y); + node._x = x; + node._y = y; + node._radius = radius; + return node; + }, + + /** + * Method: drawLineString + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * 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 path = this.getComponentsString(geometry.components); + node.setAttributeNS(null, "points", path); + return node; + }, + + /** + * Method: drawLinearRing + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * 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 path = this.getComponentsString(geometry.components); + node.setAttributeNS(null, "points", path); + return node; + }, + + /** + * Method: drawPolygon + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * 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} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the rectangle + */ + drawRectangle: function(node, geometry) { + node.setAttributeNS(null, "x", geometry.x); + node.setAttributeNS(null, "y", -geometry.y); + node.setAttributeNS(null, "width", geometry.width); + node.setAttributeNS(null, "height", geometry.height); + return node; + }, + + /** + * Method: drawSurface + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the surface + */ + drawSurface: function(node, geometry) { + + // create the svg path string representation + var d = []; + var draw = true; + for (var i=0, len=geometry.components.length; i}, will be modified inline + * + * Returns: + * {DOMElement} container holding the text label + */ + drawText: function(featureId, style, location) { + var g = OpenLayers.Renderer.NG.prototype.drawText.apply(this, arguments); + var text = g.firstChild || + this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_text", "text"); + var tspan = text.firstChild || + this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan", "tspan"); + + var res = this.getResolution(); + text.setAttributeNS(null, "x", location.x / res); + text.setAttributeNS(null, "y", - location.y / res); + g.setAttributeNS(null, "transform", "scale(" + res + ")"); + + if (style.fontColor) { + text.setAttributeNS(null, "fill", style.fontColor); + } + if (style.fontOpacity) { + text.setAttributeNS(null, "opacity", style.fontOpacity); + } + if (style.fontFamily) { + text.setAttributeNS(null, "font-family", style.fontFamily); + } + if (style.fontSize) { + text.setAttributeNS(null, "font-size", style.fontSize); + } + if (style.fontWeight) { + text.setAttributeNS(null, "font-weight", style.fontWeight); + } + if(style.labelSelect === true) { + text.setAttributeNS(null, "pointer-events", "visible"); + text._featureId = featureId; + tspan._featureId = featureId; + } else { + text.setAttributeNS(null, "pointer-events", "none"); + } + var align = style.labelAlign || "cm"; + text.setAttributeNS(null, "text-anchor", + OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle"); + + if (OpenLayers.IS_GECKO === true) { + text.setAttributeNS(null, "dominant-baseline", + OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central"); + } else { + tspan.setAttributeNS(null, "baseline-shift", + OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%"); + } + + tspan.textContent = style.label; + + if(!text.parentNode) { + text.appendChild(tspan); + g.appendChild(text); + } + + return g; + }, + + /** + * Method: getComponentString + * + * Parameters: + * components - {Array()} 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 len = components.length; + var strings = new Array(len); + for(var i=0; i} + * + * Returns: + * {String} or false if point is outside the valid range + */ + getShortString: function(point) { + return point.x + "," + (-point.y); + }, + + /** + * Method: importSymbol + * add a new symbol definition from the rendererer's symbol hash + * + * Parameters: + * graphicName - {String} name of the symbol to import + * + * Returns: + * {String} - id of 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 + if (document.getElementById(id) != null) { + return id; + } + + 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, len=symbol.length; i object + * + * Returns: + * {} A geometry from an event that + * happened on a layer. + */ + 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; + } + return featureId; + }, + + CLASS_NAME: "OpenLayers.Renderer.SVG2" +}); + +/** + * Function: OpenLayers.Renderer.SVG2.preventDefault + * Used to prevent default events (especially opening images in a new tab on + * ctrl-click) from being executed for externalGraphic and graphicName symbols + */ +OpenLayers.Renderer.SVG2.preventDefault = function(e) { + e.preventDefault && e.preventDefault(); +}; \ No newline at end of file diff --git a/tests/Layer/Vector.html b/tests/Layer/Vector.html index 1a47aaf4c3..30395638ff 100644 --- a/tests/Layer/Vector.html +++ b/tests/Layer/Vector.html @@ -458,9 +458,7 @@ t.plan(9); var map = new OpenLayers.Map("map"); - var layer = new OpenLayers.Layer.Vector(null, { - drawn: true - }); + var layer = new OpenLayers.Layer.Vector(); map.addLayer(layer); var feature = new OpenLayers.Feature.Vector( new OpenLayers.Geometry.Point(10, 10) @@ -477,6 +475,7 @@ }; // draw feature with no state + layer.drawn = true; layer.drawFeature(feature); t.ok(log.feature === feature, "[no state] drawFeature called with correct feature"); t.ok(log.style.display !== "none", "[no state] drawFeature called with style display not none"); @@ -707,6 +706,54 @@ (-y + customStyle6.graphicYOffset).toFixed().toString(), "graphicYOffset correctly set"); } + if (layer.renderer.CLASS_NAME == 'OpenLayers.Renderer.SVG2') { + feature.style = customStyle1; + layer.drawFeature(feature); + var resolution = map.getResolution(); + t.eq(root.firstChild.getAttributeNS(null, 'width'), + (2*customStyle1.pointRadius*resolution).toString(), + "given a pointRadius, width equals 2*pointRadius"); + t.eq(root.firstChild.getAttributeNS(null, 'height'), + (2*customStyle1.pointRadius*resolution).toString(), + "given a pointRadius, height equals 2*pointRadius"); + feature.style = customStyle2; + layer.drawFeature(feature); + t.eq(root.firstChild.getAttributeNS(null, 'width'), + root.firstChild.getAttributeNS(null, 'height'), + "given a graphicWidth, width equals height"); + t.eq(root.firstChild.getAttributeNS(null, 'width'), + (customStyle2.graphicWidth*resolution).toString(), + "width is set correctly"); + feature.style = customStyle3; + layer.drawFeature(feature); + t.eq(root.firstChild.getAttributeNS(null, 'height'), + root.firstChild.getAttributeNS(null, 'width'), + "given a graphicHeight, height equals width"); + t.eq(root.firstChild.getAttributeNS(null, 'height'), + (customStyle3.graphicHeight*resolution).toString(), + "height is set correctly"); + feature.style = customStyle4; + layer.drawFeature(feature); + t.eq(root.firstChild.getAttributeNS(null, 'height'), + (customStyle4.graphicHeight*resolution).toString(), + "given graphicHeight and graphicWidth, both are set: height"); + t.eq(root.firstChild.getAttributeNS(null, 'width'), + (customStyle4.graphicWidth*resolution).toString(), + "given graphicHeight and graphicWidth, both are set: width"); + feature.style = customStyle5; + layer.drawFeature(feature); + t.eq(root.firstChild.getAttributeNS(null, 'style'), + 'opacity: '+customStyle5.graphicOpacity.toString()+((OpenLayers.Util.getBrowserName() == "opera" || OpenLayers.Util.getBrowserName() == "safari") ? "" : ';'), + "graphicOpacity correctly set"); + feature.style = customStyle6; + layer.drawFeature(feature); + t.eq(root.firstChild.getAttributeNS(null, 'x'), + (geometryX + customStyle6.graphicXOffset*resolution).toString(), + "graphicXOffset correctly set"); + t.eq(root.firstChild.getAttributeNS(null, 'y'), + (-geometryY + customStyle6.graphicYOffset*resolution).toString(), + "graphicYOffset correctly set"); + } if (layer.renderer.CLASS_NAME == 'OpenLayers.Renderer.VML') { feature.style = customStyle1; layer.drawFeature(feature); diff --git a/tests/Renderer/SVG2.html b/tests/Renderer/SVG2.html new file mode 100644 index 0000000000..b4aaa283ce --- /dev/null +++ b/tests/Renderer/SVG2.html @@ -0,0 +1,424 @@ + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index 61c7b9e58f..920527f82f 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -185,6 +185,7 @@
  • Renderer/Canvas.html
  • Renderer/Elements.html
  • Renderer/SVG.html
  • +
  • Renderer/SVG2.html
  • Renderer/VML.html
  • Request.html
  • Request/XMLHttpRequest.html
  • diff --git a/tests/speed/vector-renderers.html b/tests/speed/vector-renderers.html new file mode 100644 index 0000000000..4d88dfc009 --- /dev/null +++ b/tests/speed/vector-renderers.html @@ -0,0 +1,25 @@ + + + + Vector Features Performance Test + + + + + +

    Vector Rendering Performance

    +
    +

    + This is a benchmark for vector rendering performance. Test results are + written to the debug console. + Select a renderer here: +
    + +

    + The benchmark shows the time needed to render the features, and how long a + move (drag or zoom) takes. Drag and zoom around to produce move results. +

    + + + + \ No newline at end of file diff --git a/tests/speed/vector-renderers.js b/tests/speed/vector-renderers.js new file mode 100644 index 0000000000..a11b36119e --- /dev/null +++ b/tests/speed/vector-renderers.js @@ -0,0 +1,70 @@ +var map, vectorLayer, drawFeature, features + +map = new OpenLayers.Map('map', { + eventListeners: { + movestart: function() { + console.time("move"); + }, + moveend: function() { + console.timeEnd("move"); + } + } +}); + +// allow testing of specific renderers via "?renderer=Canvas", etc +var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; +renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; + +vectorLayer = new OpenLayers.Layer.Vector("Vector Layer", { + isBaseLayer: true, + renderers: renderer, + eventListeners: { + beforefeaturesadded: function() { + console.time("addFeatures"); + }, + featuresadded: function() { + console.timeEnd("addFeatures"); + } + } +}); + +map.addLayers([vectorLayer]); +map.addControl(new OpenLayers.Control.MousePosition()); +map.setCenter(new OpenLayers.LonLat(0, 0), 2); + +features = new Array(500); +var x, y, points +for (var i = 0; i < 500; i++) { + x = 90-Math.random()*180; + y = 45-Math.random()*90; + var pointList = []; + for(var p=0; p<19; ++p) { + var a = p * (2 * Math.PI) / 20; + var r = Math.random() * 3 + 1; + var newPoint = new OpenLayers.Geometry.Point(x + (r * Math.cos(a)), + y + (r * Math.sin(a))); + pointList.push(newPoint); + } + pointList.push(pointList[0]); + features[i] = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.LinearRing(pointList)); + +} +vectorLayer.addFeatures(features); + +var select = document.getElementById("renderers"); +var renderers = OpenLayers.Layer.Vector.prototype.renderers; +var option; +for (var i=0, len=renderers.length; i