~mortenoh/+junk/dhis2-detailed-import-export

« back to all changes in this revision

Viewing changes to gis/dhis-gis-geostat/mfbase/openlayers/lib/OpenLayers/Renderer/SVG.js

  • Committer: larshelge at gmail
  • Date: 2009-03-03 16:46:36 UTC
  • Revision ID: larshelge@gmail.com-20090303164636-2sjlrquo7ib1gf7r
Initial check-in

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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. */
 
4
 
 
5
/**
 
6
 * @requires OpenLayers/Renderer/Elements.js
 
7
 */
 
8
 
 
9
/**
 
10
 * Class: OpenLayers.Renderer.SVG
 
11
 * 
 
12
 * Inherits:
 
13
 *  - <OpenLayers.Renderer.Elements>
 
14
 */
 
15
OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
 
16
 
 
17
    /** 
 
18
     * Property: xmlns
 
19
     * {String}
 
20
     */
 
21
    xmlns: "http://www.w3.org/2000/svg",
 
22
    
 
23
    /**
 
24
     * Property: xlinkns
 
25
     * {String}
 
26
     */
 
27
    xlinkns: "http://www.w3.org/1999/xlink",
 
28
 
 
29
    /**
 
30
     * Constant: MAX_PIXEL
 
31
     * {Integer} Firefox has a limitation where values larger or smaller than  
 
32
     *           about 15000 in an SVG document lock the browser up. This 
 
33
     *           works around it.
 
34
     */
 
35
    MAX_PIXEL: 15000,
 
36
 
 
37
    /**
 
38
     * Property: translationParameters
 
39
     * {Object} Hash with "x" and "y" properties
 
40
     */
 
41
    translationParameters: null,
 
42
    
 
43
    /**
 
44
     * Property: symbolSize
 
45
     * {Object} Cache for symbol sizes according to their svg coordinate space
 
46
     */
 
47
    symbolSize: {},
 
48
 
 
49
    /**
 
50
     * Constructor: OpenLayers.Renderer.SVG
 
51
     * 
 
52
     * Parameters:
 
53
     * containerID - {String}
 
54
     */
 
55
    initialize: function(containerID) {
 
56
        if (!this.supported()) { 
 
57
            return; 
 
58
        }
 
59
        OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
 
60
                                                                arguments);
 
61
        this.translationParameters = {x: 0, y: 0};
 
62
    },
 
63
 
 
64
    /**
 
65
     * APIMethod: destroy
 
66
     */
 
67
    destroy: function() {
 
68
        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
 
69
    },
 
70
    
 
71
    /**
 
72
     * APIMethod: supported
 
73
     * 
 
74
     * Returns:
 
75
     * {Boolean} Whether or not the browser supports the SVG renderer
 
76
     */
 
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") ));
 
83
    },    
 
84
 
 
85
    /**
 
86
     * Method: inValidRange
 
87
     * See #669 for more information
 
88
     *
 
89
     * Parameters:
 
90
     * x      - {Integer}
 
91
     * y      - {Integer}
 
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.
 
94
     * 
 
95
     * Returns:
 
96
     * {Boolean} Whether or not the 'x' and 'y' coordinates are in the  
 
97
     *           valid range.
 
98
     */ 
 
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);
 
104
    },
 
105
 
 
106
    /**
 
107
     * Method: setExtent
 
108
     * 
 
109
     * Parameters:
 
110
     * extent - {<OpenLayers.Bounds>}
 
111
     * resolutionChanged - {Boolean}
 
112
     * 
 
113
     * Returns:
 
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.
 
116
     *     False otherwise.
 
117
     */
 
118
    setExtent: function(extent, resolutionChanged) {
 
119
        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
 
120
                                                               arguments);
 
121
        
 
122
        var resolution = this.getResolution();
 
123
        var left = -extent.left / resolution;
 
124
        var top = extent.top / resolution;
 
125
 
 
126
        // If the resolution has changed, start over changing the corner, because
 
127
        // the features will redraw.
 
