Bienvenue sur PostGIS.fr

Bienvenue sur PostGIS.fr , le site de la communauté des utilisateurs francophones de PostGIS.

PostGIS ajoute le support d'objets géographique à la base de données PostgreSQL. En effet, PostGIS "spatialise" le serverur PostgreSQL, ce qui permet de l'utiliser comme une base de données SIG.

Maintenu à jour, en fonction de nos disponibilités et des diverses sorties des outils que nous testons, nous vous proposons l'ensemble de nos travaux publiés en langue française.

source: trunk/workshop-routing-foss4g/web/OpenLayers/lib/OpenLayers/Format/KML.js @ 76

Revision 76, 50.8 KB checked in by djay, 12 years ago (diff)

Ajout du répertoire web

  • Property svn:executable set to *
Line 
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/Format/XML.js
8 * @requires OpenLayers/Feature/Vector.js
9 * @requires OpenLayers/Geometry/Point.js
10 * @requires OpenLayers/Geometry/LineString.js
11 * @requires OpenLayers/Geometry/Polygon.js
12 * @requires OpenLayers/Geometry/Collection.js
13 * @requires OpenLayers/Request/XMLHttpRequest.js
14 * @requires OpenLayers/Console.js
15 * @requires OpenLayers/Projection.js
16 */
17
18/**
19 * Class: OpenLayers.Format.KML
20 * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
21 *     constructor.
22 *
23 * Inherits from:
24 *  - <OpenLayers.Format.XML>
25 */
26OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
27   
28    /**
29     * Property: namespaces
30     * {Object} Mapping of namespace aliases to namespace URIs.
31     */
32    namespaces: {
33        kml: "http://www.opengis.net/kml/2.2",
34        gx: "http://www.google.com/kml/ext/2.2"
35    },
36
37    /**
38     * APIProperty: kmlns
39     * {String} KML Namespace to use. Defaults to 2.0 namespace.
40     */
41    kmlns: "http://earth.google.com/kml/2.0",
42   
43    /**
44     * APIProperty: placemarksDesc
45     * {String} Name of the placemarks.  Default is "No description available".
46     */
47    placemarksDesc: "No description available",
48   
49    /**
50     * APIProperty: foldersName
51     * {String} Name of the folders.  Default is "OpenLayers export".
52     *          If set to null, no name element will be created.
53     */
54    foldersName: "OpenLayers export",
55   
56    /**
57     * APIProperty: foldersDesc
58     * {String} Description of the folders. Default is "Exported on [date]."
59     *          If set to null, no description element will be created.
60     */
61    foldersDesc: "Exported on " + new Date(),
62   
63    /**
64     * APIProperty: extractAttributes
65     * {Boolean} Extract attributes from KML.  Default is true.
66     *           Extracting styleUrls requires this to be set to true
67     */
68    extractAttributes: true,
69   
70    /**
71     * Property: extractStyles
72     * {Boolean} Extract styles from KML.  Default is false.
73     *           Extracting styleUrls also requires extractAttributes to be
74     *           set to true
75     */
76    extractStyles: false,
77   
78    /**
79     * APIProperty: extractTracks
80     * {Boolean} Extract gx:Track elements from Placemark elements.  Default
81     *     is false.  If true, features will be generated for all points in
82     *     all gx:Track elements.  Features will have a when (Date) attribute
83     *     based on when elements in the track.  If tracks include angle
84     *     elements, features will have heading, tilt, and roll attributes.
85     *     If track point coordinates have three values, features will have
86     *     an altitude attribute with the third coordinate value.
87     */
88    extractTracks: false,
89   
90    /**
91     * APIProperty: trackAttributes
92     * {Array} If <extractTracks> is true, points within gx:Track elements will
93     *     be parsed as features with when, heading, tilt, and roll attributes.
94     *     Any additional attribute names can be provided in <trackAttributes>.
95     */
96    trackAttributes: null,
97   
98    /**
99     * Property: internalns
100     * {String} KML Namespace to use -- defaults to the namespace of the
101     *     Placemark node being parsed, but falls back to kmlns.
102     */
103    internalns: null,
104
105    /**
106     * Property: features
107     * {Array} Array of features
108     *     
109     */
110    features: null,
111
112    /**
113     * Property: styles
114     * {Object} Storage of style objects
115     *     
116     */
117    styles: null,
118   
119    /**
120     * Property: styleBaseUrl
121     * {String}
122     */
123    styleBaseUrl: "",
124
125    /**
126     * Property: fetched
127     * {Object} Storage of KML URLs that have been fetched before
128     *     in order to prevent reloading them.
129     */
130    fetched: null,
131
132    /**
133     * APIProperty: maxDepth
134     * {Integer} Maximum depth for recursive loading external KML URLs
135     *           Defaults to 0: do no external fetching
136     */
137    maxDepth: 0,
138
139    /**
140     * Constructor: OpenLayers.Format.KML
141     * Create a new parser for KML.
142     *
143     * Parameters:
144     * options - {Object} An optional object whose properties will be set on
145     *     this instance.
146     */
147    initialize: function(options) {
148        // compile regular expressions once instead of every time they are used
149        this.regExes = {
150            trimSpace: (/^\s*|\s*$/g),
151            removeSpace: (/\s*/g),
152            splitSpace: (/\s+/),
153            trimComma: (/\s*,\s*/g),
154            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
155            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
156            straightBracket: (/\$\[(.*?)\]/g)
157        };
158        // KML coordinates are always in longlat WGS84
159        this.externalProjection = new OpenLayers.Projection("EPSG:4326");
160
161        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
162    },
163
164    /**
165     * APIMethod: read
166     * Read data from a string, and return a list of features.
167     *
168     * Parameters:
169     * data    - {String} or {DOMElement} data to read/parse.
170     *
171     * Returns:
172     * {Array(<OpenLayers.Feature.Vector>)} List of features.
173     */
174    read: function(data) {
175        this.features = [];
176        this.styles   = {};
177        this.fetched  = {};
178
179        // Set default options
180        var options = {
181            depth: 0,
182            styleBaseUrl: this.styleBaseUrl
183        };
184
185        return this.parseData(data, options);
186    },
187
188    /**
189     * Method: parseData
190     * Read data from a string, and return a list of features.
191     *
192     * Parameters:
193     * data    - {String} or {DOMElement} data to read/parse.
194     * options - {Object} Hash of options
195     *
196     * Returns:
197     * {Array(<OpenLayers.Feature.Vector>)} List of features.
198     */
199    parseData: function(data, options) {
200        if(typeof data == "string") {
201            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
202        }
203
204        // Loop throught the following node types in this order and
205        // process the nodes found
206        var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
207        for(var i=0, len=types.length; i<len; ++i) {
208            var type = types[i];
209
210            var nodes = this.getElementsByTagNameNS(data, "*", type);
211
212            // skip to next type if no nodes are found
213            if(nodes.length == 0) { 
214                continue;
215            }
216
217            switch (type.toLowerCase()) {
218
219                // Fetch external links
220                case "link":
221                case "networklink":
222                    this.parseLinks(nodes, options);
223                    break;
224
225                // parse style information
226                case "style":
227                    if (this.extractStyles) {
228                        this.parseStyles(nodes, options);
229                    }
230                    break;
231                case "stylemap":
232                    if (this.extractStyles) {
233                        this.parseStyleMaps(nodes, options);
234                    }
235                    break;
236
237                // parse features
238                case "placemark":
239                    this.parseFeatures(nodes, options);
240                    break;
241            }
242        }
243       
244        return this.features;
245    },
246
247    /**
248     * Method: parseLinks
249     * Finds URLs of linked KML documents and fetches them
250     *
251     * Parameters:
252     * nodes   - {Array} of {DOMElement} data to read/parse.
253     * options - {Object} Hash of options
254     *
255     */
256    parseLinks: function(nodes, options) {
257       
258        // Fetch external links <NetworkLink> and <Link>
259        // Don't do anything if we have reached our maximum depth for recursion
260        if (options.depth >= this.maxDepth) {
261            return false;
262        }
263
264        // increase depth
265        var newOptions = OpenLayers.Util.extend({}, options);
266        newOptions.depth++;
267
268        for(var i=0, len=nodes.length; i<len; i++) {
269            var href = this.parseProperty(nodes[i], "*", "href");
270            if(href && !this.fetched[href]) {
271                this.fetched[href] = true; // prevent reloading the same urls
272                var data = this.fetchLink(href);
273                if (data) {
274                    this.parseData(data, newOptions);
275                }
276            } 
277        }
278
279    },
280
281    /**
282     * Method: fetchLink
283     * Fetches a URL and returns the result
284     *
285     * Parameters:
286     * href  - {String} url to be fetched
287     *
288     */
289    fetchLink: function(href) {
290        var request = OpenLayers.Request.GET({url: href, async: false});
291        if (request) {
292            return request.responseText;
293        }
294    },
295
296    /**
297     * Method: parseStyles
298     * Looks for <Style> nodes in the data and parses them
299     * Also parses <StyleMap> nodes, but only uses the 'normal' key
300     *
301     * Parameters:
302     * nodes    - {Array} of {DOMElement} data to read/parse.
303     * options  - {Object} Hash of options
304     *
305     */
306    parseStyles: function(nodes, options) {
307        for(var i=0, len=nodes.length; i<len; i++) {
308            var style = this.parseStyle(nodes[i]);
309            if(style) {
310                var styleName = (options.styleBaseUrl || "") + "#" + style.id;
311               
312                this.styles[styleName] = style;
313            }
314        }
315    },
316
317    /**
318     * Method: parseKmlColor
319     * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
320     * color and opacity or null if the color is invalid.
321     *
322     * Parameters:
323     * kmlColor - {String} a kml formated color
324     *
325     * Returns:
326     * {Object}
327     */
328    parseKmlColor: function(kmlColor) {
329        var color = null;
330        if (kmlColor) {
331            var matches = kmlColor.match(this.regExes.kmlColor);
332            if (matches) {
333                color = {
334                    color: '#' + matches[4] + matches[3] + matches[2],
335                    opacity: parseInt(matches[1], 16) / 255
336                };
337            }
338        }
339        return color;
340    },
341
342    /**
343     * Method: parseStyle
344     * Parses the children of a <Style> node and builds the style hash
345     * accordingly
346     *
347     * Parameters:
348     * node - {DOMElement} <Style> node
349     *
350     */
351    parseStyle: function(node) {
352        var style = {};
353       
354        var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle", 
355                     "LabelStyle"];
356        var type, nodeList, geometry, parser;
357        for(var i=0, len=types.length; i<len; ++i) {
358            type = types[i];
359            styleTypeNode = this.getElementsByTagNameNS(node, 
360                                                   "*", type)[0];
361            if(!styleTypeNode) { 
362                continue;
363            }
364
365            // only deal with first geometry of this type
366            switch (type.toLowerCase()) {
367                case "linestyle":
368                    var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
369                    var color = this.parseKmlColor(kmlColor);
370                    if (color) {
371                        style["strokeColor"] = color.color;
372                        style["strokeOpacity"] = color.opacity;
373                    }
374                   
375                    var width = this.parseProperty(styleTypeNode, "*", "width");
376                    if (width) {
377                        style["strokeWidth"] = width;
378                    }
379                    break;
380
381                case "polystyle":
382                    var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
383                    var color = this.parseKmlColor(kmlColor);
384                    if (color) {
385                        style["fillOpacity"] = color.opacity;
386                        style["fillColor"] = color.color;
387                    }
388                    // Check if fill is disabled
389                    var fill = this.parseProperty(styleTypeNode, "*", "fill");
390                    if (fill == "0") {
391                        style["fillColor"] = "none";
392                    }
393                    // Check if outline is disabled
394                    var outline = this.parseProperty(styleTypeNode, "*", "outline");
395                    if (outline == "0") {
396                        style["strokeWidth"] = "0";
397                    }
398                   
399                    break;
400
401                case "iconstyle":
402                    // set scale
403                    var scale = parseFloat(this.parseProperty(styleTypeNode, 
404                                                          "*", "scale") || 1);
405 
406                    // set default width and height of icon
407                    var width = 32 * scale;
408                    var height = 32 * scale;
409
410                    var iconNode = this.getElementsByTagNameNS(styleTypeNode, 
411                                               "*", 
412                                               "Icon")[0];
413                    if (iconNode) {
414                        var href = this.parseProperty(iconNode, "*", "href");
415                        if (href) {                                                   
416
417                            var w = this.parseProperty(iconNode, "*", "w");
418                            var h = this.parseProperty(iconNode, "*", "h");
419
420                            // Settings for Google specific icons that are 64x64
421                            // We set the width and height to 64 and halve the
422                            // scale to prevent icons from being too big
423                            var google = "http://maps.google.com/mapfiles/kml";
424                            if (OpenLayers.String.startsWith(
425                                                 href, google) && !w && !h) {
426                                w = 64;
427                                h = 64;
428                                scale = scale / 2;
429                            }
430                               
431                            // if only dimension is defined, make sure the
432                            // other one has the same value
433                            w = w || h;
434                            h = h || w;
435
436                            if (w) {
437                                width = parseInt(w) * scale;
438                            }
439
440                            if (h) {
441                                height = parseInt(h) * scale;
442                            }
443
444                            // support for internal icons
445                            //    (/root://icons/palette-x.png)
446                            // x and y tell the position on the palette:
447                            // - in pixels
448                            // - starting from the left bottom
449                            // We translate that to a position in the list
450                            // and request the appropriate icon from the
451                            // google maps website
452                            var matches = href.match(this.regExes.kmlIconPalette);
453                            if (matches)  {
454                                var palette = matches[1];
455                                var file_extension = matches[2];
456
457                                var x = this.parseProperty(iconNode, "*", "x");
458                                var y = this.parseProperty(iconNode, "*", "y");
459
460                                var posX = x ? x/32 : 0;
461                                var posY = y ? (7 - y/32) : 7;
462
463                                var pos = posY * 8 + posX;
464                                href = "http://maps.google.com/mapfiles/kml/pal" 
465                                     + palette + "/icon" + pos + file_extension;
466                            }
467
468                            style["graphicOpacity"] = 1; // fully opaque
469                            style["externalGraphic"] = href;
470                        }
471
472                    }
473
474
475                    // hotSpots define the offset for an Icon
476                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, 
477                                               "*", 
478                                               "hotSpot")[0];
479                    if (hotSpotNode) {
480                        var x = parseFloat(hotSpotNode.getAttribute("x"));
481                        var y = parseFloat(hotSpotNode.getAttribute("y"));
482
483                        var xUnits = hotSpotNode.getAttribute("xunits");
484                        if (xUnits == "pixels") {
485                            style["graphicXOffset"] = -x * scale;
486                        }
487                        else if (xUnits == "insetPixels") {
488                            style["graphicXOffset"] = -width + (x * scale);
489                        }
490                        else if (xUnits == "fraction") {
491                            style["graphicXOffset"] = -width * x;
492                        }
493
494                        var yUnits = hotSpotNode.getAttribute("yunits");
495                        if (yUnits == "pixels") {
496                            style["graphicYOffset"] = -height + (y * scale) + 1;
497                        }
498                        else if (yUnits == "insetPixels") {
499                            style["graphicYOffset"] = -(y * scale) + 1;
500                        }
501                        else if (yUnits == "fraction") {
502                            style["graphicYOffset"] =  -height * (1 - y) + 1;
503                        }
504                    }
505
506                    style["graphicWidth"] = width;
507                    style["graphicHeight"] = height;
508                    break;
509
510                case "balloonstyle":
511                    var balloonStyle = OpenLayers.Util.getXmlNodeValue(
512                                            styleTypeNode);
513                    if (balloonStyle) {
514                        style["balloonStyle"] = balloonStyle.replace(
515                                       this.regExes.straightBracket, "${$1}");
516                    }
517                    break;
518                case "labelstyle":
519                    var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
520                    var color = this.parseKmlColor(kmlColor);
521                    if (color) {
522                        style["fontColor"] = color.color;
523                        style["fontOpacity"] = color.opacity;
524                    }
525                    break;
526
527                default:
528            }
529        }
530
531        // Some polygons have no line color, so we use the fillColor for that
532        if (!style["strokeColor"] && style["fillColor"]) {
533            style["strokeColor"] = style["fillColor"];
534        }
535
536        var id = node.getAttribute("id");
537        if (id && style) {
538            style.id = id;
539        }
540
541        return style;
542    },
543
544    /**
545     * Method: parseStyleMaps
546     * Looks for <Style> nodes in the data and parses them
547     * Also parses <StyleMap> nodes, but only uses the 'normal' key
548     *
549     * Parameters:
550     * nodes    - {Array} of {DOMElement} data to read/parse.
551     * options  - {Object} Hash of options
552     *
553     */
554    parseStyleMaps: function(nodes, options) {
555        // Only the default or "normal" part of the StyleMap is processed now
556        // To do the select or "highlight" bit, we'd need to change lots more
557
558        for(var i=0, len=nodes.length; i<len; i++) {
559            var node = nodes[i];
560            var pairs = this.getElementsByTagNameNS(node, "*", 
561                            "Pair");
562
563            var id = node.getAttribute("id");
564            for (var j=0, jlen=pairs.length; j<jlen; j++) {
565                var pair = pairs[j];
566                // Use the shortcut in the SLD format to quickly retrieve the
567                // value of a node. Maybe it's good to have a method in
568                // Format.XML to do this
569                var key = this.parseProperty(pair, "*", "key");
570                var styleUrl = this.parseProperty(pair, "*", "styleUrl");
571
572                if (styleUrl && key == "normal") {
573                    this.styles[(options.styleBaseUrl || "") + "#" + id] =
574                        this.styles[(options.styleBaseUrl || "") + styleUrl];
575                }
576
577                if (styleUrl && key == "highlight") {
578                    // TODO: implement the "select" part
579                }
580
581            }
582        }
583
584    },
585
586
587    /**
588     * Method: parseFeatures
589     * Loop through all Placemark nodes and parse them.
590     * Will create a list of features
591     *
592     * Parameters:
593     * nodes    - {Array} of {DOMElement} data to read/parse.
594     * options  - {Object} Hash of options
595     *
596     */
597    parseFeatures: function(nodes, options) {
598        var features = [];
599        for(var i=0, len=nodes.length; i<len; i++) {
600            var featureNode = nodes[i];
601            var feature = this.parseFeature.apply(this,[featureNode]) ;
602            if(feature) {
603
604                // Create reference to styleUrl
605                if (this.extractStyles && feature.attributes &&
606                    feature.attributes.styleUrl) {
607                    feature.style = this.getStyle(feature.attributes.styleUrl, options);
608                }
609
610                if (this.extractStyles) {
611                    // Make sure that <Style> nodes within a placemark are
612                    // processed as well
613                    var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
614                                                        "*",
615                                                        "Style")[0];
616                    if (inlineStyleNode) {
617                        var inlineStyle= this.parseStyle(inlineStyleNode);
618                        if (inlineStyle) {
619                            feature.style = OpenLayers.Util.extend(
620                                feature.style, inlineStyle
621                            );
622                        }
623                    }
624                }
625
626                // check if gx:Track elements should be parsed
627                if (this.extractTracks) {
628                    var tracks = this.getElementsByTagNameNS(
629                        featureNode, this.namespaces.gx, "Track"
630                    );
631                    if (tracks && tracks.length > 0) {
632                        var track = tracks[0];
633                        var container = {
634                            features: [],
635                            feature: feature
636                        };
637                        this.readNode(track, container);
638                        if (container.features.length > 0) {
639                            features.push.apply(features, container.features);
640                        }
641                    }
642                } else {
643                    // add feature to list of features
644                    features.push(feature);                   
645                }
646            } else {
647                throw "Bad Placemark: " + i;
648            }
649        }
650
651        // add new features to existing feature list
652        this.features = this.features.concat(features);
653    },
654   
655    /**
656     * Property: readers
657     * Contains public functions, grouped by namespace prefix, that will
658     *     be applied when a namespaced node is found matching the function
659     *     name.  The function will be applied in the scope of this parser
660     *     with two arguments: the node being read and a context object passed
661     *     from the parent.
662     */
663    readers: {
664        "kml": {
665            "when": function(node, container) {
666                container.whens.push(OpenLayers.Date.parse(
667                    this.getChildValue(node)
668                ));
669            },
670            "_trackPointAttribute": function(node, container) {
671                var name = node.nodeName.split(":").pop();
672                container.attributes[name].push(this.getChildValue(node));
673            }
674        },
675        "gx": {
676            "Track": function(node, container) {
677                var obj = {
678                    whens: [],
679                    points: [],
680                    angles: []
681                };
682                if (this.trackAttributes) {
683                    var name;
684                    obj.attributes = {};
685                    for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
686                        name = this.trackAttributes[i];
687                        obj.attributes[name] = [];
688                        if (!(name in this.readers.kml)) {
689                            this.readers.kml[name] = this.readers.kml._trackPointAttribute;
690                        }
691                    }
692                }
693                this.readChildNodes(node, obj);
694                if (obj.whens.length !== obj.points.length) {
695                    throw new Error("gx:Track with unequal number of when (" + obj.whens.length + ") and gx:coord (" + obj.points.length + ") elements.");
696                }
697                var hasAngles = obj.angles.length > 0;
698                if (hasAngles && obj.whens.length !== obj.angles.length) {
699                    throw new Error("gx:Track with unequal number of when (" + obj.whens.length + ") and gx:angles (" + obj.angles.length + ") elements.");
700                }
701                var feature, point, angles;
702                for (var i=0, ii=obj.whens.length; i<ii; ++i) {
703                    feature = container.feature.clone();
704                    feature.fid = container.feature.fid || container.feature.id;
705                    point = obj.points[i];
706                    feature.geometry = point;
707                    if ("z" in point) {
708                        feature.attributes.altitude = point.z;
709                    }
710                    if (this.internalProjection && this.externalProjection) {
711                        feature.geometry.transform(
712                            this.externalProjection, this.internalProjection
713                        ); 
714                    }
715                    if (this.trackAttributes) {
716                        for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
717                            feature.attributes[name] = obj.attributes[this.trackAttributes[j]][i];
718                        }
719                    }
720                    feature.attributes.when = obj.whens[i];
721                    feature.attributes.trackId = container.feature.id;
722                    if (hasAngles) {
723                        angles = obj.angles[i];
724                        feature.attributes.heading = parseFloat(angles[0]);
725                        feature.attributes.tilt = parseFloat(angles[1]);
726                        feature.attributes.roll = parseFloat(angles[2]);
727                    }
728                    container.features.push(feature);
729                }
730            },
731            "coord": function(node, container) {
732                var str = this.getChildValue(node);
733                var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
734                var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
735                if (coords.length > 2) {
736                    point.z = parseFloat(coords[2]);
737                }
738                container.points.push(point);
739            },
740            "angles": function(node, container) {
741                var str = this.getChildValue(node);
742                var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
743                container.angles.push(parts);
744            }
745        }
746    },
747   
748    /**
749     * Method: parseFeature
750     * This function is the core of the KML parsing code in OpenLayers.
751     *     It creates the geometries that are then attached to the returned
752     *     feature, and calls parseAttributes() to get attribute data out.
753     *
754     * Parameters:
755     * node - {DOMElement}
756     *
757     * Returns:
758     * {<OpenLayers.Feature.Vector>} A vector feature.
759     */
760    parseFeature: function(node) {
761        // only accept one geometry per feature - look for highest "order"
762        var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
763        var type, nodeList, geometry, parser;
764        for(var i=0, len=order.length; i<len; ++i) {
765            type = order[i];
766            this.internalns = node.namespaceURI ? 
767                    node.namespaceURI : this.kmlns;
768            nodeList = this.getElementsByTagNameNS(node, 
769                                                   this.internalns, type);
770            if(nodeList.length > 0) {
771                // only deal with first geometry of this type
772                var parser = this.parseGeometry[type.toLowerCase()];
773                if(parser) {
774                    geometry = parser.apply(this, [nodeList[0]]);
775                    if (this.internalProjection && this.externalProjection) {
776                        geometry.transform(this.externalProjection, 
777                                           this.internalProjection); 
778                    }                       
779                } else {
780                    OpenLayers.Console.error(OpenLayers.i18n(
781                                "unsupportedGeometryType", {'geomType':type}));
782                }
783                // stop looking for different geometry types
784                break;
785            }
786        }
787
788        // construct feature (optionally with attributes)
789        var attributes;
790        if(this.extractAttributes) {
791            attributes = this.parseAttributes(node);
792        }
793        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
794
795        var fid = node.getAttribute("id") || node.getAttribute("name");
796        if(fid != null) {
797            feature.fid = fid;
798        }
799
800        return feature;
801    },       
802   
803    /**
804     * Method: getStyle
805     * Retrieves a style from a style hash using styleUrl as the key
806     * If the styleUrl doesn't exist yet, we try to fetch it
807     * Internet
808     *
809     * Parameters:
810     * styleUrl  - {String} URL of style
811     * options   - {Object} Hash of options
812     *
813     * Returns:
814     * {Object}  - (reference to) Style hash
815     */
816    getStyle: function(styleUrl, options) {
817
818        var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
819
820        var newOptions = OpenLayers.Util.extend({}, options);
821        newOptions.depth++;
822        newOptions.styleBaseUrl = styleBaseUrl;
823
824        // Fetch remote Style URLs (if not fetched before)
825        if (!this.styles[styleUrl] 
826                && !OpenLayers.String.startsWith(styleUrl, "#") 
827                && newOptions.depth <= this.maxDepth
828                && !this.fetched[styleBaseUrl] ) {
829
830            var data = this.fetchLink(styleBaseUrl);
831            if (data) {
832                this.parseData(data, newOptions);
833            }
834
835        }
836
837        // return requested style
838        var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
839        return style;
840    },
841   
842    /**
843     * Property: parseGeometry
844     * Properties of this object are the functions that parse geometries based
845     *     on their type.
846     */
847    parseGeometry: {
848       
849        /**
850         * Method: parseGeometry.point
851         * Given a KML node representing a point geometry, create an OpenLayers
852         *     point geometry.
853         *
854         * Parameters:
855         * node - {DOMElement} A KML Point node.
856         *
857         * Returns:
858         * {<OpenLayers.Geometry.Point>} A point geometry.
859         */
860        point: function(node) {
861            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
862                                                       "coordinates");
863            var coords = [];
864            if(nodeList.length > 0) {
865                var coordString = nodeList[0].firstChild.nodeValue;
866                coordString = coordString.replace(this.regExes.removeSpace, "");
867                coords = coordString.split(",");
868            }
869
870            var point = null;
871            if(coords.length > 1) {
872                // preserve third dimension
873                if(coords.length == 2) {
874                    coords[2] = null;
875                }
876                point = new OpenLayers.Geometry.Point(coords[0], coords[1],
877                                                      coords[2]);
878            } else {
879                throw "Bad coordinate string: " + coordString;
880            }
881            return point;
882        },
883       
884        /**
885         * Method: parseGeometry.linestring
886         * Given a KML node representing a linestring geometry, create an
887         *     OpenLayers linestring geometry.
888         *
889         * Parameters:
890         * node - {DOMElement} A KML LineString node.
891         *
892         * Returns:
893         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
894         */
895        linestring: function(node, ring) {
896            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
897                                                       "coordinates");
898            var line = null;
899            if(nodeList.length > 0) {
900                var coordString = this.getChildValue(nodeList[0]);
901
902                coordString = coordString.replace(this.regExes.trimSpace,
903                                                  "");
904                coordString = coordString.replace(this.regExes.trimComma,
905                                                  ",");
906                var pointList = coordString.split(this.regExes.splitSpace);
907                var numPoints = pointList.length;
908                var points = new Array(numPoints);
909                var coords, numCoords;
910                for(var i=0; i<numPoints; ++i) {
911                    coords = pointList[i].split(",");
912                    numCoords = coords.length;
913                    if(numCoords > 1) {
914                        if(coords.length == 2) {
915                            coords[2] = null;
916                        }
917                        points[i] = new OpenLayers.Geometry.Point(coords[0],
918                                                                  coords[1],
919                                                                  coords[2]);
920                    } else {
921                        throw "Bad LineString point coordinates: " +
922                              pointList[i];
923                    }
924                }
925                if(numPoints) {
926                    if(ring) {
927                        line = new OpenLayers.Geometry.LinearRing(points);
928                    } else {
929                        line = new OpenLayers.Geometry.LineString(points);
930                    }
931                } else {
932                    throw "Bad LineString coordinates: " + coordString;
933                }
934            }
935
936            return line;
937        },
938       
939        /**
940         * Method: parseGeometry.polygon
941         * Given a KML node representing a polygon geometry, create an
942         *     OpenLayers polygon geometry.
943         *
944         * Parameters:
945         * node - {DOMElement} A KML Polygon node.
946         *
947         * Returns:
948         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
949         */
950        polygon: function(node) {
951            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
952                                                       "LinearRing");
953            var numRings = nodeList.length;
954            var components = new Array(numRings);
955            if(numRings > 0) {
956                // this assumes exterior ring first, inner rings after
957                var ring;
958                for(var i=0, len=nodeList.length; i<len; ++i) {
959                    ring = this.parseGeometry.linestring.apply(this,
960                                                        [nodeList[i], true]);
961                    if(ring) {
962                        components[i] = ring;
963                    } else {
964                        throw "Bad LinearRing geometry: " + i;
965                    }
966                }
967            }
968            return new OpenLayers.Geometry.Polygon(components);
969        },
970       
971        /**
972         * Method: parseGeometry.multigeometry
973         * Given a KML node representing a multigeometry, create an
974         *     OpenLayers geometry collection.
975         *
976         * Parameters:
977         * node - {DOMElement} A KML MultiGeometry node.
978         *
979         * Returns:
980         * {<OpenLayers.Geometry.Collection>} A geometry collection.
981         */
982        multigeometry: function(node) {
983            var child, parser;
984            var parts = [];
985            var children = node.childNodes;
986            for(var i=0, len=children.length; i<len; ++i ) {
987                child = children[i];
988                if(child.nodeType == 1) {
989                    var type = (child.prefix) ?
990                            child.nodeName.split(":")[1] :
991                            child.nodeName;
992                    var parser = this.parseGeometry[type.toLowerCase()];
993                    if(parser) {
994                        parts.push(parser.apply(this, [child]));
995                    }
996                }
997            }
998            return new OpenLayers.Geometry.Collection(parts);
999        }
1000       
1001    },
1002
1003    /**
1004     * Method: parseAttributes
1005     *
1006     * Parameters:
1007     * node - {DOMElement}
1008     *
1009     * Returns:
1010     * {Object} An attributes object.
1011     */
1012    parseAttributes: function(node) {
1013        var attributes = {};
1014       
1015        // Extended Data is parsed first.
1016        var edNodes = node.getElementsByTagName("ExtendedData");
1017        if (edNodes.length) {
1018            attributes = this.parseExtendedData(edNodes[0]);
1019        }
1020       
1021        // assume attribute nodes are type 1 children with a type 3 or 4 child
1022        var child, grandchildren, grandchild;
1023        var children = node.childNodes;
1024
1025        for(var i=0, len=children.length; i<len; ++i) {
1026            child = children[i];
1027            if(child.nodeType == 1) {
1028                grandchildren = child.childNodes;
1029                if(grandchildren.length >= 1 && grandchildren.length <= 3) {
1030                    var grandchild;
1031                    switch (grandchildren.length) {
1032                        case 1:
1033                            grandchild = grandchildren[0];
1034                            break;
1035                        case 2:
1036                            var c1 = grandchildren[0];
1037                            var c2 = grandchildren[1];
1038                            grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
1039                                c1 : c2;
1040                            break;
1041                        case 3:
1042                        default:
1043                            grandchild = grandchildren[1];
1044                            break;
1045                    }
1046                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
1047                        var name = (child.prefix) ?
1048                                child.nodeName.split(":")[1] :
1049                                child.nodeName;
1050                        var value = OpenLayers.Util.getXmlNodeValue(grandchild);
1051                        if (value) {
1052                            value = value.replace(this.regExes.trimSpace, "");
1053                            attributes[name] = value;
1054                        }
1055                    }
1056                } 
1057            }
1058        }
1059        return attributes;
1060    },
1061
1062    /**
1063     * Method: parseExtendedData
1064     * Parse ExtendedData from KML. Limited support for schemas/datatypes.
1065     *     See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
1066     *     for more information on extendeddata.
1067     */
1068    parseExtendedData: function(node) {
1069        var attributes = {};
1070        var i, len, data, key;
1071        var dataNodes = node.getElementsByTagName("Data");
1072        for (i = 0, len = dataNodes.length; i < len; i++) {
1073            data = dataNodes[i];
1074            key = data.getAttribute("name");
1075            var ed = {};
1076            var valueNode = data.getElementsByTagName("value");
1077            if (valueNode.length) {
1078                ed['value'] = this.getChildValue(valueNode[0]);
1079            }   
1080            var nameNode = data.getElementsByTagName("displayName");
1081            if (nameNode.length) {
1082                ed['displayName'] = this.getChildValue(nameNode[0]);
1083            }
1084            attributes[key] = ed;
1085        }
1086        var simpleDataNodes = node.getElementsByTagName("SimpleData");
1087        for (i = 0, len = simpleDataNodes.length; i < len; i++) {
1088            var ed = {};
1089            data = simpleDataNodes[i];
1090            key = data.getAttribute("name");
1091            ed['value'] = this.getChildValue(data);
1092            ed['displayName'] = key;
1093            attributes[key] = ed;
1094        }
1095       
1096        return attributes;   
1097    },
1098   
1099    /**
1100     * Method: parseProperty
1101     * Convenience method to find a node and return its value
1102     *
1103     * Parameters:
1104     * xmlNode    - {<DOMElement>}
1105     * namespace  - {String} namespace of the node to find
1106     * tagName    - {String} name of the property to parse
1107     *
1108     * Returns:
1109     * {String} The value for the requested property (defaults to null)
1110     */   
1111    parseProperty: function(xmlNode, namespace, tagName) {
1112        var value;
1113        var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
1114        try {
1115            value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
1116        } catch(e) {
1117            value = null;
1118        }
1119     
1120        return value;
1121    },                                                             
1122
1123    /**
1124     * APIMethod: write
1125     * Accept Feature Collection, and return a string.
1126     *
1127     * Parameters:
1128     * features - {Array(<OpenLayers.Feature.Vector>} An array of features.
1129     *
1130     * Returns:
1131     * {String} A KML string.
1132     */
1133    write: function(features) {
1134        if(!(features instanceof Array)) {
1135            features = [features];
1136        }
1137        var kml = this.createElementNS(this.kmlns, "kml");
1138        var folder = this.createFolderXML();
1139        for(var i=0, len=features.length; i<len; ++i) {
1140            folder.appendChild(this.createPlacemarkXML(features[i]));
1141        }
1142        kml.appendChild(folder);
1143        return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
1144    },
1145
1146    /**
1147     * Method: createFolderXML
1148     * Creates and returns a KML folder node
1149     *
1150     * Returns:
1151     * {DOMElement}
1152     */
1153    createFolderXML: function() {
1154        // Folder
1155        var folder = this.createElementNS(this.kmlns, "Folder");
1156
1157        // Folder name
1158        if (this.foldersName) {
1159            var folderName = this.createElementNS(this.kmlns, "name");
1160            var folderNameText = this.createTextNode(this.foldersName); 
1161            folderName.appendChild(folderNameText);
1162            folder.appendChild(folderName);
1163        }
1164
1165        // Folder description
1166        if (this.foldersDesc) {
1167            var folderDesc = this.createElementNS(this.kmlns, "description");       
1168            var folderDescText = this.createTextNode(this.foldersDesc); 
1169            folderDesc.appendChild(folderDescText);
1170            folder.appendChild(folderDesc);
1171        }
1172
1173        return folder;
1174    },
1175
1176    /**
1177     * Method: createPlacemarkXML
1178     * Creates and returns a KML placemark node representing the given feature.
1179     *
1180     * Parameters:
1181     * feature - {<OpenLayers.Feature.Vector>}
1182     *
1183     * Returns:
1184     * {DOMElement}
1185     */
1186    createPlacemarkXML: function(feature) {       
1187        // Placemark name
1188        var placemarkName = this.createElementNS(this.kmlns, "name");
1189        var name = feature.style && feature.style.label ? feature.style.label :
1190                   feature.attributes.name || feature.id;
1191        placemarkName.appendChild(this.createTextNode(name));
1192
1193        // Placemark description
1194        var placemarkDesc = this.createElementNS(this.kmlns, "description");
1195        var desc = feature.attributes.description || this.placemarksDesc;
1196        placemarkDesc.appendChild(this.createTextNode(desc));
1197       
1198        // Placemark
1199        var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
1200        if(feature.fid != null) {
1201            placemarkNode.setAttribute("id", feature.fid);
1202        }
1203        placemarkNode.appendChild(placemarkName);
1204        placemarkNode.appendChild(placemarkDesc);
1205
1206        // Geometry node (Point, LineString, etc. nodes)
1207        var geometryNode = this.buildGeometryNode(feature.geometry);
1208        placemarkNode.appendChild(geometryNode);       
1209       
1210        // TBD - deal with remaining (non name/description) attributes.
1211        return placemarkNode;
1212    },   
1213
1214    /**
1215     * Method: buildGeometryNode
1216     * Builds and returns a KML geometry node with the given geometry.
1217     *
1218     * Parameters:
1219     * geometry - {<OpenLayers.Geometry>}
1220     *
1221     * Returns:
1222     * {DOMElement}
1223     */
1224    buildGeometryNode: function(geometry) {
1225        if (this.internalProjection && this.externalProjection) {
1226            geometry = geometry.clone();
1227            geometry.transform(this.internalProjection, 
1228                               this.externalProjection);
1229        }                       
1230        var className = geometry.CLASS_NAME;
1231        var type = className.substring(className.lastIndexOf(".") + 1);
1232        var builder = this.buildGeometry[type.toLowerCase()];
1233        var node = null;
1234        if(builder) {
1235            node = builder.apply(this, [geometry]);
1236        }
1237        return node;
1238    },
1239
1240    /**
1241     * Property: buildGeometry
1242     * Object containing methods to do the actual geometry node building
1243     *     based on geometry type.
1244     */
1245    buildGeometry: {
1246        // TBD: Anybody care about namespace aliases here (these nodes have
1247        //    no prefixes)?
1248
1249        /**
1250         * Method: buildGeometry.point
1251         * Given an OpenLayers point geometry, create a KML point.
1252         *
1253         * Parameters:
1254         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
1255         *
1256         * Returns:
1257         * {DOMElement} A KML point node.
1258         */
1259        point: function(geometry) {
1260            var kml = this.createElementNS(this.kmlns, "Point");
1261            kml.appendChild(this.buildCoordinatesNode(geometry));
1262            return kml;
1263        },
1264       
1265        /**
1266         * Method: buildGeometry.multipoint
1267         * Given an OpenLayers multipoint geometry, create a KML
1268         *     GeometryCollection.
1269         *
1270         * Parameters:
1271         * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
1272         *
1273         * Returns:
1274         * {DOMElement} A KML GeometryCollection node.
1275         */
1276        multipoint: function(geometry) {
1277            return this.buildGeometry.collection.apply(this, [geometry]);
1278        },
1279
1280        /**
1281         * Method: buildGeometry.linestring
1282         * Given an OpenLayers linestring geometry, create a KML linestring.
1283         *
1284         * Parameters:
1285         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
1286         *
1287         * Returns:
1288         * {DOMElement} A KML linestring node.
1289         */
1290        linestring: function(geometry) {
1291            var kml = this.createElementNS(this.kmlns, "LineString");
1292            kml.appendChild(this.buildCoordinatesNode(geometry));
1293            return kml;
1294        },
1295       
1296        /**
1297         * Method: buildGeometry.multilinestring
1298         * Given an OpenLayers multilinestring geometry, create a KML
1299         *     GeometryCollection.
1300         *
1301         * Parameters:
1302         * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
1303         *
1304         * Returns:
1305         * {DOMElement} A KML GeometryCollection node.
1306         */
1307        multilinestring: function(geometry) {
1308            return this.buildGeometry.collection.apply(this, [geometry]);
1309        },
1310
1311        /**
1312         * Method: buildGeometry.linearring
1313         * Given an OpenLayers linearring geometry, create a KML linearring.
1314         *
1315         * Parameters:
1316         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
1317         *
1318         * Returns:
1319         * {DOMElement} A KML linearring node.
1320         */
1321        linearring: function(geometry) {
1322            var kml = this.createElementNS(this.kmlns, "LinearRing");
1323            kml.appendChild(this.buildCoordinatesNode(geometry));
1324            return kml;
1325        },
1326       
1327        /**
1328         * Method: buildGeometry.polygon
1329         * Given an OpenLayers polygon geometry, create a KML polygon.
1330         *
1331         * Parameters:
1332         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
1333         *
1334         * Returns:
1335         * {DOMElement} A KML polygon node.
1336         */
1337        polygon: function(geometry) {
1338            var kml = this.createElementNS(this.kmlns, "Polygon");
1339            var rings = geometry.components;
1340            var ringMember, ringGeom, type;
1341            for(var i=0, len=rings.length; i<len; ++i) {
1342                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
1343                ringMember = this.createElementNS(this.kmlns, type);
1344                ringGeom = this.buildGeometry.linearring.apply(this,
1345                                                               [rings[i]]);
1346                ringMember.appendChild(ringGeom);
1347                kml.appendChild(ringMember);
1348            }
1349            return kml;
1350        },
1351       
1352        /**
1353         * Method: buildGeometry.multipolygon
1354         * Given an OpenLayers multipolygon geometry, create a KML
1355         *     GeometryCollection.
1356         *
1357         * Parameters:
1358         * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
1359         *
1360         * Returns:
1361         * {DOMElement} A KML GeometryCollection node.
1362         */
1363        multipolygon: function(geometry) {
1364            return this.buildGeometry.collection.apply(this, [geometry]);
1365        },
1366
1367        /**
1368         * Method: buildGeometry.collection
1369         * Given an OpenLayers geometry collection, create a KML MultiGeometry.
1370         *
1371         * Parameters:
1372         * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
1373         *
1374         * Returns:
1375         * {DOMElement} A KML MultiGeometry node.
1376         */
1377        collection: function(geometry) {
1378            var kml = this.createElementNS(this.kmlns, "MultiGeometry");
1379            var child;
1380            for(var i=0, len=geometry.components.length; i<len; ++i) {
1381                child = this.buildGeometryNode.apply(this,
1382                                                     [geometry.components[i]]);
1383                if(child) {
1384                    kml.appendChild(child);
1385                }
1386            }
1387            return kml;
1388        }
1389    },
1390
1391    /**
1392     * Method: buildCoordinatesNode
1393     * Builds and returns the KML coordinates node with the given geometry
1394     * <coordinates>...</coordinates>
1395     *
1396     * Parameters:
1397     * geometry - {<OpenLayers.Geometry>}
1398     *
1399     * Return:
1400     * {DOMElement}
1401     */     
1402    buildCoordinatesNode: function(geometry) {
1403        var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
1404       
1405        var path;
1406        var points = geometry.components;
1407        if(points) {
1408            // LineString or LinearRing
1409            var point;
1410            var numPoints = points.length;
1411            var parts = new Array(numPoints);
1412            for(var i=0; i<numPoints; ++i) {
1413                point = points[i];
1414                parts[i] = point.x + "," + point.y;
1415            }
1416            path = parts.join(" ");
1417        } else {
1418            // Point
1419            path = geometry.x + "," + geometry.y;
1420        }
1421       
1422        var txtNode = this.createTextNode(path);
1423        coordinatesNode.appendChild(txtNode);
1424       
1425        return coordinatesNode;
1426    },   
1427
1428    CLASS_NAME: "OpenLayers.Format.KML" 
1429});
Note: See TracBrowser for help on using the repository browser.