Extending Cesium Viewer Widget

1,182 views
Skip to first unread message

jlo...@gmail.com

unread,
Feb 1, 2013, 11:49:01 AM2/1/13
to cesiu...@googlegroups.com
I'm creating an app that wraps the functionality of the Cesium Viewer Widget by overlaying some UI elements and adding some custom business logic, and I was wondering what the best way would be to incorporate the Cesium and Cesium Viewer Widget into my project.

Right now my build script just copies the files from the CesiumViewer app that is in the release zip file, but I noticed I'm having issues if I need to modify anything in the widget (add functions, remove buttons, etc.). Sometimes I can modify things in the widget, but other things seem to be integrated into the dojo.js file in the dojo directory of the CesiumViewer app. Should I be running the Cesium build script within my build instead, or doing something else entirely?

Thanks,

Jon

Scott Hunter

unread,
Feb 1, 2013, 6:38:33 PM2/1/13
to cesiu...@googlegroups.com
Hi Jon,

The Cesium Viewer Widget is unfortunately one of the rougher edges of Cesium at the moment, but there's been a lot of recent discussion about how to improve things. The Dojo-based CesiumViewerWidget was always intended to be merely one of several options for building applications, though we haven't managed to build those alternatives yet.

Today's b13 release changes some things about how the CesiumViewer application is packaged, so the dojo.js file no longer contains the minified application modules.  Instead, a CesiumViewerStartup layer contains all those dependencies.

Are you using a framework to build your application?  If so, which one?  If you need to make customizations to the widget, you may find it easier to work with the individual Source modules and reference those in your application instead of the built files from CesiumViewer.  

For deployment, you can re-run the Cesium build process to re-build the CesiumViewer files with your customizations included, or if your application uses AMD modules directly, you could incorporate the Cesium source modules at your application's level.

In the near future, we hope to start building a framework-independent application engine that can handle most of what the viewer widget does today as far as render loop and data management, with hooks to let external UIs do what they need to do.

Scott


--
You received this message because you are subscribed to the Google Groups "cesium-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cesium-dev+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



Jonathan Lounsbury

unread,
Feb 5, 2013, 3:34:42 PM2/5/13
to cesiu...@googlegroups.com
Scott,

Thank you for the quick reply.  We were previously not using a framework to build the javascript side of our application, but I've since created a dojo-based build for our javascript, referencing the Cesium (b13) AMD modules directly, as you suggested.

It works well, and the minified/optimized production build works great, but we're seeing a strange issue when we try and run the app from the unbuilt javascript.  Everything works as in production, but we have some code which highlights a polygon when the user clicks on it which is not working as expected.  In the development mode, Cesium crashes when a user clicks on a polygon.  The stack trace I get is:

  1. Uncaught TypeError: Cannot assign to read only property 'red' of (1, 1, 0, 1) CzmlColor.js:99
    1. CzmlColor.getValueCzmlColor.js:99
    2. DynamicProperty.getValueDynamicProperty.js:241
    3. DynamicColorMaterial.getValueDynamicColorMaterial.js:73
    4. DynamicMaterialProperty.getValueDynamicMaterialProperty.js:72
    5. DynamicPolygonVisualizer._updateObjectDynamicPolygonVisualizer.js:231
    6. DynamicPolygonVisualizer.updateDynamicPolygonVisualizer.js:104
    7. VisualizerCollection.updateVisualizerCollection.js:119

And here is the highlight code:

onObjectSelected : function(selectedObject)
{
var selected = selectedObject !== undefined;

widget.highlightObject(selectedObject);

var objectSelected = selected && selectedObject.material && selectedObject.material.uniforms;

if (objectSelected)
{
selectedObject.material.uniforms.color = Color.YELLOW;
selectedObjectId = selectedObject.dynamicObject.id;
}
}

Any ideas?  I can open a new thread if you think it's not related to my build.  Again, this code works in the production build.

Thanks again,
Jon

Matthew Amato

unread,
Feb 5, 2013, 3:40:55 PM2/5/13
to cesiu...@googlegroups.com
Try changing

selectedObject.material.uniforms.color = Color.YELLOW;

to

selectedObject.material.uniforms.color = Color.YELLOW.clone(selectedObject.material.uniforms.color);

