[OpenLayers-Commits] r4219 - in trunk/openlayers: examples lib/OpenLayers/Format tests/Format

commits at openlayers.org commits at openlayers.org
Tue Sep 11 11:18:43 EDT 2007


Author: tschaub
Date: 2007-09-11 11:18:42 -0400 (Tue, 11 Sep 2007)
New Revision: 4219

Modified:
   trunk/openlayers/examples/vector-formats.html
   trunk/openlayers/lib/OpenLayers/Format/KML.js
   trunk/openlayers/tests/Format/test_KML.html
Log:
Full read/write support for KML.  All KML 2.1 geometries supported.  All OL geometries supported (closes #927).

Modified: trunk/openlayers/examples/vector-formats.html
===================================================================
--- trunk/openlayers/examples/vector-formats.html	2007-09-11 14:46:55 UTC (rev 4218)
+++ trunk/openlayers/examples/vector-formats.html	2007-09-11 15:18:42 UTC (rev 4219)
@@ -77,8 +77,8 @@
             formats = {
                 wkt: new OpenLayers.Format.WKT(),
                 geojson: new OpenLayers.Format.GeoJSON(),
-                gml: new OpenLayers.Format.GML() //,
-                //kml: new OpenLayers.Format.KML()
+                gml: new OpenLayers.Format.GML(),
+                kml: new OpenLayers.Format.KML()
             };
             
             map.setCenter(new OpenLayers.LonLat(0, 0), 1);
@@ -146,7 +146,7 @@
             <label for="formatType">Format</label>
             <select name="formatType" id="formatType">
                 <option value="geojson" selected="selected">GeoJSON</option>
-                <!--<option value="kml">KML</option>-->
+                <option value="kml">KML</option>
                 <option value="gml">GML</option>
                 <option value="wkt">Well-Known Text (WKT)</option>
             </select>

Modified: trunk/openlayers/lib/OpenLayers/Format/KML.js
===================================================================
--- trunk/openlayers/lib/OpenLayers/Format/KML.js	2007-09-11 14:46:55 UTC (rev 4218)
+++ trunk/openlayers/lib/OpenLayers/Format/KML.js	2007-09-11 15:18:42 UTC (rev 4219)
@@ -5,34 +5,63 @@
 /**
  * @requires OpenLayers/Format.js
  * @requires OpenLayers/Feature/Vector.js
- * @requires OpenLayers/Ajax.js
+ *
+ * Class: OpenLayers.Format.KML
+ * Read/Wite KML. Create a new instance with the <OpenLayers.Format.KML>
+ *     constructor. 
  * 
- * Class: OpenLayers.Format.KML
- * Read only KML. Largely Proof of Concept: does not support advanced Features,
- *     including Polygons.  Create a new instance with the 
- *     <OpenLayers.Format.KML> constructor.
- *
  * Inherits from:
- *  - <OpenLayers.Format>
+ *  - <OpenLayers.Format.XML>
  */
-OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format, {
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
     
     /**
      * APIProperty: kmlns
-     * KML Namespace to use. Defaults to 2.0 namespace.
+     * {String} KML Namespace to use. Defaults to 2.0 namespace.
      */
     kmlns: "http://earth.google.com/kml/2.0",
     
+    /** 
+     * APIProperty: placemarksDesc
+     * {String} Name of the placemarks.  Default is "No description available."
+     */
+    placemarksDesc: "No description available",
+    
+    /** 
+     * APIProperty: foldersName
+     * {String} Name of the folders.  Default is "OpenLayers export."
+     */
+    foldersName: "OpenLayers export",
+    
+    /** 
+     * APIProperty: foldersDesc
+     * {String} Description of the folders. Default is "Exported on [date]."
+     */
+    foldersDesc: "Exported on " + new Date(),
+    
     /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract attributes from KML.  Default is true.
+     */
+    extractAttributes: true,
+
+    /**
      * Constructor: OpenLayers.Format.KML
-     * Create a new parser for KML
+     * Create a new parser for KML.
      *
      * Parameters:
      * options - {Object} An optional object whose properties will be set on
-     *                    this instance.
+     *     this instance.
      */
     initialize: function(options) {
-        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+        // compile regular expressions once instead of every time they are used
+        this.regExes = {
+            trimSpace: (/^\s*|\s*$/g),
+            removeSpace: (/\s*/g),
+            splitSpace: (/\s+/),
+            trimComma: (/\s*,\s*/g)
+        };
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
     },
 
     /**
@@ -40,156 +69,569 @@
      * Read data from a string, and return a list of features. 
      * 
      * Parameters: 
-     * data - {string} or {XMLNode>} data to read/parse.
+     * data - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} List of features.
      */
-     read: function(data) {
-        if (typeof data == "string") { 
-            data = OpenLayers.parseXMLString(data);
-        }    
-        var featureNodes = OpenLayers.Ajax.getElementsByTagNameNS(data, this.kmlns, "", "Placemark");
-        
-        var features = [];
-        
-        // Process all the featureMembers
-        for (var i = 0; i < featureNodes.length; i++) {
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var featureNodes = this.getElementsByTagNameNS(data,
+                                                       this.kmlns,
+                                                       "Placemark");
+        var numFeatures = featureNodes.length;
+        var features = new Array(numFeatures);
+        for(var i=0; i<numFeatures; i++) {
             var feature = this.parseFeature(featureNodes[i]);
-
-            if (feature) {
-                features.push(feature);
+            if(feature) {
+                features[i] = feature;
+            } else {
+                throw "Bad Placemark: " + i;
             }
         }
         return features;
-     },
+    },
 
-     /**
-      * Method: parseFeature
-      * This function is the core of the KML parsing code in OpenLayers.
-      * It creates the geometries that are then attached to the returned
-      * feature, and calls parseAttributes() to get attribute data out.
-      *
-      * Parameters:
-      * xmlNode - {<DOMElement>} 
-      */
-     parseFeature: function(xmlNode) {
-        var geom;
-        var p; // [points,bounds]
+    /**
+     * Method: parseFeature
+     * This function is the core of the KML parsing code in OpenLayers.
+     *     It creates the geometries that are then attached to the returned
+     *     feature, and calls parseAttributes() to get attribute data out.
+     *
+     * Parameters:
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A vector feature.
+     */
+    parseFeature: function(node) {
+        // only accept one geometry per feature - look for highest "order"
+        var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+        var type, nodeList, geometry, parser;
+        for(var i=0; i<order.length; ++i) {
+            type = order[i];
+            nodeList = this.getElementsByTagNameNS(node, this.kmlns, type);
+            if(nodeList.length > 0) {
+                // only deal with first geometry of this type
+                var parser = this.parseGeometry[type.toLowerCase()];
+                if(parser) {
+                    geometry = parser.apply(this, [nodeList[0]]);
+                } else {
+                    OpenLayers.Console.error("Unsupported geometry type: " +
+                                             type);
+                }
+                // stop looking for different geometry types
+                break;
+            }
+        }
 
-        var feature = new OpenLayers.Feature.Vector();
+        // construct feature (optionally with attributes)
+        var attributes;
+        if(this.extractAttributes) {
+            attributes = this.parseAttributes(node);
+        }
+        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
 
-        // match Point
-        if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,
-            this.kmlns, "", "Point").length != 0) {
-            var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,
-                this.kmlns, "", "Point")[0];
-            
-            p = this.parseCoords(point);
-            if (p.points) {
-                geom = p.points[0];
-                // TBD Bounds only set for one of multiple geometries
-                geom.extendBounds(p.bounds);
+        var fid = node.getAttribute("id");
+        if(fid != null) {
+            feature.fid = fid;
+        }
+
+        return feature;
+    },        
+    
+    /**
+     * Property: parseGeometry
+     * Properties of this object are the functions that parse geometries based
+     *     on their type.
+     */
+    parseGeometry: {
+        
+        /**
+         * Method: parseGeometry.point
+         * Given a KML node representing a point geometry, create an OpenLayers
+         *     point geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Point node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Point>} A point geometry.
+         */
+        point: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.kmlns,
+                                                       "coordinates");
+            var coords = [];
+            if(nodeList.length > 0) {
+                var coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.removeSpace, "");
+                coords = coordString.split(",");
             }
+
+            var point = null;
+            if(coords.length > 1) {
+                // preserve third dimension
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+                                                      coords[2]);
+            } else {
+                throw "Bad coordinate string: " + coordString;
+            }
+            return point;
+        },
         
