Files
openlayers/lib/OpenLayers/Format/GML.js
crschmidt 6d6c08fbef Formats now support reprojection using internalProjection and
externalProjection properties. These allow for the reprojection of data --
OpenLayers users with SphericalMercator get this built in for EPSG:900913, and
other users can use the external proj4js library available from MapBuilder SVN
to add support for any number of projections. This means that featres can be,
for example, transformed from a KML doc in 4326 to Spherical Mercator before
being added to a layer, making using SphericalMercator slightly more enticing. 
r=elemoine
(Closes #1039)


git-svn-id: http://svn.openlayers.org/trunk/openlayers@5516 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
2007-12-19 22:07:12 +00:00

834 lines
31 KiB
JavaScript

/* Copyright (c) 2006-2007 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Feature/Vector.js
* @requires OpenLayers/Geometry/Point.js
* @requires OpenLayers/Geometry/MultiPoint.js
* @requires OpenLayers/Geometry/LineString.js
* @requires OpenLayers/Geometry/MultiLineString.js
* @requires OpenLayers/Geometry/Polygon.js
* @requires OpenLayers/Geometry/MultiPolygon.js
*
* Class: OpenLayers.Format.GML
* Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML>
* constructor. Supports the GML simple features profile.
*
* Inherits from:
* - <OpenLayers.Format>
*/
OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
/*
* APIProperty: featureNS
* {String} Namespace used for feature attributes. Default is
* "http://mapserver.gis.umn.edu/mapserver".
*/
featureNS: "http://mapserver.gis.umn.edu/mapserver",
/**
* APIProperty: featurePrefix
* {String} Namespace alias (or prefix) for feature nodes. Default is
* "feature".
*/
featurePrefix: "feature",
/*
* APIProperty: featureName
* {String} Element name for features. Default is "featureMember".
*/
featureName: "featureMember",
/*
* APIProperty: layerName
* {String} Name of data layer. Default is "features".
*/
layerName: "features",
/**
* APIProperty: geometry
* {String} Name of geometry element. Defaults to "geometry".
*/
geometryName: "geometry",
/**
* APIProperty: collectionName
* {String} Name of featureCollection element.
*/
collectionName: "FeatureCollection",
/**
* APIProperty: gmlns
* {String} GML Namespace.
*/
gmlns: "http://www.opengis.net/gml",
/**
* APIProperty: extractAttributes
* {Boolean} Extract attributes from GML.
*/
extractAttributes: true,
/**
* APIProperty: xy
* {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
* Changing is not recommended, a new Format should be instantiated.
*/
xy: true,
/**
* Constructor: OpenLayers.Format.GML
* Create a new parser for GML.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(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]);
},
/**
* APIMethod: read
* Read data from a string, and return a list of features.
*
* Parameters:
* data - {String} or {DOMElement} data to read/parse.
*
* Returns:
* {Array(<OpenLayers.Feature.Vector>)} An array of features.
*/
read: function(data) {
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var featureNodes = this.getElementsByTagNameNS(data.documentElement,
this.gmlns,
this.featureName);
var features = [];
for(var i=0; i<featureNodes.length; i++) {
var feature = this.parseFeature(featureNodes[i]);
if(feature) {
features.push(feature);
}
}
return features;
},
/**
* Method: parseFeature
* This function is the core of the GML 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} A GML feature node.
*/
parseFeature: function(node) {
// only accept on geometry per feature - look for highest "order"
var order = ["MultiPolygon", "Polygon",
"MultiLineString", "LineString",
"MultiPoint", "Point", "Envelope"];
var type, nodeList, geometry, parser;
for(var i=0; i<order.length; ++i) {
type = order[i];
nodeList = this.getElementsByTagNameNS(node, this.gmlns, 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]]);
if (this.internalProjection && this.externalProjection) {
geometry.transform(this.externalProjection,
this.internalProjection);
}
} else {
OpenLayers.Console.error("Unsupported geometry type: " +
type);
}
// stop looking for different geometry types
break;
}
}
// construct feature (optionally with attributes)
var attributes;
if(this.extractAttributes) {
attributes = this.parseAttributes(node);
}
var feature = new OpenLayers.Feature.Vector(geometry, attributes);
// assign fid - this can come from a "fid" or "id" attribute
var childNode = node.firstChild;
var fid;
while(childNode) {
if(childNode.nodeType == 1) {
fid = childNode.getAttribute("fid") ||
childNode.getAttribute("id");
if(fid) {
break;
}
}
childNode = childNode.nextSibling;
}
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 GML node representing a point geometry, create an OpenLayers
* point geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.Point>} A point geometry.
*/
point: function(node) {
/**
* Three coordinate variations to consider:
* 1) <gml:pos>x y z</gml:pos>
* 2) <gml:coordinates>x, y, z</gml:coordinates>
* 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
*/
var nodeList, coordString;
var coords = [];
// look for <gml:pos>
var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
if(nodeList.length > 0) {
coordString = nodeList[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
// look for <gml:coordinates>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coordinates");
if(nodeList.length > 0) {
coordString = nodeList[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.removeSpace,
"");
coords = coordString.split(",");
}
}
// look for <gml:coord>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coord");
if(nodeList.length > 0) {
var xList = this.getElementsByTagNameNS(nodeList[0],
this.gmlns, "X");
var yList = this.getElementsByTagNameNS(nodeList[0],
this.gmlns, "Y");
if(xList.length > 0 && yList.length > 0) {
coords = [xList[0].firstChild.nodeValue,
yList[0].firstChild.nodeValue];
}
}
}
// preserve third dimension
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
return new OpenLayers.Geometry.Point(coords[0], coords[1],
coords[2]);
}
else{
return new OpenLayers.Geometry.Point(coords[1], coords[0],
coords[2]);
}
},
/**
* Method: parseGeometry.multipoint
* Given a GML node representing a multipoint geometry, create an
* OpenLayers multipoint geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
*/
multipoint: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"Point");
var components = [];
if(nodeList.length > 0) {
var point;
for(var i=0; i<nodeList.length; ++i) {
point = this.parseGeometry.point.apply(this, [nodeList[i]]);
if(point) {
components.push(point);
}
}
}
return new OpenLayers.Geometry.MultiPoint(components);
},
/**
* Method: parseGeometry.linestring
* Given a GML node representing a linestring geometry, create an
* OpenLayers linestring geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.LineString>} A linestring geometry.
*/
linestring: function(node, ring) {
/**
* Two coordinate variations to consider:
* 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
* 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
*/
var nodeList, coordString;
var coords = [];
var points = [];
// look for <gml:posList>
nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
if(nodeList.length > 0) {
coordString = this.concatChildValues(nodeList[0]);
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
var dim = parseInt(nodeList[0].getAttribute("dimension"));
var j, x, y, z;
for(var i=0; i<coords.length/dim; ++i) {
j = i * dim;
x = coords[j];
y = coords[j+1];
z = (dim == 2) ? null : coords[j+2];
if (this.xy) {
points.push(new OpenLayers.Geometry.Point(x, y, z));
} else {
points.push(new OpenLayers.Geometry.Point(y, x, z));
}
}
}
// look for <gml:coordinates>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coordinates");
if(nodeList.length > 0) {
coordString = this.concatChildValues(nodeList[0]);
coordString = coordString.replace(this.regExes.trimSpace,
"");
coordString = coordString.replace(this.regExes.trimComma,
",");
var pointList = coordString.split(this.regExes.splitSpace);
for(var i=0; i<pointList.length; ++i) {
coords = pointList[i].split(",");
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
points.push(new OpenLayers.Geometry.Point(coords[0],
coords[1],
coords[2]));
} else {
points.push(new OpenLayers.Geometry.Point(coords[1],
coords[0],
coords[2]));
}
}
}
}
var line = null;
if(points.length != 0) {
if(ring) {
line = new OpenLayers.Geometry.LinearRing(points);
} else {
line = new OpenLayers.Geometry.LineString(points);
}
}
return line;
},
/**
* Method: parseGeometry.multilinestring
* Given a GML node representing a multilinestring geometry, create an
* OpenLayers multilinestring geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
*/
multilinestring: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"LineString");
var components = [];
if(nodeList.length > 0) {
var line;
for(var i=0; i<nodeList.length; ++i) {
line = this.parseGeometry.linestring.apply(this,
[nodeList[i]]);
if(line) {
components.push(line);
}
}
}
return new OpenLayers.Geometry.MultiLineString(components);
},
/**
* Method: parseGeometry.polygon
* Given a GML node representing a polygon geometry, create an
* OpenLayers polygon geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.Polygon>} A polygon geometry.
*/
polygon: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"LinearRing");
var components = [];
if(nodeList.length > 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.push(ring);
}
}
}
return new OpenLayers.Geometry.Polygon(components);
},
/**
* Method: parseGeometry.multipolygon
* Given a GML node representing a multipolygon geometry, create an
* OpenLayers multipolygon geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
*/
multipolygon: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"Polygon");
var components = [];
if(nodeList.length > 0) {
var polygon;
for(var i=0; i<nodeList.length; ++i) {
polygon = this.parseGeometry.polygon.apply(this,
[nodeList[i]]);
if(polygon) {
components.push(polygon);
}
}
}
return new OpenLayers.Geometry.MultiPolygon(components);
},
envelope: function(node) {
var components = [];
var coordString;
var envelope;
var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
if (lpoint.length > 0) {
var coords = [];
if(lpoint.length > 0) {
coordString = lpoint[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
} else {
var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
}
}
var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
if (upoint.length > 0) {
var coords = [];
if(upoint.length > 0) {
coordString = upoint[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
} else {
var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
}
}
if (lowerPoint && upperPoint) {
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
var ring = new OpenLayers.Geometry.LinearRing(components);
envelope = new OpenLayers.Geometry.Polygon([ring]);
}
return envelope;
}
},
/**
* Method: parseAttributes
*
* Parameters:
* node - {<DOMElement>}
*
* Returns:
* {Object} An attributes object.
*/
parseAttributes: function(node) {
var attributes = {};
// assume attributes are children of the first type 1 child
var childNode = node.firstChild;
var children, i, child, grandchildren, grandchild, name, value;
while(childNode) {
if(childNode.nodeType == 1) {
// attributes are type 1 children with one type 3 child
children = childNode.childNodes;
for(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 ||
grandchild.nodeType == 4) {
name = (child.prefix) ?
child.nodeName.split(":")[1] :
child.nodeName;
value = grandchild.nodeValue.replace(
this.regExes.trimSpace, "");
attributes[name] = value;
}
}
}
}
break;
}
childNode = childNode.nextSibling;
}
return attributes;
},
/**
* APIMethod: write
* Generate a GML document string given a list of features.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)} List of features to
* serialize into a string.
*
* Returns:
* {String} A string representing the GML document.
*/
write: function(features) {
if(!(features instanceof Array)) {
features = [features];
}
var gml = this.createElementNS("http://www.opengis.net/wfs",
"wfs:" + this.collectionName);
for(var i=0; i<features.length; i++) {
gml.appendChild(this.createFeatureXML(features[i]));
}
return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
},
/**
* Method: createFeatureXML
* Accept an OpenLayers.Feature.Vector, and build a GML node for it.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
*
* Returns:
* {DOMElement} A node reprensting the feature in GML.
*/
createFeatureXML: function(feature) {
var geometry = feature.geometry;
var geometryNode = this.buildGeometryNode(geometry);
var geomContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
this.geometryName);
geomContainer.appendChild(geometryNode);
var featureNode = this.createElementNS(this.gmlns,
"gml:" + this.featureName);
var featureContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
this.layerName);
var fid = feature.fid || feature.id;
featureContainer.setAttribute("fid", fid);
featureContainer.appendChild(geomContainer);
for(var attr in feature.attributes) {
var attrText = this.createTextNode(feature.attributes[attr]);
var nodename = attr.substring(attr.lastIndexOf(":") + 1);
var attrContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
nodename);
attrContainer.appendChild(attrText);
featureContainer.appendChild(attrContainer);
}
featureNode.appendChild(featureContainer);
return featureNode;
},
/**
* APIMethod: buildGeometryNode
*/
buildGeometryNode: function(geometry) {
if (this.externalProjection && this.internalProjection) {
geometry = geometry.clone();
geometry.transform(this.internalProjection,
this.externalProjection);
}
var className = geometry.CLASS_NAME;
var type = className.substring(className.lastIndexOf(".") + 1);
var builder = this.buildGeometry[type.toLowerCase()];
return builder.apply(this, [geometry]);
},
/**
* Property: buildGeometry
* Object containing methods to do the actual geometry node building
* based on geometry type.
*/
buildGeometry: {
// TBD retrieve the srs from layer
// srsName is non-standard, so not including it until it's right.
// gml.setAttribute("srsName",
// "http://www.opengis.net/gml/srs/epsg.xml#4326");
/**
* Method: buildGeometry.point
* Given an OpenLayers point geometry, create a GML point.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.Point>} A point geometry.
*
* Returns:
* {DOMElement} A GML point node.
*/
point: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:Point");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.multipoint
* Given an OpenLayers multipoint geometry, create a GML multipoint.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
*
* Returns:
* {DOMElement} A GML multipoint node.
*/
multipoint: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
var points = geometry.components;
var pointMember, pointGeom;
for(var i=0; i<points.length; i++) {
pointMember = this.createElementNS(this.gmlns,
"gml:pointMember");
pointGeom = this.buildGeometry.point.apply(this,
[points[i]]);
pointMember.appendChild(pointGeom);
gml.appendChild(pointMember);
}
return gml;
},
/**
* Method: buildGeometry.linestring
* Given an OpenLayers linestring geometry, create a GML linestring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
*
* Returns:
* {DOMElement} A GML linestring node.
*/
linestring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:LineString");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.multilinestring
* Given an OpenLayers multilinestring geometry, create a GML
* multilinestring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
* geometry.
*
* Returns:
* {DOMElement} A GML multilinestring node.
*/
multilinestring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
var lines = geometry.components;
var lineMember, lineGeom;
for(var i=0; i<lines.length; ++i) {
lineMember = this.createElementNS(this.gmlns,
"gml:lineStringMember");
lineGeom = this.buildGeometry.linestring.apply(this,
[lines[i]]);
lineMember.appendChild(lineGeom);
gml.appendChild(lineMember);
}
return gml;
},
/**
* Method: buildGeometry.linearring
* Given an OpenLayers linearring geometry, create a GML linearring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
*
* Returns:
* {DOMElement} A GML linearring node.
*/
linearring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.polygon
* Given an OpenLayers polygon geometry, create a GML polygon.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
*
* Returns:
* {DOMElement} A GML polygon node.
*/
polygon: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml: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.gmlns,
"gml:" + type);
ringGeom = this.buildGeometry.linearring.apply(this,
[rings[i]]);
ringMember.appendChild(ringGeom);
gml.appendChild(ringMember);
}
return gml;
},
/**
* Method: buildGeometry.multipolygon
* Given an OpenLayers multipolygon geometry, create a GML multipolygon.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
* geometry.
*
* Returns:
* {DOMElement} A GML multipolygon node.
*/
multipolygon: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
var polys = geometry.components;
var polyMember, polyGeom;
for(var i=0; i<polys.length; ++i) {
polyMember = this.createElementNS(this.gmlns,
"gml:polygonMember");
polyGeom = this.buildGeometry.polygon.apply(this,
[polys[i]]);
polyMember.appendChild(polyGeom);
gml.appendChild(polyMember);
}
return gml;
}
},
/**
* Method: buildCoordinates
* builds the coordinates XmlNode
* (code)
* <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
* (end)
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {XmlNode} created xmlNode
*/
buildCoordinatesNode: function(geometry) {
var coordinatesNode = this.createElementNS(this.gmlns,
"gml:coordinates");
coordinatesNode.setAttribute("decimal", ".");
coordinatesNode.setAttribute("cs", ",");
coordinatesNode.setAttribute("ts", " ");
var points = (geometry.components) ? geometry.components : [geometry];
var parts = [];
for(var i=0; i<points.length; i++) {
parts.push(points[i].x + "," + points[i].y);
}
var txtNode = this.createTextNode(parts.join(" "));
coordinatesNode.appendChild(txtNode);
return coordinatesNode;
},
CLASS_NAME: "OpenLayers.Format.GML"
});