0

I'm attempting to adapt the GEE community tutorial for creating interactive selection areas for analysis, tutorial here. I want to adapt the code to use a dropdown menu via ui.select to turn on and off the geometry tools, instead of individual buttons for each tool.

However, I'm running into an issue with the drawing functions firing recursively, wherein each time the drop down menu (ui.select) is changed, the callback function seems to fire n+1 times for n times the menu has changed. onChange, the menu widget summary triggers a function which includes geometryTools(). the geometryTools() then:

  1. clear any existing geometry layers

  2. retrieve the value of the summary widget, and turn on the corresponding geometry tool (Map.drawingTools().setShape(XXX)).

  3. trigger the drawingTools callback .onDraw to listen for the selected drawing tool to fire.

  4. When a geometry is drawn, fire the applyDraw() function.

It appears that the applyDraw() function gets fired n+1 times for every nth change change to the menu selection or editing a drawn geometry. This makes me think that the issue lies in how the .onDraw callback is nested in the function. but I can't figure out WHY

Any insights?

/** Set up some options for the selection widget **/
var SUMMARY_OPTIONS = {
    'NONE': {
      description: 'Do not generate timeseries',
      mode: 'NONE'
    },
    'POINT': {
      description: 'Select a location to summarize',
      mode: 'POINT'
    },
    'RECTANGLE': {
      description: 'Select an area to summarize',
      mode: 'RECTANGLE'
    },
    'POLYGON': {
      description: 'Draw an area to summarize',
      mode: 'POLYGON'
    },
    'FEATURE': {
      description: 'Select a feature to summarize',
      mode: 'LAYER'
    },
};
print(SUMMARY_OPTIONS);

  /** intialize the summarization geometry widgets  **/
var summary = {
    label: ui.Label(),
    
    // Create a select with a function that reacts to the "change" event.
    select: ui.Select({
      placeholder: 'Summarize by:',
      items: Object.keys(SUMMARY_OPTIONS),
      onChange: function() {
        
        // Update the label's value with the select's description.
        var option = SUMMARY_OPTIONS[summary.select.getValue()];
        
        summary.label.setValue(option.description);
        
        geometryTools();

      }
    })
  };

  // The panel for the summary section with corresponding widgets.
var summarypanel = ui.Panel({
    widgets: [
      ui.Label('3) Example:', {fontWeight: 'bold'}),
      summary.select,
      summary.label
    ]
  });

// Add the summary widget panel to the map
Map.add(summarypanel);


  /** Function to Clear Geometry Layers **/
  var clearGeometry = function(){
      var layers = Map.drawingTools().layers();
      layers.get(0).geometries().remove(layers.get(0).geometries().get(0));
    
  };

  /** Enable Drawing Tools **/
var geometryTools = function(){
    
    while (Map.drawingTools().layers().length() > 0) {
      var layer = Map.drawingTools().layers().get(0);
      Map.drawingTools().layers().remove(layer);
    }
    
    var dummyGeometry =ui.Map.GeometryLayer({
      geometries: null, 
      name: 'geometry', 
      color: '23cba7'
    });
    
    Map.drawingTools().layers().add(dummyGeometry);
    

    function drawRectangle() {
      clearGeometry();
      Map.drawingTools().setShape('rectangle');
      // drawingTools.draw();
    }
    
    function drawPolygon() {
      clearGeometry();
      Map.drawingTools().setShape('polygon');
      // drawingTools.draw();
    }
    
    function drawPoint() {
      // clearGeometry();
      Map.drawingTools().setShape('point');
      // drawingTools.draw();
    }
    
    var geo = summary.select.getValue();
    if(summary.select.getValue() === 'POINT'){
      drawPoint();
      print('running the IF statement for drawpoint');
    }else if(summary.select.getValue() === 'RECTANGLE'){
      drawRectangle();
      print('running the IF statement for drawrectangle');
    } else if(summary.select.getValue() === 'POLYGON'){
      drawPolygon();
      
    }else {
      Map.drawingTools.setShape(null);
    }
   
    Map.drawingTools().onDraw(ui.util.debounce(applyDraw, 500));
    Map.drawingTools().onEdit(ui.util.debounce(applyDraw, 500));
  };
  
  /** Define what happens when the drawing tools are appplied **/
  var applyDraw = function(){
    print("the drawingtool set to ",Map.drawingTools().getShape()," according to applyDraw(): ");
  } 
2

The problem is you're adding a new callback function to .onDraw() every time a selection is made. You need to only add the callback one time. It looks like you were using ui.util.debounce to mitigate this, but that won't work.

Just move the calls to .onDraw and onEdit out of geometryTools() (as well as the applyDraw function declaration).

| improve this answer | |
  • That was the solution! Would have sworn I'd tried that already. Its not clear to me though why including it within the loop has an additive effect on the number of times it triggers. Once you telling drawingTools what to do onDraw once, shouldn't calling it again overwrite the previously assigned operation? Or is it an application of 'mutability' outlined in the documentation here: link – W. Kessler Aug 28 at 15:20
  • 1
    Well yeah, it is an application of mutability but moreover that's just how javascript events work...you can add multiple handlers to the same event. – blindjesse Aug 28 at 15:32
  • great, thank you for the additional clarification! – W. Kessler Aug 28 at 15:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.