Você está na página 1de 152

Google Earth Engine de ponta a ponta (material

completo do curso)
Uma introdução prática ao sensoriamento remoto aplicado usando o Google Earth
Engine.
Ujaval Gandhi
Introdução
Configurando o ambiente
Inscreva-se no Google Earth Engine
Conclua o Pré-Trabalho da Aula
Introdução ao Sensoriamento Remoto
Introdução ao Google Earth Engine
Faça os testes
Obtenha os materiais do curso
Módulo 1: Fundamentos do Earth Engine
01. Olá, Mundo
Exercício
Salvando seu trabalho
02. Trabalhando com coleções de imagens
Exercício
03. Filtrando coleções de imagens
Exercício
04. Criando mosaicos e composições a partir de ImageCollections
Exercício
05. Trabalhando com coleções de recursos
Exercício
06. Importando Dados
Exercício
07. Clipes de Imagens
Exercício
08. Exportando Dados
Exercício
Tarefa 1
Módulo 2: Earth Engine Intermediário
01. Objetos da Máquina Terrestre
Exercício
02. Cálculo de Índices
Exercício
03. Cálculo em ImageCollections
Exercício
04. Mascaramento de Nuvem
Exercício
05. Redutores
Exercício
06. Gráficos de séries temporais
Exercício
Tarefa 2
Módulo 3: Classificação Supervisionada
Introdução ao aprendizado de máquina e classificação supervisionada
01. Classificação Básica Supervisionada
Exercício
02. Avaliação de Precisão
Exercício
03. Aperfeiçoamento da Classificação
Exercício
04. Resultados de Classificação de Exportação
Exercício
05. Cálculo de Área
Exercício
Módulo 4: Detecção de Mudanças
Introdução à Detecção de Mudanças
01. Alteração do Índice Espectral
Exercício
02. Alteração da Distância Espectral
Exercício
03. Classificação Direta de Mudança
Exercício
04. Comparação pós-classificação
Exercício
Módulo 5: Aplicativos do Earth Engine
01. Cliente x Servidor
Exercício
02. Usando elementos da interface do usuário
Exercício
03. Construindo e Publicando um App
Exercício
04. Publicação do aplicativo
Exercício
05. Crie um aplicativo de painel dividido
Exercício
Módulo 6: API Python do Google Earth Engine
Introdução à API Python
Google Colab
Ambiente de Desenvolvimento Local
01. Sintaxe da API do Python
Exercício
02. Conversão Automática de Código Javascript para Python
Exercício
03. Exportações em Lote
Exercício
04. Criando Gráficos em Python
Crie um gráfico interativo usando geemap
Crie um gráfico usando o Matplotlib
Exercício
05. Automatizando downloads
Suplemento
Técnicas Avançadas de Classificação Supervisionada
Ajuste de hiperparâmetros
Resultados da classificação pós-processamento
Análise de Componentes Principais (PCA)
Compósitos Multitemporais para Classificação de Culturas
Correlação computacional
Calculando a Matriz de Correlação de Banda
Calculando Área por Classe
Gráficos de assinatura espectral
Identifique GCPs mal classificados
Normalização e Padronização de Imagens
Calcular a importância do recurso
Suavização de Séries Temporais e Preenchimento de Gaps
Suavização da janela móvel
Interpolação Temporal
Suavização Savitzky-Golay
Modelos de interface do usuário
Adicionando uma legenda discreta
Adicionando uma legenda contínua
Alterar aplicativo de IU de visualização
Aplicativo de IU do NDVI Explorer
Compartilhamento de código e módulos de script
Compartilhando um único script
Compartilhamento de vários scripts
Compartilhamento de código entre scripts
Repositórios públicos úteis
Projetos Guiados
Obtenha o código
Projeto 1: Monitoramento da Seca
Projeto 2: Mapeamento de Cheias
Projeto 3: Extraindo séries temporais
Projeto 4: Análise da Cobertura do Solo
Recursos de aprendizagem
Créditos de dados
Licença
Citação e Referência
Introdução
O Google Earth Engine é uma plataforma baseada em nuvem que permite o processamento em larga escala de imagens de satélite para
detectar mudanças, mapear tendências e quantificar diferenças na superfície da Terra. Este curso abrange toda a gama de tópicos do Earth
Engine para fornecer aos participantes habilidades práticas para dominar a plataforma e implementar seus projetos de sensoriamento
remoto.

(https://docs.google.com/presentation/d/1q8HRDTqgQEp3Hmi8IG0T7djPLTC1wRig3jXrwFTmoVE/edit?usp=sharing)

Veja a Apresentação ↗ (https://docs.google.com/presentation/d/1q8HRDTqgQEp3Hmi8IG0T7djPLTC1wRig3jXrwFTmoVE/edit?


usp=sharing)

Configurando o ambiente
Inscreva-se no Google Earth Engine
Se você já possui uma conta do Google Earth Engine, pode pular esta etapa.

Visite https://signup.earthengine.google.com/ (https://signup.earthengine.google.com/) e inscreva-se com sua conta do Google. Você pode
usar sua conta existente do Gmail para se inscrever. Geralmente leva de 1 a 2 dias para aprovação. Portanto, faça esta etapa o mais rápido
possível.

Dicas para garantir um processo de inscrição tranquilo:

Use o navegador Google Chrome.


Ao se inscrever no Earth Engine, saia de todas as contas do Google e verifique se você está conectado a apenas 1 conta que deseja
associar ao Earth Engine.
Prefira usar o e-mail da sua universidade/organização para se inscrever.
O acesso ao Google Earth Engine é concedido por meio dos Grupos do Google. As configurações padrão devem estar corretas, mas
verifique se você ativou a configuração correta.
Visite groups.google.com (http://groups.google.com/)
Clique em Configurações (ícone de engrenagem) e selecione Configurações globais.
Certifique-se de que a opção Permitir que os gerentes de grupo me adicionem aos seus grupos esteja marcada.

Conclua o Pré-Trabalho da Aula


Esta classe precisa de cerca de 2 horas de pré-trabalho. Assista aos vídeos a seguir para entender bem o sensoriamento remoto e como o
Earth Engine funciona. Os vídeos estão disponíveis online e podem ser transmitidos usando os links de vídeo abaixo.

Introdução ao Sensoriamento Remoto


Este vídeo apresenta os conceitos, terminologia e técnicas de sensoriamento remoto.

(https://www.youtube.com/watch?v=xAyNu9HbK8s)

Assista o vídeo (https://www.youtube.com/watch?v=xAyNu9HbK8s)


Veja a Apresentação ↗ (https://docs.google.com/presentation/d/1opRKXIV8XSMa5h7Gqw10KXY5nW7_khfdiBmyDEcylUE/edit?
usp=sharing)
Introdução ao Google Earth Engine
Este vídeo oferece uma ampla visão geral do Google Earth Engine com estudos de caso e aplicativos selecionados. O vídeo também aborda a
arquitetura do Earth Engine e como ela é diferente do software de sensoriamento remoto tradicional.

(https://www.youtube.com/watch?v=kpfncBHZBto)

Assista o vídeo (https://www.youtube.com/watch?v=kpfncBHZBto)


Veja a Apresentação ↗ (https://docs.google.com/presentation/d/1RMyufK1bD7_Mj0b0Pub-CADiBsmT8LqyGMXIgem3UW4/edit?
usp=sharing)

Faça os testes
Depois de assistir aos vídeos, preencha os 2 questionários a seguir

1. Quiz-1 Fundamentos de Sensoriamento Remoto (https://forms.gle/BoaYhMgpjwNn3amS8) .


2. Quiz-2 Fundamentos do Google Earth Engine (https://forms.gle/pGVShApd9f6uVYR89) .

Obtenha os materiais do curso


O material do curso e os exercícios estão na forma de scripts do Earth Engine compartilhados por meio de um repositório de código.

1. Clique neste link (https://code.earthengine.google.co.in/?accept_repo=users/ujavalgandhi/End-to-End-GEE) para abrir o editor de


código do Google Earth Engine e adicionar o repositório à sua conta.
2. Se for bem-sucedido, você terá um novo repositório nomeado users/ujavalgandhi/End-to-End-GEE na guia Scripts na seção Leitor .
3. Verifique se o seu editor de código se parece com o abaixo

Editor de código após adicionar o repositório de classes

Se você não vir o repositório na seção Leitor , clique no botão Atualizar cache do repositório na guia Scripts e ele aparecerá.
Atualizar cache do repositório
Módulo 1: Fundamentos do Earth Engine
O Módulo 1 foi projetado para fornecer habilidades básicas para encontrar conjuntos de dados necessários para o seu projeto, filtrá-los para
sua região de interesse, aplicar o processamento básico e exportar os resultados. Dominar isso permitirá que você comece a usar o Earth
Engine para seu projeto rapidamente e economize muito tempo pré-processando os dados.

01. Olá, Mundo


Este script apresenta a sintaxe básica do Javascript e o vídeo aborda os conceitos de programação que você precisa aprender ao usar o Earth
Engine. Para saber mais, visite a seção Introdução ao JavaScript para Earth Engine (https://developers.google.com/earth-
engine/tutorials/tutorial_js_01) do Guia do usuário do Earth Engine.

(https://www.youtube.com/watch?v=RV3Sv5iogHs)

Assista o vídeo (https://www.youtube.com/watch?v=RV3Sv5iogHs)

O Editor de Código é um Ambiente de Desenvolvimento Integrado (IDE) para a API Javascript do Earth Engine. Ele oferece uma maneira fácil
de digitar, depurar, executar e gerenciar código. Digite o código abaixo e clique em Executar para executá-lo e veja a saída na guia Console .

Dica: você pode usar o atalho de teclado Ctrl+Enter para executar o código no Code Editor

Olá Mundo

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2F01b_Hello_World_(complete))
print('Hello World');

// Variables
var city = 'Bengaluru';
var country = 'India';
print(city, country);

var population = 8400000;


print(population);

// List
var majorCities = ['Mumbai', 'Delhi', 'Chennai', 'Kolkata'];
print(majorCities);

// Dictionary
var cityData = {
'city': city,
'population': 8400000,
'elevation': 930
};
print(cityData);

// Function
var greet = function(name) {
return 'Hello ' + name;
};
print(greet('World'));

// This is a comment

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F01c_Hello_World_(exercise))

// These are the 5 largest cities in the world:


// Tokyo, Delhi, Shanghai, Mexico City, Sao Paulo

// Create a list named 'largeCities'


// The list should have names of all the above cities
// Print the list

Salvando seu trabalho


Ao modificar qualquer script para o repositório do curso, você pode querer salvar uma cópia para si mesmo. Se você tentar clicar no botão
Salvar , receberá uma mensagem de erro como abaixo

Isso ocorre porque o repositório de classe compartilhada é um repositório somente leitura . Você pode clicar em Sim para salvar uma cópia em
seu repositório. Se esta for a primeira vez que você está usando o Earth Engine, você será solicitado a escolher o nome da sua pasta pessoal .
Escolha o nome com cuidado, pois ele não pode ser alterado depois de criado.
02. Trabalhando com coleções de imagens
A maioria dos conjuntos de dados no Earth Engine vem como um arquivo ImageCollection . Um ImageCollection é um conjunto de dados
que consiste em imagens tiradas em diferentes horários e locais - geralmente do mesmo satélite ou provedor de dados. Você pode carregar
uma coleção pesquisando o Catálogo de dados do Earth Engine (https://developers.google.com/earth-engine/datasets) pelo ImageCollection
ID . Pesquise o conjunto de dados Sentinel-2 Nível 1C e você encontrará seu id COPERNICUS/S2_SR . Visite a página do Sentinel-2, Nível 1C
(https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2) e consulte a seção Explorar no Earth Engine para encontrar
o snippet de código para carregar e visualizar a coleção. Este snippet é um excelente ponto de partida para seu trabalho com este conjunto
de dados. Clique no botão Copiar amostra de código e cole o código no editor de código. Clique em Executare você verá os blocos de imagens
carregados no mapa.

No trecho de código, você verá uma função Map.setCenter() que define a viewport para um local específico e nível de zoom. A função usa
a coordenada X (longitude), a coordenada Y (latitude) e os parâmetros do Nível de Zoom. Substitua as coordenadas X e Y pelas coordenadas
da sua cidade e clique em Executar para ver as imagens da sua cidade.
Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F02c_Image_Collections_(exercise))

// Find the 'Sentinel-2 Level-1C' dataset page


// https://developers.google.com/earth-engine/datasets

// Copy/page the code snippet

// Change the code to display images for your home city

03. Filtrando coleções de imagens


A coleção contém todas as imagens já coletadas pelo sensor. As coleções inteiras não são muito úteis. A maioria dos aplicativos requer um
subconjunto das imagens. Usamos filtros para selecionar as imagens apropriadas. Existem muitos tipos de funções de filtro, consulte o
ee.Filter... módulo para ver todos os filtros disponíveis. Selecione um filtro e execute a filter() função com os parâmetros do filtro.

Aprenderemos sobre 3 tipos principais de técnicas de filtragem

Filtrar por metadados : você pode aplicar um filtro nos metadados da imagem usando filtros como ee.Filter.eq() ,
ee.Filter.lt() etc. Você pode filtrar por valores de CAMINHO/LINHA, número de órbita, cobertura de nuvens etc.
Filtrar por data : você pode selecionar imagens em um determinado intervalo de datas usando filtros como ee.Filter.date() .
Filtrar por local : você pode selecionar o subconjunto de imagens com uma caixa delimitadora, local ou geometria usando o
ee.Filter.bounds() . Você também pode usar as ferramentas de desenho para desenhar uma geometria para filtragem.

Depois de aplicar os filtros, você pode usar a size() função para verificar quantas imagens correspondem aos filtros.
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F03b_Filtering_Image_Collection_(complete))

var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241])


Map.centerObject(geometry, 10)

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');

// Filter by metadata
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30));

// Filter by date
var filtered = s2.filter(ee.Filter.date('2019-01-01', '2020-01-01'));

// Filter by location
var filtered = s2.filter(ee.Filter.bounds(geometry));

// Let's apply all the 3 filters together on the collection

// First apply metadata fileter


var filtered1 = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30));
// Apply date filter on the results
var filtered2 = filtered1.filter(
ee.Filter.date('2019-01-01', '2020-01-01'));
// Lastly apply the location filter
var filtered3 = filtered2.filter(ee.Filter.bounds(geometry));

// Instead of applying filters one after the other, we can 'chain' them
// Use the . notation to apply all the filters together
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

print(filtered.size());

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F03c_Filtering_Image_Collection_(exercise))
// Add one more filter in the script below to select images
// from only one of the satellites - Sentinel-2A - from the
// Sentinel-2 constellation

// Hint1: Use the 'SPACECRAFT_NAME' property


// Hint2: Use the ee.Filter.eq() filter

var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);


Map.centerObject(geometry, 10);

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
// Remove this comment and add a filter here

print(filtered.size());

04. Criando mosaicos e composições a partir de ImageCollections


A ordem padrão da coleção é por data. Portanto, quando você exibe a coleção, ela cria implicitamente um mosaico com os pixels mais
recentes no topo. Você pode chamar .mosaic() uma ImageCollection para criar uma imagem em mosaico dos pixels na parte superior.

Também podemos criar uma imagem composta aplicando critérios de seleção a cada pixel de todos os pixels da pilha. Aqui usamos a
median() função para criar uma composição onde cada valor de pixel é a mediana de todos os pixels da pilha.

Dica: Se você precisar criar um mosaico onde as imagens estejam em uma ordem específica, você pode
usar a .sort() função para classificar sua coleção por uma propriedade primeiro.

Mosaico vs. Composto

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2F04b_Mosaics_and_Composites_(complete))
var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var mosaic = filtered.mosaic();

var medianComposite = filtered.median();

Map.addLayer(filtered, rgbVis, 'Filtered Collection');


Map.addLayer(mosaic, rgbVis, 'Mosaic');
Map.addLayer(medianComposite, rgbVis, 'Median Composite');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F04c_Mosaics_and_Composites_(exercise))

// Create a median composite for the year 2020 and load it to the map

// Compare both the composites to see the changes in your city

05. Trabalhando com coleções de recursos


As coleções de recursos são semelhantes às coleções de imagens - mas contêm recursos , não imagens. Eles são equivalentes a Camadas
Vetoriais em um GIS. Podemos carregar, filtrar e exibir coleções de recursos usando técnicas semelhantes que aprendemos até agora.

Pesquise por GAUL Second Level Administrative Boundaries e carregue a coleção. Esta é uma coleção global que contém todos os limites
Admin2. Podemos aplicar um filtro usando a ADM1_NAME propriedade para obter todos os limites Admin2 (ou seja, distritos) de um estado.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2F05b_Feature_Collections_(complete))
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');

var karnataka = admin2.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'));

var visParams = {'color': 'red'};


Map.addLayer(karnataka, visParams, 'Karnataka Districts');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F05c_Feature_Collections_(exercise))

// Apply a filter to select only the 'Bangalore Urban' district


// Display only the selected district

// Hint: The district names are in ADM2_NAME property

var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');


var karnataka = admin2.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'));

var visParams = {'color': 'red'};

06. Importando Dados


Você pode importar dados vetoriais ou rasterizados para o Earth Engine. Vamos agora importar um shapefile de Urban Areas
(https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-urban-area/) for Natural Earth. Descompacte o
ne_10m_urban_areas.zip em uma pasta em seu computador. No Code Editor, vá para Assets → New → Table Upload → Shape Files .
Selecione .shp , .shx , .dbf e . prj arquivos. Digite ne_10m_urban_areas como o Nome do ativo e clique em Carregar . Depois que o
upload e a ingestão forem concluídos, você terá um novo ativo na guia Ativos . O shapefile é importado como uma coleção de recursos no
Earth Engine. Selecione o ne_10m_urban_areas ativo e clique em Importar . Você pode então visualizar os dados importados.

Importando um Shapefile

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2F06b_Import_(complete))

// Let's import some data to Earth Engine


// Upload the ne_10m_urban_areas.shp shapefile

// Import the collection


var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/ne_10m_urban_areas');

// Visualize the collection


Map.addLayer(urban, {color: 'blue'}, 'Urban Areas');
Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F06c_Import_(exercise))

// Apply a filter to select only large urban areas


// Use the property 'area_sqkm' and select all features
// with area > 400 sq.km

var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/ne_10m_urban_areas');

07. Clipes de Imagens


Muitas vezes, é desejável recortar as imagens na sua área de interesse. Você pode usar a clip() função para mascarar uma imagem usando
uma geometria.

Enquanto em um software de área de trabalho, o recorte é desejável para remover partes desnecessárias
de uma imagem grande e economizar tempo de computação, no Earth Engine, o recorte pode realmente
aumentar o tempo de computação. Conforme descrito no guia de práticas recomendadas de codificação
do Earth Engine (https://developers.google.com/earth-engine/guides/best_practices?hl=en#if-you-dont-
need-to-clip,-dont-use-clip) , evite recortar as imagens ou faça isso no final do script.

Imagem original vs. imagem recortada

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2F07b_Clipping_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var urban = ee.FeatureCollection("users/ujavalgandhi/e2e/ne_10m_urban_areas");

// Find the feature id by adding the layer to the map and using Inspector.
var filtered = urban.filter(ee.Filter.eq('system:index', '00000000000000002bf8'));

var geometry = filtered.geometry();

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var image = filtered.median();

var clipped = image.clip(geometry);

Map.addLayer(clipped, rgbVis, 'Clipped');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F07c_Clipping_(exercise))

// Add the imported table to the Map


// Use the Inspector to find the id of your home city or any urban area of your choice
// Change the filter to use the id of the selected feature

08. Exportando Dados


O Earth Engine permite exportar dados vetoriais e raster para serem usados ​em um programa externo. Dados vetoriais podem ser
exportados como a CSV ou a Shapefile , enquanto Rasters podem ser exportados como GeoTIFF arquivos. Agora, exportaremos o
Sentinel-2 Composite como um arquivo GeoTIFF.

Dica: o Editor de código oferece suporte ao preenchimento automático de funções de API usando a
combinação Ctrl+Espaço . Digite alguns caracteres de uma função e pressione Ctrl+Espaço para ver as
sugestões de preenchimento automático. Você também pode usar a mesma combinação de teclas para
preencher todos os parâmetros da função automaticamente.

Depois de executar este script, a guia Tarefas será destacada. Alterne para a guia e você verá as tarefas em espera. Clique em Executar ao lado
de cada tarefa para iniciar o processo.
Ao clicar no botão Executar , uma caixa de diálogo de confirmação será solicitada. Verifique as configurações e clique em Executar para iniciar
a exportação.

Assim que a exportação terminar, um arquivo GeoTiff para cada tarefa de exportação será adicionado ao seu Google Drive na pasta
especificada. Você pode baixá-los e usá-los em um software GIS.
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F08b_Export_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/ne_10m_urban_areas');

var filtered = urban.filter(ee.Filter.eq('system:index', '00000000000000002bf8'));


var geometry = filtered.geometry();

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var image = filtered.median();

var clipped = image.clip(geometry);

Map.addLayer(clipped, rgbVis, 'Clipped');

var exportImage = clipped.select('B.*');

Export.image.toDrive({
image: exportImage,
description: 'Bangalore_Composite_Raw',
folder: 'earthengine',
fileNamePrefix: 'bangalore_composite_raw',
region: geometry,
scale: 10,
maxPixels: 1e9
});

// Rather than exporting raw bands, we can apply a rendered image


// visualize() function allows you to apply the same parameters
// that are used in earth engine which exports a 3-band RGB image
print(clipped);
var visualized = clipped.visualize(rgbVis);
print(visualized);
// Now the 'visualized' image is RGB image, no need to give visParams
Map.addLayer(visualized, {}, 'Visualized Image');

Export.image.toDrive({
image: visualized,
description: 'Bangalore_Composite_Visualized',
folder: 'earthengine',
fileNamePrefix: 'bangalore_composite_visualized',
region: geometry,
scale: 10,
maxPixels: 1e9
});

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F08c_Export_(exercise))

// Write the export function to export the results for your chosen urban area

Tarefa 1
Carregue os dados das luzes noturnas de 2019 e 2020. Compare as imagens da sua região e encontre as mudanças na cidade.
Atribuição 1 Saída Esperada

Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-


Engine-Basics%2FAssignment1)

// Assignment
// Export the Night Lights images for May,2015 and May,2020

// Workflow:
// Load the VIIRS Nighttime Day/Night Band Composites collection
// Filter the collection to the date range
// Extract the 'avg_rad' band which represents the nighttime lights
// Clip the image to the geometry of your city
// Export the resulting image as a GeoTIFF file.

// Hint1:

// There are 2 VIIRS Nighttime Day/Night collections


// Use the one that corrects for stray light

// Hint2:

// The collection contains 1 global image per month


// After filtering for the month, there will be only 1 image in the collection

// You can use the following technique to extract that image


// var image = ee.Image(filtered.first())
Módulo 2: Earth Engine Intermediário
O Módulo 2 se baseia nas habilidades básicas do Earth Engine que você adquiriu. Este modelo apresenta os conceitos de programação
paralela usando Map/Reduce - que é a chave para usar o Earth Engine de forma eficaz para analisar grandes volumes de dados. Você
aprenderá como usar a API do Earth Engine para calcular vários índices espectrais, mascarar nuvens e, em seguida, usar map/reduce para
aplicar esses cálculos a coleções de imagens. Você também aprenderá como obter longas séries temporais de dados e criar gráficos.

