Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds KmlExporter class for exporting an EntityCollection as KML #7921

Merged
merged 29 commits into from
Jun 26, 2019

Conversation

tfili
Copy link
Contributor

@tfili tfili commented Jun 7, 2019

CZML or the Entity API don't have a one to one mapping into KML, but we try our best. This handles the following:

  • Constant Points/Billboards -> Point geometry
  • Time Dynamic Points/Billboards/Models/Paths - > gx:Track geometry
  • Polylines -> LineString geometry
  • Polygons -> Polygon geometry
  • Constant Model -> Model geometry

Options to exportKml.

  • entities: EntityCollection to export.
  • ellipsoid: Since KML uses degrees, we can control what Ellipsoid to use to convert from cartesian to cartographic. Defaults to WGS84.
  • modelCallback: Textures and Models have callbacks that are specified so the user can say how to handle the external files. I may actually just return the textures as blobs instead, but I'm not sure.

Since KML isn't truly dynamic, so we take some parameters to deal with it

  • time: The time to sample properties that aren't time dynamic in KML at all (eg color, etc).
  • defaultAvailability: For properties that are time dynamic without samples, we will sample across their availability. If an entity has an non-infinite availability we will use it. Otherwise we will use this. Defaults to the computeAvailability() of the entity collection.
  • sampleDuration: The frequency to the sampling in seconds. Defaults to 60.

There are tests for all these. You can test using the CZML Sandcastle examples and just adding the following code

function download(filename, data) {
    var blob = new Blob([data], { type: 'application/xml' });
    if (window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    } else {
        var elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;
        document.body.appendChild(elem);
        elem.click();
        document.body.removeChild(elem);
    }
}

// Call the following after the datasource is loaded
var kml = exportKml({
      entities: dataSource.entities
   })
  .then(function(result) {
    download('polylines.kml', result.kml);
  });

TODO:

  • Add textures GlobeOverlays

@cesium-concierge
Copy link

Thanks for the pull request @tfili!

  • ✔️ Signed CLA found.
  • CHANGES.md was not updated.
    • If this change updates the public API in any way, please add a bullet point to CHANGES.md.

Reviewers, don't forget to make sure that:

  • Cesium Viewer works.
  • Works in 2D/CV.
  • Works (or fails gracefully) in IE11.

@tfili tfili requested a review from OmarShehata June 7, 2019 20:31
Copy link
Contributor

@OmarShehata OmarShehata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super super cool! This is a GeoJSON pulled into Cesium, that's extruded, and then exported as a KML and viewed in Google Earth pro:

kml_cesium

So you can use CesiumJS, the visualization library, as a neat pipeline of sorts of almost.

I do have some suggestions about the usability here:

1) I think modelCallback and textureCallback are confusing

I almost feel like modelCallback should be required if a model is present, since there's no scenario where it will work as exported directly, since you at least need to replace the glTF with Collada.

It's also confusing because textureCallback takes in 3 possible different types of arguments, and it's not clear what the data types are (it's also not clear that it passes in a time parameter, which I only discovered from reading the specs).

I think you'll either need to fix the doc, and make this easier with some code examples, or design it so it's a function that takes in the URL and modifies it, instead of taking in the source object. In this design, they would be called imageUrlCallback and modelUrlCallback. The imageUrl callback would essentially run on the output of:

function defaultTextureCallback(texture) {
            if (typeof texture === 'string') {
                return texture;
            }

            if (texture instanceof Resource) {
                return texture.url;
            }

            if (texture instanceof HTMLCanvasElement) {
                return texture.toDataURL();
            }

            return '';
        }

So all you need to do is modify the correct path to the URL, and same thing for the model.

2) Why can't I specify a global start/stop time for sampling the entities to export?

It looks like defaultAvailability gets overridden by the entities availability. But this means I can't take the CZML satellites example and export a full rotation of the satellites. I feel like this is a simpler way to think about it and gives the user more control? Here's what I was trying to do:

var timeInterval = new Cesium.TimeInterval({
    start : Cesium.JulianDate.fromIso8601('2012-03-15T10:27:12.7559055118181277Z'),
    stop : Cesium.JulianDate.fromIso8601("2012-03-16T09:45:15.5905511811142787Z"),
    isStartIncluded : true,
    isStopIncluded : true,
});

var exporter = new Cesium.KmlExporter(dataSource.entities, {
    defaultAvailability: timeInterval
});

var kml = exporter.toString();    
download('test.kml', kml);

But I only get just a small slice of the full rotation:

transparent_kml_small

3) Time dynamic lines are transparent instead of disappearing?

Is this intentional? It's more obvious on the green line in the GIF above. I can see all the instances of the line, the ones that are not "in this frame" are slightly transparent. It's kind of cool but perhaps you might not want to see this?

4) Using data URIs for images

I think converting images to dataURIs would be nice in that it would be one more step to make it work out of the box, but then there would be no way to have external image files as is common in KML. I think I'd rather go with the imageUrlCallback and the user can then decide what they want to do with that URL.

5) Some missing coverage spots

Overall code coverage looks pretty good! Looks like it's missing a test for labelGraphics, multi geometry, and entity.path.

label_0_coverage

multi_geometry

path_0_coverage

Here's the full coverage for KmlExporter to save you some time:

KMLExporter_Coverage.zip

var kml = exporter.toString();
download('test.kml', kml);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test here for just debug? Should this be removed? Similarly with the download function here.

* @param {Function} [options.modelCallback] A callback that will be called with a ModelGraphics instance and should return the URI to use in the KML. By default it will just return the model's uri property directly.
* @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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a code example here if we're not creating a Sandcastle example.
You also might want to explain some context here about how time dynamic entities are handled or the caveats about this class (that there isn't a 1:1 mapping so don't expect it to look exactly the same etc?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this comment. There should be a long-form paragraph before the params with everything @OmarShehata mentioned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I added an example and a paragraph about how we basically handle everything.

@tfili
Copy link
Contributor Author

tfili commented Jun 19, 2019

Thanks @OmarShehata.

@mramato you want to take a look now that the first pass comments have been addressed.

@OmarShehata
Copy link
Contributor

Some todos here:

@mramato
Copy link
Contributor

mramato commented Jun 21, 2019

I actually haven't finished reviewing this yet. Going to look now.

* });
*
*/
function KmlExporter(entities, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a class instead of a standalone function? Why not just exportKml(options) where options.entities is required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I had bigger plans. I was thinking they could work with the DOM object before exporting. I'll switch it.

styleCache.save(kmlDocumentElement);
}

KmlExporter.prototype.export = function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming there's a good reason to keep this as a class, this would need doc.

*
* @param {EntityCollection} entities The EntityCollection to export as KML
* @param {Object} options An object with the following properties:
* @param {Function} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for the output file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Ellipsoid not Function.

* @param {EntityCollection} entities The EntityCollection to export as KML
* @param {Object} options An object with the following properties:
* @param {Function} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for the output file
* @param {Function} [options.modelCallback] A callback that will be called with a ModelGraphics instance and should return the URI to use in the KML. This will throw a RuntimeError if there is a model and this is undefined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually define the specific callback as a type instead of just Function so its crystal clear what parameters are expected. Search for TimeInterval~MergeCallback as an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I think that's what @OmarShehata was referring to and not textureCallback. I'll add the callback doc and that should keep things straight.

@mramato
Copy link
Contributor

mramato commented Jun 21, 2019

  • textureCallback

@OmarShehata Unless I'm mistaken, textureCallback is gone. All files are now returned as both hrefs and blobs.


processMaterial(that, polylineGraphics.material, lineStyle);

// TODO: <gx:physicalWidth>?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this TODO (write up an issue if it's something we still need to handle)