-        // match LineString 
-        } else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,
-            this.kmlns, "", "LineString").length != 0) {
-            var linestring = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,
-                this.kmlns, "", "LineString")[0];
-            p = this.parseCoords(linestring);
-            if (p.points) {
-                geom = new OpenLayers.Geometry.LineString(p.points);
-                // TBD Bounds only set for one of multiple geometries
-                geom.extendBounds(p.bounds);
+        /**
+         * Method: parseGeometry.linestring
+         * Given a KML node representing a linestring geometry, create an
+         *     OpenLayers linestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML LineString node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         */
+        linestring: function(node, ring) {
+            var nodeList = this.getElementsByTagNameNS(node, this.kmlns,
+                                                       "coordinates");
+            var line = null;
+            if(nodeList.length > 0) {
+                var coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.trimSpace,
+                                                  "");
+                coordString = coordString.replace(this.regExes.trimComma,
+                                                  ",");
+                var pointList = coordString.split(this.regExes.splitSpace);
+                var numPoints = pointList.length;
+                var points = new Array(numPoints);
+                var coords, numCoords;
+                for(var i=0; i<numPoints; ++i) {
+                    coords = pointList[i].split(",");
+                    numCoords = coords.length;
+                    if(numCoords > 1) {
+                        if(coords.length == 2) {
+                            coords[2] = null;
+                        }
+                        points[i] = new OpenLayers.Geometry.Point(coords[0],
+                                                                  coords[1],
+                                                                  coords[2]);
+                    } else {
+                        throw "Bad LineString point coordinates: " +
+                              pointList[i];
+                    }
+                }
+                if(numPoints) {
+                    if(ring) {
+                        line = new OpenLayers.Geometry.LinearRing(points);
+                    } else {
+                        line = new OpenLayers.Geometry.LineString(points);
+                    }
+                } else {
+                    throw "Bad LineString coordinates: " + coordString;
+                }
             }
+
+            return line;
+        },
+        
+        /**
+         * Method: parseGeometry.polygon
+         * Given a KML node representing a polygon geometry, create an
+         *     OpenLayers polygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Polygon node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         */
+        polygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.kmlns,
+                                                       "LinearRing");
+            var numRings = nodeList.length;
+            var components = new Array(numRings);
+            if(numRings > 0) {
+                // this assumes exterior ring first, inner rings after
+                var ring;
+                for(var i=0; i<nodeList.length; ++i) {
+                    ring = this.parseGeometry.linestring.apply(this,
+                                                        [nodeList[i], true]);
+                    if(ring) {
+                        components[i] = ring;
+                    } else {
+                        throw "Bad LinearRing geometry: " + i;
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Polygon(components);
+        },
+        
+        /**
+         * Method: parseGeometry.multigeometry
+         * Given a KML node representing a multigeometry, create an
+         *     OpenLayers geometry collection.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML MultiGeometry node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A geometry collection.
+         */
+        multigeometry: function(node) {
+            var child, parser;
+            var parts = [];
+            var children = node.childNodes;
+            for(var i=0; i<children.length; ++i ) {
+                child = children[i];
+                if(child.nodeType == 1) {
+                    type = (child.prefix) ?
+                            child.nodeName.split(":")[1] :
+                            child.nodeName;
+                    var parser = this.parseGeometry[type.toLowerCase()];
+                    if(parser) {
+                        parts.push(parser.apply(this, [child]));
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Collection(parts);
         }
         
-        feature.geometry = geom;
-        feature.attributes = this.parseAttributes(xmlNode);
-        
-        return feature;
-    },        
-    
+    },
+
     /**
      * Method: parseAttributes
-     * recursive function parse the attributes of a KML node.
-     * Searches for any child nodes which aren't geometries,
-     * and gets their value.
      *
      * Parameters:
-     * xmlNode - {<DOMElement>} 
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {Object} An attributes object.
      */
-    parseAttributes: function(xmlNode) {
-        var nodes = xmlNode.childNodes;
+    parseAttributes: function(node) {
         var attributes = {};
-        for(var i = 0; i < nodes.length; i++) {
-            var name = nodes[i].nodeName;
-            var value = OpenLayers.Util.getXmlNodeValue(nodes[i]);
-            // Ignore Geometry attributes
-            // match ".//gml:pos|.//gml:posList|.//gml:coordinates"
-            if((name.search(":pos")!=-1)
-              ||(name.search(":posList")!=-1)
-              ||(name.search(":coordinates")!=-1)){
-               continue;    
+        // assume attribute nodes are type 1 children with a type 3 child
+        var child, grandchildren, grandchild;
+        var children = node.childNodes;
+        for(var i=0; i<children.length; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                grandchildren = child.childNodes;
+                if(grandchildren.length == 1) {
+                    grandchild = grandchildren[0];
+                    if(grandchild.nodeType == 3) {
+                        name = (child.prefix) ?
+                                child.nodeName.split(":")[1] :
+                                child.nodeName;
+                        value = grandchild.nodeValue.replace(
+                                                this.regExes.trimSpace, "");
+                        attributes[name] = value;
+                    }
+                }
             }
-            
-            // Check for a leaf node
-            if((nodes[i].childNodes.length == 1 && nodes[i].childNodes[0].nodeName == "#text")
-                || (nodes[i].childNodes.length == 0 && nodes[i].nodeName!="#text")) {
-                attributes[name] = value;
-            }
-            OpenLayers.Util.extend(attributes, this.parseAttributes(nodes[i]))
-        }   
+        }
         return attributes;
     },
-    
+
     /**
-     * Method: parseCoords
-     * Extract Geographic coordinates from an XML node.
+     * APIMethod: write
+     * Accept Feature Collection, and return a string. 
+     * 
+     * Parameters:
+     * features - An array of <OpenLayers.Feature.Vector> features.
      *
+     * Returns:
+     * {String} A KML string.
+     */
+     write: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        var kml = this.createElementNS(this.kmlns, "kml");
+        var folder = this.createFolderXML();
+        for(var i=0; i<features.length; ++i) {
+            folder.appendChild(this.createPlacemarkXML(features[i]));
+        }
+        kml.appendChild(folder);
+        return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+     },
+
+    /**
+     * Method: createFolderXML
+     * Creates and returns a KML folder node
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    createFolderXML: function() {
+        // Folder name
+        var folderName = this.createElementNS(this.kmlns, "name");
+        var folderNameText = this.createTextNode(this.foldersName); 
+        folderName.appendChild(folderNameText);
+
+        // Folder description
+        var folderDesc = this.createElementNS(this.kmlns, "description");        
+        var folderDescText = this.createTextNode(this.foldersDesc); 
+        folderDesc.appendChild(folderDescText);
+
+        // Folder
+        var folder = this.createElementNS(this.kmlns, "Folder");
+        folder.appendChild(folderName);
+        folder.appendChild(folderDesc);
+        
+        return folder;
+    },
+
+    /**
+     * Method: createPlacemarkXML
+     * Creates and returns a KML placemark node representing the given feature. 
+     * 
      * Parameters:
-     * xmlNode - {<XMLNode>} 
+     * feature - {<OpenLayers.Feature.Vector>}
      * 
      * Returns:
-     * An array of <OpenLayers.Geometry.Point> points.
+     * {DOMElement}
      */
-    parseCoords: function(xmlNode) {
-        var p = [];
-        p.points = [];
-        // TBD: Need to handle an array of coordNodes not just coordNodes[0]
+    createPlacemarkXML: function(feature) {        
+        // Placemark name
+        var placemarkName = this.createElementNS(this.kmlns, "name");
+        var name = (feature.attributes.name) ?
+                    feature.attributes.name : feature.id;
+        placemarkName.appendChild(this.createTextNode(name));
+
+        // Placemark description
+        var placemarkDesc = this.createElementNS(this.kmlns, "description");
+        var desc = (feature.attributes.description) ?
+                    feature.attributes.description : this.placemarksDesc;
+        placemarkDesc.appendChild(this.createTextNode(desc));
         
-        var coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.kmlns, "", "coordinates")[0];
-        var coordString = OpenLayers.Util.getXmlNodeValue(coordNodes);
+        // Placemark
+        var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+        if(feature.fid != null) {
+            placemarkNode.setAttribute("id", feature.fid);
+        }
+        placemarkNode.appendChild(placemarkName);
+        placemarkNode.appendChild(placemarkDesc);
+
+        // Geometry node (Point, LineString, etc. nodes)
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        placemarkNode.appendChild(geometryNode);        
         
-        var firstCoord = coordString.split(" ");
+        // TBD - deal with remaining (non name/description) attributes.
+        return placemarkNode;
+    },    
+
+    /**
+     * Method: buildGeometryNode
+     * Builds and returns a KML geometry node with the given geometry.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    buildGeometryNode: function(geometry) {
+        var className = geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        var builder = this.buildGeometry[type.toLowerCase()];
+        var node = null;
+        if(builder) {
+            node = builder.apply(this, [geometry]);
+        }
+        return node;
+    },
+
+    /**
+     * Property: buildGeometry
+     * Object containing methods to do the actual geometry node building
+     *     based on geometry type.
+     */
+    buildGeometry: {
+        // TBD: Anybody care about namespace aliases here (these nodes have
+        //    no prefixes)?
+
+        /**
+         * Method: buildGeometry.point
+         * Given an OpenLayers point geometry, create a KML point.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML point node.
+         */
+        point: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Point");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
         
-        while (firstCoord[0] == "") 
-            firstCoord.shift();
+        /**
+         * Method: buildGeometry.multipoint
+         * Given an OpenLayers multipoint geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipoint: function(geometry) {
+            return this.buildGeometry.collection(geometry);
+        },
+
+        /**
+         * Method: buildGeometry.linestring
+         * Given an OpenLayers linestring geometry, create a KML linestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linestring node.
+         */
+        linestring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LineString");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
         
-        var dim = firstCoord[0].split(",").length;
+        /**
+         * Method: buildGeometry.multilinestring
+         * Given an OpenLayers multilinestring geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multilinestring: function(geometry) {
+            return this.buildGeometry.collection(geometry);
+        },
 
-        // Extract an array of Numbers from CoordString
-        var nums = (coordString) ? coordString.split(/[, \n\t]+/) : [];
+        /**
+         * Method: buildGeometry.linearring
+         * Given an OpenLayers linearring geometry, create a KML linearring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linearring node.
+         */
+        linearring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LinearRing");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
         
+        /**
+         * Method: buildGeometry.polygon
+         * Given an OpenLayers polygon geometry, create a KML polygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML polygon node.
+         */
+        polygon: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Polygon");
+            var rings = geometry.components;
+            var ringMember, ringGeom, type;
+            for(var i=0; i<rings.length; ++i) {
+                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+                ringMember = this.createElementNS(this.kmlns, type);
+                ringGeom = this.buildGeometry.linearring.apply(this,
+                                                               [rings[i]]);
+                ringMember.appendChild(ringGeom);
+                kml.appendChild(ringMember);
+            }
+            return kml;
+        },
         
-        // Remove elements caused by leading and trailing white space
-        while (nums[0] == "") 
-            nums.shift();
-        
-        while (nums[nums.length-1] == "") 
-            nums.pop();
-        
-        for(i = 0; i < nums.length; i = i + dim) {
-            x = parseFloat(nums[i]);
-            y = parseFloat(nums[i+1]);
-            p.points.push(new OpenLayers.Geometry.Point(x, y));
-            
-            if (!p.bounds) {
-                p.bounds = new OpenLayers.Bounds(x, y, x, y);
-            } else {
-                p.bounds.extend(x, y);
+        /**
+         * Method: buildGeometry.multipolygon
+         * Given an OpenLayers multipolygon geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipolygon: function(geometry) {
+            return this.buildGeometry.collection(geometry);
+        },
+
+        /**
+         * Method: buildGeometry.collection
+         * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+         *
+         * Returns:
+         * {DOMElement} A KML MultiGeometry node.
+         */
+        collection: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+            var child;
+            for(var i=0; i<geometry.components.length; ++i) {
+                child = this.buildGeometryNode.apply(this,
+                                                     [geometry.components[i]]);
+                if(child) {
+                    kml.appendChild(child);
+                }
             }
+            return kml;
         }
-        return p;
     },
 
+    /**
+     * Method: buildCoordinatesNode
+     * Builds and returns the KML coordinates node with the given geometry
+     * <coordinates>...</coordinates>
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Return:
+     * {DOMElement}
+     */     
+    buildCoordinatesNode: function(geometry) {
+        var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+        
+        var path;
+        var points = geometry.components;
+        if(points) {
+            // LineString or LinearRing
+            var point;
+            var numPoints = points.length;
+            var parts = new Array(numPoints);
+            for(var i=0; i<numPoints; ++i) {
+                point = points[i];
+                parts[i] = point.x + "," + point.y;
+            }
+            path = parts.join(" ");
+        } else {
+            // Point
+            path = geometry.x + "," + geometry.y;
+        }
+        
+        var txtNode = this.createTextNode(path);
+        coordinatesNode.appendChild(txtNode);
+        
+        return coordinatesNode;
+    },    
+
     CLASS_NAME: "OpenLayers.Format.KML" 
-});     
+});
\ No newline at end of file

