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. bar ... 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= /\s*<\/body>/im; var wmsServiceExceptionReportRegex = //im; var titleRegex = /([\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;