Earth Engine Apps: Gerenciador de Camadas [Pt. 1/n]

Iago Mendes
OLAB Learning
Published in
9 min readJan 16, 2024

Por Iago Mendes, OLAB Learning

Se você está começando agora a usar o Google Earth Engine (GEE), é provável que ainda não tenha explorado a API de interface do usuário (UI). Isso é comum, já que o foco inicial dos estudos tende a ser o processamento de dados. A API UI, contudo, nos possibilita transformar achados em insights valiosos para todos os públicos.

Conforme descrito na documentação oficial do GEE:

Earth Engine provides access to client-side user interface (UI) widgets through the ui package.

O pacote ui é uma coleção de ferramentas e componentes (widgets) que permite aos desenvolvedores construir interfaces interativas personalizáveis. Isso possibilita aos usuários não só visualizar e analisar, mas também interagir dinamicamente com os dados processados pelo GEE.

O objetivo deste artigo, que inaugura uma série sobre o desenvolvimento de Apps no GEE, é detalhar, a partir de um caso de uso, o uso do pacote ui. Começaremos com uma funcionalidade presente em muitas aplicações: o gerenciador de camadas. Neste artigo, vamos explorar os componentes ui.Checkbox, ui.Panel e ui.Map.Layer e introduzir o conceito de eventos assíncronos.

Gerenciado de camadas.

Acessando a API UI

Para conhecer as funcionalidades da API UI, clique em ui na aba Docs, no canto superior esquerdo do Code Editor.

Ao navegar por essa seção, você encontrará uma variedade de componentes, cada um com suas próprias funcionalidades e parâmetros específicos.

Ao concluir a leitura deste artigo, recomendo que você volte, inspecione e teste cada um dos componentes para entender como funcionam e como podem ser utilizados.

Instanciando um componente

Para instanciar um componente, utilize ui.NomeDoComponente(). Desta forma:

// Para instanciar uma caixa de seleção (checkbox).
var checkbox = ui.Checkbox('Click me!');

// Para mostrar o checkbox no Console.
print(checkbox);

Recomendo fortemente que rode este código no Code Editor. Ao executá-lo, uma caixa de seleção (checkbox) com o rótulo “Click me!” aparecerá no Console.

Simples, não é?

Agora, vamos dar um passo adiante e adicionar funcionalidade ao nosso checkbox. Utilizaremos o parâmetro onChange para isso:

var checkbox = ui.Checkbox({
label: 'Click me!',
onChange: function(value) {
print('Valor do checkbox: ', value);
}
});

print(checkbox);

Aqui, introduzimos uma função de callback que reage às interações com o checkbox. Cada vez que você marcar ou desmarcar a caixa, uma mensagem será exibida no Console, demonstrando a reação do componente à sua ação.

O value na função de callback é um exemplo de um parâmetro de evento. Ele reflete o estado atual do checkbox: true se estiver marcado, false se não.

Para manter o código mais organizado, podemos separar a função de callback.

// Define a função de callback.
function printValue(value) {
print('Valor do checkbox: ', value);
}

// Cria uma instância do componente checkbox.
var checkbox = ui.Checkbox({
label: 'Click me!',
// Passa a função de callback como argumento do parâmetro onChange.
onChange: printValue
});

// Mostra o checkbox no Console.
print(checkbox);

Esta abordagem ajuda a manter o código limpo, especialmente quando lidamos com funções mais complexas ou quando queremos reutilizar a mesma função em diferentes componentes.

Além disso, é possível definir o estado inicial do checkbox através do parâmetro value.

// Define a função de callback.
function printValue(value) {
print('Valor do checkbox: ', value);
}

// Cria uma instância do componente checkbox.
var checkbox = ui.Checkbox({
label: 'Click me!',
value: true, // Define o estado inicial do checkbox.
onChange: printValue // Passa a função de callback como argumento.
});

// Mostra o checkbox no Console.
print(checkbox);

Outra característica interessante é o parâmetro disabled. Se definido como true, ele desabilita o componente, impedindo a interação do usuário. Por padrão, todos os componentes vêm com disabled: false.

Por último, o parâmetro style permite personalizar a aparência/estilo do componente. Embora não nos aprofundaremos nos detalhes aqui, encorajo você a experimentá-lo. A documentação do GEE é um excelente recurso para entender as possibilidades de cada parâmetro.

Com essas informações, você está bem equipado para começar a criar interfaces mais dinâmicas e interativas no GEE. Continuemos.

Criando o gerenciador de camadas

