UNPKG

terriajs

Version:

Geospatial data visualization platform.

299 lines (245 loc) 12.4 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 Rectangle = require('terriajs-cesium/Source/Core/Rectangle'); var TerriaError = require('../Core/TerriaError'); var CatalogGroup = require('./CatalogGroup'); var inherit = require('../Core/inherit'); var proxyCatalogItemUrl = require('./proxyCatalogItemUrl'); var unionRectangles = require('../Map/unionRectangles'); var WebFeatureServiceCatalogItem = require('./WebFeatureServiceCatalogItem'); var xml2json = require('../ThirdParty/xml2json'); /** * A {@link CatalogGroup} representing a collection of feature types from a Web Feature Service (WFS) server. * * @alias WebFeatureServiceCatalogGroup * @constructor * @extends CatalogGroup * * @param {Terria} terria The Terria instance. */ var WebFeatureServiceCatalogGroup = function(terria) { CatalogGroup.call(this, terria, 'wfs-getCapabilities'); /** * Gets or sets the URL of the WFS 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} */ this.dataCustodian = 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; knockout.track(this, ['url', 'dataCustodian', 'blacklist']); }; inherit(CatalogGroup, WebFeatureServiceCatalogGroup); defineProperties(WebFeatureServiceCatalogGroup.prototype, { /** * Gets the type of data member represented by this instance. * @memberOf WebFeatureServiceCatalogGroup.prototype * @type {String} */ type : { get : function() { return 'wfs-getCapabilities'; } }, /** * Gets a human-readable name for this type of data source, such as 'Web Feature Service (WFS)'. * @memberOf WebFeatureServiceCatalogGroup.prototype * @type {String} */ typeName : { get : function() { return 'Web Feature Service (WFS) 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 WebFeatureServiceCatalogGroup.prototype * @type {Object} */ serializers : { get : function() { return WebFeatureServiceCatalogGroup.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} */ WebFeatureServiceCatalogGroup.defaultSerializers = clone(CatalogGroup.defaultSerializers); WebFeatureServiceCatalogGroup.defaultSerializers.items = CatalogGroup.enabledShareableItemsSerializer; WebFeatureServiceCatalogGroup.defaultSerializers.isLoading = function(wfsGroup, json, propertyName, options) {}; freezeObject(WebFeatureServiceCatalogGroup.defaultSerializers); WebFeatureServiceCatalogGroup.prototype._getValuesThatInfluenceLoad = function() { return [this.url, this.blacklist]; }; WebFeatureServiceCatalogGroup.prototype._load = function() { var url = cleanAndProxyUrl(this, this.url) + '?service=WFS&version=1.1.0&request=GetCapabilities'; var that = this; return loadXML(url).then(function(xml) { // Is this really a GetCapabilities response? if (!xml || !xml.documentElement || xml.documentElement.localName !== 'WFS_Capabilities') { throw new TerriaError({ title: 'Invalid WFS server', message: '\ An error occurred while invoking GetCapabilities on the WFS 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); var supportsJsonGetFeature = false; if (defined(json.OperationsMetadata)) { var getFeatureOperation = findElementByName(json.OperationsMetadata.Operation, 'GetFeature'); if (defined(getFeatureOperation)) { var outputFormatParameter = findElementByName(getFeatureOperation.Parameter, 'outputFormat'); if (defined(outputFormatParameter) && defined(outputFormatParameter.Value)) { supportsJsonGetFeature = outputFormatParameter.Value.indexOf('json') >= 0 || outputFormatParameter.Value.indexOf('JSON') >= 0 || outputFormatParameter.Value.indexOf('application/json') >= 0; } } } var dataCustodian = that.dataCustodian; if (!defined(dataCustodian) && defined(json.ServiceProvider) && defined(json.ServiceProvider.ProviderName)) { dataCustodian = json.ServiceProvider.ProviderName; if (defined(json.ServiceProvider.ProviderSite) && defined(json.ServiceProvider.ProviderSite.href)) { dataCustodian = '[' + dataCustodian + '](' + json.ServiceProvider.ProviderSite.href + ')'; } if (defined(json.ServiceProvider.ServiceContact) && defined(json.ServiceProvider.ServiceContact.Address) && defined(json.ServiceProvider.ServiceContact.Address.ElectronicMailAddress)) { dataCustodian += '<br/>'; dataCustodian += '[' + json.ServiceProvider.ServiceContact.Address.ElectronicMailAddress + '](mailto:' + json.ServiceProvider.ServiceContact.Address.ElectronicMailAddress + ')<br/>'; } } if (defined(json.FeatureTypeList)) { addFeatureTypes(that, json.FeatureTypeList.FeatureType, that.items, undefined, supportsJsonGetFeature, dataCustodian); } }).otherwise(function(e) { throw new TerriaError({ title: 'Group is not available', message: '\ An error occurred while invoking GetCapabilities on the WFS 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+' \ Map 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 findElementByName(list, name) { if (!defined(list)) { return undefined; } for (var i = 0; i < list.length; ++i) { if (list[i].name === name) { return list[i]; } } return undefined; } 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 addFeatureTypes(wfsGroup, featureTypes, items, parent, supportsJsonGetFeature, dataCustodian) { if (!defined(featureTypes)) { return; } if (!(featureTypes instanceof Array)) { featureTypes = [featureTypes]; } for (var i = 0; i < featureTypes.length; ++i) { var featureType = featureTypes[i]; if (wfsGroup.blacklist && wfsGroup.blacklist[featureType.Title]) { console.log('Provider Feedback: Filtering out ' + featureType.Title + ' (' + featureType.Name + ') because it is blacklisted.'); continue; } wfsGroup.add(createWfsDataSource(wfsGroup, featureType, supportsJsonGetFeature, dataCustodian)); } } function createWfsDataSource(wfsGroup, featureType, supportsJsonGetFeature, dataCustodian) { var result = new WebFeatureServiceCatalogItem(wfsGroup.terria); result.name = featureType.Title; result.description = defined(featureType.Abstract) && featureType.Abstract.length > 0 ? featureType.Abstract : wfsGroup.description; result.dataCustodian = dataCustodian; result.url = wfsGroup.url; result.typeNames = featureType.Name; result.description = ''; var wfsGroupHasDescription = defined(wfsGroup.description) && wfsGroup.description.length > 0; var layerHasAbstract = defined(featureType.Abstract) && featureType.Abstract.length > 0; if (wfsGroupHasDescription) { result.description += wfsGroup.description; } if (wfsGroupHasDescription && layerHasAbstract) { result.description += '<br/>'; } if (layerHasAbstract) { result.description += featureType.Abstract; } result.requestGeoJson = supportsJsonGetFeature; result.requestGml = true; var boundingBoxes = featureType.WGS84BoundingBox; var rectangle; if (boundingBoxes instanceof Array) { rectangle = wgs84BoundingBoxToRectangle(boundingBoxes[0]); for (var i = 1; i < boundingBoxes.length; ++i) { rectangle = unionRectangles(rectangle, wgs84BoundingBoxToRectangle(boundingBoxes[i])); } } else if (defined(boundingBoxes)) { rectangle = wgs84BoundingBoxToRectangle(boundingBoxes); } else { rectangle = Rectangle.MAX_VALUE; } result.rectangle = rectangle; return result; } function wgs84BoundingBoxToRectangle(boundingBox) { if (!defined(boundingBox)) { return Rectangle.MAX_VALUE; } var lowerCorner = boundingBox.LowerCorner; var upperCorner = boundingBox.UpperCorner; if (!defined(lowerCorner) || !defined(upperCorner)) { return Rectangle.MAX_VALUE; } var lowerCoordinates = lowerCorner.split(' '); var upperCoordinates = upperCorner.split(' '); if (lowerCoordinates.length !== 2 || upperCoordinates.length !== 2) { return Rectangle.MAX_VALUE; } return Rectangle.fromDegrees(lowerCoordinates[0], lowerCoordinates[1], upperCoordinates[0], upperCoordinates[1]); } module.exports = WebFeatureServiceCatalogGroup;