128
        if (resolutionChanged) {
 
129
            this.left = left;
 
130
            this.top = top;
 
131
            // Set the viewbox
 
132
            var extentString = "0 0 " + this.size.w + " " + this.size.h;
 
133
 
 
134
            this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
 
135
            this.translate(0, 0);
 
136
            return true;
 
137
        } else {
 
138
            var inRange = this.translate(left - this.left, top - this.top);
 
139
            if (!inRange) {
 
140
                // recenter the coordinate system
 
141
                this.setExtent(extent, true);
 
142
            }
 
143
            return inRange;
 
144
        }
 
145
    },
 
146
    
 
147
    /**
 
148
     * Method: translate
 
149
     * Transforms the SVG coordinate system
 
150
     * 
 
151
     * Parameters:
 
152
     * x - {Float}
 
153
     * y - {Float}
 
154
     * 
 
155
     * Returns:
 
156
     * {Boolean} true if the translation parameters are in the valid coordinates
 
157
     *     range, false otherwise.
 
158
     */
 
159
    translate: function(x, y) {
 
160
        if (!this.inValidRange(x, y, true)) {
 
161
            return false;
 
162
        } else {
 
163
            var transformString = "";
 
164
            if (x || y) {
 
165
                transformString = "translate(" + x + "," + y + ")";
 
166
            }
 
167
            this.root.setAttributeNS(null, "transform", transformString);
 
168
            this.translationParameters = {x: x, y: y};
 
169
            return true;
 
170
        }
 
171
    },
 
172
 
 
173
    /**
 
174
     * Method: setSize
 
175
     * Sets the size of the drawing surface.
 
176
     * 
 
177
     * Parameters:
 
178
     * size - {<OpenLayers.Size>} The size of the drawing surface
 
179
     */
 
180
    setSize: function(size) {
 
181
        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
 
182
        
 
183
        this.rendererRoot.setAttributeNS(null, "width", this.size.w);
 
184
        this.rendererRoot.setAttributeNS(null, "height", this.size.h);
 
185
    },
 
186
 
 
187
    /** 
 
188
     * Method: getNodeType 
 
189
     * 
 
190
     * Parameters:
 
191
     * geometry - {<OpenLayers.Geometry>}
 
192
     * style - {Object}
 
193
     * 
 
194
     * Returns:
 
195
     * {String} The corresponding node type for the specified geometry
 
196
     */
 
197
    getNodeType: function(geometry, style) {
 
198
        var nodeType = null;
 
199
        switch (geometry.CLASS_NAME) {
 
200
            case "OpenLayers.Geometry.Point":
 
201
                if (style.externalGraphic) {
 
202
                    nodeType = "image";
 
203
                } else if (this.isComplexSymbol(style.graphicName)) {
 
204
                    nodeType = "use";
 
205
                } else {
 
206
                    nodeType = "circle";
 
207
                }
 
208
                break;
 
209
            case "OpenLayers.Geometry.Rectangle":
 
210
                nodeType = "rect";
 
211
                break;
 
212
            case "OpenLayers.Geometry.LineString":
 
213
                nodeType = "polyline";
 
214
                break;
 
215
            case "OpenLayers.Geometry.LinearRing":
 
216
                nodeType = "polygon";
 
217
                break;
 
218
            case "OpenLayers.Geometry.Polygon":
 
219
            case "OpenLayers.Geometry.Curve":
 
220
            case "OpenLayers.Geometry.Surface":
 
221
                nodeType = "path";
 
222
                break;
 
223
            default:
 
224
                break;
 
225
        }
 
226
        return nodeType;
 
227
    },
 
228
 
 
229
    /** 
 
230
     * Method: setStyle
 
231
     * Use to set all the style attributes to a SVG node.
 
232
     * 
 
233
     * Takes care to adjust stroke width and point radius to be
 
234
     * resolution-relative
 
235
     *
 
236
     * Parameters:
 
237
     * node - {SVGDomElement} An SVG element to decorate
 
238
     * style - {Object}
 
239
     * options - {Object} Currently supported options include 
 
240
     *                              'isFilled' {Boolean} and
 
241
     *                              'isStroked' {Boolean}
 
242
     */
 