(https://docs.google.com/presentation/d/10qOyxhubkwnsAVjniW54ETgwUHq3DXYKo3HGb6Gemi0/edit?usp=sharing)

Veja a Apresentação ↗ (https://docs.google.com/presentation/d/10qOyxhubkwnsAVjniW54ETgwUHq3DXYKo3HGb6Gemi0/edit?


usp=sharing)

01. Objetos da Máquina Terrestre


Este script apresenta os fundamentos da API do Earth Engine. Ao programar no Earth Engine, você deve usar a API do Earth Engine para que
seus cálculos possam usar os servidores do Google Earth Engine. Para saber mais, visite a seção Objetos e métodos
(https://developers.google.com/earth-engine/tutorial_js_02) do Earth Engine no Guia do usuário do Earth Engine.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2F01b_Earth_Engine_Objects_(complete))
// Let's see how to take a list of numbers and add 1 to each element
var myList = ee.List.sequence(1, 10);

// Define a function that takes a number and adds 1 to it


var myFunction = function(number) {
return number + 1;
}
print(myFunction(1));

//Re-Define a function using Earth Engine API


var myFunction = function(number) {
return ee.Number(number).add(1);
}

// Map the function of the list


var newList = myList.map(myFunction);
print(newList);

// Extracting value from a list

var value = newList.get(0);


print(value);

// Casting

// Let's try to do some computation on the extracted value


//var newValue = value.add(1)
//print(newValue)

// You get an error because Earth Engine doesn't know what is the type of 'value'
// We need to cast it to appropriate type first
var value = ee.Number(value);
var newValue = value.add(1);
print(newValue);

// Dictionary
// Convert javascript objects to EE Objects
var data = {'city': 'Bengaluru', 'population': 8400000, 'elevation': 930};
var eeData = ee.Dictionary(data);
// Once converted, you can use the methods from the
// ee.Dictionary module
print(eeData.get('city'))

// Dates
// For any date computation, you should use ee.Date module

var date = ee.Date('2019-01-01')


var futureDate = date.advance(1, 'year')
print(futureDate)

Como regra geral, você sempre deve usar os métodos da API do Earth Engine em seu código, há uma
exceção em que você precisará usar o método Javascript do lado do cliente. Se você deseja obter a hora
atual, o servidor não sabe sua hora. Você precisa usar o método javascript e convertê-lo em um objeto
Earth Engine.

var now = Date.now()


print(now)
var now = ee.Date(now)
print(now)

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F01c_Earth_Engine_Objects_(exercise))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);

var now = Date.now()


var now = ee.Date(now)
// Apply another filter to the collection below to filter images
// collected in the last 1-month
// Do not hard-code the dates, it should always show images
// from the past 1-month whenever you run the script
// Hint: Use ee.Date.advance() function
// to compute the date 1 month before now
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))

02. Cálculo de Índices


Índices espectrais são centrais para muitos aspectos do sensoriamento remoto. Esteja você estudando vegetação ou rastreando incêndios -
você precisará calcular uma proporção pixel a pixel de 2 ou mais bandas. A fórmula mais comumente usada para calcular um índice é a
Diferença Normalizada entre 2 bandas. O Earth Engine fornece uma função auxiliar normalizedDifference() para ajudar a calcular índices
normalizados, como o Índice de Vegetação por Diferença Normalizada (NDVI). Para fórmulas mais complexas, você também pode usar a
expression() função para descrever o cálculo.

Imagens MNDWI, SAVI e NDVI

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2F02b_Calculating_Indices_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');

var bangalore = admin2.filter(ee.Filter.eq('ADM2_NAME', 'Bangalore Urban'));


var geometry = bangalore.geometry();

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var image = filtered.median();

// Calculate Normalized Difference Vegetation Index (NDVI)


// 'NIR' (B8) and 'RED' (B4)
var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);

// Calculate Modified Normalized Difference Water Index (MNDWI)


// 'GREEN' (B3) and 'SWIR1' (B11)
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);

// Calculate Soil-adjusted Vegetation Index (SAVI)


// 1.5 * ((NIR - RED) / (NIR + RED + 0.5))

// For more complex indices, you can use the expression() function

// Note:
// For the SAVI formula, the pixel values need to converted to reflectances
// Multiplyng the pixel values by 'scale' gives us the reflectance value
// The scale value is 0.0001 for Sentinel-2 dataset

var savi = image.expression(


'1.5 * ((NIR - RED) / (NIR + RED + 0.5))', {
'NIR': image.select('B8').multiply(0.0001),
'RED': image.select('B4').multiply(0.0001),
}).rename('savi');

var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
var ndviVis = {min:0, max:1, palette: ['white', 'green']};
var ndwiVis = {min:0, max:0.5, palette: ['white', 'blue']};

Map.addLayer(image.clip(geometry), rgbVis, 'Image');


Map.addLayer(mndwi.clip(geometry), ndwiVis, 'mndwi');
Map.addLayer(savi.clip(geometry), ndviVis, 'savi');
Map.addLayer(ndvi.clip(geometry), ndviVis, 'ndvi');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F02c_Calculating_Indices_(exercise))

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');

var bangalore = admin2.filter(ee.Filter.eq('ADM2_NAME', 'Bangalore Urban'));


var geometry = bangalore.geometry();
Map.centerObject(geometry);

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var image = filtered.median();

// Exercise

// Calculate the Normalized Difference Built-Up Index (NDBI) for the image
// Hint: NDBI = (SWIR1 – NIR) / (SWIR1 + NIR)
// Visualize the built-up area using a 'red' palette
03. Cálculo em ImageCollections
Até agora, aprendemos como executar a computação em imagens únicas. Se você quiser aplicar algum cálculo - como calcular um índice - a
muitas imagens, precisará usar map() . Você primeiro define uma função que recebe 1 imagem e retorna o resultado da computação nessa
imagem. Em seguida, você pode map() executar essa função sobre o ImageCollection, que resulta em um novo ImageCollection com os
resultados do cálculo. Isso é semelhante a um loop for com o qual você talvez esteja familiarizado - mas o uso map() permite que a
computação seja executada em paralelo. Saiba mais em Mapeamento sobre uma ImageCollection (https://developers.google.com/earth-
engine/guides/ic_mapping)

Computação NDVI em uma ImageCollection

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2F03b_Computation_on_Image_Collections_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin1 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level1');

var karnataka = admin1.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'));


var geometry = karnataka.geometry();
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var composite = filtered.median().clip(geometry);


Map.addLayer(composite, rgbVis, 'Karnataka Composite');

// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

// Map the function over the collection


var withNdvi = filtered.map(addNDVI);

var composite = withNdvi.median();

var ndviComposite = composite.select('ndvi').clip(karnataka);

var palette = [
'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301'];

var ndviVis = {min:0, max:0.5, palette: palette };


Map.addLayer(ndviComposite, ndviVis, 'ndvi');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F03c_Computation_on_Image_Collections_(exercise))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin1 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level1');

var karnataka = admin1.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'));


var geometry = karnataka.geometry();
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

var composite = filtered.median().clip(geometry);


Map.addLayer(composite, rgbVis, 'Karnataka Composite');

// This function calculates both NDVI an d NDWI indices


// and returns an image with 2 new bands added to the original image.
function addIndices(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
var ndwi = image.normalizedDifference(['B3', 'B8']).rename('ndwi');
return image.addBands(ndvi).addBands(ndwi);
}

// Map the function over the collection


var withIndices = filtered.map(addIndices);

// Composite
var composite = withIndices.median();
print(composite);

// Extract the 'ndwi' band and display a NDWI map


// use the palette ['white', 'blue']
// Hint: Use .select() function to select a band

04. Mascaramento de Nuvem


Mascarar pixels em uma imagem torna esses pixels transparentes e os exclui da análise e visualização. Para mascarar uma imagem, podemos
usar a updateMask() função e passar uma imagem com valores 0 e 1. Todos os pixels onde a imagem da máscara é 0 serão mascarados.

A maioria dos conjuntos de dados de sensoriamento remoto vem com uma banda QA ou Cloud Mask que contém as informações sobre se os
pixels estão nublados ou não. Seu editor de código contém funções predefinidas para mascarar nuvens para conjuntos de dados populares na
guia Scripts → Exemplos → Máscara de nuvem .

O script abaixo pega a função de mascaramento do Sentinel-2 e mostra como aplicá-la em uma imagem.

Aplicando bitmask QA pixel a pixel


Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F04b_Cloud_Masking_(complete))

var image = ee.Image('COPERNICUS/S2_HARMONIZED/20190703T050701_20190703T052312_T43PGP')


var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

Map.centerObject(image);
Map.addLayer(image, rgbVis, 'Full Image', false);

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60');
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask)
.select('B.*')
.copyProperties(image, ['system:time_start']);
}

var maskedImage = ee.Image(maskS2clouds(image));


Map.addLayer(maskedImage, rgbVis, 'Masked Image');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F04c_Cloud_Masking_(exercise))

// Get the Level-2A Surface Reflectance image


var imageSR = ee.Image('COPERNICUS/S2_SR_HARMONIZED/20190703T050701_20190703T052312_T43PGP');
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Map.centerObject(imageSR);
Map.addLayer(imageSR, rgbVis, 'SR Image');

// Function to remove cloud and snow pixels from Sentinel-2 SR image


function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(5);
var snow = snowProb.lt(5);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 5% or cloud shadow classification
var mask = (cloud.and(snow)).and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

// Exercise
// Apply the above cloud masking function to SR image
// Add the masked image to the map

// Hint: After adding the masked image to the map, turn-off


// the original image layer to see the result of the masking function
Se você estiver usando dados do Sentinel-2, verifique uma técnica alternativa de máscara de nuvem
usando o conjunto de dados S2 Cloudless . Saber mais (https://medium.com/google-earth/more-accurate-
and-flexible-cloud-masking-for-sentinel-2-images-766897a9ba5f)

var imageSR = ee.Image('COPERNICUS/S2_SR/20190703T050701_20190703T052312_T43PGP')


var s2Cloudless = ee.Image('COPERNICUS/S2_CLOUD_PROBABILITY/20190703T050701_20190703T052312_T43PG
P')
var clouds = s2Cloudless.lt(50)
var cloudlessMasked = imageSR.mask(clouds)
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.addLayer(cloudlessMasked, rgbVis, 'S2 Cloudless Masked Image')

05. Redutores
Ao escrever código de computação paralela, uma operação Reduce permite que você calcule estatísticas em uma grande quantidade de
entradas. No Earth Engine, você precisa executar a operação de redução ao criar compostos, calcular estatísticas, fazer análises de
regressão, etc. A API do Earth Engine vem com um grande número de funções redutoras integradas (como ee.Reducer.sum() ,
ee.Reducer.histogram() , ee.Reducer.linearFit() etc.) de operações estatísticas em dados de entrada. Você pode executar
redutores usando a reduce() função. O Earth Engine suporta a execução de redutores em todas as estruturas de dados que podem conter
vários valores, como imagens (redutores executados em bandas diferentes), ImageCollection, FeatureCollection, lista, dicionário etc. O
script abaixo apresenta conceitos básicos relacionados a redutores.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2F05b_Reducers_(complete))
// Computing stats on a list
var myList = ee.List.sequence(1, 10);
print(myList)

// Use a reducer to compute min and max in the list


var mean = myList.reduce(ee.Reducer.mean());
print(mean);

var geometry = ee.Geometry.Polygon([[


[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]]);
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
Map.centerObject(geometry);

// Apply a reducer on a image collection


var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');

print(filtered.size());
var collMean = filtered.reduce(ee.Reducer.mean());
print('Reducer on Collection', collMean);

var image = ee.Image('COPERNICUS/S2/20190223T050811_20190223T051829_T44RPR');


var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.addLayer(image, rgbVis, 'Image');
Map.addLayer(geometry, {color: 'red'}, 'Farm');
// If we want to compute the average value in each band,
// we can use reduceRegion instead
var stats = image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: geometry,
scale: 100,
maxPixels: 1e10
});
print(stats);

// Result of reduceRegion is a dictionary.


// We can extract the values using .get() function
print('Average value in B4', stats.get('B4'));

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F05c_Reducers_(exercise))

var geometry = ee.Geometry.Polygon([[


[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]]);

var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
var image = ee.Image('COPERNICUS/S2_HARMONIZED/20190223T050811_20190223T051829_T44RPR');
Map.addLayer(image, rgbVis, 'Image');
Map.addLayer(geometry, {color: 'red'}, 'Farm');
Map.centerObject(geometry);

var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');

// Exercise
// Compute the average NDVI for the farm from the given image
// Hint: Use the reduceRegion() function
06. Gráficos de séries temporais
Agora podemos reunir todas as habilidades que aprendemos até agora - filtrar, mapear, reduzir e mascarar nuvens para criar um gráfico de
valores médios de NDVI para um determinado farm durante 1 ano. A API do Earth Engine vem com suporte para funções de gráficos
baseadas na API do Google Chart. Aqui usamos a ui.Chart.image.series() função para criar um gráfico de série temporal.

Calculando séries temporais NDVI para uma fazenda


Série temporal NDVI mostrando o ciclo de corte duplo

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2F06b_Time_Series_Charts_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Polygon([[
[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]]);
Map.addLayer(geometry, {color: 'red'}, 'Farm');
Map.centerObject(geometry);
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};

var filtered = s2
.filter(ee.Filter.date('2017-01-01', '2018-01-01'))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry));

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60');
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask)//.divide(10000)
.select('B.*')
.copyProperties(image, ['system:time_start']);
}

var filtered = filtered.map(maskS2clouds);


// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

// Map the function over the collection


var withNdvi = filtered.map(addNDVI);

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: withNdvi.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 10
}).setOptions({
lineWidth: 1,
title: 'NDVI Time Series',
interpolateNulls: true,
vAxis: {title: 'NDVI'},
hAxis: {title: '', format: 'YYYY-MMM'}
})
print(chart);

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F06c_Time_Series_Charts_(exercise))

// Delete the farm boundary from the previous script


// and add another farm at a location of your choice

// Print the chart.


Tarefa 2

Atribuição 2 Saída Esperada

Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-


Engine-Intermediate%2FAssignment2)

var terraclimate = ee.ImageCollection("IDAHO_EPSCOR/TERRACLIMATE");


var geometry = ee.Geometry.Point([77.54849920033682, 12.91215102400037]);

// Assignment
// Use Gridded Climate dataset to chart a 40+ year time series
// if temparature at any location

// Workflow
// Load the TerraClimate collection
// Select the 'tmmx' band
// Scale the band values
// Filter the scaled collection
// Use ui.Chart.image.series() function to create the chart

// Hint1
// Data needed to be scaled by 0.1
// map() a function and multiply each image
// Multiplying creates a new image that doesn't have the same properties
// Use copyProperties() function to copy timestamp to new image
var tmax = terraclimate.select('tmmx')
var tmaxScaled = tmax.map(function(image) {
return image.multiply(0.1)
.copyProperties(image,['system:time_start']);
})

// Hint2
// You will need to specify a scale in meters for charting
// Use projection().nominalScale() to find the
// image resolution in meters
var image = ee.Image(terraclimate.first())
print(image.projection().nominalScale())
Módulo 3: Classificação Supervisionada
Introdução ao aprendizado de máquina e classificação supervisionada
A classificação supervisionada é indiscutivelmente a mais importante técnica clássica de aprendizado de máquina em sensoriamento
remoto. As aplicações vão desde a geração de mapas de Uso/Cobertura do Solo até a detecção de alterações. O Google Earth Engine é
adequado exclusivamente para fazer classificação supervisionada em escala. A natureza interativa do desenvolvimento do Earth Engine
permite o desenvolvimento iterativo de fluxos de trabalho de classificação supervisionados, combinando muitos conjuntos de dados
diferentes no modelo. Este módulo abrange o fluxo de trabalho básico de classificação supervisionada, avaliação de precisão, ajuste de
hiperparâmetros e detecção de alterações.

(https://docs.google.com/presentation/d/19L1b5vsxb38xS8GlHNKOjvPZ0IGqDhv93681btMEL5w/edit?usp=sharing)

Veja a Apresentação ↗ (https://docs.google.com/presentation/d/19L1b5vsxb38xS8GlHNKOjvPZ0IGqDhv93681btMEL5w/edit?


usp=sharing)

01. Classificação Básica Supervisionada


Aprenderemos como fazer uma classificação básica da cobertura do solo usando amostras de treinamento coletadas do Editor de código
usando as imagens do mapa base de alta resolução fornecidas pelo Google Maps. Este método não requer dados de treinamento prévio e é
bastante eficaz para gerar amostras de classificação de alta qualidade em qualquer lugar do mundo. O objetivo é classificar cada pixel de
origem em uma das seguintes classes - urbano, nu, água ou vegetação. Usando as ferramentas de desenho no editor de código, você cria 4
novas coleções de recursos com pontos que representam pixels dessa classe. Cada coleção de feições possui uma propriedade chamada
landcover com valores de 0, 1, 2 ou 3 indicando se a coleção de feições representa urbana, nua, água ou vegetação, respectivamente. Em
seguida, treinamos uma Random Forestclassificador usando esse conjunto de treinamento para construir um modelo e aplicá-lo a todos os
pixels da imagem para criar uma imagem de 4 classes.

Curiosidade: os classificadores na API do Earth Engine têm nomes que começam com smile - como
ee.Classifier.smileRandomForest() . A parte do sorriso refere-se à biblioteca JAVA Statistical
Machine Intelligence and Learning Engine (SMILE) (https://haifengl.github.io/index.html) , que é usada
pelo Google Earth Engine para implementar esses algoritmos.

Saída de Classificação Supervisionada

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F01d_Basic_Supervised_Classification_(noimport))
var bangalore = ee.FeatureCollection('users/ujavalgandhi/public/bangalore_boundary');
var geometry = bangalore.geometry();

var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
// The following collections were created using the
// Drawing Tools in the code editor
var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/urban_gcps');
var bare = ee.FeatureCollection('users/ujavalgandhi/e2e/bare_gcps');
var water = ee.FeatureCollection('users/ujavalgandhi/e2e/water_gcps');
var vegetation = ee.FeatureCollection('users/ujavalgandhi/e2e/vegetation_gcps');

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');

var composite = filtered.median().clip(geometry);

// Display the input composite.


var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Map.addLayer(composite, rgbVis, 'image');

var gcps = urban.merge(bare).merge(water).merge(vegetation);

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: gcps,
properties: ['landcover'],
scale: 10
});

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
// // Classify the image.
var classified = composite.classify(classifier);
Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

// Display the GCPs


// We use the style() function to style the GCPs
var palette = ee.List(['gray','brown','blue','green'])
var landcover = ee.List([0, 1, 2, 3])

var gcpsStyled = ee.FeatureCollection(


landcover.map(function(lc){
var color = palette.get(landcover.indexOf(lc));
var markerStyle = { color: 'white', pointShape: 'diamond',
pointSize: 4, width: 1, fillColor: color}
return gcps.filter(ee.Filter.eq('landcover', lc))
.map(function(point){
return point.set('style', markerStyle)
})
})).flatten();

Map.addLayer(gcpsStyled.style({styleProperty:"style"}), {}, 'GCPs')


Map.centerObject(gcpsStyled)
Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-
Supervised-Classification%2F01c_Basic_Supervised_Classification_(exercise))

var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var urbanAreas = ee.FeatureCollection('users/ujavalgandhi/e2e/ne_10m_urban_areas');

// Perform supervised classification for your city


// Find the feature id by adding the layer to the map and using Inspector.
var city = urbanAreas.filter(ee.Filter.eq('system:index', '00000000000000002bf8'));
var geometry = city.geometry();
Map.centerObject(geometry);

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');

var composite = filtered.median().clip(geometry);

// Display the input composite.

var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.addLayer(composite, rgbVis, 'image');

// Exercise
// Add training points for 4 classes
// Assign the 'landcover' property as follows

// urban: 0
// bare: 1
// water: 2
// vegetation: 3

// After adding points, uncomments lines below

// var gcps = urban.merge(bare).merge(water).merge(vegetation);

// // Overlay the point on the image to get training data.


// var training = composite.sampleRegions({
// collection: gcps,
// properties: ['landcover'],
// scale: 10,
// tileScale: 16
// });
// print(training);

// // Train a classifier.
// var classifier = ee.Classifier.smileRandomForest(50).train({
// features: training,
// classProperty: 'landcover',
// inputProperties: composite.bandNames()
// });
// // // Classify the image.
// var classified = composite.classify(classifier);
// Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

02. Avaliação de Precisão


É importante obter uma estimativa quantitativa da precisão da classificação. Para fazer isso, uma estratégia comum é dividir suas amostras
de treinamento em 2 frações aleatórias - uma usada para treinar o modelo e outra para validação das previsões. Depois que um classificador é
treinado, ele pode ser usado para classificar a imagem inteira. Podemos então comparar os valores classificados com os da fração de
validação. Podemos usar o ee.Classifier.confusionMatrix() método para calcular uma matriz de confusão que representa a precisão
esperada.

Os resultados da classificação são avaliados com base nas seguintes métricas

Precisão geral : quantas amostras foram classificadas corretamente.


Precisão do produtor : Quão bem a classificação previu cada classe.
Precisão do consumidor (confiabilidade) : quão confiável é a previsão em cada classe.
Coeficiente Kappa : O desempenho da classificação em comparação com a atribuição aleatória.

Avaliação de Precisão

Não se empolgue ajustando seu modelo para lhe dar a maior precisão de validação. Você deve usar
medidas qualitativas (como inspeção visual de resultados) junto com medidas quantitativas para avaliar os
resultados.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F02b_Accuracy_Assessment_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_7");
var gcp = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_gcps");

var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640));


var geometry = arkavathy.geometry();
Map.centerObject(geometry);

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');

var composite = filtered.median().clip(geometry);

// Display the input composite.


Map.addLayer(composite, rgbVis, 'image');

// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn();

// This being a simpler classification, we take 60% points


// for validation. Normal recommended ratio is
// 70% training, 30% validation
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Classify the image.


var classified = composite.classify(classifier);

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

//**************************************************************************
// Accuracy Assessment
//**************************************************************************

// Use classification map to assess accuracy using the validation fraction


// of the overall training set created above.
var test = classified.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
tileScale: 16,
scale: 10,
});
var testConfusionMatrix = test.errorMatrix('landcover', 'classification')
// Printing of confusion matrix may time out. Alternatively, you can export it as CSV
print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());

// Alternate workflow
// This is similar to machine learning practice
var validation = composite.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});

var test = validation.classify(classifier);

var testConfusionMatrix = test.errorMatrix('landcover', 'classification')


// Printing of confusion matrix may time out. Alternatively, you can export it as CSV
print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-
Supervised-Classification%2F02c_Accuracy_Assessment_(exercise))

var classified = ee.Image('users/ujavalgandhi/e2e/arkavathy_base_classification');


var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var gcp = gcp.randomColumn();

var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));


var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));

//**************************************************************************
// Accuracy Assessment
//**************************************************************************

// Use classification map to assess accuracy using the validation fraction


// of the overall training set created above.
var test = classified.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
tileScale: 16,
scale: 10,
});

var testConfusionMatrix = test.errorMatrix('landcover', 'classification');


print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());

// Exercise

// Calculate and print the following assessment metrics


// 1. Producer's accuracy
// 2. Consumer's accuracy
// 3. Kappa coefficient

// Hint: Look at the ee.ConfusionMatrix module for appropriate methods

03. Aperfeiçoamento da Classificação


O modelo de dados do Earth Engine é especialmente adequado para tarefas de aprendizado de máquina devido à sua capacidade de
incorporar facilmente fontes de dados de diferentes resoluções espaciais, projeções e tipos de dados. Ao fornecer informações adicionais ao
classificador, ele é capaz de separar classes diferentes facilmente. Aqui, pegamos o mesmo exemplo e o aumentamos com as seguintes
técnicas

Aplicar máscara de nuvem


Adicionar Índices Espectrais : Adicionamos bandas para diferentes índices espectrais, como - NDVI, NDBI, MNDWI e BSI.
Adicionar Elevação e Inclinação : Também adicionamos bandas de inclinação e elevação do ALOS DEM.
Normalize as entradas : os modelos de aprendizado de máquina funcionam melhor quando todas as entradas têm a mesma escala.
Vamos dividir cada banda com o valor máximo. Este método garante que todos os valores de entrada estejam entre 0-1. Uma técnica
mais completa e robusta para normalização de imagens é fornecida no Suplemento do curso.

Nossos recursos de treinamento têm mais parâmetros e contêm valores da mesma escala. O resultado é uma classificação muito melhorada.

Precisão de classificação aprimorada com o uso de índices espectrais e dados de elevação

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F03b_Improving_the_Classification_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.Image('JAXA/ALOS/AW3D30/V2_2');

var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640));


var geometry = arkavathy.geometry();
Map.centerObject(geometry);

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
// Function to remove cloud and snow pixels from Sentinel-2 SR image

function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(10);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 10% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.map(maskCloudAndShadowsSR)
.select('B.*');

var composite = filtered.median().clip(geometry);

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi);
};

var composite = addIndices(composite);

// Calculate Slope and Elevation


var elev = alos.select('AVE_DSM').rename('elev');
var slope = ee.Terrain.slope(alos.select('AVE_DSM')).rename('slope');

var composite = composite.addBands(elev).addBands(slope);

var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000, gamma: 1.2};
Map.addLayer(composite, visParams, 'RGB');

// Normalize the image

// Machine learning algorithms work best on images when all features have
// the same range

// Function to Normalize Image