Color.YELLOW is a frozen object, so it can't be modified.  The CZML processing is trying to modify selectedObject.material.uniforms.color rather than assign it (for performance reasons)  which is leading to this problem.  I think we can probably change the way visualizers work to avoid issues like this in the future, but in general it's a good idea to clone constants rather than assign them directly because you never know who might want to change that value later.

Jonathan Lounsbury

unread,
Feb 5, 2013, 4:10:34 PM2/5/13
to cesiu...@googlegroups.com
Thanks, I'll try that tonight!

Jon

Jonathan Lounsbury

unread,
Feb 5, 2013, 5:49:35 PM2/5/13
to cesiu...@googlegroups.com
Unfortunately this isn't working.  No more crashing, but now there's no change in color.

I notice that if I create primitives on the globe I can highlight/unhighlight them fine, but these objects, which are loaded from CZML, don't seem to respond to any changes in their display properties.

Jon

Scott Hunter

unread,
Feb 6, 2013, 11:39:49 AM2/6/13
to cesiu...@googlegroups.com
I suspect the problem now is that the CZML visualizers are overwriting the color each frame with the color that the CZML data specifies.  The visualizers don't know that you've intentionally overridden the color, so each update it calculates what the color is supposed to be for the current time and sets it.

In the future, we'll have some API for overriding CZML properties, but for now, I can think of two workarounds:

1. Instead of changing the color when the object is selected, change the color of the selected object later, during the update function, after the call to `this.visualizers.update(currentTime);`.  That way your overrides happen after the visualizers set the state of the primitives.

2. (more complicated) Maintain two DynamicObjectCollections, one for the CZML data, and one for client-side overrides.  Combine those two collections into a CompositeDynamicObjectCollection, and give that composite collection to the visualizers.  Then, you can create dynamic properties for your objects in the client-side collection that can return calculated values.  This is how our "Lots of Satellites" demo does highlighting.  Some example code:

var clientObjects = new DynamicObjectCollection();
var satellites = new DynamicObjectCollection();
var compositeCollection = new CompositeDynamicObjectCollection([satellites, clientObjects]);

compositeCollection.objectPropertiesChanged.addEventListener(function(compositeCollectionArg, updatedObjects) {
    // listen for when objects appear in the composite collection
    var objectsWithLabels = [];
    for ( var i = updatedObjects.length - 1; i > -1; i--) {
        var id = updatedObjects[i].id;
        // look up an object in the client collection with the same ID
        var clientObject = clientObjects.getOrCreateObject(id);

        // give the client object a label property if it doesn't have one already
        if (typeof clientObject.label === 'undefined') {
            objectsWithLabels.push(clientObject);

            // make the client object's label's fillColor property return yellow
            // if the object is the selected object, otherwise white
            var dynamicLabel = new DynamicLabel();
            dynamicLabel.fillColor = {
                getValue : function(time) {
                  var selectedObject = cesium.selectedObject;
                  if (selectedObject && id === selectedObject.dynamicObject.id) {
                      return Color.YELLOW;
                  }
                  return Color.WHITE;
              }
            };
            clientObject.label = dynamicLabel;
        }
    }
    // we have keep track of the objects that we've changed so we can notify 
    // the composite collection
    if (objectsWithLabels.length > 0) {
        clientObjects.objectPropertiesChanged.raiseEvent(clientObjects, objectsWithLabels);
    }
});

// populate the server collection from CZML as normal, the client collection will be filled 
// with matching overridden objects.

This will still probably need some changes to the CesiumViewerWidget to allow you to give it a dynamicObjectCollection instead of creating one, but the advantage of this approach is that you can replace any number of properties externally.

Let me know if that makes sense.

Scott

Jonathan Lounsbury

unread,
Feb 6, 2013, 4:08:16 PM2/6/13
to cesiu...@googlegroups.com
Scott,

Makes sense.  As a quick fix we implemented your first option, but since in the future (imminently) we'll need to add things like allowing the user to select multiple CZML objects at a time, we're going to write some more custom code, probably like your second option, to handle that and add it to the CesiumViewerWidget.  I'll keep you updated and let you know what we come up with.

Jon


--

Jonathan Lounsbury

