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) {
const result = [];
const features = json.features;
for (let i = 0; i < features.length; ++i) {
const feature = features[i];
const 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") {
const longitude = feature.geometry.coordinates[0];
const latitude = feature.geometry.coordinates[1];
featureInfo.position = Cartographic.fromDegrees(longitude, latitude);
}
result.push(featureInfo);
}
return result;
}
const mapInfoMxpNamespace = "http://www.mapinfo.com/mxp";
const esriWmsNamespace = "http://www.esri.com/wms";
const wfsNamespace = "http://www.opengis.net/wfs";
const gmlNamespace = "http://www.opengis.net/gml";
function xmlToFeatureInfo(xml) {
const 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) {
const result = [];
const multiFeatureCollection = xml.documentElement;
const features = multiFeatureCollection.getElementsByTagNameNS(
mapInfoMxpNamespace,
"Feature"
);
for (let featureIndex = 0; featureIndex < features.length; ++featureIndex) {
const feature = features[featureIndex];
const properties = {};
const propertyElements = feature.getElementsByTagNameNS(
mapInfoMxpNamespace,
"Val"
);
for (
let propertyIndex = 0;
propertyIndex < propertyElements.length;
++propertyIndex
) {
const propertyElement = propertyElements[propertyIndex];
if (propertyElement.hasAttribute("ref")) {
const name = propertyElement.getAttribute("ref");
const value = propertyElement.textContent.trim();
properties[name] = value;
}
}
const featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = feature;
featureInfo.properties = properties;
featureInfo.configureNameFromProperties(properties);
featureInfo.configureDescriptionFromProperties(properties);
result.push(featureInfo);
}
return result;
}
function esriXmlToFeatureInfo(xml) {
const featureInfoResponse = xml.documentElement;
const result = [];
let properties;
const features = featureInfoResponse.getElementsByTagNameNS("*", "FIELDS");
if (features.length > 0) {
// Standard esri format
for (let featureIndex = 0; featureIndex < features.length; ++featureIndex) {
const feature = features[featureIndex];
properties = {};
const propertyAttributes = feature.attributes;
for (
let attributeIndex = 0;
attributeIndex < propertyAttributes.length;
++attributeIndex
) {
const 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
const featureInfoElements = featureInfoResponse.getElementsByTagNameNS(
"*",
"FeatureInfo"
);
for (
let featureInfoElementIndex = 0;
featureInfoElementIndex < featureInfoElements.length;
++featureInfoElementIndex
) {
const featureInfoElement = featureInfoElements[featureInfoElementIndex];
properties = {};
// node.children is not supported in IE9-11, so use childNodes and check that child.nodeType is an element
const featureInfoChildren = featureInfoElement.childNodes;
for (
let childIndex = 0;
childIndex < featureInfoChildren.length;
++childIndex
) {
const child = featureInfoChildren[childIndex];
if (child.nodeType === Node.ELEMENT_NODE) {
properties[child.localName] = child.textContent;
}
}
result.push(
imageryLayerFeatureInfoFromDataAndProperties(
featureInfoElement,
properties
)
);
}
}
return result;
}
function gmlToFeatureInfo(xml) {
const result = [];
const featureCollection = xml.documentElement;
const featureMembers = featureCollection.getElementsByTagNameNS(
gmlNamespace,
"featureMember"
);
for (
let featureIndex = 0;
featureIndex < featureMembers.length;
++featureIndex
) {
const featureMember = featureMembers[featureIndex];
const properties = {};
getGmlPropertiesRecursively(featureMember, properties);
result.push(
imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties)
);
}
return result;
}
// msGmlToFeatureInfo is similar to gmlToFeatureInfo, but assumes different XML structure
// eg.