// Pixel Values should be between 0 and 1
// Formula is (x - xmin) / (xmax - xmin)
//**************************************************************************
function normalize(image){
var bandNames = image.bandNames();
// Compute min and max of the image
var minDict = image.reduceRegion({
reducer: ee.Reducer.min(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var maxDict = image.reduceRegion({
reducer: ee.Reducer.max(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var mins = ee.Image.constant(minDict.values(bandNames));
var maxs = ee.Image.constant(maxDict.values(bandNames));

var normalized = image.subtract(mins).divide(maxs.subtract(mins));


return normalized;
}

var composite = normalize(composite);


// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn();

// This being a simpler classification, we take 60% points


// for validation. Normal recommended ratio is
// 70% training, 30% validation
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});
print(training);

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Classify the image.


var classified = composite.classify(classifier);

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

//**************************************************************************
// Accuracy Assessment
//**************************************************************************

// Use classification map to assess accuracy using the validation fraction


// of the overall training set created above.
var test = classified.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});

var testConfusionMatrix = test.errorMatrix('landcover', 'classification');

// Printing of confusion matrix may time out. Alternatively, you can export it as CSV
print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-
Supervised-Classification%2F03c_Improving_the_Classification_(exercise))

// Exercise

// Improve your classification from Exercise 01c


// Add different spectral indicies to your composite
// by using the function below

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi);
};

04. Resultados de Classificação de Exportação


Ao trabalhar com classificadores complexos em grandes regiões, você pode obter um limite de memória do usuário excedido ou um erro de
tempo limite de computação no Editor de código. A razão para isso é que há um limite de tempo fixo e memória menor alocada para o código
que é executado com o modo On-Demand Computation . Para cálculos maiores, você pode usar o modo Batch com as Export funções. As
exportações são executadas em segundo plano e podem durar mais de 5 minutos alocados para o código de computação executado no Editor
de código. Isso permite que você processe conjuntos de dados muito grandes e complexos. Aqui está um exemplo mostrando como exportar
seus resultados de classificação para o Google Drive.

Só podemos exportar imagens ou FeatureCollections. E se você quisesse exportar um número resultante


de uma longa computação? Um hack útil é criar um FeatureCollection com apenas 1 recurso contendo
null geometria e uma propriedade contendo o número que você deseja exportar.
Saídas de classificação exportadas

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F04b_Exporting_Classification_Results_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.Image('JAXA/ALOS/AW3D30/V2_2');

var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640))


var geometry = arkavathy.geometry()

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
// Function to remove cloud and snow pixels from Sentinel-2 SR image

function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(10);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 10% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.map(maskCloudAndShadowsSR)
.select('B.*');

var composite = filtered.median().clip(geometry) ;

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi);
};

var composite = addIndices(composite);

// Calculate Slope and Elevation


var elev = alos.select('AVE_DSM').rename('elev');
var slope = ee.Terrain.slope(alos.select('AVE_DSM')).rename('slope');

var composite = composite.addBands(elev).addBands(slope);

var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000, gamma: 1.2};
Map.addLayer(composite, visParams, 'RGB');

// Normalize the image

// Machine learning algorithms work best on images when all features have
// the same range
// Function to Normalize Image
// Pixel Values should be between 0 and 1
// Formula is (x - xmin) / (xmax - xmin)
//**************************************************************************
function normalize(image){
var bandNames = image.bandNames();
// Compute min and max of the image
var minDict = image.reduceRegion({
reducer: ee.Reducer.min(),
geometry: geometry,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var maxDict = image.reduceRegion({
reducer: ee.Reducer.max(),
geometry: geometry,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var mins = ee.Image.constant(minDict.values(bandNames));
var maxs = ee.Image.constant(maxDict.values(bandNames));

var normalized = image.subtract(mins).divide(maxs.subtract(mins));


return normalized;
}

var composite = normalize(composite);


// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn();

// This being a simpler classification, we take 60% points


// for validation. Normal recommended ratio is
// 70% training, 30% validation
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));
Map.addLayer(validationGcp);

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});
print(training);

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Classify the image.


var classified = composite.classify(classifier);

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

//**************************************************************************
// Accuracy Assessment
//**************************************************************************

// Use classification map to assess accuracy using the validation fraction


// of the overall training set created above.
var test = classified.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});

var testConfusionMatrix = test.errorMatrix('landcover', 'classification');

print('Confusion Matrix', testConfusionMatrix);


print('Test Accuracy', testConfusionMatrix.accuracy());

//**************************************************************************
// Exporting Results
//**************************************************************************

// Create a Feature with null geometry and the value we want to export.
// Use .array() to convert Confusion Matrix to an Array so it can be
// exported in a CSV file
var fc = ee.FeatureCollection([
ee.Feature(null, {
'accuracy': testConfusionMatrix.accuracy(),
'matrix': testConfusionMatrix.array()
})
]);

print(fc);

Export.table.toDrive({
collection: fc,
description: 'Accuracy_Export',
folder: 'earthengine',
fileNamePrefix: 'accuracy',
fileFormat: 'CSV'
});

Exercício
Também é uma boa ideia exportar a imagem classificada como um ativo. Isso permitirá que você importe a imagem classificada em outro
script sem executar todo o fluxo de trabalho de classificação. Use a função Export.image.toAsset() para exportar a imagem classificada como
um ativo.

Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F04c_Exporting_Classification_Results_(exercise))
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.Image('JAXA/ALOS/AW3D30/V2_2');

var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640))


var geometry = arkavathy.geometry()

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
// Function to remove cloud and snow pixels from Sentinel-2 SR image

function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(10);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 10% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.map(maskCloudAndShadowsSR)
.select('B.*');

var composite = filtered.median().clip(geometry) ;

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi);
};

var composite = addIndices(composite);

// Calculate Slope and Elevation


var elev = alos.select('AVE_DSM').rename('elev');
var slope = ee.Terrain.slope(alos.select('AVE_DSM')).rename('slope');

var composite = composite.addBands(elev).addBands(slope);

var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000, gamma: 1.2};
Map.addLayer(composite, visParams, 'RGB');

// Normalize the image

// Machine learning algorithms work best on images when all features have
// the same range
// Function to Normalize Image
// Pixel Values should be between 0 and 1
// Formula is (x - xmin) / (xmax - xmin)
//**************************************************************************
function normalize(image){
var bandNames = image.bandNames();
// Compute min and max of the image
var minDict = image.reduceRegion({
reducer: ee.Reducer.min(),
geometry: geometry,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var maxDict = image.reduceRegion({
reducer: ee.Reducer.max(),
geometry: geometry,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var mins = ee.Image.constant(minDict.values(bandNames));
var maxs = ee.Image.constant(maxDict.values(bandNames));

var normalized = image.subtract(mins).divide(maxs.subtract(mins));


return normalized;
}

var composite = normalize(composite);


// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn();

// This being a simpler classification, we take 60% points


// for validation. Normal recommended ratio is
// 70% training, 30% validation
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));
Map.addLayer(validationGcp);

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});
print(training);

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Classify the image.


var classified = composite.classify(classifier);

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

// Exercise

// Use the Export.image.toAsset() function to export the


// classified image as a Earth Engine Asset.

// This will allows you to import the classified image in another script
// without running the whole classification workflow.
// Hint: For images with discrete pixel values, we must set the
// pyramidingPolicy to 'mode'.
// The pyramidingPolicy parameter should a dictionary specifying
// the policy for each band. A simpler way to specify it for all
// bands is to use {'.default': 'mode'}

05. Cálculo de Área


Agora que temos os resultados de nossa classificação, aprenderemos como calcular a área dos pixels em cada classe. O cálculo da área para
feições é feito usando a area() função e para imagens usando a ee.Image.pixelArea() função. A ee.Image.pixelArea() função cria
uma imagem onde o valor de cada pixel é a área do pixel. Multiplicamos esta imagem de área de pixel com nossa imagem e somamos a área
usando a reduceRegion() função.

A ee.Image.pixelArea() função usa uma projeção de área igual personalizada para cálculo de área. O
resultado é a área em metros quadrados independente da projeção da imagem de entrada. Saber mais
(https://groups.google.com/g/google-earth-engine-developers/c/Ccaorx-obVw/m/_ZQdP2wVAgAJ)

Calculando a cobertura verde a partir de imagens classificadas

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-


Supervised-Classification%2F05b_Calculating_Area_(complete))
var classified = ee.Image('users/ujavalgandhi/e2e/bangalore_classified');
var bangalore = ee.FeatureCollection('users/ujavalgandhi/public/bangalore_boundary');

Map.addLayer(bangalore, {color: 'blue'}, 'Bangalore City');

Map.addLayer(classified,
{min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']},
'Classified Image 2019');

// Calling .geometry() on a feature collection gives the


// dissolved geometry of all features in the collection

// .area() function calculates the area in square meters


var cityArea = bangalore.geometry().area();

// We can cast the result to a ee.Number() and calculate the


// area in square kilometers
var cityAreaSqKm = ee.Number(cityArea).divide(1e6).round();
print(cityAreaSqKm);

// Area Calculation for Images


var vegetation = classified.eq(3);
// If the image contains values 0 or 1, we can calculate the
// total area using reduceRegion() function

// The result of .eq() operation is a binary image with pixels


// values of 1 where the condition matched and 0 where it didn't
Map.addLayer(vegetation, {min:0, max:1, palette: ['white', 'green']}, 'Green Cover');

// Since our image has only 0 and 1 pixel values, the vegetation
// pixels will have values equal to their area
var areaImage = vegetation.multiply(ee.Image.pixelArea());

// Now that each pixel for vegetation class in the image has the value
// equal to its area, we can sum up all the values in the region
// to get the total green cover.

var area = areaImage.reduceRegion({


reducer: ee.Reducer.sum(),
geometry: bangalore.geometry(),
scale: 10,
maxPixels: 1e10
});
// The result of the reduceRegion() function is a dictionary with the key
// being the band name. We can extract the area number and convert it to
// square kilometers
var vegetationAreaSqKm = ee.Number(area.get('classification')).divide(1e6).round();
print(vegetationAreaSqKm);

Se você deseja calcular a área coberta por cada classe, pode usar um redutor agrupado
(https://developers.google.com/earth-engine/reducers_grouping) . Consulte o Suplemento para ver um
trecho de código.

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-
Supervised-Classification%2F05c_Calculating_Area_(exercise))

// Exercise
// Compute and print the percentage green cover of the city

Módulo 4: Detecção de Mudanças


Introdução à Detecção de Mudanças
Muitos conjuntos de dados de observação da Terra estão disponíveis em intervalos regulares durante longos períodos de tempo. Isso nos
permite detectar mudanças na superfície da Terra. A técnica de detecção de mudanças em sensoriamento remoto se enquadra nas seguintes
categorias

Mudança de Banda Única : Medindo a mudança em uma imagem de banda única ou um índice espectral usando um limite
Mudança de banda múltipla : Medindo a distância espectral e o ângulo espectral entre duas imagens multibanda
Classificação de mudança : classificação de passagem única usando imagem empilhada contendo bandas de antes e depois de um
evento
Comparação pós-classificação : comparando duas imagens classificadas e computando transições de classe

Nas seções a seguir, aplicaremos as técnicas de classificação supervisionada para detecção de mudanças usando imagens multitemporais.

(https://docs.google.com/presentation/d/1vdFTWJ61yDuVfbfhpnumQ8zuMPGwGcHpHsBTRgo_o5I/edit?usp=sharing)

Veja a Apresentação ↗ (https://docs.google.com/presentation/d/1vdFTWJ61yDuVfbfhpnumQ8zuMPGwGcHpHsBTRgo_o5I/edit?


usp=sharing)

01. Alteração do Índice Espectral


Muitos tipos de alteração podem ser detectados medindo a alteração em um índice espectral e aplicando um limite. Essa técnica é adequada
quando há um índice espectral adequado disponível para o tipo de alteração que você está interessado em detectar.

Aqui aplicamos esta técnica para mapear a extensão e a gravidade de um incêndio florestal. A taxa de queima normalizada (NBR) é um índice
projetado para destacar áreas de vegetação queimada. Calculamos o NBR para imagens antes e depois. Em seguida, aplicamos um limite
adequado para encontrar áreas queimadas.
Detecção de Mudança de Índice Espectral

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-


Change-Detection%2F01b_Spectral_Index_Change_(complete))
// On 21st February 2019, massive forest fires broke out in
// numerous places across the Bandipur National Park of
// the Karnataka state in India.
// By 25 February 2019 most fire was brought under control
// This script shows how to do damage assessment using
// spectral index change detection technique.

// Define the area of interest


var geometry = ee.Geometry.Polygon([[
[76.37639666685044, 11.766523239445169],
[76.37639666685044, 11.519036946599561],
[76.78426409849106, 11.519036946599561],
[76.78426409849106, 11.766523239445169]
]]);
var fireStart = ee.Date('2019-02-20');
var fireEnd = ee.Date('2019-02-25');

Map.centerObject(geometry, 10)

var s2 = ee.ImageCollection("COPERNICUS/S2")

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

// Apply filters and cloud mask


var filtered = s2
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.select('B.*')

// Create Before and After composites


var before = filtered
.filter(ee.Filter.date(
fireStart.advance(-2, 'month'), fireStart))
.median()

var after = filtered


.filter(ee.Filter.date(
fireEnd, fireEnd.advance(1, 'month')))
.median()

// Freshly burnt regions appeat bright in SWIR-bands


// Use a False Color Visualization
var swirVis = {
min: 0.0,
max: 3000,
bands: ['B12', 'B8', 'B4'],
};
Map.addLayer(before, swirVis, 'Before')
Map.addLayer(after, swirVis, 'After')

// Write a function to calculate Normalized Burn Ratio (NBR)


// 'NIR' (B8) and 'SWIR-2' (B12)
var addNBR = function(image) {
var nbr = image.normalizedDifference(['B8', 'B12']).rename(['nbr']);
return image.addBands(nbr)
}

var beforeNbr = addNBR(before).select('nbr');


var afterNbr = addNBR(after).select('nbr');
var nbrVis = {min: -0.5, max: 0.5, palette: ['white', 'black']}

Map.addLayer(beforeNbr, nbrVis, 'Prefire NBR');


Map.addLayer(afterNbr, nbrVis, 'Postfire NBR');

// Calculate Change in NBR (dNBR)


var change = beforeNbr.subtract(afterNbr)

// Apply a threshold
var threshold = 0.3

// Display Burned Areas


var burned = change.gt(threshold)
Map.addLayer(burned, {min:0, max:1, palette: ['white', 'red']}, 'Burned', false)

Exercício

Classificando a Imagem de Alteração

Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-


Change-Detection%2F01c_Spectral_Index_Change_(exercise))
// Define the area of interest
var geometry = ee.Geometry.Polygon([[
[76.37639666685044, 11.766523239445169],
[76.37639666685044, 11.519036946599561],
[76.78426409849106, 11.519036946599561],
[76.78426409849106, 11.766523239445169]
]]);
var fireStart = ee.Date('2019-02-20');
var fireEnd = ee.Date('2019-02-25');

Map.centerObject(geometry, 10)

var s2 = ee.ImageCollection("COPERNICUS/S2")

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

// Apply filters and cloud mask


var filtered = s2
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.select('B.*')

// Create Before and After composites


var before = filtered
.filter(ee.Filter.date(
fireStart.advance(-2, 'month'), fireStart))
.median()

var after = filtered


.filter(ee.Filter.date(
fireEnd, fireEnd.advance(1, 'month')))
.median()

// Write a function to calculate Normalized Burn Ratio (NBR)


// 'NIR' (B8) and 'SWIR-2' (B12)
var addNBR = function(image) {
var nbr = image.normalizedDifference(['B8', 'B12']).rename(['nbr']);
return image.addBands(nbr)
}

var beforeNbr = addNBR(before).select('nbr');


var afterNbr = addNBR(after).select('nbr');

// Calculate Change in NBR (dNBR)


var change = beforeNbr.subtract(afterNbr)

var dnbrPalette = ['#ffffb2','#fecc5c','#fd8d3c','#f03b20','#bd0026'];


// Display the change image
Map.addLayer(change, {min:0.1, max: 0.7, palette: dnbrPalette},
'Change in NBR')

// We can also classify the change image according to


// burn severity

// United States Geological Survey (USGS) proposed


// a classification table to interpret the burn severity
// We will assign a discrete class value and visualize it
// | Severity | dNBR Range | Class |
// |--------------|--------------------|-------|
// | Unburned | < 0.1 | 0 |
// | Low Severity | >= 0.10 and <0.27 | 1 |
// | Moderate-Low | >= 0.27 and <0.44 | 2 |
// | Moderate-High| >= 0.44 and< 0.66 | 3 |
// | High | >= 0.66 | 4 |

// Classification of continuous values can be done


// using the .where() function
var severity = change
.where(change.lt(0.10), 0)
.where(change.gte(0.10).and(change.lt(0.27)), 1)
.where(change.gte(0.27).and(change.lt(0.44)), 2)
.where(change.gte(0.44).and(change.lt(0.66)), 3)
.where(change.gt(0.66), 4)

// Exercise

// The resulting image 'severity' is a discrete image with


// pixel values from 0-4 representing the severity class

// Display the image according to the following color table

// | Severity | Class | Color |


// |--------------|-------|---------|
// | Unburned | 0 | green |
// | Low Severity | 1 | yellow |
// | Moderate-Low | 2 | organge |
// | Moderate-High| 3 | red |
// | High | 4 | magenta |

02. Alteração da Distância Espectral


Quando você deseja detectar alterações em imagens multibanda, uma técnica útil é calcular a distância espectral e o ângulo espectral entre
as duas imagens. Pixels que apresentam uma grande mudança terão uma distância maior em comparação com aqueles que não mudaram.
Esta técnica é particularmente útil quando não existem índices adequados para detectar a alteração. Pode ser aplicado para detectar
mudanças após desastres naturais ou conflitos humanos.

Aqui usamos esta técnica para detectar deslizamentos de terra usando compósitos antes/depois. Você pode aprender mais sobre essa
técnica na apresentação de Detecção de Mudanças de Craig D'Souza (https://goo.gl/xotYhk) .
Detecção de mudança de distância espectral
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-
Change-Detection%2F01b_Spectral_Distance_Change_(complete))

var geometry = ee.Geometry.Polygon([[


[75.70357667713435, 12.49723970868507],
[75.70357667713435, 12.470171844429931],
[75.7528434923199, 12.470171844429931],
[75.7528434923199, 12.49723970868507]
]]);
Map.centerObject(geometry)
var s2 = ee.ImageCollection("COPERNICUS/S2")
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.select('B.*')

var dateOfIncident = ee.Date('2018-12-15')


var before = filtered
.filter(ee.Filter.date(
dateOfIncident.advance(-2, 'year'), dateOfIncident))
.filter(ee.Filter.calendarRange(12, 12, 'month'))
.median()

var after = filtered


.filter(ee.Filter.date(
dateOfIncident, dateOfIncident.advance(1, 'month')))
.median()

Map.addLayer(before, rgbVis, 'Before')


Map.addLayer(after, rgbVis, 'After')

// Use the spectralDistnace() function to get spectral distance measures

// Use the metric 'Spectral Angle Mapper (SAM)


// The result is the spectral angle in radians
var angle = after.spectralDistance(before, 'sam');
Map.addLayer(angle, {min: 0, max: 1, palette: ['white', 'purple']}, 'Spectral Angle');

// Use the metric 'Squared Euclidian Distance (SED)'


var sed = after.spectralDistance(before, 'sed');
// Take square root to get euclidian distance
var distance = sed.sqrt();

Map.addLayer(distance, {min: 0, max: 1500, palette: ['white', 'red']}, 'spectral distance');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-
Change-Detection%2F01c_Spectral_Distance_Change_(exercise))
var geometry = ee.Geometry.Polygon([[
[75.70357667713435, 12.49723970868507],
[75.70357667713435, 12.470171844429931],
[75.7528434923199, 12.470171844429931],
[75.7528434923199, 12.49723970868507]
]]);
Map.centerObject(geometry)
var s2 = ee.ImageCollection("COPERNICUS/S2")
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.select('B.*')

var dateOfIncident = ee.Date('2018-12-15')


var before = filtered
.filter(ee.Filter.date(
dateOfIncident.advance(-2, 'year'), dateOfIncident))
.filter(ee.Filter.calendarRange(12, 12, 'month'))
.median()
var after = filtered.filter(ee.Filter.date(
dateOfIncident, dateOfIncident.advance(1, 'month'))).median()

Map.addLayer(before, rgbVis, 'Before')


Map.addLayer(after, rgbVis, 'After')

var sed = after.spectralDistance(before, 'sed');


var distance = sed.sqrt();

Map.addLayer(distance, {min: 0, max: 1500, palette: ['white', 'red']}, 'spectral distance');

// Exercise
// Inspect the distance image and find a suitable threshold
// that signifies damage after the landslides
// Apply the threshold and create a new image showing landslides
// Display the results

// Hint: Use the .gt() method to apply the threshold

03. Classificação Direta de Mudança


Essa técnica de detecção de alterações também é conhecida como classificação de passagem única ou classificação direta de várias datas . Aqui
criamos uma única imagem empilhada contendo bandas de antes e depois das imagens. Treinamos um classificador com dados de
treinamento amostrados da imagem empilhada e aplicamos o classificador na imagem empilhada para encontrar todos os pixels alterados.
Todos os pixels que mudaram de solo descoberto para construído

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-


Change-Detection%2F03b_Classifying_Change_(complete))
var bangalore = ee.FeatureCollection('users/ujavalgandhi/public/bangalore_boundary');
var change = ee.FeatureCollection('users/ujavalgandhi/e2e/bangalore_change_gcps');
var nochange = ee.FeatureCollection('users/ujavalgandhi/e2e/bangalore_nochange_gcps')
var s2 = ee.ImageCollection("COPERNICUS/S2")

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-02-01'))
.filter(ee.Filter.bounds(bangalore))
.map(maskS2clouds)

var image2019 = filtered.median().clip(bangalore)


// Display the input composite.
Map.addLayer(image2019, rgbVis, '2019');

var filtered = s2
.filter(ee.Filter.date('2020-01-01', '2020-02-01'))
.filter(ee.Filter.bounds(bangalore))
.map(maskS2clouds)

var image2020 = filtered.median().clip(bangalore)

Map.addLayer(image2020, rgbVis, '2020');

var stackedImage = image2019.addBands(image2020)

// Overlay the point on the image to get training data.


var training = stackedImage.sampleRegions({
collection: change.merge(nochange),
properties: ['class'],
scale: 10
});

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'class',
inputProperties: stackedImage.bandNames()
});

// Classify the image.


var classified = stackedImage.classify(classifier);
Map.addLayer(classified, {min: 0, max: 1, palette: ['white', 'red']}, 'change');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?accept_repo=users%2Fujavalgandhi%2FEnd-to-End-
GEE&scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-Change-Detection%2F03c_Classifying_Change_(exercise))
// Add an NDBI band to improve the detection of changes.

var addNDBI = function(image) {


var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
return image.addBands(ndbi)
}

// use addNDBI() function to add the NDBI band to both 2019 and 2020 composite images
// Hint1: You can save the resulting image in the same variable to avoid changing
// a lot of code.
// var image = addNDBI(image)

04. Comparação pós-classificação


Ao lidar com imagens multiclasse, uma métrica útil para detecção de alterações é saber quantos pixels da classe X mudaram para a classe Y.
Isso pode ser feito usando o ee.Reducer.frequencyHistogram() redutor conforme mostrado abaixo.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-


Change-Detection%2F04b_Post_Classification_Comparison_(complete))
var bangalore = ee.FeatureCollection("users/ujavalgandhi/public/bangalore_boundary")
var urban = ee.FeatureCollection("users/ujavalgandhi/e2e/urban_gcps")
var bare = ee.FeatureCollection("users/ujavalgandhi/e2e/bare_gcps")
var water = ee.FeatureCollection("users/ujavalgandhi/e2e/water_gcps")
var vegetation = ee.FeatureCollection("users/ujavalgandhi/e2e/vegetation_gcps")
var s2 = ee.ImageCollection("COPERNICUS/S2_SR")

Map.centerObject(bangalore)
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// 2019 Jan
var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-02-01'))
.filter(ee.Filter.bounds(bangalore))
.select('B.*')

var before = filtered.median().clip(bangalore)


// Display the input composite.
Map.addLayer(before, rgbVis, 'before');

var training = urban.merge(bare).merge(water).merge(vegetation)

// Overlay the point on the image to get training data.


