1
/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3
* full text of the license. */
6
* @requires OpenLayers/Renderer/Elements.js
10
* Class: OpenLayers.Renderer.SVG
13
* - <OpenLayers.Renderer.Elements>
15
OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
21
xmlns: "http://www.w3.org/2000/svg",
27
xlinkns: "http://www.w3.org/1999/xlink",
31
* {Integer} Firefox has a limitation where values larger or smaller than
32
* about 15000 in an SVG document lock the browser up. This
38
* Property: translationParameters
39
* {Object} Hash with "x" and "y" properties
41
translationParameters: null,
44
* Property: symbolSize
45
* {Object} Cache for symbol sizes according to their svg coordinate space
50
* Constructor: OpenLayers.Renderer.SVG
53
* containerID - {String}
55
initialize: function(containerID) {
56
if (!this.supported()) {
59
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
61
this.translationParameters = {x: 0, y: 0};
68
OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
72
* APIMethod: supported
75
* {Boolean} Whether or not the browser supports the SVG renderer
77
supported: function() {
78
var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
79
return (document.implementation &&
80
(document.implementation.hasFeature("org.w3c.svg", "1.0") ||
81
document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
82
document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
86
* Method: inValidRange
87
* See #669 for more information
92
* xyOnly - {Boolean} whether or not to just check for x and y, which means
93
* to not take the current translation parameters into account if true.
96
* {Boolean} Whether or not the 'x' and 'y' coordinates are in the
99
inValidRange: function(x, y, xyOnly) {
100
var left = x + (xyOnly ? 0 : this.translationParameters.x);
101
var top = y + (xyOnly ? 0 : this.translationParameters.y);
102
return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
103
top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
110
* extent - {<OpenLayers.Bounds>}
111
* resolutionChanged - {Boolean}
114
* {Boolean} true to notify the layer that the new extent does not exceed
115
* the coordinate range, and the features will not need to be redrawn.
118
setExtent: function(extent, resolutionChanged) {
119
OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,
122
var resolution = this.getResolution();
123
var left = -extent.left / resolution;
124
var top = extent.top / resolution;
126
// If the resolution has changed, start over changing the corner, because
127
// the features will redraw.
128
if (resolutionChanged) {
132
var extentString = "0 0 " + this.size.w + " " + this.size.h;
134
this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
135
this.translate(0, 0);
138
var inRange = this.translate(left - this.left, top - this.top);
140
// recenter the coordinate system
141
this.setExtent(extent, true);
149
* Transforms the SVG coordinate system
156
* {Boolean} true if the translation parameters are in the valid coordinates
157
* range, false otherwise.
159
translate: function(x, y) {
160
if (!this.inValidRange(x, y, true)) {
163
var transformString = "";
165
transformString = "translate(" + x + "," + y + ")";
167
this.root.setAttributeNS(null, "transform", transformString);
168
this.translationParameters = {x: x, y: y};
175
* Sets the size of the drawing surface.
178
* size - {<OpenLayers.Size>} The size of the drawing surface
180
setSize: function(size) {
181
OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
183
this.rendererRoot.setAttributeNS(null, "width", this.size.w);
184
this.rendererRoot.setAttributeNS(null, "height", this.size.h);
188
* Method: getNodeType
191
* geometry - {<OpenLayers.Geometry>}
195
* {String} The corresponding node type for the specified geometry
197
getNodeType: function(geometry, style) {
199
switch (geometry.CLASS_NAME) {
200
case "OpenLayers.Geometry.Point":
201
if (style.externalGraphic) {
203
} else if (this.isComplexSymbol(style.graphicName)) {
209
case "OpenLayers.Geometry.Rectangle":
212
case "OpenLayers.Geometry.LineString":
213
nodeType = "polyline";
215
case "OpenLayers.Geometry.LinearRing":
216
nodeType = "polygon";
218
case "OpenLayers.Geometry.Polygon":
219
case "OpenLayers.Geometry.Curve":
220
case "OpenLayers.Geometry.Surface":
231
* Use to set all the style attributes to a SVG node.
233
* Takes care to adjust stroke width and point radius to be
234
* resolution-relative
237
* node - {SVGDomElement} An SVG element to decorate
239
* options - {Object} Currently supported options include
240
* 'isFilled' {Boolean} and
241
* 'isStroked' {Boolean}
243
setStyle: function(node, style, options) {
244
style = style || node._style;
245
options = options || node._options;
246
var r = parseFloat(node.getAttributeNS(null, "r"));
249
if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
250
if (style.externalGraphic) {
251
pos = this.getPosition(node);
253
if (style.graphicWidth && style.graphicHeight) {
254
node.setAttributeNS(null, "preserveAspectRatio", "none");
256
var width = style.graphicWidth || style.graphicHeight;
257
var height = style.graphicHeight || style.graphicWidth;
258
width = width ? width : style.pointRadius*2;
259
height = height ? height : style.pointRadius*2;
260
var xOffset = (style.graphicXOffset != undefined) ?
261
style.graphicXOffset : -(0.5 * width);
262
var yOffset = (style.graphicYOffset != undefined) ?
263
style.graphicYOffset : -(0.5 * height);
265
var opacity = style.graphicOpacity || style.fillOpacity;
267
node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
268
node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
269
node.setAttributeNS(null, "width", width);
270
node.setAttributeNS(null, "height", height);
271
node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
272
node.setAttributeNS(null, "style", "opacity: "+opacity);
273
} else if (this.isComplexSymbol(style.graphicName)) {
274
// the symbol viewBox is three times as large as the symbol
275
var offset = style.pointRadius * 3;
276
var size = offset * 2;
277
var id = this.importSymbol(style.graphicName);
279
pos = this.getPosition(node);
280
widthFactor = this.symbolSize[id] / size;
281
// Only set the href if it is different from the current one.
282
// This is a workaround for strange rendering behavior in FF3.
283
if (node.getAttributeNS(this.xlinkns, "href") != href) {
284
node.setAttributeNS(this.xlinkns, "href", href);
285
} else if (size != parseFloat(node.getAttributeNS(null, "width"))) {
286
// hide the element (and force a reflow so it really gets
287
// hidden. This workaround is needed for Safari.
288
node.style.visibility = "hidden";
289
this.container.scrollLeft = this.container.scrollLeft;
291
node.setAttributeNS(null, "width", size);
292
node.setAttributeNS(null, "height", size);
293
node.setAttributeNS(null, "x", pos.x - offset);
294
node.setAttributeNS(null, "y", pos.y - offset);
295
// set the visibility back to normal (after the Safari
297
node.style.visibility = "";
299
node.setAttributeNS(null, "r", style.pointRadius);
302
if (typeof style.rotation != "undefined" && pos) {
303
var rotation = OpenLayers.String.format(
304
"rotate(${0} ${1} ${2})", [style.rotation, pos.x, pos.y]);
305
node.setAttributeNS(null, "transform", rotation);
309
if (options.isFilled) {
310
node.setAttributeNS(null, "fill", style.fillColor);
311
node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
313
node.setAttributeNS(null, "fill", "none");
316
if (options.isStroked) {
317
node.setAttributeNS(null, "stroke", style.strokeColor);
318
node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
319
node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
320
node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap);
321
// Hard-coded linejoin for now, to make it look the same as in VML.
322
// There is no strokeLinejoin property yet for symbolizers.
323
node.setAttributeNS(null, "stroke-linejoin", "round");
324
node.setAttributeNS(null, "stroke-dasharray", this.dashStyle(style,
327
node.setAttributeNS(null, "stroke", "none");
330
if (style.pointerEvents) {
331
node.setAttributeNS(null, "pointer-events", style.pointerEvents);
334
if (style.cursor != null) {
335
node.setAttributeNS(null, "cursor", style.cursor);
345
* widthFactor - {Number}
348
* {String} A SVG compliant 'stroke-dasharray' value
350
dashStyle: function(style, widthFactor) {
351
var w = style.strokeWidth * widthFactor;
353
switch (style.strokeDashstyle) {
357
return [1, 4 * w].join();
359
return [4 * w, 4 * w].join();
361
return [4 * w, 4 * w, 1, 4 * w].join();
363
return [8 * w, 4 * w].join();
365
return [8 * w, 4 * w, 1, 4 * w].join();
367
return style.strokeDashstyle.replace(/ /g, ",");
375
* type - {String} Kind of node to draw
376
* id - {String} Id for node
379
* {DOMElement} A new node of the given type and id
381
createNode: function(type, id) {
382
var node = document.createElementNS(this.xmlns, type);
384
node.setAttributeNS(null, "id", id);
390
* Method: nodeTypeCompare
393
* node - {SVGDomElement} An SVG element
394
* type - {String} Kind of node
397
* {Boolean} Whether or not the specified node is of the specified type
399
nodeTypeCompare: function(node, type) {
400
return (type == node.nodeName);
404
* Method: createRenderRoot
407
* {DOMElement} The specific render engine's root element
409
createRenderRoot: function() {
410
return this.nodeFactory(this.container.id + "_svgRoot", "svg");
417
* {DOMElement} The main root element to which we'll add vectors
419
createRoot: function() {
420
return this.nodeFactory(this.container.id + "_root", "g");
427
* {DOMElement} The element to which we'll add the symbol definitions
429
createDefs: function() {
430
var defs = this.nodeFactory("ol-renderer-defs", "defs");
431
this.rendererRoot.appendChild(defs);
435
/**************************************
437
* GEOMETRY DRAWING FUNCTIONS *
439
**************************************/
443
* This method is only called by the renderer itself.
446
* node - {DOMElement}
447
* geometry - {<OpenLayers.Geometry>}
450
* {DOMElement} or false if the renderer could not draw the point
452
drawPoint: function(node, geometry) {
453
return this.drawCircle(node, geometry, 1);
458
* This method is only called by the renderer itself.
461
* node - {DOMElement}
462
* geometry - {<OpenLayers.Geometry>}
466
* {DOMElement} or false if the renderer could not draw the circle
468
drawCircle: function(node, geometry, radius) {
469
var resolution = this.getResolution();
470
var x = (geometry.x / resolution + this.left);
471
var y = (this.top - geometry.y / resolution);
473
if (this.inValidRange(x, y)) {
474
node.setAttributeNS(null, "cx", x);
475
node.setAttributeNS(null, "cy", y);
476
node.setAttributeNS(null, "r", radius);
485
* Method: drawLineString
486
* This method is only called by the renderer itself.
489
* node - {DOMElement}
490
* geometry - {<OpenLayers.Geometry>}
493
* {DOMElement} or null if the renderer could not draw all components of
494
* the linestring, or false if nothing could be drawn
496
drawLineString: function(node, geometry) {
497
var componentsResult = this.getComponentsString(geometry.components);
498
if (componentsResult.path) {
499
node.setAttributeNS(null, "points", componentsResult.path);
500
return (componentsResult.complete ? node : null);
507
* Method: drawLinearRing
508
* This method is only called by the renderer itself.
511
* node - {DOMElement}
512
* geometry - {<OpenLayers.Geometry>}
515
* {DOMElement} or null if the renderer could not draw all components
516
* of the linear ring, or false if nothing could be drawn
518
drawLinearRing: function(node, geometry) {
519
var componentsResult = this.getComponentsString(geometry.components);
520
if (componentsResult.path) {
521
node.setAttributeNS(null, "points", componentsResult.path);
522
return (componentsResult.complete ? node : null);
529
* Method: drawPolygon
530
* This method is only called by the renderer itself.
533
* node - {DOMElement}
534
* geometry - {<OpenLayers.Geometry>}
537
* {DOMElement} or null if the renderer could not draw all components
538
* of the polygon, or false if nothing could be drawn
540
drawPolygon: function(node, geometry) {
544
var linearRingResult, path;
545
for (var j=0, len=geometry.components.length; j<len; j++) {
547
linearRingResult = this.getComponentsString(
548
geometry.components[j].components, " ");
549
path = linearRingResult.path;
552
complete = linearRingResult.complete && complete;
559
node.setAttributeNS(null, "d", d);
560
node.setAttributeNS(null, "fill-rule", "evenodd");
561
return complete ? node : null;
568
* Method: drawRectangle
569
* This method is only called by the renderer itself.
572
* node - {DOMElement}
573
* geometry - {<OpenLayers.Geometry>}
576
* {DOMElement} or false if the renderer could not draw the rectangle
578
drawRectangle: function(node, geometry) {
579
var resolution = this.getResolution();
580
var x = (geometry.x / resolution + this.left);
581
var y = (this.top - geometry.y / resolution);
583
if (this.inValidRange(x, y)) {
584
node.setAttributeNS(null, "x", x);
585
node.setAttributeNS(null, "y", y);
586
node.setAttributeNS(null, "width", geometry.width / resolution);
587
node.setAttributeNS(null, "height", geometry.height / resolution);
595
* Method: drawSurface
596
* This method is only called by the renderer itself.
599
* node - {DOMElement}
600
* geometry - {<OpenLayers.Geometry>}
603
* {DOMElement} or false if the renderer could not draw the surface
605
drawSurface: function(node, geometry) {
607
// create the svg path string representation
610
for (var i=0, len=geometry.components.length; i<len; i++) {
611
if ((i%3) == 0 && (i/3) == 0) {
612
var component = this.getShortString(geometry.components[i]);
613
if (!component) { draw = false; }
614
d = "M " + component;
615
} else if ((i%3) == 1) {
616
var component = this.getShortString(geometry.components[i]);
617
if (!component) { draw = false; }
618
d += " C " + component;
620
var component = this.getShortString(geometry.components[i]);
621
if (!component) { draw = false; }
622
d += " " + component;
627
node.setAttributeNS(null, "d", d);
635
* Method: getComponentString
638
* components - {Array(<OpenLayers.Geometry.Point>)} Array of points
639
* separator - {String} character between coordinate pairs. Defaults to ","
642
* {Object} hash with properties "path" (the string created from the
643
* components and "complete" (false if the renderer was unable to
644
* draw all components)
646
getComponentsString: function(components, separator) {
649
var len = components.length;
651
var str, component, j;
652
for(var i=0; i<len; i++) {
653
component = components[i];
654
renderCmp.push(component);
655
str = this.getShortString(component);
659
// The current component is outside the valid range. Let's
660
// see if the previous or next component is inside the range.
661
// If so, add the coordinate of the intersection with the
662
// valid range bounds.
664
if (this.getShortString(components[i - 1])) {
665
strings.push(this.clipLine(components[i],
670
if (this.getShortString(components[i + 1])) {
671
strings.push(this.clipLine(components[i],
680
path: strings.join(separator || ","),
687
* Given two points (one inside the valid range, and one outside),
688
* clips the line betweeen the two points so that the new points are both
689
* inside the valid range.
692
* badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
694
* goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
697
* {String} the SVG coordinate pair of the clipped point (like
698
* getShortString), or an empty string if both passed componets are at
701
clipLine: function(badComponent, goodComponent) {
702
if (goodComponent.equals(badComponent)) {
705
var resolution = this.getResolution();
706
var maxX = this.MAX_PIXEL - this.translationParameters.x;
707
var maxY = this.MAX_PIXEL - this.translationParameters.y;
708
var x1 = goodComponent.x / resolution + this.left;
709
var y1 = this.top - goodComponent.y / resolution;
710
var x2 = badComponent.x / resolution + this.left;
711
var y2 = this.top - badComponent.y / resolution;
713
if (x2 < -maxX || x2 > maxX) {
714
k = (y2 - y1) / (x2 - x1);
715
x2 = x2 < 0 ? -maxX : maxX;
716
y2 = y1 + (x2 - x1) * k;
718
if (y2 < -maxY || y2 > maxY) {
719
k = (x2 - x1) / (y2 - y1);
720
y2 = y2 < 0 ? -maxY : maxY;
721
x2 = x1 + (y2 - y1) * k;
723
return x2 + "," + y2;
727
* Method: getShortString
730
* point - {<OpenLayers.Geometry.Point>}
733
* {String} or false if point is outside the valid range
735
getShortString: function(point) {
736
var resolution = this.getResolution();
737
var x = (point.x / resolution + this.left);
738
var y = (this.top - point.y / resolution);
740
if (this.inValidRange(x, y)) {
748
* Method: getPosition
749
* Finds the position of an svg node.
752
* node - {DOMElement}
755
* {Object} hash with x and y properties, representing the coordinates
756
* within the svg coordinate system
758
getPosition: function(node) {
760
x: parseFloat(node.getAttributeNS(null, "cx")),
761
y: parseFloat(node.getAttributeNS(null, "cy"))
766
* Method: importSymbol
767
* add a new symbol definition from the rendererer's symbol hash
770
* graphicName - {String} name of the symbol to import
773
* {String} - id of the imported symbol
775
importSymbol: function (graphicName) {
777
// create svg defs tag
778
this.defs = this.createDefs();
780
var id = this.container.id + "-" + graphicName;
782
// check if symbol already exists in the defs
783
if (document.getElementById(id) != null) {
787
var symbol = OpenLayers.Renderer.symbol[graphicName];
789
throw new Error(graphicName + ' is not a valid symbol name');
793
var symbolNode = this.nodeFactory(id, "symbol");
794
var node = this.nodeFactory(null, "polygon");
795
symbolNode.appendChild(node);
796
var symbolExtent = new OpenLayers.Bounds(
797
Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
801
for (var i=0; i<symbol.length; i=i+2) {
804
symbolExtent.left = Math.min(symbolExtent.left, x);
805
symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
806
symbolExtent.right = Math.max(symbolExtent.right, x);
807
symbolExtent.top = Math.max(symbolExtent.top, y);
808
points += " " + x + "," + y;
811
node.setAttributeNS(null, "points", points);
813
var width = symbolExtent.getWidth();
814
var height = symbolExtent.getHeight();
815
// create a viewBox three times as large as the symbol itself,
816
// to allow for strokeWidth being displayed correctly at the corners.
817
var viewBox = [symbolExtent.left - width,
818
symbolExtent.bottom - height, width * 3, height * 3];
819
symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
820
this.symbolSize[id] = Math.max(width, height) * 3;
822
this.defs.appendChild(symbolNode);
823
return symbolNode.id;
826
CLASS_NAME: "OpenLayers.Renderer.SVG"