/**
* An object with filename and blobs for external files
* @memberof ExternalFileHandler.prototype
* @type {ObjectId}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, does this even need doc? Same as the promise before it, isn't this an internal class?

@mramato
Copy link
Contributor

mramato commented Jun 21, 2019

Please run generateDocumentation and look at the doc from an end user's point of view and fix/update/improve accordingly.

@OmarShehata this is also something that's good to remember to do when reviewing PRs that include branch new API/classes as well. It's really easy to write JSDoc that doesn't error but doesn't turn out quite right either.

@mramato
Copy link
Contributor

mramato commented Jun 21, 2019

Functionality itself looks great, we just need to polish it up from the end user's point of view.

There's probably no reason for me to look at this again unless something new comes up. @OmarShehata please merge once both our comments are addressed and you're happy.

@tfili
Copy link
Contributor Author

tfili commented Jun 21, 2019

@mramato @OmarShehata I think this should be good now.

@tfili
Copy link
Contributor Author

tfili commented Jun 24, 2019

Added KMZ support. Just specify kmz: true and it will export that instead. @OmarShehata, can you take a look.

function downloadBlob(filename, blob) {
    if (window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    } else {
        var elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;
        document.body.appendChild(elem);
        elem.click();
        document.body.removeChild(elem);
    }
}

viewer.dataSources.add(Cesium.CzmlDataSource.load('../../SampleData/Vehicle.czml'))
    .then(function(dataSource) {
        return Cesium.exportKml({
            entities: dataSource.entities,
            kmz: true
        });
    })
    .then(function(result) {
        downloadBlob('vehicle.kmz', result.kmz);
    });

@OmarShehata
Copy link
Contributor

@tfili this is all working great! The KMZ output is super useful, I imagine most people will want to use it that way. Mostly doc/wording comments below.

  • In exportKML.js, replace A callback that will be called with a ModelGraphics instance with A callback that will be called with a {@link ModelGraphics} instance
  • For modelCallback doc, replace This will throw a RuntimeError if there is a model and this is undefined. with Required if a model exists in the entity collection. so it's more concise.
  • As a user, this doc isn't very meaningful: Function interface for merging interval data. The description of model callback in the arguments makes a lot more sense to me. Consider saying something like Since KML does not support glTF models, this callback is required to specify what URL to use for the model in the KML document.
  • entities and ellipsoid doc missing a period at the end.
  • KMZ boolean parameter missing default?

I made some tweaks to the top paragraph doc, adding code ticks and doc @links, give it a read and if it's accurate you can just use it:

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.

  • Change the runtime error Model callback must be specified if there are models in the entity collection to Encountered a model entity while exporting to KML, but no model callback was supplied. to add a little more context (also it's not "if" in this case, a model was found).

This was referenced Jun 25, 2019
@tfili
Copy link
Contributor Author

tfili commented Jun 25, 2019

Thanks @OmarShehata. I updated the doc, so this should be ready to go.

@OmarShehata OmarShehata merged commit d710bf8 into master Jun 26, 2019
@OmarShehata OmarShehata deleted the kml-export branch June 26, 2019 15:29
@liyangis
Copy link

liyangis commented Sep 4, 2019

if czml contains model(gltf contians .bin),does it can be exported?I try it,but failed

@liyangis
Copy link

liyangis commented Sep 4, 2019

It can't be opened in Sandcastle Demo named KML,what is exported by the Sandcastle Demo named Export KML for Model in the dropdown list

@tfili
Copy link
Contributor Author

tfili commented Sep 4, 2019

@liyangis KML only supports COLLADA models and Cesium only supports glTF models. For that reason, when exporting a KML document you must specify a modelCallback to write out the correct model information in a KML supported format.

Unfortunately, KML loading in Cesium doesn't support model features because there isn't support for COLLADA in Cesium. For this reason you won't be able to export a CZML with models as a KML and reimport it into Cesium.

@liyangis
Copy link

liyangis commented Sep 5, 2019

@liyangis KML only supports COLLADA models and Cesium only supports glTF models. For that reason, when exporting a KML document you must specify a modelCallback to write out the correct model information in a KML supported format.

Unfortunately, KML loading in Cesium doesn't support model features because there isn't support for COLLADA in Cesium. For this reason you won't be able to export a CZML with models as a KML and reimport it into Cesium.

Thank you!
But How to thansfor .gltf to .dae,is there any tools ?

@OmarShehata
Copy link
Contributor

@liyangis I've found assimp to be useful for these kinds of conversions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants