1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Format/XML.js
7 * @requires OpenLayers/Feature/Vector.js
8 * @requires OpenLayers/Geometry/Point.js
9 * @requires OpenLayers/Geometry/MultiPoint.js
10 * @requires OpenLayers/Geometry/LineString.js
11 * @requires OpenLayers/Geometry/MultiLineString.js
12 * @requires OpenLayers/Geometry/Polygon.js
13 * @requires OpenLayers/Geometry/MultiPolygon.js
14 * @requires OpenLayers/Console.js
18 * Class: OpenLayers.Format.GML
19 * Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML>
20 * constructor. Supports the GML simple features profile.
23 * - <OpenLayers.Format>
25 OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
28 * APIProperty: featureNS
29 * {String} Namespace used for feature attributes. Default is
30 * "http://mapserver.gis.umn.edu/mapserver".
32 featureNS: "http://mapserver.gis.umn.edu/mapserver",
35 * APIProperty: featurePrefix
36 * {String} Namespace alias (or prefix) for feature nodes. Default is
39 featurePrefix: "feature",
42 * APIProperty: featureName
43 * {String} Element name for features. Default is "featureMember".
45 featureName: "featureMember",
48 * APIProperty: layerName
49 * {String} Name of data layer. Default is "features".
51 layerName: "features",
54 * APIProperty: geometryName
55 * {String} Name of geometry element. Defaults to "geometry".
57 geometryName: "geometry",
60 * APIProperty: collectionName
61 * {String} Name of featureCollection element.
63 collectionName: "FeatureCollection",
67 * {String} GML Namespace.
69 gmlns: "http://www.opengis.net/gml",
72 * APIProperty: extractAttributes
73 * {Boolean} Extract attributes from GML.
75 extractAttributes: true,
79 * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
80 * Changing is not recommended, a new Format should be instantiated.
85 * Constructor: OpenLayers.Format.GML
86 * Create a new parser for GML.
89 * options - {Object} An optional object whose properties will be set on
92 initialize: function(options) {
93 // compile regular expressions once instead of every time they are used
95 trimSpace: (/^\s*|\s*$/g),
96 removeSpace: (/\s*/g),
98 trimComma: (/\s*,\s*/g)
100 OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
105 * Read data from a string, and return a list of features.
108 * data - {String} or {DOMElement} data to read/parse.
111 * {Array(<OpenLayers.Feature.Vector>)} An array of features.
113 read: function(data) {
114 if(typeof data == "string") {
115 data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
117 var featureNodes = this.getElementsByTagNameNS(data.documentElement,
121 for(var i=0; i<featureNodes.length; i++) {
122 var feature = this.parseFeature(featureNodes[i]);
124 features.push(feature);
131 * Method: parseFeature
132 * This function is the core of the GML parsing code in OpenLayers.
133 * It creates the geometries that are then attached to the returned
134 * feature, and calls parseAttributes() to get attribute data out.
137 * node - {DOMElement} A GML feature node.
139 parseFeature: function(node) {
140 // only accept one geometry per feature - look for highest "order"
141 var order = ["MultiPolygon", "Polygon",
142 "MultiLineString", "LineString",
143 "MultiPoint", "Point", "Envelope", "Box"];
144 var type, nodeList, geometry, parser;
145 for(var i=0; i<order.length; ++i) {
147 nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
148 if(nodeList.length > 0) {
149 // only deal with first geometry of this type
150 var parser = this.parseGeometry[type.toLowerCase()];
152 geometry = parser.apply(this, [nodeList[0]]);
153 if (this.internalProjection && this.externalProjection) {
154 geometry.transform(this.externalProjection,
155 this.internalProjection);
158 OpenLayers.Console.error(OpenLayers.i18n(
159 "unsupportedGeometryType", {'geomType':type}));
161 // stop looking for different geometry types
166 // construct feature (optionally with attributes)
168 if(this.extractAttributes) {
169 attributes = this.parseAttributes(node);
171 var feature = new OpenLayers.Feature.Vector(geometry, attributes);
174 featureType: node.firstChild.nodeName.split(":")[1],
175 featureNS: node.firstChild.namespaceURI,
176 featureNSPrefix: node.firstChild.prefix
179 // assign fid - this can come from a "fid" or "id" attribute
180 var childNode = node.firstChild;
183 if(childNode.nodeType == 1) {
184 fid = childNode.getAttribute("fid") ||
185 childNode.getAttribute("id");
190 childNode = childNode.nextSibling;
197 * Property: parseGeometry
198 * Properties of this object are the functions that parse geometries based
204 * Method: parseGeometry.point
205 * Given a GML node representing a point geometry, create an OpenLayers
209 * node - {DOMElement} A GML node.
212 * {<OpenLayers.Geometry.Point>} A point geometry.
214 point: function(node) {
216 * Three coordinate variations to consider:
217 * 1) <gml:pos>x y z</gml:pos>
218 * 2) <gml:coordinates>x, y, z</gml:coordinates>
219 * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
221 var nodeList, coordString;
224 // look for <gml:pos>
225 var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
226 if(nodeList.length > 0) {
227 coordString = nodeList[0].firstChild.nodeValue;
228 coordString = coordString.replace(this.regExes.trimSpace, "");
229 coords = coordString.split(this.regExes.splitSpace);
232 // look for <gml:coordinates>
233 if(coords.length == 0) {
234 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
236 if(nodeList.length > 0) {
237 coordString = nodeList[0].firstChild.nodeValue;
238 coordString = coordString.replace(this.regExes.removeSpace,
240 coords = coordString.split(",");
244 // look for <gml:coord>
245 if(coords.length == 0) {
246 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
248 if(nodeList.length > 0) {
249 var xList = this.getElementsByTagNameNS(nodeList[0],
251 var yList = this.getElementsByTagNameNS(nodeList[0],
253 if(xList.length > 0 && yList.length > 0) {
254 coords = [xList[0].firstChild.nodeValue,
255 yList[0].firstChild.nodeValue];
260 // preserve third dimension
261 if(coords.length == 2) {
266 return new OpenLayers.Geometry.Point(coords[0], coords[1],
270 return new OpenLayers.Geometry.Point(coords[1], coords[0],
276 * Method: parseGeometry.multipoint
277 * Given a GML node representing a multipoint geometry, create an
278 * OpenLayers multipoint geometry.
281 * node - {DOMElement} A GML node.
284 * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
286 multipoint: function(node) {
287 var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
290 if(nodeList.length > 0) {
292 for(var i=0; i<nodeList.length; ++i) {
293 point = this.parseGeometry.point.apply(this, [nodeList[i]]);
295 components.push(point);
299 return new OpenLayers.Geometry.MultiPoint(components);
303 * Method: parseGeometry.linestring
304 * Given a GML node representing a linestring geometry, create an
305 * OpenLayers linestring geometry.
308 * node - {DOMElement} A GML node.
311 * {<OpenLayers.Geometry.LineString>} A linestring geometry.
313 linestring: function(node, ring) {
315 * Two coordinate variations to consider:
316 * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
317 * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
319 var nodeList, coordString;
323 // look for <gml:posList>
324 nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
325 if(nodeList.length > 0) {
326 coordString = this.getChildValue(nodeList[0]);
327 coordString = coordString.replace(this.regExes.trimSpace, "");
328 coords = coordString.split(this.regExes.splitSpace);
329 var dim = parseInt(nodeList[0].getAttribute("dimension"));
331 for(var i=0; i<coords.length/dim; ++i) {
335 z = (dim == 2) ? null : coords[j+2];
337 points.push(new OpenLayers.Geometry.Point(x, y, z));
339 points.push(new OpenLayers.Geometry.Point(y, x, z));
344 // look for <gml:coordinates>
345 if(coords.length == 0) {
346 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
348 if(nodeList.length > 0) {
349 coordString = this.getChildValue(nodeList[0]);
350 coordString = coordString.replace(this.regExes.trimSpace,
352 coordString = coordString.replace(this.regExes.trimComma,
354 var pointList = coordString.split(this.regExes.splitSpace);
355 for(var i=0; i<pointList.length; ++i) {
356 coords = pointList[i].split(",");
357 if(coords.length == 2) {
361 points.push(new OpenLayers.Geometry.Point(coords[0],
365 points.push(new OpenLayers.Geometry.Point(coords[1],
374 if(points.length != 0) {
376 line = new OpenLayers.Geometry.LinearRing(points);
378 line = new OpenLayers.Geometry.LineString(points);
385 * Method: parseGeometry.multilinestring
386 * Given a GML node representing a multilinestring geometry, create an
387 * OpenLayers multilinestring geometry.
390 * node - {DOMElement} A GML node.
393 * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
395 multilinestring: function(node) {
396 var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
399 if(nodeList.length > 0) {
401 for(var i=0; i<nodeList.length; ++i) {
402 line = this.parseGeometry.linestring.apply(this,
405 components.push(line);
409 return new OpenLayers.Geometry.MultiLineString(components);
413 * Method: parseGeometry.polygon
414 * Given a GML node representing a polygon geometry, create an
415 * OpenLayers polygon geometry.
418 * node - {DOMElement} A GML node.
421 * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
423 polygon: function(node) {
424 var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
427 if(nodeList.length > 0) {
428 // this assumes exterior ring first, inner rings after
430 for(var i=0; i<nodeList.length; ++i) {
431 ring = this.parseGeometry.linestring.apply(this,
432 [nodeList[i], true]);
434 components.push(ring);
438 return new OpenLayers.Geometry.Polygon(components);
442 * Method: parseGeometry.multipolygon
443 * Given a GML node representing a multipolygon geometry, create an
444 * OpenLayers multipolygon geometry.
447 * node - {DOMElement} A GML node.
450 * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
452 multipolygon: function(node) {
453 var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
456 if(nodeList.length > 0) {
458 for(var i=0; i<nodeList.length; ++i) {
459 polygon = this.parseGeometry.polygon.apply(this,
462 components.push(polygon);
466 return new OpenLayers.Geometry.MultiPolygon(components);
469 envelope: function(node) {
474 var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
475 if (lpoint.length > 0) {
478 if(lpoint.length > 0) {
479 coordString = lpoint[0].firstChild.nodeValue;
480 coordString = coordString.replace(this.regExes.trimSpace, "");
481 coords = coordString.split(this.regExes.splitSpace);
484 if(coords.length == 2) {
488 var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
490 var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
494 var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
495 if (upoint.length > 0) {
498 if(upoint.length > 0) {
499 coordString = upoint[0].firstChild.nodeValue;
500 coordString = coordString.replace(this.regExes.trimSpace, "");
501 coords = coordString.split(this.regExes.splitSpace);
504 if(coords.length == 2) {
508 var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
510 var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
514 if (lowerPoint && upperPoint) {
515 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
516 components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
517 components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
518 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
519 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
521 var ring = new OpenLayers.Geometry.LinearRing(components);
522 envelope = new OpenLayers.Geometry.Polygon([ring]);
529 * Method: parseAttributes
532 * node - {<DOMElement>}
535 * {Object} An attributes object.
537 parseAttributes: function(node) {
539 // assume attributes are children of the first type 1 child
540 var childNode = node.firstChild;
541 var children, i, child, grandchildren, grandchild, name, value;
543 if(childNode.nodeType == 1) {
544 // attributes are type 1 children with one type 3 child
545 children = childNode.childNodes;
546 for(i=0; i<children.length; ++i) {
548 if(child.nodeType == 1) {
549 grandchildren = child.childNodes;
550 if(grandchildren.length == 1) {
551 grandchild = grandchildren[0];
552 if(grandchild.nodeType == 3 ||
553 grandchild.nodeType == 4) {
554 name = (child.prefix) ?
555 child.nodeName.split(":")[1] :
557 value = grandchild.nodeValue.replace(
558 this.regExes.trimSpace, "");
559 attributes[name] = value;
562 // If child has no childNodes (grandchildren),
563 // set an attribute with null value.
564 // e.g. <prefix:fieldname/> becomes
566 attributes[child.nodeName.split(":").pop()] = null;
572 childNode = childNode.nextSibling;
579 * Generate a GML document string given a list of features.
582 * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
583 * serialize into a string.
586 * {String} A string representing the GML document.
588 write: function(features) {
589 if(!(features instanceof Array)) {
590 features = [features];
592 var gml = this.createElementNS("http://www.opengis.net/wfs",
593 "wfs:" + this.collectionName);
594 for(var i=0; i<features.length; i++) {
595 gml.appendChild(this.createFeatureXML(features[i]));
597 return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
601 * Method: createFeatureXML
602 * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
605 * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
608 * {DOMElement} A node reprensting the feature in GML.
610 createFeatureXML: function(feature) {
611 var geometry = feature.geometry;
612 var geometryNode = this.buildGeometryNode(geometry);
613 var geomContainer = this.createElementNS(this.featureNS,
614 this.featurePrefix + ":" +
616 geomContainer.appendChild(geometryNode);
617 var featureNode = this.createElementNS(this.gmlns,
618 "gml:" + this.featureName);
619 var featureContainer = this.createElementNS(this.featureNS,
620 this.featurePrefix + ":" +
622 var fid = feature.fid || feature.id;
623 featureContainer.setAttribute("fid", fid);
624 featureContainer.appendChild(geomContainer);
625 for(var attr in feature.attributes) {
626 var attrText = this.createTextNode(feature.attributes[attr]);
627 var nodename = attr.substring(attr.lastIndexOf(":") + 1);
628 var attrContainer = this.createElementNS(this.featureNS,
629 this.featurePrefix + ":" +
631 attrContainer.appendChild(attrText);
632 featureContainer.appendChild(attrContainer);
634 featureNode.appendChild(featureContainer);
639 * APIMethod: buildGeometryNode
641 buildGeometryNode: function(geometry) {
642 if (this.externalProjection && this.internalProjection) {
643 geometry = geometry.clone();
644 geometry.transform(this.internalProjection,
645 this.externalProjection);
647 var className = geometry.CLASS_NAME;
648 var type = className.substring(className.lastIndexOf(".") + 1);
649 var builder = this.buildGeometry[type.toLowerCase()];
650 return builder.apply(this, [geometry]);
654 * Property: buildGeometry
655 * Object containing methods to do the actual geometry node building
656 * based on geometry type.
659 // TBD retrieve the srs from layer
660 // srsName is non-standard, so not including it until it's right.
661 // gml.setAttribute("srsName",
662 // "http://www.opengis.net/gml/srs/epsg.xml#4326");
665 * Method: buildGeometry.point
666 * Given an OpenLayers point geometry, create a GML point.
669 * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
672 * {DOMElement} A GML point node.
674 point: function(geometry) {
675 var gml = this.createElementNS(this.gmlns, "gml:Point");
676 gml.appendChild(this.buildCoordinatesNode(geometry));
681 * Method: buildGeometry.multipoint
682 * Given an OpenLayers multipoint geometry, create a GML multipoint.
685 * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
688 * {DOMElement} A GML multipoint node.
690 multipoint: function(geometry) {
691 var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
692 var points = geometry.components;
693 var pointMember, pointGeom;
694 for(var i=0; i<points.length; i++) {
695 pointMember = this.createElementNS(this.gmlns,
697 pointGeom = this.buildGeometry.point.apply(this,
699 pointMember.appendChild(pointGeom);
700 gml.appendChild(pointMember);
706 * Method: buildGeometry.linestring
707 * Given an OpenLayers linestring geometry, create a GML linestring.
710 * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
713 * {DOMElement} A GML linestring node.
715 linestring: function(geometry) {
716 var gml = this.createElementNS(this.gmlns, "gml:LineString");
717 gml.appendChild(this.buildCoordinatesNode(geometry));
722 * Method: buildGeometry.multilinestring
723 * Given an OpenLayers multilinestring geometry, create a GML
727 * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
731 * {DOMElement} A GML multilinestring node.
733 multilinestring: function(geometry) {
734 var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
735 var lines = geometry.components;
736 var lineMember, lineGeom;
737 for(var i=0; i<lines.length; ++i) {
738 lineMember = this.createElementNS(this.gmlns,
739 "gml:lineStringMember");
740 lineGeom = this.buildGeometry.linestring.apply(this,
742 lineMember.appendChild(lineGeom);
743 gml.appendChild(lineMember);
749 * Method: buildGeometry.linearring
750 * Given an OpenLayers linearring geometry, create a GML linearring.
753 * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
756 * {DOMElement} A GML linearring node.
758 linearring: function(geometry) {
759 var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
760 gml.appendChild(this.buildCoordinatesNode(geometry));
765 * Method: buildGeometry.polygon
766 * Given an OpenLayers polygon geometry, create a GML polygon.
769 * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
772 * {DOMElement} A GML polygon node.
774 polygon: function(geometry) {
775 var gml = this.createElementNS(this.gmlns, "gml:Polygon");
776 var rings = geometry.components;
777 var ringMember, ringGeom, type;
778 for(var i=0; i<rings.length; ++i) {
779 type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
780 ringMember = this.createElementNS(this.gmlns,
782 ringGeom = this.buildGeometry.linearring.apply(this,
784 ringMember.appendChild(ringGeom);
785 gml.appendChild(ringMember);
791 * Method: buildGeometry.multipolygon
792 * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
795 * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
799 * {DOMElement} A GML multipolygon node.
801 multipolygon: function(geometry) {
802 var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
803 var polys = geometry.components;
804 var polyMember, polyGeom;
805 for(var i=0; i<polys.length; ++i) {
806 polyMember = this.createElementNS(this.gmlns,
807 "gml:polygonMember");
808 polyGeom = this.buildGeometry.polygon.apply(this,
810 polyMember.appendChild(polyGeom);
811 gml.appendChild(polyMember);
818 * Method: buildGeometry.bounds
819 * Given an OpenLayers bounds, create a GML box.
822 * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
825 * {DOMElement} A GML box node.
827 bounds: function(bounds) {
828 var gml = this.createElementNS(this.gmlns, "gml:Box");
829 gml.appendChild(this.buildCoordinatesNode(bounds));
835 * Method: buildCoordinates
836 * builds the coordinates XmlNode
838 * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
841 * geometry - {<OpenLayers.Geometry>}
844 * {XmlNode} created xmlNode
846 buildCoordinatesNode: function(geometry) {
847 var coordinatesNode = this.createElementNS(this.gmlns,
849 coordinatesNode.setAttribute("decimal", ".");
850 coordinatesNode.setAttribute("cs", ",");
851 coordinatesNode.setAttribute("ts", " ");
855 if(geometry instanceof OpenLayers.Bounds){
856 parts.push(geometry.left + "," + geometry.bottom);
857 parts.push(geometry.right + "," + geometry.top);
859 var points = (geometry.components) ? geometry.components : [geometry];
860 for(var i=0; i<points.length; i++) {
861 parts.push(points[i].x + "," + points[i].y);
865 var txtNode = this.createTextNode(parts.join(" "));
866 coordinatesNode.appendChild(txtNode);
868 return coordinatesNode;
871 CLASS_NAME: "OpenLayers.Format.GML"