var training = before.sampleRegions({
collection: training,
properties: ['landcover'],
scale: 10
});

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'landcover',
inputProperties: before.bandNames()
});

// // Classify the image.


var beforeClassified = before.classify(classifier);
Map.addLayer(beforeClassified,
{min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, 'before_classified');

// 2020 Jan
var after = s2
.filter(ee.Filter.date('2020-01-01', '2020-02-01'))
.filter(ee.Filter.bounds(bangalore))
.select('B.*')
.median()
.clip(bangalore)

Map.addLayer(after, rgbVis, 'after');

// Classify the image.


var afterClassified= after.classify(classifier);
Map.addLayer(afterClassified,
{min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, 'after_classified');

// Reclassify from 0-3 to 1-4


var beforeClasses = beforeClassified.remap([0, 1, 2, 3], [1, 2, 3, 4])
var afterClasses = afterClassified.remap([0, 1, 2, 3], [1, 2, 3, 4])

// Show all changed areas


var changed = afterClasses.subtract(beforeClasses).neq(0)
Map.addLayer(changed, {min:0, max:1, palette: ['white', 'red']}, 'Change')
// We multiply the before image with 100 and add the after image
// The resulting pixel values will be unique and will represent each unique transition
// i.e. 102 is urban to bare, 103 urban to water etc.
var merged = beforeClasses.multiply(100).add(afterClasses).rename('transitions')

// Use a frequencyHistogram to get a pixel count per class


var transitionMatrix = merged.reduceRegion({
reducer: ee.Reducer.frequencyHistogram(),
geometry: bangalore,
maxPixels: 1e10,
scale:10,
tileScale: 16
})
// This prints number of pixels for each class transition
print(transitionMatrix.get('transitions'))

// If we want to calculate the area of each class transition


// we can use a grouped reducer

// Divide by 1e6 to get the area in sq.km.


var areaImage = ee.Image.pixelArea().divide(1e6).addBands(merged);
// Calculate Area by each Transition Class
// using a Grouped Reducer
var areas = areaImage.reduceRegion({
reducer: ee.Reducer.sum().group({
groupField: 1,
groupName: 'transitions',
}),
geometry: bangalore,
scale: 100,
tileScale: 4,
maxPixels: 1e10
});

// Post-process the result to generate a clean output


var classAreas = ee.List(areas.get('groups'))
var classAreaLists = classAreas.map(function(item) {
var areaDict = ee.Dictionary(item)
var classNumber = ee.Number(areaDict.get('transitions')).format()
var area = ee.Number(areaDict.get('sum')).round()
return ee.List([classNumber, area])
})
var classTransitionsAreaDict = ee.Dictionary(classAreaLists.flatten())
print(classTransitionsAreaDict)

Exercício

Pixels de água perdidos entre 2019 e 2020

Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-


Change-Detection%2F04c_Post_Classification_Comparison_(exercise))
// Exercise
// Show all areas where water became other classes and display the result
// Hint1: Select class 3 pixels from before image and NOT class 3 pixels from after image
// Hint2: use the .and() operation to select pixels matching both conditions

Módulo 5: Aplicativos do Earth Engine


Este módulo é focado nos conceitos relacionados a cliente vs. servidor que irão ajudá-lo na criação de aplicações web. Vamos criar um
aplicativo usando a API de interface do usuário do Earth Engine e publicá-lo no Google Cloud.

01. Cliente x Servidor


Os elementos da interface do usuário em seu editor de código - exibição de mapa, ferramentas de desenho etc. são elementos do 'lado do
cliente'. Eles são executados no SEU navegador. Coleções de imagens, coleções de recursos, cálculos em objetos do Earth Engine etc. são
elementos do 'lado do servidor'. Eles são executados no data center do Google. Você não pode misturar esses dois objetos. Para saber mais,
visite a seção Cliente vs. Servidor (https://developers.google.com/earth-engine/guides/client_server) do Guia do usuário do Earth Engine.

Para converter objetos do lado do cliente em objetos do lado do servidor, você pode usar a função de API apropriada. As funções do
lado do servidor começam com ee. , como ee.Date() , ee.Image() etc.
Para converter objetos do lado do servidor em objetos do lado do cliente, você pode chamar .getInfo() um objeto do Earth Engine.
Para a API Python, esta é a única maneira de extrair informações de um objeto do lado do servidor, mas a API Javascript fornece um
método melhor (e preferido) para trazer objetos do lado do servidor para o lado do cliente usando o evaluate() método . Este
método recupera de forma assíncrona o valor do objeto, sem bloquear a interface do usuário - o que significa que permitirá que seu
código continue a ser executado enquanto busca o valor.

Dica: você pode usar ee.Algorithms.ObjectType() para obter o tipo de um objeto do lado do servidor

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-


Engine-Apps%2F01b_Client_vs_Server_(complete))

var date = '2020-01-01' // This is client-side


print(typeof(date))

var eedate = ee.Date('2020-01-01').format() // This is server-side


print(typeof(eedate))

// To bring server-side objects to client-side, you can call .getInfo()

// var clientdate = eedate.getInfo()


// print(clientdate)
// print(typeof(clientdate))

// getInfo() blocks the execution of your code till the value is fetched
// If the value takes time to compute, your code editor will freeze
// This is not a good user experience
var s2 = ee.ImageCollection("COPERNICUS/S2_SR")
var filtered = s2.filter(ee.Filter.date('2020-01-01', '2021-01-01'))

//var numImages = filtered.size().getInfo()


//print(numImages)

// A better approach is to use evaluate() function

// You need to define a 'callback' function which will be called once the
// value has been computed and ready to be used.

var myCallback = function(object) {


print(object)
}
print('Computing the size of the collection')
var numImages = filtered.size().evaluate(myCallback)

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F01c_Client_vs_Server_(exercise))
var date = ee.Date.fromYMD(2019, 1, 1)
print(date)

// We can use the format() function to create


// a string from a date object
var dateString = date.format('dd MMM, YYYY')
print(dateString)

// Exercise
// The print statement below combines a client-side string
// with a server-side string - resulting in an error.

// Fix the code so that the following message is printed


// 'The date is 01 Jan, 2019'
var message = 'The date is ' + dateString
print(message)

// Hint:
// Convert the client-side string to a server-side string
// Use ee.String() to create a server-side string
// Use the .cat() function instead of + to combine 2 strings

02. Usando elementos da interface do usuário


O Earth Engine vem com uma API de interface do usuário que permite criar um aplicativo da web interativo desenvolvido pelo Earth Engine.

A API do Earth Engine fornece uma biblioteca de widgets de interface do usuário (UI) - como botões, menus suspensos, controles deslizantes
etc. - que podem ser usados ​para criar aplicativos interativos. Todas as funções da interface do usuário estão contidas no ui. pacote - como
ui.Select() , ui.Button() . Você pode criar esses elementos chamando essas funções com os parâmetros apropriados. Saiba mais na
seção API da interface do usuário (https://developers.google.com/earth-engine/guides/ui) do Earth Engine do Guia do usuário do Earth
Engine.

Esta seção mostra como criar um seletor suspenso usando o ui.Select() widget.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-


Engine-Apps%2F02b_Using_UI_Elements_(complete))

// You can add any widgets from the ui.* module to the map
var years = ['2014', '2015', '2016', '2017'];

// Let's create a ui.Select() dropdown with the above values


var yearSelector = ui.Select({
items: years,
value: '2014',
placeholder: 'Select a year',
})
Map.add(yearSelector);

var loadImage = function() {


var year = yearSelector.getValue();
var col = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG");
var startDate = ee.Date.fromYMD(
ee.Number.parse(year), 1, 1);
var endDate = startDate.advance(1, 'year');
var filtered = col.filter(ee.Filter.date(startDate, endDate));
var composite = filtered.mean().select('avg_rad');
var layerName = 'Night Lights ' + year;
var nighttimeVis = {min: 0.0, max: 60.0};
Map.addLayer(composite, nighttimeVis, layerName);
};

var button = ui.Button({


label: 'Click to Load Image',
onClick: loadImage,
});
Map.add(button);
Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F02c_Using_UI_Elements_(exercise))

// Instead of manually creating a list of years like before


// we can create a list of years using ee.List.sequence()
var years = ee.List.sequence(2014, 2020)

// Convert them to strings using format() function


var yearStrings = years.map(function(year){
return ee.Number(year).format('%04d')
})
print(yearStrings);

// Convert the server-side object to client-side using


// evaluate() and use it with ui.Select()
yearStrings.evaluate(function(yearList) {
var yearSelector = ui.Select({
items: yearList,
value: '2014',
placeholder: 'Select a year',
})
Map.add(yearSelector)
});

// Exercise

// Create another dropdown with months from 1 to 12


// and add it to the map.

03. Construindo e Publicando um App


Construir um aplicativo de mapeamento da web geralmente requer as habilidades de um desenvolvedor full stack e está fora do alcance da
maioria dos analistas e cientistas. Mas a API de interface do usuário do Earth Engine torna esse processo muito mais acessível, fornecendo
widgets prontos para uso e hospedagem gratuita na nuvem para permitir que qualquer pessoa publique um aplicativo com apenas algumas
linhas de código. O objeto contêiner principal é o ui.Panel() que pode conter diferentes tipos de widgets.

O código abaixo mostra como construir um aplicativo chamado Night Lights Explorer (https://santhosh-m.users.earthengine.app/view/night-
lights-explorer) que permite a qualquer um escolher um ano/mês e carregar o VIIRS Nighttime Day/Night Band Composite para o mês
selecionado. Copie/cole o código abaixo no seu Editor de código e clique em Executar .
Você verá um painel no lado direito com 2 caixas suspensas e um botão. Esses são widgets da interface do usuário (IU) fornecidos pela API do
Earth Engine que permite ao usuário selecionar os valores interativamente. Você pode selecionar os valores para ano e mês e clicar no botão
Carregar para ver a imagem do mês selecionado.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-


Engine-Apps%2F03b_Building_an_App_with_UI_Widgets_(complete))
// Panels are the main container widgets
var mainPanel = ui.Panel({
style: {width: '300px'}
});

var title = ui.Label({


value: 'Night Lights Explorer',
style: {'fontSize': '24px'}
});
// You can add widgets to the panel
mainPanel.add(title)

// You can even add panels to other panels


var dropdownPanel = ui.Panel({
layout: ui.Panel.Layout.flow('horizontal'),
});
mainPanel.add(dropdownPanel);

var yearSelector = ui.Select({


placeholder: 'please wait..',
})

var monthSelector = ui.Select({


placeholder: 'please wait..',
})

var button = ui.Button('Load')


dropdownPanel.add(yearSelector)
dropdownPanel.add(monthSelector)
dropdownPanel.add(button)

// Let's add a dropdown with the years


var years = ee.List.sequence(2014, 2020)
var months = ee.List.sequence(1, 12)

// Dropdown items need to be strings


var yearStrings = years.map(function(year){
return ee.Number(year).format('%04d')
})
var monthStrings = months.map(function(month){
return ee.Number(month).format('%02d')
})

// Evaluate the results and populate the dropdown


yearStrings.evaluate(function(yearList) {
yearSelector.items().reset(yearList)
yearSelector.setPlaceholder('select a year')
})

monthStrings.evaluate(function(monthList) {
monthSelector.items().reset(monthList)
monthSelector.setPlaceholder('select a month')

})

// Define a function that triggers when any value is changed


var loadComposite = function() {
var col = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG");
var year = yearSelector.getValue()
var month = monthSelector.getValue()
var startDate = ee.Date.fromYMD(
ee.Number.parse(year), ee.Number.parse(month), 1)
var endDate = startDate.advance(1, 'month')
var filtered = col.filter(ee.Filter.date(startDate, endDate))

var image = ee.Image(filtered.first()).select('avg_rad')


var nighttimeVis = {min: 0.0, max: 60.0}
var layerName = 'Night Lights ' + year + '-' + month
Map.addLayer(image, nighttimeVis, layerName)
}
button.onClick(loadComposite)

Map.setCenter(76.43, 12.41, 8)
ui.root.add(mainPanel);

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F03c_Building_an_App_with_UI_Widgets_(exercise))

// Exercise
// Add a button called 'Reset'
// Clicking the button should remove all loaded layers

// Hint: Use Map.clear() for removing the layers

04. Publicação do aplicativo


Agora vamos publicar este aplicativo. Clique no botão Aplicativos .

Aplicativo com elementos de interface do usuário

Na janela Gerenciar aplicativos , clique em Novo aplicativo .


Digite o nome do seu aplicativo. O aplicativo será hospedado no Google Cloud, portanto, você precisará criar e vincular um projeto do
Google Cloud ao aplicativo. Se você não tiver uma conta do Google Cloud, poderá clicar no botão Ainda não para criar um novo projeto.

Quando solicitado a Escolher um projeto de nuvem para seus aplicativos , você pode selecionar Criar um novo projeto de nuvem, inserir um ID
exclusivo e clicar em Selecionar .

Você pode receber um erro solicitando que aceite os termos de serviço. Clique no link Cloud Terms of Service e siga as instruções. Feito isso,
clique em OK .

De volta à caixa de diálogo Publicar novo aplicativo , deixe todas as outras configurações como padrão e clique em Publicar .
O aplicativo será hospedado no Google Cloud e você poderá acessá-lo clicando no Nome do aplicativo do seu aplicativo na caixa de diálogo
Gerenciar aplicativos .

Você verá seu aplicativo baseado no Earth Engine em execução no navegador. Qualquer pessoa pode acessar e interagir com o aplicativo
apenas visitando o URL do aplicativo.

O processo de publicação do aplicativo leva alguns minutos. Portanto, se você receber um erro
informando que seu aplicativo ainda não está pronto, verifique novamente em alguns minutos.

Explore o aplicativo ↗ (https://ujavalgandhi.users.earthengine.app/view/night-lights-explorer)


Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F04c_Publishing_the_App_(exercise))
// Panels are the main container widgets
var mainPanel = ui.Panel({
style: {width: '300px'}
});

var title = ui.Label({


value: 'Night Lights Explorer',
style: {'fontSize': '24px'}
});
// You can add widgets to the panel
mainPanel.add(title)

// You can even add panels to other panels


var dropdownPanel = ui.Panel({
layout: ui.Panel.Layout.flow('horizontal'),
});
mainPanel.add(dropdownPanel);

var yearSelector = ui.Select({


placeholder: 'please wait..',
})

var monthSelector = ui.Select({


placeholder: 'please wait..',
})

var button = ui.Button('Load')


dropdownPanel.add(yearSelector)
dropdownPanel.add(monthSelector)
dropdownPanel.add(button)

// Let's add a dropdown with the years


var years = ee.List.sequence(2014, 2020)
var months = ee.List.sequence(1, 12)

// Dropdown items need to be strings


var yearStrings = years.map(function(year){
return ee.Number(year).format('%04d')
})
var monthStrings = months.map(function(month){
return ee.Number(month).format('%02d')
})

// Evaluate the results and populate the dropdown


yearStrings.evaluate(function(yearList) {
yearSelector.items().reset(yearList)
yearSelector.setPlaceholder('select a year')
})

monthStrings.evaluate(function(monthList) {
monthSelector.items().reset(monthList)
monthSelector.setPlaceholder('select a month')

})

// Define a function that triggers when any value is changed


var loadComposite = function() {
var col = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG");
var year = yearSelector.getValue()
var month = monthSelector.getValue()
var startDate = ee.Date.fromYMD(
ee.Number.parse(year), ee.Number.parse(month), 1)
var endDate = startDate.advance(1, 'month')
var filtered = col.filter(ee.Filter.date(startDate, endDate))

var image = ee.Image(filtered.first()).select('avg_rad')


var nighttimeVis = {min: 0.0, max: 60.0}
var layerName = 'Night Lights ' + year + '-' + month
Map.addLayer(image, nighttimeVis, layerName)
}
button.onClick(loadComposite)

// Exercise
// Set the map center to your area of interst
// Replace the author label with your name
// Publish the app.
Map.setCenter(76.43, 12.41, 8)
var authorLabel = ui.Label('App by: Ujaval Gandhi');
mainPanel.add(authorLabel);

ui.root.add(mainPanel);

05. Crie um aplicativo de painel dividido


Outro widget útil que pode ser usado em aplicativos é o ui.SplitPanel() . Isso permite que você crie um aplicativo que pode exibir 2
imagens diferentes da mesma região que podem ser exploradas interativamente passando o dedo. Aqui criamos um aplicativo para explorar
o conjunto de dados de classificação global ESA WorldCover 10m (https://developers.google.com/earth-
engine/datasets/catalog/ESA_WorldCover_v100) .

No painel esquerdo, carregaremos um composto Sentinel-2 para o ano de 2020. No painel direito, carregaremos a classificação de 11 classes
de cobertura do solo da mesma região.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-


Engine-Apps%2F05b_Split_Panel_App_(complete))
var admin2 = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level2");
var selected = admin2
.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'))
.filter(ee.Filter.eq('ADM2_NAME', 'Bangalore Urban'))
var geometry = selected.geometry();
Map.centerObject(geometry)

var s2 = ee.ImageCollection("COPERNICUS/S2");

// Write a function for Cloud masking