unread,
Feb 6, 2013, 6:00:04 PM2/6/13
to cesiu...@googlegroups.com
Success (for now).  I ended up adding the following functions to the CesiumViewerWidget:


        /**
         * Highlight an object in the scene, usually in response to a click or hover.
         *
         * @function
         * @memberof CesiumViewerWidget.prototype
         * @param {Object} selectedObject - The object to highlight.
         */
        highlightObject : function(selectedObject)
{
if(typeof selectedObject === 'undefined')
{
throw new DeveloperError('selectedObject is required');
}
this.highlightedObjects.add(selectedObject);
        },
        /**
         * Unhighlight an object in the scene.
         *
         * @function
         * @memberof CesiumViewerWidget.prototype
         * @param {Object} selectedObject - The object to unhighlight.
         */
        unhighlightObject : function(selectedObject)
{
if(typeof selectedObject === 'undefined')
{
throw new DeveloperError('selectedObject is required');
}
this.highlightedObjects.remove(selectedObject);
        },
        /**
         * Clear all highlighted objects in the scene.
         *
         * @function
         * @memberof CesiumViewerWidget.prototype
         */
        unhighlightAllObjects : function()
{
this.highlightedObjects.clear();
        },


Then, I added the following code in the "update" method after visualizers.update, per your option 1:

this.highlightedObjects.each(function(highlightedObject) { if (highlightedObject && highlightedObject.material) { highlightedObject.material.uniforms.color.alpha = 0.75; } });
Where highlightedObjects is an instance of my new CesiumObjectCollection class which I based on DynamicObjectCollection:

/*global define*/ define([ ], function( ) { "use strict"; /** * A collection of Object instances. * @alias CesiumObjectCollection * @constructor */ var CesiumObjectCollection = function() { this._hash = {}; this._array = []; }; /** * Gets an object with the specified id. * @param {Object} id The id of the object to retrieve. * * @exception {Error} id is required. * * @returns The Object with the provided id, or undefined if no such object exists. */ CesiumObjectCollection.prototype.get = function(id) { if (typeof id === 'undefined') { throw new Error('id is required.'); } return this._hash[id]; }; /** * Gets the array of Object instances in this composite collection. * @returns {Array} the array of Object instances in this composite collection. */ CesiumObjectCollection.prototype.getAll = function() { return this._array; }; /** * Determines whether an object exists in the collection. * @param {Object} obj The object to check. * * @exception {Error} obj is required. * @exception {Error} obj must have a dynamic object property. * * @returns The Object with the provided id, or undefined if no such object exists. */ CesiumObjectCollection.prototype.contains = function(obj) { var id = getObjectId(obj); return typeof this._hash[id] !== 'undefined'; }; /** * Add the specified object to the collection. * @param {Object} obj The object to add. * * @exception {Error} obj is required. * @exception {Error} obj must have a dynamic object property. * * @returns Whether the collection was changed as a result of the addition. */ CesiumObjectCollection.prototype.add = function(obj) { var id = getObjectId(obj); var existingObj = this._hash[id]; var result = typeof existingObj === 'undefined'; if (result) { this._hash[id] = obj; this._array.push(obj); } return result; }; /** * Removes an object from the collection. * @param {Object} obj The object to remove. * * @exception {Error} obj is required. * @exception {Error} obj must have a dynamic object property. * * @returns Whether the collection was changed as a result of the removal. */ CesiumObjectCollection.prototype.remove = function(obj) { var id = getObjectId(obj); var obj = this._hash[id]; var result = typeof obj !== 'undefined'; if (result) { this._hash[id] = undefined; this._array.splice(this._array.indexOf(obj), 1); } return result; }; /** * Removes all objects from the collection. */ CesiumObjectCollection.prototype.clear = function() { this._hash = {}; this._array = []; }; /** * Performs a function on each object in the collection. * @param {Object} func The function to perform. * * @exception {Error} func is required. * */ CesiumObjectCollection.prototype.each = function(func) { if (typeof func === 'undefined') { throw new Error('func is required.'); } for(var i = 0; i < this._array.length; i++) { func(this._array[i]); } }; function getObjectId(obj) { if (typeof obj === 'undefined') { throw new Error('obj is required.'); } else if(!obj.dynamicObject) { throw new Error('obj must have a dynamic object property.'); } return obj.dynamicObject.id; }; return CesiumObjectCollection; });

Works for now, assuming all objects are inserted from CZML.

Jon
Reply all
Reply to author
Forward
0 new messages