How to Load KML without Viewer Widget

Hi,
I would like to load some KML/KMZ files, but I'm having a difficult time figuring out how to do this without using the viewer widget. We use Cesium without the viewer. Can someone point me in the right direction to accomplish this?

Thanks,
Rob

Based on some code I found in the forum for loading CZML without the viewer widget I am trying the following approach...

    var ds = new Cesium.KmlDataSource();
    ds.load('path_to_kmz');
    var dsDisplay = new Cesium.DataSourceDisplay(scene);
    dsDisplay.getDataSources().add(ds);

However, I am getting an error that states "Uncaught TypeError: Cannot read property 'dataSourceAdded' of undefined".

Here’s a complete example of using KML with the CesiumWidget You can paste it into Sandcastle to see it in action.

var cesiumWidget = new Cesium.CesiumWidget(‘cesiumContainer’);

var dataSources = new Cesium.DataSourceCollection();

var dataSourceDisplay = new Cesium.DataSourceDisplay({

scene: cesiumWidget.scene,

dataSourceCollection: dataSources

});

//dataSourceDisplay.update needs to be called once a frame after all other updates have been made, in this case we call it in the preRender event.

cesiumWidget.scene.preRender.addEventListener(function(scene, time){

dataSourceDisplay.update(time);

});

//Now that everything is configured, we can load KML and add to list of data sources.

dataSources.add(Cesium.KmlDataSource.load(’…/…/SampleData/kml/facilities/facilities.kml’));

Hi Matthew,

Thank you for the detailed reply!

However, can you describe how to do this without using the CesiumWidget? We are creating a canvas object and passing that to the creation of the Cesium scene. We are then creating a Cesium globe.

Is there a way to do this without the Cesium viewer or the CesiumWidget?

Thanks again

You don’t need the CesiumWidget, just an instance to the Scene. If you look at my code I’m only using the scene property off of the widget and nothing else. Just replace cesiumWidget.scene with your own scene.

That being said, I’m curious as to why you are not just using CesiumWidget? It takes care of a lot of grunt work for you.

I thought that might be the case with the scene object so I am working on modifying your code to simply use the scene object we have. However, I'm still getting the error message "Uncaught TypeError: Cannot read property 'dataSourceAdded' of undefined". I'm assuming this message is coming from the DataSourceCollection?

Unless I'm mistaken or things have changed... the widget has built in GUI elements (user controls, etc) that we didn't want to display. Maybe using the widget and somehow not showing these controls might be an option?

CesiumWidget has no GUI whatsoever, it’s what you should use if you just want a glove and provide your own UI.

Viewer does have a bunch of default UI, but it can be selectively turned off by passing options to the constructor. See the documentation for the full list.

It sounds like you should be using CesiumWidget.

The exception you are seeing sounds like a bug in your own code when constructing the DataSourceDisplay. If you can’t get it working, please post your own code for us to look at.

Maybe we should look into using the widget. I think it will take a bit of work to update all of our code, but it may be worth it. I will look into this.
Below the code we use to set up Cesium (with a few items stripped out)...

The code to load the KMZ is at the bottom.

I'm still getting the error I described in the earlier posts.

var canvas = document.createElement('canvas');
canvas.className = "fullSize";

if (!document.getElementById("divCanvas")) {
    var divCesiumContainer = document.createElement("div");
    divCesiumContainer.id = "divCanvas";
    document.body.appendChild(divCesiumContainer);
}
document.getElementById("divCanvas").appendChild(canvas);

scene = new Cesium.Scene({
    canvas: canvas
});

scene.globe = new Cesium.Globe();
scene.globe.baseColor = Cesium.Color.BLACK;

scene.fxaa = true;
scene.skyAtmosphere = new Cesium.SkyAtmosphere();
var skyBoxBaseUrl = "resources/plugins/cesium/Cesium/Assets/Textures/SkyBox/tycho2t3_80";
scene.skyBox = new Cesium.SkyBox({
    sources : {
        positiveX : skyBoxBaseUrl + '_px.jpg',
        negativeX : skyBoxBaseUrl + '_mx.jpg',
        positiveY : skyBoxBaseUrl + '_py.jpg',
        negativeY : skyBoxBaseUrl + '_my.jpg',
        positiveZ : skyBoxBaseUrl + '_pz.jpg',
        negativeZ : skyBoxBaseUrl + '_mz.jpg'
    }
});

scene.scene3DOnly = true;

// Tile imagery
scene.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerImageryProvider({ url:‘http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer’ }));

// Terrain
var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider({
    url: ‘http://cesiumjs.org/stk-terrain/tilesets/world/tiles’,
    credit: ‘’
});
scene.terrainProvider = cesiumTerrainProviderMeshes;

scene.globe.enableLighting = false;
scene.globe.showNight = false;
scene.globe.showDay = false;
scene.globe.affectedByLighting = false;

scene.debugShowFramesPerSecond = false;

if (blnShowFPS) {
    var performanceContainer = document.createElement('div');
    performanceContainer.className = 'cesium-performanceDisplay';
    performanceContainer.style.position = 'absolute';
    performanceContainer.style.bottom = '60px';
    performanceContainer.style.right = '10px';
    document.getElementById("divCanvas").appendChild(performanceContainer);
    var performanceDisplay = new Cesium.PerformanceDisplay({container: performanceContainer});
}

var lastCamera = {position: Cesium.Cartesian3.clone(scene.camera.position),
                  direction: Cesium.Cartesian3.clone(scene.camera.direction),
                  up: Cesium.Cartesian3.clone(scene.camera.up),
                  right: Cesium.Cartesian3.clone(scene.camera.right),
                  transform: Cesium.Matrix4.clone(scene.camera.transform)
};

function animate() {

    if (blnShowFPS && Cesium.defined(performanceDisplay)) performanceDisplay.update();

    refreshAnimatedObjects();
    if (scene.camera) {
        if (!scene.camera.position.equals(lastCamera.position) ||
            !scene.camera.direction.equals(lastCamera.direction) ||
            !scene.camera.up.equals(lastCamera.up) ||
            !scene.camera.right.equals(lastCamera.right) ||
            !scene.camera.transform.equals(lastCamera.transform)) {
                lastCamera = {position: Cesium.Cartesian3.clone(scene.camera.position),
                              direction: Cesium.Cartesian3.clone(scene.camera.direction),
                              up: Cesium.Cartesian3.clone(scene.camera.up),
                              right: Cesium.Cartesian3.clone(scene.camera.right),
                              transform: Cesium.Matrix4.clone(scene.camera.transform)
                };
                EVENT_CameraChanged.dispatch();
        }
    }
}

function tick() {
    scene.initializeFrame();
    animate();
    scene.render();
    Cesium.requestAnimationFrame(tick);
}
tick();

// Prevent right-click from opening a context menu.
canvas.oncontextmenu = function() {
    return false;
};

// Resize handler
var onResize = function() {

    var width = window.innerWidth;
    var height = window.innerHeight;

    if (canvas.width === width && canvas.height === height) {
        return;
    }

    canvas.width = width;
    canvas.height = height;

    scene.camera.frustum.aspectRatio = width / height;
};
window.addEventListener('resize', onResize, false);
onResize();

scene.camera.setView({
    position : Cesium.Cartesian3.fromDegrees(dblInitLon, dblInitLat, dblInitAlt),
    heading : degreesToRadians(0.0), // east, default value is 0.0 (north)
    pitch : degreesToRadians(-90), // default value (looking down)
    roll : 0.0 // default value
});

// Screen handler
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);

