3

Is there any way to convert hand-drawn geometries into a ee.FeatureCollection, ee.Geometry or something similar; in order to be used at the first instance and without having to re-run the script after drawing?

For example, the following code, slightly modified from one of the GEE examples (this is just a demonstration of the issue):

// Load an image.
var image = ee.Image('LANDSAT/LC08/C01/T1_TOA/LC08_044034_20140318');

// Define the visualization parameters.
var vizParams = {
  bands: ['B5', 'B4', 'B3'],
  min: 0,
  max: 0.5,
  gamma: [0.95, 1.1, 1]
};

// Center the map and display the image.
Map.setCenter(-122.1899, 37.5010, 10); // San Francisco Bay
Map.addLayer(image, vizParams, 'false color composite');

var clipButton = ui.Button({
    label: 'Clip image',
    onClick: function() {
      image = image.clip(geometry);
      Map.addLayer(image, vizParams, 'clipped');
    },
    style: {
        color: 'blue',
    },
});

print(clipButton)

If I press Run, later draw a polygon and then press the button the "geometry is not defined" error raises (Despite var geometry is added to imports section). In my case geometries cannot be defined before running the code because areas of interest are discovered after some calculations, so they are random and should be established by the user.

Summarizing, could hand-drawn geometries be recognized after running the code just once as some way and later be used through a button or other?

  • 1
    I found a proper example in the 'App Engine Example Apps' section (developers.google.com/earth-engine/app_engine_examples), but it uses the Python API, so the code includes commands that are not supported on Google Earth Engine web app. Any clues? – gtapko Feb 10 '18 at 4:43
  • Not sure if this is exactly what you want, but did you try the polygon-drawing demo from the examples? The example runs a reduceRegion of elevation based on a hand-drawn polygon. It's pretty slick. – grovduck Jun 8 '18 at 22:35
  • @grovduck I can not see the example that you are referring to. Could you provide me the specific script name or share the code here? – gtapko Oct 3 '18 at 18:22
1
+50

I think the problem is that the ui.* interface doesn't have access to the geometries created using the drawing tool. Those drawn geometries are added as objects in the code, but not layers that can be interacted with via the ui.* methods.

One solution is to build a stand-alone web app that automatically adds a drawn geometry as a layer.

I learned from this email thread on the Developer's list that what you are trying to do may not be possible without building a standalone app. Later in the thread on April 18, 2017, Nicholas Clinton suggests that some of this functionality may be coming in new versions of Earth Engine.

The code below is modified from the web app built by Gennadii Donchyts in that same email thread.

Here's the code you wrote:

// Load an image.
var image = ee.Image('LANDSAT/LC08/C01/T1_TOA/LC08_044034_20140318');

// Define the visualization parameters.
var vizParams = {
  bands: ['B5', 'B4', 'B3'],
  min: 0,
  max: 0.5,
  gamma: [0.95, 1.1, 1]
};

// Center the map and display the image.
Map.setCenter(-122.1899, 37.5010, 10); // San Francisco Bay
Map.addLayer(image, vizParams, 'false color composite');

And a modified version of your clipButton code... The difference is that I define the geometry within the function passed to onClick. The layer_names variable is a character list of all the names of the current layers of the main Map. The idx variable is the index of the Map layer named "geometry". The geometry variable is the Earth Engine object extracted from that geometry layer. Because of the way the web app works, the new layer built by drawing will be called "geometry", but you could change that.

var clipButton = ui.Button({
    label: 'Clip image',
    onClick: function() {
      var layers_as_features = Map.layers().map(function (lyr) {
                                                                var ftr = ee.Feature(lyr.getEeObject().geometry(), 
                                                                                    {name: lyr.getName()})
                                                                return ftr
                                                                });
      var geometry = ee.FeatureCollection(layers_as_features).filter(ee.Filter.eq("name", "geometry"))

      Map.addLayer(image.clip(geometry), vizParams, 'clipped');
    },
    style: {
        color: 'blue',
    },
});

print(clipButton);

Note: there may be a better way to get the layer to clip. For instance, there's a forEach function that can be applied to the object returned by Map.layers(), but I couldn't get it to work, so I coerced to a JavaScript array and just used map(). I also started experimenting with using a ui.Select such that the specific geometry that a user wanted to use for clipping could be selected, but I couldn't get it to work :/

Here's the stand alone web application (only very slightly modified from what Gennadii Donchyts wrote) for drawing a geometry and then converting it to a layer (such that the Earth Engine object can the be extracted from that layer when the "Clip image" button is clicked):

var app = function() {
  Map.setOptions("HYBRID")

  var tool = new DrawAreaTool(Map)

  // subscribe to selection
  tool.onFinished(function(geometry) {
    checkbox.setValue(false, false)
    print(geometry)
    Map.addLayer(geometry, {}, "geometry")
  })

  // add checkbox to activate selector when checkbox is clicked
  var checkbox = ui.Checkbox({label: 'Draw area', style: {position: 'top-center'}});

  checkbox.onChange(function(checked) {
    if(checked) {
      tool.startDrawing()
    } else {
      tool.stopDrawing()
    }
  });


  ui.root.setLayout(ui.Panel.Layout.absolute());
  ui.root.add(checkbox)

}


