define(function () { "use strict"; function JsonGMLParser(extractAttributes, xy, gmlnsm, wfsnsm, featureName, geometryAttribute) { this._extractAttributes = extractAttributes; this._featureName = featureName || "featureMember"; this._xy = xy; this._gmlns = gmlnsm || "http://www.opengis.net/gml", this._wfs = wfsnsm || "http://www.opengis.net/wfs", this._geometryAttribute = geometryAttribute; // 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) }; } Object.defineProperties(JsonGMLParser.prototype, { extractAttributes: { get: function () { return this._extractAttributes; }, set: function (value) { this._extractAttributes = value; } }, xy: { get: function () { return this._xy; }, } }); JsonGMLParser.prototype.read = function (data) { if (this._wfsPrefix == undefined) { this.detectPrefixes(); } var documentElement = data["?xml"]; if (documentElement == undefined) documentElement = data[this._wfsPrefix + "FeatureCollection"]; else documentElement = data["?xml"][this._wfsPrefix + "FeatureCollection"]; if (documentElement == undefined) { throw "Invalid GML format. Could not find root element"; } var featureNodes = documentElement[this._gmlPrefix + this._featureName] || []; var features = []; var objName = this._featureName; if (featureNodes.length == undefined) { var arr = Object.keys(featureNodes).map(function (key) { return { objName: featureNodes[key] }; }); featureNodes = arr; } for (var i = 0; i < featureNodes.length; i++) { var featureNode = featureNodes[i]; var typeName = Object.getOwnPropertyNames(featureNode)[0]; var feature = this.parseFeature(featureNode[typeName]); if (feature) { features.push(feature); } } return features; }; JsonGMLParser.prototype.detectPrefixes = function (data) { this._wfsPrefix = "wfs:"; this._gmlPrefix = "gml:"; } JsonGMLParser.prototype.detectGeometryAttribute = function (data) { this._geometryAttribute = "Geom"; } /** * 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. */ function containGeometryAttribute(propName, geometryAttribute) { var index = propName.indexOf(':' + geometryAttribute); return (index > 0 && index == propName.length - geometryAttribute.length - 1); } JsonGMLParser.prototype.parseFeature = function (node) { // only accept one geometry per feature - look for highest "order" var order = ["MultiPolygon", "Polygon", "MultiLineString", "LineString", "MultiPoint", "Point"]; // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope, // this code creates a geometry derived from the Envelope. This is not correct. if (this._geometryAttribute == null) this.detectGeometryAttribute(node); var geometry, type, parser; var attributes = {}; var nemes = ""; for (var propName in node) { if (nemes == "") { nemes = propName; } if (propName == this._geometryAttribute || containGeometryAttribute(propName, this._geometryAttribute)) { var fullType = Object.getOwnPropertyNames(node[propName])[0] type = fullType.replace(this._gmlPrefix, ""); parser = this.parseGeometry[type.toLowerCase()]; if (parser) { geometry = parser.apply(this, [node[propName][fullType]]); } else { console.log("unsupportedGeometryType:" + type); } } else if (propName != "_attributes") { attributes[propName] = node[propName].value; } } // TODO: optinally parse gml:boundedBy on feature var bounds; // construct feature (optionally with attributes) if (this.extractAttributes == false) { attributes = undefined; } var feature = { geometryType: type.toLowerCase(), positions: geometry, attributes: attributes }; feature.bounds = bounds; // assign fid - this can come from a "fid" or "id" attribute // console.log(node); //feature.fid = node["_attributes"].fid || node["_attributes"].id; if (node["_attributes"] != undefined) { feature.fid = node["_attributes"].fid || node["_attributes"].id; } else { feature.fid = node[nemes].value; } return feature; }; /** * Property: parseGeometry * Properties of this object are the functions that parse geometries based * on their type. */ JsonGMLParser.prototype.parseGeometry = { /** * Method: parseGeometry.point * Given a GML node representing a point geometry, create an OpenLayers * point geometry. * * Parameters: * node - {DOMElement} A GML node. * * Returns: * {} A point geometry. */ point: function (node) { /** * Three coordinate variations to consider: * 1) x y z * 2) x, y, z * 3) xy */ var nodeList, coordString; var coords = []; // look for if (node[this._gmlPrefix + "pos"]) { coordString = node[this._gmlPrefix + "pos"].value; coordString = coordString.replace(this._regExes.trimSpace, ""); coords = coordString.split(this._regExes.splitSpace); } // look for else if (node[this._gmlPrefix + "coordinates"]) { coordString = node[this._gmlPrefix + "coordinates"].value; coordString = coordString.replace(this._regExes.removeSpace, ""); coords = coordString.split(","); } // look for else if (node[this._gmlPrefix + "coord"]) { var xVal = node[this._gmlPrefix + "coord"][this._gmlPrefix + "X"].value; var yVal = node[this._gmlPrefix + "coord"][this._gmlPrefix + "Y"].value; if (xVal != undefined && yVal != undefined) { coords = [xVal, yVal]; } } // preserve third dimension if (coords.length == 2) { coords[2] = null; } if (this.xy) { return [parseFloat(coords[0]), parseFloat(coords[1]), parseFloat(coords[2])]; } else { return [parseFloat(coords[1]), parseFloat(coords[0]), parseFloat(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: * {} A multipoint geometry. */ multipoint: function (node) { var nodeList = node[this._gmlPrefix + "pointMember"]; var components = []; if (nodeList.length == undefined) nodeList = [nodeList]; if (nodeList.length > 0) { var point; for (var i = 0; i < nodeList.length; ++i) { point = this.parseGeometry.point.apply(this, [nodeList[i][this._gmlPrefix + "Point"]]); if (point) { components.push(point); } } } return components; }, /** * Method: parseGeometry.linestring * Given a GML node representing a linestring geometry, create an * OpenLayers linestring geometry. * * Parameters: * node - {DOMElement} A GML node. * * Returns: * {} A linestring geometry. */ linestring: function (node) { /** * Two coordinate variations to consider: * 1) x0 y0 z0 x1 y1 z1 * 2) x0, y0, z0 x1, y1, z1 */ var nodeList, coordString; var coords = []; var points = []; // look for if (node[this._gmlPrefix + "posList"]) { coordString = node[this._gmlPrefix + "posList"].value; coordString = coordString.replace(this._regExes.trimSpace, ""); coords = coordString.split(this._regExes.splitSpace); var dim = node[this._gmlPrefix + "posList"]._attributes["dimension"]; var j, x, y, z; for (var i = 0; i < coords.length / dim; ++i) { j = i * dim; x = parseFloat(coords[j]); y = parseFloat(coords[j + 1]); z = (dim == 2) ? null : parseFloat(coords[j + 2]); if (this.xy) { points.push(x, y, z); } else { points.push(y, x, z); } } } // look for else if (node[this._gmlPrefix + "coordinates"]) { coordString = node[this._gmlPrefix + "coordinates"].value; 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(parseFloat(coords[0]), parseFloat(coords[1]), parseFloat(coords[2])); } else { points.push(parseFloat(coords[1]), parseFloat(coords[0]), parseFloat(coords[2])); } } } return points; }, /** * Method: parseGeometry.multilinestring * Given a GML node representing a multilinestring geometry, create an * OpenLayers multilinestring geometry. * * Parameters: * node - {DOMElement} A GML node. * * Returns: * {} A multilinestring geometry. */ multilinestring: function (node) { var nodeList = node[this._gmlPrefix + "lineStringMember"]; var components = []; if (nodeList.length == undefined) nodeList = [nodeList]; if (nodeList.length > 0) { var line; for (var i = 0; i < nodeList.length; ++i) { line = this.parseGeometry.linestring.apply(this, [nodeList[i][this._gmlPrefix + "LineString"]]); if (line) { components.push(line); } } } return components; }, /** * Method: parseGeometry.polygon * Given a GML node representing a polygon geometry, create an * OpenLayers polygon geometry. * * Parameters: * node - {DOMElement} A GML node. * * Returns: * {} A polygon geometry. */ polygon: function (node) { var rings = [node[this._gmlPrefix + "outerBoundaryIs"][this._gmlPrefix + "LinearRing"]]; if (node[this._gmlPrefix + "innerBoundaryIs"]) { for (var i = 0; i < node[this._gmlPrefix + "innerBoundaryIs"].length; i++) rings.push(node[this._gmlPrefix + "innerBoundaryIs"][i][this._gmlPrefix + "LinearRing"]); } var components = []; if (rings.length > 0) { // this assumes exterior ring first, inner rings after var ring; for (var i = 0; i < rings.length; ++i) { ring = this.parseGeometry.linestring.apply(this, [rings[i]]); if (ring) { components.push(ring); } } } return components; }, /** * Method: parseGeometry.multipolygon * Given a GML node representing a multipolygon geometry, create an * OpenLayers multipolygon geometry. * * Parameters: * node - {DOMElement} A GML node. * * Returns: * {} A multipolygon geometry. */ multipolygon: function (node) { var nodeList = node[this._gmlPrefix + "polygonMember"]; var components = []; if (nodeList.length == undefined) nodeList = [nodeList]; if (nodeList.length > 0) { var polygon; for (var i = 0; i < nodeList.length; ++i) { polygon = this.parseGeometry.polygon.apply(this, [nodeList[i][this._gmlPrefix + "Polygon"]]); if (polygon) { components.push(polygon); } } } return components; }, }; return JsonGMLParser; });