Para facilitar o entendimento do fluxo de trabalho adotado, vamos dividir o script em algumas partes.

Obtenção, filtragem e seleção das imagens

var pt = ee.Geometry.Point([-45.00, -21.25]);
var colId = 'LANDSAT/LC08/C02/T1_L2';
var images = ee.ImageCollection(colId)
.filterBounds(pt)
.filterDate('2023-01-01', '2024-01-01')
.filter(ee.Filter.lt('CLOUD_COVER', 5));

Aqui, escolhemos uma coleção de imagens colId e filtramos essa coleção para incluir apenas imagens que cobrem o ponto pt, dentro de um intervalo de datas específico e com baixa cobertura de nuvens.

Resultado de “Obtenção, filtragem e seleção das imagens”.

Preparação da coleção

images = images.aggregate_array('system:index');

Convertemos a coleção filtrada em uma lista de IDs de imagens. Essas serão as únicas informações que usaremos para criar o gerenciador de camadas.

Resultado de “Preparação da coleção”.

Definição dos parâmetros de visualização

var visParams = {
bands: ['SR_B4', 'SR_B3', 'SR_B2'],
min: 0,
max: 0.2
};

Esses são os parâmetros de visualização que usaremos para configurar o componente ui.Map.Layer. Em outras palavras, são os parâmetros que vão definir como as imagens serão mostradas.

Função para processamento das imagens Landsat 8

function scaleImage(image) {
var scale = 0.0000275;
var offset = -0.2;
var scaledImage = image
.select(['SR_B[2-4]'])
.multiply(scale)
.add(offset)
.copyProperties(image, image.propertyNames());
return ee.Image(scaledImage);
}

A função scaleImage é responsável por ajustar a escala dos dados de uma imagem Landsat 8. Ela seleciona bandas específicas com select(['SR_B[2-4]']) , aplica um fator de escala scale e um offset, e preserva as propriedades originais da imagem com copyProperties().

Saiba mais sobre scale e offset.

Criação do painel de controle

var controlPanel = ui.Panel({
style: { position: 'middle-left' }
});

Map.widgets().add(controlPanel);

O componente ui.Panel é muito útil para organizar e exibir outros componentes de maneira estruturada.

Ao instanciar o componente, utilizamos o parâmetro style, que, conforme já menciomaos, define a aparência/estilo (dimensões, cor, bordas, margens, etc) do painel.

Propriedades que podem ser modificadas via parâmetro “style”.

Aqui, a única propriedade ajustada dentro de style é a position. Essa propriedade define a localização do painel em relação ao mapa, o componente Map, no qual o painel de controle foi adicionado. O valor middle-left posiciona o painel no centro da borda esquerda do mapa.

O painel (vazio) adicionado no centro da borda esquerda do mapa.

Processamento assíncrono dos dados

No contexto do GEE, muitas operações, especialmente as que envolvem o acesso a dados armazenados nos servidores, são assíncronas. Isso significa que elas não são executadas imediatamente e, em vez disso, ocorrem “em segundo plano”.

O processamento assíncrono é assencial no GEE devido à grande quantidade de dados que precisam ser processados e à complexidade das operações realizadas.

Aqui é onde a mágica acontece:

images.evaluate(function (images) {
images.forEach(function (imageId, index) {
var id = [colId, imageId].join('/');
var image = ee.Image(id);
image = scaleImage(image);

var shown = index === 0 ? true : false;

var layer = ui.Map.Layer(image, visParams, imageId, shown);
Map.layers().add(layer);

var checkbox = ui.Checkbox({
label: imageId,
value: shown,
onChange: function (shown) {
layer.setShown(shown);
}
});

controlPanel.widgets().add(checkbox);
});
});

Utilizamos o método evaluate para lidar com operações assíncronas no GEE. Quando você chama evaluate em um objeto (como images, no código acima), ele instrui o GEE a iniciar o processamento dos dados associados a esse objeto.

Uma alternativa não muito conveniente é o getInfo. Mas a própria documentação não recomenda seu uso.

Resumidamente, a principal diferença entre evaluate e o getInfo é a seguinte:

  • evaluate é um método assíncrono. Isso significa que ele solicita dados ao servidor e, enquanto os dados estão sendo processados, seu script pode continuar executando outras tarefas. Devido à sua natureza assíncrona, evaluate é preferível pois mantém a interface do usuário responsiva e permite que outras operações ocorram em paralelo.
  • getInfo é um método síncrono. Ele bloqueia a execução do seu script até que os dados sejam retornados do servidor. Isso significa que seu código fica "bloqueado" até que a resposta chegue, o que pode levar a uma experiência de usuário lenta e a uma interface não responsiva, especialmente se a solicitação de dados for demorada.