243
    setStyle: function(node, style, options) {
 
244
        style = style  || node._style;
 
245
        options = options || node._options;
 
246
        var r = parseFloat(node.getAttributeNS(null, "r"));
 
247
        var widthFactor = 1;
 
248
        var pos;
 
249
        if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
 
250
            if (style.externalGraphic) {
 
251
                pos = this.getPosition(node);
 
252
                
 
253
                if (style.graphicWidth && style.graphicHeight) {
 
254
                  node.setAttributeNS(null, "preserveAspectRatio", "none");
 
255
                }
 
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);
 
264
 
 
265
                var opacity = style.graphicOpacity || style.fillOpacity;
 
266
                
 
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);
 
278
                var href = "#" + id;
 
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;
 
290
                }
 
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
 
296
                // workaround above)
 
297
                node.style.visibility = "";
 
298
            } else {
 
299
                node.setAttributeNS(null, "r", style.pointRadius);
 
300
            }
 
301
 
 
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);
 
306
            }
 
307
        }
 
308
        
 
309
        if (options.isFilled) {
 
310
            node.setAttributeNS(null, "fill", style.fillColor);
 
311
            node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
 
312
        } else {
 
313
            node.setAttributeNS(null, "fill", "none");
 
314
        }
 
315
 
 
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,
 
325
                widthFactor));
 
326
        } else {
 
327
            node.setAttributeNS(null, "stroke", "none");
 
328
        }
 
329
        
 
330
        if (style.pointerEvents) {
 
331
            node.setAttributeNS(null, "pointer-events", style.pointerEvents);
 
332
        }
 
333
        
 
334
        if (style.cursor != null) {
 
335
            node.setAttributeNS(null, "cursor", style.cursor);
 
336
        }
 
337
        return node;
 
338
    },
 
339
 
 
340
    /** 
 
341
     * Method: dashStyle
 
342
     * 
 
343
     * Parameters:
 
344
     * style - {Object}
 
345
     * widthFactor - {Number}
 
346
     * 
 
347
     * Returns:
 
348
     * {String} A SVG compliant 'stroke-dasharray' value
 
349
     */
 
350
    dashStyle: function(style, widthFactor) {
 
351
        var w = style.strokeWidth * widthFactor;
 
352
 
 
353
        switch (style.strokeDashstyle) {
 
354
            case 'solid':
 
355
                return 'none';
 
356
            case 'dot':
 
357
                return [1, 4 * w].join();
 
358
            case 'dash':
 
359
                return [4 * w, 4 * w].join();
 
360
            case 'dashdot':
 
361
                return [4 * w, 4 * w, 1, 4 * w].join();
 
362
            case 'longdash':
 
363
                return [8 * w, 4 * w].join();
 
364
            case 'longdashdot':
 
365
                return [8 * w, 4 * w, 1, 4 * w].join();
 
366
            default:
 
367
                return style.strokeDashstyle.replace(/ /g, ",");
 
368
        }
 
369
    },
 
370
    
 
371
    /** 
 
372
     * Method: createNode
 
373
     * 
 
374
     * Parameters:
 
375
     * type - {String} Kind of node to draw
 
376
     * id - {String} Id for node
 
377
     * 
 
378
     * Returns:
 
379
     * {DOMElement} A new node of the given type and id
 
380
     */
 
381
    createNode: function(type, id) {
 
382
        var node = document.createElementNS(this.xmlns, type);
 
383
        if (id) {
 
384
            node.setAttributeNS(null, "id", id);
 
385
        }
 
386
        return node;    
 
387
    },
 
388
    
 
389
    /** 
 
390
     * Method: nodeTypeCompare
 
391
     * 
 
392
     * Parameters:
 
393
     * node - {SVGDomElement} An SVG element
 
394
     * type - {String} Kind of node
 
395
     * 
 
396
     * Returns:
 
397
     * {Boolean} Whether or not the specified node is of the specified type
 
398
     */
 
399
    nodeTypeCompare: function(node, type) {
 
400
        return (type == node.nodeName);
 
401
    },
 