var maskS2clouds = function(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

// Write a function to scale the bands


var scaleImage = function(image) {
return image
.multiply(0.0001)
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.map(scaleImage);

// Create a median composite for 2020


var composite = filtered.median();

// Load ESA WorldCover 2020 Classification


var worldcover = ee.ImageCollection("ESA/WorldCover/v100")
var filtered = worldcover
.filter(ee.Filter.date('2020-01-01', '2021-01-01'));
var classification = ee.Image(filtered.first());

// Create a Split Panel App

// Set a center and zoom level.


var center = {lon: 77.58, lat: 12.97, zoom: 12};

// Create two maps.


var leftMap = ui.Map(center);
var rightMap = ui.Map(center);

// Link them together.


var linker = new ui.Map.Linker([leftMap, rightMap]);

// Create a split panel with the two maps.


var splitPanel = ui.SplitPanel({
firstPanel: leftMap,
secondPanel: rightMap,
orientation: 'horizontal',
wipe: true
});

// Remove the default map from the root panel.


ui.root.clear();

// Add our split panel to the root panel.


ui.root.add(splitPanel);
// Add the layers to the maps
// Composite goes to the leftMap
var rgbVis = {min: 0.0, max: 0.3, bands: ['B4', 'B3', 'B2']};
leftMap.addLayer(composite.clip(geometry), rgbVis, '2020 Composite');

// Classification foes to the rightMap


rightMap.addLayer(classification.clip(geometry), {}, 'WorldCover Classification');

Exercício
Tente no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F05c_Split_Panel_App_(exercise))
var admin2 = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level2");
var selected = admin2
.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'))
.filter(ee.Filter.eq('ADM2_NAME', 'Bangalore Urban'))
var geometry = selected.geometry();
Map.centerObject(geometry)

var s2 = ee.ImageCollection("COPERNICUS/S2");

// Write a function for Cloud masking


var maskS2clouds = function(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

// Write a function to scale the bands


var scaleImage = function(image) {
return image
.multiply(0.0001)
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.map(scaleImage);

// Create a median composite for 2020


var composite = filtered.median();

// Load ESA WorldCover 2020 Classification


var worldcover = ee.ImageCollection("ESA/WorldCover/v100")
var filtered = worldcover
.filter(ee.Filter.date('2020-01-01', '2021-01-01'));
var classification = ee.Image(filtered.first());

// Create a Split Panel App

// Set a center and zoom level.


var center = {lon: 77.58, lat: 12.97, zoom: 12};

// Create two maps.


var leftMap = ui.Map(center);
var rightMap = ui.Map(center);

// Link them together.


var linker = new ui.Map.Linker([leftMap, rightMap]);

// Create a split panel with the two maps.


var splitPanel = ui.SplitPanel({
firstPanel: leftMap,
secondPanel: rightMap,
orientation: 'horizontal',
wipe: true
});

// Remove the default map from the root panel.


ui.root.clear();

// Add our split panel to the root panel.


ui.root.add(splitPanel);
// Add the layers to the maps
// Composite goes to the leftMap
var rgbVis = {min: 0.0, max: 0.3, bands: ['B4', 'B3', 'B2']};
leftMap.addLayer(composite.clip(geometry), rgbVis, '2020 Composite');

// Classification foes to the rightMap


rightMap.addLayer(classification.clip(geometry), {}, 'WorldCover Classification');

// Adding a Legend
// The following code creates a legend with class names and colors

// Create the panel for the legend items.


var legend = ui.Panel({
style: {
position: 'middle-right',
padding: '8px 15px'
}
});

// Create and add the legend title.


var legendTitle = ui.Label({
value: 'ESA WorldCover Classes',
style: {
fontWeight: 'bold',
fontSize: '18px',
margin: '0 0 4px 0',
padding: '0'
}
});
legend.add(legendTitle);

var loading = ui.Label('Loading legend...', {margin: '2px 0 4px 0'});


legend.add(loading);

// Creates and styles 1 row of the legend.


var makeRow = function(color, name) {
// Create the label that is actually the colored box.
var colorBox = ui.Label({
style: {
backgroundColor: '#' + color,
// Use padding to give the box height and width.
padding: '8px',
margin: '0 0 4px 0'
}
});

// Create the label filled with the description text.


var description = ui.Label({
value: name,
style: {margin: '0 0 4px 6px'}
});

return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')
});
};

var BAND_NAME = 'Map';


// Get the list of palette colors and class names from the image.
classification.toDictionary().select([BAND_NAME + ".*"]).evaluate(function(result) {
var palette = result[BAND_NAME + "_class_palette"];
var names = result[BAND_NAME + "_class_names"];
loading.style().set('shown', false);

for (var i = 0; i < names.length; i++) {


legend.add(makeRow(palette[i], names[i]));
}
});
// Print the panel containing the legend
print(legend);

// Exercise

// The 'legend' panel contains the legend for the classification


// Add the legend to the map below

// Hint: UI Widgets can only be shown once in the app. Remove the
// print statement before adding the legend to the map.
// Hint: Load the legend in the right-hand side map.

Módulo 6: API Python do Google Earth Engine


Introdução à API Python
Até este ponto do curso, usamos a API Javascript do Earth Engine para todas as nossas análises. O Earth Engine também fornece uma API
Python. Se você for um programador Python, talvez prefira usar a API Python para integrar o Earth Engine em seu fluxo de trabalho de
análise espacial. Há muitas opções para executar o código Python que usa a API do Google Earth Engine. Usaremos os dois métodos a seguir
neste curso.

(https://docs.google.com/presentation/d/1hPVRnxp2Vp1VHXBtu36SH_UtEOjPz70KcDV-zGIin3U/edit?usp=sharing)

Veja a Apresentação ↗ (https://docs.google.com/presentation/d/1hPVRnxp2Vp1VHXBtu36SH_UtEOjPz70KcDV-zGIin3U/edit?


usp=sharing)

Google Colab
Uma maneira fácil de começar a usar a API Python do Google Earth Engine é por meio do Google Colab (https://colab.research.google.com/) .
O Google Colaboratory fornece um ambiente hospedado para executar notebooks Python sem a necessidade de instalar o Python
localmente. Ele também vem pré-instalado com muitos pacotes úteis - incluindo a API Python do Google Earth Engine. Você pode
simplesmente visitar https://colab.research.google.com/ (https://colab.research.google.com/) e iniciar um novo notebook.

Ambiente de Desenvolvimento Local


Uma vantagem da API do Python é que você pode usá-la em seu próprio ambiente de desenvolvimento - assim, você obtém muito mais
flexibilidade para automatizar, bem como combinar outras bibliotecas de análise e visualização com o Earth Engine. Isso requer a instalação
do Python e da API Python do Earth Engine em sua máquina ou servidor. Você também precisa fazer uma autenticação única e salvar o token
na máquina. O método preferido para instalar a API Python do Earth Engine é por meio do Anaconda. Siga nosso Guia de instalação da API
Python do Google Earth Engine (install-gee-python-api.html) para obter instruções passo a passo.

01. Sintaxe da API do Python


Abrir no Google Colab ↗
(https://colab.research.google.com/github/spatialthoughts/courses/blob/master/code/end_to_end_gee/01_python_api_syntax.ipynb)

Vindo da programação no Earth Engine através do Code Editor, você precisará adaptar um pouco seus scripts para poder rodar em Python.
Para a maior parte do seu código, você usará os objetos e funções do lado do servidor da Earth Engine API - que serão exatamente os
mesmos em Python. Você só precisa fazer algumas alterações sintáticas.

Aqui está a lista completa (https://developers.google.com/earth-engine/python_install#syntax) de diferenças.

Inicialização
Em primeiro lugar, você precisa executar as seguintes células para inicializar a API e autorizar sua conta. Você será solicitado a fazer login na
conta e permitir o acesso para visualizar e gerenciar seus dados do Earth Engine . Depois de aprovar, você receberá um código de verificação
que precisa ser inserido no prompt. Esta etapa precisa ser feita apenas uma vez por sessão.

import ee
ee.Authenticate()

ee.Initialize()

Variáveis
O código Python não usa a palavra-chave 'var'

código javascript:

var city = 'San Fransico'


var state = 'California'
print(city, state)

var population = 881549


print(population)

city = 'San Fransico'


state = 'California'
print(city, state)

population = 881549
print(population)

Objetos do Motor de Terra


Você pode criar objetos Earth Engine usando as ee funções da mesma maneira.

s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
geometry = ee.Geometry.Polygon([[
[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]])

Continuação de linha
Python não usa ponto-e-vírgula para finalização de linha. Para indicar a continuação da linha, você precisa usar o caractere \

código javascript:

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-02-01', '2019-03-01'))
.filter(ee.Filter.bounds(geometry));

filtered = s2 \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.filter(ee.Filter.date('2019-02-01', '2019-03-01')) \
.filter(ee.Filter.bounds(geometry))

Funções
Em vez da palavra- function chave, o Python usa a palavra- def chave. Além disso, as funções in-line são definidas usando
lambda funções anônimas.

No exemplo abaixo, agora também o and operador - que é capitalizado como And na versão Python para evitar conflito com o
and operador integrado. O mesmo se aplica aos operadores Or e . , , em Python também são escritos como , e
. Not true false null True False None

código javascript:
function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

def maskS2clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask) \
.select("B.*") \
.copyProperties(image, ["system:time_start"])

def addNDVI(image):
ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi')
return image.addBands(ndvi)

withNdvi = filtered \
.map(maskS2clouds) \
.map(addNDVI)

Argumentos da Função
Argumentos nomeados para funções do Earth Engine precisam estar entre aspas. Além disso, ao passar os argumentos nomeados como um
dicionário, ele precisa ser passado usando a palavra- ** chave.

código javascript:

var composite = withNdvi.median();


var ndvi = composite.select('ndvi');

var stats = ndvi.reduceRegion({


reducer: ee.Reducer.mean(),
geometry: geometry,
scale: 10,
maxPixels: 1e10
})

composite = withNdvi.median()
ndvi = composite.select('ndvi')

stats = ndvi.reduceRegion(**{
'reducer': ee.Reducer.mean(),
'geometry': geometry,
'scale': 10,
'maxPixels': 1e10
})

Imprimindo Valores
A print() sintaxe da função é a mesma. Mas você deve se lembrar que no Code Editor quando você cann print , o valor do objeto do
servidor é buscado e depois impresso. Você deve fazer isso explicitamente chamando getInfo() qualquer objeto do lado do servidor.

código javascript:

print(stats.get('ndvi')
print(stats.get('ndvi').getInfo())

Funções em linha
A sintaxe para definir funções in-line também é um pouco diferente. Você precisa usar a palavra- lambda chave.

código javascript:

var myList = ee.List.sequence(1, 10);


var newList = myList.map(function(number) {
return ee.Number(number).pow(2);
print(newList);

myList = ee.List.sequence(1, 10)


newList = myList.map(lambda number: ee.Number(number).pow(2))
print(newList.getInfo())

Exercício
Pegue o trecho de código Javascript abaixo e escreva o código Python equivalente na célula abaixo.

Dica1 : O encadeamento de filtros requer o uso do caractere de continuação de linha \


Dica 2 : A impressão de objetos do lado do servidor requer a chamada .getInfo() do objeto

O código correto deve imprimir o valor 30 .

var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));

print(filtered.size());

import ee
ee.Authenticate()
ee.Initialize()

02. Conversão Automática de Código Javascript para Python


Abrir no Google Colab ↗
(https://colab.research.google.com/github/spatialthoughts/courses/blob/master/code/end_to_end_gee/02_automatic_conversion_of_scripts.ipyn
Mapa de folheto interativo criado por geemap

O geemap (https://github.com/giswqs/geemap) é um pacote Python de código aberto que vem com muitos recursos úteis que ajudam você a
usar o Earth Engine com eficiência em Python.

Ele vem com uma função que pode ajudá-lo a traduzir automaticamente o código javascript do mecanismo de terra para Python.

O Google Colab não vem pré-instalado com o pacote, então nós o instalamos via pip.

try:
import geemap
except ModuleNotFoundError:
if 'google.colab' in str(get_ipython()):
print('geemap not found, installing via pip in Google Colab...')
!pip install geemap --quiet
import geemap
else:
print('geemap not found, please install via conda in your environment')

A conversão automática do código é feita chamando a geemap.js_snippet_to_py() função. Primeiro criamos uma string com o código
javascript.
javascript_code = """
var geometry = ee.Geometry.Point([107.61303468448624, 12.130969369851766]);
Map.centerObject(geometry, 12)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-12-31'))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)

// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

var withNdvi = filtered.map(addNDVI);

var composite = withNdvi.median()


palette = [
'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301'];

ndviVis = {min:0, max:0.5, palette: palette }


Map.addLayer(withNdvi.select('ndvi'), ndviVis, 'NDVI Composite')

"""

lines = geemap.js_snippet_to_py(
javascript_code, add_new_cell=False,
import_ee=True, import_geemap=True, show_map=True)
for line in lines:
print(line.rstrip())

A conversão automática funciona muito bem. Revise-o e cole-o na célula abaixo.


import ee
import geemap
Map = geemap.Map()

geometry = ee.Geometry.Point([107.61303468448624, 12.130969369851766])


Map.centerObject(geometry, 12)
s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
rgbVis = {
'min': 0.0,
'max': 3000,
'bands': ['B4', 'B3', 'B2'],
}

# Write a function for Cloud masking


def maskS2clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask) \
.select("B.*") \
.copyProperties(image, ["system:time_start"])

filtered = s2 \
.filter(ee.Filter.date('2019-01-01', '2019-12-31')) \
.filter(ee.Filter.bounds(geometry)) \
.map(maskS2clouds)

# Write a function that computes NDVI for an image and adds it as a band
def addNDVI(image):
ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi')
return image.addBands(ndvi)

withNdvi = filtered.map(addNDVI)

composite = withNdvi.median()
palette = [
'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301']

ndviVis = {'min':0, 'max':0.5, 'palette': palette }


Map.addLayer(withNdvi.select('ndvi'), ndviVis, 'NDVI Composite')
Map

Exercício
Pegue o trecho de código Javascript abaixo e use geemap -o para convertê-lo automaticamente em Python.

var admin2 = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level2");

var karnataka = admin2.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'))

var visParams = {color: 'red'}


Map.centerObject(karnataka)
Map.addLayer(karnataka, visParams, 'Karnataka Districts')

03. Exportações em Lote


Abrir no Google Colab ↗
(https://colab.research.google.com/github/spatialthoughts/courses/blob/master/code/end_to_end_gee/03_export_a_collection.ipynb)

Uma das perguntas mais frequentes dos usuários do Earth Engine é: Como faço o download de todas as imagens de uma coleção ? A API Python
do Earth Engine vem com um ee.batch módulo que permite iniciar exportações em lote e gerenciar tarefas. A maneira recomendada de
fazer exportações em lote como essa é usar as ee.batch.Export funções da API do Python e usar um loop for do Python para iterar e
exportar cada imagem. O ee.batch módulo também oferece a capacidade de controlar Tarefas - permitindo que você automatize as
exportações.

import ee

ee.Authenticate()

ee.Initialize()

Criar uma coleção

geometry = ee.Geometry.Point([107.61303468448624, 12.130969369851766])


s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
rgbVis = {
'min': 0.0,
'max': 3000,
'bands': ['B4', 'B3', 'B2'],
}

# Write a function for Cloud masking


def maskS2clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask) \
.select("B.*") \
.copyProperties(image, ["system:time_start"])

filtered = s2 \
.filter(ee.Filter.date('2019-01-01', '2020-01-01')) \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.filter(ee.Filter.bounds(geometry)) \
.map(maskS2clouds)

# Write a function that computes NDVI for an image and adds it as a band
def addNDVI(image):
ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi')
return image.addBands(ndvi)

withNdvi = filtered.map(addNDVI)

Exportar todas as imagens


As exportações são feitas através do ee.batch módulo. Este módulo permite que você inicie automaticamente uma exportação - tornando-
a adequada para exportações em lote.

image_ids = withNdvi.aggregate_array('system:index').getInfo()
print('Total images: ', len(image_ids))

# Export with 100m resolution for this demo


for i, image_id in enumerate(image_ids):
image = ee.Image(withNdvi.filter(ee.Filter.eq('system:index', image_id)).first())
task = ee.batch.Export.image.toDrive(**{
'image': image.select('ndvi'),
'description': 'Image Export {}'.format(i+1),
'fileNamePrefix': image_id,
'folder':'earthengine',
'scale': 100,
'region': image.geometry(),
'maxPixels': 1e10
})
task.start()
print('Started Task: ', i+1)
Gerenciar tarefas em execução/em espera
Você também pode gerenciar tarefas. Obtenha uma lista de tarefas e obtenha informações de estado sobre elas

tasks = ee.batch.Task.list()
for task in tasks:
task_id = task.status()['id']
task_state = task.status()['state']
print(task_id, task_state)

Você também pode cancelar tarefas

tasks = ee.batch.Task.list()
for task in tasks:
task_id = task.status()['id']
task_state = task.status()['state']
if task_state == 'RUNNING' or task_state == 'READY':
task.cancel()
print('Task {} canceled'.format(task_id))
else:
print('Task {} state is {}'.format(task_id, task_state))

Exercício
O código abaixo usa os dados do TerraClimate e cria um ImageCollection com 12 imagens mensais de temperatura máxima. Ele também
extrai a geometria para a Austrália da coleção LSIB. Adicione o código para iniciar uma tarefa de exportação para cada imagem na coleção
para austrália.

Dica1 : As imagens do TerraClimate têm uma escala de 4638,3m


Dica2 : Você precisa exportar a imagem contida na variável clippedImage

import ee

lsib = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
australia = lsib.filter(ee.Filter.eq('country_na', 'Australia'))
geometry = australia.geometry()

terraclimate = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')
tmax = terraclimate.select('tmmx')

def scale(image):
return image.multiply(0.1) \
.copyProperties(image,['system:time_start'])

tmaxScaled = tmax.map(scale)

filtered = tmaxScaled \
.filter(ee.Filter.date('2020-01-01', '2021-01-01')) \
.filter(ee.Filter.bounds(geometry))

image_ids = filtered.aggregate_array('system:index').getInfo()
print('Total images: ', len(image_ids))

Substitua os comentários pelo seu código.

for i, image_id in enumerate(image_ids):


exportImage = ee.Image(filtered.filter(ee.Filter.eq('system:index', image_id)).first())
# Clip the image to the region geometry
clippedImage = exportImage.clip(geometry)

## Create the export task using ee.batch.Export.image.toDrive()

## Start the task


Iniciando várias tarefas usando a API Python

04. Criando Gráficos em Python


Abrir no Google Colab ↗
(https://colab.research.google.com/github/spatialthoughts/courses/blob/master/code/end_to_end_gee/04_creating_charts.ipynb)

A API Python do Google Earth Engine não vem com um módulo de gráficos. Mas você pode usar módulos de terceiros para criar gráficos
interativos. Você também pode converter os objetos Earth Engine em um dataframe Pandas e plotá-lo usando bibliotecas Python como
Matplotlib

Este notebook mostra como usar o geemap pacote para criar um gráfico de série temporal a partir de uma ImageCollection.

Referências:

módulo Geemap Chart (https://geemap.org/chart/)


exemplo de bloco de notas (https://geemap.org/notebooks/63_charts/) geemap (https://geemap.org/notebooks/63_charts/)

import ee

try:
import geemap
except ModuleNotFoundError:
if 'google.colab' in str(get_ipython()):
print('geemap not found, installing via pip in Google Colab...')
!pip install geemap --quiet
import geemap
else:
print('geemap not found, please install via conda in your environment')

ee.Authenticate()

ee.Initialize()

Carregue a coleção TerraClimate e selecione a banda 'tmmx'.

terraclimate = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')
tmax = terraclimate.select('tmmx')

Defina um local de ponto para o gráfico.

geometry = ee.Geometry.Point([77.57738128916243, 12.964758918835752])


Escale os valores da banda para que estejam em graus Celsius.

def scale_image(image):
return ee.Image(image).multiply(0.1)\
.copyProperties(image, ['system:time_start'])

tmaxScaled = tmax.map(scale_image)

Filtre a coleção.

filtered = tmaxScaled.filter(ee.Filter.date('2019-01-01', '2020-01-01')) \


.filter(ee.Filter.bounds(geometry))

Para traçar uma série de imagens em Python, devemos primeiro extrair os valores de cada imagem e criar uma FeatureCollection.

def extract_data(image):
stats = image.reduceRegion(**{
'reducer':ee.Reducer.mean(),
'geometry':geometry,
'scale':5000
})
properties = {
'month': image.get('system:index'),
'tmmx': stats.get('tmmx')
}
return ee.Feature(None, properties)

data = ee.FeatureCollection(filtered.map(extract_data))

print(data.first().getInfo())

Crie um gráfico interativo usando geemap


from geemap import chart

options = {
'title': 'Max Monthly Temperature at Bangalore',
'legend_location': 'top-right',
'height': '500px',
'ylabel': 'Temperature (C)',
'xlabel': 'Date',
'colors': ['blue']
}

chart.feature_byFeature(data, 'month', ['tmmx'], **options)

Crie um gráfico usando o Matplotlib


Podemos converter um FeatureCollection em um DataFrame usando a geemap função auxiliar ee_to_pandas .

import geemap
df = geemap.ee_to_pandas(data)

df

Agora temos um dataframe regular do Pandas que pode ser plotado com matplotlib .

%matplotlib inline
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.set_size_inches(20,10)

df.plot(ax=ax,
title='Max Monthly Temperature at Bangalore',
x='month',
ylabel='Temperature (C)',
kind='line')
plt.tight_layout()

Exercício
Personalize o gráfico acima plotando-o como um gráfico de linhas na cor vermelha.

Dica1 : Use kind='line' junto com uma color opção.

05. Automatizando downloads


Outro uso comum da API GEE Python é automatizar o processamento e a exportação de dados. Você pode criar um script Python que pode
ser chamado de um servidor ou iniciado em um agendamento usando ferramentas como o Windows Scheduler
(https://medium.com/@roddyjaques/how-to-run-anaconda-programs-with-a-bat-file-5f6dd7675508) ou crontab (https://donny-
son.github.io/posts/cronjob-with-conda/) .

Este script abaixo fornece um exemplo completo de automatização de um download usando a API do Google Earth Engine. Ele usa a API do
Google Earth Engine para calcular a umidade média do solo para um determinado período de tempo em todos os distritos de um estado. O
resultado é baixado como um arquivo JSON e salvo localmente.

Certifique-se de ter concluído o fluxo de autenticação única (install-gee-python-api.html#authentication)


antes de executar o script.

Siga as etapas abaixo para criar um script para baixar dados do GEE.

1. Crie um novo arquivo nomeado download_data.py com o conteúdo mostrado abaixo.


import datetime
import ee
import csv
import os

ee.Initialize()

# Get current date and convert to milliseconds


start_date = ee.Date.fromYMD(2022, 1, 1)
end_date = start_date.advance(1, 'month')

date_string = end_date.format('YYYY_MM')
filename = 'ssm_{}.csv'.format(date_string.getInfo())

# Saving to current directory. You can change the path to appropriate location
output_path = os.path.join(filename)

# Datasets
# SMAP is in safe mode and not generating new data since August 2022
# https://nsidc.org/data/user-resources/data-announcements/user-notice-smap-safe-mode
soilmoisture = ee.ImageCollection("NASA_USDA/HSL/SMAP10KM_soil_moisture")
admin2 = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level2")

# Filter to a state
karnataka = admin2.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'))

# Select the ssm band


ssm = soilmoisture.select('ssm')

filtered = ssm .filter(ee.Filter.date(start_date, end_date))

mean = filtered.mean()

stats = mean.reduceRegions(**{
'collection': karnataka,
'reducer': ee.Reducer.mean().setOutputs(['meanssm']),
'scale': 10000,
'crs': 'EPSG:32643'
})

# Select columns to keep and remove geometry to make the result lightweight
# Change column names to match your uploaded shapefile
columns = ['ADM2_NAME', 'meanssm']
exportCollection = stats.select(**{
'propertySelectors': columns,
'retainGeometry': False})

features = exportCollection.getInfo()['features']

data = []

for f in features:
data.append(f['properties'])

field_names = ['ADM2_NAME', 'meanssm']

with open(output_path, 'w') as csvfile:


writer = csv.DictWriter(csvfile, fieldnames = field_names)
writer.writeheader()
writer.writerows(data)
print('Success: File written at', output_path)

2. No terminal, navegue até o diretório onde você criou o arquivo e digite o comando abaixo para executar o script.

python download_data.py
3. O script baixará os dados do GEE e salvará um arquivo em seu diretório atual.
Suplemento
Esta seção contém scripts úteis e trechos de código que podem ser adaptados para seus projetos.

Técnicas Avançadas de Classificação Supervisionada


Ajuste de hiperparâmetros
Uma prática recomendada para melhorar a precisão do seu modelo de aprendizado de máquina é ajustar diferentes parâmetros. Por
exemplo, ao usar o ee.Classifier.smileRandomForest() classificador, devemos especificar o Número de Árvores . Sabemos que um
número maior de árvores resulta em mais requisitos de computação, mas não resulta necessariamente em melhores resultados. Em vez de
adivinhar, tentamos programaticamente um intervalo de valores e escolhemos o menor valor possível que resulta na maior precisão.

Saída de Classificação Supervisionada

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FHyperparameter_Tuning)
var s2 = ee.ImageCollection("COPERNICUS/S2_SR");
var basin = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_7");
var gcp = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_gcps");
var alos = ee.Image("JAXA/ALOS/AW3D30/V2_2");

var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640))


var boundary = arkavathy.geometry()
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
// Function to remove cloud and snow pixels from Sentinel-2 SR image

function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(10);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 10% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(boundary))
.map(maskCloudAndShadowsSR)
.select('B.*')

var composite = filtered.median().clip(boundary)

var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000, gamma: 1.2};
Map.centerObject(boundary)
Map.addLayer(composite, visParams, 'RGB');

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi)
}

var composite = addIndices(composite);

// Calculate Slope and Elevation


var elev = alos.select('AVE_DSM').rename('elev');
var slope = ee.Terrain.slope(alos.select('AVE_DSM')).rename('slope');

var composite = composite.addBands(elev).addBands(slope);

// Normalize the image

// Machine learning algorithms work best on images when all features have
// the same range

// Function to Normalize Image


// Pixel Values should be between 0 and 1
// Formula is (x - xmin) / (xmax - xmin)
//**************************************************************************
function normalize(image){
var bandNames = image.bandNames();
// Compute min and max of the image
var minDict = image.reduceRegion({
reducer: ee.Reducer.min(),
geometry: boundary,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var maxDict = image.reduceRegion({
reducer: ee.Reducer.max(),
geometry: boundary,
scale: 20,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var mins = ee.Image.constant(minDict.values(bandNames));
var maxs = ee.Image.constant(maxDict.values(bandNames));

var normalized = image.subtract(mins).divide(maxs.subtract(mins))


return normalized
}

var composite = normalize(composite);


// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn()

// This being a simpler classification, we take 60% points


// for validation. Normal recommended ratio is
// 70% training, 30% validation
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));
Map.addLayer(validationGcp)
// Overlay the point on the image to get training data.
var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});
print(training)
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

//**************************************************************************
// Feature Importance
//**************************************************************************

// Run .explain() to see what the classifer looks like


print(classifier.explain())

// Calculate variable importance


var importance = ee.Dictionary(classifier.explain().get('importance'))

// Calculate relative importance


var sum = importance.values().reduce(ee.Reducer.sum())
var relativeImportance = importance.map(function(key, val) {
return (ee.Number(val).multiply(100)).divide(sum)
})
print(relativeImportance)

// Create a FeatureCollection so we can chart it


var importanceFc = ee.FeatureCollection([
ee.Feature(null, relativeImportance)
])

var chart = ui.Chart.feature.byProperty({


features: importanceFc
}).setOptions({
title: 'Feature Importance',
vAxis: {title: 'Importance'},
hAxis: {title: 'Feature'}
})
print(chart)

//**************************************************************************
// Hyperparameter Tuning
//**************************************************************************

var test = composite.sampleRegions({


collection: validationGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});

// Tune the numberOfTrees parameter.


var numTreesList = ee.List.sequence(10, 150, 10);

var accuracies = numTreesList.map(function(numTrees) {


var classifier = ee.Classifier.smileRandomForest(numTrees)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Here we are classifying a table instead of an image


// Classifiers work on both images and tables
return test
.classify(classifier)
.errorMatrix('landcover', 'classification')
.accuracy();
});

var chart = ui.Chart.array.values({


array: ee.Array(accuracies),
axis: 0,
xLabels: numTreesList
}).setOptions({
title: 'Hyperparameter Tuning for the numberOfTrees Parameters',
vAxis: {title: 'Validation Accuracy'},
hAxis: {title: 'Number of Tress', gridlines: {count: 15}}
});
print(chart)

// Tuning Multiple Parameters


// We can tune many parameters together using
// nested map() functions
// Let's tune 2 parameters
// numTrees and bagFraction
var numTreesList = ee.List.sequence(10, 150, 10);
var bagFractionList = ee.List.sequence(0.1, 0.9, 0.1);
var accuracies = numTreesList.map(function(numTrees) {
return bagFractionList.map(function(bagFraction) {
var classifier = ee.Classifier.smileRandomForest({
numberOfTrees: numTrees,
bagFraction: bagFraction
})
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Here we are classifying a table instead of an image


// Classifiers work on both images and tables
var accuracy = test
.classify(classifier)
.errorMatrix('landcover', 'classification')
.accuracy();
return ee.Feature(null, {'accuracy': accuracy,
'numberOfTrees': numTrees,
'bagFraction': bagFraction})
})
}).flatten()
var resultFc = ee.FeatureCollection(accuracies)

// Export the result as CSV


Export.table.toDrive({
collection: resultFc,
description: 'Multiple_Parameter_Tuning_Results',
folder: 'earthengine',
fileNamePrefix: 'numtrees_bagfraction',
fileFormat: 'CSV'})

Resultados da classificação pós-processamento


Os resultados da classificação supervisionada geralmente contêm ruído de sal e pimenta causado por pixels mal classificados. Geralmente é
preferível aplicar algumas técnicas de pós-processamento para remover esse ruído. O script a seguir contém o código para duas técnicas
populares de pós-processamento de resultados de classificação.

Usando clustering não supervisionado para substituir o valor classificado pelo valor majoritário em cada cluster.
Substituindo pixels isolados com valor circundante com um filtro majoritário.

Lembre-se de que os métodos de vizinhança dependem da escala, portanto, os resultados mudarão


conforme você aumentar/diminuir o zoom. Exporte os resultados na escala desejada para ver o efeito do
pós-processamento.
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3ASupplement%2FSupervised_Classification%2FPost_Processing_Classification_Results)
// Sentinel-2 Median Composite
var composite = ee.Image("users/ujavalgandhi/e2e/arkavathy_2019_composite");
Map.addLayer(composite, {min: 0, max: 0.3, bands: ['B4', 'B3', 'B2']}, 'RGB Composite');

// Raw Supervised Classification Results


var classified = ee.Image("users/ujavalgandhi/e2e/arkavathy_final_classification");
Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, 'Original');
Map.centerObject(classified, 10)

//**************************************************************************
// Post process by clustering
//**************************************************************************

// Cluster using Unsupervised Clustering methods


var seeds = ee.Algorithms.Image.Segmentation.seedGrid(5);

var snic = ee.Algorithms.Image.Segmentation.SNIC({


image: composite.select('B.*'),
compactness: 0,
connectivity: 4,
neighborhoodSize: 10,
size: 2,
seeds: seeds
})
var clusters = snic.select('clusters')

// Assign class to each cluster based on 'majority' voting (using ee.Reducer.mode()


var smoothed = classified.addBands(clusters);

var clusterMajority = smoothed.reduceConnectedComponents({


reducer: ee.Reducer.mode(),
labelBand: 'clusters'
});
Map.addLayer(clusterMajority, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']},
'Processed using Clusters');

//**************************************************************************
// Post process by replacing isolated pixels with surrounding value
//**************************************************************************

// count patch sizes


var patchsize = classified.connectedPixelCount(40, false);

// run a majority filter


var filtered = classified.focal_mode({
radius: 10,
kernelType: 'square',
units: 'meters',
});

// updated image with majority filter where patch size is small


var connectedClassified = classified.where(patchsize.lt(25),filtered);
Map.addLayer(connectedClassified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']},
'Processed using Connected Pixels');

Análise de Componentes Principais (PCA)


PCA é uma técnica muito útil para melhorar seus resultados de classificação supervisionada. Esta é uma técnica estatística que comprime
dados de um grande número de bandas em menos bandas não correlacionadas. Você pode executar o PCA em sua imagem e adicionar as
primeiras (normalmente 3) bandas de componentes principais ao composto original antes de amostrar os pontos de treinamento. No
exemplo abaixo, você notará que 97% da variação da imagem original de 13 bandas é capturada na imagem PCA de 3 bandas. Isso envia um
sinal mais forte para o classificador e melhora a precisão, permitindo que ele distinga melhor as diferentes classes.
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3ASupplement%2FSupervised_Classification%2FPrincipal_Components_Analysis)
// Script showing how to do Principal Component Analysis on images
var composite = ee.Image("users/ujavalgandhi/e2e/arkavathy_2019_composite");
var boundary = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_boundary");

print('Input Composite', composite);


Map.centerObject(composite)
Map.addLayer(composite, {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.3, gamma: 1.2}, 'RGB');

// Define the geometry and scale parameters


var geometry = boundary.geometry();
var scale = 20;

// Run the PCA function


var pca = PCA(composite)

// Extract the properties of the pca image


var variance = pca.toDictionary()
print('Variance of Principal Components', variance)

// As you see from the printed results, ~97% of the variance


// from the original image is captured in the first 3 principal components
// We select those and discard others
var pca = PCA(composite).select(['pc1', 'pc2', 'pc3'])
print('First 3 PCA Bands', pca);

// PCA computation is expensive and can time out when displaying on the map
// Export the results and import them back
Export.image.toAsset({
image: pca,
description: 'Principal_Components_Image',
assetId: 'users/ujavalgandhi/e2e/arkavathy_pca',
region: geometry,
scale: scale,
maxPixels: 1e10})
// Once the export finishes, import the asset and display
var pcaImported = ee.Image('users/ujavalgandhi/e2e/arkavathy_pca')
var pcaVisParams = {bands: ['pc1', 'pc2', 'pc3'], min: -2, max: 2};

Map.addLayer(pcaImported, pcaVisParams, 'Principal Components');

//**************************************************************************
// Function to calculate Principal Components
// Code adapted from https://developers.google.com/earth-engine/guides/arrays_eigen_analysis
//**************************************************************************
function PCA(maskedImage){
var image = maskedImage.unmask()
var scale = scale;
var region = geometry;
var bandNames = image.bandNames();
// Mean center the data to enable a faster covariance reducer
// and an SD stretch of the principal components.
var meanDict = image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: region,
scale: scale,
maxPixels: 1e13,
tileScale: 16
});
var means = ee.Image.constant(meanDict.values(bandNames));
var centered = image.subtract(means);
// This helper function returns a list of new band names.
var getNewBandNames = function(prefix) {
var seq = ee.List.sequence(1, bandNames.length());
return seq.map(function(b) {
return ee.String(prefix).cat(ee.Number(b).int());
});
};
// This function accepts mean centered imagery, a scale and
// a region in which to perform the analysis. It returns the
// Principal Components (PC) in the region as a new image.
var getPrincipalComponents = function(centered, scale, region) {
// Collapse the bands of the image into a 1D array per pixel.
var arrays = centered.toArray();

// Compute the covariance of the bands within the region.


var covar = arrays.reduceRegion({
reducer: ee.Reducer.centeredCovariance(),
geometry: region,
scale: scale,
maxPixels: 1e13,
tileScale: 16
});

// Get the 'array' covariance result and cast to an array.


// This represents the band-to-band covariance within the region.
var covarArray = ee.Array(covar.get('array'));

// Perform an eigen analysis and slice apart the values and vectors.
var eigens = covarArray.eigen();

// This is a P-length vector of Eigenvalues.


var eigenValues = eigens.slice(1, 0, 1);

// Compute Percentage Variance of each component


// This will allow us to decide how many components capture
// most of the variance in the input
var eigenValuesList = eigenValues.toList().flatten()
var total = eigenValuesList.reduce(ee.Reducer.sum())

var percentageVariance = eigenValuesList.map(function(item) {


var component = eigenValuesList.indexOf(item).add(1).format('%02d')
var variance = ee.Number(item).divide(total).multiply(100).format('%.2f')
return ee.List([component, variance])
})
// Create a dictionary that will be used to set properties on final image
var varianceDict = ee.Dictionary(percentageVariance.flatten())
// This is a PxP matrix with eigenvectors in rows.
var eigenVectors = eigens.slice(1, 1);
// Convert the array image to 2D arrays for matrix computations.
var arrayImage = arrays.toArray(1);

// Left multiply the image array by the matrix of eigenvectors.


var principalComponents = ee.Image(eigenVectors).matrixMultiply(arrayImage);

// Turn the square roots of the Eigenvalues into a P-band image.


// Call abs() to turn negative eigenvalues to positive before
// taking the square root
var sdImage = ee.Image(eigenValues.abs().sqrt())
.arrayProject([0]).arrayFlatten([getNewBandNames('sd')]);

// Turn the PCs into a P-band image, normalized by SD.


return principalComponents
// Throw out an an unneeded dimension, [[]] -> [].
.arrayProject([0])
// Make the one band array image a multi-band image, [] -> image.
.arrayFlatten([getNewBandNames('pc')])
// Normalize the PCs by their SDs.
.divide(sdImage)
.set(varianceDict);
};
var pcImage = getPrincipalComponents(centered, scale, region);
return pcImage.mask(maskedImage.mask());
}
Compósitos Multitemporais para Classificação de Culturas
A classificação das culturas é um problema difícil. Uma técnica útil que auxilia na distinção clara das culturas é contabilizar a fenologia da
cultura. Esta técnica pode ser aplicada para detectar um tipo específico de cultura ou distinguir culturas de outras formas de vegetação. Você
pode criar imagens compostas para diferentes períodos do ciclo de corte e criar uma imagem empilhada para ser usada para classificação.
Isso permite que o classificador aprenda o padrão temporal e detecte pixels que exibem padrões semelhantes.

