Source: Scene/GetFeatureInfoFormat.js

  1. /*global define*/
  2. define([
  3. '../Core/Cartographic',
  4. '../Core/defaultValue',
  5. '../Core/defined',
  6. '../Core/DeveloperError',
  7. '../Core/RuntimeError',
  8. './ImageryLayerFeatureInfo'
  9. ], function(
  10. Cartographic,
  11. defaultValue,
  12. defined,
  13. DeveloperError,
  14. RuntimeError,
  15. ImageryLayerFeatureInfo) {
  16. 'use strict';
  17. /**
  18. * Describes the format in which to request GetFeatureInfo from a Web Map Service (WMS) server.
  19. *
  20. * @alias GetFeatureInfoFormat
  21. * @constructor
  22. *
  23. * @param {String} type The type of response to expect from a GetFeatureInfo request. Valid
  24. * values are 'json', 'xml', 'html', or 'text'.
  25. * @param {String} [format] The info format to request from the WMS server. This is usually a
  26. * MIME type such as 'application/json' or text/xml'. If this parameter is not specified, the provider will request 'json'
  27. * using 'application/json', 'xml' using 'text/xml', 'html' using 'text/html', and 'text' using 'text/plain'.
  28. * @param {Function} [callback] A function to invoke with the GetFeatureInfo response from the WMS server
  29. * in order to produce an array of picked {@link ImageryLayerFeatureInfo} instances. If this parameter is not specified,
  30. * a default function for the type of response is used.
  31. */
  32. function GetFeatureInfoFormat(type, format, callback) {
  33. //>>includeStart('debug', pragmas.debug);
  34. if (!defined(type)) {
  35. throw new DeveloperError('type is required.');
  36. }
  37. //>>includeEnd('debug');
  38. this.type = type;
  39. if (!defined(format)) {
  40. if (type === 'json') {
  41. format = 'application/json';
  42. } else if (type === 'xml') {
  43. format = 'text/xml';
  44. } else if (type === 'html') {
  45. format = 'text/html';
  46. } else if (type === 'text') {
  47. format = 'text/plain';
  48. }
  49. //>>includeStart('debug', pragmas.debug);
  50. else {
  51. throw new DeveloperError('format is required when type is not "json", "xml", "html", or "text".');
  52. }
  53. //>>includeEnd('debug');
  54. }
  55. this.format = format;
  56. if (!defined(callback)) {
  57. if (type === 'json') {
  58. callback = geoJsonToFeatureInfo;
  59. } else if (type === 'xml') {
  60. callback = xmlToFeatureInfo;
  61. } else if (type === 'html') {
  62. callback = textToFeatureInfo;
  63. } else if (type === 'text') {
  64. callback = textToFeatureInfo;
  65. }
  66. //>>includeStart('debug', pragmas.debug);
  67. else {
  68. throw new DeveloperError('callback is required when type is not "json", "xml", "html", or "text".');
  69. }
  70. //>>includeEnd('debug');
  71. }
  72. this.callback = callback;
  73. }
  74. function geoJsonToFeatureInfo(json) {
  75. var result = [];
  76. var features = json.features;
  77. for (var i = 0; i < features.length; ++i) {
  78. var feature = features[i];
  79. var featureInfo = new ImageryLayerFeatureInfo();
  80. featureInfo.data = feature;
  81. featureInfo.properties = feature.properties;
  82. featureInfo.configureNameFromProperties(feature.properties);
  83. featureInfo.configureDescriptionFromProperties(feature.properties);
  84. // If this is a point feature, use the coordinates of the point.
  85. if (defined(feature.geometry) && feature.geometry.type === 'Point') {
  86. var longitude = feature.geometry.coordinates[0];
  87. var latitude = feature.geometry.coordinates[1];
  88. featureInfo.position = Cartographic.fromDegrees(longitude, latitude);
  89. }
  90. result.push(featureInfo);
  91. }
  92. return result;
  93. }
  94. var mapInfoMxpNamespace = 'http://www.mapinfo.com/mxp';
  95. var esriWmsNamespace = 'http://www.esri.com/wms';
  96. var wfsNamespace = 'http://www.opengis.net/wfs';
  97. var gmlNamespace = 'http://www.opengis.net/gml';
  98. function xmlToFeatureInfo(xml) {
  99. var documentElement = xml.documentElement;
  100. if (documentElement.localName === 'MultiFeatureCollection' && documentElement.namespaceURI === mapInfoMxpNamespace) {
  101. // This looks like a MapInfo MXP response
  102. return mapInfoXmlToFeatureInfo(xml);
  103. } else if (documentElement.localName === 'FeatureInfoResponse' && documentElement.namespaceURI === esriWmsNamespace) {
  104. // This looks like an Esri WMS response
  105. return esriXmlToFeatureInfo(xml);
  106. } else if (documentElement.localName === 'FeatureCollection' && documentElement.namespaceURI === wfsNamespace) {
  107. // This looks like a WFS/GML response.
  108. return gmlToFeatureInfo(xml);
  109. } else if (documentElement.localName === 'ServiceExceptionReport') {
  110. // This looks like a WMS server error, so no features picked.
  111. throw new RuntimeError(new XMLSerializer().serializeToString(documentElement));
  112. } else if (documentElement.localName === 'msGMLOutput') {
  113. return msGmlToFeatureInfo(xml);
  114. } else {
  115. // Unknown response type, so just dump the XML itself into the description.
  116. return unknownXmlToFeatureInfo(xml);
  117. }
  118. }
  119. function mapInfoXmlToFeatureInfo(xml) {
  120. var result = [];
  121. var multiFeatureCollection = xml.documentElement;
  122. var features = multiFeatureCollection.getElementsByTagNameNS(mapInfoMxpNamespace, 'Feature');
  123. for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
  124. var feature = features[featureIndex];
  125. var properties = {};
  126. var propertyElements = feature.getElementsByTagNameNS(mapInfoMxpNamespace, 'Val');
  127. for (var propertyIndex = 0; propertyIndex < propertyElements.length; ++propertyIndex) {
  128. var propertyElement = propertyElements[propertyIndex];
  129. if (propertyElement.hasAttribute('ref')) {
  130. var name = propertyElement.getAttribute('ref');
  131. var value = propertyElement.textContent.trim();
  132. properties[name] = value;
  133. }
  134. }
  135. var featureInfo = new ImageryLayerFeatureInfo();
  136. featureInfo.data = feature;
  137. featureInfo.properties = properties;
  138. featureInfo.configureNameFromProperties(properties);
  139. featureInfo.configureDescriptionFromProperties(properties);
  140. result.push(featureInfo);
  141. }
  142. return result;
  143. }
  144. function esriXmlToFeatureInfo(xml) {
  145. var featureInfoResponse = xml.documentElement;
  146. var result = [];
  147. var properties;
  148. var features = featureInfoResponse.getElementsByTagNameNS('*', 'FIELDS');
  149. if (features.length > 0) {
  150. // Standard esri format
  151. for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
  152. var feature = features[featureIndex];
  153. properties = {};
  154. var propertyAttributes = feature.attributes;
  155. for (var attributeIndex = 0; attributeIndex < propertyAttributes.length; ++attributeIndex) {
  156. var attribute = propertyAttributes[attributeIndex];
  157. properties[attribute.name] = attribute.value;
  158. }
  159. result.push(imageryLayerFeatureInfoFromDataAndProperties(feature, properties));
  160. }
  161. } else {
  162. // Thredds format -- looks like esri, but instead of containing FIELDS, contains FeatureInfo element
  163. var featureInfoElements = featureInfoResponse.getElementsByTagNameNS('*', 'FeatureInfo');
  164. for (var featureInfoElementIndex = 0; featureInfoElementIndex < featureInfoElements.length; ++featureInfoElementIndex) {
  165. var featureInfoElement = featureInfoElements[featureInfoElementIndex];
  166. properties = {};
  167. // node.children is not supported in IE9-11, so use childNodes and check that child.nodeType is an element
  168. var featureInfoChildren = featureInfoElement.childNodes;
  169. for (var childIndex = 0; childIndex < featureInfoChildren.length; ++childIndex) {
  170. var child = featureInfoChildren[childIndex];
  171. if (child.nodeType === Node.ELEMENT_NODE) {
  172. properties[child.localName] = child.textContent;
  173. }
  174. }
  175. result.push(imageryLayerFeatureInfoFromDataAndProperties(featureInfoElement, properties));
  176. }
  177. }
  178. return result;
  179. }
  180. function gmlToFeatureInfo(xml) {
  181. var result = [];
  182. var featureCollection = xml.documentElement;
  183. var featureMembers = featureCollection.getElementsByTagNameNS(gmlNamespace, 'featureMember');
  184. for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
  185. var featureMember = featureMembers[featureIndex];
  186. var properties = {};
  187. getGmlPropertiesRecursively(featureMember, properties);
  188. result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
  189. }
  190. return result;
  191. }
  192. // msGmlToFeatureInfo is similar to gmlToFeatureInfo, but assumes different XML structure
  193. // eg. <msGMLOutput> <ABC_layer> <ABC_feature> <foo>bar</foo> ... </ABC_feature> </ABC_layer> </msGMLOutput>
  194. function msGmlToFeatureInfo(xml) {
  195. var result = [];
  196. // Find the first child. Except for IE, this would work:
  197. // var layer = xml.documentElement.children[0];
  198. var layer;
  199. var children = xml.documentElement.childNodes;
  200. for (var i = 0; i < children.length; i++) {
  201. if (children[i].nodeType === Node.ELEMENT_NODE) {
  202. layer = children[i];
  203. break;
  204. }
  205. }
  206. var featureMembers = layer.childNodes;
  207. for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
  208. var featureMember = featureMembers[featureIndex];
  209. if (featureMember.nodeType === Node.ELEMENT_NODE) {
  210. var properties = {};
  211. getGmlPropertiesRecursively(featureMember, properties);
  212. result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
  213. }
  214. }
  215. return result;
  216. }
  217. function getGmlPropertiesRecursively(gmlNode, properties) {
  218. var isSingleValue = true;
  219. for (var i = 0; i < gmlNode.childNodes.length; ++i) {
  220. var child = gmlNode.childNodes[i];
  221. if (child.nodeType === Node.ELEMENT_NODE) {
  222. isSingleValue = false;
  223. }
  224. if (child.localName === 'Point' || child.localName === 'LineString' || child.localName === 'Polygon' || child.localName === 'boundedBy') {
  225. continue;
  226. }
  227. if (child.hasChildNodes() && getGmlPropertiesRecursively(child, properties)) {
  228. properties[child.localName] = child.textContent;
  229. }
  230. }
  231. return isSingleValue;
  232. }
  233. function imageryLayerFeatureInfoFromDataAndProperties(data, properties) {
  234. var featureInfo = new ImageryLayerFeatureInfo();
  235. featureInfo.data = data;
  236. featureInfo.properties = properties;
  237. featureInfo.configureNameFromProperties(properties);
  238. featureInfo.configureDescriptionFromProperties(properties);
  239. return featureInfo;
  240. }
  241. function unknownXmlToFeatureInfo(xml) {
  242. var xmlText = new XMLSerializer().serializeToString(xml);
  243. var element = document.createElement('div');
  244. var pre = document.createElement('pre');
  245. pre.textContent = xmlText;
  246. element.appendChild(pre);
  247. var featureInfo = new ImageryLayerFeatureInfo();
  248. featureInfo.data = xml;
  249. featureInfo.description = element.innerHTML;
  250. return [featureInfo];
  251. }
  252. var emptyBodyRegex= /<body>\s*<\/body>/im;
  253. var wmsServiceExceptionReportRegex = /<ServiceExceptionReport([\s\S]*)<\/ServiceExceptionReport>/im;
  254. var titleRegex = /<title>([\s\S]*)<\/title>/im;
  255. function textToFeatureInfo(text) {
  256. // If the text is HTML and it has an empty body tag, assume it means no features were found.
  257. if (emptyBodyRegex.test(text)) {
  258. return undefined;
  259. }
  260. // If this is a WMS exception report, treat it as "no features found" rather than showing
  261. // bogus feature info.
  262. if (wmsServiceExceptionReportRegex.test(text)) {
  263. return undefined;
  264. }
  265. // If the text has a <title> element, use it as the name.
  266. var name;
  267. var title = titleRegex.exec(text);
  268. if (title && title.length > 1) {
  269. name = title[1];
  270. }
  271. var featureInfo = new ImageryLayerFeatureInfo();
  272. featureInfo.name = name;
  273. featureInfo.description = text;
  274. featureInfo.data = text;
  275. return [featureInfo];
  276. }
  277. return GetFeatureInfoFormat;
  278. });