Modified: trunk/openlayers/tests/Format/test_KML.html
===================================================================
--- trunk/openlayers/tests/Format/test_KML.html	2007-09-11 14:46:55 UTC (rev 4218)
+++ trunk/openlayers/tests/Format/test_KML.html	2007-09-11 15:18:42 UTC (rev 4219)
@@ -3,6 +3,7 @@
     <script src="../../lib/OpenLayers.js"></script> 
     <script type="text/javascript">
 
+    var test_content = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>OpenLayers export</name><description>Vector geometries from OpenLayers</description><Placemark id="KML.Polygon"><name>OpenLayers.Feature.Vector_344</name><description>A KLM Polygon</description><Polygon><outerBoundaryIs><LinearRing><coordinates>5.001370157823406,49.26855713824488 8.214706453896161,49.630662409673505 8.397385910100951,48.45172350357396 5.001370157823406,49.26855713824488</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark><Placemark id="KML.LineString"><name>OpenLayers.Feature.Vector_402</name><description>A KML LineString</description><LineString><coordinates>5.838523393080493,49.74814616928052 5.787079558782349,48.410795432216574 8.91427702008381,49.28932499608202</coordinates></LineString></Placemark><Placemark id="KML.Point"><name>OpenLayers.Feature.Vector_451</name><description>A KML Point</description><Point><coordinates>6.985073041685488,49.8682250149058</coordinates></Point></Placemark><Placemark id="KML.MultiGeometry"><name>SF Marina Harbor Master</name><description>KML MultiGeometry</description><MultiGeometry><LineString><coordinates>-122.4425587930444,37.80666418607323 -122.4428379594768,37.80663578323093</coordinates></LineString><LineString><coordinates>-122.4425509770566,37.80662588061205 -122.4428340530617,37.8065999493009</coordinates></LineString></MultiGeometry></Placemark></Folder></kml>';
 
     function test_Format_KML_constructor(t) { 
         t.plan(4); 
@@ -14,24 +15,32 @@
         t.eq(format.foo, "bar", "constructor sets options correctly"); 
         t.eq(typeof format.read, "function", "format has a read function"); 
         t.eq(typeof format.write, "function", "format has a write function");
-
     }
 
     function test_Format_KML_read(t) {
-        t.plan(1);
-        t.ok(true, "Read tests not done yet.");
+        t.plan(5);
+        var features = (new OpenLayers.Format.KML()).read(this.test_content);
+        t.eq(features.length, 4, "Number of features read is correct");
+        t.ok(features[0].geometry.toString() == "POLYGON((5.001370157823406 49.26855713824488,8.214706453896161 49.630662409673505,8.397385910100951 48.45172350357396,5.001370157823406 49.26855713824488))", "polygon feature geometry correctly created");
+        t.ok(features[1].geometry.toString() == "LINESTRING(5.838523393080493 49.74814616928052,5.787079558782349 48.410795432216574,8.91427702008381 49.28932499608202)", "linestring feature geometry correctly created");
+        t.ok(features[2].geometry.toString() == "POINT(6.985073041685488 49.8682250149058)", "point feature geometry correctly created");
+        t.ok(features[3].geometry.CLASS_NAME == "OpenLayers.Geometry.Collection",
+             "read geometry collection");
     }
 
     function test_Format_KML_write(t) {
+        // make sure id, name, and description are preserved
         t.plan(1);
-        t.ok(true, "Write tests not done yet.");
+        var kmlExpected = this.test_content;
+        var options = {
+            folderName: "OpenLayers export",
+            foldersDesc: "Vector geometries from OpenLayers"
+        }
 
-        var format = new OpenLayers.Format.KML();
-        //var doc = format.read(text);
-        //var out = format.write(doc);
-        //out = out.replace(/[\r\n]/g, '');
-        //t.eq(text, out,
-        //     "correctly writes an KML DOM doc");
+        var format = new OpenLayers.Format.KML(options);
+        var features = format.read(kmlExpected);
+        var kmlOut = format.write(features);
+        t.eq(kmlOut, kmlExpected, "correctly writes an KML doc string");
     }
 
     </script> 



More information about the Commits mailing list