Voltemos à explicação do código:

Você se lembra da variável images?

Objeto “images”.

O objeto images é um array (representado pela classe List no GEE) que armazena os IDs das imagens da coleção de imagens após aplicação dos filtros.

Percebam o cuidado que tive ao obter apenas a informação necessária (o ID de cada imagem) antes de chamar o evaluate.

Quando usamos images.aggregate_array('system:index'), transformamos o que antes era um objeto complexo (ImageCollection) em um objeto mais "simples" (um objeto List contendo algumas strings).

Quando usamos o método evaluate para transferir dados do servidor para o cliente, o objeto images, que originalmente é um objeto computado da classe List, é convertido em um Array JavaScript (JS). Essa conversão é importante, pois nos permite usar métodos nativos do JS para manipular esses dados. Um exemplo é o uso do método forEach(), utilizado para executar uma função específica em cada elemento do array images.

Saiba mais sobre o método forEach().

Dentro do escopo da função que passamos como callback do forEach, cada elemento (cada ID) recebe o nome imageId.

  1. images.forEach(function(imageId, index) { ... });
    Cria um loop forEach no array images. Para cada elemento (imageId) e seu índice (index) o array, executa a função fornecida.
  2. var id = [colId, imageId].join('/');
    Cria uma string id juntando colId (o ID da coleção de imagens, declarado lá no início) e imageId (o ID da imagem atual) com uma barra (/) entre eles. Isso forma o ID completo da imagem no Earth Engine.
  3. var image = ee.Image(id);
    Cria um objeto image da classe ee.Image usando o ID completo da imagem. Esta linha carrega a imagem do GEE.
  4. image = scaleImage(image);
    Aplica a função scaleImage à imagem carregada.
  5. var shown = index === 0 ? true : false;
    Define uma variável booleana shown. Se a imagem atual é a primeira no array (index === 0), shown é definido como true, caso contrário, é false. Isso determina se a camada da imagem será visível inicialmente no mapa.
  6. var layer = ui.Map.Layer(image, visParams, imageId, shown);
    Cria uma camada layer com a imagem processada, os parâmetros de visualização visParams , o ID da imagem imageId como o nome da camada, e a variável shown para definir se a camada será visível inicialmente.
  7. Map.layers().add(layer);
    Adiciona a camada recém-criada ao mapa.
  8. var checkbox = ui.Checkbox({ ... });
    Cria um checkbox, conforme vimos anteriormente. O rótulo label do checkbox é o ID da imagem imageId, o estado inicial value é definido pela variável shown e uma função de callback é definida para o evento onChange.
  9. onChange: function(shown) { layer.setShown(shown); }
    Dentro do checkbox, define uma função de callback para o evento onChange. Quando o estado do checkbox muda (marcado ou desmarcado), essa função é chamada e ajusta a visibilidade da camada correspondente no mapa com. layer.setShown(shown).
  10. controlPanel.widgets().add(checkbox);
    Adiciona o checkbox ao painel de controle controlPanel.

Esse código cria um gerenciador de camadas interativo no GEE, onde cada camada da imagem é associada a um checkbox na interface do usuário. Isso permite aos usuários controlar a visibilidade de cada camada no mapa de forma dinâmica.

Conclusão

Neste artigo, exploramos em detalhes a criação de um gerenciador de camadas no GEE. Através do uso inteligente do método evaluate, conseguirmos trazer informações relevantes para o cliente e utilizar funções nativas do JS, como forEach, para processá-las. Além disso, com a utilização dos componentes fornecidos pela API UI, como ui.Checkbox, ui.Panel e ui.Map.Layer, conseguimos desenvolver uma funcionalidade presente em muitas aplicações envolvendo dados geoespaciais.

Fica o desafio

Procurei simplificar este artigo para facilitar a compreensão do assunto, mas não posso encerrar sem mencionar uma combinação muito interessante que não abordada: o uso conjunto de checkboxes e sliders.

Enquanto o checkbox permite alternar a visibilidade de uma camada (mostrando ou ocultando-a), o slider oferece controle sobre a opacidade da camada, permitindo ajustes finos na forma como os dados são visualizados.

Dúvidas? Encontrou algum erro? Sinta-se à vontade para deixar suas perguntas e considerações nos comentários.

Script | Siga @olablearning

--

--