Problematic Behavior When Adding/Removing Labels

We've been running into some issues with Cesium labels. I've created an example that illustrates.

Basically all I'm doing is creating a set of labels by first adding the label collection, then creating labels and adding them one by one.

Then I remove some labels, and bad things happen. This is what I'm observing:

Remove three: Nothing bad happens until you try to clear, then you get errors.
Remove four or five: Everything is fine for a second, then Cesium dies.
Remove six: Everything is fine for a second, then the last remaining label
inexplicably disappears.

http://cesium.agi.com/Cesium/Apps/Sandcastle/?src=Billboards.html

Replace Javascript with this:

require([
    'Source/Cesium', 'Widgets/Dojo/CesiumWidget',
    'dojo/on', 'dojo/dom', 'dijit/form/Button'
], function(
    Cesium, CesiumWidget,
    on, dom, Button)
{

    "use strict";
   
    /* Create some global variables */
    var pointArray;
    var textArray;
    var billboards;
    var labels;
    
    function addBillboards(scene, ellipsoid) {
    
        /* Set up some variables to use */
        pointArray = ;
        textArray = ;
        billboards = new Cesium.BillboardCollection();
        labels = new Cesium.LabelCollection();
    
        /* Add collections */
        widget.scene.getPrimitives().add(billboards);
        widget.scene.getPrimitives().add(labels);
        
        /* Set up canvas */
        var canvas = document.createElement('canvas');
        canvas.width = 16;
        canvas.height = 16;
        var context2D = canvas.getContext('2d');
        context2D.beginPath();
        context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true);
        context2D.closePath();
        context2D.fillStyle='rgb(255, 255, 255)';
        context2D.fill();
        var textureAtlas = widget.scene.getContext().createTextureAtlas({image : canvas});
        billboards.setTextureAtlas(textureAtlas);
        
        /* Add seven points */
        for (var i = 0; i < 7; i++) {
            
            /* Create label */
            var text = new Cesium.Label({}, labels);
            text = labels.add(text);
            
            text.setPosition(ellipsoid.cartographicToCartesian(
                Cesium.Cartographic.fromDegrees(-75.59777+Math.pow(-1, i)*i, 40.03883+i)));
            text.setText("Point"+i);
            textArray.push(text);
        }
        
    }
    
    function removeThree(scene, ellipsoid) {
        /* Remove points */
        for (var i = 0; i < 3; i++) {
            labels.remove(textArray[i]);
        }
    }
    function removeFour(scene, ellipsoid) {
        /* Remove points */
        for (var i = 0; i < 4; i++) {
            labels.remove(textArray[i]);
        }
    }
    function removeFive(scene, ellipsoid) {
        /* Remove points */
        for (var i = 0; i < 5; i++) {
            labels.remove(textArray[i]);
        }
    }
    function removeSix(scene, ellipsoid) {
        /* Remove points */
        for (var i = 0; i < 6; i++) {
            labels.remove(textArray[i]);
        }
    }
    
    function createButtons(widget) {
        var ellipsoid = widget.ellipsoid;
        var scene = widget.scene;
        var primitives = scene.getPrimitives();
        
        new Button({
            label: 'Add points',
            onClick: function() {
                primitives.removeAll();
                addBillboards(scene, ellipsoid);
            }
        }).placeAt('toolbar');
        
        new Button({
            label: 'Remove Three',
            onClick: function() {
                removeThree(scene, ellipsoid);
            }
        }).placeAt('toolbar');
        new Button({
            label: 'Remove Four',
            onClick: function() {
                removeFour(scene, ellipsoid);
            }
        }).placeAt('toolbar');
        new Button({
            label: 'Remove Five',
            onClick: function() {
                removeFive(scene, ellipsoid);
            }
        }).placeAt('toolbar');
        new Button({
            label: 'Remove Six',
            onClick: function() {
                removeSix(scene, ellipsoid);
            }
        }).placeAt('toolbar');
        
        new Button({
            label: 'Clear',
            onClick: function() {
                primitives.removeAll();
            }
        }).placeAt('toolbar');
    }

    var widget = new CesiumWidget();
    widget.placeAt(dom.byId('cesiumContainer'));
    widget.startup();
    dom.byId('toolbar').innerHTML = '';

    createButtons(widget);
});

Thanks David, I’m looking into this now.

David, first off, thanks for finding and reporting this bug. Using your example I was able to determine the cause and fix it. I’ve opened this pull request with the solution:

https://github.com/AnalyticalGraphicsInc/cesium/pull/452

I realize your example code was written to reproduce the issue you were having (which it did) but there were two things I thought I would point out.

  1. You should not create new instances of Cesium.Label yourself, instead you should always call “add” on the LabelCollection. I’m not a big fan of this paradigm, but for now it’s how it is done.

  2. Once a label is removed from the collection, it is destroyed and cannot be re-used. It’s possible in your example code to get into a case where it re-adds destroyed labels.

Thanks,

Matt

Thanks, Matt!

Thanks also for the advice on using LabelCollection. I wonder if at some point y'all might create IDs for labels and billboards and such. I say this because we have these Cesium objects associated with OpenLayers features, and can only keep track of the association by carrying around references to the Cesium objects (leaving open the possibility you mentioned in #2). If we were able to use some kind of ID instead of an object reference, it would probably be better practice.

Thanks again,
-- David