import Cartesian2 from '../Core/Cartesian2.js'; import Cartesian3 from '../Core/Cartesian3.js'; import Cartographic from '../Core/Cartographic.js'; import Color from '../Core/Color.js'; import createGuid from '../Core/createGuid.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.js'; import DeveloperError from '../Core/DeveloperError.js'; import Ellipsoid from '../Core/Ellipsoid.js'; import isArray from '../Core/isArray.js'; import Iso8601 from '../Core/Iso8601.js'; import JulianDate from '../Core/JulianDate.js'; import CesiumMath from '../Core/Math.js'; import Rectangle from '../Core/Rectangle.js'; import ReferenceFrame from '../Core/ReferenceFrame.js'; import Resource from '../Core/Resource.js'; import RuntimeError from '../Core/RuntimeError.js'; import TimeInterval from '../Core/TimeInterval.js'; import TimeIntervalCollection from '../Core/TimeIntervalCollection.js'; import HeightReference from '../Scene/HeightReference.js'; import HorizontalOrigin from '../Scene/HorizontalOrigin.js'; import VerticalOrigin from '../Scene/VerticalOrigin.js'; import when from '../ThirdParty/when.js'; import zip from '../ThirdParty/zip.js'; import BillboardGraphics from './BillboardGraphics.js'; import CompositePositionProperty from './CompositePositionProperty.js'; import ModelGraphics from './ModelGraphics.js'; import RectangleGraphics from './RectangleGraphics.js'; import SampledPositionProperty from './SampledPositionProperty.js'; import SampledProperty from './SampledProperty.js'; import ScaledPositionProperty from './ScaledPositionProperty.js'; var BILLBOARD_SIZE = 32; var kmlNamespace = 'http://www.opengis.net/kml/2.2'; var gxNamespace = 'http://www.google.com/kml/ext/2.2'; var xmlnsNamespace = 'http://www.w3.org/2000/xmlns/'; // // Handles files external to the KML (eg. textures and models) // function ExternalFileHandler(modelCallback) { this._files = {}; this._promises = []; this._count = 0; this._modelCallback = modelCallback; } var imageTypeRegex = /^data:image\/([^,;]+)/; ExternalFileHandler.prototype.texture = function(texture) { var that = this; var filename; if ((typeof texture === 'string') || (texture instanceof Resource)) { texture = Resource.createIfNeeded(texture); if (!texture.isDataUri) { return texture.url; } // If its a data URI try and get the correct extension and then fetch the blob var regexResult = texture.url.match(imageTypeRegex); filename = 'texture_' + (++this._count); if (defined(regexResult)) { filename += '.' + regexResult[1]; } var promise = texture.fetchBlob() .then(function(blob) { that._files[filename] = blob; }); this._promises.push(promise); return filename; } if (texture instanceof HTMLCanvasElement) { var deferred = when.defer(); this._promises.push(deferred.promise); filename = 'texture_' + (++this._count) + '.png'; texture.toBlob(function(blob) { that._files[filename] = blob; deferred.resolve(); }); return filename; } return ''; }; function getModelBlobHander(that, filename) { return function (blob) { that._files[filename] = blob; }; } ExternalFileHandler.prototype.model = function(model, time) { var modelCallback = this._modelCallback; if (!defined(modelCallback)) { throw new RuntimeError('Encountered a model entity while exporting to KML, but no model callback was supplied.'); } var externalFiles = {}; var url = modelCallback(model, time, externalFiles); // Iterate through external files and add them to our list once the promise resolves for (var filename in externalFiles) { if(externalFiles.hasOwnProperty(filename)) { var promise = when(externalFiles[filename]); this._promises.push(promise); promise.then(getModelBlobHander(this, filename)); } } return url; }; defineProperties(ExternalFileHandler.prototype, { promise : { get : function() { return when.all(this._promises); } }, files : { get : function() { return this._files; } } }); // // Handles getting values from properties taking the desired time and default values into account // function ValueGetter(time) { this._time = time; } ValueGetter.prototype.get = function(property, defaultVal, result) { var value; if (defined(property)) { value = defined(property.getValue) ? property.getValue(this._time, result) : property; } return defaultValue(value, defaultVal); }; ValueGetter.prototype.getColor = function(property, defaultVal) { var result = this.get(property, defaultVal); if (defined(result)) { return colorToString(result); } }; ValueGetter.prototype.getMaterialType = function(property) { if (!defined(property)) { return; } return property.getType(this._time); }; // // Caches styles so we don't generate a ton of duplicate styles // function StyleCache() { this._ids = {}; this._styles = {}; this._count = 0; } StyleCache.prototype.get = function(element) { var ids = this._ids; var key = element.innerHTML; if (defined(ids[key])) { return ids[key]; } var styleId = 'style-' + (++this._count); element.setAttribute('id', styleId); // Store with # styleId = '#' + styleId; ids[key] = styleId; this._styles[key] = element; return styleId; }; StyleCache.prototype.save = function(parentElement) { var styles = this._styles; var firstElement = parentElement.childNodes[0]; for (var key in styles) { if (styles.hasOwnProperty(key)) { parentElement.insertBefore(styles[key], firstElement); } } }; // // Manages the generation of IDs because an entity may have geometry and a Folder for children // function IdManager() { this._ids = {}; } IdManager.prototype.get = function(id) { if (!defined(id)) { return this.get(createGuid()); } var ids = this._ids; if (!defined(ids[id])) { ids[id] = 0; return id; } return id.toString() + '-' + (++ids[id]); }; /** * Exports an EntityCollection as a KML document. Only Point, Billboard, Model, Path, Polygon, Polyline geometries * will be exported. Note that there is not a 1 to 1 mapping of Entity properties to KML Feature properties. For * example, entity properties that are time dynamic but cannot be dynamic in KML are exported with their values at * options.time or the beginning of the EntityCollection's time interval if not specified. For time-dynamic properties * that are supported in KML, we use the samples if it is a {@link SampledProperty} otherwise we sample the value using * the options.sampleDuration. Point, Billboard, Model and Path geometries with time-dynamic positions will be exported * as gx:Track Features. Not all Materials are representable in KML, so for more advanced Materials just the primary * color is used. Canvas objects are exported as PNG images. * * @exports exportKml * * @param {Object} options An object with the following properties: * @param {EntityCollection} options.entities The EntityCollection to export as KML. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for the output file. * @param {exportKml~ModelCallback} [options.modelCallback] A callback that will be called with a {@link ModelGraphics} instance and should return the URI to use in the KML. Required if a model exists in the entity collection. * @param {JulianDate} [options.time=entities.computeAvailability().start] The time value to use to get properties that are not time varying in KML. * @param {TimeInterval} [options.defaultAvailability=entities.computeAvailability()] The interval that will be sampled if an entity doesn't have an availability. * @param {Number} [options.sampleDuration=60] The number of seconds to sample properties that are varying in KML. * @param {Boolean} [options.kmz=false] If true KML and external files will be compressed into a kmz file. * * @returns {Promise} A promise that resolved to an object containing the KML string and a dictionary of external file blobs, or a kmz file as a blob if options.kmz is true. * @demo {@link https://sandcastle.cesium.com/index.html?src=Export%20KML.html|Cesium Sandcastle KML Export Demo} * @example * Cesium.exportKml({ * entities: entityCollection * }) * .then(function(result) { * // The XML string is in result.kml * * var externalFiles = result.externalFiles * for(var file in externalFiles) { * // file is the name of the file used in the KML document as the href * // externalFiles[file] is a blob with the contents of the file * } * }); * */ function exportKml(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var entities = options.entities; var kmz = defaultValue(options.kmz, false); //>>includeStart('debug', pragmas.debug); if (!defined(entities)) { throw new DeveloperError('entities is required.'); } //>>includeEnd('debug'); // Get the state that is passed around during the recursion // This is separated out for testing. var state = exportKml._createState(options); // Filter EntityCollection so we only have top level entities var rootEntities = entities.values.filter(function(entity) { return !defined(entity.parent); }); // Add the var kmlDoc = state.kmlDoc; var kmlElement = kmlDoc.documentElement; kmlElement.setAttributeNS(xmlnsNamespace, 'xmlns:gx', gxNamespace); var kmlDocumentElement = kmlDoc.createElement('Document'); kmlElement.appendChild(kmlDocumentElement); // Create the KML Hierarchy recurseEntities(state, kmlDocumentElement, rootEntities); // Write out the