From aa486090f1bdedd7a4b082025a7c4f9b29b827d5 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 6 Jun 2008 18:42:44 +0000 Subject: [PATCH] Implemented rotation of externalGraphic vector point features. r=tschaub (closes #1433) git-svn-id: http://svn.openlayers.org/trunk/openlayers@7324 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/styles-rotation.html | 83 ++++++++++++++++++++++ lib/OpenLayers/Format/SLD/v1.js | 3 + lib/OpenLayers/Renderer/SVG.js | 6 ++ lib/OpenLayers/Renderer/VML.js | 118 +++++++++++++++++++++++++++++++- tests/Renderer/VML.html | 41 +++++++++++ 5 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 examples/styles-rotation.html diff --git a/examples/styles-rotation.html b/examples/styles-rotation.html new file mode 100644 index 0000000000..f81509d0ff --- /dev/null +++ b/examples/styles-rotation.html @@ -0,0 +1,83 @@ + + + OpenLayers Styles Rotation Example + + + + + +

Rotation Styles Example

+

Vector point feature symbolizers can have a rotation property. The center of the rotation is the point of the image specified by graphicXOffset and graphicYOffset.

+
+
+ + diff --git a/lib/OpenLayers/Format/SLD/v1.js b/lib/OpenLayers/Format/SLD/v1.js index b16733dc0a..374be28ea3 100644 --- a/lib/OpenLayers/Format/SLD/v1.js +++ b/lib/OpenLayers/Format/SLD/v1.js @@ -227,6 +227,9 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, { if(graphic.href != undefined) { symbolizer.externalGraphic = graphic.href; } + if(graphic.rotation != undefined) { + symbolizer.rotation = graphic.rotation; + } }, "ExternalGraphic": function(node, graphic) { this.readChildNodes(node, graphic); diff --git a/lib/OpenLayers/Renderer/SVG.js b/lib/OpenLayers/Renderer/SVG.js index 6773d19720..e816e8238d 100644 --- a/lib/OpenLayers/Renderer/SVG.js +++ b/lib/OpenLayers/Renderer/SVG.js @@ -222,6 +222,12 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, { } else { node.setAttributeNS(null, "r", style.pointRadius); } + + if (style.rotation) { + var rotation = OpenLayers.String.format( + "rotate(${0} ${1} ${2})", [style.rotation, x, y]); + node.setAttributeNS(null, "transform", rotation); + } } if (options.isFilled) { diff --git a/lib/OpenLayers/Renderer/VML.js b/lib/OpenLayers/Renderer/VML.js index 4a0ae60859..4bc1112542 100644 --- a/lib/OpenLayers/Renderer/VML.js +++ b/lib/OpenLayers/Renderer/VML.js @@ -171,6 +171,7 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed(); node.style.width = width; node.style.height = height; + node.style.flip = "y"; // modify style/options for fill and stroke styling below style.fillColor = "none"; @@ -209,11 +210,20 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { fill.setAttribute("src", style.externalGraphic); fill.setAttribute("type", "frame"); - node.style.flip = "y"; if (!(style.graphicWidth && style.graphicHeight)) { fill.aspect = "atmost"; } + + // additional rendering for rotated graphics + if (style.rotation) { + this.graphicRotate(node, xOffset, yOffset); + // make the fill fully transparent, because we now have + // the graphic as imagedata element. We cannot just remove + // the fill, because this is part of the hack described + // in graphicRotate + fill.setAttribute("opacity", 0); + } } if (fill.parentNode != node) { node.appendChild(fill); @@ -249,6 +259,112 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { return node; }, + /** + * Method: graphicRotate + * If a point is to be styled with externalGraphic and rotation, VML fills + * cannot be used to display the graphic, because rotation of graphic + * fills is not supported by the VML implementation of Internet Explorer. + * This method creates a olv:imagedata element inside the VML node, + * DXImageTransform.Matrix and BasicImage filters for rotation and + * opacity, and a 3-step hack to remove rendering artefacts from the + * graphic and preserve the ability of graphics to trigger events. + * Finally, OpenLayers methods are used to determine the correct + * insertion point of the rotated image, because DXImageTransform.Matrix + * does the rotation without the ability to specify a rotation center + * point. + * + * Parameters: + * node - {DOMElement} + * xOffset - {Number} rotation center relative to image, x coordinate + * yOffset - {Number} rotation center relative to image, y coordinate + */ + graphicRotate: function(node, xOffset, yOffset) { + var style = style || node._style; + var options = node._options; + + var aspectRatio, size; + if (!(style.graphicWidth && style.graphicHeight)) { + // load the image to determine its size + var img = new Image(); + img.onreadystatechange = OpenLayers.Function.bind(function() { + if(img.readyState == "complete" || + img.readyState == "interactive") { + aspectRatio = img.width / img.height; + size = Math.max(style.pointRadius * 2, + style.graphicWidth || 0, + style.graphicHeight || 0); + xOffset = xOffset * aspectRatio; + style.graphicWidth = size * aspectRatio; + style.graphicHeight = size; + this.graphicRotate(node, xOffset, yOffset) + } + }, this); + img.src = style.externalGraphic; + + // will be called again by the onreadystate handler + return; + } else { + size = Math.max(style.graphicWidth, style.graphicHeight); + aspectRatio = style.graphicWidth / style.graphicHeight; + } + + var width = Math.round(style.graphicWidth || size * aspectRatio); + var height = Math.round(style.graphicHeight || size); + node.style.width = width; + node.style.height = height; + + // Three steps are required to remove artefacts for images with + // transparent backgrounds (resulting from using DXImageTransform + // filters on svg objects), while preserving awareness for browser + // events on images: + // - Use the fill as usual (like for unrotated images) to handle + // events + // - specify an imagedata element with the same src as the fill + // - style the imagedata element with an AlphaImageLoader filter + // with empty src + var image = document.getElementById(node.id + "_image"); + if (!image) { + image = this.createNode("olv:imagedata", node.id + "_image"); + node.appendChild(image); + } + image.style.width = width; + image.style.height = height; + image.src = style.externalGraphic; + image.style.filter = + "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + + "src='', sizingMethod='scale')"; + + var rotation = style.rotation * Math.PI / 180; + var sintheta = Math.sin(rotation); + var costheta = Math.cos(rotation); + + // do the rotation on the image + var filter = + "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta + + ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta + + ",SizingMethod='auto expand')\n" + + // set the opacity (needed for the imagedata) + var opacity = style.graphicOpacity || style.fillOpacity; + if (opacity && opacity != 1) { + filter += + "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + + opacity+")\n"; + } + node.style.filter = filter; + + // do the rotation again on a box, so we know the insertion point + var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset); + var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry(); + imgBox.rotate(style.rotation, centerPoint); + var imgBounds = imgBox.getBounds(); + + node.style.left = Math.round( + parseInt(node.style.left) + imgBounds.left); + node.style.top = Math.round( + parseInt(node.style.top) - imgBounds.bottom); + }, + /** * Method: postDraw * Some versions of Internet Explorer seem to be unable to set fillcolor diff --git a/tests/Renderer/VML.html b/tests/Renderer/VML.html index 17e837dac4..6569f8f213 100644 --- a/tests/Renderer/VML.html +++ b/tests/Renderer/VML.html @@ -143,6 +143,47 @@ t.eq(node.style.width, (2 * radius) + "px", "width is correct"); t.eq(node.style.height, (2 * radius) + "px", "height is correct"); } + + function test_VML_drawGraphic(t) { + if (!OpenLayers.Renderer.VML.prototype.supported()) { + t.plan(0); + return; + } + + t.plan(6); + + var r = new OpenLayers.Renderer.VML(document.body); + r.resolution = 1; + + var node = document.createElement('div'); + node.id = "test" + node._geometryClass = "OpenLayers.Geometry.Point"; + + var geometry = { + x: 1, + y: 2 + } + + var style = { + externalGraphic: "foo.png", + graphicWidth: 7, + graphicHeight: 10 + } + + r.drawGeometryNode(node, geometry, style); + + t.eq(node.childNodes[0].id, "test_fill", "fill child node correctly created"); + t.eq(node.style.left, "-3px", "x of insertion point with calculated xOffset correct"); + t.eq(node.style.top, "-3px", "y of insertion point with calculated yOffset correct"); + + style.rotation = 90; + + r.drawGeometryNode(node, geometry, style); + + t.eq(node.childNodes[1].id, "test_image", "image child node correctly created"); + t.eq(node.style.left, "-4px", "x of insertion point of rotated image correct"); + t.eq(node.style.top, "-4px", "y of insertion point of rotated image correct"); + } function test_VML_drawlinestring(t) { if (!OpenLayers.Renderer.VML.prototype.supported()) {