UNPKG

terriajs

Version:

Geospatial data visualization platform.

288 lines (251 loc) 11.7 kB
'use strict'; /*global require,Document*/ var defined = require('terriajs-cesium/Source/Core/defined'); var defineProperties = require('terriajs-cesium/Source/Core/defineProperties'); var Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid'); //var KmlDataSource = require('terriajs-cesium/Source/DataSources/KmlDataSource'); var knockout = require('terriajs-cesium/Source/ThirdParty/knockout'); var PolygonHierarchy = require('terriajs-cesium/Source/Core/PolygonHierarchy'); var sampleTerrain = require('terriajs-cesium/Source/Core/sampleTerrain'); var when = require('terriajs-cesium/Source/ThirdParty/when'); var DataSourceCatalogItem = require('./DataSourceCatalogItem'); var Metadata = require('./Metadata'); var TerriaError = require('../Core/TerriaError'); var inherit = require('../Core/inherit'); var promiseFunctionToExplicitDeferred = require('../Core/promiseFunctionToExplicitDeferred'); var proxyCatalogItemUrl = require('./proxyCatalogItemUrl'); var readXml = require('../Core/readXml'); /** * A {@link CatalogItem} representing KML or KMZ feature data. * * @alias KmlCatalogItem * @constructor * @extends CatalogItem * * @param {Terria} terria The Terria instance. * @param {String} [url] The URL from which to retrieve the KML or KMZ data. */ var KmlCatalogItem = function(terria, url) { DataSourceCatalogItem.call(this, terria); this._dataSource = undefined; this.url = url; /** * Gets or sets the KML or KMZ data, represented as a binary Blob, DOM Document, or a Promise for one of those things. * If this property is set, {@link CatalogItem#url} is ignored. * This property is observable. * @type {Blob|Document|Promise} */ this.data = undefined; /** * Gets or sets the URL from which the {@link KmlCatalogItem#data} was obtained. This will be used * to resolve any resources linked in the KML file, if any. This property is observable. * @type {String} */ this.dataSourceUrl = undefined; knockout.track(this, ['data', 'dataSourceUrl']); }; inherit(DataSourceCatalogItem, KmlCatalogItem); defineProperties(KmlCatalogItem.prototype, { /** * Gets the type of data member represented by this instance. * @memberOf KmlCatalogItem.prototype * @type {String} */ type : { get : function() { return 'kml'; } }, /** * Gets a human-readable name for this type of data source, 'KML'. * @memberOf KmlCatalogItem.prototype * @type {String} */ typeName : { get : function() { return 'KML or KMZ'; } }, /** * Gets the metadata associated with this data source and the server that provided it, if applicable. * @memberOf KmlCatalogItem.prototype * @type {Metadata} */ metadata : { get : function() { var result = new Metadata(); result.isLoading = false; result.dataSourceErrorMessage = 'This data source does not have any details available.'; result.serviceErrorMessage = 'This service does not have any details available.'; return result; } }, /** * Gets the data source associated with this catalog item. * @memberOf KmlCatalogItem.prototype * @type {DataSource} */ dataSource : { get : function() { return this._dataSource; } } }); KmlCatalogItem.prototype._getValuesThatInfluenceLoad = function() { return [this.url, this.data]; }; var kmzRegex = /\.kmz$/i; KmlCatalogItem.prototype._load = function() { var codeSplittingDeferred = when.defer(); var that = this; require.ensure('terriajs-cesium/Source/DataSources/KmlDataSource', function() { var KmlDataSource = require('terriajs-cesium/Source/DataSources/KmlDataSource'); promiseFunctionToExplicitDeferred(codeSplittingDeferred, function() { // If there is an existing data source, remove it first. var reAdd = false; if (defined(that._dataSource)) { reAdd = that.terria.dataSources.remove(that._dataSource, true); } var dataSource = new KmlDataSource({ // Currently we don't pass camera and canvas, which are technically required as of Cesium v1.23. // We get away with it because A) the code to check that they're supplied is removed // in release builds of Cesium, and B) the code that actually uses them (building network // link URLs) has guards so it won't totally fail if they're not supplied. But for // proper network link support, we'll need to figure out how to get those things in here, // even though a single KmlCatalogItem can be shown on multiple maps. Some refactoring of // Cesium will be required. proxy: { // Don't cache resources referenced by the KML. getURL: url => that.terria.corsProxy.getURLProxyIfNecessary(url, '0d') } }); that._dataSource = dataSource; if (reAdd) { that.terria.dataSources.add(that._dataSource); } if (defined(that.data)) { return when(that.data, function(data) { if (data instanceof Document) { return dataSource.load(data, proxyCatalogItemUrl(that, that.dataSourceUrl, '1d')).then(function() { doneLoading(that); }).otherwise(function() { errorLoading(that); }); } else if (typeof Blob !== 'undefined' && data instanceof Blob) { if (that.dataSourceUrl && that.dataSourceUrl.match(kmzRegex)) { return dataSource.load(data, proxyCatalogItemUrl(that, that.dataSourceUrl, '1d')).then(function() { doneLoading(that); }).otherwise(function() { errorLoading(that); }); } else { return readXml(data).then(function(xml) { return dataSource.load(xml, proxyCatalogItemUrl(that, that.dataSourceUrl, '1d')).then(function() { doneLoading(that); }); }).otherwise(function() { errorLoading(that); }); } } else if (data instanceof String || typeof data === 'string') { var parser = new DOMParser(); var xml; try { xml = parser.parseFromString(data, 'text/xml'); } catch (e) { } if (!xml || !xml.documentElement || xml.getElementsByTagName('parsererror').length > 0) { errorLoading(that); } return dataSource.load(xml, proxyCatalogItemUrl(that, that.dataSourceUrl, '1d')).then(function() { doneLoading(that); }).otherwise(function() { errorLoading(that); }); } else { throw new TerriaError({ sender: that, title: 'Unexpected type of KML data', message: '\ KmlCatalogItem.data is expected to be an XML Document, Blob, or File, but it was none of these. \ This may indicate a bug in '+that.terria.appName+' or incorrect use of the '+that.terria.appName+' API. \ If you believe it is a bug in '+that.terria.appName+', please report it by emailing \ <a href="mailto:'+that.terria.supportEmail+'">'+that.terria.supportEmail+'</a>.' }); } }); } else { return dataSource.load(proxyCatalogItemUrl(that, that.url, '1d')).then(function() { doneLoading(that); }).otherwise(function() { errorLoading(that); }); } }); }, 'Cesium-DataSources'); return codeSplittingDeferred.promise; }; function doneLoading(kmlItem) { var dataSource = kmlItem._dataSource; kmlItem.clock = dataSource.clock; // Clamp features to terrain. if (defined(kmlItem.terria.cesium)) { var positionsToSample = []; var correspondingCartesians = []; var entities = dataSource.entities.values; for (var i = 0; i < entities.length; ++i) { var entity = entities[i]; var polygon = entity.polygon; if (defined(polygon)) { polygon.perPositionHeight = true; var polygonHierarchy = polygon.hierarchy.getValue(); // assuming hierarchy is not time-varying samplePolygonHierarchyPositions(polygonHierarchy, positionsToSample, correspondingCartesians); } } var terrainProvider = kmlItem.terria.cesium.scene.globe.terrainProvider; sampleTerrain(terrainProvider, 11, positionsToSample).then(function() { var i; for (i = 0; i < positionsToSample.length; ++i) { var position = positionsToSample[i]; if (!defined(position.height)) { continue; } Ellipsoid.WGS84.cartographicToCartesian(position, correspondingCartesians[i]); } // Force the polygons to be rebuilt. for (i = 0; i < entities.length; ++i) { var polygon = entities[i].polygon; if (!defined(polygon)) { continue; } var existingHierarchy = polygon.hierarchy.getValue(); polygon.hierarchy = new PolygonHierarchy(existingHierarchy.positions, existingHierarchy.holes); } }); } } function samplePolygonHierarchyPositions(polygonHierarchy, positionsToSample, correspondingCartesians) { var positions = polygonHierarchy.positions; var i; for (i = 0; i < positions.length; ++i) { var position = positions[i]; correspondingCartesians.push(position); positionsToSample.push(Ellipsoid.WGS84.cartesianToCartographic(position)); } var holes = polygonHierarchy.holes; for (i = 0; i < holes.length; ++i) { samplePolygonHierarchyPositions(holes[i], positionsToSample, correspondingCartesians); } } function errorLoading(kmlItem) { var terria = kmlItem.terria; throw new TerriaError({ sender: kmlItem, title: 'Error loading KML or KMZ', message: '\ An error occurred while loading a KML or KMZ file. This may indicate that the file is invalid or that it \ is not supported by '+terria.appName+'. If you would like assistance or further information, please email us \ at <a href="mailto:'+terria.supportEmail+'">'+terria.supportEmail+'</a>.' }); } module.exports = KmlCatalogItem;