handler.setInputAction(function(movement) {
    // get an array of all primitives at the mouse position
    var pickedObjects = scene.pick(movement.position);
    if(Cesium.defined(pickedObjects)) {
        if (typeof pickedObjects['primitive']['TrackId'] !== 'undefined' && pickedObjects['primitive']['TrackId'] !== null) {
            onModelClick(null,pickedObjects['primitive']['TrackId']);
        }
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

scene.screenSpaceCameraController.tiltEventTypes = [Cesium.CameraEventType.RIGHT_DRAG, Cesium.CameraEventType.PINCH];
scene.screenSpaceCameraController.zoomEventTypes = [Cesium.CameraEventType.WHEEL, Cesium.CameraEventType.PINCH];
scene.screenSpaceCameraController.enableLook = false;

// *********************************************************************
// Test loading a KMZ file
var dataSources = new Cesium.DataSourceCollection();

var dataSourceDisplay = new Cesium.DataSourceDisplay({
    scene: scene,
    dataSourceCollection: dataSources
});

//dataSourceDisplay.update needs to be called once a frame after all other updates have been made,
//in this case we call it in the preRender event.
scene.preRender.addEventListener(function(scene, time){
    dataSourceDisplay.update(time);
});

//Now that everything is configured, we can load KML and add to list of data sources.
dataSources.add(Cesium.KmlDataSource.load('path_to_kmz'));
// *********************************************************************

I've created a simple test page using the Cesium Widget, but I'm still not successfully loading a KMZ file. I'm confident it's not a CORS issue as the test KMZ file is on the server and in the same location as my source files.

I'm not receiving any errors. And I'm successfully getting past the load function.

Below is the contents of the test HTML page (I have Cesium's JS and CSS referenced properly in the page).

<div id="cesiumContainer"></div>
        
<script>
    var widget = new Cesium.CesiumWidget('cesiumContainer');
            
    // Test loading a KMZ file
    var dataSources = new Cesium.DataSourceCollection();

    var dataSourceDisplay = new Cesium.DataSourceDisplay({
        scene: widget.scene,
        dataSourceCollection: dataSources
    });

    //dataSourceDisplay.update needs to be called once a frame after all other updates have been made,
    //in this case we call it in the preRender event.
    widget.scene.preRender.addEventListener(function(scene, time){
        dataSourceDisplay.update(time);
    });
            
    //Now that everything is configured, we can load KML and add to list of data sources.
    dataSources.add(Cesium.KmlDataSource.load('path_to_KMZ'));
            
</script>

I can provide the test KMZ file if desired.

It looks like the code is able to load KMZ/KML files. I just attempted to load a very simple KML, and it loaded successfully. Thank you for the help in getting this far!

It appears there must be something in the KMZ/KML I'm trying to load that is not supported. However, there is no error being thrown and no messages printed to the console. How would I go about determining what items in the file I'm trying to load are not supported (other than manually removing them one by one since this is a 5MB file)?

You should definitely be seeing something in the KMZ for unsupported features, if not it’s a bug. If you can share the KMZ file I’ll take a look and see what’s going on. Glad you got the basic setup working.

Matthew,

Is there a method to upload a file here?

The original version of the KMZ/KML (link below) contains a couple of screen overlays which I have removed in the version I'm testing with (as it appears they are not supported).

Here's the link to the original file...
http://metroplexenvironmental.com/docs/socal_metroplex/Google_Earth_150816/Grid Points - San Diego County.kmz

Hey Matthew,

I was wondering if you've had a chance to check out the KMZ file and determine why I can't load it and why I wouldn't be getting any error or explanation messages in the console.

As I mentioned above I removed the screen overlays that are in the file because my understanding is that they are not supported. But I'm not sure why the rest of the file isn't loading.

Thanks for any help,
Rob

I apologize for the delay, I had looked at it but then my schedule got crazy and I never got a chance to reply.

I think the first problem is that the KMZ file starts with everything turned off, so when you load this into Cesium, everything is off. That being said, I modified the code to ignore that fact and it still had problems. Upon further inspection, it looks like the browser is not properly parsing your KML (which sounds crazy to me) and when I try and get the Document element from the root kml node, I end up with an empty text element. So there’s 3 possible explanations:

  1. The KML file is bad somehow but Google Earth has a workaround to load it anyway.

  2. The KML file is fine but there’s a bug in the browser causing it to be parsed incorrectly.

  3. The KML is fine and there’s a bug in Cesium.

My gut always tells me it’s #3, but right now I’m actually leading towards 1. By tweaking the kml and document elements (where namespaces are defined) I was able to get a subset of your data to load in Chrome. Then I got the idea that maybe the problem was Chrome specific, so I tried to load the original data in Firefox and it gave me the same problem, only this time it spit out another error:

prefix not bound to a namespace

This is an XML namespace error, which tells me there’s got to be something wrong with the KML that the browsers don’t like. Looking at your kmz file, I noticed this in doc.kml:

I think xmlns:kml=“http://www.opengis.net/kml/2.2” may be the culprit here, but I’m not sure. I replaced the entire above line with

and was able to get the folder I extracted to load (although the size of the KML made it take a while, performance was fine once loaded).

So that’s where I’m at. I don’t think it’s a data size problem (at least not yet). I think your KML is not 100% valid XML and the browsers are choking on it. If you are producing these KMZs yourself, you might want to tweak the code to change how you are declaring namespaces and see what that does.

Hope that helps.

I ran into a weird problem with Google Earth once that may actually sorta kinda almost be vaguely relevant here. Somehow, having an “xmlns:xal” namespace inside my KML was keeping GE from properly handling “update” tags. Here’s the SO question I posted at the time before I figured it out and answered it myself: http://stackoverflow.com/questions/7249565/how-to-properly-update-google-earth-kml-using-networklinkcontrol-and-the-java-ap

This is probably not going to actually help, it’s more of a “huh, yeah, weird stuff” comment :slight_smile:

Matthew, thank you for the detailed response and the effort you put into this. And, Mark, thank you for the input as well.

I/we are not creating these KMZ/KML files. I've been asked to see what it would take to read and display them in our JavaScript applications (both Cesium and Leaflet based).

I did notice that the visibility was set to 0 in these files. And as you mentioned I have found the namespace declarations to be an issue. I'm not sure what to do about the namespace issue since I'm not creating the files.

I have broken these files down into smaller pieces and with the namespace changes I have been able to load portions of the files. So we'll have to think about how to tackle this...

Thanks again for your time and help!