402
   
 
403
    /**
 
404
     * Method: createRenderRoot
 
405
     * 
 
406
     * Returns:
 
407
     * {DOMElement} The specific render engine's root element
 
408
     */
 
409
    createRenderRoot: function() {
 
410
        return this.nodeFactory(this.container.id + "_svgRoot", "svg");
 
411
    },
 
412
 
 
413
    /**
 
414
     * Method: createRoot
 
415
     * 
 
416
     * Returns:
 
417
     * {DOMElement} The main root element to which we'll add vectors
 
418
     */
 
419
    createRoot: function() {
 
420
        return this.nodeFactory(this.container.id + "_root", "g");
 
421
    },
 
422
 
 
423
    /**
 
424
     * Method: createDefs
 
425
     *
 
426
     * Returns:
 
427
     * {DOMElement} The element to which we'll add the symbol definitions
 
428
     */
 
429
    createDefs: function() {
 
430
        var defs = this.nodeFactory("ol-renderer-defs", "defs");
 
431
        this.rendererRoot.appendChild(defs);
 
432
        return defs;
 
433
    },
 
434
 
 
435
    /**************************************
 
436
     *                                    *
 
437
     *     GEOMETRY DRAWING FUNCTIONS     *
 
438
     *                                    *
 
439
     **************************************/
 
440
 
 
441
    /**
 
442
     * Method: drawPoint
 
443
     * This method is only called by the renderer itself.
 
444
     * 
 
445
     * Parameters: 
 
446
     * node - {DOMElement}
 
447
     * geometry - {<OpenLayers.Geometry>}
 
448
     * 
 
449
     * Returns:
 
450
     * {DOMElement} or false if the renderer could not draw the point
 
451
     */ 
 
452
    drawPoint: function(node, geometry) {
 
453
        return this.drawCircle(node, geometry, 1);
 
454
    },
 
455
 
 
456
    /**
 
457
     * Method: drawCircle
 
458
     * This method is only called by the renderer itself.
 
459
     * 
 
460
     * Parameters: 
 
461
     * node - {DOMElement}
 
462
     * geometry - {<OpenLayers.Geometry>}
 
463
     * radius - {Float}
 
464
     * 
 
465
     * Returns:
 
466
     * {DOMElement} or false if the renderer could not draw the circle
 
467
     */
 
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);
 
472
 
 
473
        if (this.inValidRange(x, y)) { 
 
474
            node.setAttributeNS(null, "cx", x);
 
475
            node.setAttributeNS(null, "cy", y);
 
476
            node.setAttributeNS(null, "r", radius);
 
477
            return node;
 
478
        } else {
 
479
            return false;
 
480
        }    
 
481
            
 
482
    },
 
483
    
 
484
    /**
 
485
     * Method: drawLineString
 
486
     * This method is only called by the renderer itself.
 
487
     * 
 
488
     * Parameters: 
 
489
     * node - {DOMElement}
 
490
     * geometry - {<OpenLayers.Geometry>}
 
491
     * 
 
492
     * Returns:
 
493
     * {DOMElement} or null if the renderer could not draw all components of
 
494
     *     the linestring, or false if nothing could be drawn
 
495
     */ 
 
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);  
 
501
        } else {
 
502
            return false;
 
503
        }
 
504
    },
 
505
    
 
506
    /**
 
507
     * Method: drawLinearRing
 
508
     * This method is only called by the renderer itself.
 
509
     * 
 
510
     * Parameters: 
 
511
     * node - {DOMElement}
 
512
     * geometry - {<OpenLayers.Geometry>}
 
513
     * 
 
514
     * Returns:
 
515
     * {DOMElement} or null if the renderer could not draw all components
 
516
     *     of the linear ring, or false if nothing could be drawn
 
517
     */ 
 
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);  
 
523
        } else {
 
524
            return false;
 
525
        }
 
526
    },
 
527
    
 
528
    /**
 
529
     * Method: drawPolygon
 
530
     * This method is only called by the renderer itself.
 
531
     * 
 
532
     * Parameters: 
 
533
     * node - {DOMElement}
 
534
     * geometry - {<OpenLayers.Geometry>}
 
535
     * 
 
536
     * Returns:
 
537
     * {DOMElement} or null if the renderer could not draw all components
 
538
     *     of the polygon, or false if nothing could be drawn
 
539
     */ 
 
540
    drawPolygon: function(node, geometry) {
 
541
        var d = "";
 
542
        var draw = true;
 
543
        var complete = true;
 
544
        var linearRingResult, path;
 
545
        for (var j=0, len=geometry.components.length; j<len; j++) {
 
546
            d += " M";
 
547
            linearRingResult = this.getComponentsString(
 
548
                geometry.components[j].components, " ");
 
549
            path = linearRingResult.path;
 
550
            if (path) {
 
551
                d += " " + path;
 
552
                complete = linearRingResult.complete && complete;
 
553
            } else {
 
554
                draw = false;
 
555
            }
 
556
        }
 
557
        d += " z";
 
558
        if (draw) {
 
559
            node.setAttributeNS(null, "d", d);
 
560
            node.setAttributeNS(null, "fill-rule", "evenodd");
 
561
            return complete ? node : null;
 
562
        } else {
 
563
            return false;
 
564
        }    
 
565
    },
 
566
    
 
567
    /**
 
568
     * Method: drawRectangle
 
569
     * This method is only called by the renderer itself.
 
570
     * 
 
571
     * Parameters: 
 
572
     * node - {DOMElement}
 
573
     * geometry - {<OpenLayers.Geometry>}
 
574
     * 
 
575
     * Returns:
 
576
     * {DOMElement} or false if the renderer could not draw the rectangle
 
577
     */ 
 
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);
 
582
 
 
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);
 
588
            return node;
 
589
        } else {
 
590
            return false;
 
591
        }
 
592
    },
 
593
    
 
594
    /**
 
595
     * Method: drawSurface
 
596
     * This method is only called by the renderer itself.
 
597
     * 
 
598
     * Parameters: 
 
599
     * node - {DOMElement}
 
600
     * geometry - {<OpenLayers.Geometry>}
 
601
     * 
 
602
     * Returns:
 
603
     * {DOMElement} or false if the renderer could not draw the surface
 
604
     */ 
 
605
    drawSurface: function(node, geometry) {
 
606
 
 
607
        // create the svg path string representation
 
608
        var d = null;
 
609
        var draw = true;
 
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;
 
619
            } else {
 
620
                var component = this.getShortString(geometry.components[i]);
 
621
                if (!component) { draw = false; }
 
622
                d += " " + component;
 
623
            }
 
624
        }
 
625
        d += " Z";
 
626
        if (draw) {
 
627
            node.setAttributeNS(null, "d", d);
 
628
            return node;
 
629
        } else {
 
630
            return false;
 
631
        }    
 
632
    },
 
633
 
 
634
    /** 
 
635
     * Method: getComponentString
 
636
     * 
 
637
     * Parameters:
 
638
     * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
 
639
     * separator - {String} character between coordinate pairs. Defaults to ","
 
640
     * 
 
641
     * Returns:
 
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)
 
645
     */
 
646
    getComponentsString: function(components, separator) {
 
647
        var renderCmp = [];
 
648
        var complete = true;
 
649
        var len = components.length;
 
650
        var strings = [];
 
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);
 
656
            if (str) {
 
657
                strings.push(str);
 
658
            } else {
 
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.
 
663
                if (i > 0) {
 
664
                    if (this.getShortString(components[i - 1])) {
 
665
                        strings.push(this.clipLine(components[i],
 
666
                            components[i-1]));
 
667
                    }
 
668
                }
 
669
                if (i < len - 1) {
 
670
                    if (this.getShortString(components[i + 1])) {
 
671
                        strings.push(this.clipLine(components[i],
 
672
                            components[i+1]));
 
673
                    }
 
674
                }
 
675
                complete = false;
 
676
            }
 
677
        }
 
678
 
 
679
        return {
 
680
            path: strings.join(separator || ","),
 
681
            complete: complete
 
682
        };
 
683
    },
 
684
    
 
685
    /**
 
686
     * Method: clipLine
 
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.
 
690
     * 
 
691
     * Parameters:
 
692
     * badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
 
693
     *     invalid point
 
694
     * goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
 
695
     *     valid point
 
696
     * Returns
 
697
     * {String} the SVG coordinate pair of the clipped point (like
 
698
     *     getShortString), or an empty string if both passed componets are at
 
699
     *     the same point.
 
700
     */
 
701
    clipLine: function(badComponent, goodComponent) {
 
702
        if (goodComponent.equals(badComponent)) {
 
703
            return "";
 
704
        }
 
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;
 
712
        var k;
 
713
        if (x2 < -maxX || x2 > maxX) {
 
714
            k = (y2 - y1) / (x2 - x1);
 
715
            x2 = x2 < 0 ? -maxX : maxX;
 
716
            y2 = y1 + (x2 - x1) * k;
 
717
        }
 
718
        if (y2 < -maxY || y2 > maxY) {
 
719
            k = (x2 - x1) / (y2 - y1);
 
720
            y2 = y2 < 0 ? -maxY : maxY;
 
721
            x2 = x1 + (y2 - y1) * k;
 
722
        }
 
723
        return x2 + "," + y2;
 
724
    },
 
725
 
 
726
    /** 
 
727
     * Method: getShortString
 
728
     * 
 
729
     * Parameters:
 
730
     * point - {<OpenLayers.Geometry.Point>}
 
731
     * 
 
732
     * Returns:
 
733
     * {String} or false if point is outside the valid range
 
734
     */
 
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);
 
739
 
 
740
        if (this.inValidRange(x, y)) { 
 
741
            return x + "," + y;
 
742
        } else {
 
743
            return false;
 
744
        }
 
745
    },
 
746
    
 
747
    /**
 
748
     * Method: getPosition
 
749
     * Finds the position of an svg node.
 
750
     * 
 
751
     * Parameters:
 
752
     * node - {DOMElement}
 
753
     * 
 
754
     * Returns:
 
755
     * {Object} hash with x and y properties, representing the coordinates
 
756
     *     within the svg coordinate system
 
757
     */
 
758
    getPosition: function(node) {
 
759
        return({
 
760
            x: parseFloat(node.getAttributeNS(null, "cx")),
 
761
            y: parseFloat(node.getAttributeNS(null, "cy"))
 
762
        });
 
763
    },
 
764
 
 
765
    /**
 
766
     * Method: importSymbol
 
767
     * add a new symbol definition from the rendererer's symbol hash
 
768
     * 
 
769
     * Parameters:
 
770
     * graphicName - {String} name of the symbol to import
 
771
     * 
 
772
     * Returns:
 
773
     * {String} - id of the imported symbol
 
774
     */      
 
775
    importSymbol: function (graphicName)  {
 
776
        if (!this.defs) {
 
777
            // create svg defs tag
 
778
            this.defs = this.createDefs();
 
779
        }
 
780
        var id = this.container.id + "-" + graphicName;
 
781
        
 
782
        // check if symbol already exists in the defs
 
783
        if (document.getElementById(id) != null) {
 
784
            return id;
 
785
        }
 
786
        
 
787
        var symbol = OpenLayers.Renderer.symbol[graphicName];
 
788
        if (!symbol) {
 
789
            throw new Error(graphicName + ' is not a valid symbol name');
 
790
            return;
 
791
        }
 
792
 
 
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);
 
798
 
 
799
        var points = "";
 
800
        var x,y;
 
801
        for (var i=0; i<symbol.length; i=i+2) {
 
802
            x = symbol[i];
 
803
            y = symbol[i+1];
 
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;
 
809
        }
 
810
        
 
811
        node.setAttributeNS(null, "points", points);
 
812
        
 
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;
 
821
        
 
822
        this.defs.appendChild(symbolNode);
 
823
        return symbolNode.id;
 
824
    },
 
825
 
 
826
    CLASS_NAME: "OpenLayers.Renderer.SVG"
 
827
});