1 | /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
---|
2 | * full list of contributors). Published under the Clear BSD license. |
---|
3 | * See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
---|
4 | * full text of the license. */ |
---|
5 | |
---|
6 | /** |
---|
7 | * @requires OpenLayers/Renderer/Elements.js |
---|
8 | */ |
---|
9 | |
---|
10 | /** |
---|
11 | * Class: OpenLayers.Renderer.SVG |
---|
12 | * |
---|
13 | * Inherits: |
---|
14 | * - <OpenLayers.Renderer.Elements> |
---|
15 | */ |
---|
16 | OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, { |
---|
17 | |
---|
18 | /** |
---|
19 | * Property: xmlns |
---|
20 | * {String} |
---|
21 | */ |
---|
22 | xmlns: "http://www.w3.org/2000/svg", |
---|
23 | |
---|
24 | /** |
---|
25 | * Property: xlinkns |
---|
26 | * {String} |
---|
27 | */ |
---|
28 | xlinkns: "http://www.w3.org/1999/xlink", |
---|
29 | |
---|
30 | /** |
---|
31 | * Constant: MAX_PIXEL |
---|
32 | * {Integer} Firefox has a limitation where values larger or smaller than |
---|
33 | * about 15000 in an SVG document lock the browser up. This |
---|
34 | * works around it. |
---|
35 | */ |
---|
36 | MAX_PIXEL: 15000, |
---|
37 | |
---|
38 | /** |
---|
39 | * Property: translationParameters |
---|
40 | * {Object} Hash with "x" and "y" properties |
---|
41 | */ |
---|
42 | translationParameters: null, |
---|
43 | |
---|
44 | /** |
---|
45 | * Property: symbolMetrics |
---|
46 | * {Object} Cache for symbol metrics according to their svg coordinate |
---|
47 | * space. This is an object keyed by the symbol's id, and values are |
---|
48 | * an array of [width, centerX, centerY]. |
---|
49 | */ |
---|
50 | symbolMetrics: null, |
---|
51 | |
---|
52 | /** |
---|
53 | * Property: isGecko |
---|
54 | * {Boolean} |
---|
55 | */ |
---|
56 | isGecko: null, |
---|
57 | |
---|
58 | /** |
---|
59 | * Property: supportUse |
---|
60 | * {Boolean} true if defs/use is supported - known to not work as expected |
---|
61 | * at least in some applewebkit/5* builds. |
---|
62 | * See https://bugs.webkit.org/show_bug.cgi?id=33322 |
---|
63 | */ |
---|
64 | supportUse: null, |
---|
65 | |
---|
66 | /** |
---|
67 | * Constructor: OpenLayers.Renderer.SVG |
---|
68 | * |
---|
69 | * Parameters: |
---|
70 | * containerID - {String} |
---|
71 | */ |
---|
72 | initialize: function(containerID) { |
---|
73 | if (!this.supported()) { |
---|
74 | return; |
---|
75 | } |
---|
76 | OpenLayers.Renderer.Elements.prototype.initialize.apply(this, |
---|
77 | arguments); |
---|
78 | this.translationParameters = {x: 0, y: 0}; |
---|
79 | this.supportUse = (navigator.userAgent.toLowerCase().indexOf("applewebkit/5") == -1); |
---|
80 | this.isGecko = (navigator.userAgent.toLowerCase().indexOf("gecko/") != -1); |
---|
81 | |
---|
82 | this.symbolMetrics = {}; |
---|
83 | }, |
---|
84 | |
---|
85 | /** |
---|
86 | * APIMethod: destroy |
---|
87 | */ |
---|
88 | destroy: function() { |
---|
89 | OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments); |
---|
90 | }, |
---|
91 | |
---|
92 | /** |
---|
93 | * APIMethod: supported |
---|
94 | * |
---|
95 | * Returns: |
---|
96 | * {Boolean} Whether or not the browser supports the SVG renderer |
---|
97 | */ |
---|
98 | supported: function() { |
---|
99 | var svgFeature = "http://www.w3.org/TR/SVG11/feature#"; |
---|
100 | return (document.implementation && |
---|
101 | (document.implementation.hasFeature("org.w3c.svg", "1.0") || |
---|
102 | document.implementation.hasFeature(svgFeature + "SVG", "1.1") || |
---|
103 | document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") )); |
---|
104 | }, |
---|
105 | |
---|
106 | /** |
---|
107 | * Method: inValidRange |
---|
108 | * See #669 for more information |
---|
109 | * |
---|
110 | * Parameters: |
---|
111 | * x - {Integer} |
---|
112 | * y - {Integer} |
---|
113 | * xyOnly - {Boolean} whether or not to just check for x and y, which means |
---|
114 | * to not take the current translation parameters into account if true. |
---|
115 | * |
---|
116 | * Returns: |
---|
117 | * {Boolean} Whether or not the 'x' and 'y' coordinates are in the |
---|
118 | * valid range. |
---|
119 | */ |
---|
120 | inValidRange: function(x, y, xyOnly) { |
---|
121 | var left = x + (xyOnly ? 0 : this.translationParameters.x); |
---|
122 | var top = y + (xyOnly ? 0 : this.translationParameters.y); |
---|
123 | return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL && |
---|
124 | top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL); |
---|
125 | }, |
---|
126 | |
---|
127 | /** |
---|
128 | * Method: setExtent |
---|
129 | * |
---|
130 | * Parameters: |
---|
131 | * extent - {<OpenLayers.Bounds>} |
---|
132 | * resolutionChanged - {Boolean} |
---|
133 | * |
---|
134 | * Returns: |
---|
135 | * {Boolean} true to notify the layer that the new extent does not exceed |
---|
136 | * the coordinate range, and the features will not need to be redrawn. |
---|
137 | * False otherwise. |
---|
138 | */ |
---|
139 | setExtent: function(extent, resolutionChanged) { |
---|
140 | OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, |
---|
141 | arguments); |
---|
142 | |
---|
143 | var resolution = this.getResolution(); |
---|
144 | var left = -extent.left / resolution; |
---|
145 | var top = extent.top / resolution; |
---|
146 | |
---|
147 | // If the resolution has changed, start over changing the corner, because |
---|
148 | // the features will redraw. |
---|
149 | if (resolutionChanged) { |
---|
150 | this.left = left; |
---|
151 | this.top = top; |
---|
152 | // Set the viewbox |
---|
153 | var extentString = "0 0 " + this.size.w + " " + this.size.h; |
---|
154 | |
---|
155 | this.rendererRoot.setAttributeNS(null, "viewBox", extentString); |
---|
156 | this.translate(0, 0); |
---|
157 | return true; |
---|
158 | } else { |
---|
159 | var inRange = this.translate(left - this.left, top - this.top); |
---|
160 | if (!inRange) { |
---|
161 | // recenter the coordinate system |
---|
162 | this.setExtent(extent, true); |
---|
163 | } |
---|
164 | return inRange; |
---|
165 | } |
---|
166 | }, |
---|
167 | |
---|
168 | /** |
---|
169 | * Method: translate |
---|
170 | * Transforms the SVG coordinate system |
---|
171 | * |
---|
172 | * Parameters: |
---|
173 | * x - {Float} |
---|
174 | * y - {Float} |
---|
175 | * |
---|
176 | * Returns: |
---|
177 | * {Boolean} true if the translation parameters are in the valid coordinates |
---|
178 | * range, false otherwise. |
---|
179 | */ |
---|
180 | translate: function(x, y) { |
---|
181 | if (!this.inValidRange(x, y, true)) { |
---|
182 | return false; |
---|
183 | } else { |
---|
184 | var transformString = ""; |
---|
185 | if (x || y) { |
---|
186 | transformString = "translate(" + x + "," + y + ")"; |
---|
187 | } |
---|
188 | this.root.setAttributeNS(null, "transform", transformString); |
---|
189 | this.translationParameters = {x: x, y: y}; |
---|
190 | return true; |
---|
191 | } |
---|
192 | }, |
---|
193 | |
---|
194 | /** |
---|
195 | * Method: setSize |
---|
196 | * Sets the size of the drawing surface. |
---|
197 | * |
---|
198 | * Parameters: |
---|
199 | * size - {<OpenLayers.Size>} The size of the drawing surface |
---|
200 | */ |
---|
201 | setSize: function(size) { |
---|
202 | OpenLayers.Renderer.prototype.setSize.apply(this, arguments); |
---|
203 | |
---|
204 | this.rendererRoot.setAttributeNS(null, "width", this.size.w); |
---|
205 | this.rendererRoot.setAttributeNS(null, "height", this.size.h); |
---|
206 | }, |
---|
207 | |
---|
208 | /** |
---|
209 | * Method: getNodeType |
---|
210 | * |
---|
211 | * Parameters: |
---|
212 | * geometry - {<OpenLayers.Geometry>} |
---|
213 | * style - {Object} |
---|
214 | * |
---|
215 | * Returns: |
---|
216 | * {String} The corresponding node type for the specified geometry |
---|
217 | */ |
---|
218 | getNodeType: function(geometry, style) { |
---|
219 | var nodeType = null; |
---|
220 | switch (geometry.CLASS_NAME) { |
---|
221 | case "OpenLayers.Geometry.Point": |
---|
222 | if (style.externalGraphic) { |
---|
223 | nodeType = "image"; |
---|
224 | } else if (this.isComplexSymbol(style.graphicName)) { |
---|
225 | nodeType = this.supportUse === false ? "svg" : "use"; |
---|
226 | } else { |
---|
227 | nodeType = "circle"; |
---|
228 | } |
---|
229 | break; |
---|
230 | case "OpenLayers.Geometry.Rectangle": |
---|
231 | nodeType = "rect"; |
---|
232 | break; |
---|
233 | case "OpenLayers.Geometry.LineString": |
---|
234 | nodeType = "polyline"; |
---|
235 | break; |
---|
236 | case "OpenLayers.Geometry.LinearRing": |
---|
237 | nodeType = "polygon"; |
---|
238 | break; |
---|
239 | case "OpenLayers.Geometry.Polygon": |
---|
240 | case "OpenLayers.Geometry.Curve": |
---|
241 | case "OpenLayers.Geometry.Surface": |
---|
242 | nodeType = "path"; |
---|
243 | break; |
---|
244 | default: |
---|
245 | break; |
---|
246 | } |
---|
247 | return nodeType; |
---|
248 | }, |
---|
249 | |
---|
250 | /** |
---|
251 | * Method: setStyle |
---|
252 | * Use to set all the style attributes to a SVG node. |
---|
253 | * |
---|
254 | * Takes care to adjust stroke width and point radius to be |
---|
255 | * resolution-relative |
---|
256 | * |
---|
257 | * Parameters: |
---|
258 | * node - {SVGDomElement} An SVG element to decorate |
---|
259 | * style - {Object} |
---|
260 | * options - {Object} Currently supported options include |
---|
261 | * 'isFilled' {Boolean} and |
---|
262 | * 'isStroked' {Boolean} |
---|
263 | */ |
---|
264 | setStyle: function(node, style, options) { |
---|
265 | style = style || node._style; |
---|
266 | options = options || node._options; |
---|
267 | var r = parseFloat(node.getAttributeNS(null, "r")); |
---|
268 | var widthFactor = 1; |
---|
269 | var pos; |
---|
270 | if (node._geometryClass == "OpenLayers.Geometry.Point" && r) { |
---|
271 | node.style.visibility = ""; |
---|
272 | if (style.graphic === false) { |
---|
273 | node.style.visibility = "hidden"; |
---|
274 | } else if (style.externalGraphic) { |
---|
275 | pos = this.getPosition(node); |
---|
276 | |
---|
277 | if (style.graphicTitle) { |
---|
278 | node.setAttributeNS(null, "title", style.graphicTitle); |
---|
279 | } |
---|
280 | if (style.graphicWidth && style.graphicHeight) { |
---|
281 | node.setAttributeNS(null, "preserveAspectRatio", "none"); |
---|
282 | } |
---|
283 | var width = style.graphicWidth || style.graphicHeight; |
---|
284 | var height = style.graphicHeight || style.graphicWidth; |
---|
285 | width = width ? width : style.pointRadius*2; |
---|
286 | height = height ? height : style.pointRadius*2; |
---|
287 | var xOffset = (style.graphicXOffset != undefined) ? |
---|
288 | style.graphicXOffset : -(0.5 * width); |
---|
289 | var yOffset = (style.graphicYOffset != undefined) ? |
---|
290 | style.graphicYOffset : -(0.5 * height); |
---|
291 | |
---|
292 | var opacity = style.graphicOpacity || style.fillOpacity; |
---|
293 | |
---|
294 | node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed()); |
---|
295 | node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed()); |
---|
296 | node.setAttributeNS(null, "width", width); |
---|
297 | node.setAttributeNS(null, "height", height); |
---|
298 | node.setAttributeNS(this.xlinkns, "href", style.externalGraphic); |
---|
299 | node.setAttributeNS(null, "style", "opacity: "+opacity); |
---|
300 | } else if (this.isComplexSymbol(style.graphicName)) { |
---|
301 | // the symbol viewBox is three times as large as the symbol |
---|
302 | var offset = style.pointRadius * 3; |
---|
303 | var size = offset * 2; |
---|
304 | var id = this.importSymbol(style.graphicName); |
---|
305 | pos = this.getPosition(node); |
---|
306 | widthFactor = this.symbolMetrics[id][0] * 3 / size; |
---|
307 | |
---|
308 | // remove the node from the dom before we modify it. This |
---|
309 | // prevents various rendering issues in Safari and FF |
---|
310 | var parent = node.parentNode; |
---|
311 | var nextSibling = node.nextSibling; |
---|
312 | if(parent) { |
---|
313 | parent.removeChild(node); |
---|
314 | } |
---|
315 | |
---|
316 | if(this.supportUse === false) { |
---|
317 | // workaround for webkit versions that cannot do defs/use |
---|
318 | // (see https://bugs.webkit.org/show_bug.cgi?id=33322): |
---|
319 | // copy the symbol instead of referencing it |
---|
320 | var src = document.getElementById(id); |
---|
321 | node.firstChild && node.removeChild(node.firstChild); |
---|
322 | node.appendChild(src.firstChild.cloneNode(true)); |
---|
323 | node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox")); |
---|
324 | } else { |
---|
325 | node.setAttributeNS(this.xlinkns, "href", "#" + id); |
---|
326 | } |
---|
327 | node.setAttributeNS(null, "width", size); |
---|
328 | node.setAttributeNS(null, "height", size); |
---|
329 | node.setAttributeNS(null, "x", pos.x - offset); |
---|
330 | node.setAttributeNS(null, "y", pos.y - offset); |
---|
331 | |
---|
332 | // now that the node has all its new properties, insert it |
---|
333 | // back into the dom where it was |
---|
334 | if(nextSibling) { |
---|
335 | parent.insertBefore(node, nextSibling); |
---|
336 | } else if(parent) { |
---|
337 | parent.appendChild(node); |
---|
338 | } |
---|
339 | } else { |
---|
340 | node.setAttributeNS(null, "r", style.pointRadius); |
---|
341 | } |
---|
342 | |
---|
343 | var rotation = style.rotation; |
---|
344 | if ((rotation !== undefined || node._rotation !== undefined) && pos) { |
---|
345 | node._rotation = rotation; |
---|
346 | rotation |= 0; |
---|
347 | if(node.nodeName !== "svg") { |
---|
348 | node.setAttributeNS(null, "transform", |
---|
349 | "rotate(" + rotation + " " + pos.x + " " + |
---|
350 | pos.y + ")"); |
---|
351 | } else { |
---|
352 | var metrics = this.symbolMetrics[id]; |
---|
353 | node.firstChild.setAttributeNS(null, "transform", |
---|
354 | "rotate(" + style.rotation + " " + metrics[1] + |
---|
355 | " " + metrics[2] + ")"); |
---|
356 | } |
---|
357 | } |
---|
358 | } |
---|
359 | |
---|
360 | if (options.isFilled) { |
---|
361 | node.setAttributeNS(null, "fill", style.fillColor); |
---|
362 | node.setAttributeNS(null, "fill-opacity", style.fillOpacity); |
---|
363 | } else { |
---|
364 | node.setAttributeNS(null, "fill", "none"); |
---|
365 | } |
---|
366 | |
---|
367 | if (options.isStroked) { |
---|
368 | node.setAttributeNS(null, "stroke", style.strokeColor); |
---|
369 | node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity); |
---|
370 | node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor); |
---|
371 | node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round"); |
---|
372 | // Hard-coded linejoin for now, to make it look the same as in VML. |
---|
373 | // There is no strokeLinejoin property yet for symbolizers. |
---|
374 | node.setAttributeNS(null, "stroke-linejoin", "round"); |
---|
375 | style.strokeDashstyle && node.setAttributeNS(null, |
---|
376 | "stroke-dasharray", this.dashStyle(style, widthFactor)); |
---|
377 | } else { |
---|
378 | node.setAttributeNS(null, "stroke", "none"); |
---|
379 | } |
---|
380 | |
---|
381 | if (style.pointerEvents) { |
---|
382 | node.setAttributeNS(null, "pointer-events", style.pointerEvents); |
---|
383 | } |
---|
384 | |
---|
385 | if (style.cursor != null) { |
---|
386 | node.setAttributeNS(null, "cursor", style.cursor); |
---|
387 | } |
---|
388 | |
---|
389 | return node; |
---|
390 | }, |
---|
391 | |
---|
392 | /** |
---|
393 | * Method: dashStyle |
---|
394 | * |
---|
395 | * Parameters: |
---|
396 | * style - {Object} |
---|
397 | * widthFactor - {Number} |
---|
398 | * |
---|
399 | * Returns: |
---|
400 | * {String} A SVG compliant 'stroke-dasharray' value |
---|
401 | */ |
---|
402 | dashStyle: function(style, widthFactor) { |
---|
403 | var w = style.strokeWidth * widthFactor; |
---|
404 | var str = style.strokeDashstyle; |
---|
405 | switch (str) { |
---|
406 | case 'solid': |
---|
407 | return 'none'; |
---|
408 | case 'dot': |
---|
409 | return [1, 4 * w].join(); |
---|
410 | case 'dash': |
---|
411 | return [4 * w, 4 * w].join(); |
---|
412 | case 'dashdot': |
---|
413 | return [4 * w, 4 * w, 1, 4 * w].join(); |
---|
414 | case 'longdash': |
---|
415 | return [8 * w, 4 * w].join(); |
---|
416 | case 'longdashdot': |
---|
417 | return [8 * w, 4 * w, 1, 4 * w].join(); |
---|
418 | default: |
---|
419 | return OpenLayers.String.trim(str).replace(/\s+/g, ","); |
---|
420 | } |
---|
421 | }, |
---|
422 | |
---|
423 | /** |
---|
424 | * Method: createNode |
---|
425 | * |
---|
426 | * Parameters: |
---|
427 | * type - {String} Kind of node to draw |
---|
428 | * id - {String} Id for node |
---|
429 | * |
---|
430 | * Returns: |
---|
431 | * {DOMElement} A new node of the given type and id |
---|
432 | */ |
---|
433 | createNode: function(type, id) { |
---|
434 | var node = document.createElementNS(this.xmlns, type); |
---|
435 | if (id) { |
---|
436 | node.setAttributeNS(null, "id", id); |
---|
437 | } |
---|
438 | return node; |
---|
439 | }, |
---|
440 | |
---|
441 | /** |
---|
442 | * Method: nodeTypeCompare |
---|
443 | * |
---|
444 | * Parameters: |
---|
445 | * node - {SVGDomElement} An SVG element |
---|
446 | * type - {String} Kind of node |
---|
447 | * |
---|
448 | * Returns: |
---|
449 | * {Boolean} Whether or not the specified node is of the specified type |
---|
450 | */ |
---|
451 | nodeTypeCompare: function(node, type) { |
---|
452 | return (type == node.nodeName); |
---|
453 | }, |
---|
454 | |
---|
455 | /** |
---|
456 | * Method: createRenderRoot |
---|
457 | * |
---|
458 | * Returns: |
---|
459 | * {DOMElement} The specific render engine's root element |
---|
460 | */ |
---|
461 | createRenderRoot: function() { |
---|
462 | return this.nodeFactory(this.container.id + "_svgRoot", "svg"); |
---|
463 | }, |
---|
464 | |
---|
465 | /** |
---|
466 | * Method: createRoot |
---|
467 | * |
---|
468 | * Parameter: |
---|
469 | * suffix - {String} suffix to append to the id |
---|
470 | * |
---|
471 | * Returns: |
---|
472 | * {DOMElement} |
---|
473 | */ |
---|
474 | createRoot: function(suffix) { |
---|
475 | return this.nodeFactory(this.container.id + suffix, "g"); |
---|
476 | }, |
---|
477 | |
---|
478 | /** |
---|
479 | * Method: createDefs |
---|
480 | * |
---|
481 | * Returns: |
---|
482 | * {DOMElement} The element to which we'll add the symbol definitions |
---|
483 | */ |
---|
484 | createDefs: function() { |
---|
485 | var defs = this.nodeFactory(this.container.id + "_defs", "defs"); |
---|
486 | this.rendererRoot.appendChild(defs); |
---|
487 | return defs; |
---|
488 | }, |
---|
489 | |
---|
490 | /************************************** |
---|
491 | * * |
---|
492 | * GEOMETRY DRAWING FUNCTIONS * |
---|
493 | * * |
---|
494 | **************************************/ |
---|
495 | |
---|
496 | /** |
---|
497 | * Method: drawPoint |
---|
498 | * This method is only called by the renderer itself. |
---|
499 | * |
---|
500 | * Parameters: |
---|
501 | * node - {DOMElement} |
---|
502 | * geometry - {<OpenLayers.Geometry>} |
---|
503 | * |
---|
504 | * Returns: |
---|
505 | * {DOMElement} or false if the renderer could not draw the point |
---|
506 | */ |
---|
507 | drawPoint: function(node, geometry) { |
---|
508 | return this.drawCircle(node, geometry, 1); |
---|
509 | }, |
---|
510 | |
---|
511 | /** |
---|
512 | * Method: drawCircle |
---|
513 | * This method is only called by the renderer itself. |
---|
514 | * |
---|
515 | * Parameters: |
---|
516 | * node - {DOMElement} |
---|
517 | * geometry - {<OpenLayers.Geometry>} |
---|
518 | * radius - {Float} |
---|
519 | * |
---|
520 | * Returns: |
---|
521 | * {DOMElement} or false if the renderer could not draw the circle |
---|
522 | */ |
---|
523 | drawCircle: function(node, geometry, radius) { |
---|
524 | var resolution = this.getResolution(); |
---|
525 | var x = (geometry.x / resolution + this.left); |
---|
526 | var y = (this.top - geometry.y / resolution); |
---|
527 | |
---|
528 | if (this.inValidRange(x, y)) { |
---|
529 | node.setAttributeNS(null, "cx", x); |
---|
530 | node.setAttributeNS(null, "cy", y); |
---|
531 | node.setAttributeNS(null, "r", radius); |
---|
532 | return node; |
---|
533 | } else { |
---|
534 | return false; |
---|
535 | } |
---|
536 | |
---|
537 | }, |
---|
538 | |
---|
539 | /** |
---|
540 | * Method: drawLineString |
---|
541 | * This method is only called by the renderer itself. |
---|
542 | * |
---|
543 | * Parameters: |
---|
544 | * node - {DOMElement} |
---|
545 | * geometry - {<OpenLayers.Geometry>} |
---|
546 | * |
---|
547 | * Returns: |
---|
548 | * {DOMElement} or null if the renderer could not draw all components of |
---|
549 | * the linestring, or false if nothing could be drawn |
---|
550 | */ |
---|
551 | drawLineString: function(node, geometry) { |
---|
552 | var componentsResult = this.getComponentsString(geometry.components); |
---|
553 | if (componentsResult.path) { |
---|
554 | node.setAttributeNS(null, "points", componentsResult.path); |
---|
555 | return (componentsResult.complete ? node : null); |
---|
556 | } else { |
---|
557 | return false; |
---|
558 | } |
---|
559 | }, |
---|
560 | |
---|
561 | /** |
---|
562 | * Method: drawLinearRing |
---|
563 | * This method is only called by the renderer itself. |
---|
564 | * |
---|
565 | * Parameters: |
---|
566 | * node - {DOMElement} |
---|
567 | * geometry - {<OpenLayers.Geometry>} |
---|
568 | * |
---|
569 | * Returns: |
---|
570 | * {DOMElement} or null if the renderer could not draw all components |
---|
571 | * of the linear ring, or false if nothing could be drawn |
---|
572 | */ |
---|
573 | drawLinearRing: function(node, geometry) { |
---|
574 | var componentsResult = this.getComponentsString(geometry.components); |
---|
575 | if (componentsResult.path) { |
---|
576 | node.setAttributeNS(null, "points", componentsResult.path); |
---|
577 | return (componentsResult.complete ? node : null); |
---|
578 | } else { |
---|
579 | return false; |
---|
580 | } |
---|
581 | }, |
---|
582 | |
---|
583 | /** |
---|
584 | * Method: drawPolygon |
---|
585 | * This method is only called by the renderer itself. |
---|
586 | * |
---|
587 | * Parameters: |
---|
588 | * node - {DOMElement} |
---|
589 | * geometry - {<OpenLayers.Geometry>} |
---|
590 | * |
---|
591 | * Returns: |
---|
592 | * {DOMElement} or null if the renderer could not draw all components |
---|
593 | * of the polygon, or false if nothing could be drawn |
---|
594 | */ |
---|
595 | drawPolygon: function(node, geometry) { |
---|
596 | var d = ""; |
---|
597 | var draw = true; |
---|
598 | var complete = true; |
---|
599 | var linearRingResult, path; |
---|
600 | for (var j=0, len=geometry.components.length; j<len; j++) { |
---|
601 | d += " M"; |
---|
602 | linearRingResult = this.getComponentsString( |
---|
603 | geometry.components[j].components, " "); |
---|
604 | path = linearRingResult.path; |
---|
605 | if (path) { |
---|
606 | d += " " + path; |
---|
607 | complete = linearRingResult.complete && complete; |
---|
608 | } else { |
---|
609 | draw = false; |
---|
610 | } |
---|
611 | } |
---|
612 | d += " z"; |
---|
613 | if (draw) { |
---|
614 | node.setAttributeNS(null, "d", d); |
---|
615 | node.setAttributeNS(null, "fill-rule", "evenodd"); |
---|
616 | return complete ? node : null; |
---|
617 | } else { |
---|
618 | return false; |
---|
619 | } |
---|
620 | }, |
---|
621 | |
---|
622 | /** |
---|
623 | * Method: drawRectangle |
---|
624 | * This method is only called by the renderer itself. |
---|
625 | * |
---|
626 | * Parameters: |
---|
627 | * node - {DOMElement} |
---|
628 | * geometry - {<OpenLayers.Geometry>} |
---|
629 | * |
---|
630 | * Returns: |
---|
631 | * {DOMElement} or false if the renderer could not draw the rectangle |
---|
632 | */ |
---|
633 | drawRectangle: function(node, geometry) { |
---|
634 | var resolution = this.getResolution(); |
---|
635 | var x = (geometry.x / resolution + this.left); |
---|
636 | var y = (this.top - geometry.y / resolution); |
---|
637 | |
---|
638 | if (this.inValidRange(x, y)) { |
---|
639 | node.setAttributeNS(null, "x", x); |
---|
640 | node.setAttributeNS(null, "y", y); |
---|
641 | node.setAttributeNS(null, "width", geometry.width / resolution); |
---|
642 | node.setAttributeNS(null, "height", geometry.height / resolution); |
---|
643 | return node; |
---|
644 | } else { |
---|
645 | return false; |
---|
646 | } |
---|
647 | }, |
---|
648 | |
---|
649 | /** |
---|
650 | * Method: drawSurface |
---|
651 | * This method is only called by the renderer itself. |
---|
652 | * |
---|
653 | * Parameters: |
---|
654 | * node - {DOMElement} |
---|
655 | * geometry - {<OpenLayers.Geometry>} |
---|
656 | * |
---|
657 | * Returns: |
---|
658 | * {DOMElement} or false if the renderer could not draw the surface |
---|
659 | */ |
---|
660 | drawSurface: function(node, geometry) { |
---|
661 | |
---|
662 | // create the svg path string representation |
---|
663 | var d = null; |
---|
664 | var draw = true; |
---|
665 | for (var i=0, len=geometry.components.length; i<len; i++) { |
---|
666 | if ((i%3) == 0 && (i/3) == 0) { |
---|
667 | var component = this.getShortString(geometry.components[i]); |
---|
668 | if (!component) { draw = false; } |
---|
669 | d = "M " + component; |
---|
670 | } else if ((i%3) == 1) { |
---|
671 | var component = this.getShortString(geometry.components[i]); |
---|
672 | if (!component) { draw = false; } |
---|
673 | d += " C " + component; |
---|
674 | } else { |
---|
675 | var component = this.getShortString(geometry.components[i]); |
---|
676 | if (!component) { draw = false; } |
---|
677 | d += " " + component; |
---|
678 | } |
---|
679 | } |
---|
680 | d += " Z"; |
---|
681 | if (draw) { |
---|
682 | node.setAttributeNS(null, "d", d); |
---|
683 | return node; |
---|
684 | } else { |
---|
685 | return false; |
---|
686 | } |
---|
687 | }, |
---|
688 | |
---|
689 | /** |
---|
690 | * Method: drawText |
---|
691 | * This method is only called by the renderer itself. |
---|
692 | * |
---|
693 | * Parameters: |
---|
694 | * featureId - {String} |
---|
695 | * style - |
---|
696 | * location - {<OpenLayers.Geometry.Point>} |
---|
697 | */ |
---|
698 | drawText: function(featureId, style, location) { |
---|
699 | var resolution = this.getResolution(); |
---|
700 | |
---|
701 | var x = (location.x / resolution + this.left); |
---|
702 | var y = (location.y / resolution - this.top); |
---|
703 | |
---|
704 | var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "text"); |
---|
705 | var tspan = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan", "tspan"); |
---|
706 | |
---|
707 | label.setAttributeNS(null, "x", x); |
---|
708 | label.setAttributeNS(null, "y", -y); |
---|
709 | |
---|
710 | if (style.fontColor) { |
---|
711 | label.setAttributeNS(null, "fill", style.fontColor); |
---|
712 | } |
---|
713 | if (style.fontOpacity) { |
---|
714 | label.setAttributeNS(null, "opacity", style.fontOpacity); |
---|
715 | } |
---|
716 | if (style.fontFamily) { |
---|
717 | label.setAttributeNS(null, "font-family", style.fontFamily); |
---|
718 | } |
---|
719 | if (style.fontSize) { |
---|
720 | label.setAttributeNS(null, "font-size", style.fontSize); |
---|
721 | } |
---|
722 | if (style.fontWeight) { |
---|
723 | label.setAttributeNS(null, "font-weight", style.fontWeight); |
---|
724 | } |
---|
725 | if(style.labelSelect === true) { |
---|
726 | label.setAttributeNS(null, "pointer-events", "visible"); |
---|
727 | label._featureId = featureId; |
---|
728 | tspan._featureId = featureId; |
---|
729 | tspan._geometry = location; |
---|
730 | tspan._geometryClass = location.CLASS_NAME; |
---|
731 | } else { |
---|
732 | label.setAttributeNS(null, "pointer-events", "none"); |
---|
733 | } |
---|
734 | var align = style.labelAlign || "cm"; |
---|
735 | label.setAttributeNS(null, "text-anchor", |
---|
736 | OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle"); |
---|
737 | |
---|
738 | if (this.isGecko) { |
---|
739 | label.setAttributeNS(null, "dominant-baseline", |
---|
740 | OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central"); |
---|
741 | } else { |
---|
742 | tspan.setAttributeNS(null, "baseline-shift", |
---|
743 | OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%"); |
---|
744 | } |
---|
745 | |
---|
746 | tspan.textContent = style.label; |
---|
747 | |
---|
748 | if(!label.parentNode) { |
---|
749 | label.appendChild(tspan); |
---|
750 | this.textRoot.appendChild(label); |
---|
751 | } |
---|
752 | }, |
---|
753 | |
---|
754 | /** |
---|
755 | * Method: getComponentString |
---|
756 | * |
---|
757 | * Parameters: |
---|
758 | * components - {Array(<OpenLayers.Geometry.Point>)} Array of points |
---|
759 | * separator - {String} character between coordinate pairs. Defaults to "," |
---|
760 | * |
---|
761 | * Returns: |
---|
762 | * {Object} hash with properties "path" (the string created from the |
---|
763 | * components and "complete" (false if the renderer was unable to |
---|
764 | * draw all components) |
---|
765 | */ |
---|
766 | getComponentsString: function(components, separator) { |
---|
767 | var renderCmp = []; |
---|
768 | var complete = true; |
---|
769 | var len = components.length; |
---|
770 | var strings = []; |
---|
771 | var str, component; |
---|
772 | for(var i=0; i<len; i++) { |
---|
773 | component = components[i]; |
---|
774 | renderCmp.push(component); |
---|
775 | str = this.getShortString(component); |
---|
776 | if (str) { |
---|
777 | strings.push(str); |
---|
778 | } else { |
---|
779 | // The current component is outside the valid range. Let's |
---|
780 | // see if the previous or next component is inside the range. |
---|
781 | // If so, add the coordinate of the intersection with the |
---|
782 | // valid range bounds. |
---|
783 | if (i > 0) { |
---|
784 | if (this.getShortString(components[i - 1])) { |
---|
785 | strings.push(this.clipLine(components[i], |
---|
786 | components[i-1])); |
---|
787 | } |
---|
788 | } |
---|
789 | if (i < len - 1) { |
---|
790 | if (this.getShortString(components[i + 1])) { |
---|
791 | strings.push(this.clipLine(components[i], |
---|
792 | components[i+1])); |
---|
793 | } |
---|
794 | } |
---|
795 | complete = false; |
---|
796 | } |
---|
797 | } |
---|
798 | |
---|
799 | return { |
---|
800 | path: strings.join(separator || ","), |
---|
801 | complete: complete |
---|
802 | }; |
---|
803 | }, |
---|
804 | |
---|
805 | /** |
---|
806 | * Method: clipLine |
---|
807 | * Given two points (one inside the valid range, and one outside), |
---|
808 | * clips the line betweeen the two points so that the new points are both |
---|
809 | * inside the valid range. |
---|
810 | * |
---|
811 | * Parameters: |
---|
812 | * badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the |
---|
813 | * invalid point |
---|
814 | * goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the |
---|
815 | * valid point |
---|
816 | * Returns |
---|
817 | * {String} the SVG coordinate pair of the clipped point (like |
---|
818 | * getShortString), or an empty string if both passed componets are at |
---|
819 | * the same point. |
---|
820 | */ |
---|
821 | clipLine: function(badComponent, goodComponent) { |
---|
822 | if (goodComponent.equals(badComponent)) { |
---|
823 | return ""; |
---|
824 | } |
---|
825 | var resolution = this.getResolution(); |
---|
826 | var maxX = this.MAX_PIXEL - this.translationParameters.x; |
---|
827 | var maxY = this.MAX_PIXEL - this.translationParameters.y; |
---|
828 | var x1 = goodComponent.x / resolution + this.left; |
---|
829 | var y1 = this.top - goodComponent.y / resolution; |
---|
830 | var x2 = badComponent.x / resolution + this.left; |
---|
831 | var y2 = this.top - badComponent.y / resolution; |
---|
832 | var k; |
---|
833 | if (x2 < -maxX || x2 > maxX) { |
---|
834 | k = (y2 - y1) / (x2 - x1); |
---|
835 | x2 = x2 < 0 ? -maxX : maxX; |
---|
836 | y2 = y1 + (x2 - x1) * k; |
---|
837 | } |
---|
838 | if (y2 < -maxY || y2 > maxY) { |
---|
839 | k = (x2 - x1) / (y2 - y1); |
---|
840 | y2 = y2 < 0 ? -maxY : maxY; |
---|
841 | x2 = x1 + (y2 - y1) * k; |
---|
842 | } |
---|
843 | return x2 + "," + y2; |
---|
844 | }, |
---|
845 | |
---|
846 | /** |
---|
847 | * Method: getShortString |
---|
848 | * |
---|
849 | * Parameters: |
---|
850 | * point - {<OpenLayers.Geometry.Point>} |
---|
851 | * |
---|
852 | * Returns: |
---|
853 | * {String} or false if point is outside the valid range |
---|
854 | */ |
---|
855 | getShortString: function(point) { |
---|
856 | var resolution = this.getResolution(); |
---|
857 | var x = (point.x / resolution + this.left); |
---|
858 | var y = (this.top - point.y / resolution); |
---|
859 | |
---|
860 | if (this.inValidRange(x, y)) { |
---|
861 | return x + "," + y; |
---|
862 | } else { |
---|
863 | return false; |
---|
864 | } |
---|
865 | }, |
---|
866 | |
---|
867 | /** |
---|
868 | * Method: getPosition |
---|
869 | * Finds the position of an svg node. |
---|
870 | * |
---|
871 | * Parameters: |
---|
872 | * node - {DOMElement} |
---|
873 | * |
---|
874 | * Returns: |
---|
875 | * {Object} hash with x and y properties, representing the coordinates |
---|
876 | * within the svg coordinate system |
---|
877 | */ |
---|
878 | getPosition: function(node) { |
---|
879 | return({ |
---|
880 | x: parseFloat(node.getAttributeNS(null, "cx")), |
---|
881 | y: parseFloat(node.getAttributeNS(null, "cy")) |
---|
882 | }); |
---|
883 | }, |
---|
884 | |
---|
885 | /** |
---|
886 | * Method: importSymbol |
---|
887 | * add a new symbol definition from the rendererer's symbol hash |
---|
888 | * |
---|
889 | * Parameters: |
---|
890 | * graphicName - {String} name of the symbol to import |
---|
891 | * |
---|
892 | * Returns: |
---|
893 | * {String} - id of the imported symbol |
---|
894 | */ |
---|
895 | importSymbol: function (graphicName) { |
---|
896 | if (!this.defs) { |
---|
897 | // create svg defs tag |
---|
898 | this.defs = this.createDefs(); |
---|
899 | } |
---|
900 | var id = this.container.id + "-" + graphicName; |
---|
901 | |
---|
902 | // check if symbol already exists in the defs |
---|
903 | if (document.getElementById(id) != null) { |
---|
904 | return id; |
---|
905 | } |
---|
906 | |
---|
907 | var symbol = OpenLayers.Renderer.symbol[graphicName]; |
---|
908 | if (!symbol) { |
---|
909 | throw new Error(graphicName + ' is not a valid symbol name'); |
---|
910 | } |
---|
911 | |
---|
912 | var symbolNode = this.nodeFactory(id, "symbol"); |
---|
913 | var node = this.nodeFactory(null, "polygon"); |
---|
914 | symbolNode.appendChild(node); |
---|
915 | var symbolExtent = new OpenLayers.Bounds( |
---|
916 | Number.MAX_VALUE, Number.MAX_VALUE, 0, 0); |
---|
917 | |
---|
918 | var points = []; |
---|
919 | var x,y; |
---|
920 | for (var i=0; i<symbol.length; i=i+2) { |
---|
921 | x = symbol[i]; |
---|
922 | y = symbol[i+1]; |
---|
923 | symbolExtent.left = Math.min(symbolExtent.left, x); |
---|
924 | symbolExtent.bottom = Math.min(symbolExtent.bottom, y); |
---|
925 | symbolExtent.right = Math.max(symbolExtent.right, x); |
---|
926 | symbolExtent.top = Math.max(symbolExtent.top, y); |
---|
927 | points.push(x, ",", y); |
---|
928 | } |
---|
929 | |
---|
930 | node.setAttributeNS(null, "points", points.join(" ")); |
---|
931 | |
---|
932 | var width = symbolExtent.getWidth(); |
---|
933 | var height = symbolExtent.getHeight(); |
---|
934 | // create a viewBox three times as large as the symbol itself, |
---|
935 | // to allow for strokeWidth being displayed correctly at the corners. |
---|
936 | var viewBox = [symbolExtent.left - width, |
---|
937 | symbolExtent.bottom - height, width * 3, height * 3]; |
---|
938 | symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" ")); |
---|
939 | this.symbolMetrics[id] = [ |
---|
940 | Math.max(width, height), |
---|
941 | symbolExtent.getCenterLonLat().lon, |
---|
942 | symbolExtent.getCenterLonLat().lat |
---|
943 | ]; |
---|
944 | |
---|
945 | this.defs.appendChild(symbolNode); |
---|
946 | return symbolNode.id; |
---|
947 | }, |
---|
948 | |
---|
949 | /** |
---|
950 | * Method: getFeatureIdFromEvent |
---|
951 | * |
---|
952 | * Parameters: |
---|
953 | * evt - {Object} An <OpenLayers.Event> object |
---|
954 | * |
---|
955 | * Returns: |
---|
956 | * {<OpenLayers.Geometry>} A geometry from an event that |
---|
957 | * happened on a layer. |
---|
958 | */ |
---|
959 | getFeatureIdFromEvent: function(evt) { |
---|
960 | var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments); |
---|
961 | if(this.supportUse === false && !featureId) { |
---|
962 | var target = evt.target; |
---|
963 | featureId = target.parentNode && target != this.rendererRoot && |
---|
964 | target.parentNode._featureId; |
---|
965 | } |
---|
966 | return featureId; |
---|
967 | }, |
---|
968 | |
---|
969 | CLASS_NAME: "OpenLayers.Renderer.SVG" |
---|
970 | }); |
---|
971 | |
---|
972 | /** |
---|
973 | * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN |
---|
974 | * {Object} |
---|
975 | */ |
---|
976 | OpenLayers.Renderer.SVG.LABEL_ALIGN = { |
---|
977 | "l": "start", |
---|
978 | "r": "end", |
---|
979 | "b": "bottom", |
---|
980 | "t": "hanging" |
---|
981 | }; |
---|
982 | |
---|
983 | /** |
---|
984 | * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT |
---|
985 | * {Object} |
---|
986 | */ |
---|
987 | OpenLayers.Renderer.SVG.LABEL_VSHIFT = { |
---|
988 | // according to |
---|
989 | // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html |
---|
990 | // a baseline-shift of -70% shifts the text exactly from the |
---|
991 | // bottom to the top of the baseline, so -35% moves the text to |
---|
992 | // the center of the baseline. |
---|
993 | "t": "-70%", |
---|
994 | "b": "0" |
---|
995 | }; |
---|