GetFeatureInfoFormat.js 13 KB

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