Capturando a fenologia da cultura por meio de compostos sazonais

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FSeasonal_Composites_for_Crop_Classification)
var s2 = ee.ImageCollection("COPERNICUS/S2_SR")
var basin = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_7")
var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640))
var boundary = arkavathy.geometry()
Map.centerObject(boundary, 11)

// Function to remove cloud pixels from Sentinel-2 SR image


function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(10);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 10% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask).divide(10000)
.copyProperties(image, ['system:time_start']);
}

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(boundary))
.map(maskCloudAndShadowsSR)
// There are 3 distinct crop seasons in the area of interest
// Jan-March = Winter (Rabi) Crops
// April-June = Summer Crops / Harvest
// July-December = Monsoon (Kharif) Crops
var cropCalendar = ee.List([[1,3], [4,6], [7,12]])

// We create different composites for each season


var createSeasonComposites = function(months) {
var startMonth = ee.List(months).get(0)
var endMonth = ee.List(months).get(1)
var monthFilter = ee.Filter.calendarRange(startMonth, endMonth, 'month')
var seasonFiltered = filtered.filter(monthFilter)
var composite = seasonFiltered.median()
return composite.select('B.*').clip(boundary)
}

var compositeList = cropCalendar.map(createSeasonComposites)

var rabi = ee.Image(compositeList.get(0))


var harvest = ee.Image(compositeList.get(1))
var kharif = ee.Image(compositeList.get(2))

var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.3, gamma: 1.2};
Map.addLayer(rabi, visParams, 'Rabi')
Map.addLayer(harvest, visParams, 'Harvest')
Map.addLayer(kharif, visParams, 'Kharif')

// Create a stacked image with composites from all seasons


// This multi-temporal image is able capture the crop phenology
// Classifier will be able to detect crop-pixels from non-crop pixels
var composite = rabi.addBands(harvest).addBands(kharif)

// This is a 36-band image


// Use this image for sampling training points for
// to train a crop classifier
print(composite)

Correlação computacional
Uma técnica útil para ajudar na classificação da cultura é modelar a correlação entre a precipitação e as mudanças na vegetação. Isso
permite que o modelo capture respostas diferenciadas à precipitação (ou seja, colheitas alimentadas por invasão versus florestas
permanentes). Primeiro preparamos uma coleção de imagens onde cada imagem consiste em 2 bandas - precipitação cumulativa para cada
mês e NDVI médio para o próximo mês. Isso criará 11 imagens por ano que mostram precipitação e NDVI defasado de 1 mês em cada pixel. A
coleção é então reduzida usando o ee.Reducer.pearsonsCorrelation() que produz uma correlation banda. Valores positivos
mostrarão regiões onde a precipitação causou um aumento no NDVI. Adicionar esta faixa à sua imagem de entrada para classificação ajudará
muito o classificador na separação de diferentes tipos de vegetação.

Abrir no Editor de Código ↗ (https://code.earthengine.google.com/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FRainfall_NDVI_Correlation)
// Calculate Rainfall-NDVI Correlation
var geometry = ee.Geometry.Point([75.71168046831512, 13.30751919691132]);
Map.centerObject(geometry, 10)
var s2 = ee.ImageCollection("COPERNICUS/S2_SR");

var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

// Function to remove cloud and snow pixels from Sentinel-2 SR image


function maskCloudAndShadowsSR(image) {
var cloudProb = image.select('MSK_CLDPRB');
var snowProb = image.select('MSK_SNWPRB');
var cloud = cloudProb.lt(5);
var snow = snowProb.lt(5);
var scl = image.select('SCL');
var shadow = scl.eq(3); // 3 = cloud shadow
var cirrus = scl.eq(10); // 10 = cirrus
// Cloud probability less than 5% or cloud shadow classification
var mask = cloud.and(cirrus.neq(1)).and(shadow.neq(1));
return image.updateMask(mask);
}

// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

var s2Filtered = s2
.filter(ee.Filter.date('2020-01-01', '2021-01-01'))
.filter(ee.Filter.bounds(geometry))
.map(maskCloudAndShadowsSR)
.map(addNDVI)

var composite = s2Filtered.median()


Map.addLayer(composite, rgbVis, 'Composite')

// Rainfall
var chirps = ee.ImageCollection("UCSB-CHG/CHIRPS/PENTAD");
var chirpsFiltered = chirps
.filter(ee.Filter.date('2020-01-01', '2021-01-01'))

// Monsoon months
var months = ee.List.sequence(1, 11)

// Monthly Images
var byMonth = months.map(function(month) {
var monthlyRain = chirpsFiltered
.filter(ee.Filter.calendarRange(month, month, 'month'))
var totalRain = monthlyRain.sum()

var nextMonth = ee.Number(month).add(1)


var monthly = s2Filtered
.filter(ee.Filter.calendarRange(nextMonth, nextMonth, 'month'))
var medianComposite = monthly.median()

return totalRain.addBands(medianComposite).set({'month': month})


})
var monthlyCol = ee.ImageCollection.fromImages(byMonth)
// Display Composites
var julImage = ee.Image(monthlyCol.filter(ee.Filter.eq('month', 7)).first())
var augImage = ee.Image(monthlyCol.filter(ee.Filter.eq('month', 8)).first())
var sepImage = ee.Image(monthlyCol.filter(ee.Filter.eq('month', 9)).first())

Map.addLayer(julImage, rgbVis, 'July', false)


Map.addLayer(augImage, rgbVis, 'August', false)
Map.addLayer(sepImage, rgbVis, 'September', false)

var correlationCol = monthlyCol.select(['precipitation', 'ndvi'])

var correlation = correlationCol.reduce(ee.Reducer.pearsonsCorrelation());

var positive = correlation.select('correlation').gt(0.5)

Map.addLayer(correlation.select('correlation'),
{min:-1, max:1, palette: ['red', 'white', 'green']}, 'Correlation');
Map.addLayer(positive.selfMask(),
{palette: ['yellow']}, 'Positive Correlation', false);

Calculando a Matriz de Correlação de Banda


Ao selecionar recursos para seu modelo de aprendizado de máquina, é importante ter recursos que não estejam correlacionados entre si. Os
recursos correlacionados dificultam que os modelos de aprendizado de máquina descubram as interações entre diferentes recursos. Uma
técnica comumente usada para auxiliar na remoção de variáveis ​redundantes é criar uma Matriz de Correlação. No Earth Engine, você pode
obter uma imagem de várias bandas e calcular a correlação de pares entre as bandas usando ee.Reducer.pearsonsCorrelation() ou
ee.Reducer.spearmansCorrelation() . A matriz de correlação ajuda a identificar variáveis ​redundantes e que podem ser removidas. O
código abaixo também mostra como exportar a tabela de recursos que pode ser usada em outro software para calcular a correlação.

Matriz de correlação criada em Python usando dados exportados do GEE

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FCorrelation_Matrix)
// Calculate Pair-wise Correlation Between Bands of an Image

// We take a multi-band composite image created in the previous sections


var composite = ee.Image('users/ujavalgandhi/e2e/arkavathy_multiband_composite');
var visParams = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000, gamma: 1.2};
Map.addLayer(composite, visParams, 'RGB');

var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');


var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640));
var geometry = arkavathy.geometry();
Map.centerObject(geometry);

// This image has 18 bands and we want to compute correlation between them.
// Get the band names
// These bands will be the input variables to the model
var bands = composite.bandNames();
print(bands);

// Generate random points to sample from the image


var numPoints = 5000
var samples = composite.sample({
region: geometry,
scale: 10,
numPixels: numPoints,
tileScale: 16
});
print(samples.first());

// Calculate pairwise-correlation between each pair of bands


// Use ee.Reducer.pearsonsCorrelation() for Pearson's Correlation
// Use ee.Reducer.spearmansCorrelation() for Spearman's Correlation
var pairwiseCorr = ee.FeatureCollection(bands.map(function(i){
return bands.map(function(j){
var stats = samples.reduceColumns({
reducer: ee.Reducer.pearsonsCorrelation(),
selectors: [i,j]
});
var bandNames = ee.String(i).cat('_').cat(j);
return ee.Feature(null, {'correlation': stats.get('correlation'), 'band': bandNames});
});
}).flatten());

// Export the table as a CSV file


Export.table.toDrive({
collection: pairwiseCorr,
description: 'Pairwise_Correlation',
folder: 'earthengine',
fileNamePrefix: 'pairwise_correlation',
fileFormat: 'CSV',
});

// You can also export the sampled points and calculate correlation
// in Python or R. Reference Python implementation is at
// https://courses.spatialthoughts.com/python-dataviz.html#feature-correlation-matrix
Export.table.toDrive({
collection: samples,
description: 'Feature_Sample_Data',
folder: 'earthengine',
fileNamePrefix: 'feature_sample_data',
fileFormat: 'CSV',
selectors: bands.getInfo()
});

Calculando Área por Classe


Este trecho de código mostra como usar um redutor agrupado (https://developers.google.com/earth-engine/guides/reducers_grouping) para
calcular a área coberta por cada classe em uma imagem classificada. Também mostra como usar a ui.Chart.image.byClass() função para
criar um gráfico mostrando a área de cada classe.
Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3ASupplement%2FSupervised_Classification%2FCalculating_Area_by_Class)
var classified = ee.Image("users/ujavalgandhi/e2e/bangalore_classified");
var bangalore = ee.FeatureCollection("users/ujavalgandhi/public/bangalore_boundary");

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

// Create a 2 band image with the area image and the classified image
// Divide the area image by 1e6 so area results are in Sq Km
var areaImage = ee.Image.pixelArea().divide(1e6).addBands(classified);

// Calculate Area by Class


// Using a Grouped Reducer
var areas = areaImage.reduceRegion({
reducer: ee.Reducer.sum().group({
groupField: 1,
groupName: 'classification',
}),
geometry: bangalore,
scale: 100,
tileScale: 4,
maxPixels: 1e10
});

var classAreas = ee.List(areas.get('groups'))


print(classAreas)

var areaChart = ui.Chart.image.byClass({


image: areaImage,
classBand: 'classification',
region: bangalore,
scale: 100,
reducer: ee.Reducer.sum(),
classLabels: ['urban', 'bare', 'water', 'vegetation'],
}).setOptions({
hAxis: {title: 'Classes'},
vAxis: {title: 'Area Km^2'},
title: 'Area by class',
series: {
0: { color: 'gray' },
1: { color: 'brown' },
2: { color: 'blue' },
3: { color: 'green' }
}
});
print(areaChart);

Gráficos de assinatura espectral


Para classificação supervisionada, é útil visualizar respostas espectrais médias para cada banda para cada classe. Esses gráficos são
chamados de Curvas de Resposta Espectral ou Assinaturas Espectrais . Esses gráficos ajudam a determinar a separabilidade das classes. Se as
classes tiverem assinaturas muito diferentes, um classificador poderá separá-las bem.

Também podemos traçar assinaturas espectrais de todas as amostras de treinamento para uma classe e verificar a qualidade do conjunto de
dados de treinamento. Se todas as amostras de treinamento mostrarem assinaturas semelhantes, isso indica que você fez um bom trabalho
ao coletar as amostras apropriadas. Você também pode detectar possíveis outliers desses gráficos.

Esses gráficos fornecem métodos qualitativos e visuais para verificar a separabilidade das classes. Para métodos quantitativos, pode-se
aplicar medidas como distância espectral, distância de Mahalanobis, distância de Bhattacharyya, distância de Jeffreys-Matusita (JM) etc.
Você pode encontrar o código para eles nesta resposta do Stack Exchange (https://gis.stackexchange.com/a/323778/5160) .
Assinaturas médias para todas as classes

Assinaturas espectrais para todos os pontos de treinamento por classe

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FSpectral_Signatures)
var gcps = ee.FeatureCollection("users/ujavalgandhi/e2e/bangalore_gcps");
var composite = ee.Image('users/ujavalgandhi/e2e/bangalore_composite');

// Overlay the point on the image to get bands data.


var training = composite.sampleRegions({
collection: gcps,
properties: ['landcover'],
scale: 10
});

// We will create a chart of spectral signature for all classes

// We have multiple GCPs for each class


// Use a grouped reducer to calculate the average reflectance
// for each band for each class

// We have 12 bands so need to repeat the reducer 12 times


// We also need to group the results by class
// So we find the index of the landcover property and use it
// to group the results
var bands = composite.bandNames()
var numBands = bands.length()
var bandsWithClass = bands.add('landcover')
var classIndex = bandsWithClass.indexOf('landcover')

// Use .combine() to get a reducer capable of


// computing multiple stats on the input
var combinedReducer = ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(),
sharedInputs: true})

// Use .repeat() to get a reducer for each band


// We then use .group() to get stats by class
var repeatedReducer = combinedReducer.repeat(numBands).group(classIndex)

var gcpStats = training.reduceColumns({


selectors: bands.add('landcover'),
reducer: repeatedReducer,
})

// Result is a dictionary, we do some post-processing to


// extract the results
var groups = ee.List(gcpStats.get('groups'))

var classNames = ee.List(['urban', 'bare', 'water', 'vegetation'])

var fc = ee.FeatureCollection(groups.map(function(item) {
// Extract the means
var values = ee.Dictionary(item).get('mean')
var groupNumber = ee.Dictionary(item).get('group')
var properties = ee.Dictionary.fromLists(bands, values)
var withClass = properties.set('class', classNames.get(groupNumber))
return ee.Feature(null, withClass)
}))

// Chart spectral signatures of training data


var options = {
title: 'Average Spectral Signatures',
hAxis: {title: 'Bands'},
vAxis: {title: 'Reflectance',
viewWindowMode:'explicit',
viewWindow: {
max:0.6,
min:0
}},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: 'grey'},
1: {color: 'brown'},
2: {color: 'blue'},
3: {color: 'green'},
}};

// Default band names don't sort propertly


// Instead, we can give a dictionary with
// labels for each band in the X-Axis
var bandDescriptions = {
'B2': 'B02/Blue',
'B3': 'B03/Green',
'B4': 'B04/Red',
'B8': 'B08/NIR',
'B11': 'B11/SWIR-1',
'B12': 'B12/SWIR-2'
}
// Create the chart and set options.
var chart = ui.Chart.feature.byProperty({
features: fc,
xProperties: bandDescriptions,
seriesProperty: 'class'
})
.setChartType('ScatterChart')
.setOptions(options);

print(chart)

var classChart = function(landcover, label, color) {


var options = {
title: 'Spectral Signatures for ' + label + ' Class',
hAxis: {title: 'Bands'},
vAxis: {title: 'Reflectance',
viewWindowMode:'explicit',
viewWindow: {
max:0.6,
min:0
}},
lineWidth: 1,
pointSize: 4,
};

var fc = training.filter(ee.Filter.eq('landcover', landcover))


var chart = ui.Chart.feature.byProperty({
features: fc,
xProperties: bandDescriptions,
})
.setChartType('ScatterChart')
.setOptions(options);

print(chart)
}
classChart(0, 'Urban')
classChart(1, 'Bare')
classChart(2, 'Water')
classChart(3, 'Vegetation')

Identifique GCPs mal classificados


Ao fazer a avaliação de precisão, você verá os recursos de validação que não foram classificados corretamente. É útil ver visualmente os
pontos que foram classificados incorretamente. Podemos usar filtros ee.Filter.eq() e ee.Filter.neq() para filtrar os recursos em que
as classes reais e previstas eram diferentes. O código abaixo mostra como implementar isso e também usar a style() função visualizá-los
de forma eficaz.

Abrir no Editor de Código ↗ (https://code.earthengine.google.com/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FIdentify_Misclassified_Data)
// Script that shows how to apply filters to identify
// validation points that were misclassified
var s2 = ee.ImageCollection("COPERNICUS/S2_SR");
var basin = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_7");
var gcp = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_gcps");

Map.centerObject(gcp)
var arkavathy = basin.filter(ee.Filter.eq('HYBAS_ID', 4071139640))
var boundary = arkavathy.geometry()
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(boundary))
.select('B.*')

var composite = filtered.median().clip(boundary)

// Display the input composite.


Map.addLayer(composite, rgbVis, 'image');

// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn()
var trainingGcp = gcp.filter(ee.Filter.lt('random', 0.6));
var validationGcp = gcp.filter(ee.Filter.gte('random', 0.6));

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: trainingGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16,
geometries:true
});
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});

// Classify the image.


var classified = composite.classify(classifier);

Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

var test = classified.sampleRegions({


collection: validationGcp,
properties: ['landcover'],
tileScale: 16,
scale: 10,
geometries:true
});

var testConfusionMatrix = test.errorMatrix('landcover', 'classification')


print('Confusion Matrix', testConfusionMatrix);

// Let's apply filters to find misclassified points


// We can find all points which are labeled landcover=0 (urban)
// but were not classified correctly

// We use ee.Filter.and() function to create a combined filter


var combinedFilter = ee.Filter.and(
ee.Filter.eq('landcover', 0), ee.Filter.neq('classification', 0))
var urbanMisclassified = test.filter(combinedFilter)
print('Urban Misclassified Points', urbanMisclassified)

// We can also apply a filter to select all misclassified points


// Since we are comparing 2 properties agaist each-other,
// we need to use a binary filter
var misClassified = test.filter(ee.Filter.notEquals({
leftField:'classification', rightField:'landcover'}))

print('All Misclassified Points', misClassified)

// Display the misclassified points by styling them


var landcover = ee.List([0, 1, 2, 3])
var palette = ee.List(['gray','brown','blue','green'])
var misclassStyled = ee.FeatureCollection(
landcover.map(function(lc){
var feature = misClassified.filter(ee.Filter.eq('landcover', lc))
var color = palette.get(landcover.indexOf(lc));
var markerStyle = {color:color}
return feature.map(function(point){
return point.set('style', markerStyle)
})
})).flatten();

Map.addLayer(misclassStyled.style({styleProperty:"style"}), {}, 'Misclassified Points')

Normalização e Padronização de Imagens


