6

In my Google Earth Engine script, the user can click on a point on the map, and then a time series history chart of that pixel value is displayed in the console. However, after the user has clicked on a few points, it's not clear anymore which chart belongs to which point.

That's why I'd like to add labels (consecutive numbers probably) to all the points on the map and to the chart. However, I have no clue how to add text at a coordinate location on the map.

I know that printing the point coordinates is a workaround but that's not user-friendly at all.

Here's what I have (without the chart because that's not really part of the problem):

Map.style().set({cursor: 'crosshair'});

var i=0;
var print_point = function(coords, map) {
  i++;
  var coord_array = Object.keys(coords).map(function (key) { return coords[key]; });
  var point = ee.Geometry.Point(coord_array);
  print('point ' + i, point);
  print('placeholder for time series chart');
  Map.addLayer(point);
  // put i somewhere near that point on the map
};

Map.onClick(print_point);

How do I add labels to the points on the map? (I mean label in the generic sense, it doesn't have to be a ui.Label.

EDIT: Any answer that makes it easy for the user to relate each chart to a point will be fine. It doesn't need to be a label on the map. For instance a solution where the chart pops up when a point is clicked is good too. However, it's important that GEE doesn't have to recalculate the chart every time if the same point is clicked multiple times.

  • may be there is a workaround, but it is not easy, I couldn't find it. The only workaround I thought of is to make a FeatureCollection with "label shapes", but that would be a lot of work, and just for predefine labels. – Rodrigo E. Principe Jul 12 '17 at 12:30
  • yea, I've considered that. If I sat down a day, I could draw the digits 0-9 and have GEE arrange them to draw any (natural) number on the map. Way too much work though and the text size would change with zoom... – Joooeey Jul 12 '17 at 20:57
2

This technique might be useful - shared by Gennaddi Donchyts on the Earth Engine Developers Group. It draws text labels on the map.

/*
Author: Gennadii Donchyts
License: Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0 

Known issues:
* for some fonts the letter 'o' is converted with small changes, not really an ellipse
* size is specified in empirical units, characters need to be scaled to e.g. 1x1 degree

*/

/**
 * Translates and scales polygon geometry.
 */
function movePolygon(poly, x, y, scaleX, scaleY) {
  var coordLists = ee.List(poly.coordinates())

  coordLists = coordLists.map(function(list) {
    return ee.List(list).map(function(o) {
      var pt = ee.List(o)
      return ee.List([
        ee.Number(pt.get(0)).multiply(scaleX).add(x), 
        ee.Number(pt.get(1)).multiply(scaleY).add(y)
      ]);
    })
  })

  return ee.Algorithms.GeometryConstructors.Polygon(coordLists);
}

/**
 * Translates and scales MultiPolygon geometry.
 */
function moveTo(geom, x, y, scaleX, scaleY) {
  var geoms = geom.geometries();

  geoms = geoms.map(function(g) {
    return movePolygon(ee.Geometry(g), x, y, scaleX, scaleY);
  })

  return ee.Feature(ee.Algorithms.GeometryConstructors.MultiPolygon(geoms))
}

/**
 * Converts character to a geospatial feature.
 */
function charToFeature(characters, char) {
  return ee.Feature(characters.filterMetadata('ascii', 'equals', char.charCodeAt(0)).first());
}

/**
 * Converts character to a geospatial feature.
 */
function charToFeatureParallel(characters, char) {
  return ee.Feature(characters.filter(ee.Filter.stringContains('char', char)).first());
}

/**
 * Converts text string to a feature collection using position, scale and space between characters.
 */
function textToFeatures(text, x, y, opt_args) {
  var args = opt_args || {};
  var font = args['font'] || 'ProductSans'

  var fonts = {
    'ProductSans' : ee.FeatureCollection('ft:1LwsANQcu6eheWHrqGxZ9fNX5FEjNBTxTEVOClWRw'),
    'Hacker' : ee.FeatureCollection('ft:1GKzZFxab7pSiKdv-s1UpyWHbEHXDwQ4ll37bVZnF'),
    'Helvetica' : ee.FeatureCollection('ft:1ouSXtIXd8syBi6dI2XQYkW5Z7i3bt7sPy2aXEGdS'),
    'HelveticaBold' : ee.FeatureCollection('ft:1JrMdGhJN5O0QPutmzBpC1nebcXY9-N86X0Zb5bLP'),
    'HelveticaBoldItalic' : ee.FeatureCollection('ft:1YKBnGW6txEmb-csXRlWVkeovqzAmaNEppKwpJB9I'),
    'HelveticaItalic' : ee.FeatureCollection('ft:1AnCP7EJcke-hzfVUezweHuwTqSjGWqPpnBrn4PMq'),
    'Consolas' : ee.FeatureCollection('ft:1QseQFRqhtKaYS7z54ohheB_Oy802bL1AcuFyudeK'),
    'CourrierNew' : ee.FeatureCollection('ft:1VAK4EIsvjRr57E-vkhJVLwAp82HAMMktFrGSPB4H'),
  }

  var sizeX = args['sizeX'] || 0.1;
  var sizeY = args['sizeY'] || 0.1;
  var charSpace = args['charSpace'] || 0.1;

  charSpace = ee.Number(charSpace)

  var scaleX = sizeX, scaleY = sizeY; // TODO: regenerate fonts so that their size will be known

  var chars = text.split('');
  var features = ee.List([]);
  var offsetX = ee.Number(x);
  var offsetY = ee.Number(y);

  for(var i = 0; i < chars.length; i++) {
    var f = charToFeature(fonts[font], chars[i]);

    var g = moveTo(f.geometry(), offsetX, offsetY, scaleX, scaleY);
    features = features.add(ee.Feature(g))

    var w = ee.Number(f.get('width')).multiply(scaleX)
    offsetX = offsetX.add(w).add(charSpace);
  }

  return ee.FeatureCollection(features);
}

/**
 * Converts text string to a feature collection using position, scale and space between characters.
 */
function textToFeaturesParallel(text, x, y, opt_args) {
  var args = opt_args || {};
  var font = args['font'] || 'ProductSans'

  var fonts = {
    'ProductSans' : ee.FeatureCollection('ft:1LwsANQcu6eheWHrqGxZ9fNX5FEjNBTxTEVOClWRw'),
    'Hacker' : ee.FeatureCollection('ft:1GKzZFxab7pSiKdv-s1UpyWHbEHXDwQ4ll37bVZnF'),
    'Helvetica' : ee.FeatureCollection('ft:1ouSXtIXd8syBi6dI2XQYkW5Z7i3bt7sPy2aXEGdS'),
    'HelveticaBold' : ee.FeatureCollection('ft:1JrMdGhJN5O0QPutmzBpC1nebcXY9-N86X0Zb5bLP'),
    'HelveticaBoldItalic' : ee.FeatureCollection('ft:1YKBnGW6txEmb-csXRlWVkeovqzAmaNEppKwpJB9I'),
    'HelveticaItalic' : ee.FeatureCollection('ft:1AnCP7EJcke-hzfVUezweHuwTqSjGWqPpnBrn4PMq'),
    'Consolas' : ee.FeatureCollection('ft:1QseQFRqhtKaYS7z54ohheB_Oy802bL1AcuFyudeK'),
    'CourrierNew' : ee.FeatureCollection('ft:1VAK4EIsvjRr57E-vkhJVLwAp82HAMMktFrGSPB4H'),
  }

  var sizeX = args['sizeX'] || 0.1;
  var sizeY = args['sizeY'] || 0.1;
  var charSpace = args['charSpace'] || 0.1;

  charSpace = ee.Number(charSpace)

  var scaleX = sizeX, scaleY = sizeY; // TODO: regenerate fonts so that their size will be known

  var chars = ee.String(text).split('');
  var features = ee.List([]);
  var offsetX = ee.Number(x);
  var offsetY = ee.Number(y);

  var charFeatures = ee.List(ee.List(chars).slice(1).iterate(function(char, prev) { 
    return ee.List(prev).add(charToFeatureParallel(fonts[font], char)); 
  }, ee.List([])))

  var offsetsX = ee.List(charFeatures.iterate(function(f, prev) { 
    var w = ee.Number(ee.Feature(f).get('width')).multiply(scaleX)

    var list = ee.List(prev)
    var last = ee.Number(list.get(list.length().subtract(1)))

    return list.add(last.add(w).add(charSpace))
  }, ee.List([offsetX]))).slice(0, chars.length())

  var newCharFeatures = charFeatures.zip(offsetsX).map(function(o) {
    var list = ee.List(o)
    var f = ee.Feature(list.get(0))
    var x = list.get(1)

    return moveTo(f.geometry(), x, offsetY, scaleX, scaleY);
  })

  return ee.FeatureCollection(newCharFeatures);
}

/**
 * Converts text string to an image layer using position, scale and space between characters.
 */
function textToImage(text, x, y, opt_args) {
  //var features = textToFeatures(text, x, y, opt_args)
  var features = textToFeaturesParallel(text, x, y, opt_args)

  var args = opt_args || {};
  var filled = typeof args['filled'] == 'undefined' ? true : args['filled'];

  var image = ee.Image(0).toByte();

  if(filled) {
    image = image.paint(features, 1); // paint fill
  }

  image = image.paint(features, 1, 1); // paint outline
  image = image.mask(image);

  return image;
}

// show current date
var image = textToImage(Date().toString(), 8, 0,
  {sizeX:0.2, sizeY:0.2, charSpace:0.5, font:'Hacker', filled:false});

Map.addLayer(image, {palette:['ffffff']}, 'date (text)')

// show welcome messate
var image = textToImage('Hello Google Earth Engine!', 8, 10,
  {sizeX:0.7, sizeY:0.7, charSpace:1.4, font:'ProductSans', filled:true});

Map.addLayer(image.focal_max(2), {palette:['000000'], opacity:0.7}, 'hello (text)')

// test different fonts
var rect = ee.Geometry.Rectangle([[-5, -13],[120, -5]], 'EPSG:4326', false)
Map.addLayer(rect, {color:'ffffff'}, 'rectangle')


Map.addLayer(textToImage('Product Sans', -3, -10, 
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'ProductSans', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Product Sans')

Map.addLayer(textToImage('Consolas', 20, -10,
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'Consolas', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Consolas')

Map.addLayer(textToImage('Hacker', 37, -10, 
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'Hacker', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Hacker')

Map.addLayer(textToImage('Helvetica', 55, -10, 
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'Helvetica', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Helvetica')

Map.addLayer(textToImage('Helvetica Bold', 70, -10,
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'HelveticaBold', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Helvetica Bold')

Map.addLayer(textToImage('Courrier New', 96, -10,
  {sizeX:0.3, sizeY:0.3, charSpace:0.6, font:'CourrierNew', filled:true}), 
  {palette:['000000'], opacity:0.7}, 'Font: Courrier New')

// zoom out and switch to satellite map
Map.setCenter(54.64, 3.10, 4)
print(Map.getCenter())

https://code.earthengine.google.com/08146a0183f51ff94a34bb90ae987e0d

To remove a syntax error I deleted the last line which just changes the base to satellite.

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.