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
This commit is contained in:
83
examples/styles-rotation.html
Normal file
83
examples/styles-rotation.html
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>OpenLayers Styles Rotation Example</title>
|
||||||
|
<style type="text/css">
|
||||||
|
#map {
|
||||||
|
width: 800px;
|
||||||
|
height: 475px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="../lib/OpenLayers.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var map;
|
||||||
|
var vectors;
|
||||||
|
|
||||||
|
function init(){
|
||||||
|
map = new OpenLayers.Map('map');
|
||||||
|
var wms = new OpenLayers.Layer.WMS(
|
||||||
|
"OpenLayers WMS",
|
||||||
|
"http://labs.metacarta.com/wms/vmap0",
|
||||||
|
{layers: 'basic'}
|
||||||
|
);
|
||||||
|
|
||||||
|
vectors = new OpenLayers.Layer.Vector(
|
||||||
|
"Simple Geometry",
|
||||||
|
{
|
||||||
|
styleMap: new OpenLayers.StyleMap({
|
||||||
|
"default": {
|
||||||
|
externalGraphic: "../img/marker-gold.png",
|
||||||
|
//graphicWidth: 17,
|
||||||
|
graphicHeight: 20,
|
||||||
|
graphicYOffset: -19,
|
||||||
|
rotation: "${angle}",
|
||||||
|
fillOpacity: "${opacity}"
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
cursor: "crosshair",
|
||||||
|
externalGraphic: "../img/marker.png"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
map.addLayers([wms, vectors]);
|
||||||
|
|
||||||
|
var features = [];
|
||||||
|
var x = -111.04;
|
||||||
|
var y = 45.68;
|
||||||
|
for(var i = 0; i < 10; i++){
|
||||||
|
x += i * .5;
|
||||||
|
y += i * .1;
|
||||||
|
features.push(
|
||||||
|
new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.Point(x, y), {angle: (i*36)%360-180, opacity:i/10+.1}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
features.push(
|
||||||
|
new OpenLayers.Feature.Vector(
|
||||||
|
new OpenLayers.Geometry.Point(x, y), {angle: (i*36)%360, opacity:i/10+.1}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.setCenter(new OpenLayers.LonLat(x-10, y), 5);
|
||||||
|
vectors.addFeatures(features);
|
||||||
|
|
||||||
|
var selectControl = new OpenLayers.Control.SelectFeature(
|
||||||
|
vectors, {hover: true});
|
||||||
|
map.addControl(selectControl);
|
||||||
|
selectControl.activate();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
<h1 id="title">Rotation Styles Example</h1>
|
||||||
|
<p id="shortdesc">Vector point feature symbolizers can have a <tt>rotation</tt> property. The center of the rotation is the point of the image specified by <tt>graphicXOffset</tt> and <tt>graphicYOffset</tt>.</p>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div id="docs"/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -227,6 +227,9 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
|
|||||||
if(graphic.href != undefined) {
|
if(graphic.href != undefined) {
|
||||||
symbolizer.externalGraphic = graphic.href;
|
symbolizer.externalGraphic = graphic.href;
|
||||||
}
|
}
|
||||||
|
if(graphic.rotation != undefined) {
|
||||||
|
symbolizer.rotation = graphic.rotation;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ExternalGraphic": function(node, graphic) {
|
"ExternalGraphic": function(node, graphic) {
|
||||||
this.readChildNodes(node, graphic);
|
this.readChildNodes(node, graphic);
|
||||||
|
|||||||
@@ -222,6 +222,12 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
|
|||||||
} else {
|
} else {
|
||||||
node.setAttributeNS(null, "r", style.pointRadius);
|
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) {
|
if (options.isFilled) {
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
|
|||||||
node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
|
node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
|
||||||
node.style.width = width;
|
node.style.width = width;
|
||||||
node.style.height = height;
|
node.style.height = height;
|
||||||
|
node.style.flip = "y";
|
||||||
|
|
||||||
// modify style/options for fill and stroke styling below
|
// modify style/options for fill and stroke styling below
|
||||||
style.fillColor = "none";
|
style.fillColor = "none";
|
||||||
@@ -209,11 +210,20 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
|
|||||||
|
|
||||||
fill.setAttribute("src", style.externalGraphic);
|
fill.setAttribute("src", style.externalGraphic);
|
||||||
fill.setAttribute("type", "frame");
|
fill.setAttribute("type", "frame");
|
||||||
node.style.flip = "y";
|
|
||||||
|
|
||||||
if (!(style.graphicWidth && style.graphicHeight)) {
|
if (!(style.graphicWidth && style.graphicHeight)) {
|
||||||
fill.aspect = "atmost";
|
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) {
|
if (fill.parentNode != node) {
|
||||||
node.appendChild(fill);
|
node.appendChild(fill);
|
||||||
@@ -249,6 +259,112 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
|
|||||||
return node;
|
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
|
* Method: postDraw
|
||||||
* Some versions of Internet Explorer seem to be unable to set fillcolor
|
* Some versions of Internet Explorer seem to be unable to set fillcolor
|
||||||
|
|||||||
@@ -144,6 +144,47 @@
|
|||||||
t.eq(node.style.height, (2 * radius) + "px", "height 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) {
|
function test_VML_drawlinestring(t) {
|
||||||
if (!OpenLayers.Renderer.VML.prototype.supported()) {
|
if (!OpenLayers.Renderer.VML.prototype.supported()) {
|
||||||
t.plan(0);
|
t.plan(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user