Para aprendizado de máquina, é uma prática recomendada normalizar ou padronizar seus recursos. O código abaixo mostra como
implementar essas técnicas de dimensionamento de recursos.

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FImage_Normalization_and_Standardization)
var image = ee.Image("users/ujavalgandhi/e2e/arkavathy_2019_composite");
var boundary = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_boundary")
var geometry = boundary.geometry()

//**************************************************************************
// Function to Normalize Image
// Pixel Values should be between 0 and 1
// Formula is (x - xmin) / (xmax - xmin)
//**************************************************************************
function normalize(image){
var bandNames = image.bandNames();
// Compute min and max of the image
var minDict = image.reduceRegion({
reducer: ee.Reducer.min(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var maxDict = image.reduceRegion({
reducer: ee.Reducer.max(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var mins = ee.Image.constant(minDict.values(bandNames));
var maxs = ee.Image.constant(maxDict.values(bandNames));

var normalized = image.subtract(mins).divide(maxs.subtract(mins))


return normalized
}

//**************************************************************************
// Function to Standardize Image
// (Mean Centered Imagery with Unit Standard Deviation)
// https://365datascience.com/tutorials/statistics-tutorials/standardization/
//**************************************************************************
function standardize(image){
var bandNames = image.bandNames();
// Mean center the data to enable a faster covariance reducer
// and an SD stretch of the principal components.
var meanDict = image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var means = ee.Image.constant(meanDict.values(bandNames));
var centered = image.subtract(means)

var stdDevDict = image.reduceRegion({


reducer: ee.Reducer.stdDev(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
var stddevs = ee.Image.constant(stdDevDict.values(bandNames));

var standardized = centered.divide(stddevs);

return standardized
}
var standardizedImage = standardize(image)
var normalizedImage = normalize(image)

Map.addLayer(image,
{bands: ['B4', 'B3', 'B2'], min: 0, max: 0.3, gamma: 1.2}, 'Original Image');
Map.addLayer(normalizedImage,
{bands: ['B4', 'B3', 'B2'], min: 0, max: 1, gamma: 1.2}, 'Normalized Image');
Map.addLayer(standardizedImage,
{bands: ['B4', 'B3', 'B2'], min: -1, max: 2, gamma: 1.2}, 'Standarized Image');
Map.centerObject(geometry)

// Verify Normalization

var beforeDict = image.reduceRegion({


reducer: ee.Reducer.minMax(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});

var afterDict = normalizedImage.reduceRegion({


reducer: ee.Reducer.minMax(),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});

print('Original Image Min/Max', beforeDict)


print('Normalized Image Min/Max', afterDict)

// Verify Standadization
// Verify that the means are 0 and standard deviations are 1
var beforeDict = image.reduceRegion({
reducer: ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(), sharedInputs: true}),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});

var resultDict = standardizedImage.reduceRegion({


reducer: ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(), sharedInputs: true}),
geometry: geometry,
scale: 10,
maxPixels: 1e9,
bestEffort: true,
tileScale: 16
});
// Means are very small franctions close to 0
// Round them off to 2 decimals
var afterDict = resultDict.map(function(key, value) {
return ee.Number(value).format('%.2f')
})

print('Original Image Mean/StdDev', beforeDict)


print('Standadized Image Mean/StdDev', afterDict)
Calcular a importância do recurso
Muitos classificadores em GEE possuem um explain() método que calcula as importâncias dos recursos. O classificador atribuirá uma
pontuação a cada variável de entrada sobre a utilidade delas na previsão do valor correto. O script abaixo mostra como extrair a importância
do recurso e criar um gráfico para visualizá-lo.

Importância relativa do recurso

Abrir no Editor de Código ↗ (https://code.earthengine.google.com/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FSupervised_Classification%2FFeature_Importance)
var bangalore = ee.FeatureCollection('users/ujavalgandhi/public/bangalore_boundary')
var s2 = ee.ImageCollection('COPERNICUS/S2_SR')
var gcps = ee.FeatureCollection('users/ujavalgandhi/e2e/bangalore_gcps')

var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(bangalore))
.select('B.*')

var composite = filtered.median().clip(bangalore)

var addIndices = function(image) {


var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);
var ndbi = image.normalizedDifference(['B11', 'B8']).rename(['ndbi']);
var mndwi = image.normalizedDifference(['B3', 'B11']).rename(['mndwi']);
var bsi = image.expression(
'(( X + Y ) - (A + B)) /(( X + Y ) + (A + B)) ', {
'X': image.select('B11'), //swir1
'Y': image.select('B4'), //red
'A': image.select('B8'), // nir
'B': image.select('B2'), // blue
}).rename('bsi');
return image.addBands(ndvi).addBands(ndbi).addBands(mndwi).addBands(bsi)
}

composite = addIndices(composite)

// Display the input composite.


var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Map.addLayer(composite, rgbVis, 'image');

// Overlay the point on the image to get training data.


var training = composite.sampleRegions({
collection: gcps,
properties: ['landcover'],
scale: 10
});

// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
// // Classify the image.
var classified = composite.classify(classifier);
Map.addLayer(classified, {min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

//**************************************************************************
// Calculate Feature Importance
//**************************************************************************

// Run .explain() to see what the classifer looks like


print(classifier.explain())

// Calculate variable importance


var importance = ee.Dictionary(classifier.explain().get('importance'))
// Calculate relative importance
var sum = importance.values().reduce(ee.Reducer.sum())

var relativeImportance = importance.map(function(key, val) {


return (ee.Number(val).multiply(100)).divide(sum)
})
print(relativeImportance)

// Create a FeatureCollection so we can chart it


var importanceFc = ee.FeatureCollection([
ee.Feature(null, relativeImportance)
])

var chart = ui.Chart.feature.byProperty({


features: importanceFc
}).setOptions({
title: 'Feature Importance',
vAxis: {title: 'Importance'},
hAxis: {title: 'Feature'},
legend: {position: 'none'}
})
print(chart)

Suavização de Séries Temporais e Preenchimento de Gaps


Suavização da janela móvel
Uma técnica aplicada a uma série temporal para remoção da variação granular entre etapas de tempo é conhecida como Smoothing. Este
exemplo mostra como um algoritmo de suavização de janela móvel pode ser aplicado no Earth Engine. Usando um Save-all Join
(https://developers.google.com/earth-engine/guides/joins_save_all) , a coleção é unida a si mesma e todas as imagens que se enquadram na
janela temporal são adicionadas como uma propriedade de cada imagem. Em seguida, um redutor de média é aplicado em todas as imagens,
resultando no valor médio do pixel dentro do time-frame. A série temporal resultante reduz os picos e vales acentuados - e é mais robusta
contra outliers (como pixels nublados)

Suavização Média da Janela Móvel

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?accept_repo=users%2Fujavalgandhi%2FEnd-to-End-


GEE&scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3ASupplement%2FTime_Series_Smoothing%2FMoving_Window_Smoothing)
// Moving-Window Temporal Smoothing
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([74.80368345518073, 30.391793042969]);

var startDate = ee.Date.fromYMD(2019, 1, 1);


var endDate = ee.Date.fromYMD(2021, 1, 1);

// Function to add a NDVI band to an image


function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

// Function to mask clouds


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var originalCollection = s2
.filter(ee.Filter.date(startDate, endDate))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.map(addNDVI);

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: originalCollection.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'Original NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#238b45'},
},

})
print(chart);

// Moving-Window Smoothing

// Specify the time-window


var days = 15;

// Convert to milliseconds
var millis = ee.Number(days).multiply(1000*60*60*24);

// We use a 'save-all join' to find all images


// that are within the time-window

// The join will add all matching images into a


// new property called 'images'
var join = ee.Join.saveAll({
matchesKey: 'images'
});
// This filter will match all images that are captured
// within the specified day of the source image
var diffFilter = ee.Filter.maxDifference({
difference: millis,
leftField: 'system:time_start',
rightField: 'system:time_start'
});

var joinedCollection = join.apply({


primary: originalCollection,
secondary: originalCollection,
condition: diffFilter
});

print('Joined Collection', joinedCollection);

// Each image in the joined collection will contain


// matching images in the 'images' property
// Extract and return the mean of matched images
var extractAndComputeMean = function(image) {
var matchingImages = ee.ImageCollection.fromImages(image.get('images'));
var meanImage = matchingImages.reduce(
ee.Reducer.mean().setOutputs(['moving_average']))
return ee.Image(image).addBands(meanImage)
}

var smoothedCollection = ee.ImageCollection(


joinedCollection.map(extractAndComputeMean));

print('Smoothed Collection', smoothedCollection)

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: smoothedCollection.select(['ndvi', 'ndvi_moving_average']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#66c2a4', lineDashStyle: [1, 1], pointSize: 2}, // Original NDVI
1: {color: '#238b45', lineWidth: 2 }, // Smoothed NDVI
},

})
print(chart);

// Let's export the NDVI time-series as a video


var palette = ['#d73027','#f46d43','#fdae61','#fee08b',
'#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a9850'];
var ndviVis = {min:-0.2, max: 0.8, palette: palette}

Map.centerObject(geometry, 16);
var bbox = Map.getBounds({asGeoJSON: true});

var visualizeImage = function(image) {


return image.visualize(ndviVis).clip(bbox).selfMask()
}

var visCollectionOriginal = originalCollection.select('ndvi')


.map(visualizeImage)

var visCollectionSmoothed = smoothedCollection.select('ndvi_moving_average')


.map(visualizeImage)

Export.video.toDrive({
collection: visCollectionOriginal,
description: 'Original_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'original',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Export.video.toDrive({
collection: visCollectionSmoothed,
description: 'Smoothed_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'smoothed',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Interpolação Temporal
O código abaixo mostra como fazer o preenchimento de lacunas temporais de dados de séries temporais. Uma explicação detalhada do
código e outros exemplos são descritos em nosso blog Temporal Gap-Filling with Linear Interpolation in GEE
(https://spatialthoughts.com/2021/11/08/temporal-interpolation-gee/) .

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?accept_repo=users%2Fujavalgandhi%2FEnd-to-End-


GEE&scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3ASupplement%2FTime_Series_Smoothing%2FTemporal_Interpolation)
// Temporal Interpolation (Gap-Filling Masked Pixels)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([74.80368345518073, 30.391793042969]);

var startDate = ee.Date.fromYMD(2019, 1, 1);


var endDate = ee.Date.fromYMD(2021, 1, 1);

// Function to add a NDVI band to an image


function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

// Function to mask clouds


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var originalCollection = s2
.filter(ee.Filter.date(startDate, endDate))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.map(addNDVI);

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: originalCollection.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'Original NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#238b45'},
},

})
print(chart);

// Gap-filling

// Add a band containing timestamp to each image


// This will be used to do pixel-wise interpolation later
var originalCollection = originalCollection.map(function(image) {
var timeImage = image.metadata('system:time_start').rename('timestamp')
// The time image doesn't have a mask.
// We set the mask of the time band to be the same as the first band of the image
var timeImageMasked = timeImage.updateMask(image.mask().select(0))
return image.addBands(timeImageMasked).toFloat();
})

// For each image in the collection, we need to find all images


// before and after the specified time-window

// This is accomplished using Joins


// We need to do 2 joins
// Join 1: Join the collection with itself to find all images before each image
// Join 2: Join the collection with itself to find all images after each image

// We first define the filters needed for the join

// Define a maxDifference filter to find all images within the specified days
// The filter needs the time difference in milliseconds
// Convert days to milliseconds

// Specify the time-window to look for unmasked pixel


var days = 45;
var millis = ee.Number(days).multiply(1000*60*60*24)

var maxDiffFilter = ee.Filter.maxDifference({


difference: millis,
leftField: 'system:time_start',
rightField: 'system:time_start'
})

// We need a lessThanOrEquals filter to find all images after a given image


// This will compare the given image's timestamp against other images' timestamps
var lessEqFilter = ee.Filter.lessThanOrEquals({
leftField: 'system:time_start',
rightField: 'system:time_start'
})

// We need a greaterThanOrEquals filter to find all images before a given image


// This will compare the given image's timestamp against other images' timestamps
var greaterEqFilter = ee.Filter.greaterThanOrEquals({
leftField: 'system:time_start',
rightField: 'system:time_start'
})

// Apply the joins

// For the first join, we need to match all images that are after the given image.
// To do this we need to match 2 conditions
// 1. The resulting images must be within the specified time-window of target image
// 2. The target image's timestamp must be lesser than the timestamp of resulting images
// Combine two filters to match both these conditions
var filter1 = ee.Filter.and(maxDiffFilter, lessEqFilter)
// This join will find all images after, sorted in descending order
// This will gives us images so that closest is last
var join1 = ee.Join.saveAll({
matchesKey: 'after',
ordering: 'system:time_start',
ascending: false})

var join1Result = join1.apply({


primary: originalCollection,
secondary: originalCollection,
condition: filter1
})
// Each image now as a property called 'after' containing
// all images that come after it within the time-window
print(join1Result.first())

// Do the second join now to match all images within the time-window
// that come before each image
var filter2 = ee.Filter.and(maxDiffFilter, greaterEqFilter)
// This join will find all images before, sorted in ascending order
// This will gives us images so that closest is last
var join2 = ee.Join.saveAll({
matchesKey: 'before',
ordering: 'system:time_start',
ascending: true})

var join2Result = join2.apply({


primary: join1Result,
secondary: join1Result,
condition: filter2
})

var joinedCol = join2Result;

// Each image now as a property called 'before' containing


// all images that come after it within the time-window
print(joinedCol.first())
// Do the gap-filling

// We now write a function that will be used to interpolate all images


// This function takes an image and replaces the masked pixels
// with the interpolated value from before and after images.

var interpolateImages = function(image) {


var image = ee.Image(image);
// We get the list of before and after images from the image property
// Mosaic the images so we a before and after image with the closest unmasked pixel
var beforeImages = ee.List(image.get('before'))
var beforeMosaic = ee.ImageCollection.fromImages(beforeImages).mosaic()
var afterImages = ee.List(image.get('after'))
var afterMosaic = ee.ImageCollection.fromImages(afterImages).mosaic()

// Interpolation formula
// y = y1 + (y2-y1)*((t – t1) / (t2 – t1))
// y = interpolated image
// y1 = before image
// y2 = after image
// t = interpolation timestamp
// t1 = before image timestamp
// t2 = after image timestamp

// We first compute the ratio (t – t1) / (t2 – t1)

// Get image with before and after times


var t1 = beforeMosaic.select('timestamp').rename('t1')
var t2 = afterMosaic.select('timestamp').rename('t2')

var t = image.metadata('system:time_start').rename('t')

var timeImage = ee.Image.cat([t1, t2, t])

var timeRatio = timeImage.expression('(t - t1) / (t2 - t1)', {


't': timeImage.select('t'),
't1': timeImage.select('t1'),
't2': timeImage.select('t2'),
})
// You can replace timeRatio with a constant value 0.5
// if you wanted a simple average

// Compute an image with the interpolated image y


var interpolated = beforeMosaic
.add((afterMosaic.subtract(beforeMosaic).multiply(timeRatio)))
// Replace the masked pixels in the current image with the average value
var result = image.unmask(interpolated)
return result.copyProperties(image, ['system:time_start'])
}

// map() the function to gap-fill all images in the collection


var gapFilledCol = ee.ImageCollection(joinedCol.map(interpolateImages))

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: gapFilledCol.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'Gap-Filled NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#238b45'},
},
})
print(chart);

// Let's visualize the NDVI time-series


Map.centerObject(geometry, 16);
var bbox = Map.getBounds({asGeoJSON: true});

var palette = ['#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a985


0'];
var ndviVis = {min:-0.2, max: 0.8, palette: palette}

var visualizeImage = function(image) {


return image.visualize(ndviVis).clip(bbox).selfMask()
}

var visCollectionOriginal = originalCollection.select('ndvi')


.map(visualizeImage)

var visualizeIGapFilled = gapFilledCol.select('ndvi')


.map(visualizeImage)

Export.video.toDrive({
collection: visCollectionOriginal,
description: 'Original_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'original',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Export.video.toDrive({
collection: visualizeIGapFilled,
description: 'Gap_Filled_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'gap_filled',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Suavização Savitzky-Golay
O filtro Savitzky–Golay ajusta um polinômio a um conjunto de pontos de dados em uma série temporal. A Open Earth Engine Library (OEEL)
(https://www.open-geocomputing.org/OpenEarthEngineLibrary/) fornece uma implementação eficiente desse filtro que pode ser aplicado
em uma ImageCollection. No entanto, as séries temporais devem ser pré-processadas para que haja imagens em intervalos regulares.
Usamos a técnica de interpolação descrita na seção anterior e preparamos uma série temporal contínua sem nenhum pixel mascarado. O
resultado é uma nova ImageCollection contendo imagens em um intervalo regular (5 dias) e com valores de pixel suavizados usando o filtro
Savitzky–Golay.
Suavização Savitzky-Golay

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FTime_Series_Smoothing%2FSavitzky_Golay_Smoothing)
// Aplying Savitzky-Golay Filter on a NDVI Time-Series
// This script uses the OEEL library to apply a
// Savitzky-Golay filter on a imagecollection

// We require a regularly-spaced time-series without


// any masked pixels. So this script applies
// linear interpolation to created regularly spaced images
// from the original time-series

// Step-1: Prepare a NDVI Time-Series


// Step-2: Create an empty Time-Series with images at n days
// Step-3: Use Joins to find before/after images
// Step-4: Apply linear interpolation to fill each image
// Step-5: Apply Savitzky-Golay filter
// Step-6: Visualize the results

var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([74.80368345518073, 30.391793042969]);

var startDate = ee.Date.fromYMD(2019, 1, 1);


var endDate = ee.Date.fromYMD(2021, 1, 1);

// Function to add a NDVI band to an image


function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

// Function to mask clouds


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

var originalCollection = s2
.filter(ee.Filter.date(startDate, endDate))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.map(addNDVI);

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: originalCollection.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'Original NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#238b45'},
},

})
print(chart);

// Prepare a regularly-spaced Time-Series


// Generate an empty multi-band image matching the bands
// in the original collection
var bandNames = ee.Image(originalCollection.first()).bandNames();
var numBands = bandNames.size();
var initBands = ee.List.repeat(ee.Image(), numBands);
var initImage = ee.ImageCollection(initBands).toBands().rename(bandNames)

// Select the interval. We will have 1 image every n days


var n = 5;
var firstImage = ee.Image(originalCollection.sort('system:time_start').first())
var lastImage = ee.Image(originalCollection.sort('system:time_start', false).first())
var timeStart = ee.Date(firstImage.get('system:time_start'))
var timeEnd = ee.Date(lastImage.get('system:time_start'))

var totalDays = timeEnd.difference(timeStart, 'day');


var daysToInterpolate = ee.List.sequence(0, totalDays, n)

var initImages = daysToInterpolate.map(function(day) {


var image = initImage.set({
'system:index': ee.Number(day).format('%d'),
'system:time_start': timeStart.advance(day, 'day').millis(),
// Set a property so we can identify interpolated images
'type': 'interpolated'
})
return image
})

var initCol = ee.ImageCollection.fromImages(initImages)


print('Empty Collection', initCol)

// Merge original and empty collections


var originalCollection = originalCollection.merge(initCol)

// Interpolation

// Add a band containing timestamp to each image


// This will be used to do pixel-wise interpolation later
var originalCollection = originalCollection.map(function(image) {
var timeImage = image.metadata('system:time_start').rename('timestamp')
// The time image doesn't have a mask.
// We set the mask of the time band to be the same as the first band of the image
var timeImageMasked = timeImage.updateMask(image.mask().select(0))
return image.addBands(timeImageMasked).toFloat();
})

// For each image in the collection, we need to find all images


// before and after the specified time-window

// This is accomplished using Joins


// We need to do 2 joins
// Join 1: Join the collection with itself to find all images before each image
// Join 2: Join the collection with itself to find all images after each image

// We first define the filters needed for the join

// Define a maxDifference filter to find all images within the specified days
// The filter needs the time difference in milliseconds
// Convert days to milliseconds

// Specify the time-window to look for unmasked pixel


var days = 45;
var millis = ee.Number(days).multiply(1000*60*60*24)

var maxDiffFilter = ee.Filter.maxDifference({


difference: millis,
leftField: 'system:time_start',
rightField: 'system:time_start'
})
// We need a lessThanOrEquals filter to find all images after a given image
// This will compare the given image's timestamp against other images' timestamps
var lessEqFilter = ee.Filter.lessThanOrEquals({
leftField: 'system:time_start',
rightField: 'system:time_start'
})

// We need a greaterThanOrEquals filter to find all images before a given image


// This will compare the given image's timestamp against other images' timestamps
var greaterEqFilter = ee.Filter.greaterThanOrEquals({
leftField: 'system:time_start',
rightField: 'system:time_start'
})

// Apply the joins

// For the first join, we need to match all images that are after the given image.
// To do this we need to match 2 conditions
// 1. The resulting images must be within the specified time-window of target image
// 2. The target image's timestamp must be lesser than the timestamp of resulting images
// Combine two filters to match both these conditions
var filter1 = ee.Filter.and(maxDiffFilter, lessEqFilter)
// This join will find all images after, sorted in descending order
// This will gives us images so that closest is last
var join1 = ee.Join.saveAll({
matchesKey: 'after',
ordering: 'system:time_start',
ascending: false})

var join1Result = join1.apply({


primary: originalCollection,
secondary: originalCollection,
condition: filter1
})
// Each image now as a property called 'after' containing
// all images that come after it within the time-window
print(join1Result.first())

// Do the second join now to match all images within the time-window
// that come before each image
var filter2 = ee.Filter.and(maxDiffFilter, greaterEqFilter)
// This join will find all images before, sorted in ascending order
// This will gives us images so that closest is last
var join2 = ee.Join.saveAll({
matchesKey: 'before',
ordering: 'system:time_start',
ascending: true})

var join2Result = join2.apply({


primary: join1Result,
secondary: join1Result,
condition: filter2
})

// Each image now as a property called 'before' containing


// all images that come after it within the time-window
print(join2Result.first())

var joinedCol = join2Result;

// Do the interpolation

// We now write a function that will be used to interpolate all images


// This function takes an image and replaces the masked pixels
// with the interpolated value from before and after images.

var interpolateImages = function(image) {


var image = ee.Image(image);
// We get the list of before and after images from the image property
// Mosaic the images so we a before and after image with the closest unmasked pixel
var beforeImages = ee.List(image.get('before'))
var beforeMosaic = ee.ImageCollection.fromImages(beforeImages).mosaic()
var afterImages = ee.List(image.get('after'))
var afterMosaic = ee.ImageCollection.fromImages(afterImages).mosaic()

// Interpolation formula
// y = y1 + (y2-y1)*((t – t1) / (t2 – t1))
// y = interpolated image
// y1 = before image
// y2 = after image
// t = interpolation timestamp
// t1 = before image timestamp
// t2 = after image timestamp

// We first compute the ratio (t – t1) / (t2 – t1)

// Get image with before and after times


var t1 = beforeMosaic.select('timestamp').rename('t1')
var t2 = afterMosaic.select('timestamp').rename('t2')

var t = image.metadata('system:time_start').rename('t')

var timeImage = ee.Image.cat([t1, t2, t])

var timeRatio = timeImage.expression('(t - t1) / (t2 - t1)', {


't': timeImage.select('t'),
't1': timeImage.select('t1'),
't2': timeImage.select('t2'),
})
// You can replace timeRatio with a constant value 0.5
// if you wanted a simple average

// Compute an image with the interpolated image y


var interpolated = beforeMosaic
.add((afterMosaic.subtract(beforeMosaic).multiply(timeRatio)))
// Replace the masked pixels in the current image with the average value
var result = image.unmask(interpolated)
return result.copyProperties(image, ['system:time_start'])
}

// map() the function to interpolate all images in the collection


var interpolatedCol = ee.ImageCollection(joinedCol.map(interpolateImages))

// Once the interpolation are done, remove original images


// We keep only the generated interpolated images
var regularCol = interpolatedCol.filter(ee.Filter.eq('type', 'interpolated'))

// Display a time-series chart


var chart = ui.Chart.image.series({
imageCollection: regularCol.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
title: 'Regular NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#238b45'},
},
})
print(chart);

// SavatskyGolayFilter
// https://www.open-geocomputing.org/OpenEarthEngineLibrary/#.ImageCollection.SavatskyGolayFilter

// Use the default distanceFunction


var distanceFunction = function(infromedImage, estimationImage) {
return ee.Image.constant(
ee.Number(infromedImage.get('system:time_start'))
.subtract(
ee.Number(estimationImage.get('system:time_start')))
);
}