var DrawAreaTool = function(map) {
  this.map = map
  this.layer = ui.Map.Layer({name: 'area selection tool', visParams: { color:'yellow' }})
  this.selection = null
  this.active = false
  this.points = []
  this.area = null

  this.listeners = []

  var tool = this;

  this.initialize = function() {
    this.map.onClick(this.onMouseClick)
    this.map.layers().add(this.layer)
  }

  this.startDrawing = function() {
    this.active = true
    this.points = []

    this.map.style().set('cursor', 'crosshair');
    this.layer.setShown(true)
  }

  this.stopDrawing = function() {
    tool.active = false
    tool.map.style().set('cursor', 'hand');

    if(tool.points.length < 2) {
      return
    }

    tool.area = ee.Geometry.Polygon(tool.points)
    tool.layer.setEeObject(tool.area)

    tool.listeners.map(function(listener) {
      listener(tool.area)
    })
  }

  /***
  * Mouse click event handler
  */
  this.onMouseClick = function(coords) {
    if(!tool.active) {
      return
    }

    tool.points.push([coords.lon, coords.lat])

    var geom = tool.points.length > 1 ? ee.Geometry.LineString(tool.points) : ee.Geometry.Point(tool.points[0])
    tool.layer.setEeObject(geom)

    var l = ee.Geometry.LineString([tool.points[0], tool.points[tool.points.length-1]]).length(1).getInfo()

    if(tool.points.length > 1 && l / Map.getScale() < 5) {
      tool.stopDrawing()
    }
  }

  /***
  * Adds a new event handler, fired on feature selection. 
  */
  this.onFinished = function(listener) {
    tool.listeners.push(listener)
  }

  this.initialize()
}


app()

The only change that I made from Gennadii Donchyts's original web app code is to change the name of the added layer after drawing from "area" to "geometry" to make it match your code.

The workflow is:

  1. Run the code
  2. Check the "Draw area" tickbox in the upper center of the Map
  3. Draw the polygon you'd like to clip to (It's a little slow because apparently there are some server side/client side interactions)
  4. Finish the polygon by unchecking the box or clicking near the start of the polygon you're creating
  5. Click the "Clip image" button in the console.
  6. Go to the Layers on the map and see that a new layer called "clipped" has been added to the Map.

Hopefully this gets you closer!

Gennadii Donchyts's original code is here (also from that email thread): https://code.earthengine.google.com/cbe8f13014c33eb4ffb9a2b3b0e8ae81

This is the link to this code is here: https://code.earthengine.google.com/e255b42bea7f8511a220591a373953a0

EDIT:

I rewrote the clipButton code to clip by a FeatureCollection comprising all the geometries added as layers that are named "geometry". This is starting to feel pretty hacked together, but it does work for a single or multiple drawn polygons.

The new workflow is:

  1. Run the code
  2. Check the "Draw area" tickbox in the upper center of the Map
  3. Draw the polygon you'd like to clip to (It's a little slow because apparently there are some server side/client side interactions)
  4. Finish the polygon by unchecking the box or clicking near the start of the polygon you're creating
  5. NEW STEP: If you wish to clip to an additional polygon, check the "Draw area" tickbox and draw another polygon. Repeat as desired.
  6. Click the "Clip image" button in the console.
  7. Go to the Layers on the map and see that a new layer called "clipped" has been added to the Map.

The new code is here (in addition to being written above): https://code.earthengine.google.com/82b08b69bd596ada4747cb4bb7ea9526

  • Nice and detailed answer, thank you for your efforts and the script included on it. I use your code in a custom script, and it works very well on images, however, when I use it with a Feature Collection (with FilterBounds command) does not work because the 'geometry' returned is an object. I tried to cast the geometry var as ee.Geometry and ee.Feature without success. Do you have any clues? – gtapko Feb 12 '18 at 1:59
  • Also, I do not have access to the Google Groups posted as references, I have tried to contact the admin several times, but I did not receive any answer from him. – gtapko Feb 12 '18 at 2:06
  • Ah, bummer about no access to the Developer list. Do you mind if I point folks to this answer on the email thread I mentioned? Maybe that will encourage some movement on a better implementation of this feature. – mikoontz Feb 12 '18 at 17:24
  • I'm not sure I'm understanding the issue with the Feature Collection. Like you are trying to draw several polygons and clip an image to a multi-polygon? Or you are trying to clip a polygon by using the geometry that you draw? Or something else? – mikoontz Feb 12 '18 at 17:26
  • It would be great if you could bring more people to complement and improve your answer. Regarding the last comment, I am trying to 'clip' a Feature Collection based on one or several geometries. – gtapko Feb 13 '18 at 0:05

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.