UNPKG

terriajs

Version:

Geospatial data visualization platform.

314 lines (264 loc) 12.6 kB
'use strict'; /*global require*/ var URI = require('urijs'); var clone = require('terriajs-cesium/Source/Core/clone'); var defined = require('terriajs-cesium/Source/Core/defined'); var defineProperties = require('terriajs-cesium/Source/Core/defineProperties'); var freezeObject = require('terriajs-cesium/Source/Core/freezeObject'); var knockout = require('terriajs-cesium/Source/ThirdParty/knockout'); var loadXML = require('../Core/loadXML'); var TerriaError = require('../Core/TerriaError'); var CatalogGroup = require('./CatalogGroup'); var inherit = require('../Core/inherit'); var proxyCatalogItemUrl = require('./proxyCatalogItemUrl'); var WebMapServiceCatalogItem = require('./WebMapServiceCatalogItem'); var xml2json = require('../ThirdParty/xml2json'); /** * A {@link CatalogGroup} representing a collection of layers from a Web Map Service (WMS) server. * * @alias WebMapServiceCatalogGroup * @constructor * @extends CatalogGroup * * @param {Terria} terria The Terria instance. */ var WebMapServiceCatalogGroup = function(terria) { CatalogGroup.call(this, terria, 'wms-getCapabilities'); /** * Gets or sets the URL of the WMS server. This property is observable. * @type {String} */ this.url = ''; /** * Gets or sets a description of the custodian of the data sources in this group. * This property is an HTML string that must be sanitized before display to the user. * This property is observable. * @type {String} * @editorformat textarea */ this.dataCustodian = undefined; /** * Gets or sets the additional parameters to pass to the WMS server when requesting images. * All parameter names must be entered in lowercase in order to be consistent with references in TerrisJS code. * If this property is undefiend, {@link WebMapServiceCatalogItem.defaultParameters} is used. * @type {Object} */ this.parameters = undefined; /** * Gets or sets a hash of names of blacklisted data layers. A layer that appears in this hash * will not be shown to the user. In this hash, the keys should be the Title of the layers to blacklist, * and the values should be "true". This property is observable. * @type {Object} */ this.blacklist = undefined; /** * Gets or sets the field name to use as the primary title in the catalog view: each WMS layer's * "title" (default), "name", or "abstract". * @type {String} */ this.titleField = 'title'; /** * Gets or sets a hash of properties that will be set on each child item. * For example, { 'treat404AsError': false } * @type {Object} */ this.itemProperties = undefined; /** * Gets or sets a value indicating whether the list of layers queried from GetCapabilities should be * flattened into a list with no hierarchy. * @type {Boolean} * @default false */ this.flatten = false; knockout.track(this, ['url', 'dataCustodian', 'parameters', 'blacklist', 'titleField', 'itemProperties', 'flatten']); }; inherit(CatalogGroup, WebMapServiceCatalogGroup); defineProperties(WebMapServiceCatalogGroup.prototype, { /** * Gets the type of data member represented by this instance. * @memberOf WebMapServiceCatalogGroup.prototype * @type {String} */ type : { get : function() { return 'wms-getCapabilities'; } }, /** * Gets a human-readable name for this type of data source, such as 'Web Map Service (WMS)'. * @memberOf WebMapServiceCatalogGroup.prototype * @type {String} */ typeName : { get : function() { return 'Web Map Service (WMS) Server'; } }, /** * Gets the set of functions used to serialize individual properties in {@link CatalogMember#serializeToJson}. * When a property name on the model matches the name of a property in the serializers object literal, * the value will be called as a function and passed a reference to the model, a reference to the destination * JSON object literal, and the name of the property. * @memberOf WebMapServiceCatalogGroup.prototype * @type {Object} */ serializers : { get : function() { return WebMapServiceCatalogGroup.defaultSerializers; } } }); /** * Gets or sets the set of default serializer functions to use in {@link CatalogMember#serializeToJson}. Types derived from this type * should expose this instance - cloned and modified if necesary - through their {@link CatalogMember#serializers} property. * @type {Object} */ WebMapServiceCatalogGroup.defaultSerializers = clone(CatalogGroup.defaultSerializers); WebMapServiceCatalogGroup.defaultSerializers.items = CatalogGroup.enabledShareableItemsSerializer; WebMapServiceCatalogGroup.defaultSerializers.isLoading = function(wmsGroup, json, propertyName, options) {}; freezeObject(WebMapServiceCatalogGroup.defaultSerializers); WebMapServiceCatalogGroup.prototype._getValuesThatInfluenceLoad = function() { return [this.url, this.blacklist, this.titleField]; }; WebMapServiceCatalogGroup.prototype._load = function() { var url = cleanAndProxyUrl(this, this.url) + '?service=WMS&request=GetCapabilities&version=1.3.0&tiled=true'; var that = this; return loadXML(url).then(function(xml) { // Is this really a GetCapabilities response? if (!xml || !xml.documentElement || (xml.documentElement.localName !== 'WMS_Capabilities' && xml.documentElement.localName !== 'WMT_MS_Capabilities')) { throw new TerriaError({ title: 'Invalid WMS server', message: '\ An error occurred while invoking GetCapabilities on the WMS server. The server\'s response does not appear to be a valid GetCapabilities document. \ <p>If you entered the link manually, please verify that the link is correct.</p>\ <p>If you did not enter this link manually, this error may indicate that the group you opened is temporarily unavailable or there is a \ problem with your internet connection. Try opening the group again, and if the problem persists, please report it by \ sending an email to <a href="mailto:'+that.terria.supportEmail+'">'+that.terria.supportEmail+'</a>.</p>' }); } var json = xml2json(xml); // Skip the root layer, if there's only one. // But use it for the name of this catalog item if the item is currently named by the URL. var parentLayer; var rootLayers = json.Capability.Layer; if (rootLayers) { if (!(rootLayers instanceof Array)) { rootLayers = [rootLayers]; } if (rootLayers.length === 1 && rootLayers[0].Layer) { var singleRoot = rootLayers[0]; if (that.name === that.url) { that.name = getNameFromLayer(that, singleRoot); } parentLayer = singleRoot; rootLayers = singleRoot.Layer; } var infoDerivedFromCapabilities = { availableStyles: WebMapServiceCatalogItem.getAllAvailableStylesFromCapabilities(json), availableDimensions: WebMapServiceCatalogItem.getAllAvailableDimensionsFromCapabilities(json) }; addLayersRecursively(that, that, json, rootLayers, parentLayer, infoDerivedFromCapabilities); } }).otherwise(function(e) { throw new TerriaError({ sender: that, title: 'Group is not available', message: '\ An error occurred while invoking GetCapabilities on the WMS server. \ <p>If you entered the link manually, please verify that the link is correct.</p>\ <p>This error may also indicate that the server does not support <a href="http://enable-cors.org/" target="_blank">CORS</a>. If this is your \ server, verify that CORS is enabled and enable it if it is not. If you do not control the server, \ please contact the administrator of the server and ask them to enable CORS. Or, contact the '+that.terria.appName+' \ team by emailing <a href="mailto:'+that.terria.supportEmail+'">'+that.terria.supportEmail+'</a> \ and ask us to add this server to the list of non-CORS-supporting servers that may be proxied by '+that.terria.appName+' \ itself.</p>\ <p>If you did not enter this link manually, this error may indicate that the group you opened is temporarily unavailable or there is a \ problem with your internet connection. Try opening the group again, and if the problem persists, please report it by \ sending an email to <a href="mailto:'+that.terria.supportEmail+'">'+that.terria.supportEmail+'</a>.</p>' }); }); }; function cleanAndProxyUrl(catalogGroup, url) { // Strip off the search portion of the URL var uri = new URI(url); uri.search(''); var cleanedUrl = uri.toString(); return proxyCatalogItemUrl(catalogGroup, cleanedUrl, '1d'); } function getNameFromLayer(wmsGroup, layer) { if (wmsGroup.titleField === 'name') { return layer.Name; } else if (wmsGroup.titleField === 'abstract') { return layer.Abstract; } else { return layer.Title; } } function addLayersRecursively(wmsGroup, parentGroup, capabilities, layers, parent, infoDerivedFromCapabilities) { if (!(layers instanceof Array)) { layers = [layers]; } for (var i = 0; i < layers.length; ++i) { var layer = layers[i]; // Record this layer's parent, so we can walk up the layer hierarchy looking for inherited properties. layer._parent = parent; if (wmsGroup.blacklist && wmsGroup.blacklist[layer.Title]) { console.log('Provider Feedback: Filtering out ' + layer.Title + ' (' + layer.Name + ') because it is blacklisted.'); continue; } if (defined(layer.Layer)) { var group = parentGroup; if (!wmsGroup.flatten) { // Create a group for this layer group = createWmsSubGroup(wmsGroup, layer); } // WMS 1.1.1 spec section 7.1.4.5.2 says any layer with a Name property can be used // in the 'layers' parameter of a GetMap request. This is true in 1.0.0 and 1.3.0 as well. var allName = '(All)'; var originalNameForAll; if (defined(layer.Name) && layer.Name.length > 0) { var all = createWmsDataSource(wmsGroup, capabilities, layer, infoDerivedFromCapabilities); if (!wmsGroup.flatten) { originalNameForAll = all.name; all.name = allName + ' ' + all.name; } group.add(all); } addLayersRecursively(wmsGroup, group, capabilities, layer.Layer, layer, infoDerivedFromCapabilities); if (!wmsGroup.flatten) { if (group.items.length === 1 && group.items[0].name.indexOf(allName) === 0) { group.items[0].name = originalNameForAll; parentGroup.add(group.items[0]); } else if (group.items.length > 0) { parentGroup.add(group); } } } else { parentGroup.add(createWmsDataSource(wmsGroup, capabilities, layer)); } } } function createWmsDataSource(wmsGroup, capabilities, layer, infoDerivedFromCapabilities) { var result = new WebMapServiceCatalogItem(wmsGroup.terria); result.name = getNameFromLayer(wmsGroup, layer); result.layers = layer.Name; result.url = wmsGroup.url; result.updateFromCapabilities(capabilities, false, layer, infoDerivedFromCapabilities); if (typeof(wmsGroup.itemProperties) === 'object') { result.updateFromJson(wmsGroup.itemProperties); } return result; } function createWmsSubGroup(wmsGroup, layer) { var result = new CatalogGroup(wmsGroup.terria); if (wmsGroup.titleField === 'name') { result.name = layer.Name; } else if (wmsGroup.titleField === 'abstract') { result.name = layer.Abstract; } else { result.name = layer.Title; } return result; } module.exports = WebMapServiceCatalogGroup;