// Apply smoothing

var oeel=require('users/OEEL/lib:loadAll');

var order = 3;

var sgFilteredCol = oeel.ImageCollection.SavatskyGolayFilter(


regularCol,
maxDiffFilter,
distanceFunction,
order)

print(sgFilteredCol.first())
// Display a time-series chart
var chart = ui.Chart.image.series({
imageCollection: sgFilteredCol.select(['ndvi', 'd_0_ndvi'], ['ndvi', 'ndvi_sg']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20
}).setOptions({
lineWidth: 1,
title: 'NDVI Time Series',
interpolateNulls: false,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: '', format: 'YYYY-MM'},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '#66c2a4', lineDashStyle: [1, 1], pointSize: 2}, // Original NDVI
1: {color: '#238b45', lineWidth: 2 }, // Smoothed NDVI
},

})
print(chart);

// Let's visualize the NDVI time-series


Map.centerObject(geometry, 16);
var bbox = Map.getBounds({asGeoJSON: true});

var palette = ['#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a985


0'];
var ndviVis = {min:-0.2, max: 0.8, palette: palette}

var visualizeImage = function(image) {


return image.visualize(ndviVis).clip(bbox).selfMask()
}

var visCollectionRegular = regularCol.select('ndvi')


.map(visualizeImage)

var visualizeSgFiltered = sgFilteredCol.select('d_0_ndvi')


.map(visualizeImage)

Export.video.toDrive({
collection: visCollectionRegular,
description: 'Regular_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'regular',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Export.video.toDrive({
collection: visualizeSgFiltered,
description: 'Filtered_Time_Series',
folder: 'earthengine',
fileNamePrefix: 'sg_filtered',
framesPerSecond: 2,
dimensions: 800,
region: bbox})

Modelos de interface do usuário


Adicionando uma legenda discreta
Você pode querer adicionar uma legenda para uma imagem classificada à sua visualização de mapa em seu aplicativo. Aqui está um trecho de
código que mostra como você pode construí-lo usando os widgets de interface do usuário.

Criando uma legenda de mapa discreta

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FUI_Widgets_and_Apps%2FMap_Legend)
var classified = ee.Image("users/ujavalgandhi/e2e/bangalore_classified")
Map.centerObject(classified)
Map.addLayer(classified,
{min: 0, max: 3, palette: ['gray', 'brown', 'blue', 'green']}, '2019');

var legend = ui.Panel({style: {position: 'middle-right', padding: '8px 15px'}});

var makeRow = function(color, name) {


var colorBox = ui.Label({
style: {color: '#ffffff',
backgroundColor: color,
padding: '10px',
margin: '0 0 4px 0',
}
});
var description = ui.Label({
value: name,
style: {
margin: '0px 0 4px 6px',
}
});
return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')}
)};

var title = ui.Label({


value: 'Legend',
style: {fontWeight: 'bold',
fontSize: '16px',
margin: '0px 0 4px 0px'}});

legend.add(title);
legend.add(makeRow('gray','Built-up'))
legend.add(makeRow('brown','Bare Earth'))
legend.add(makeRow('blue','Water'))
legend.add(makeRow('green','Vegetation'))

Map.add(legend);

Adicionando uma legenda contínua


Se você estiver exibindo uma camada raster em seu aplicativo com uma paleta de cores, poderá usar a técnica a seguir para adicionar uma
barra de cores usando o trecho abaixo.
Criando uma Legenda Raster Contínua

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FUI_Widgets_and_Apps%2FColorbar_Legend)
var s2 = ee.ImageCollection("COPERNICUS/S2");
var admin2 = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level2");

var bangalore = admin2.filter(ee.Filter.eq('ADM2_NAME', 'Bangalore Urban'))


var geometry = bangalore.geometry()

var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))


.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))

var image = filtered.median();

// Calculate Normalized Difference Vegetation Index (NDVI)


// 'NIR' (B8) and 'RED' (B4)
var ndvi = image.normalizedDifference(['B8', 'B4']).rename(['ndvi']);

var palette = ['#d7191c','#fdae61','#ffffbf','#a6d96a','#1a9641']


var ndviVis = {min:0, max:0.5, palette: palette}
Map.centerObject(geometry, 12)
Map.addLayer(ndvi.clip(geometry), ndviVis, 'ndvi')

function createColorBar(titleText, palette, min, max) {


// Legend Title
var title = ui.Label({
value: titleText,
style: {fontWeight: 'bold', textAlign: 'center', stretch: 'horizontal'}});

// Colorbar
var legend = ui.Thumbnail({
image: ee.Image.pixelLonLat().select(0),
params: {
bbox: [0, 0, 1, 0.1],
dimensions: '200x20',
format: 'png',
min: 0, max: 1,
palette: palette},
style: {stretch: 'horizontal', margin: '8px 8px', maxHeight: '40px'},
});

// Legend Labels
var labels = ui.Panel({
widgets: [
ui.Label(min, {margin: '4px 10px',textAlign: 'left', stretch: 'horizontal'}),
ui.Label((min+max)/2, {margin: '4px 20px', textAlign: 'center', stretch: 'horizontal'}),
ui.Label(max, {margin: '4px 10px',textAlign: 'right', stretch: 'horizontal'})],
layout: ui.Panel.Layout.flow('horizontal')});

// Create a panel with all 3 widgets


var legendPanel = ui.Panel({
widgets: [title, legend, labels],
style: {position: 'bottom-center', padding: '8px 15px'}
})
return legendPanel
}
// Call the function to create a colorbar legend
var colorBar = createColorBar('NDVI Values', palette, 0, 0.5)

Map.add(colorBar)

Alterar aplicativo de IU de visualização


Um caso de uso comum para aplicativos do Earth Engine é exibir 2 imagens em um aplicativo de painel dividido. O script abaixo contém um
modelo simples que você pode usar para criar um aplicativo de painel dividido interativo. Aqui temos 2 objetos de mapa - leftMap e
rightMap . Você pode adicionar diferentes imagens a cada um dos mapas e os usuários poderão explorá-los lado a lado. [ Ver GIF animado ↗
(https://courses.spatialthoughts.com/images/end_to_end_gee/dust_storm_app.gif) ]
Um aplicativo de painel dividido que exibe imagens pré e pós-tempestade

Abrir no Editor de Código ↗ (https://code.earthengine.google.com/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FUI_Widgets_and_Apps%2FChange_Visualization_UI_App)
// On June 9, 2018 - A massive dust storm hit North India
// This example shows before and after imagery from Sentinel-2

// Display two visualizations of a map.

// Set a center and zoom level.


var center = {lon: 77.47, lat: 28.41, zoom: 12};

// Create two maps.


var leftMap = ui.Map(center);
var rightMap = ui.Map(center);

// Remove UI controls from both maps, but leave zoom control on the left map.
leftMap.setControlVisibility(false);
rightMap.setControlVisibility(false);
leftMap.setControlVisibility({zoomControl: true});

// Link them together.


var linker = new ui.Map.Linker([leftMap, rightMap]);

// Create a split panel with the two maps.


var splitPanel = ui.SplitPanel({
firstPanel: leftMap,
secondPanel: rightMap,
orientation: 'horizontal',
wipe: true
});

// Remove the default map from the root panel.


ui.root.clear();

// Add our split panel to the root panel.


ui.root.add(splitPanel);

var rgb_vis = {min: 0, max: 3200, bands: ['B4', 'B3', 'B2']};

var preStorm = ee.Image('COPERNICUS/S2/20180604T052651_20180604T053435_T43RGM')


var postStorm = ee.Image('COPERNICUS/S2/20180614T052651_20180614T053611_T43RGM')

// Add a RGB Landsat 8 layer to the left map.


leftMap.addLayer(preStorm, rgb_vis);
rightMap.addLayer(postStorm, rgb_vis);

Aplicativo de IU do NDVI Explorer


Os aplicativos Earth Engine permitem que você exiba gráficos interativos em resposta à ação do usuário. Este aplicativo mostra o padrão de
design comum para criar um aplicativo que permite ao usuário clicar em qualquer lugar no mapa e obter um gráfico usando o local clicado.
Aplicativo Explorador Global NDVI

Abrir no Editor de Código ↗ (https://code.earthengine.google.com/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-


GEE%3ASupplement%2FUI_Widgets_and_Apps%2FNDVI%20Explorer%20UI%20App)
var geometry = ee.Geometry.Point([77.5979, 13.00896]);
Map.centerObject(geometry, 10)

var s2 = ee.ImageCollection("COPERNICUS/S2")
var rgbVis = {
min: 0.0,
max: 0.3,
bands: ['B4', 'B3', 'B2'],
};

var palette = [
'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301'];

var ndviVis = {min:0, max:0.5, palette: palette }

// Write a function for Cloud masking


function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}

// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}

function getComposite(geometry) {
var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-12-31'))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
// Map the function over the collection
var withNdvi = filtered.map(addNDVI);

var composite = withNdvi.median()


return composite
}

// Create UI Elements
var title = ui.Label('Global NDVI Explorer');
title.style().set({
'position': 'top-center',
'fontSize': '24px'
});
var resultsPanel = ui.Panel();
var chartPanel = ui.Panel();
var selectionPanel = ui.Panel({
layout: ui.Panel.Layout.flow('horizontal'),
});

resultsPanel.style().set({
width: '400px',
position: 'bottom-right'
});

var resetPanel = ui.Panel();


resultsPanel.add(selectionPanel)
resultsPanel.add(chartPanel)
resultsPanel.add(resetPanel)

// Function to reset the app to initial state


var resetEverything = function() {
chartPanel.clear()
selectionPanel.clear()
resetPanel.clear()

Map.clear()

Map.add(title);
Map.add(resultsPanel)
Map.onClick(displayChart)
// Use the current viewport
var bounds = ee.Geometry.Rectangle(Map.getBounds())
var composite = getComposite(bounds)
Map.addLayer(composite, rgbVis, 'Sentinel-2 Composite')
var label = ui.Label('Click anywhere to see the chart')
resetPanel.add(label)

// Function to create and display NDVI time-series chart


var displayChart = function(point) {
resetPanel.clear()
var button = ui.Button({
label: 'Reset',
onClick: resetEverything})
resetPanel.add(button)
var geometry = ee.Geometry.Point(point['lon'], point['lat']);

var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-12-31'))
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 50))
.map(maskS2clouds)
.map(addNDVI)
.filter(ee.Filter.bounds(geometry))

var chart = ui.Chart.image.series({


imageCollection: filtered.select('ndvi'),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 20}).setOptions({
title: 'NDVI Time Series',
vAxis: {title: 'NDVI'},
hAxis: {title: 'Date', gridlines: {count: 12}}
})

chartPanel.clear()
selectionPanel.clear()
selectionPanel.add(ui.Label('Choose an image to display:'))
chartPanel.add(chart)

var addNdviLayer = function(dateString) {


var date = ee.Date.parse('YYYY-MM-dd', dateString)
var image = ee.Image(filtered.filter(ee.Filter.date(date, date.advance(1, 'day'))).mosaic())
Map.addLayer(image.select('ndvi'), ndviVis, 'NDVI Image -' + dateString)
}

filtered.aggregate_array('system:time_start').evaluate(function(ids) {
var dates = ee.List(ids).distinct().map(function(timestamp) {
return ee.Date(timestamp).format('YYYY-MM-dd')
})
dates.evaluate(function(dateList){
selectionPanel.add(ui.Select({
items: dateList,
onChange: addNdviLayer,
placeholder: 'Select a date'
}))
})

});

}
// Call the function to build the initial UI state.
resetEverything();

Compartilhamento de código e módulos de script


À medida que seu projeto do Earth Engine cresce, você precisa de uma maneira de organizar e compartilhar seu código para colaborar com
outras pessoas. Aprenderemos algumas práticas recomendadas sobre a melhor forma de configurar seu projeto no Earth Engine.

Compartilhando um único script


Para compartilhar seu código de um único script, você precisa usar o botão Get Link no editor de código. Conforme você clica no botão, o
conteúdo do seu editor de código é capturado e codificado em um URL. Quando você compartilha esse URL com alguém, essa pessoa pode
ver o mesmo conteúdo do seu editor de código. Essa é uma ótima maneira de enviar um instantâneo do seu código para que outras pessoas
possam reproduzir sua saída. Lembre-se de que os links do script são apenas instantâneos, se você alterar seu código após enviar o link para
alguém, eles não verão as atualizações.

Ao tentar enviar um link para alguém, NÃO clique no botão Copiar caminho do script . Enviar este caminho
para alguém NÃO dará acesso ao seu código. O caminho do script funciona apenas em repositórios
públicos ou compartilhados.

Compartilhamento de código usando o botão Obter link

Ao compartilhar o script usando Get Link , você também deve compartilhar quaisquer Ativos privados que você possa ter carregado e esteja
usando no script. Você pode compartilhar o ativo com um endereço de e-mail específico ou marcar a caixa Qualquer pessoa pode ler se quiser
que qualquer pessoa com o link do script possa acessá-lo. Deixar de fazer isso impedirá que outras pessoas executem seu script.
Compartilhamento de ativos carregados
Saiba mais na seção Links de script (https://developers.google.com/earth-engine/guides/playground#get-link) do Guia do usuário do Google
Earth Engine.
Compartilhamento de vários scripts
Se você deseja compartilhar uma coleção de scripts com outros usuários ou seus colaboradores, a melhor maneira é criar um novo
Repositório .

Criando Novo Repositório

Você pode colocar vários scripts no repositório e compartilhar o repositório com outros usuários. Você pode conceder a eles acesso de
Leitor ou Gravador para que possam visualizar/adicionar/modificar/excluir scripts nesse repositório. Se você quiser torná-lo legível pelo
Público , marque a opção Qualquer um pode ler . Você verá uma URL na forma de
https://code.earthengine.google.co.in/?accept_repo=... . Quando você compartilha esse URL com outros usuários e eles visitam
esse link, seu repositório será adicionado ao Editor de código deles na pasta Leitor ou Gravador , dependendo do acesso.

Criando Novo Repositório

Saiba mais na seção Gerenciador (https://developers.google.com/earth-engine/guides/playground#script-manager-scripts-tab) de scripts do


Guia do usuário do Google Earth Engine.

Compartilhamento de código entre scripts


Para um projeto grande, é preferível compartilhar as funções comumente usadas entre os scripts. Dessa forma, cada script não precisa
reimplementar o mesmo código. O Earth Engine permite isso usando módulos de script . Usando um objeto especial chamado exports ,
você pode expor uma função a outros scripts. Saiba mais na seção Módulos (https://developers.google.com/earth-
engine/guides/playground#script-modules) de script do Guia do usuário do Google Earth Engine.

Há muitos usuários do Earth Engine que compartilharam seus repositórios publicamente e escreveram módulos de script para executar uma
variedade de tarefas. Aqui está um exemplo de uso do grid módulo do users/gena/packages repositório para criar grades regularmente
espaçadas no Earth Engine.
Usando uma função de um módulo de script

Abrir no Editor de Código ↗ (https://code.earthengine.google.co.in/?accept_repo=users%2Fujavalgandhi%2FEnd-to-End-


GEE&scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3ASupplement%2FMiscellaneous%2FCode_Sharing_and_Script_Modules)

var karnataka = ee.FeatureCollection("users/ujavalgandhi/public/karnataka");


Map.addLayer(karnataka, {color: 'gray'}, 'State Boundary')
var bounds = karnataka.geometry().bounds()
var coords = ee.List(bounds.coordinates().get(0))
var xmin = ee.List(coords.get(0)).get(0)
var ymin = ee.List(coords.get(0)).get(1)
var xmax = ee.List(coords.get(2)).get(0)
var ymax = ee.List(coords.get(2)).get(1)
// source code for the grid package:
// https://code.earthengine.google.com/?accept_repo=users/gena/packages

// Import the module to our script using 'require'


var gridModule = require('users/gena/packages:grid')

// Now we can run any function from the module


// We try running the generateGrid function to create regularly spaced vector grid
// generateGrid(xmin, ymin, xmax, ymax, dx, dy, marginx, marginy, opt_proj)

var spacing = 0.5


var gridVector = gridModule.generateGrid(xmin, ymin, xmax, ymax, spacing, spacing, 0, 0)
Map.centerObject(gridVector)
Map.addLayer(gridVector, {color: 'blue'}, 'Grids')

Repositórios públicos úteis


Visite o Awesome Earth Engine (https://github.com/giswqs/Awesome-GEE) para ver uma lista selecionada de recursos do Google Earth
Engine.

Também temos algumas recomendações de alguns pacotes selecionados, que possuem funções muito úteis para torná-lo produtivo no Earth
Engine.

Pacotes de uso geral

eepackages (https://github.com/gee-community/ee-packages-py) : um conjunto de utilitários do Google Earth Engine mantido por


Gennadii Donchyts para Javascript e Python API.
geetools (https://github.com/fitoprincipe/geetools-code-editor/wiki) : Ferramentas para mascaramento de nuvem, processamento em
lote e muito mais
ee-palettes (https://github.com/gee-community/ee-palettes) : Módulo para geração de paletas de cores
spectral (https://github.com/awesome-spectral-indices/spectral) : Um módulo javascript que fornece uma lista de índices espectrais
prontos para uso para GEE.
eemont (https://github.com/davemlz/eemont) : pacote Python que fornece métodos utilitários para criar um código mais fluido por ser
amigável com o encadeamento de métodos Python.

Pacotes específicos de aplicativos

LEAF-Toolbox (https://github.com/rfernand387/LEAF-Toolbox/wiki) : aplicativo do Google Earth Engine que produz vários produtos
biofísicos de vegetação, incluindo índice de área foliar (LAI).
RivWidthCloud (https://github.com/seanyx/RivWidthCloudPaper) : Pacote para automatizar a extração da linha central e da largura
do rio para API Javascript e Python.
Projetos Guiados
Veja abaixo passo a passo passo a passo baseado em vídeo para implementar projetos do mundo real usando o Earth Engine. Você pode
continuar sua jornada de aprendizado implementando esses projetos para sua região de interesse após a aula.

Obtenha o código
1. Clique neste link (https://code.earthengine.google.co.in/?accept_repo=users/ujavalgandhi/End-to-End-Projects) para abrir o editor de
código do Google Earth Engine e adicionar o repositório à sua conta.
2. Se for bem-sucedido, você terá um novo repositório nomeado users/ujavalgandhi/End-to-End-Projects na guia Scripts na seção
Leitor .

Editor de código após adicionar o repositório de projetos

Se você não vir o repositório na seção Leitor , clique no botão Atualizar cache do repositório na guia Scripts e ele aparecerá.

Atualizar cache do repositório

Projeto 1: Monitoramento da Seca


Calculando o desvio de precipitação da média de 30 anos usando dados de precipitação em grade CHIRPS
(https://www.youtube.com/watch?

v=zHUCM3XLc6k&list=PLppGmFLhQ1HJ5VhW6BZfhPX6spUcTY7SR)

Iniciar projeto guiado (https://www.youtube.com/watch?v=zHUCM3XLc6k&list=PLppGmFLhQ1HJ5VhW6BZfhPX6spUcTY7SR)

Projeto 2: Mapeamento de Cheias


Mapeamento rápido de uma inundação usando Sentinel-1 SAR Data.

(https://www.youtube.com/watch?

v=jYsK9Y4ICrY&list=PLppGmFLhQ1HJzzKVS_4v8nBiXLYxAu100)

Iniciar projeto guiado (https://www.youtube.com/watch?v=jYsK9Y4ICrY&list=PLppGmFLhQ1HJzzKVS_4v8nBiXLYxAu100)

Projeto 3: Extraindo séries temporais


Extraindo uma série temporal NDVI de 10 anos em vários polígonos usando dados MODIS.

(https://www.youtube.com/watch?

v=LqSClCXrMl4&list=PLppGmFLhQ1HJV1CctqanQvXQI1JmqGDDD)

Iniciar projeto guiado (https://www.youtube.com/watch?v=LqSClCXrMl4&list=PLppGmFLhQ1HJV1CctqanQvXQI1JmqGDDD)

Projeto 4: Análise da Cobertura do Solo


Use produtos existentes de cobertura do solo para extrair classes específicas e computar estatísticas em muitas regiões.

(https://youtube.com/playlist?

list=PLppGmFLhQ1HLl0St2wiOPePr58sKu0Vh1)

Iniciar projeto guiado (https://youtube.com/playlist?list=PLppGmFLhQ1HLl0St2wiOPePr58sKu0Vh1)


Recursos de aprendizagem
Sensoriamento remoto baseado em nuvem com o Google Earth Engine: fundamentos e aplicativos (https://www.eefabook.org/) : um
livro gratuito e de acesso aberto com 55 capítulos que cobrem os fundamentos e aplicações do GEE. Também inclui vídeos do YouTube
resumindo cada capítulo.
Awesome Earth Engine (https://github.com/giswqs/Awesome-GEE) : uma lista selecionada de recursos do Google Earth Engine.

Créditos de dados
Sentinel-2 Nível-1C, Nível-2A e Sentinel-1 SAR GRD : Contém dados do Copernicus Sentinel.
TerraClimate: Clima mensal e balanço hídrico climático para superfícies terrestres globais, Universidade de Idaho : Abatzoglou, JT,
SZ Dobrowski, SA Parks, KC Hegewisch, 2018, Terraclimate, um conjunto de dados global de alta resolução do clima mensal e balanço
hídrico mensal de 1958- 2015, Scientific Data 5:170191, doi:10.1038/sdata.2017.191 (doi:10.1038/sdata.2017.191)
VIIRS Stray Light Corrigido Nighttime Day/Night Band Composites Versão 1 : CD Elvidge, KE Baugh, M. Zhizhin e F.-C. Hsu, “Por que
os dados do VIIRS são superiores ao DMSP para mapear as luzes noturnas,” Asia-Pacific Advanced Network 35, vol. 35, pág. 62, 2013.
FAO GAUL 500m: Camadas da Unidade Administrativa Global 2015, Unidades Administrativas de Segundo Nível : Origem dos
limites administrativos: Conjunto de dados das Camadas da Unidade Administrativa Global (GAUL), implementado pela FAO nos
projetos CountrySTAT e Sistema de Informação do Mercado Agrícola (AMIS).
CHIRPS Pentad: Climate Hazards Group InfraRed Precipitation with Station Data (versão 2.0 final) : Funk, Chris, Pete Peterson,
Martin Landsfeld, Diego Pedreros, James Verdin, Shraddhanand Shukla, Gregory Husak, James Rowland, Laura Harrison, Andrew
Hoell e Joel Michaelsen . “O clima ameaça a precipitação infravermelha com estações – um novo recorde ambiental para
monitoramento de extremos”. Scientific Data 2, 150066. doi:10.1038/sdata.2015.66 (doi:10.1038/sdata.2015.66) 2015.
MOD13Q1.006 Índices de Vegetação de Terra 16 dias Global 250m : Didan, K. (2015). MOD13Q1 Índices de vegetação MODIS/Terra
16 dias L3 Global 250m SIN Grid V006 [Conjunto de dados]. NASA EOSDIS Land Processa DAAC. Acessado em 2021-05-06 em
https://doi.org/10.5067/MODIS/MOD13Q1.006 (https://doi.org/10.5067/MODIS/MOD13Q1.006)
Natural Earth Urban Areas : Áreas Urbanas Shapefile. Transferido de Natural Earth. Dados de mapas vetoriais e raster gratuitos @
naturalearthdata.com.

Licença
O material do curso (texto, imagens, apresentações, vídeos) está licenciado sob uma Licença Creative Commons Atribuição 4.0 Internacional
(https://creativecommons.org/licenses/by/4.0/) .

O código (scripts, notebooks Jupyter) é licenciado sob a licença MIT. Para obter uma cópia, consulte https://opensource.org/licenses/MIT
(https://opensource.org/licenses/MIT)

Por favor, dê os devidos créditos ao autor original conforme abaixo:

Copyright © 2022 Ujaval Gandhi www.spatialthoughts.com (https://spatialthoughts.com)

Citação e Referência
Você pode citar os materiais do curso da seguinte forma

Gandhi, Ujaval, 2021. Curso completo do Google Earth Engine . Pensamentos Espaciais. https://courses.spatialthoughts.com/end-to-
end-gee.html (https://courses.spatialthoughts.com/end-to-end-gee.html)

Este curso é oferecido como uma aula on-line conduzida por instrutor. Visite Spatial Thoughts (https://spatialthoughts.com/events/) para
saber detalhes das próximas sessões.

Você também pode gostar