import Cartographic from "../Core/Cartographic.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import RuntimeError from "../Core/RuntimeError.js"; import ImageryLayerFeatureInfo from "./ImageryLayerFeatureInfo.js"; /** * Describes the format in which to request GetFeatureInfo from a Web Map Service (WMS) server. * * @alias GetFeatureInfoFormat * @constructor * * @param {String} type The type of response to expect from a GetFeatureInfo request. Valid * values are 'json', 'xml', 'html', or 'text'. * @param {String} [format] The info format to request from the WMS server. This is usually a * MIME type such as 'application/json' or text/xml'. If this parameter is not specified, the provider will request 'json' * using 'application/json', 'xml' using 'text/xml', 'html' using 'text/html', and 'text' using 'text/plain'. * @param {Function} [callback] A function to invoke with the GetFeatureInfo response from the WMS server * in order to produce an array of picked {@link ImageryLayerFeatureInfo} instances. If this parameter is not specified, * a default function for the type of response is used. */ function GetFeatureInfoFormat(type, format, callback) { //>>includeStart('debug', pragmas.debug); if (!defined(type)) { throw new DeveloperError("type is required."); } //>>includeEnd('debug'); this.type = type; if (!defined(format)) { if (type === "json") { format = "application/json"; } else if (type === "xml") { format = "text/xml"; } else if (type === "html") { format = "text/html"; } else if (type === "text") { format = "text/plain"; } //>>includeStart('debug', pragmas.debug); else { throw new DeveloperError( 'format is required when type is not "json", "xml", "html", or "text".' ); } //>>includeEnd('debug'); } this.format = format; if (!defined(callback)) { if (type === "json") { callback = geoJsonToFeatureInfo; } else if (type === "xml") { callback = xmlToFeatureInfo; } else if (type === "html") { callback = textToFeatureInfo; } else if (type === "text") { callback = textToFeatureInfo; } //>>includeStart('debug', pragmas.debug); else { throw new DeveloperError( 'callback is required when type is not "json", "xml", "html", or "text".' ); } //>>includeEnd('debug'); } this.callback = callback; } function geoJsonToFeatureInfo(json) { var result = []; var features = json.features; for (var i = 0; i < features.length; ++i) { var feature = features[i]; var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.data = feature; featureInfo.properties = feature.properties; featureInfo.configureNameFromProperties(feature.properties); featureInfo.configureDescriptionFromProperties(feature.properties); // If this is a point feature, use the coordinates of the point. if (defined(feature.geometry) && feature.geometry.type === "Point") { var longitude = feature.geometry.coordinates[0]; var latitude = feature.geometry.coordinates[1]; featureInfo.position = Cartographic.fromDegrees(longitude, latitude); } result.push(featureInfo); } return result; } var mapInfoMxpNamespace = "http://www.mapinfo.com/mxp"; var esriWmsNamespace = "http://www.esri.com/wms"; var wfsNamespace = "http://www.opengis.net/wfs"; var gmlNamespace = "http://www.opengis.net/gml"; function xmlToFeatureInfo(xml) { var documentElement = xml.documentElement; if ( documentElement.localName === "MultiFeatureCollection" && documentElement.namespaceURI === mapInfoMxpNamespace ) { // This looks like a MapInfo MXP response return mapInfoXmlToFeatureInfo(xml); } else if ( documentElement.localName === "FeatureInfoResponse" && documentElement.namespaceURI === esriWmsNamespace ) { // This looks like an Esri WMS response return esriXmlToFeatureInfo(xml); } else if ( documentElement.localName === "FeatureCollection" && documentElement.namespaceURI === wfsNamespace ) { // This looks like a WFS/GML response. return gmlToFeatureInfo(xml); } else if (documentElement.localName === "ServiceExceptionReport") { // This looks like a WMS server error, so no features picked. throw new RuntimeError( new XMLSerializer().serializeToString(documentElement) ); } else if (documentElement.localName === "msGMLOutput") { return msGmlToFeatureInfo(xml); } else { // Unknown response type, so just dump the XML itself into the description. return unknownXmlToFeatureInfo(xml); } } function mapInfoXmlToFeatureInfo(xml) { var result = []; var multiFeatureCollection = xml.documentElement; var features = multiFeatureCollection.getElementsByTagNameNS( mapInfoMxpNamespace, "Feature" ); for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) { var feature = features[featureIndex]; var properties = {}; var propertyElements = feature.getElementsByTagNameNS( mapInfoMxpNamespace, "Val" ); for ( var propertyIndex = 0; propertyIndex < propertyElements.length; ++propertyIndex ) { var propertyElement = propertyElements[propertyIndex]; if (propertyElement.hasAttribute("ref")) { var name = propertyElement.getAttribute("ref"); var value = propertyElement.textContent.trim(); properties[name] = value; } } var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.data = feature; featureInfo.properties = properties; featureInfo.configureNameFromProperties(properties); featureInfo.configureDescriptionFromProperties(properties); result.push(featureInfo); } return result; } function esriXmlToFeatureInfo(xml) { var featureInfoResponse = xml.documentElement; var result = []; var properties; var features = featureInfoResponse.getElementsByTagNameNS("*", "FIELDS"); if (features.length > 0) { // Standard esri format for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) { var feature = features[featureIndex]; properties = {}; var propertyAttributes = feature.attributes; for ( var attributeIndex = 0; attributeIndex < propertyAttributes.length; ++attributeIndex ) { var attribute = propertyAttributes[attributeIndex]; properties[attribute.name] = attribute.value; } result.push( imageryLayerFeatureInfoFromDataAndProperties(feature, properties) ); } } else { // Thredds format -- looks like esri, but instead of containing FIELDS, contains FeatureInfo element var featureInfoElements = featureInfoResponse.getElementsByTagNameNS( "*", "FeatureInfo" ); for ( var featureInfoElementIndex = 0; featureInfoElementIndex < featureInfoElements.length; ++featureInfoElementIndex ) { var featureInfoElement = featureInfoElements[featureInfoElementIndex]; properties = {}; // node.children is not supported in IE9-11, so use childNodes and check that child.nodeType is an element var featureInfoChildren = featureInfoElement.childNodes; for ( var childIndex = 0; childIndex < featureInfoChildren.length; ++childIndex ) { var child = featureInfoChildren[childIndex]; if (child.nodeType === Node.ELEMENT_NODE) { properties[child.localName] = child.textContent; } } result.push( imageryLayerFeatureInfoFromDataAndProperties( featureInfoElement, properties ) ); } } return result; } function gmlToFeatureInfo(xml) { var result = []; var featureCollection = xml.documentElement; var featureMembers = featureCollection.getElementsByTagNameNS( gmlNamespace, "featureMember" ); for ( var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex ) { var featureMember = featureMembers[featureIndex]; var properties = {}; getGmlPropertiesRecursively(featureMember, properties); result.push( imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties) ); } return result; } // msGmlToFeatureInfo is similar to gmlToFeatureInfo, but assumes different XML structure // eg. <msGMLOutput> <ABC_layer> <ABC_feature> <foo>bar</foo> ... </ABC_feature> </ABC_layer> </msGMLOutput> function msGmlToFeatureInfo(xml) { var result = []; // Find the first child. Except for IE, this would work: // var layer = xml.documentElement.children[0]; var layer; var children = xml.documentElement.childNodes; for (var i = 0; i < children.length; i++) { if (children[i].nodeType === Node.ELEMENT_NODE) { layer = children[i]; break; } } if (!defined(layer)) { throw new RuntimeError( "Unable to find first child of the feature info xml document" ); } var featureMembers = layer.childNodes; for ( var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex ) { var featureMember = featureMembers[featureIndex]; if (featureMember.nodeType === Node.ELEMENT_NODE) { var properties = {}; getGmlPropertiesRecursively(featureMember, properties); result.push( imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties) ); } } return result; } function getGmlPropertiesRecursively(gmlNode, properties) { var isSingleValue = true; for (var i = 0; i < gmlNode.childNodes.length; ++i) { var child = gmlNode.childNodes[i]; if (child.nodeType === Node.ELEMENT_NODE) { isSingleValue = false; } if ( child.localName === "Point" || child.localName === "LineString" || child.localName === "Polygon" || child.localName === "boundedBy" ) { continue; } if ( child.hasChildNodes() && getGmlPropertiesRecursively(child, properties) ) { properties[child.localName] = child.textContent; } } return isSingleValue; } function imageryLayerFeatureInfoFromDataAndProperties(data, properties) { var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.data = data; featureInfo.properties = properties; featureInfo.configureNameFromProperties(properties); featureInfo.configureDescriptionFromProperties(properties); return featureInfo; } function unknownXmlToFeatureInfo(xml) { var xmlText = new XMLSerializer().serializeToString(xml); var element = document.createElement("div"); var pre = document.createElement("pre"); pre.textContent = xmlText; element.appendChild(pre); var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.data = xml; featureInfo.description = element.innerHTML; return [featureInfo]; } var emptyBodyRegex = /<body>\s*<\/body>/im; var wmsServiceExceptionReportRegex = /<ServiceExceptionReport([\s\S]*)<\/ServiceExceptionReport>/im; var titleRegex = /<title>([\s\S]*)<\/title>/im; function textToFeatureInfo(text) { // If the text is HTML and it has an empty body tag, assume it means no features were found. if (emptyBodyRegex.test(text)) { return undefined; } // If this is a WMS exception report, treat it as "no features found" rather than showing // bogus feature info. if (wmsServiceExceptionReportRegex.test(text)) { return undefined; } // If the text has a <title> element, use it as the name. var name; var title = titleRegex.exec(text); if (title && title.length > 1) { name = title[1]; } var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.name = name; featureInfo.description = text; featureInfo.data = text; return [featureInfo]; } export default GetFeatureInfoFormat;