Javascript closure: How to add dynamic baselayers to Cesium map

249 views Asked by At

I am attempting to add multiple baselayers to a Cesium map from a set of values in a database. I would like the javascript to just iterate over them and then add them. Here is the static code example from Cesium:

var imageryViewModels = [];
imageryViewModels.push(new Cesium.ProviderViewModel({
     name : 'Open\u00adStreet\u00adMap',
     iconUrl : Cesium.buildModuleUrl('openStreetMap.png'),
     tooltip : 'OpenStreetMap (OSM)',
     creationFunction : function() {
         return Cesium.createOpenStreetMapImageryProvider({
             url : 'https://a.tile.openstreetmap.org/'
         });
     }
 }));

 imageryViewModels.push(new Cesium.ProviderViewModel({
     name : 'Black Marble',
     iconUrl : Cesium.buildModuleUrl('blackMarble.png'),
     tooltip : 'outlines of civilization',
     creationFunction : function() {
         return Cesium.createTileMapServiceImageryProvider({
             url : 'https://cesiumjs.org/blackmarble',
             credit : 'B Observatory',
             flipXY : true
         });
     }
 }));

Super simple so far with a set of values that are static. Here is where it gets tricky, I want to loop over a set of values and then push the ProviderViewModels on to the array. Like this -

var imageryViewModels = [];
baseLayers = HashOfValuesFromDatabase;
for(int i=0; i<baseLayers.length; i++{

  imageryViewModels.push(new Cesium.ProviderViewModel({
       name : baseLayers[i].name,
       iconUrl : Cesium.buildModuleUrl(baseLayers[i].iconUrl),
       tooltip : baseLayser[i].toolTip,
       creationFunction : function() {
           return Cesium.createOpenStreetMapImageryProvider({
               url : baseLayers[i].url   // ***** this value always the last value ****
           });
       }
   }));

}

The problem is that the javascript closure is causing me problems and every one of my imageryViewModels is the last value in the baseLayer.url object. Cesium needs this to be a function here. Is there a way to keep this a function but get the values of the iterated baseLayer.url?

2

There are 2 answers

0
emackey On BEST ANSWER

Ben Aston's answer is the correct approach here. He didn't try running it, and there's an extra semicolon after the }(i)) that's illegal inside an argument list, and there are also some problems he copy-pasted from your original question. Here's the corrected version, and I did get this to run:

var imageryViewModels = [];
for(var i=0; i<baseLayers.length; i++) {

  imageryViewModels.push(new Cesium.ProviderViewModel({
       name : baseLayers[i].name,
       iconUrl : Cesium.buildModuleUrl(baseLayers[i].iconUrl),
       tooltip : baseLayers[i].toolTip,
       creationFunction : (function(i) {
           return function() {
             return Cesium.createOpenStreetMapImageryProvider({
                 url : baseLayers[i].url  
             });
           };
       }(i))
   }));
}

The reason this fixes the url parameter is because of the IIFE (immediately invoked function expression) that uses i as a parameter. This creates a new JavaScript closure per iteration through the loop, which is expensive, but is required so that each individual create function gets its very own closure with its own copy of the value of i from that particular iteration of the loop. Without this, there is only one closure with only one i variable, and by the time any of the create functions are actually called, the loop is long since finished and the value of i has reached its maximum.

0
Ben Aston On

Please try this (I haven't run it):

var imageryViewModels = [];
baseLayers = HashOfValuesFromDatabase;
for(int i=0; i<baseLayers.length; i++{

  imageryViewModels.push(new Cesium.ProviderViewModel({
       name : baseLayers[i].name,
       iconUrl : Cesium.buildModuleUrl(baseLayers[i].iconUrl),
       tooltip : baseLayser[i].toolTip,
       creationFunction : (function(i) {
           return function() {
             return Cesium.createOpenStreetMapImageryProvider({
                 url : baseLayers[i].url  
             });
           }
       }(i))
   }));

}

Edit: correct syntax (thank you to emackey)