0

When clicking on a GEE map, I would like to get the value of an Image's (1-band raster) pixel.

I'm using Map.onClick to extract values from point on a map. According to GEE examples (e.g. https://developers.google.com/earth-engine/tutorial_api_03), xx.reduceRegion(ee.Reducer.mean(), clickpoint, scale) should work for ImageCollection objects but does not seem to work for Image objects.

// Load 1-band image
var dataset = ee.Image('WWF/HydroSHEDS/03VFDEM').select('b1');
print(dataset);
Map.style().set('cursor', 'crosshair');
Map.addLayer(dataset);

// Following is adapted from https://code.earthengine.google.com/?scriptPath=Examples:User+Interface/Ocean+Timeseries+Investigator
// Set a callback function for when the user clicks the map.
var header = ui.Label('Some text', {fontWeight: 'bold', fontSize: '18px'});
var toolPanel = ui.Panel([header], 'flow', {width: '400px'});

Map.onClick(function(coords) {
  // Create or update the location label (the second widget in the panel)
  var location = 'lon: ' + coords.lon.toFixed(4) + ' ' +
                 'lat: ' + coords.lat.toFixed(4);
  var click_point = ee.Geometry.Point(coords.lon, coords.lat);
  var demValue = dataset.reduceRegion(ee.Reducer.first(), click_point, 90);
  var demText = 'Habitat suitability: ' + demValue;
// var demText = 'Habitat suitability: ' + demValue.get('b1');
  toolPanel.widgets().set(1, ui.Label(location));
  toolPanel.widgets().set(2, ui.Label(demText));

  // Add a red dot to the map where the user clicked.
  Map.layers().set(1, ui.Map.Layer(click_point, {color: 'FF0000'}));

});

// Add the panel to the ui.root.
ui.root.add(toolPanel);

Using demValue directly or demValue.get('b1') doesn't change anything.

The resulting panel gives (-86.6746,35.3036 being a random point):

Some text

lon: -86.6746 lat: 35.3036

Habitat suitability: ee.Dictionary({ "type": "Invocation", "arguments": { "image": { "type": "Invocation", "arguments": { "input": { "type": "Invocation", "arguments": { "id": "WWF/HydroSHEDS/03VFDEM" }, "functionName": "Image.load" }, "bandSelectors": [ "b1" ] }, "functionName": "Image.select" }, "reducer": { "type": "Invocation", "arguments": {}, "functionName": "Reducer.first" }, "geometry": { "type": "Point", "coordinates": [ -86.67460937499999, 35.30364788916715 ] }, "scale": 90 }, "functionName": "Image.reduceRegion" })

GEE script: https://code.earthengine.google.com/1204e25cbbdb782d9af4c7944d047bf2

1

demValue is a "server side" object. When you print it in the code editor console, the print function retrieves the value of the object from the server to be a "client side" object. In this line:

var demText = 'Habitat suitability: ' + demValue;

you are mixing a "client side" string with a "server side" object, and what you get is the serialization of that object (the call to the server that will retrieve its value) which is a client side object:

{ "type": "Invocation", "arguments": { "image": { "type": "Invocation", "arguments": { "input": { "type": "Invocation", "arguments": { "id": "WWF/HydroSHEDS/03VFDEM" }, "functionName": "Image.load" }, "bandSelectors": [ "b1" ] }, "functionName": "Image.select" }, "reducer": { "type": "Invocation", "arguments": {}, "functionName": "Reducer.first" }, "geometry": { "type": "Point", "coordinates": [ -86.67460937499999, 35.30364788916715 ] }, "scale": 90 }, "functionName": "Image.reduceRegion" }

Widgets live in the "client side", so you will have to retrieve it yourself:

var demText = 'Habitat suitability: ' + demValue.get('b1').getInfo();
  • Thanks for the clarification. – YGS Jul 18 at 13:14
1

To add to @Rodrigo's answer. You definitely need to bring the data to client side before you can display it on your UI. BUT since you are dealing with UI, you definitely don't want the application to freeze when you click on map. So it might be a better idea to use the evaluate function instead which will run the data fetching process in the background without stopping the UI functions. You can even temporarily set the label as something like loading... An implementation of it within the onClick function could be

Map.onClick(function(coords) {
  var location = 'lon: ' + coords.lon.toFixed(4) + ' ' +
                 'lat: ' + coords.lat.toFixed(4);
  var click_point = ee.Geometry.Point(coords.lon, coords.lat);
  var demValue = dataset.reduceRegion(ee.Reducer.first(), click_point, 90).evaluate(function(val){
    var demText = 'Habitat suitability: ' + val.b1;
    toolPanel.widgets().set(2, ui.Label(demText));
  });
  toolPanel.widgets().set(1, ui.Label(location));
// Edit: To be temporary, the "loading..." panel number has to be the same as the demText panel number (changed from 1 to 2).
  toolPanel.widgets().set(2, ui.Label("loading..."));
  Map.layers().set(1, ui.Map.Layer(click_point, {color: 'FF0000'}));
});
  • Thanks Nishanta. I appreciate your solution may be more broadly applicable. – YGS Jul 18 at 13:16
  • Yes, you are absolutely right, I just wanted to bring the basic concept. I'd also use evaluate, I should have mentioned it at least – Rodrigo E. Principe Jul 19 at 2:14
  • 1
    In fact, I made a custom widget using async call: asyncSelect – Rodrigo E. Principe Jul 19 at 2:35

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.