Files
RS4OSINT/docs/F6.html
2022-12-21 14:59:52 +00:00

2837 lines
258 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
<meta charset="utf-8">
<meta name="generator" content="quarto-1.2.269">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Google Earth Engine for OSINT - 7&nbsp; Advanced Topics</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
</style>
<script src="site_libs/quarto-nav/quarto-nav.js"></script>
<script src="site_libs/quarto-nav/headroom.min.js"></script>
<script src="site_libs/clipboard/clipboard.min.js"></script>
<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
<script src="site_libs/quarto-search/fuse.min.js"></script>
<script src="site_libs/quarto-search/quarto-search.js"></script>
<meta name="quarto:offset" content="./">
<link href="./lights.html" rel="next">
<link href="./F5.html" rel="prev">
<link href="./favicon.ico" rel="icon">
<script src="site_libs/quarto-html/quarto.js"></script>
<script src="site_libs/quarto-html/popper.min.js"></script>
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
<script src="site_libs/quarto-html/anchor.min.js"></script>
<link href="site_libs/quarto-html/tippy.css" rel="stylesheet">
<link href="site_libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-text-highlighting-styles">
<link href="site_libs/quarto-html/quarto-syntax-highlighting-dark.css" rel="stylesheet" class="quarto-color-scheme quarto-color-alternate" id="quarto-text-highlighting-styles">
<script src="site_libs/bootstrap/bootstrap.min.js"></script>
<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
<link href="site_libs/bootstrap/bootstrap.min.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-bootstrap" data-mode="light">
<link href="site_libs/bootstrap/bootstrap-dark.min.css" rel="stylesheet" class="quarto-color-scheme quarto-color-alternate" id="quarto-bootstrap" data-mode="dark">
<script id="quarto-search-options" type="application/json">{
"location": "sidebar",
"copy-button": false,
"collapse-after": 3,
"panel-placement": "start",
"type": "textbox",
"limit": 20,
"language": {
"search-no-results-text": "No results",
"search-matching-documents-text": "matching documents",
"search-copy-link-title": "Copy link to search",
"search-hide-matches-text": "Hide additional matches",
"search-more-match-text": "more match in this document",
"search-more-matches-text": "more matches in this document",
"search-clear-button-title": "Clear",
"search-detached-cancel-button-title": "Cancel",
"search-submit-button-title": "Submit"
}
}</script>
<script async="" src="https://www.googletagmanager.com/gtag/js?id=G-RK9ZLZQ6GL"></script>
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
</head>
<body class="nav-sidebar floating">
<div id="quarto-search-results"></div>
<header id="quarto-header" class="headroom fixed-top">
<nav class="quarto-secondary-nav" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
<div class="container-fluid d-flex justify-content-between">
<h1 class="quarto-secondary-nav-title"><span class="chapter-number">7</span>&nbsp; <span class="chapter-title">Advanced Topics</span></h1>
<button type="button" class="quarto-btn-toggle btn" aria-label="Show secondary navigation">
<i class="bi bi-chevron-right"></i>
</button>
</div>
</nav>
</header>
<!-- content -->
<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article">
<!-- sidebar -->
<nav id="quarto-sidebar" class="sidebar collapse sidebar-navigation floating overflow-auto">
<div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
<a href="./index.html" class="sidebar-logo-link">
<img src="./logo_white.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
</a>
<div class="sidebar-title mb-0 py-0">
<a href="./">Google Earth Engine for OSINT</a>
<div class="sidebar-tools-main tools-wide">
<a href="https://github.com/oballinger/GEE_OSINT/" title="Source Code" class="sidebar-tool px-1"><i class="bi bi-github"></i></a>
<a href="" title="Download" id="sidebar-tool-dropdown-0" class="sidebar-tool dropdown-toggle px-1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-download"></i></a>
<ul class="dropdown-menu" aria-labelledby="sidebar-tool-dropdown-0">
<li>
<a class="dropdown-item sidebar-tools-main-item" href="./Google-Earth-Engine-for-OSINT.pdf">
<i class="bi bi-bi-file-pdf pe-1"></i>
Download PDF
</a>
</li>
<li>
<a class="dropdown-item sidebar-tools-main-item" href="./Google-Earth-Engine-for-OSINT.epub">
<i class="bi bi-bi-journal pe-1"></i>
Download ePub
</a>
</li>
</ul>
<a href="" title="Share" id="sidebar-tool-dropdown-1" class="sidebar-tool dropdown-toggle px-1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-share"></i></a>
<ul class="dropdown-menu" aria-labelledby="sidebar-tool-dropdown-1">
<li>
<a class="dropdown-item sidebar-tools-main-item" href="https://twitter.com/intent/tweet?url=|url|">
<i class="bi bi-bi-twitter pe-1"></i>
Twitter
</a>
</li>
<li>
<a class="dropdown-item sidebar-tools-main-item" href="https://www.facebook.com/sharer/sharer.php?u=|url|">
<i class="bi bi-bi-facebook pe-1"></i>
Facebook
</a>
</li>
</ul>
<a href="" class="quarto-color-scheme-toggle sidebar-tool" onclick="window.quartoToggleColorScheme(); return false;" title="Toggle dark mode"><i class="bi"></i></a>
</div>
</div>
</div>
<div class="mt-2 flex-shrink-0 align-items-center">
<div class="sidebar-search">
<div id="quarto-search" class="" title="Search"></div>
</div>
</div>
<div class="sidebar-menu-container">
<ul class="list-unstyled mt-1">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./index.html" class="sidebar-item-text sidebar-link">Introduction</a>
</div>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-1" aria-expanded="true">Learning</a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-1" aria-expanded="true">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-1" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./ch1.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">Remote Sensing</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./ch2.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Data Acquisition</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./F1.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Getting Started</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./F2.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">4</span>&nbsp; <span class="chapter-title">Interpreting Images</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./F4.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">5</span>&nbsp; <span class="chapter-title">Interpreting Image Series</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./F5.html" class="sidebar-item-text sidebar-link"><span class="chapter-number">6</span>&nbsp; <span class="chapter-title">Vectors and Tables</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./F6.html" class="sidebar-item-text sidebar-link active"><span class="chapter-number">7</span>&nbsp; <span class="chapter-title">Advanced Topics</span></a>
</div>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" aria-expanded="true">Case Studies</a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" aria-expanded="true">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./lights.html" class="sidebar-item-text sidebar-link">War at Night</a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./refineries.html" class="sidebar-item-text sidebar-link">Refinery Detection</a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="./ships.html" class="sidebar-item-text sidebar-link">Refinery Detection</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<!-- margin-sidebar -->
<div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
<nav id="TOC" role="doc-toc" class="toc-active">
<h2 id="toc-title">Table of contents</h2>
<ul>
<li><a href="#advanced-raster-visualization" id="toc-advanced-raster-visualization" class="nav-link active" data-scroll-target="#advanced-raster-visualization"><span class="toc-section-number">8</span> Advanced Raster Visualization</a>
<ul class="collapse">
<li><a href="#palettes" id="toc-palettes" class="nav-link" data-scroll-target="#palettes"><span class="toc-section-number">8.1</span> Palettes</a></li>
<li><a href="#remapping-and-palettes" id="toc-remapping-and-palettes" class="nav-link" data-scroll-target="#remapping-and-palettes"><span class="toc-section-number">8.2</span> Remapping&nbsp;and Palettes</a></li>
<li><a href="#annotations" id="toc-annotations" class="nav-link" data-scroll-target="#annotations"><span class="toc-section-number">8.3</span> Annotations</a></li>
<li><a href="#animations" id="toc-animations" class="nav-link" data-scroll-target="#animations"><span class="toc-section-number">8.4</span> Animations</a></li>
<li><a href="#terrain-visualization" id="toc-terrain-visualization" class="nav-link" data-scroll-target="#terrain-visualization"><span class="toc-section-number">8.5</span> Terrain Visualization</a></li>
<li><a href="#synthesis" id="toc-synthesis" class="nav-link" data-scroll-target="#synthesis">Synthesis</a></li>
<li><a href="#conclusion" id="toc-conclusion" class="nav-link" data-scroll-target="#conclusion">Conclusion</a></li>
<li><a href="#references" id="toc-references" class="nav-link" data-scroll-target="#references">References</a></li>
</ul></li>
<li><a href="#collaborating-in-earth-engine-with-scripts-and-assets" id="toc-collaborating-in-earth-engine-with-scripts-and-assets" class="nav-link" data-scroll-target="#collaborating-in-earth-engine-with-scripts-and-assets"><span class="toc-section-number">9</span> Collaborating&nbsp;in Earth Engine with Scripts and Assets</a>
<ul class="collapse">
<li><a href="#using-get-link-to-share-a-script" id="toc-using-get-link-to-share-a-script" class="nav-link" data-scroll-target="#using-get-link-to-share-a-script"><span class="toc-section-number">9.1</span> Using Get Link to Share a Script</a></li>
<li><a href="#sharing-assets-from-your-asset-manager" id="toc-sharing-assets-from-your-asset-manager" class="nav-link" data-scroll-target="#sharing-assets-from-your-asset-manager"><span class="toc-section-number">9.2</span> Sharing Assets from Your Asset Manager</a></li>
<li><a href="#working-with-shared-repositories" id="toc-working-with-shared-repositories" class="nav-link" data-scroll-target="#working-with-shared-repositories"><span class="toc-section-number">9.3</span> Working with Shared Repositories</a></li>
<li><a href="#using-the-require-function-to-load-a-module" id="toc-using-the-require-function-to-load-a-module" class="nav-link" data-scroll-target="#using-the-require-function-to-load-a-module"><span class="toc-section-number">9.4</span> Using the Require Function to Load a Module</a></li>
<li><a href="#synthesis-1" id="toc-synthesis-1" class="nav-link" data-scroll-target="#synthesis-1">Synthesis</a></li>
<li><a href="#conclusion-1" id="toc-conclusion-1" class="nav-link" data-scroll-target="#conclusion-1">Conclusion</a></li>
<li><a href="#references-1" id="toc-references-1" class="nav-link" data-scroll-target="#references-1">References</a></li>
</ul></li>
<li><a href="#scaling-up-in-earth-engine" id="toc-scaling-up-in-earth-engine" class="nav-link" data-scroll-target="#scaling-up-in-earth-engine"><span class="toc-section-number">10</span> Scaling Up in Earth Engine</a>
<ul class="collapse">
<li><a href="#scaling-across-time" id="toc-scaling-across-time" class="nav-link" data-scroll-target="#scaling-across-time"><span class="toc-section-number">10.1</span> Scaling Across Time</a>
<ul class="collapse">
<li><a href="#scaling-up-with-earth-engine-operators-annual-daily-climate-data" id="toc-scaling-up-with-earth-engine-operators-annual-daily-climate-data" class="nav-link" data-scroll-target="#scaling-up-with-earth-engine-operators-annual-daily-climate-data"><span class="toc-section-number">10.1.1</span> 1.1. Scaling Up with Earth Engine Operators: Annual Daily Climate Data</a></li>
<li><a href="#scaling-across-time-by-batching-get-20-years-of-daily-climate-data" id="toc-scaling-across-time-by-batching-get-20-years-of-daily-climate-data" class="nav-link" data-scroll-target="#scaling-across-time-by-batching-get-20-years-of-daily-climate-data"><span class="toc-section-number">10.1.2</span> 1.2. Scaling Across Time by Batching: Get 20 Years of Daily Climate Data</a></li>
</ul></li>
<li><a href="#scaling-across-space-via-spatial-tiling" id="toc-scaling-across-space-via-spatial-tiling" class="nav-link" data-scroll-target="#scaling-across-space-via-spatial-tiling"><span class="toc-section-number">10.2</span> Scaling Across Space via Spatial Tiling</a>
<ul class="collapse">
<li><a href="#generate-a-cloud-free-satellite-composite-limits-to-on-the-fly-computing" id="toc-generate-a-cloud-free-satellite-composite-limits-to-on-the-fly-computing" class="nav-link" data-scroll-target="#generate-a-cloud-free-satellite-composite-limits-to-on-the-fly-computing"><span class="toc-section-number">10.2.1</span> 2.1.&nbsp;Generate a Cloud-Free Satellite Composite: Limits to On-the-Fly Computing</a></li>
<li><a href="#generate-a-regional-composite-through-spatial-tiling" id="toc-generate-a-regional-composite-through-spatial-tiling" class="nav-link" data-scroll-target="#generate-a-regional-composite-through-spatial-tiling"><span class="toc-section-number">10.2.2</span> 2.2.&nbsp;Generate a Regional Composite Through Spatial Tiling</a></li>
</ul></li>
<li><a href="#multistep-workflows-and-intermediate-assets" id="toc-multistep-workflows-and-intermediate-assets" class="nav-link" data-scroll-target="#multistep-workflows-and-intermediate-assets"><span class="toc-section-number">10.3</span> Multistep Workflows and Intermediate Assets</a></li>
<li><a href="#synthesis-2" id="toc-synthesis-2" class="nav-link" data-scroll-target="#synthesis-2">Synthesis</a></li>
<li><a href="#references-2" id="toc-references-2" class="nav-link" data-scroll-target="#references-2">References</a></li>
</ul></li>
<li><a href="#sharing-work-in-earth-engine-basic-ui-and-apps" id="toc-sharing-work-in-earth-engine-basic-ui-and-apps" class="nav-link" data-scroll-target="#sharing-work-in-earth-engine-basic-ui-and-apps"><span class="toc-section-number">11</span> Sharing Work in Earth Engine: Basic UI and Apps</a>
<ul class="collapse">
<li><a href="#building-an-earth-engine-app-using-javascript" id="toc-building-an-earth-engine-app-using-javascript" class="nav-link" data-scroll-target="#building-an-earth-engine-app-using-javascript"><span class="toc-section-number">11.1</span> Building an Earth Engine App Using JavaScript</a></li>
<li><a href="#publishing-an-earth-engine-app-from-the-code-editor" id="toc-publishing-an-earth-engine-app-from-the-code-editor" class="nav-link" data-scroll-target="#publishing-an-earth-engine-app-from-the-code-editor"><span class="toc-section-number">11.2</span> Publishing an Earth Engine App from the Code Editor</a></li>
<li><a href="#developing-an-earth-engine-app-using-geemap" id="toc-developing-an-earth-engine-app-using-geemap" class="nav-link" data-scroll-target="#developing-an-earth-engine-app-using-geemap"><span class="toc-section-number">11.3</span> Developing an Earth Engine App Using geemap</a></li>
</ul></li>
<li><a href="#import-the-nlcd-collection." id="toc-import-the-nlcd-collection." class="nav-link" data-scroll-target="#import-the-nlcd-collection."><span class="toc-section-number">12</span> Import the NLCD collection.</a></li>
<li><a href="#filter-the-collection-to-the-2019-product." id="toc-filter-the-collection-to-the-2019-product." class="nav-link" data-scroll-target="#filter-the-collection-to-the-2019-product."><span class="toc-section-number">13</span> Filter the collection to the 2019 product.</a></li>
<li><a href="#select-the-land-cover-band." id="toc-select-the-land-cover-band." class="nav-link" data-scroll-target="#select-the-land-cover-band."><span class="toc-section-number">14</span> Select the land cover band.</a></li>
<li><a href="#display-land-cover-on-the-map.map.addlayerlandcover-nlcd-2019" id="toc-display-land-cover-on-the-map.map.addlayerlandcover-nlcd-2019" class="nav-link" data-scroll-target="#display-land-cover-on-the-map.map.addlayerlandcover-nlcd-2019"><span class="toc-section-number">15</span> Display land cover on the map.Map.addLayer(landcover, {}, NLCD 2019)</a></li>
<li><a href="#get-an-nlcd-image-by-year." id="toc-get-an-nlcd-image-by-year." class="nav-link" data-scroll-target="#get-an-nlcd-image-by-year."><span class="toc-section-number">16</span> Get an NLCD image by year.</a></li>
<li><a href="#create-an-nlcd-image-collection-for-the-selected-years." id="toc-create-an-nlcd-image-collection-for-the-selected-years." class="nav-link" data-scroll-target="#create-an-nlcd-image-collection-for-the-selected-years."><span class="toc-section-number">17</span> Create an NLCD image collection for the selected years.</a>
<ul class="collapse">
<li><a href="#publishing-an-earth-engine-app-using-a-local-web-server" id="toc-publishing-an-earth-engine-app-using-a-local-web-server" class="nav-link" data-scroll-target="#publishing-an-earth-engine-app-using-a-local-web-server"><span class="toc-section-number">17.1</span> Publishing an Earth Engine App Using a Local Web Server</a></li>
<li><a href="#publish-an-earth-engine-app-using-cloud-platforms" id="toc-publish-an-earth-engine-app-using-cloud-platforms" class="nav-link" data-scroll-target="#publish-an-earth-engine-app-using-cloud-platforms"><span class="toc-section-number">17.2</span> Publish an Earth Engine App Using Cloud Platforms</a></li>
<li><a href="#synthesis-3" id="toc-synthesis-3" class="nav-link" data-scroll-target="#synthesis-3">Synthesis</a></li>
<li><a href="#conclusion-2" id="toc-conclusion-2" class="nav-link" data-scroll-target="#conclusion-2">Conclusion</a></li>
<li><a href="#references-3" id="toc-references-3" class="nav-link" data-scroll-target="#references-3">References</a></li>
</ul></li>
<li><a href="#use-install.packages-to-install-r-packages-from-the-cran-repository." id="toc-use-install.packages-to-install-r-packages-from-the-cran-repository." class="nav-link" data-scroll-target="#use-install.packages-to-install-r-packages-from-the-cran-repository."><span class="toc-section-number">18</span> Use install.packages to install R packages from the CRAN repository.</a>
<ul class="collapse">
<li><a href="#installing-rgee" id="toc-installing-rgee" class="nav-link" data-scroll-target="#installing-rgee"><span class="toc-section-number">18.1</span> Installing rgee&nbsp;</a></li>
</ul></li>
<li><a href="#initialize-just-earth-engine" id="toc-initialize-just-earth-engine" class="nav-link" data-scroll-target="#initialize-just-earth-engine"><span class="toc-section-number">19</span> Initialize just Earth Engine</a></li>
<li><a href="#initialize-earth-engine-and-gdee_initializedrive-true" id="toc-initialize-earth-engine-and-gdee_initializedrive-true" class="nav-link" data-scroll-target="#initialize-earth-engine-and-gdee_initializedrive-true"><span class="toc-section-number">20</span> Initialize Earth Engine and GDee_Initialize(drive = TRUE)</a>
<ul class="collapse">
<li><a href="#creating-a-3d-population-density-map-with-rgee-and-rayshader" id="toc-creating-a-3d-population-density-map-with-rgee-and-rayshader" class="nav-link" data-scroll-target="#creating-a-3d-population-density-map-with-rgee-and-rayshader"><span class="toc-section-number">20.1</span> Creating&nbsp;a 3D Population Density Map with rgee and rayshader</a></li>
<li><a href="#displaying-maps-interactively" id="toc-displaying-maps-interactively" class="nav-link" data-scroll-target="#displaying-maps-interactively"><span class="toc-section-number">20.2</span> Displaying Maps Interactively</a></li>
</ul></li>
<li><a href="#define-a-imagecollection" id="toc-define-a-imagecollection" class="nav-link" data-scroll-target="#define-a-imagecollection"><span class="toc-section-number">21</span> Define a ImageCollection</a></li>
<li><a href="#set-viz-paramsviz---list" id="toc-set-viz-paramsviz---list" class="nav-link" data-scroll-target="#set-viz-paramsviz---list"><span class="toc-section-number">22</span> Set viz paramsviz &lt;- list(</a></li>
<li><a href="#print-map-results-interactively" id="toc-print-map-results-interactively" class="nav-link" data-scroll-target="#print-map-results-interactively"><span class="toc-section-number">23</span> Print map results interactively</a></li>
<li><a href="#estimate-the-median-of-the-yearly-composites-from-2016-to-2020." id="toc-estimate-the-median-of-the-yearly-composites-from-2016-to-2020." class="nav-link" data-scroll-target="#estimate-the-median-of-the-yearly-composites-from-2016-to-2020."><span class="toc-section-number">24</span> Estimate the median of the yearly composites from 2016 to 2020.</a></li>
<li><a href="#estimate-the-median-of-the-winter-season-composites-from-2016-to-2020." id="toc-estimate-the-median-of-the-winter-season-composites-from-2016-to-2020." class="nav-link" data-scroll-target="#estimate-the-median-of-the-winter-season-composites-from-2016-to-2020."><span class="toc-section-number">25</span> Estimate the median of the winter season composites from 2016 to 2020.</a>
<ul class="collapse">
<li><a href="#synthesis-4" id="toc-synthesis-4" class="nav-link" data-scroll-target="#synthesis-4">Synthesis</a></li>
<li><a href="#conclusion-3" id="toc-conclusion-3" class="nav-link" data-scroll-target="#conclusion-3">Conclusion</a></li>
<li><a href="#references-4" id="toc-references-4" class="nav-link" data-scroll-target="#references-4">References</a></li>
</ul></li>
</ul>
<div class="toc-actions"><div><i class="bi bi-github"></i></div><div class="action-links"><p><a href="https://github.com/oballinger/GEE_OSINT/edit/main/F6.qmd" class="toc-action">Edit this page</a></p></div></div></nav>
</div>
<!-- main -->
<main class="content" id="quarto-document-content">
<header id="title-block-header" class="quarto-title-block default">
<div class="quarto-title">
<h1 class="title d-none d-lg-block"><span class="chapter-number">7</span>&nbsp; <span class="chapter-title">Advanced Topics</span></h1>
</div>
<div class="quarto-title-meta">
</div>
</header>
<p>Although you now know the most basic fundamentals of Earth Engine, there is still much more that can be done. The Part presents some advanced topics that can help expand your skill set for doing larger and more complex projects. These include tools for sharing code among users, scaling up with efficient project design, creating apps for non-expert users, and combining R with other information processing platforms.</p>
<section id="advanced-raster-visualization" class="level1" data-number="8">
<h1 data-number="8"><span class="header-section-number">8</span> Advanced Raster Visualization</h1>
<div class="callout-tip callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Chapter Information
</div>
</div>
<div class="callout-body-container callout-body">
<section id="author" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="author">Author</h2>
<p>Gennadii Donchyts, Fedor Baart</p>
</section>
<section id="overview" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="overview">Overview</h2>
<p>This chapter should help users of Earth Engine to better understand raster data by applying visualization algorithms such as hillshading, hill shadows, and custom colormaps. We will also learn how image collection datasets can be explored by animating them as well as by annotating with text labels, using, for example, attributes of images or values queried from images.</p>
</section>
<section id="learning-outcomes" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="learning-outcomes">Learning Outcomes</h2>
<ul>
<li>Understanding why perceptually uniform colormaps are better to present data and using them efficiently for raster visualization.</li>
<li>Using palettes with images before and after remapping values.</li>
<li>Adding text annotations when visualizing images or features.</li>
<li>Animating image collections in multiple ways (animated GIFs, exporting video clips, interactive animations with UI controls).</li>
<li>Adding hillshading and shadows to help visualize raster datasets.</li>
</ul>
</section>
<section id="assumes-you-know-how-to" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="assumes-you-know-how-to">Assumes you know how to:</h2>
<ul>
<li>Import images and image collections, filter, and visualize (Part F1).</li>
<li>Write a function and map&nbsp;it over an ImageCollection&nbsp;(Chap. F4.0).</li>
<li>Inspect an Image&nbsp;and an ImageCollection, as well as their properties&nbsp;(Chap. F4.1).</li>
</ul>
</section>
</div>
</div>
<section id="introduction" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="introduction">Introduction</h2>
<p>Visualization is the step to transform data into a visual representation. You make a visualization as soon as you add your first layer to your map in Google Earth Engine. Sometimes you just want to have a first look at a dataset during the exploration phase. But as you move towards the dissemination phase, where you want to spread your results, it is good to think about a more structured approach to visualization. A typical workflow for creating visualization consists of the following steps:</p>
<ul>
<li>Defining the story (what is the message?)</li>
<li>Finding inspiration (for example by making a moodboard)</li>
<li>Choosing a canvas/medium (here, this is the Earth Engine map canvas)</li>
<li>Choosing datasets (co-visualized or combined using derived indicators)</li>
<li>Data preparation (interpolating in time and space, filtering/mapping/reducing)</li>
<li>Converting data into visual elements (shape and color)</li>
<li>Adding annotations and interactivity (labels, scales, legend, zoom, time slider)</li>
</ul>
<p>A good standard work on all the choices that one can make while creating a visualization is provided by the Grammar of Graphics (GoG) by Wilkinson (1999). It was the inspiration behind many modern visualization libraries (ggplot, vega). The main concept is that you can subdivide your visualization into several aspects.</p>
<p>In this chapter, we will cover several aspects mentioned in the Grammar of Graphics to convert (raster) data into visual elements. The accurate representation of data is essential in science communication. However, color maps that visually distort data through uneven color gradients or are unreadable to those with color-vision deficiency remain prevalent in science (Crameri, 2020). You will also learn how to add annotation text and symbology, while improving your visualizations by mixing images with hillshading as you explore some of the amazing datasets that have been collected in recent years in Earth Engine.</p>
</section>
<section id="palettes" class="level2" data-number="8.1">
<h2 data-number="8.1" class="anchored" data-anchor-id="palettes"><span class="header-section-number">8.1</span> Palettes</h2>
<p>In this section we will explore examples of colormaps to visualize raster data. Colormaps translate values to colors for display on a map. This requires a set of colors (referred to as a “palette” in Earth Engine) and a range of values to map (specified by the min and max values in the visualization parameters).</p>
<p>There are multiple types of colormaps, each used for a different purpose. These include the following:</p>
<p>Sequential:&nbsp;These are probably the most commonly used colormaps, and are useful for ordinal, interval, and ratio&nbsp;data. Also referred to as a linear colormap, a sequential colormap looks like the viridis&nbsp;colormap (Fig. F6.0.1) from matplotlib. It is popular because it is a perceptual uniform colormap, where an equal interval in values is mapped to an equal interval in the perceptual colorspace. If you have a ratio variable where zero means nothing, you can use a sequential colormap starting at white, transparent, or, when you have a black background, at black—for example, the turku&nbsp;colormap from Crameri&nbsp;(Fig. F6.0.1). You can use this for variables like population count or gross domestic product.</p>
<p>Diverging: This type of colormap is used for visualizing data where you have positive and negative values and where zero has a meaning. Later in this tutorial, we will use the balance&nbsp;colormap from the cmocean&nbsp;package (Fig. F6.0.1) to show temperature change.</p>
<p>Circular: Some variables are periodic, returning to the same value after a period of time. For example, the season, angle, and time of day are typically represented as circular variables. For variables like this, a circular colormap is designed to represent the first and last values with the same color. An example is the circular cet-c2&nbsp;colormap (Fig. F6.0.1) from the colorcet package.</p>
<p>Semantic: Some colormaps do not map to arbitrary colors but choose colors that provide meaning. We refer to these as semantic&nbsp;colormaps. Later in this tutorial, we will use the ice colormap (Fig. F6.0.1) from the cmocean&nbsp;package for our ice example.</p>
<p><img src="F6/image40.png" class="img-fluid"></p>
<p>Fig. F6.0.1&nbsp;Examples of colormaps from a variety of packages: viridis&nbsp;from matplotlib, turku&nbsp;from Crameri, balance&nbsp;from cmocean, cet-c2&nbsp;from colorcet and ice&nbsp;from cmocean</p>
<p>Popular sources of colormaps include:</p>
<ul>
<li>cmocean&nbsp;(semantic perceptual uniform colormaps for geophysical applications)</li>
<li>colorcet (set of perceptual colormaps with varying colors and saturation)</li>
<li>cpt-city (comprehensive overview of colormaps,</li>
<li>colorbrewer (colormaps with variety of colors)</li>
<li>Crameri (stylish colormaps for dark and light themes)</li>
</ul>
<p>Our first example&nbsp;in this section applies a diverging&nbsp;colormap to temperature.</p>
<p>// Load the ERA5 reanalysis monthly means.<br>
var&nbsp;era5 = ee.ImageCollection(ECMWF/ERA5_LAND/MONTHLY);</p>
<p>// Load the palettes package.<br>
var&nbsp;palettes = require(users/gena/packages:palettes);</p>
<p>// Select temperature near ground.<br>
era5 = era5.select(temperature_2m);</p>
<p>Now we can visualize the data. Here we have a temperature difference. That means that zero has a special meaning. By using a divergent colormap we can give zero the color white, which denotes that there is no significant difference. Here we will use the colormap Balance&nbsp;from the cmocean&nbsp;package. The color red is associated with warmth, and the color blue is associated with cold. We will choose the minimum and maximum values for the palette to be symmetric around zero (-2, 2) so that white appears in the correct place. &nbsp;For comparison we also visualize the data with a simple [blue, white, red]&nbsp;palette. As you can see (Fig. F6.0.2), the Balance&nbsp;colormap has a more elegant and professional feel to it, because it uses a perceptual uniform palette and both saturation and value.</p>
<p>// Choose a diverging colormap for anomalies.<br>
var&nbsp;balancePalette = palettes.cmocean.Balance[7];<br>
var&nbsp;threeColorPalette = [blue, white, red];</p>
<p>// Show the palette in the Inspector window.<br>
palettes.showPalette(temperature anomaly, balancePalette);<br>
palettes.showPalette(temperature anomaly, threeColorPalette);</p>
<p>// Select 2 time windows of 10 years.<br>
var&nbsp;era5_1980 = era5.filterDate(1981-01-01, 1991-01-01).mean();<br>
var&nbsp;era5_2010 = era5.filterDate(2011-01-01, 2020-01-01).mean();</p>
<p>// Compute the temperature change.<br>
var&nbsp;era5_diff = era5_2010.subtract(era5_1980);</p>
<p>// Show it on the map.<br>
Map.addLayer(era5_diff, {<br>
&nbsp; &nbsp;palette: threeColorPalette,<br>
&nbsp; &nbsp;min: -2,<br>
&nbsp; &nbsp;max: 2}, Blue White Red palette);</p>
<p>Map.addLayer(era5_diff, {<br>
&nbsp; &nbsp;palette: balancePalette,<br>
&nbsp; &nbsp;min: -2,<br>
&nbsp; &nbsp;max: 2}, Balance palette);</p>
<p><img src="F6/image66.png" class="img-fluid"><img src="F6/image53.png" class="img-fluid"></p>
<p>Fig. F6.0.2&nbsp;Temperature difference of ERA5 (20112020, 19811990) using the balance&nbsp;colormap from cmocean&nbsp;(right) versus a basic blue-white-red&nbsp;colormap (left)</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60a. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Our second example in this&nbsp;section focuses on visualizing a region of the Antarctic, the Thwaites Glacier. This is one of the fast-flowing glaciers that causes concern because it loses so much mass that it causes the sea level to rise. If we want to visualize this region, we have a challenge. The Antarctic region is in the dark for four to five months each winter. That means that we cant use optical images to see the ice flowing into the sea. We therefore will use radar images. Here we will use a semantic&nbsp;colormap to denote the meaning of the radar images.</p>
<p>Lets start by importing the dataset of radar images. We will use the images from the Sentinel-1 constellation of the Copernicus program. This satellite uses a C-band synthetic-aperture radar and has near-polar coverage. The radar senses images using a polarity for the sender and receiver. The collection has images of four different possible combinations of sender/receiver polarity pairs. The image that well use has a band of the Horizontal/Horizontal polarity (HH).</p>
<p>// An image of the Thwaites glacier.<br>
var&nbsp;imageId =COPERNICUS/S1_GRD/S1B_EW_GRDM_1SSH_20211216T041925_20211216T042029_030045_03965B_AF0A;</p>
<p>// Look it up and select the HH band.<br>
var&nbsp;img = ee.Image(imageId).select(HH);</p>
<p>For the next step, we will use the palette library. We will stylize the radar images to look like optical images, so that viewers can contrast ice and sea ice from water (Lhermitte, 2020). We will use the Ice&nbsp;colormap from the cmocean&nbsp;package (Thyng, 2016).</p>
<p>// Use the palette library.<br>
var&nbsp;palettes = require(users/gena/packages:palettes);</p>
<p>// Access the ice palette.<br>
var&nbsp;icePalette = palettes.cmocean.Ice[7];</p>
<p>// Show it in the console.<br>
palettes.showPalette(Ice, icePalette);</p>
<p>// Use &nbsp;it to visualize the radar data.<br>
Map.addLayer(img, {<br>
&nbsp; &nbsp;palette: icePalette,<br>
&nbsp; &nbsp;min: -15,<br>
&nbsp; &nbsp;max: 1}, Sentinel-1 radar);</p>
<p>// Zoom to the grounding line of the Thwaites Glacier.<br>
Map.centerObject(ee.Geometry.Point([-105.45882094907664, -&nbsp; &nbsp;74.90419580705336]), 8);</p>
<p>If you zoom in (F6.0.3) you can see how long cracks have recently appeared near the pinning&nbsp;point (a peak in the bathymetry that functions as a buttress, see Wild, 2022) of the glacier.</p>
<p><img src="F6/image13.png" class="img-fluid"></p>
<p>Fig. F6.0.3.&nbsp;Ice observed in Antarctica by the Sentinel-1 satellite. The image is rendered using the ice&nbsp;color palette stretched to backscatter amplitude values [-15; 1].</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60b. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
</section>
<section id="remapping-and-palettes" class="level2" data-number="8.2">
<h2 data-number="8.2" class="anchored" data-anchor-id="remapping-and-palettes"><span class="header-section-number">8.2</span> Remapping&nbsp;and Palettes</h2>
<p>Classified rasters&nbsp;in Earth Engine have metadata attached that can help with analysis and visualization. This includes lists of the names, values, and colors associated with class. These are used as the default color palette for drawing a classification, as seen next. The USGS National Land Cover Database (NLCD) is one such example. Lets access the NLCD dataset, name it nlcd, and view it (Fig. F6.0.4) with its built-in palette.</p>
<p>// Advanced remapping using NLCD.<br>
// Import NLCD.<br>
var&nbsp;nlcd = ee.ImageCollection(USGS/NLCD_RELEASES/2016_REL);</p>
<p>// Use Filter to select the 2016 dataset.<br>
var&nbsp;nlcd2016 = nlcd.filter(ee.Filter.eq(system:index, 2016))<br>
&nbsp; &nbsp;.first();</p>
<p>// Select the land cover band.<br>
var&nbsp;landcover = nlcd2016.select(landcover);</p>
<p>// Map the NLCD land cover.<br>
Map.addLayer(landcover, null, NLCD Landcover);</p>
<p><img src="F6/image54.png" class="img-fluid"></p>
<p>Fig. F6.0.4&nbsp;The NLCD visualized with default colors for each class</p>
<p>But suppose you want to change the display palette. For example, you might want to have multiple classes displayed using the same color, or use different colors for some classes. Lets try having all three urban classes display as dark red (ab0000).</p>
<p>// Now suppose we want to change the color palette.<br>
var&nbsp;newPalette = [466b9f, d1def8, dec5c5,&nbsp; &nbsp;ab0000, ab0000, ab0000,&nbsp; &nbsp;b3ac9f, 68ab5f, 1c5f2c,&nbsp; &nbsp;b5c58f, af963c, ccb879,&nbsp; &nbsp;dfdfc2, d1d182, a3cc51,&nbsp; &nbsp;82ba9e, dcd939, ab6c28,&nbsp; &nbsp;b8d9eb, 6c9fb8<br>
];</p>
<p>// Try mapping with the new color palette.<br>
Map.addLayer(landcover, {<br>
&nbsp; &nbsp;palette: newPalette<br>
}, NLCD New Palette);</p>
<p>However, if you map this, you will see an unexpected result (Fig. F6.0.5).</p>
<p><img src="F6/image64.png" class="img-fluid"></p>
<p>Fig. F6.0.5&nbsp;Applying a new palette to a multi-class layer has some unexpected results</p>
<p>This is because the numeric codes for the different classes are not sequential. Thus, Earth Engine stretches the given palette across the whole range of values and produces an unexpected color palette. To fix this issue, we will create a new index for the class values so that they are sequential.</p>
<p>// Extract the class values and save them as a list.<br>
var&nbsp;values = ee.List(landcover.get(landcover_class_values));</p>
<p>// Print the class values to console.<br>
print(raw class values, values);</p>
<p>// Determine the maximum index value<br>
var&nbsp;maxIndex = values.size().subtract(1);</p>
<p>// Create a new index for the remap<br>
var&nbsp;indexes = ee.List.sequence(0, maxIndex);</p>
<p>// Print the updated class values to console.<br>
print(updated class values, indexes);</p>
<p>// Remap NLCD and display it in the map.<br>
var&nbsp;colorized = landcover.remap(values, indexes)<br>
&nbsp; &nbsp;.visualize({<br>
&nbsp; &nbsp; &nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp; &nbsp; &nbsp;max: maxIndex,<br>
&nbsp; &nbsp; &nbsp; &nbsp;palette: newPalette<br>
&nbsp; &nbsp;});<br>
Map.addLayer(colorized, {}, NLCD Remapped Colors);</p>
<p>Using this remapping approach, we can properly visualize the new color palette (Fig. F6.0.6).</p>
<p><img src="F6/image57.png" class="img-fluid"></p>
<p>Fig. F6.0.6&nbsp;Expected results of the new color palette. All urban areas are now correctly showing as dark red and the other land cover types remain their original color.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60c. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
</section>
<section id="annotations" class="level2" data-number="8.3">
<h2 data-number="8.3" class="anchored" data-anchor-id="annotations"><span class="header-section-number">8.3</span> Annotations</h2>
<p>Annotations&nbsp;are the way to visualize data on maps to provide additional information about raster values or any other data relevant to the context. In this case, this additional information is usually shown as geometries, text labels, diagrams, or other visual elements. Some annotations in Earth Engine can be added by making use of the ui&nbsp;portion of the Earth Engine API, resulting in graphical user interface elements such as labels or charts added on top of the map. However, it is frequently useful to render annotations as a part of images, such as by visualizing various image properties or to highlight specific areas.</p>
<p>In many cases, these annotations can be mixed with output images&nbsp;generated outside of Earth Engine, for example, by post-processing exported images using Python libraries or by annotating using GIS applications such as QGIS or ArcGIS. However, annotations could also be also very useful to highlight and/or label specific areas directly within the Code Editor. Earth Engine provides a sufficiently rich API to turn vector features and geometries into raster images which can serve as annotations. We recommend checking the ee.FeatureCollection.style&nbsp;function in the Earth Engine documentation to learn how geometries can be rendered.</p>
<p>For&nbsp; textual annotation, we will make use of an external package users/gena/packages:text&nbsp;that provides a way to render strings into raster images directly using the Earth Engine raster API. It is beyond the scope of the current tutorials to explain the implementation of this package, but internally this package makes use of bitmap fonts&nbsp;which are ingested into Earth Engine as raster assets and are used to turn every character of a provided string into image glyphs, which are then translated to desired coordinates.</p>
<p>The API of the text&nbsp;package includes the following mandatory and optional arguments:</p>
<p>/**<br>
* Draws a string as a raster image at a given point.<br>
*<br>
* <span class="citation" data-cites="param">(<a href="#ref-param" role="doc-biblioref"><strong>param?</strong></a>)</span> {string} str - string to draw<br>
* <span class="citation" data-cites="param">(<a href="#ref-param" role="doc-biblioref"><strong>param?</strong></a>)</span> {ee.Geometry} point - location the the string will be drawn<br>
* <span class="citation" data-cites="param">(<a href="#ref-param" role="doc-biblioref"><strong>param?</strong></a>)</span> {{string, Object}} options - optional properties used to style text<br>
*<br>
* The options dictionary may include one or more of the following:<br>
* &nbsp; &nbsp; fontSize &nbsp; &nbsp; &nbsp; - 16|18|24|32 - the size of the font (default: 16)<br>
* &nbsp; &nbsp; fontType &nbsp; &nbsp; &nbsp; - Arial|Consolas - the type of the font (default: Arial)<br>
* &nbsp; &nbsp; alignX &nbsp; &nbsp; &nbsp; &nbsp; - left|center|right (default: left)<br>
* &nbsp; &nbsp; alignY &nbsp; &nbsp; &nbsp; &nbsp; - top|center|bottom (default: top)<br>
* &nbsp; &nbsp; textColor &nbsp; &nbsp; &nbsp;- text color string (default: ffffff - white)<br>
* &nbsp; &nbsp; textOpacity &nbsp; &nbsp;- 0-1, opacity of the text (default: 0.9)<br>
* &nbsp; &nbsp; textWidth &nbsp; &nbsp; &nbsp;- width of the text (default: 1)<br>
* &nbsp; &nbsp; outlineColor &nbsp; - text outline color string (default: 000000 - black)<br>
* &nbsp; &nbsp; outlineOpacity - 0-1, opacity of the text outline (default: 0.4)<br>
* &nbsp; &nbsp; outlineWidth &nbsp; - width of the text outlines (default: 0)<br>
*/</p>
<p>To demonstrate how to use this API, lets render a simple Hello World!&nbsp;text string placed at the map center using default text parameters. The code for this will be:</p>
<p>// Include the text package.<br>
var&nbsp;text = require(users/gena/packages:text);</p>
<p>// Configure map (change center and map type).<br>
Map.setCenter(0, 0, 10);<br>
Map.setOptions(HYBRID);</p>
<p>// Draw text string and add to map.<br>
var&nbsp;pt = Map.getCenter();<br>
var&nbsp;scale = Map.getScale();<br>
var&nbsp;image = text.draw(Hello World!, pt, scale);<br>
Map.addLayer(image);</p>
<p>Running the above script will generate a new image containing the Hello World!&nbsp;string placed in the map center. Notice that before calling the text.draw() function we configure the map to be centered at specific coordinates (0,0) and zoom level 10 because map parameters such as center and scale are passed as arguments to&nbsp;that text.draw() function. This ensures that the resulting image containing string characters is scaled properly.</p>
<p>When exporting images containing rendered text strings, it is important to use proper scale to avoid distorted text strings that are difficult to read, depending on the selected font size, as shown in Fig. 6.0.7.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60d. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p><img src="F6/image39.png" class="img-fluid"><img src="F6/image74.png" class="img-fluid"><img src="F6/image44.png" class="img-fluid"></p>
<p>Fig. 6.0.7&nbsp;Results of the text.draw&nbsp;call, scaled to 1x: var&nbsp;scale = Map.getScale()<em>1;&nbsp;(left), 2x: var&nbsp;scale = Map.getScale()</em>2;&nbsp;(center), and 0.5x: var&nbsp;scale = Map.getScale()*0.5;&nbsp;(right)</p>
<p>These&nbsp;artifacts can be avoided to some extent by specifying a larger font size (e.g., 32). However, it is better to render text at the native 1:1 scale to achieve best results. The same applies to the text color and outline: They may need to be adjusted to achieve the best result. Usually, text needs to be rendered using colors that have opposite brightness and colors when compared to the surrounding background. Notice that in the above example, the map was configured to have a dark background (HYBRID) to ensure that the white text (default color) would be visible. Multiple parameters listed in the above API documentation can be used to adjust text rendering. For example, lets switch font size, font type, text, and outline parameters to render the same string, as below. Replace the existing one-line text.draw&nbsp;call in your script with the following code, and then run it again to see the difference (Fig. F6.0.8):</p>
<p>var&nbsp;image = text.draw(Hello World!, pt, scale, {<br>
&nbsp; &nbsp;fontSize: 32,<br>
&nbsp; &nbsp;fontType: Consolas,<br>
&nbsp; &nbsp;textColor: black,<br>
&nbsp; &nbsp;outlineColor: white,<br>
&nbsp; &nbsp;outlineWidth: 1,<br>
&nbsp; &nbsp;outlineOpacity: 0.8<br>
});</p>
<p>// Add the text image to the map.<br>
Map.addLayer(image);</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60e. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p><img src="F6/image33.png" class="img-fluid"></p>
<p>Fig. 6.0.8&nbsp;Rendering text with adjusted parameters (font type: Consolas, fontSize: 32, textColor: black, outlineWidth: 1, outlineColor: white, outlineOpacity: 0.8)</p>
<p>Of course, non-optional parameters such as pt&nbsp;and scale, as well as the text string, do not have to be hard-coded in the script; instead, they can be acquired by the code using, for example, properties coming from a FeatureCollection. Lets demonstrate this by showing the cloudiness of Landsat 8 images as text labels rendered in the center of every image. In addition to annotating every image with a cloudiness text string, we will also draw yellow outlines to indicate image boundaries. For convenience, we can also define the code to annotate an image as a function. We will then map that function (as described in Chap. F4.0) over the filtered ImageCollection. The code is as follows:</p>
<p>var&nbsp;text = require(users/gena/packages:text);</p>
<p>var&nbsp;geometry = ee.Geometry.Polygon(<br>
&nbsp; &nbsp;[<br>
&nbsp; &nbsp; &nbsp; &nbsp;[<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-109.248, 43.3913],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-109.248, 33.2689],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-86.5283, 33.2689],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-86.5283, 43.3913]<br>
&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;], null, false);</p>
<p>Map.centerObject(geometry, 6);</p>
<p>function&nbsp;annotate(image) {&nbsp; &nbsp;// Annotates an image by adding outline border and cloudiness&nbsp; &nbsp;// Cloudiness is shown as a text string rendered at the image center.&nbsp; &nbsp;// Add an edge around the image.&nbsp; &nbsp;var&nbsp;edge = ee.FeatureCollection([image])<br>
&nbsp; &nbsp; &nbsp; &nbsp;.style({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;color: cccc00cc,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fillColor: 00000000&nbsp; &nbsp; &nbsp; &nbsp;});&nbsp; &nbsp;// Draw cloudiness as text.&nbsp; &nbsp;var&nbsp;props = {<br>
&nbsp; &nbsp; &nbsp; &nbsp;textColor: 0000aa,<br>
&nbsp; &nbsp; &nbsp; &nbsp;outlineColor: ffffff,<br>
&nbsp; &nbsp; &nbsp; &nbsp;outlineWidth: 2,<br>
&nbsp; &nbsp; &nbsp; &nbsp;outlineOpacity: 0.6,<br>
&nbsp; &nbsp; &nbsp; &nbsp;fontSize: 24,<br>
&nbsp; &nbsp; &nbsp; &nbsp;fontType: Consolas&nbsp; &nbsp;};&nbsp; &nbsp;var&nbsp;center = image.geometry().centroid(1);&nbsp; &nbsp;var&nbsp;str = ee.Number(image.get(CLOUD_COVER)).format(%.2f);&nbsp; &nbsp;var&nbsp;scale = Map.getScale();&nbsp; &nbsp;var&nbsp;textCloudiness = text.draw(str, center, scale, props);&nbsp; &nbsp;// Shift left 25 pixels.&nbsp; &nbsp;textCloudiness = textCloudiness<br>
&nbsp; &nbsp; &nbsp; &nbsp;.translate(-scale * 25, 0, meters, EPSG:3857);&nbsp; &nbsp;// Merge results.&nbsp; &nbsp;return&nbsp;ee.ImageCollection([edge, textCloudiness]).mosaic();<br>
}</p>
<p>// Select images.<br>
var&nbsp;images = ee.ImageCollection(LANDSAT/LC08/C02/T1_RT_TOA)<br>
&nbsp; &nbsp;.select([5, 4, 2])<br>
&nbsp; &nbsp;.filterBounds(geometry)<br>
&nbsp; &nbsp;.filterDate(2018-01-01, 2018-01-7);</p>
<p>// dim background.<br>
Map.addLayer(ee.Image(1), {<br>
&nbsp; &nbsp;palette: [black]<br>
}, black, true, 0.5);</p>
<p>// Show images.<br>
Map.addLayer(images, {<br>
&nbsp; &nbsp;min: 0.05,<br>
&nbsp; &nbsp;max: 1,<br>
&nbsp; &nbsp;gamma: 1.4}, images);</p>
<p>// Show annotations.<br>
var&nbsp;labels = images.map(annotate);<br>
var&nbsp;labelsLayer = ui.Map.Layer(labels, {}, annotations);<br>
Map.layers().add(labelsLayer);</p>
<p>The result of defining and mapping this function over the filtered set of images is shown in Fig. F6.0.9. Notice that by adding an outline around the text, we can ensure the text is visible for both dark and light images. Earth Engine requires casting properties to their corresponding value type, which is why weve used ee.Number&nbsp;(as described in Chap. F1.0) before generating a formatted string. Also, we have shifted the resulting text image 25 pixels to the left. This was necessary to ensure that the text is positioned properly. In more complex text rendering applications, users may be required to compute the text position&nbsp;in a different way using ee.Geometry&nbsp;calls from the Earth Engine API: for example, by positioning text labels somewhere near the corners.</p>
<p><img src="F6/image3.png" class="img-fluid"></p>
<p>Fig. F6.0.9&nbsp;Annotating Landsat 8 images with image boundaries, border, and text strings indicating cloudiness</p>
<p>Because we render text labels using the Earth Engine raster API, they are not automatically scaled depending on map zoom size. This may cause unwanted artifacts; To avoid that, the text labels image needs to be updated every time the map zoom changes. To implement this in a script, we can make use of the Map API—in particular, the Map.onChangeZoom&nbsp;event handler. The following code snippet shows how the image containing text annotations can be re-rendered every time the map zoom changes. Add it to the end of your script.</p>
<p>// re-render (rescale) annotations when map zoom changes.<br>
Map.onChangeZoom(function(zoom) {<br>
&nbsp; &nbsp;labelsLayer.setEeObject(images.map(annotate));<br>
});</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60f. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Try commenting that event handler and observe how annotation rendering changes when you zoom in or zoom out.</p>
</section>
<section id="animations" class="level2" data-number="8.4">
<h2 data-number="8.4" class="anchored" data-anchor-id="animations"><span class="header-section-number">8.4</span> Animations</h2>
<p>Visualizing raster images as animations is a useful technique to explore changes in time-dependent datasets, but also, to render short animations to communicate how changing various parameters affects the resulting image—for example, varying thresholds of spectral indices resulting in different binary maps or the changing&nbsp;geometry of vector features.</p>
<p>Animations are very useful when exploring satellite imagery, as they allow viewers to quickly comprehend dynamics of changes of earth surface or atmospheric properties. Animations can also help to decide what steps should be taken next to designing a robust algorithm&nbsp;to extract useful information from satellite image time series. Earth Engine provides two standard ways to generate animations: as animated GIFs, and as AVI video clips. Animation can also be rendered from a sequence of images exported from Earth Engine, using numerous tools such as ffmpeg&nbsp;or moviepy. However, in many cases it is useful to have a way to quickly explore image collections as animation without requiring extra steps.</p>
<p>In this section, we will generate animations in three different ways:</p>
<ol type="1">
<li>Generate animated GIF</li>
<li>Export video as an AVI file to Google Drive</li>
<li>Animate image collection interactively using UI controls and map layers</li>
</ol>
<p>We will use an image collection showing sea ice as an input dataset to generate animations with visualization parameters from earlier. However, instead of querying a single Sentinel-1 image, lets generate a filtered image collection with all images intersecting with our area of interest. After importing some packages and palettes and defining a point and rectangle, well build the image collection. Here we will use point geometry to define the location where the image date label will be rendered and the rectangle geometry to indicate the area of interest for the animation. To do this we will build the following logic in a new script. Open a new script and paste the following code into it:</p>
<p>// Include packages.<br>
var&nbsp;palettes = require(users/gena/packages:palettes);<br>
var&nbsp;text = require(users/gena/packages:text);</p>
<p>var&nbsp;point = /* color: #98ff00 */&nbsp;ee.Geometry.Point([-&nbsp; &nbsp;106.15944300895228, -74.58262940096245<br>
]);</p>
<p>var&nbsp;rect = /* color: #d63000 */&nbsp; &nbsp;ee.Geometry.Polygon(<br>
&nbsp; &nbsp; &nbsp; &nbsp;[<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-106.19789515738981, -74.56509549360152],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-106.19789515738981, -74.78071448733921],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-104.98115931754606, -74.78071448733921],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[-104.98115931754606, -74.56509549360152]<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp; &nbsp; &nbsp;], null, false);</p>
<p>// Lookup the ice palette.<br>
var&nbsp;palette = palettes.cmocean.Ice[7];</p>
<p>// Show it in the console.<br>
palettes.showPalette(Ice, palette);</p>
<p>// Center map on geometry.<br>
Map.centerObject(point, 9);</p>
<p>// Select S1 images for the Thwaites glacier.<br>
var&nbsp;images = ee.ImageCollection(COPERNICUS/S1_GRD)<br>
&nbsp; &nbsp;.filterBounds(rect)<br>
&nbsp; &nbsp;.filterDate(2021-01-01, 2021-03-01)<br>
&nbsp; &nbsp;.select(HH)&nbsp; &nbsp;// Make sure we include only images which fully contain the region geometry.&nbsp; &nbsp;.filter(ee.Filter.isContained({<br>
&nbsp; &nbsp; &nbsp; &nbsp;leftValue: rect,<br>
&nbsp; &nbsp; &nbsp; &nbsp;rightField: .geo&nbsp; &nbsp;}))<br>
&nbsp; &nbsp;.sort(system:time_start);</p>
<p>// Print number of images.<br>
print(images.size());</p>
<p>As you see from the last last lines of the above code, it is frequently useful to print the number of images in an image collection: an example of whats often known as a “sanity&nbsp;check.”</p>
<p>Here we have used two custom geometries to configure animations: the green pin named point, used to filter image collection and to position text labels drawn on top of the image, and the blue rectangle rect, used to define a bounding box for the exported animations. To make sure that the point and rectangle geometries are shown under the Geometry Imports in the Code Editor, you need to click on these variables in the code and then select the Convert&nbsp;link.</p>
<p>Notice that in addition to the bounds and date filter, we have also used a less known isContained&nbsp;filter to ensure that we get only images that fully cover our region. To better understand this filter, you could try commenting out the filter and compare the differences, observing images with empty (masked) pixels in the resulting image collection.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60g. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Next, to simplify the animation API calls, we will generate a composite RGB image collection out of satellite images and draw the images acquisition date as a label on every image, positioned within our region geometry.</p>
<p>// Render images.<br>
var&nbsp;vis = {<br>
&nbsp; &nbsp;palette: palette,<br>
&nbsp; &nbsp;min: -15,<br>
&nbsp; &nbsp;max: 1<br>
};</p>
<p>var&nbsp;scale = Map.getScale();<br>
var&nbsp;textProperties = {<br>
&nbsp; &nbsp;outlineColor: 000000,<br>
&nbsp; &nbsp;outlineWidth: 3,<br>
&nbsp; &nbsp;outlineOpacity: 0.6<br>
};</p>
<p>var&nbsp;imagesRgb = images.map(function(i) {&nbsp; &nbsp;// Use the date as the label.&nbsp; &nbsp;var&nbsp;label = i.date().format(YYYY-MM-dd);&nbsp; &nbsp;var&nbsp;labelImage = text.draw(label, point, scale,<br>
&nbsp; &nbsp; &nbsp; &nbsp;textProperties);&nbsp; &nbsp;return&nbsp;i.visualize(vis)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.blend(labelImage) // Blend label image on top.&nbsp; &nbsp; &nbsp; &nbsp;.set({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;label: label<br>
&nbsp; &nbsp; &nbsp; &nbsp;}); // Keep the text property.<br>
});<br>
Map.addLayer(imagesRgb.first());<br>
Map.addLayer(rect, {color:blue}, rect, 1, 0.5);</p>
<p>In addition to printing the size of the&nbsp;ImageCollection, we also often begin by adding a single image to the map from a mapped collection to see that everything works as expected—another example of a sanity check. The resulting map layer will look like F6.0.10.</p>
<p><img src="F6/image6.png" class="img-fluid"></p>
<p>Fig. F6.0.10&nbsp;The results of adding the first layer from the RGB composite image collection showing Sentinel-1 images with a label blended on top at a specified location. The blue geometry is used to define the bounds for the animation to be exported.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60h. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Animation 1: Animated GIF with ui.Thumbnail</p>
<p>The quickest way to generate an animation in Earth Engine is to use the animated GIF API and either print it to the Console or print the URL to download the generated GIF. The following code snippet will result in an animated GIF as well as the URL to the animated GIF printed to Console. This is as shown in Fig. F6.0.11:</p>
<p>// Define GIF visualization parameters.<br>
var&nbsp;gifParams = {<br>
&nbsp; &nbsp;region: rect,<br>
&nbsp; &nbsp;dimensions: 600,<br>
&nbsp; &nbsp;crs: EPSG:3857,<br>
&nbsp; &nbsp;framesPerSecond: 10<br>
};</p>
<p>// Print the GIF URL to the console.<br>
print(imagesRgb.getVideoThumbURL(gifParams));</p>
<p>// Render the GIF animation in the console.<br>
print(ui.Thumbnail(imagesRgb, gifParams));</p>
<p>Earth Engine provides multiple options to specify the size of the resulting video. In this example we specify 600 as the size of the maximum dimension. We also specify the number of frames per second for the resulting animated GIF as well as the target projected coordinate system to be used (EPSG:3857 here, which is the projection used in web maps such as Google Maps and the Code Editor background).</p>
<p><img src="F6/image56.png" class="img-fluid"></p>
<p>Fig. F6.0.11&nbsp;Console&nbsp;output after running the animated GIF code snippet, showing the GIF URL and an animation shown directly in the Console</p>
<p>Animation 2: Exporting an Animation with Export.video.toDrive</p>
<p>Animated GIFs can be useful to generate animations quickly. However, they have several limitations. In particular, they are limited to 256 colors, become large for larger animations, and most web players do not provide play controls when playing animated GIFs. To overcome these limitations, Earth&nbsp;Engine provides export of animations as video files in MP4 format. Lets use the same RGB image collection we have used for the animated GIF to generate a short video. We can ask Earth Engine to export the video to the Google Drive using the following code snippet:</p>
<p>Export.video.toDrive({<br>
&nbsp; &nbsp;collection: imagesRgb,<br>
&nbsp; &nbsp;description: ice-animation,<br>
&nbsp; &nbsp;fileNamePrefix: ice-animation,<br>
&nbsp; &nbsp;framesPerSecond: 10,<br>
&nbsp; &nbsp;dimensions: 600,<br>
&nbsp; &nbsp;region: rect,<br>
&nbsp; &nbsp;crs: EPSG:3857<br>
});</p>
<p>Here, many arguments to the Export.video.toDrive&nbsp;function resemble the ones weve used in the ee.Image.getVideoThumbURL&nbsp;code above. Additional arguments include description and fileNamePrefix, which are required to configure the name of the task and the target file of the video file to be saved to Google Drive. Running the above code will result in a new task created under the Tasks tab in the Code Editor. Starting the export video task (F6.0.12) will result in a video file saved in the Google Drive once completed.</p>
<p><img src="F6/image9.png" class="img-fluid"></p>
<p>Fig. F6.0.12&nbsp;A new export video tasks in the Tasks panel of the Code Editor</p>
<p>Animation 3: The Custom Animation Package</p>
<p>For the last animation example, we will use the custom package users/gena/packages:animation, built using the Earth Engine User Interface API. The main difference between this package and the above examples is that it generates an interactive animation by adding Map layers individually to the layer set, and providing UI controls that allow users to play animations or interactively switch between frames. The animate&nbsp;function in that package generates an interactive animation of an ImageCollection, as described below. This function has a number of optional arguments allowing you to configure, for example, the number of frames to be animated, the number of frames to be preloaded, or a few others. The optional parameters to control the function are the following:</p>
<ul>
<li>maxFrames: maximum number of frames to show (default: 30)</li>
<li>vis: visualization parameters for every frame (default: {})</li>
<li>Label: text property of images to show in the animation controls (default: undefined)</li>
<li>width: width&nbsp;of the animation panel (default: 600px)</li>
<li>compact: show only play control and frame slider (default: false)</li>
<li>position: position of the animation panel (default: top-center)</li>
<li>timeStep: time step (ms) used when playing animation (default: 100)</li>
<li>preloadCount: number of frames (map layers) to preload (default: all)</li>
</ul>
<p>Lets call this function to add interactive animation controls to the current Map:</p>
<p>// include the animation package<br>
var&nbsp;animation = require(users/gena/packages:animation);</p>
<p>// show animation controls<br>
animation.animate(imagesRgb, {<br>
&nbsp;label: label,<br>
&nbsp;maxFrames: 50<br>
});</p>
<p>Before using the interactive animation API, we need to include the corresponding package using require. Here we provide our pre-rendered image collection and two optional parameters (label&nbsp;and maxFrames). The first optional parameter label indicates that every image in our image collection has the label&nbsp;text property. The animate&nbsp;function uses this property to name map layers as well as to visualize in the animation UI controls when switching between frames. This can be useful when inspecting image collections. The second optional parameter, maxFrames, indicates that the maximum number of animation frames that we would like to visualize is 50. To prevent the Code Editor from crashing, this parameter should not be too large: it is best to keep it below 100. For a much larger number of frames, it is better to use the Export video or animated GIF API. Running this code snippet will result in the animation control panel added to the map as shown in Fig. F6.0.13.</p>
<p>It is important to note that the animation API uses asynchronous UI calls to make sure that the Code Editor does not hang when running the script. The drawback of this is that for complex image collections, a large amount of processing is required. Hence, it may take some time to process all images and to visualize the interactive animation panel. The same is true for map layer names: they are updated once the animation panel is visualized. Also, map layers used to visualize individual images in the provided image collection may require some time to be rendered.</p>
<p><img src="F6/image70.png" class="img-fluid"></p>
<p>Fig. F6.0.13&nbsp;Interactive animation controls when using custom animation API</p>
<p>The main advantage of the interactive animation API is that it provides a way to explore image collections at frame-by-frame basis, which can greatly improve our visual understanding of the changes captured in sets of images.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60i. The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
</section>
<section id="terrain-visualization" class="level2" data-number="8.5">
<h2 data-number="8.5" class="anchored" data-anchor-id="terrain-visualization"><span class="header-section-number">8.5</span> Terrain Visualization</h2>
<p>This section introduces several raster visualization techniques useful to visualize terrain data such as:</p>
<ul>
<li>Basic&nbsp;hillshading&nbsp;and parameters (light azimuth, elevation)</li>
<li>Combining elevation data and colors using HSV&nbsp;transform (Wikipedia: HSL and HSV)</li>
<li>Adding shadows</li>
</ul>
<p>One&nbsp;special type of raster data is data that represents height. Elevation data can include topography, bathymetry, but also other forms of height, such as sea surface height can be presented as a terrain.</p>
<p>Height is often visualized using the concept of directional light with a technique called hillshading. Because height is such a common feature in our environment, we also have an expectancy of how height is visualized. If height is visualized using a simple grayscale colormap, it looks very unnatural (Fig. F6.0.14, top left). By using hillshading, data immediately looks more natural (Fig. F6.0.14, top middle).</p>
<p>We can further improve the visualization by including shadows (Fig. F6.0.14, top right). &nbsp;A final step is to replace the simple grayscale colormap with a perceptual uniform topographic&nbsp;colormap and mix this with the hillshading and shadows (Fig. F6.0.14, bottom). This section explains how to apply these techniques.</p>
<p>Well focus on elevation data stored in raster form. Elevation data is not always stored in raster formats. Other data formats include Triangulated Irregular Network (TIN), which allows storing information at varying resolutions and as 3D objects. This format allows one to have overlapping geometries, such as bridges with a road below it. In raster-based digital elevation models, in contrast, there can only be one height recorded for each pixel.</p>
<p>Lets start by loading data from a digital elevation model. This loads a topographic dataset from the Netherlands (Algemeen Hoogtebestand Nederland). It is a Digital Surface Model, based on airborne LIDAR measurements regridded to 0.5 m resolution. Enter the following code in a new script.</p>
<p>var&nbsp;dem = ee.Image(AHN/AHN2_05M_RUW);</p>
<p>We can visualize this dataset using a sequential gradient colormap from black to white. This results in Fig. F6.0.14. One can infer which areas are lower and which are higher, but the visualization&nbsp;does not quite “feel” like a terrain.</p>
<p>// Change map style to HYBRID and center map on the Netherlands<br>
Map.setOptions(HYBRID);<br>
Map.setCenter(4.4082, 52.1775, 18);</p>
<p>// Visualize DEM using black-white color palette<br>
var&nbsp;palette = [black, white];<br>
var&nbsp;demRGB = dem.visualize({<br>
&nbsp; &nbsp;min: -5,<br>
&nbsp; &nbsp;max: 5,<br>
&nbsp; &nbsp;palette: palette<br>
});<br>
Map.addLayer(demRGB, {},DEM);</p>
<p>An important step to visualize terrain is to add shadows created by a distant point source of light. This is referred to as hillshading or a shaded relief map. This type of map became popular in the 1940s through the work of Edward Imhoff,&nbsp;who&nbsp;also used grayscale&nbsp;colormaps (Imhoff, 2015). Here well use the gena/packages:utils&nbsp;library to combine the colormap image with the shadows. That Earth Engine package implements a hillshadeRGB&nbsp;function to simplify rendering of images enhanced with hillshading and shadow effects. One important argument this function takes is the light azimuth—an angle from the image plane upward to the light source (the Sun). This should always be set to the top left to avoid bistable perception artifacts, in which the DEM can be misperceived as inverted.</p>
<p>var&nbsp;utils = require(users/gena/packages:utils);</p>
<p>var&nbsp;weight =&nbsp; &nbsp;0.4; // Weight of Hillshade vs RGB (0 - flat, 1 - hillshaded).<br>
var&nbsp;exaggeration = 5; // Vertical exaggeration.<br>
var&nbsp;azimuth = 315; // Sun azimuth.<br>
var&nbsp;zenith = 20; // Sun elevation.<br>
var&nbsp;brightness = -0.05; // 0 - default.<br>
var&nbsp;contrast = 0.05; // 0 - default.<br>
var&nbsp;saturation = 0.8; // 1 - default.<br>
var&nbsp;castShadows = false;</p>
<p>var&nbsp;rgb = utils.hillshadeRGB(<br>
&nbsp; &nbsp;demRGB, dem, weight, exaggeration, azimuth, zenith,<br>
&nbsp; &nbsp;contrast, brightness, saturation, castShadows);</p>
<p>Map.addLayer(rgb, {}, DEM (no shadows));</p>
<p>Standard&nbsp;hillshading&nbsp;only determines per pixel if it will be directed to the light or not. One can also project shadows on the map. That is done using the ee.Algorithms.HillShadow&nbsp;algorithm. Here well turn on castShadows&nbsp;in the hillshadeRGB&nbsp;function. This results in a more realistic map, as can be seen in Figure F6.0.14.</p>
<p>var&nbsp;castShadows = true;</p>
<p>var&nbsp;rgb = utils.hillshadeRGB(<br>
&nbsp; &nbsp;demRGB, dem, weight, exaggeration, azimuth, zenith,<br>
&nbsp; &nbsp;contrast, brightness, saturation, castShadows);</p>
<p>Map.addLayer(rgb, {}, DEM (with shadows));</p>
<p>The final step is to add a topographic colormap. To visualize topographic information, one often uses special topographic colormaps. Here well use the oleron&nbsp;colormap from crameri. The colors get mixed with the shadows using the hillshadeRGB&nbsp;function. As you can see in Fig. F6.0.14, this gives a nice overview of the terrain. The area colored in blue is located below sea level.</p>
<p>var&nbsp;palettes = require(users/gena/packages:palettes);<br>
var&nbsp;palette = palettes.crameri.oleron[50];</p>
<p>var&nbsp;demRGB = dem.visualize({<br>
&nbsp; &nbsp;min: -5,<br>
&nbsp; &nbsp;max: 5,<br>
&nbsp; &nbsp;palette: palette<br>
});</p>
<p>var&nbsp;castShadows = true;</p>
<p>var&nbsp;rgb = utils.hillshadeRGB(<br>
&nbsp; &nbsp;demRGB, dem, weight, exaggeration, azimuth, zenith,<br>
&nbsp; &nbsp;contrast, brightness, saturation, castShadows);</p>
<p>Map.addLayer(rgb, {}, DEM colormap);</p>
<p>Steps to further improve a terrain visualization include using light sources from multiple directions. This allows the user&nbsp;to render terrain to appear more natural. In the real world light is often scattered by clouds and other reflections.</p>
<p>One can also use lights to emphasize certain regions. To use even more advanced lighting techniques one can use a raytracing engine, such as the R rayshader&nbsp;library, as discussed earlier&nbsp;in this chapter. The raytracing engine in the Blender 3D program is also capable of producing stunning terrain visualizations using physical-based rendering, mist, environment lights, and camera effects such as depth of field.</p>
<p><img src="F6/image36.png" class="img-fluid"><img src="F6/image58.png" class="img-fluid"><img src="F6/image47.png" class="img-fluid"></p>
<p><img src="F6/image55.png" class="img-fluid"></p>
<p>Figure F6.0.14&nbsp;Hillshading with shadows</p>
<p>Steps in visualizing a topographic dataset:</p>
<ol type="1">
<li>Top left, topography with grayscale colormap</li>
<li>Top middle, topography with grayscale colormap and hillshading</li>
<li>Top right,&nbsp;topography with grayscale colormap, hillshading, and shadows</li>
<li>Bottom, topography with topographic colormap, hillshading, and shadows</li>
</ol>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F60j.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
</section>
<section id="synthesis" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="synthesis">Synthesis</h2>
<p>To synthesize what you have learned in this chapter, you can do the following assignments.</p>
<p>Assignment 1.&nbsp;Experiment with different color palettes from the palettes&nbsp;library. Try combining palettes with image opacity (using ee.Image.updateMask&nbsp;call) to visualize different physical features (for example, hot or cold areas using temperature and elevation).</p>
<p>Assignment 2.&nbsp;Render multiple text annotations when generating animations using image collection. For example, show other image properties in addition to date or image statistics generated using regional reducers for every image.</p>
<p>Assignment 3.&nbsp;In addition to text annotations, try blending geometry elements (lines, polygons) to highlight specific areas of rendered images.</p>
</section>
<section id="conclusion" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>In this chapter we have learned about several techniques that can greatly improve visualization and analysis of images and image collections. Using predefined palettes can help to better comprehend and communicate Earth observation data, and combining with other visualization techniques such as hillshading and annotations can help to better understand processes studied with Earth Engine. When working with image collections, it is often very helpful to analyze their properties through time by visualizing them as animations. Usually, this step helps to better understand dynamics of the changes that are stored in image collections and to develop a proper algorithm to study these changes.</p>
</section>
<section id="references" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="references">References</h2>
<p>Burrough PA, McDonnell RA, Lloyd CD (2015) Principles of Geographical Information Systems. Oxford University Press</p>
<p>Crameri F, Shephard GE, Heron PJ (2020) The misuse of colour in science communication. Nat Commun 11:110. https://doi.org/10.1038/s41467-020-19160-7</p>
<p>Imhof E (2015) Cartographic Relief Presentation. Walter de Gruyter GmbH &amp; Co KG</p>
<p>Lhermitte S, Sun S, Shuman C, et al (2020) Damage accelerates ice shelf instability and mass loss in Amundsen Sea Embayment. Proc Natl Acad Sci USA 117:2473524741. https://doi.org/10.1073/pnas.1912890117</p>
<p>Thyng KM, Greene CA, Hetland RD, et al (2016) True colors of oceanography. Oceanography 29:913</p>
<p>Wikipedia (2022) Terrain cartography. https://en.wikipedia.org/wiki/Terrain_cartography#Shaded_relief. Accessed 1 Apr 2022</p>
<p>Wikipedia (2022) HSL and HSV. https://en.wikipedia.org/wiki/HSL_and_HSV. Accessed 1 Apr 2022</p>
<p>Wild CT, Alley KE, Muto A, et al (2022) Weakening of the pinning point buttressing Thwaites Glacier, West Antarctica. Cryosphere 16:397417. https://doi.org/10.5194/tc-16-397-2022</p>
<p>Wilkinson L (2005) The Grammar of Graphics. Springer Verlag</p>
</section>
</section>
<section id="collaborating-in-earth-engine-with-scripts-and-assets" class="level1" data-number="9">
<h1 data-number="9"><span class="header-section-number">9</span> Collaborating&nbsp;in Earth Engine with Scripts and Assets</h1>
<div class="callout-tip callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Chapter Information
</div>
</div>
<div class="callout-body-container callout-body">
<section id="author-1" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="author-1">Author</h2>
<p>Sabrina H. Szeto</p>
</section>
<section id="overview-1" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="overview-1">Overview</h2>
<p>Many users find themselves needing to collaborate with others in Earth Engine at some point. Students may need to work on a group project, people from different organizations might want to collaborate on research together, or people may want to share a script or an asset they created with others. This chapter will show you how to collaborate with others and share your work.</p>
</section>
<section id="learning-outcomes-1" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="learning-outcomes-1">Learning Outcomes</h2>
<ul>
<li>Understanding when it is important to share a script or asset.</li>
<li>Understanding what roles and permission options are available.</li>
<li>Sharing a script with others.</li>
<li>Sharing an asset with others.</li>
<li>Sharing an asset so it can be displayed in an app.</li>
<li>Sharing a repository with others.</li>
<li>Seeing who made changes to a script and what changes were made.</li>
<li>Reverting to a previous version of a script.</li>
<li>Using the require&nbsp;function to load modules.</li>
<li>Creating a script to share as a module.</li>
</ul>
</section>
<section id="assumes-you-know-how-to-1" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="assumes-you-know-how-to-1">Assumes you know how to:</h2>
<ul>
<li>Sign up for an Earth Engine account, open the Code Editor, and save your script (Chap. F1.0).</li>
</ul>
</section>
</div>
</div>
<section id="introduction-1" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="introduction-1">Introduction</h2>
<p>Many people find themselves needing to share a script when they encounter a problem; they wish to share the script with someone else so they can ask a question. When this occurs, sharing a link to the script often suffices. The other person can then make comments or changes before sending a new link to the modified script.</p>
<p>If you have included any assets from the Asset Manager&nbsp;in your script, you will also need to share these assets in order for your script to work for your colleague. The same goes for sharing assets to be displayed in an app.</p>
<p>Another common situation involves collaborating with others on a project. They may have some scripts they have written that they want to reuse or modify for the new project. Alternatively, several people might want to work on the same script together. For this situation, sharing a repository would be the best way forward; team members will be able to see who made what changes to a script and even revert to a previous version.</p>
<p>If you or your group members find yourselves repeatedly reusing certain functions for visualization or for part of your analysis, you could use the require&nbsp;module to call that function instead of having to copy and paste it into a new script each time. You could even make this function or module available to others to use via require.</p>
<p>Lets get started. For this lab, you will need to work in small groups or pairs.</p>
</section>
<section id="using-get-link-to-share-a-script" class="level2" data-number="9.1">
<h2 data-number="9.1" class="anchored" data-anchor-id="using-get-link-to-share-a-script"><span class="header-section-number">9.1</span> Using Get Link to Share a Script</h2>
<p>Copy and paste the following code into the Code Editor.</p>
<p>print(The author of this script is MyName.);</p>
<p>Replace MyName&nbsp;with your name, then click on Save&nbsp;to save the script in your home repository. Next, click on the Get Link&nbsp;button and copy the link to this script onto your clipboard. Using your email program of choice, send this script to one of your group members.</p>
<p>Now add the following code below the line of code that you pasted&nbsp;earlier.</p>
<p>print(I just sent this script to GroupMemberName.);</p>
<p>Replace GroupMemberName&nbsp;with the name of the person you sent this script to, then save the script again. Next, click on the Get Link&nbsp;button and copy the link to this script onto your clipboard. Using your email program of choice, send this script to the same person.</p>
<p>Question 1. You should also have received two emails from someone in your group who is also doing this exercise. Open the first and second links in your Code Editor by clicking on them. Is the content of both scripts the same?</p>
<p>Answer:&nbsp;No, the scripts will be different, because Get Link&nbsp;sends a snapshot of the script at a particular point in time. Thus, even though the script was updated, the first link does not reflect that change.</p>
<p>Question 2. What happens when you check the box for Hide code panel&nbsp;or Disable auto-run&nbsp;before sharing the script?</p>
<p>Answer.&nbsp;Hide code panel&nbsp;will minimize the code panel so the person you send the script to will see the Map&nbsp;maximized. This is useful when you want to draw the persons attention to the results rather than to the code. To expand the code panel, they have to click on the Show code&nbsp;button. Disable auto-run&nbsp;is helpful when you do not want the script to start running when the person you sent it to opens it. Perhaps your script takes very long to run or requires particular user inputs and you just want to share the code with the person.</p>
</section>
<section id="sharing-assets-from-your-asset-manager" class="level2" data-number="9.2">
<h2 data-number="9.2" class="anchored" data-anchor-id="sharing-assets-from-your-asset-manager"><span class="header-section-number">9.2</span> Sharing Assets from Your Asset Manager</h2>
<p>When you clicked the Get Link&nbsp;button earlier, you may have noticed a note in the popup reading: “To give others access to assets in the code snapshot, you may need to share them.” If your script uses an asset that you have uploaded into your Asset Manager, you will need to share that asset as well. If not, an error message will appear when the person you shared the script with tries to run it.</p>
<p>Before sharing an asset, think about whether you have permission to share it. Is this some data that is owned by you, or did you get it from somewhere else? Do you need permission to share this asset? Make sure you have the permission to share an asset before doing so.</p>
<p>Now, lets practice sharing assets. First, navigate to your Asset Manager&nbsp;by clicking on the Assets&nbsp;tab in the left panel. If you already have some assets uploaded, pick one that you have permission to share. If not, upload one to your Asset Manager. If you dont have a shapefile or raster to upload, you can upload a small text file. Consult the Earth Engine documentation for how to do this; it will take only a few steps.</p>
<p>Hover your cursor over that asset in your Asset Manager. The asset gets highlighted in gray&nbsp;and three buttons appear to the right of the asset. Click on the first button from the left (outlined in red in Fig. F6.1.1). This icon means “share.”</p>
<p><img src="F6/image26.png" class="img-fluid"></p>
<p>Fig. F6.1.1&nbsp;Three assets in the Asset Manager</p>
<p>After you click the share&nbsp;button, a Share Image&nbsp;popup will appear (Fig. F6.1.2). This popup contains information about the path of the asset and the email address of the owner. The owner of the asset can decide who can view and edit the asset.</p>
<p>Click on the dropdown menu outlined in red in Fig. F6.1.2. You will see two options for permissions: Reader&nbsp;and Writer. A Reader&nbsp;can view the asset, while a Writer&nbsp;can both view and make changes to it. For example, a Writer could add a new image to an ImageCollection. A Writer can also add other people to view or edit the asset, and a Writer can delete the asset. When in doubt, give someone the Reader role rather than the Writer role.</p>
<p><img src="F6/image10.png" class="img-fluid"></p>
<p>Fig. F6.1.2&nbsp;The Share Image&nbsp;popup window</p>
<p>To share an asset with someone, you can type their email address into the Email or domain&nbsp;text field, choose Reader&nbsp;or Writer&nbsp;in the dropdown menu, and then click on Add Access.&nbsp;You can also share an asset with everyone with a certain email domain, which is useful if you want to share an asset with everyone in your organization, for instance.</p>
<p>If you want to share reading access publicly, then check the box that says Anyone can read. Note that you still need to share the link to the asset in order for others to access it. The only exceptions to this are&nbsp;when you are using the asset in a script and sharing that script using the Get Link&nbsp;button or when you share the asset with an Earth Engine app. To do the latter, use the Select an app&nbsp;dropdown menu (outlined in orange in Fig. F6.1.2) and click Add App Access.</p>
<p>Question 3. Share an asset with a group member and give them reader access. Send them the link to that asset. You will also receive a link from someone else in your group. Open that link. What can you do with that asset? What do you need to do to import it into a script?</p>
<p>Answer:&nbsp;You can view details about the asset and import it for use in a script in the Code Editor. To import the asset, click on the blue Import&nbsp;button.</p>
<p>Question 4. Share an asset with a group member and give them writer access. Send them the link to that asset. You will also receive a link from someone else in your group. Open that link. What can you do with that asset? Try sharing the asset with a different group member.</p>
<p>Answer:&nbsp;You can view details about the asset and import it for use in a script in the Code Editor. You can also share the asset with others and delete the asset.</p>
</section>
<section id="working-with-shared-repositories" class="level2" data-number="9.3">
<h2 data-number="9.3" class="anchored" data-anchor-id="working-with-shared-repositories"><span class="header-section-number">9.3</span> Working with Shared Repositories</h2>
<p>Now that you know how to share assets and scripts, lets move on to sharing repositories. In this section, you will learn about different types of repositories and how to add a repository that someone else shared with you. You will also learn how to view previous versions of a script and how to revert back to an earlier version.</p>
<p>Earlier, we learned how to share a script using the Get Link&nbsp;button. This link shares a code snapshot from a script. This snapshot does not reflect any changes made to the script after the time the link was shared. If you want to share a script that updates to reflect the most current version when it is opened, you need to share a repository with that script instead.</p>
<p>If you look under the Scripts&nbsp;tab of the leftmost panel in the Code Editor, you will see that the first three categories are labeled Owner, Reader, and Writer.</p>
<ul>
<li>Repositories categorized under Owner&nbsp;are created and owned by you. No one else has access to view or make changes to them until you share these repositories.</li>
<li>Repositories categorized under Reader&nbsp;are repositories to which you have reader access. You can view the scripts but not make any changes to them. If you want to make any changes, you will need to save the script as a new file in a repository that you own.</li>
<li>Repositories categorized under Writer&nbsp;are repositories to which you have writer access. This means you can view and make changes to the scripts.</li>
</ul>
<p>Lets practice creating and sharing repositories. We will start by making a new repository. Click on the red New&nbsp;button located in the left panel. Select Repository&nbsp;from the dropdown menu. A New repository&nbsp;popup window will open (Fig. F6.1.3).</p>
<p><img src="F6/image25.png" class="img-fluid"></p>
<p>Fig. F6.1.3&nbsp;The New repository&nbsp;popup window</p>
<p>In the popup windows text field, type a name for your new repository, such as “ForSharing1,” then click on the blue Create&nbsp;button. You will see the new repository appear under the Owner&nbsp;category in the Scripts&nbsp;tab (Fig. F6.1.4).</p>
<p>Now, share this new repository with your group members: Hover your cursor over the repository you want to share. The repository gets highlighted in gray,&nbsp;and three buttons appear. Click on the Gear icon (outlined in red in Fig. F6.1.4).</p>
<p><img src="F6/image71.png" class="img-fluid"></p>
<p>Fig. F6.1.4&nbsp;Three repositories under the Owner&nbsp;category</p>
<p>A Share Repo&nbsp;popup window appears&nbsp;(Fig. F6.1.5)&nbsp;which is very similar to the Share Image&nbsp;popup window we saw in&nbsp;Fig. F6.1.2.&nbsp;The method for sharing a repository with a specific user or the general public is the same as for sharing assets.</p>
<p>Type the email address of a group member in the Email or domain&nbsp;text field and give this person a writer role by selecting Writer&nbsp;in the dropdown menu, then click on Add Access.&nbsp;</p>
<p><img src="F6/image29.png" class="img-fluid"></p>
<p>Fig. F6.1.5.&nbsp;The Share Repo&nbsp;popup window</p>
<p>Your group member should receive an email inviting them to edit the repository. Check your email inbox for the repository that your group member has shared with you. When you open that email, you will see content similar to what is shown in Fig. F6.1.6.</p>
<p><img src="F6/image50.png" class="img-fluid"></p>
<p>Fig. F6.1.6&nbsp;The “Invitation to edit” email</p>
<p>Now, click on the blue button that says Add [repository path] to your Earth Engine Code Editor. You will find the new repository added to the Writer&nbsp;category in your Scripts&nbsp;tab. The repository path will contain the username of your group member, such as users/username/sharing.</p>
<p>Now, lets add a script to the empty repository. Click on the red New&nbsp;button in the Scripts&nbsp;tab and select File&nbsp;from the dropdown menu. A Create file&nbsp;popup will appear, as shown in Fig. F6.1.7. Click on the gray arrow beside the default path to open a dropdown menu that will allow you to choose the path of the repository that your group member shared with you. Type a new File Name&nbsp;in the text field, such as “exercise,” then click on the blue OK&nbsp;button to create the file.</p>
<p><img src="F6/image69.png" class="img-fluid"></p>
<p>Fig. F6.1.7&nbsp;The Create file&nbsp;popup window</p>
<p>A new file should now appear in the shared repository in the Writer&nbsp;category. If you dont see it, click on the Refresh&nbsp;icon, which is to the right of the red New&nbsp;button in the Scripts&nbsp;tab.</p>
<p>Double-click on the new script in the shared repository to open it. Then, copy and paste the following code to your Code Editor.</p>
<p>print(The owner of this repository is GroupMemberName.);</p>
<p>Replace GroupMemberName&nbsp;with the name of your group member, then click Save&nbsp;to save the script in the shared repository, which is under the Writer&nbsp;category.</p>
<p>Now, navigate to the repository under Owner&nbsp;which you shared with your group member. Open the new script which they just created by double-clicking it.</p>
<p>Add the following code below the line of code that you pasted&nbsp;earlier.</p>
<p>print(This script is shared with MyName.);</p>
<p>Replace MyName&nbsp;with your name, then save the script.</p>
<p>Next, we will compare changes made to the script. Click on the Versions&nbsp;icon&nbsp;(outlined in red in Fig. F6.1.8).</p>
<p><img src="F6/image17.png" class="img-fluid"></p>
<p>Fig. F6.1.8&nbsp;Changes made and previous versions of the script</p>
<p>A popup window will appear, titled Revision history,&nbsp;followed by the path of the script (Fig. F6.1.9). There are three columns of information below the title.</p>
<ul>
<li>The left column contains the dates on which changes have &nbsp;been made.</li>
<li>The middle column contains the usernames of the people who made changes.</li>
<li>The right column contains information about what changes were made. &nbsp;</li>
</ul>
<p>The most recent version of the script is shown in the first row, while previous versions are listed in subsequent rows. (More advanced users may notice that this is actually a Git repository.)</p>
<p><img src="F6/image19.png" class="img-fluid"></p>
<p>Fig. F6.1.9&nbsp;The Revision history&nbsp;popup window</p>
<p>If you hover your cursor over a row, the row will be highlighted in gray&nbsp;and a button labeled Compare&nbsp;will appear. Clicking on this button allows you to compare differences between the current version of the script and a previous version in a Version comparison&nbsp;popup window (Fig. F6.1.10).</p>
<p><img src="F6/image30.png" class="img-fluid"></p>
<p>Fig. F6.1.10&nbsp;The Version comparison&nbsp;popup window</p>
<p>In the Version comparison&nbsp;popup, you will see text highlighted in two different colors. Text highlighted in red shows code that was present in the older version but is absent in the current version (the “latest commit”). Text highlighted in green shows code that is present in the current version but that was absent in the older version. Generally speaking, text highlighted in red has been removed in the current version and text highlighted in green has been added to the current version. Text that is not highlighted shows code that is present in both versions.</p>
<p>Question 5. What text, if any, is highlighted in red when you click on Compare&nbsp;in your “exercise” script?</p>
<p>Answer:&nbsp;No text is highlighted in red, because none was removed between the previous and current versions of the script.</p>
<p>Question 6. What text, if any, is highlighted in green when you click on Compare&nbsp;in your “exercise” script?</p>
<p>Answer:&nbsp;print(This script is shared with MyName.);</p>
<p>Question 7. What happens when you click on the blue Revert&nbsp;button?</p>
<p>Answer:&nbsp;The script reverts to the previous version, in which the only line of code is</p>
<p>print(The owner of this repository is GroupMemberName.);</p>
</section>
<section id="using-the-require-function-to-load-a-module" class="level2" data-number="9.4">
<h2 data-number="9.4" class="anchored" data-anchor-id="using-the-require-function-to-load-a-module"><span class="header-section-number">9.4</span> Using the Require Function to Load a Module</h2>
<p>In earlier chapters, you may have noticed that the require&nbsp;function allows you to reuse code that has already been written without having to copy and paste it into your current script. For example, you might have written a function for cloud masking that you would like to use in multiple scripts.&nbsp;Saving this function as a module enables you to share the code across your own scripts and with other people.&nbsp;Or you might discover a new module with capabilities you need written by other authors. This section will show you how to use the require&nbsp;function to create and share your own module or to load a module that someone else has shared.</p>
<p>The module we will use is ee-palettes, which enables users to visualize raster data using common specialized color palettes (Donchyts et al.&nbsp;2019). (If&nbsp;you would like to learn more about using these color palettes, the ee-palettes&nbsp;module is described and illustrated in detail in Chap. F6.0.) The first step is to go to this link to accept access to the repository as a reader: <a href="https://www.google.com/url?q=https://code.earthengine.google.com/?accept_repo%3Dusers/gena/packages&amp;sa=D&amp;source=editors&amp;ust=1671458841147867&amp;usg=AOvVaw2lfbVvfKSe6Nym_B5h25Z7">https://code.earthengine.google.com/?accept_repo=users/gena/</a><a href="https://www.google.com/url?q=https://code.earthengine.google.com/?accept_repo%3Dusers/gena/packages&amp;sa=D&amp;source=editors&amp;ust=1671458841148247&amp;usg=AOvVaw396ktWqHZ6LRi90IQjOwxZ">packages</a>&nbsp;</p>
<p>Now, if you navigate to your Reader&nbsp;directory in the Code Editor, you should see a new repository called users/gena/packages&nbsp;listed. Look for a script called palettes&nbsp;and click on it to load it in your Code Editor.</p>
<p>If you scroll down, you will see that the script contains a nested series of dictionaries with lists of hexadecimal color specifications (as described in Chap. F2.1) that describe a color palette, as shown in the code block below. For example, the color palette named “Algae” stored in the cmocean&nbsp;variable consists of seven colors, ranging from dark green to light green (Fig. F6.1.11).</p>
<p>exports.cmocean&nbsp;= {<br>
&nbsp; &nbsp;Thermal: {&nbsp; &nbsp; &nbsp; &nbsp;7: [042333, 2c3395, 744992, b15f82, eb7958,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fbb43d, e8fa5b&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Haline: {&nbsp; &nbsp; &nbsp; &nbsp;7: [2a186c, 14439c, 206e8b, 3c9387, 5ab978,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;aad85c, fdef9a&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Solar: {&nbsp; &nbsp; &nbsp; &nbsp;7: [331418, 682325, 973b1c, b66413, cb921a,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dac62f, e1fd4b&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Ice: {&nbsp; &nbsp; &nbsp; &nbsp;7: [040613, 292851, 3f4b96, 427bb7, 61a8c7,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;9cd4da, eafdfd&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Gray: {&nbsp; &nbsp; &nbsp; &nbsp;7: [000000, 232323, 4a4a49, 727171, 9b9a9a,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;cacac9, fffffd&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Oxy: {&nbsp; &nbsp; &nbsp; &nbsp;7: [400505, 850a0b, 6f6f6e, 9b9a9a, cbcac9,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ebf34b, ddaf19&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Deep: {&nbsp; &nbsp; &nbsp; &nbsp;7: [fdfecc, a5dfa7, 5dbaa4, 488e9e, 3e6495,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3f396c, 281a2c&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Dense: {&nbsp; &nbsp; &nbsp; &nbsp;7: [e6f1f1, a2cee2, 76a4e5, 7871d5, 7642a5,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;621d62, 360e24&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;Algae: {&nbsp; &nbsp; &nbsp; &nbsp;7: [d7f9d0, a2d595, 64b463, 129450, 126e45,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1a482f, 122414&nbsp; &nbsp; &nbsp; &nbsp;]<br>
&nbsp; &nbsp;},<br>
&nbsp; &nbsp;<br>
}</p>
<p>Notice that the variable is named exports.cmocean. Adding exports&nbsp;to the name of a function or variable makes it available to other scripts to use, as it gets added to a special global variable (Chang 2017).</p>
<p><img src="F6/image52.png" class="img-fluid"></p>
<p>Fig. F6.1.11&nbsp;Some of the color palettes from the ee-palettes&nbsp;GitHub repository</p>
<p>To see all the color palettes available in this module, go to <a href="https://www.google.com/url?q=https://github.com/gee-community/ee-palettes&amp;sa=D&amp;source=editors&amp;ust=1671458841155957&amp;usg=AOvVaw30bltn2S4_BDlhyuKIvkZH">https://github.com/gee-community/ee-palettes</a>.</p>
<p>Now lets try using the ee-palettes&nbsp;module. Look for a script in the same repository called palettes-test&nbsp;and click on it to load it in your Code Editor. When you run the script, you will see digital elevation data from the National Aeronautics and Space Administration&nbsp;Shuttle Radar Topography Mission&nbsp;satellite visualized using two palettes, &nbsp;colorbrewer.Blues&nbsp;and cmocean.Algae. The map will have two layers that show the same data with different palettes.</p>
<p>The script first imports the digital elevation model data in the Imports&nbsp;section of the Code Editor.</p>
<p>var&nbsp;dem = ee.Image(USGS/SRTMGL1_003);</p>
<p>The script then loads the ee-palettes&nbsp;module by using the require&nbsp;function. The path to the module, users/gena/packages:palettes, is passed to the function. The require&nbsp;function is then stored in a variable named palettes, which will be used later to obtain the palettes for data visualization.</p>
<p>var&nbsp;palettes = require(users/gena/packages:palettes);</p>
<p>As described by Donchyts et al.&nbsp;(2019), “Each palette is defined by a group and a name, which are separated by a period (JS object dot notation), and a color level. To retrieve a desired palette, use JS object notation to specify the group, name, and number of color levels.” We define the color palette Algae&nbsp;as palettes.cmocean.Algae[7]&nbsp;because it is part of the group cmocean&nbsp;and has 7&nbsp;color levels.&nbsp;In the next code block, you can see that the palettes (i.e., lists of hex colors) have been defined for use by setting them as the value for the palette&nbsp;key in the visParams&nbsp;object supplied to the Map.addLayer&nbsp;function.</p>
<p>// colorbrewer<br>
Map.addLayer(dem, {<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 3000,<br>
&nbsp; &nbsp;palette: palettes.colorbrewer.Blues[9]<br>
}, colorbrewer Blues[9]);</p>
<p>// cmocean<br>
Map.addLayer(dem, {<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 3000,<br>
&nbsp; &nbsp;palette: palettes.cmocean.Algae[7]<br>
}, cmocean Algae[7]);</p>
<p>Question 8. Try adding a third layer to the Map&nbsp;with a different color palette from ee-palettes. How easy was it to do?</p>
<p>Now that you have loaded and used a module shared by someone else, you can try your hand at creating your own module and sharing it with someone else in your group. First, go to the shared repository that you created in Sect. 3, create a new script in that repository, and name it “cloudmasking.”</p>
<p>Then, go to the Examples&nbsp;repository at the bottom of the Scripts&nbsp;tab and select a function from the Cloud Masking&nbsp;repository. Lets use the Landsat8&nbsp;Surface Reflectance&nbsp;cloud masking script as an example. In that script, you will see the code shown in the block below. Copy all of it into your empty script. &nbsp;</p>
<p>// This example demonstrates the use of the Landsat 8 Collection 2, Level 2<br>
// QA_PIXEL band (CFMask) to mask unwanted pixels.<br>
function&nbsp;maskL8sr(image) {&nbsp; &nbsp;// Bit 0 - Fill&nbsp; &nbsp;// Bit 1 - Dilated Cloud&nbsp; &nbsp;// Bit 2 - Cirrus&nbsp; &nbsp;// Bit 3 - Cloud&nbsp; &nbsp;// Bit 4 - Cloud Shadow&nbsp; &nbsp;var&nbsp;qaMask = image.select(QA_PIXEL).bitwiseAnd(parseInt(11111,&nbsp; &nbsp; &nbsp; &nbsp;2)).eq(0);&nbsp; &nbsp;var&nbsp;saturationMask = image.select(QA_RADSAT).eq(0);&nbsp; &nbsp;// Apply the scaling factors to the appropriate bands.&nbsp; &nbsp;var&nbsp;opticalBands = image.select(SR_B.).multiply(0.0000275).add(-&nbsp; &nbsp; &nbsp; &nbsp;0.2);&nbsp; &nbsp;var&nbsp;thermalBands = image.select(ST_B.*).multiply(0.00341802)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.add(149.0);&nbsp; &nbsp;// Replace the original bands with the scaled ones and apply the masks.&nbsp; &nbsp;return&nbsp;image.addBands(opticalBands, null, true)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.addBands(thermalBands, null, true)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.updateMask(qaMask)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.updateMask(saturationMask);<br>
}</p>
<p>// Map the function over one year of data.<br>
var&nbsp;collection = ee.ImageCollection(LANDSAT/LC08/C02/T1_L2)<br>
&nbsp; &nbsp;.filterDate(2020-01-01, 2021-01-01)<br>
&nbsp; &nbsp;.map(maskL8sr);</p>
<p>var&nbsp;composite = collection.median();</p>
<p>// Display the results.<br>
Map.setCenter(-4.52, 40.29, 7); // Iberian Peninsula<br>
Map.addLayer(composite, {<br>
&nbsp; &nbsp;bands: [SR_B4, SR_B3, SR_B2],<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 0.3<br>
});</p>
<p>Note that this code is well commented and has a header that describes what the script does. Dont forget to comment your code and describe what you are doing each step of the way. This is a good practice for collaborative coding and for your own future reference.</p>
<p>Imagine that you changed this&nbsp;maskL8sr&nbsp;function slightly for some reason and want to make it available to other users and scripts. To do that, you can turn the function into a module. Copy and modify the code from the example code into the new script you created called “cloudmasking.” (Hint: Store the function in a variable starting with exports. Be careful that you dont accidentally use Export, which is used to export datasets.)</p>
<p>Your script should be similar to the following code.</p>
<p>exports.maskL8sr&nbsp;= function(image) {&nbsp; &nbsp;// Bit 0 - Fill&nbsp; &nbsp;// Bit 1 - Dilated Cloud&nbsp; &nbsp;// Bit 2 - Cirrus&nbsp; &nbsp;// Bit 3 - Cloud&nbsp; &nbsp;// Bit 4 - Cloud Shadow&nbsp; &nbsp;var&nbsp;qaMask = image.select(QA_PIXEL).bitwiseAnd(parseInt(&nbsp; &nbsp; &nbsp; &nbsp;11111, 2)).eq(0);&nbsp; &nbsp;var&nbsp;saturationMask = image.select(QA_RADSAT).eq(0);&nbsp; &nbsp;// Apply the scaling factors to the appropriate bands.&nbsp; &nbsp;var&nbsp;opticalBands = image.select(SR_B.).multiply(0.0000275)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.add(-0.2);&nbsp; &nbsp;var&nbsp;thermalBands = image.select(ST_B.*).multiply(0.00341802)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.add(149.0);&nbsp; &nbsp;// Replace the original bands with the scaled ones and apply the masks.&nbsp; &nbsp;return&nbsp;image.addBands(opticalBands, null, true)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.addBands(thermalBands, null, true)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.updateMask(qaMask)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.updateMask(saturationMask);<br>
}</p>
<p>Next, you will create a test script that makes use of the cloud masking module you just made. Begin by creating a new script in your shared repository called “cloudmasking-test.” You can modify the last part of the example cloud masking script to use your module.</p>
<p>// Map the function over one year of data.<br>
var&nbsp;collection = ee.ImageCollection(LANDSAT/LC08/C02/T1_L2)<br>
&nbsp; &nbsp;.filterDate(2020-01-01, 2021-01-01)<br>
&nbsp; &nbsp;.map(maskL8sr);</p>
<p>var&nbsp;composite = collection.median();</p>
<p>// Display the results.<br>
Map.setCenter(-4.52, 40.29, 7); // Iberian Peninsula<br>
Map.addLayer(composite, {<br>
&nbsp; &nbsp;bands: [SR_B4, SR_B3, SR_B2],<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 0.3<br>
});</p>
<p>Question 9.&nbsp;How will you modify the cloud masking script to use your module? What does the script look like?</p>
<p>Answer:&nbsp;Your code might look something like the code block below.</p>
<p>// Load the module<br>
var&nbsp;myCloudFunctions = require(&nbsp; &nbsp;users/myusername/my-shared-repo:cloudmasking);</p>
<p>// Map the function over one year of data.<br>
var&nbsp;collection = ee.ImageCollection(LANDSAT/LC08/C02/T1_L2)<br>
&nbsp; &nbsp;.filterDate(2020-01-01, 2021-01-01)<br>
&nbsp; &nbsp;.map(myCloudFunctions.maskL8sr);</p>
<p>var&nbsp;composite = collection.median();</p>
<p>// Display the results.<br>
Map.setCenter(-4.52, 40.29, 7); // Iberian Peninsula<br>
Map.addLayer(composite, {<br>
&nbsp; &nbsp;bands: [SR_B4, SR_B3, SR_B2],<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 0.3<br>
});</p>
</section>
<section id="synthesis-1" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="synthesis-1">Synthesis</h2>
<p>Apply what you learned in this chapter by setting up a shared repository for your project, lab group, or organization. What scripts would you share? What permissions should different users have? Are there any scripts that you would turn into modules?</p>
</section>
<section id="conclusion-1" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="conclusion-1">Conclusion</h2>
<p>In this chapter, you learned how to collaborate with others in the Earth Engine Code Editor through sharing scripts, assets, and repositories. You learned about different roles and permissions available for sharing and when it is appropriate to use each. In addition, you are now able to see what changes have been made to a script and revert to a previous version. Lastly, you loaded and used a module that was shared with you and created your own module for sharing. You are now ready to start collaborating and developing scripts with others.</p>
</section>
<section id="references-1" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="references-1">References</h2>
<p>Chang A (2017) Making it easier to reuse code with Earth Engine script modules. In: Google Earth and Earth Engine. https://medium.com/google-earth/making-it-easier-to-reuse-code-with-earth-engine-script-modules-2e93f49abb13. Accessed 24 Feb 2022</p>
<p>Donchyts G, Baart F, Braaten J (2019) ee-palettes. https://github.com/gee-community/ee-palettes. Accessed 24 Feb 2022</p>
</section>
</section>
<section id="scaling-up-in-earth-engine" class="level1" data-number="10">
<h1 data-number="10"><span class="header-section-number">10</span> Scaling Up in Earth Engine</h1>
<div class="callout-tip callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Chapter Information
</div>
</div>
<div class="callout-body-container callout-body">
<section id="author-2" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="author-2">Author</h2>
<p>Jillian M. Deines, Stefania Di Tommaso, Nicholas Clinton, Noel Gorelick &nbsp; &nbsp;</p>
</section>
<section id="overview-2" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="overview-2">Overview</h2>
<p>Commonly, when Earth Engine users move from tutorials to developing their own processing scripts, they encounter the dreaded error messages, “computation timed out” or “user memory limit exceeded.” Computational resources are never unlimited, and the team at Earth Engine has designed a robust system with built-in checks to ensure server capacity is available to everyone. This chapter will introduce general tips for creating efficient Earth Engine workflows that accomplish users ambitious research objectives within the constraints of the Earth Engine ecosystem. We use two example case studies: 1) extracting a daily climate time series for many locations across two decades, and 2) generating a regional, cloud-free median composite from Sentinel-2 imagery.</p>
</section>
<section id="learning-outcomes-2" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="learning-outcomes-2">Learning Outcomes</h2>
<ul>
<li>Understanding constraints on Earth Engine resource use.</li>
<li>Becoming familiar with multiple strategies to scale Earth Engine operations.</li>
<li>Managing large projects and multistage workflows.</li>
<li>Recognizing when using the Python API may be advantageous to execute large batches of tasks.</li>
</ul>
</section>
<section id="assumes-you-know-how-to-2" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="assumes-you-know-how-to-2">Assumes you know how to:</h2>
<ul>
<li>Import images and image collections, filter, and visualize (Part F1).</li>
<li>Write a function and map it over an ImageCollection (Chap. F4.0).</li>
<li>Export and import results as Earth Engine assets (Chap. F5.0).</li>
<li>Understand distinctions among Image, ImageCollection, Feature&nbsp;and FeatureCollection&nbsp;Earth Engine objects (Part F1, Part F2, Part F5).</li>
<li>Use the require&nbsp;function to load code from existing modules (Chap. F6.1).</li>
</ul>
</section>
</div>
</div>
<section id="introduction-2" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="introduction-2">Introduction</h2>
<p>Parts F1F5 of this book have covered key remote sensing concepts and demonstrated how to implement them in Earth Engine. Most exercises have used local-scale examples to enhance understanding and complete tasks within a class-length time period. But Earth Engines power comes from its scalability—the ability to apply geospatial processing across large areas and many years.</p>
<p>How we go from small to large scales is influenced by Earth Engines design. Earth Engine runs on many individual computer servers, and its functions are designed to split up processing onto these servers. This chapter focuses on common approaches to implement large jobs within Earth Engines constraints. To do so, we first discuss Earth Engines underlying infrastructure to provide context for existing limits. We then cover four core concepts for scaling:</p>
<ol type="1">
<li>Using best coding practices.</li>
<li>Breaking up jobs across time.</li>
<li>Breaking up jobs across space.</li>
<li>Building a multipart workflow and exporting intermediate assets.</li>
</ol>
<section id="earth-engine-under-the-hood" class="level3" data-number="10.0.1">
<h3 data-number="10.0.1" class="anchored" data-anchor-id="earth-engine-under-the-hood"><span class="header-section-number">10.0.1</span> Earth Engine: Under the Hood</h3>
<p>As you use Earth Engine, you may begin to have questions about how it works and how you can use that knowledge to optimize your workflow. In general, the inner workings are opaque to users. Typical fixes and approaches that data scientists use to manage memory constraints often dont apply. Its helpful to know what users can and cannot control, and how your scripts translate to Earth Engines server operations.</p>
<p>Earth Engine is a parallel, distributed system (see Gorelick et al.&nbsp;2017), which means that when you submit tasks, it breaks up pieces of your query onto different processors to complete them more efficiently. It then collects the results and returns them to you. For many users, not having to manually design this parallel, distributed processing is a huge benefit. For some advanced users, it can be frustrating to not have better control. Wed argue that leaving the details up to Earth Engine is a huge time-saver for most cases, and learning to work within a few constraints is a good time investment.</p>
<p>One core concept useful to master is the relationship between client-side and server-side operations. Client-side operations are performed within your browser (for the JavaScript API Code Editor) or local system (for the Python API). These include things such as manipulating strings or numbers in JavaScript. Server-side operations are executed on Googles servers and include all of the ee.*&nbsp;functions. By using the Earth Engine APIs—JavaScript or Python—you are building a chain of commands to send to the servers and later receive the result back. As much as possible, you want to structure your code to send all the heavy lifting to Google, and keep processing off of your local resources.</p>
<p>In other words, your work in the Code Editor is making a description of a computation. All ee&nbsp;objects are just placeholders for server-side objects—their actual value does not exist locally on your computer. To see or use the actual value, it has to be evaluated by the server. If you print&nbsp;an Earth Engine object, it calls getInfo&nbsp;to evaluate and return the value. In contrast, you can also work with JavaScript/Python lists or numbers locally, and do basic&nbsp;JavaScript/Python things to them, like add numbers together or loop over items. These are client-side objects. Whenever you bring a server-side object into your local environment, theres a computational cost.</p>
<p>Table F6.2.1 describes some nuts and bolts about Earth Engine and their implications. Table F6.2.2 provides some of the existing limits on individual tasks.</p>
<p>Table F6.2.1 Characterics of Google Earth Engine and implications for running large jobs&nbsp;</p>
<p>Earth Engine characteristics</p>
<p>Implications</p>
<p>A parallel, distributed system</p>
<p>Occasionally, doing the exact same thing in two different orders can result in different processing distributions, impacting the ability to complete the task within system limits.</p>
<p>Most processing is done per tile (generally a square that is 256 x 256 pixels).</p>
<p>Tasks that require many tiles are the most memory intensive. Some functions have a tileScale&nbsp;argument that reduces tile size, allowing processing-intensive jobs to succeed (at the cost of reduced speed).</p>
<p>Export mode has higher memory and time allocations than interactive mode.</p>
<p>Its better to export large jobs. You can export to your Earth Engine assets, your Google Drive, or Google Cloud Storage.</p>
<p>Some operations are cached temporarily.</p>
<p>Running the same job twice could result in different run times. Occasionally tasks may run successfully on a second try.</p>
<p>Underlying infrastructure is composed of clusters of low-end servers.</p>
<p>Theres a hard limit on data size for any individual server; large computations need to be done in parallel using Earth Engine functions.</p>
<p>The image processing domain, scale, and projection are defined by the specified output and applied backwards throughout the processing chain.</p>
<p>There are not many cases when you will need to manually reproject images, and these operations are costly. Similarly, manually “clipping” images is typically unnecessary.</p>
<p>Table F6.2.2 Size limits for Earth&nbsp;Engine tasks</p>
<p>Earth Engine Component</p>
<p>Limits</p>
<p>Interactive mode</p>
<p>Can print up to 5000 records. Computations must finish within five minutes.</p>
<p>Export mode</p>
<p>Jobs have no time limit as long as they continue to make reasonable progress (defined roughly&nbsp;as 600 seconds per feature, two hours per aggregation, and 10 minutes per tile). If any one tile, feature, or aggregation takes too long, the whole job will get canceled. Any jobs that take longer than one week to run will likely fail due to Earth Engines software update release cycles.</p>
<p>Table assets</p>
<p>Maximum of 100 million features, 1000 properties (columns), and 100,000 vertices for a geometry.</p>
</section>
<section id="the-importance-of-coding-best-practices" class="level3" data-number="10.0.2">
<h3 data-number="10.0.2" class="anchored" data-anchor-id="the-importance-of-coding-best-practices"><span class="header-section-number">10.0.2</span> The Importance of Coding Best Practices</h3>
<p>Good code scales better than bad code. But what is good code? Generally, for Earth Engine, good code means 1) using Earth Engines server-side operators; 2) avoiding multiple passes through the same image collection; 3) avoiding unnecessary conversions; and 4) setting the processing scale or sample numbers appropriate for your use case, i.e., avoid using very fine scales or large samples without reason.</p>
<p>We encourage readers to become familiar with the “Coding Best Practices” page in the online Earth Engine User Guide. This page provides examples for avoiding mixing client- and server-side functions, unnecessary conversions, costly algorithms, combining reducers, and other helpful tips. Similarly, the&nbsp;“Debugging GuideScaling Errors” page of the online Earth Engine User Guide&nbsp;covers some common problems and solutions.</p>
<p>In addition, some Earth Engine functions are more efficient than others. For example, Image.reduceRegions&nbsp;is more efficient than Image.sampleRegions, because sampleRegions&nbsp;regenerates the geometries under the hood. These types of best practices are trickier to enumerate and somewhat idiosyncratic. We encourage users to learn about and make use of the Profiler&nbsp;tab, which will track and display the resources used for each operation within your script. This can help identify areas to focus efficiency improvements. Note that the profiler itself increases resource use, so only use it when necessary to develop a script and remove it for production-level execution. Other ways to discover best practices include following/posting questions to GIS StackExchange or the Earth Engine Developers Discussion Group, swapping code with others, and experimentation.</p>
</section>
</section>
<section id="scaling-across-time" class="level2" data-number="10.1">
<h2 data-number="10.1" class="anchored" data-anchor-id="scaling-across-time"><span class="header-section-number">10.1</span> Scaling Across Time</h2>
<p>In this section we use an example of extracting climate data at features (points or polygons) to demonstrate how to scale an operation across many features (Sect. 1.1) and how to break up large jobs by time units when necessary (e.g, by years; Sect. 1.2).</p>
<section id="scaling-up-with-earth-engine-operators-annual-daily-climate-data" class="level3" data-number="10.1.1">
<h3 data-number="10.1.1" class="anchored" data-anchor-id="scaling-up-with-earth-engine-operators-annual-daily-climate-data"><span class="header-section-number">10.1.1</span> 1.1. Scaling Up with Earth Engine Operators: Annual Daily Climate Data</h3>
<p>Earth Engines operators are designed to parallelize queries on the backend without user intervention. In many cases, they are sufficient to accomplish a scaling operation.</p>
<p>As an example, we will extract a daily time series of precipitation, maximum temperature, and minimum temperature&nbsp;for county polygons in the United States. We will use the GRIDMET Climate Reanalysis&nbsp;product (Abatzoglou 2013), which provides daily, 4000 m resolution gridded meteorological data from 1979 to the present across the contiguous United States. To save time for this practicum, we will focus on the states of Indiana, Illinois, and Iowa in the central United States, which together include 293 counties (Fig. F6.2.1).</p>
<p><img src="F6/image11.png" class="img-fluid"></p>
<p>Fig. F6.2.1&nbsp;Map of study area, showing 293 county features within the states of Iowa, Illinois, and Indiana in the United States</p>
<p>This example uses the ee.Image.reduceRegions&nbsp;operator, which extracts statistics from an Image&nbsp;for each Feature&nbsp;(point or polygon) in a FeatureCollection. We will map the reduceRegions&nbsp;operator over each daily image in an ImageCollection, thus providing us with the daily climate information for each county of interest.</p>
<p>Note that although our example uses a climate ImageCollection, this approach transfers to any ImageCollection, including satellite imagery, as well as image collections that you have already processed, such as cloud masking (Chap. F4.3) or time series aggregation (Chap. F4.2).</p>
<p>First, define the FeatureCollection, ImageCollection, and time period:</p>
<p>// Load county dataset.<br>
// Filter counties in Indiana, Illinois, and Iowa by state FIPS code.<br>
// Select only the unique ID column for simplicity.<br>
var&nbsp;countiesAll = ee.FeatureCollection(TIGER/2018/Counties);<br>
var&nbsp;states = [17, 18, 19];<br>
var&nbsp;uniqueID = GEOID;<br>
var&nbsp;featColl = countiesAll.filter(ee.Filter.inList(STATEFP, states))<br>
&nbsp; &nbsp;.select(uniqueID);</p>
<p>print(featColl.size());<br>
print(featColl.limit(1));</p>
<p>// Visualize target features (create Figure F6.2.1).<br>
Map.centerObject(featColl, 5);<br>
Map.addLayer(featColl);</p>
<p>// specify years of interest<br>
var&nbsp;startYear = 2020;<br>
var&nbsp;endYear = 2020;</p>
<p>// climate dataset info<br>
var&nbsp;imageCollectionName = IDAHO_EPSCOR/GRIDMET;<br>
var&nbsp;bandsWanted = [pr, tmmn, tmmx];<br>
var&nbsp;scale = 4000;</p>
<p>Printing the size of the FeatureCollection&nbsp;indicates that there are 293 counties in our subset. Since we want to pull a daily time series for one year, our final dataset will have 106,945 rows—one for each county-day.</p>
<p>Note that from our county FeatureCollection, we select only the GEOID column, which represents a unique identifier for each record in this dataset. We do this here to simplify print&nbsp;outputs; we could also specify which properties to include in the export function (see below).</p>
<p>Next, load and filter the climate data. Note we adjust the end date to January 1 of the following year, rather than December 31 of the specified year, since the filterDate&nbsp;function has an inclusive start date argument and an exclusive end date argument; without this modification the output would lack data for December 31.</p>
<p>// Load and format climate data.<br>
var&nbsp;startDate = startYear + -01-01;</p>
<p>var&nbsp;endYear_adj = endYear + 1;<br>
var&nbsp;endDate = endYear_adj + -01-01;</p>
<p>var&nbsp;imageCollection = ee.ImageCollection(imageCollectionName)<br>
&nbsp; &nbsp;.select(bandsWanted)<br>
&nbsp; &nbsp;.filterBounds(featColl)<br>
&nbsp; &nbsp;.filterDate(startDate, endDate);</p>
<p>Now get the mean value for each climate attribute within each county feature. Here, we map the ee.Image.reduceRegions&nbsp;call over the ImageCollection, specifying an ee.Reducer.mean&nbsp;reducer. The reducer will apply to each band in the image, and it returns the FeatureCollection&nbsp;with new properties. We also add a date_ymd&nbsp;time property extracted from the image to correctly associate daily values with their date. Finally, we flatten&nbsp;the output to reform a single FeatureCollection with one feature per county-day.</p>
<p>// get values at features<br>
var&nbsp;sampledFeatures = imageCollection.map(function(image) {&nbsp; &nbsp;return&nbsp;image.reduceRegions({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;collection: featColl,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;reducer: ee.Reducer.mean(),<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;scale: scale<br>
&nbsp; &nbsp; &nbsp; &nbsp;}).filter(ee.Filter.notNull(<br>
&nbsp; &nbsp; &nbsp; &nbsp;bandsWanted)) // drop rows with no data&nbsp; &nbsp; &nbsp; &nbsp;.map(function(f) { // add date property&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;time_start = image.get(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;system:time_start);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;dte = ee.Date(time_start).format(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;YYYYMMdd);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;f.set(date_ymd, dte);<br>
&nbsp; &nbsp; &nbsp; &nbsp;});<br>
}).flatten();</p>
<p>print(sampledFeatures.limit(1));</p>
<p>Note that we include a filter to remove feature-day rows that lacked data. While this is less common when using gridded climate products, missing data can be common when reducing satellite images. This is because satellite collections come in scene tiles, and each image tile likely does not overlap all of our features unless it has first been aggregated temporally. It can also occur if a cloud mask has been applied to an image prior to the reduction. By filtering out null&nbsp;values, we can reduce empty rows.</p>
<p>Now explore the result. If we simply print(sampledFeatures)&nbsp;we get our first error message: “User memory limit exceeded.” This is because&nbsp;weve created a FeatureCollection&nbsp;that exceeds the size limits set for interactive mode. How many are there? We could try print(sampledFeatures.size()), but due to the larger size, we receive a “Computation timed out” message—its unable to tell us. Of course, we know that we expect 293 counties x 365 days = 106,945 features. We can, however, check that our reducer has worked as expected by asking Earth Engine for just one feature: print(sampledFeatures.limit(1)).</p>
<p><img src="F6/image75.png" class="img-fluid"></p>
<p>Fig. F6.2.2 Screenshot of the print&nbsp;output for one feature after the reduceRegions&nbsp;call</p>
<p>Here, we can see the precipitation, minimum temperature, and maximum temperature for the county with GEOID = 17121 on January 1, 2020 (Fig. F6.2.2; note temperature is in Kelvin units).</p>
<p>Next, export the full FeatureCollection&nbsp;as a CSV to a folder in your Google Drive. Specify the names of properties to include. Build part of the filename dynamically based on arguments used for year&nbsp;and data scale, so we dont need to manually modify the filenames.</p>
<p>// export info<br>
var&nbsp;exportFolder = GEE_scalingUp;<br>
var&nbsp;filename = Gridmet_counties_IN_IL_IA_&nbsp;+ scale + m_&nbsp;+<br>
&nbsp; &nbsp;startYear + -&nbsp;+ endYear;// prepare export: specify properties/columns to include<br>
var&nbsp;columnsWanted = [uniqueID].concat([date_ymd], bandsWanted);<br>
print(columnsWanted);</p>
<p>Export.table.toDrive({<br>
&nbsp; &nbsp;collection: sampledFeatures,<br>
&nbsp; &nbsp;description: filename,<br>
&nbsp; &nbsp;folder: exportFolder,<br>
&nbsp; &nbsp;fileFormat: CSV,<br>
&nbsp; &nbsp;selectors: columnsWanted<br>
});</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F62a.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>On our first export, this job took about eight minutes to complete, producing a dataset 6.8 MB in size. The data is ready for downstream use but may need formatting to suit the users goals. You can see what the exported CSV looks like in Fig. F6.2.3.</p>
<p><img src="F6/image41.png" class="img-fluid"></p>
<p>Fig. F6.2.3&nbsp;Top six rows of the exported CSV viewed in Microsoft Excel and sorted by county GEOID&nbsp;</p>
<p>Using the Selectors Argument</p>
<p>There are two excellent reasons to use the selectors&nbsp;argument in your Export.table.toDrive&nbsp;call. First, if the argument is not specified, Earth Engine will generate the column names for the exported CSV from the first feature in your FeatureCollection. If that feature is missing properties, those properties will be dropped from the export for all features.</p>
<p>Perhaps even more important if you are seeking to scale up an analysis, including unnecessary columns can greatly increase file size and even processing time. For example, Earth Engine includes a .geo&nbsp;field that contains a GeoJSON description of each spatial feature. For non-simple geometries, the field can be quite large, as it lists coordinates for each polygon vertex. For many purposes, its not necessary to include this information for each daily record (here, 365 daily rows per feature).</p>
<p>For example, when we ran the same job as above but did not use the selectors argument, the output dataset was 5.7 GB (versus 6.8 MB!) and the runtime was slower. This is a cumbersomely large file, with no real benefit. We generally recommend dropping the .geo&nbsp;column and other unnecessary properties. To retain spatial information, a unique identifier for each feature can be used for downstream joins with the spatial data or other properties. If working with point data, latitude and longitude columns can be added prior to export to maintain easily accessible geographic information, although the .geo column for point data is far smaller than for irregularly shaped polygon features.</p>
</section>
<section id="scaling-across-time-by-batching-get-20-years-of-daily-climate-data" class="level3" data-number="10.1.2">
<h3 data-number="10.1.2" class="anchored" data-anchor-id="scaling-across-time-by-batching-get-20-years-of-daily-climate-data"><span class="header-section-number">10.1.2</span> 1.2. Scaling Across Time by Batching: Get 20 Years of Daily Climate Data</h3>
<p>Above, we extracted one year of daily data for our 293 counties. Lets say we want to do the same thing, but for 20012020. We have already written our script to flexibly specify years, so its fairly adaptable to this new use case:</p>
<p>// specify years of interest<br>
var&nbsp;startYear = 2020;<br>
var&nbsp;endYear = 2020;</p>
<p>If we only wanted a few years for a small number of features, we could just modify the startYear&nbsp;or endYear&nbsp;and proceed. Indeed, our current example is modest in size and number of features, and we were able to run 20012020 in one export job that took about two hours, with an output file size of 299 MB. However, with larger feature collections, or hourly data, we will again start to bump up against Earth Engines limits. Generally, jobs of this sort do not fail quickly—exports are allowed to run as long as they continue making progress (see Table F6.2.2). Its not uncommon, however, for a large job to take well over 24 hours to run, or even to fail after more than 24 hours of run time, as it accumulates too many records or a single aggregation fails. For users, this can be frustrating.</p>
<p>We generally find it simpler to run several small jobs rather than one large job. Outputs can then be combined in external software. This avoids any frustration with long-running jobs or delayed failures, and it allows parts of the task to be run simultaneously. Earth Engine generally executes from 220 jobs per user at a time, depending on overall user load (although 20 is rare). As a counterpart, there is some overhead for generating separate jobs.</p>
<p>Important note: When running a batch of jobs, it may be tempting to use multiple accounts to execute subsets of your batch and thus get your shared results faster. However, doing so is a direct violation of the Earth Engine terms of service and can result in your account(s) being terminated.</p>
<p>For-Loops: They Are Sometimes OK</p>
<p>Batching jobs in time is a great way to break up a task into smaller units. Other options include batching jobs by spatial regions defined by polygons (see Sect. 2), or for computationally heavy tasks, batching by both space and time.</p>
<p>Because Export&nbsp;functions are client-side functions, however, you cant create an export within an Earth Engine map&nbsp;command. Instead, we need to loop over the variable that will define our batches and create a set of export tasks.</p>
<p>But wait! Arent we supposed to avoid for-loops at all costs? Yes, within a computational chain. Here, we are using a loop to send multiple computational chains to the server.</p>
<p>First, we will start with the same script as in Sect. 1.1, but we will modify the start year. We will also modify the desired output filename to be a generic base filename, to which we will append the year for each task within the loop (in the next step).</p>
<p>// Load county dataset.<br>
var&nbsp;countiesAll = ee.FeatureCollection(TIGER/2018/Counties);<br>
var&nbsp;states = [17, 18, 19];<br>
var&nbsp;uniqueID = GEOID;<br>
var&nbsp;featColl = countiesAll.filter(ee.Filter.inList(STATEFP, states))<br>
&nbsp; &nbsp;.select(uniqueID);</p>
<p>print(featColl.size());<br>
print(featColl.limit(1));<br>
Map.addLayer(featColl);</p>
<p>// Specify years of interest.<br>
var&nbsp;startYear = 2001;<br>
var&nbsp;endYear = 2020;</p>
<p>// Climate dataset info.<br>
var&nbsp;imageCollectionName = IDAHO_EPSCOR/GRIDMET;<br>
var&nbsp;bandsWanted = [pr, tmmn, tmmx];<br>
var&nbsp;scale = 4000;</p>
<p>// Export info.<br>
var&nbsp;exportFolder = GEE_scalingUp;<br>
var&nbsp;filenameBase = Gridmet_counties_IN_IL_IA_&nbsp;+ scale + m_;</p>
<p>Now modify the code in Sect. 1.1 to use a looping variable,&nbsp;i, to represent each year. Here, we are using standard JavaScript looping syntax, where&nbsp;i&nbsp;will take on each value between our startYear&nbsp;(2001) and our endYear&nbsp;(2020) for each loop through this section of code, thus creating 20 queries to send to Earth Engines servers.</p>
<p>// Initiate a loop, in which the variable i takes on values of each year.<br>
for&nbsp;(var&nbsp;i = startYear; i &lt;= endYear; i++) { &nbsp; &nbsp; &nbsp; &nbsp;// for each year….&nbsp;&nbsp;// Load climate collection for that year.&nbsp;var&nbsp;startDate = i + -01-01;</p>
<p>&nbsp; var&nbsp;endYear_adj = i&nbsp;+ 1;&nbsp;var&nbsp;endDate = endYear_adj + -01-01;&nbsp;var&nbsp;imageCollection = ee.ImageCollection(imageCollectionName)<br>
&nbsp; &nbsp; &nbsp;.select(bandsWanted)<br>
&nbsp; &nbsp; &nbsp;.filterBounds(featColl)<br>
&nbsp; &nbsp; &nbsp;.filterDate(startDate, endDate);&nbsp;// Get values at feature collection.&nbsp;var&nbsp;sampledFeatures = imageCollection.map(function(image) {&nbsp; &nbsp;return&nbsp;image.reduceRegions({<br>
&nbsp; &nbsp; &nbsp;collection: featColl,<br>
&nbsp; &nbsp; &nbsp;reducer: ee.Reducer.mean(), &nbsp; &nbsp; &nbsp; &nbsp;<br>
&nbsp; &nbsp; &nbsp;tileScale: 1,<br>
&nbsp; &nbsp; &nbsp;scale: scale<br>
&nbsp; &nbsp;}).filter(ee.Filter.notNull(bandsWanted)) &nbsp;// remove rows without data&nbsp; &nbsp; &nbsp;.map(function(f) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// add date property&nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;time_start = image.get(system:time_start);&nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;dte = ee.Date(time_start).format(YYYYMMdd);&nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;f.set(date_ymd, dte);<br>
&nbsp; &nbsp;});<br>
&nbsp;}).flatten();&nbsp;// Prepare export: specify properties and filename.&nbsp;var&nbsp;columnsWanted = [uniqueID].concat([date_ymd], bandsWanted);&nbsp;var&nbsp;filename = filenameBase + i;&nbsp;Export.table.toDrive({<br>
&nbsp; &nbsp;collection: sampledFeatures,<br>
&nbsp; &nbsp;description: filename,<br>
&nbsp; &nbsp;folder: exportFolder,<br>
&nbsp; &nbsp;fileFormat: CSV,<br>
&nbsp; &nbsp;selectors: columnsWanted<br>
&nbsp;});<br>
&nbsp;<br>
}</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F62b.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>When we run this script, it builds our computational query for each year, creating a batch of 20 individual jobs that will show up in the Task&nbsp;pane (Fig. F6.2.4). Each task name includes the year, since we used our looping variable i&nbsp;to modify the base filename we specified.</p>
<p><img src="F6/image63.png" class="img-fluid"></p>
<p>Fig. F6.2.4&nbsp;Creation of batch tasks for each year</p>
<p>We now encounter a downside to creating batch tasks within the JavaScript Code Editor: we need to click Run&nbsp;to execute each job in turn. Here, we made this easier by programmatically assigning each job the filename we want, so we can hold the Cmd/Ctrl key and click Run&nbsp;to avoid the export task option window and only need to click once per task. Still, one can imagine that at some number of tasks, ones patience for clicking Run&nbsp;will be exceeded. We assume that number is different for everyone.</p>
<p>Note: If at any time you have submitted several tasks to the server but want to cancel them all, you can do so more easily from the Earth Engine Task Manager&nbsp;that is linked at the top of the Task pane. You can read about that task manager in the Earth Engine User Guide.</p>
<p>In order to auto-execute jobs in batch mode, wed need to use the Python API. Interested users can see the Earth Engine User Guide Python API tutorial&nbsp;for further details about the Python API.&nbsp;</p>
</section>
</section>
<section id="scaling-across-space-via-spatial-tiling" class="level2" data-number="10.2">
<h2 data-number="10.2" class="anchored" data-anchor-id="scaling-across-space-via-spatial-tiling"><span class="header-section-number">10.2</span> Scaling Across Space via Spatial Tiling</h2>
<p>Breaking up jobs in space is another key strategy for scaling operations in Earth Engine. Here, we will focus on making a cloud-free composite from the Sentinel-2 Level 2A&nbsp;Surface Reflectance product. The approach is similar to that in Chap. F4.3,&nbsp;which explores cloud-free compositing. The main difference is that Landsat scenes come with a reliable quality band for each scene, whereas the process for Sentinel-2 is a bit more complicated and computationally intense (see below).</p>
<p>Our region of interest is the state of Washington in the United States for demonstration purposes, but the method will work at much larger continental scales as well.</p>
<p>Cloud Masking Approach</p>
<p>While we do not intend to cover the theory behind Sentinel-2 cloud masking, we do want to include a brief description of the process to convey the computational needs of this approach.</p>
<p>The Sentinel-2 Level 2A collection does not come with a robust cloud mask. Instead, we will build one from related products that have been developed for this purpose. Following the existing Sentinel-2 cloud masking tutorials in the Earth Engine guides, this approach requires three Sentinel-2 image collections:</p>
<ul>
<li>The Sentinel-2 Level 2A Surface Reflectance product. This is the dataset we want to use to build our final composite.</li>
<li>The Sentinel-2 Cloud Probability Dataset, an ImageCollection&nbsp;that contains cloud probabilities for each Sentinel-2 scene.</li>
<li>The Sentinel-2 Level 1C&nbsp;top-of-atmosphere product. This collection is needed to run the Cloud Displacement Index to identify cloud shadows, which is calculated using ee.Algorithms.Sentinel2.CDI&nbsp;(see Frantz et al.&nbsp;2018 for algorithm description).</li>
</ul>
<p>These three image collections all contain 10 m resolution data for every Sentinel-2 scene. We will join them based on their system:index&nbsp;property so we can relate each Level 2A scene with the corresponding cloud probability and cloud displacement index. Furthermore, there are two ee.Image.projection&nbsp;steps to control the scale when calculating clouds and their shadows.</p>
<p>To sum up, the cloud masking approach is computationally costly, thus requiring some thought when applying it at scale.</p>
<section id="generate-a-cloud-free-satellite-composite-limits-to-on-the-fly-computing" class="level3" data-number="10.2.1">
<h3 data-number="10.2.1" class="anchored" data-anchor-id="generate-a-cloud-free-satellite-composite-limits-to-on-the-fly-computing"><span class="header-section-number">10.2.1</span> 2.1.&nbsp;Generate a Cloud-Free Satellite Composite: Limits to On-the-Fly Computing</h3>
<p>Note: Our focus here is on code structure for implementing spatial tiling. Below, we import existing tested functions for cloud masking using the require&nbsp;command.</p>
<p>First, define our region and time of interest; then, load the module&nbsp;containing the cloud functions.</p>
<p>// Set the Region of Interest:Seattle, Washington, United States<br>
var&nbsp;roi = ee.Geometry.Point([-122.33524518034544, 47.61356183942883]);</p>
<p>// Dates over which to create a median composite.<br>
var&nbsp;start = ee.Date(2019-03-01);<br>
var&nbsp;end = ee.Date(2019-09-01);</p>
<p>// Specify module with cloud mask functions.<br>
var&nbsp;s2mask_tools = require(&nbsp; &nbsp;projects/gee-edu/book:Part F - Fundamentals/F6 - Advanced Topics/F6.2 Scaling Up/modules/s2cloudmask.js<br>
);</p>
<p>Next, load and filter our three Sentinel-2 image collections.</p>
<p>// Sentinel-2 surface reflectance data for the composite.<br>
var&nbsp;s2Sr = ee.ImageCollection(COPERNICUS/S2_SR)<br>
&nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp;.filterBounds(roi)<br>
&nbsp; &nbsp;.select([B2, B3, B4, B5]);</p>
<p>// Sentinel-2 Level 1C data (top-of-atmosphere). &nbsp;<br>
// Bands B7, B8, B8A and B10 needed for CDI and the cloud mask function.<br>
var&nbsp;s2 = ee.ImageCollection(COPERNICUS/S2)<br>
&nbsp; &nbsp;.filterBounds(roi)<br>
&nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp;.select([B7, B8, B8A, B10]);</p>
<p>// Cloud probability dataset - used in cloud mask function<br>
var&nbsp;s2c = ee.ImageCollection(COPERNICUS/S2_CLOUD_PROBABILITY)<br>
&nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp;.filterBounds(roi);</p>
<p>Now apply the cloud mask:</p>
<p>// Join the cloud probability dataset to surface reflectance.<br>
var&nbsp;withCloudProbability = s2mask_tools.indexJoin(s2Sr, s2c,&nbsp; &nbsp;cloud_probability);</p>
<p>// Join the L1C data to get the bands needed for CDI.<br>
var&nbsp;withS2L1C = s2mask_tools.indexJoin(withCloudProbability, s2,&nbsp; &nbsp;l1c);</p>
<p>// Map the cloud masking function over the joined collection.<br>
// Cast output to ImageCollection<br>
var&nbsp;masked = ee.ImageCollection(withS2L1C.map(s2mask_tools<br>
.maskImage));</p>
<p>Next, generate and visualize the median composite:</p>
<p>// Take the median, specifying a tileScale to avoid memory errors.<br>
var&nbsp;median = masked.reduce(ee.Reducer.median(), 8);</p>
<p>// Display the results.<br>
Map.centerObject(roi, 12);<br>
Map.addLayer(roi);</p>
<p>var&nbsp;viz = {<br>
&nbsp; &nbsp;bands: [B4_median, B3_median, B2_median],<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 3000<br>
};<br>
Map.addLayer(median, viz, median);</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F62c.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>After about 13 minutes, Earth Engine returns our composite to us on the fly (Fig. F6.2.5). Note that panning and zooming to a new area requires that Earth Engine must again issue the compositing request to calculate the image for new areas. Given the delay, this isnt a very satisfying way to explore our composite.</p>
<p><img src="F6/image49.png" class="img-fluid"><img src="F6/image43.png" class="img-fluid"></p>
<p>Fig. F6.2.5&nbsp;Map view of Seattle, Washington, USA (left) and the corresponding Sentinel-2 composite (right)</p>
<p>Next, expand our view (set zoom to 9) to exceed the limits of on-the-fly computation (Fig. F6.2.6).</p>
<p>Map.centerObject(roi, 9);<br>
Map.addLayer(roi);<br>
Map.addLayer(median, viz, median);</p>
<p><img src="F6/image42.png" class="img-fluid"></p>
<p>Fig. F6.2.6&nbsp;Error message for exceeding memory limits in interactive mode</p>
<p>As you can see, this is an excellent candidate for an export task rather than running in “on-the-fly” interactive mode, as above.</p>
</section>
<section id="generate-a-regional-composite-through-spatial-tiling" class="level3" data-number="10.2.2">
<h3 data-number="10.2.2" class="anchored" data-anchor-id="generate-a-regional-composite-through-spatial-tiling"><span class="header-section-number">10.2.2</span> 2.2.&nbsp;Generate a Regional Composite Through Spatial Tiling</h3>
<p>Our goal is to apply the cloud masking method in Sect. 2.1&nbsp;to the state of Washington, United States. In our testing, we successfully exported one Sentinel-2 composite for this area in about nine hours, but for this tutorial, lets presume we need to split the task up to be successful.</p>
<p>Essentially, we want to split our region of interest up into a regular grid. For each grid, we will export a composite image into a new ImageCollection&nbsp;asset. We can then load and mosaic&nbsp;our composite for use in downstream scripts (see below).</p>
<p>First, generate a spatial polygon grid (FeatureCollection) of desired size over your region of interest (see Fig. F6.2.7):</p>
<p>// Specify helper functions.<br>
var&nbsp;s2mask_tools = require(&nbsp; &nbsp;projects/gee-edu/book:Part F - Fundamentals/F6 - Advanced Topics/F6.2 Scaling Up/modules/s2cloudmask.js<br>
);</p>
<p>// Set the Region of Interest: Washington, USA.<br>
var&nbsp;roi = ee.FeatureCollection(TIGER/2018/States)<br>
&nbsp; &nbsp;.filter(ee.Filter.equals(NAME, Washington));</p>
<p>// Specify grid size in projection, x and y units (based on projection).<br>
var&nbsp;projection = EPSG:4326; // WGS84 lat lon<br>
var&nbsp;dx = 2.5;<br>
var&nbsp;dy = 1.5;</p>
<p>// Dates over which to create a median composite.<br>
var&nbsp;start = ee.Date(2019-03-01);<br>
var&nbsp;end = ee.Date(2019-09-01);</p>
<p>// Make grid and visualize.<br>
var&nbsp;proj = ee.Projection(projection).scale(dx, dy);<br>
var&nbsp;grid = roi.geometry().coveringGrid(proj);</p>
<p>Map.addLayer(roi, {}, roi);<br>
Map.addLayer(grid, {}, grid);</p>
<p><img src="F6/image12.png" class="img-fluid"></p>
<p>Fig. F6.2.7 Visualization of the regular spatial grid generated for use in spatial batch processing</p>
<p>Next, create a new, empty ImageCollection&nbsp;asset to use as our export destination (Assets&nbsp;&gt; New&nbsp;&gt; Image Collection; Fig. F6.2.8). Name the image collection S2_composite_WA and specify the asset location in your user folder (e.g., “path/to/your/asset/s2_composite_WA”).</p>
<p><img src="F6/image15.png" class="img-fluid"></p>
<p>Fig. F6.2.8&nbsp;The “create new image collection asset” menu in the Code Editor</p>
<p>Specify the ImageCollection&nbsp;to export to, along with a base name for each image (the tile number will be appended in the batch export).</p>
<p>// Export info.<br>
var&nbsp;assetCollection = path/to/your/asset/s2_composite_WA;<br>
var&nbsp;imageBaseName = S2_median_;</p>
<p>Extract grid numbers to use as looping variables. Note there is one getInfo&nbsp;call here, which should be used sparingly and never within a for-loop if you can help it. We use it to bring the number of grid cells weve generated onto the client-side to set up the for-loop over grids. Note that if your grid has too many elements, you may need a different strategy.</p>
<p>// Get a list based on grid number.<br>
var&nbsp;gridSize = grid.size().getInfo();<br>
var&nbsp;gridList = grid.toList(gridSize);</p>
<p>Batch generate a composite image task export for each grid via looping:</p>
<p>// In each grid cell, export a composite<br>
for&nbsp;(var&nbsp;i = 0; i &lt; gridSize; i++) {&nbsp; &nbsp;// Extract grid polygon and filter S2 datasets for this region.&nbsp; &nbsp;var&nbsp;gridCell = ee.Feature(gridList.get(i)).geometry();&nbsp; &nbsp;var&nbsp;s2Sr = ee.ImageCollection(COPERNICUS/S2_SR)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterBounds(gridCell)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.select([B2, B3, B4, B5]);&nbsp; &nbsp;var&nbsp;s2 = ee.ImageCollection(COPERNICUS/S2)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterBounds(gridCell)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.select([B7, B8, B8A, B10]);&nbsp; &nbsp;var&nbsp;s2c = ee.ImageCollection(COPERNICUS/S2_CLOUD_PROBABILITY)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterDate(start, end)<br>
&nbsp; &nbsp; &nbsp; &nbsp;.filterBounds(gridCell);&nbsp; &nbsp;// Apply the cloud mask.&nbsp; &nbsp;var&nbsp;withCloudProbability = s2mask_tools.indexJoin(s2Sr, s2c,&nbsp; &nbsp; &nbsp; &nbsp;cloud_probability);&nbsp; &nbsp;var&nbsp;withS2L1C = s2mask_tools.indexJoin(withCloudProbability, s2,&nbsp; &nbsp; &nbsp; &nbsp;l1c);&nbsp; &nbsp;var&nbsp;masked = ee.ImageCollection(withS2L1C.map(s2mask_tools<br>
&nbsp; &nbsp; &nbsp; &nbsp;.maskImage));&nbsp; &nbsp;// Generate a median composite and export.&nbsp; &nbsp;var&nbsp;median = masked.reduce(ee.Reducer.median(), 8);&nbsp; &nbsp;// Export.&nbsp; &nbsp;var&nbsp;imagename = imageBaseName + tile&nbsp;+ i;&nbsp; &nbsp;Export.image.toAsset({<br>
&nbsp; &nbsp; &nbsp; &nbsp;image: median,<br>
&nbsp; &nbsp; &nbsp; &nbsp;description: imagename,<br>
&nbsp; &nbsp; &nbsp; &nbsp;assetId: assetCollection + /&nbsp;+ imagename,<br>
&nbsp; &nbsp; &nbsp; &nbsp;scale: 10,<br>
&nbsp; &nbsp; &nbsp; &nbsp;region: gridCell,<br>
&nbsp; &nbsp; &nbsp; &nbsp;maxPixels: 1e13&nbsp; &nbsp;});<br>
}</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F62d.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Similar to Sect. 1.2,&nbsp;we now have a list of tasks to execute. We can hold the Cmd/Ctrl key and click Run&nbsp;to execute each task (Fig. F6.2.9). Again, users with applications requiring large batches may want to explore the Earth Engine Python API, which is well-suited to batching work. The output ImageCollection&nbsp;is 35.3&nbsp;GB, so you may not want to execute all (or any) of these tasks but can access our pre-generated image, as discussed below.</p>
<p><img src="F6/image22.png" class="img-fluid"></p>
<p>Fig. F6.2.9&nbsp;Spatial batch tasks have been generated and are ready to run</p>
<p>In addition to being necessary for very large regions, batch processing can speed things up for moderate scales. In our tests, tiles averaged about one hour to complete. Because three jobs in our queue were running simultaneously, we covered the full state of Washington in about four hours (compared to about nine hours when tested for the full state of Washington at once). Users should note, however, that there is also an overhead to spinning up each batch task. Finding the balance between task size and task number is a challenge for most Earth Engine users that becomes easier with experience.</p>
<p>In a new script, load the exported ImageCollection&nbsp;and mosaic&nbsp;for use.</p>
<p>// load image collection and mosaic into single image<br>
var&nbsp;assetCollection = projects/gee-book/assets/F6-2/s2_composite_WA;<br>
var&nbsp;composite = ee.ImageCollection(assetCollection).mosaic();</p>
<p>// Display the results<br>
var&nbsp;geometry = ee.Geometry.Point([-120.5873563817392,&nbsp; &nbsp;47.39035206888694<br>
]);<br>
Map.centerObject(geometry, 6);<br>
var&nbsp;vizParams = {<br>
&nbsp; &nbsp;bands: [B4_median, B3_median, B2_median],<br>
&nbsp; &nbsp;min: 0,<br>
&nbsp; &nbsp;max: 3000<br>
};<br>
Map.addLayer(composite, vizParams, median);</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F62e.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p><img src="F6/image16.png" class="img-fluid"></p>
<p>Fig. F6.2.10&nbsp;Sentinel-2 composite covering the state of Washington, loaded from asset. The remaining white colors are snow-capped mountains, not clouds.</p>
<p>Note the ease, speed, and joy of panning and zooming to explore the pre-computed composite asset (Fig. F6.2.10) compared to the on-the-fly version discussed in Sect. 2.1.</p>
</section>
</section>
<section id="multistep-workflows-and-intermediate-assets" class="level2" data-number="10.3">
<h2 data-number="10.3" class="anchored" data-anchor-id="multistep-workflows-and-intermediate-assets"><span class="header-section-number">10.3</span> Multistep Workflows and Intermediate Assets</h2>
<p>Often, our goals require several processing steps that cannot be completed within one Earth Engine computational chain. In these cases, the best strategy becomes breaking down tasks into individual pieces that are created, stored in assets, and used across several scripts. Each sequential script creates an intermediate output, and this intermediate output becomes the input to the next script.</p>
<p>As an example, consider the land use classification task of identifying irrigated agricultural lands. This type of classification can benefit from several types of evidence, including satellite composites, aggregated weather information, soil information, and/or crop type locations. Individual steps for this type of work might include:</p>
<ul>
<li>Generating satellite composites of annual or monthly vegetation indices</li>
<li>Processing climate data into monthly or seasonal values</li>
<li>Generating random point locations from a ground truth layer for use as a feature training dataset and accuracy validation, and extracting composite and weather values at these features</li>
<li>Training a classifier and applying it, possibly across multiple years; researchers will often implement multiple classifiers and compare the performance of different methods</li>
<li>Implementing post-classification cleaning steps, such as removing “speckle”</li>
<li>Evaluating accuracy at ground truth validation points, and against government statistics using total area per administrative boundary</li>
<li>Exporting your work as spatial layers, visualizations, or other formats</li>
</ul>
<p>Multipart workflows can become unwieldy to manage, particularly if there are multiple collaborators or the project has a long timeline; it can be difficult to remember why each script was developed and where it fits in the overall workflow.</p>
<p>Here, we provide tips for managing multipart workflows. These are somewhat opinionated and based largely on concepts from “Good Enough Practices in Scientific Computing” (Wilson et al.&nbsp;2017). Ultimately, your personal workflow practices will be a combination of what works for you, what works for your larger team and organization, and, hopefully, what works for good documentation and reproducibility.</p>
<p>Tip 1. Create a repository for each project</p>
<p>The repository can be considered the fundamental project unit. In Earth Engine, sharing permissions are set for each individual repository, so this allows you to share a specific project with others (see Chap.&nbsp;F6.1).</p>
<p>By default, Earth Engine saves new scripts in a “default” repository specific for each user (users/<username>/default). You can create new repositories on the Scripts&nbsp;tab of the Code Editor (Fig. F6.2.11).</username></p>
<p><img src="F6/image20.png" class="img-fluid"></p>
<p>Fig. F6.2.11&nbsp;The Code Editor menu for creating new repositories</p>
<p>To adjust permissions for each repository, click on the Gear icon (Fig. F6.2.12):</p>
<p><img src="F6/image67.png" class="img-fluid"></p>
<p>Fig. F6.2.12&nbsp;Access the sharing and permissions menu for each repository by clicking the Gear icon</p>
<p>For users familiar with version control, Earth Engine uses a git-based script manager, so each repository can also be managed, edited, and/or synced with your local copy or collaborative spaces like GitHub.</p>
<p>Tip 2. Make a separate script for each step, and make script file names informative and self-sorting</p>
<p>Descriptive, self-sorting filenames are an excellent “good enough” way to keep your projects organized. We recommend starting script names with zero-padded numeric values to take advantage of default ordering. Because we are generating assets in early scripts that are used in later scripts, its important to preserve the order of your workflow. The name should also include short descriptions of what the script does (Fig. F6.2.13).</p>
<p><img src="F6/image62.png" class="img-fluid"></p>
<p>Fig. F6.2.13&nbsp;An example project repository with multiple scripts. Using leading numbers when naming scripts allows you to order them by their position in the workflow.</p>
<p>Leaving some decimal places between successive scripts gives you the ability to easily insert any additional steps you didnt originally anticipate. And zero-padding means your self-sorting still works once you move into double-digit numbers.</p>
<p>Other script organization strategies might involve including subfolders to collect scripts related to main steps. For example, one could have a subfolder “04_classifiers” to keep alternative classification scripts in one place, using a more tree-based file structure. Again, each user or group will develop a system that works for them. The important part is to have an organizational system.</p>
<p>Tip 3. Consider data types and file sizes when storing intermediates</p>
<p>Images and image collections are common intermediate file types, since generating satellite composites or creating land use classifications tends to be computationally intensive. These assets can also be quite large, depending on the resolution and region size. Recall that our single-year, subregional Sentinel-2 composite in Sect. 2 was about 23 GB.</p>
<p>Image values can be stored from 8-bit integers to 64-bit double floats (numbers with decimals). Higher bits allow for more precision, but have much larger file sizes and are not always necessary. For example, if we are generating a land use map with five classes, we can convert that to a signed or unsigned 8-bit integer using toInt8&nbsp;or toUint8&nbsp;prior to exporting to asset, which can accommodate 256 unique values. This results in a smaller file size. Selectively retaining only bands of interest is also helpful to reduce size.</p>
<p>For cases requiring decimals and precision, consider whether a 32-bit float will do the job instead of a 64-bit double—toFloat&nbsp;will convert an image to a 32-bit float. If you find you need&nbsp;to conserve storage, you can also scale float values and store as an integer image (image.multiply(100).toInt16(), for example). This would retain precision to the second decimal place and reduce file size by a factor of two. Note that this may require you to unscale the values in downstream use. Ultimately, the appropriate data type will be specific to your needs.</p>
<p>And of course, as mentioned above under “The Importance of Best Coding Practices,” be aware of the scale resolution you are working at, and avoid using unnecessarily high resolution when its not supported by either the input imagery or your research goals.</p>
<p>Tip 4. Consider Google Cloud Platform for hosting larger intermediates</p>
<p>If you are working with very large or very many files, you can link Earth Engine with Cloud Projects on Google Cloud Platform. See the Earth Engine documentation on “Setting Up Earth Engine Enabled Cloud Projects”&nbsp;for more information.</p>
</section>
<section id="synthesis-2" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="synthesis-2">Synthesis</h2>
<p>Earth Engine is built to be scaled. Scaling up working scripts, however, can present challenges when the computations take too long or return results that are too large or numerous. We have covered some key strategies to use when you encounter memory or computational limits. Generally, they involve 1) optimizing your code based on Earth Engines functions and infrastructure; 2) working at scales appropriate for your data, question, and region of interest, and not at higher resolutions than necessary; and 3) breaking up tasks into discrete units.</p>
</section>
<section id="references-2" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="references-2">References</h2>
<p>Abatzoglou JT (2013) Development of gridded surface meteorological data for ecological applications and modelling. Int J Climatol 33:121131. https://doi.org/10.1002/joc.3413</p>
<p>Frantz D, Haß E, Uhl A, et al (2018) Improvement of the Fmask algorithm for Sentinel-2 images: Separating clouds from bright surfaces based on parallax effects. Remote Sens Environ 215:471481. https://doi.org/10.1016/j.rse.2018.04.046</p>
<p>Gorelick N, Hancher M, Dixon M, et al (2017) Google Earth Engine: Planetary-scale geospatial analysis for everyone. Remote Sens Environ 202:1827. https://doi.org/10.1016/j.rse.2017.06.031</p>
<p>Wilson G, Bryan J, Cranston K, et al (2017) Good enough practices in scientific computing. PLoS Comput Biol 13:e1005510. https://doi.org/10.1371/journal.pcbi.1005510</p>
</section>
</section>
<section id="sharing-work-in-earth-engine-basic-ui-and-apps" class="level1" data-number="11">
<h1 data-number="11"><span class="header-section-number">11</span> Sharing Work in Earth Engine: Basic UI and Apps</h1>
<div class="callout-tip callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Chapter Information
</div>
</div>
<div class="callout-body-container callout-body">
<section id="author-3" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="author-3">Author</h2>
<p>Qiusheng&nbsp;Wu</p>
</section>
<section id="overview-3" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="overview-3">Overview</h2>
<p>The purpose of this chapter is to demonstrate&nbsp;how to design and publish Earth Engine Apps using both JavaScript and Python. You will be introduced to the Earth Engine User Interface&nbsp;JavaScript API and the geemap&nbsp;Python package. Upon completion of this chapter, you will be able to publish an Earth Engine App with a split-panel map for visualizing land cover change.</p>
</section>
<section id="learning-outcomes-3" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="learning-outcomes-3">Learning Outcomes</h2>
<ul>
<li>Designing a user interface&nbsp;for an Earth Engine App using JavaScript.</li>
<li>Publishing an Earth Engine App for visualizing land cover change.</li>
<li>Developing an Earth Engine App using Python and geemap.</li>
<li>Deploying an Earth Engine App using a local computer as a web server.</li>
<li>Publishing an Earth Engine App using Python and free cloud platforms.</li>
<li>Creating&nbsp;a conda&nbsp;environment using Anaconda/Miniconda.</li>
<li>Installing Python packages and using Jupyter Notebook.</li>
<li>Commiting changes to a GitHub repository.</li>
</ul>
</section>
<section id="assumes-you-know-how-to-3" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="assumes-you-know-how-to-3">Assumes you know how to:</h2>
<ul>
<li>Import images and image collections, filter, and visualize (Part F1).</li>
<li>Use the basic functions and logic of Python.</li>
</ul>
</section>
</div>
</div>
<section id="introduction-3" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="introduction-3">Introduction</h2>
<p>Earth Engine has a user interface API that allows users to build and publish interactive web apps directly from the JavaScript Code Editor. Many readers will have encountered a call to ui.Chart in other chapters, but much more interface functionality is available. In particular, users can utilize the ui&nbsp;functions to construct an entire graphical user interface (GUI) for their Earth Engine script. The GUI may include simple widgets (e.g., labels, buttons, checkboxes, sliders, text boxes) as well as more complex widgets (e.g., charts, maps, panels) for controlling the GUI layout. A complete list of the ui&nbsp;widgets and more information about panels can be found at the links below. Once a GUI is constructed, users can publish the App from the JavaScript Code Editor by clicking the Apps&nbsp;button above the script panel in the Code Editor.</p>
<ul>
<li>Widgets: <a href="https://www.google.com/url?q=https://developers.google.com/earth-engine/guides/ui_widgets&amp;sa=D&amp;source=editors&amp;ust=1671458841273029&amp;usg=AOvVaw10aLP4KU7kHJTwcnM5Pr4-">https://developers.google.com/earth-engine/guides/ui_widgets</a></li>
<li>Panels: <a href="https://www.google.com/url?q=https://developers.google.com/earth-engine/guides/ui_panels&amp;sa=D&amp;source=editors&amp;ust=1671458841273501&amp;usg=AOvVaw1XwxHFyCULBnago_-VpDUq">https://developers.google.com/earth-engine/guides/ui_panels</a>&nbsp;</li>
</ul>
<p>Unlike&nbsp;the Earth Engine JavaScript API, the Earth Engine Python API does not provide functionality for building interactive user interfaces. Fortunately, the Jupyter ecosystem has ipywidgets, an architecture for creating interactive user interface controls (e.g., buttons, sliders, checkboxes, text boxes, dropdown lists) in Jupyter notebooks&nbsp;that communicate with Python code. The integration of graphical widgets into the Jupyter Notebook workflow allows users to configure ad hoc control panels to interactively sweep over parameters using graphical widget controls. One very powerful widget is the output&nbsp;widget, which can be used to display rich output generated by IPython, such as text, images, charts, and videos. A complete list of widgets and more information about the output widget can be found at the links below. By integrating ipyleaflet&nbsp;(for creating interactive maps) and ipywidgets&nbsp;(for designing interactive user interfaces), the geemap&nbsp;Python package (<a href="https://www.google.com/url?q=https://geemap.org&amp;sa=D&amp;source=editors&amp;ust=1671458841274245&amp;usg=AOvVaw3rVUS8lKoS9clb8r42oxe0">https://geemap.org</a>) makes it much easier to explore and analyze massive Earth Engine datasets via a web browser in a Jupyter environment suitable for interactive exploration, teaching, and sharing. Users can build interactive Earth Engine Apps using geemap&nbsp;with minimal coding (Fig. F6.3.1).</p>
<ul>
<li>Widgets: <a href="https://www.google.com/url?q=https://ipywidgets.readthedocs.io/en/latest/examples/Widget%2520List.html&amp;sa=D&amp;source=editors&amp;ust=1671458841274780&amp;usg=AOvVaw2SzMGW9F4r3E-llbiCksCA">https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html</a>&nbsp;</li>
<li>Output: <a href="https://www.google.com/url?q=https://ipywidgets.readthedocs.io/en/latest/examples/Output%2520Widget.html&amp;sa=D&amp;source=editors&amp;ust=1671458841275255&amp;usg=AOvVaw13a3qsly2f0mg0egoTAMqw">https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html</a>&nbsp;</li>
</ul>
<p><img src="F6/image1.png" class="img-fluid"></p>
<p>Fig. F6.3.1&nbsp;The GUI of geemap&nbsp;in a Jupyter environment</p>
</section>
<section id="building-an-earth-engine-app-using-javascript" class="level2" data-number="11.1">
<h2 data-number="11.1" class="anchored" data-anchor-id="building-an-earth-engine-app-using-javascript"><span class="header-section-number">11.1</span> Building an Earth Engine App Using JavaScript</h2>
<p>In this section, you will learn how to design a user interface&nbsp;for an Earth Engine App using JavaScript and the Earth Engine User Interface API. Upon completion of this section, you will have an Earth Engine App with a split-panel map for visualizing land cover change using the Landsat-based United States Geological Survey National Land Cover Database (NLCD).</p>
<p>First, lets define a function for filtering the NLCD ImageCollection&nbsp;by year and select the landcover&nbsp;band. The function returns an Earth Engine ui.Map.Layer&nbsp;of the landcover&nbsp;band of the selected NLCD image. Note that as of this writing, NLCD spans nine epochs: 1992, 2001, 2004, 2006, 2008, 2011, 2013, 2016, and 2019. The 1992 data are primarily based on unsupervised classification of Landsat data, while the rest of the images rely on the imperviousness data layer for the urban classes and on a decision-tree classification for the rest. The 1992 image is not directly comparable to any later editions of NLCD (see the Earth Engine Data Catalog for more details, if needed). Therefore, we will use only the eight epochs after 2000 in this lab. &nbsp;</p>
<p>// Get an NLCD image by year.<br>
var&nbsp;getNLCD = function(year) {&nbsp; &nbsp;// Import the NLCD collection.&nbsp; &nbsp;var&nbsp;dataset = ee.ImageCollection(&nbsp; &nbsp; &nbsp; &nbsp;USGS/NLCD_RELEASES/2019_REL/NLCD);&nbsp; &nbsp;// Filter the collection by year.&nbsp; &nbsp;var&nbsp;nlcd = dataset.filter(ee.Filter.eq(system:index, year))<br>
&nbsp; &nbsp; &nbsp; &nbsp;.first();&nbsp; &nbsp;// Select the land cover band.&nbsp; &nbsp;var&nbsp;landcover = nlcd.select(landcover);&nbsp; &nbsp;return&nbsp;ui.Map.Layer(landcover, {}, year);<br>
};</p>
<p>Our intention is to create a dropdown list so that when a particular epoch is selected, the corresponding NLCD image layer will be displayed on the map. Well define a dictionary with each NLCD epoch as the key and its corresponding NLCD image layer as the value. The keys of the dictionary (i.e., the&nbsp;eight NLCD epochs) will be used as the input to the dropdown lists (ui.Select) on the split-level map.</p>
<p>// Create a dictionary with each year as the key<br>
// and its corresponding NLCD image layer as the value.<br>
var&nbsp;images = {&nbsp; &nbsp;2001: getNLCD(2001),&nbsp; &nbsp;2004: getNLCD(2004),&nbsp; &nbsp;2006: getNLCD(2006),&nbsp; &nbsp;2008: getNLCD(2008),&nbsp; &nbsp;2011: getNLCD(2011),&nbsp; &nbsp;2013: getNLCD(2013),&nbsp; &nbsp;2016: getNLCD(2016),&nbsp; &nbsp;2019: getNLCD(2019),<br>
};</p>
<p>The split-panel map is composed of two individual maps, leftMap&nbsp;and rightMap. The map controls (e.g., zoomControl, scaleControl, mapTypeControl) will be shown only on rightMap. A control panel (ui.Panel) composed of a label (ui.Label) and a dropdown list (ui.Select) is added to each map. When an&nbsp;NLCD&nbsp;epoch is selected from a dropdown list, the function updateMap&nbsp;will be called to show the corresponding image layer of the selected epoch.</p>
<p>// Create the left map, and have it display the first layer.<br>
var&nbsp;leftMap = ui.Map();<br>
leftMap.setControlVisibility(false);<br>
var&nbsp;leftSelector = addLayerSelector(leftMap, 0, top-left);</p>
<p>// Create the right map, and have it display the last layer.<br>
var&nbsp;rightMap = ui.Map();<br>
rightMap.setControlVisibility(true);<br>
var&nbsp;rightSelector = addLayerSelector(rightMap, 7, top-right);</p>
<p>// Adds a layer selection widget to the given map, to allow users to<br>
// change which image is displayed in the associated map.<br>
function&nbsp;addLayerSelector(mapToChange, defaultValue, position) {&nbsp; &nbsp;var&nbsp;label = ui.Label(Select a year:);&nbsp; &nbsp;// This function changes the given map to show the selected image.&nbsp; &nbsp;function&nbsp;updateMap(selection) {<br>
&nbsp; &nbsp; &nbsp; &nbsp;mapToChange.layers().set(0, images[selection]);<br>
&nbsp; &nbsp;}&nbsp; &nbsp;// Configure a selection dropdown to allow the user to choose&nbsp; &nbsp;// between images, and set the map to update when a user&nbsp; &nbsp;// makes a selection.&nbsp; &nbsp;var&nbsp;select = ui.Select({<br>
&nbsp; &nbsp; &nbsp; &nbsp;items: Object.keys(images),<br>
&nbsp; &nbsp; &nbsp; &nbsp;onChange: updateMap<br>
&nbsp; &nbsp;});<br>
&nbsp; &nbsp;select.setValue(Object.keys(images)[defaultValue], true);&nbsp; &nbsp;var&nbsp;controlPanel =&nbsp; &nbsp; &nbsp; &nbsp;ui.Panel({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;widgets: [label, select],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;position: position<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br>
&nbsp; &nbsp; &nbsp; &nbsp;});</p>
<p>&nbsp; &nbsp;mapToChange.add(controlPanel);<br>
}</p>
<p>When displaying a land cover classification image on the Map, it would be useful to add a legend to make it easier for users to interpret the land cover type associated with each color. Lets define a dictionary that will be used to construct the legend. The dictionary contains two keys: names&nbsp;(a list of land cover types) and colors&nbsp;(a list of colors associated with each land cover type). The legend will be placed in the bottom right of the Map. &nbsp; &nbsp;</p>
<p>// Set the legend title.<br>
var&nbsp;title = NLCD Land Cover Classification;</p>
<p>// Set the legend position.<br>
var&nbsp;position = bottom-right;</p>
<p>// Define a dictionary that will be used to make a legend<br>
var&nbsp;dict = {&nbsp; &nbsp;names: [&nbsp; &nbsp; &nbsp; &nbsp;11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Open Water,&nbsp; &nbsp; &nbsp; &nbsp;12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Perennial Ice/Snow,&nbsp; &nbsp; &nbsp; &nbsp;21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Developed, Open Space,&nbsp; &nbsp; &nbsp; &nbsp;22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Developed, Low Intensity,&nbsp; &nbsp; &nbsp; &nbsp;23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Developed, Medium Intensity,&nbsp; &nbsp; &nbsp; &nbsp;24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Developed, High Intensity,&nbsp; &nbsp; &nbsp; &nbsp;31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Barren Land (Rock/Sand/Clay),&nbsp; &nbsp; &nbsp; &nbsp;41&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Deciduous Forest,&nbsp; &nbsp; &nbsp; &nbsp;42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Evergreen Forest,&nbsp; &nbsp; &nbsp; &nbsp;43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Mixed Forest,&nbsp; &nbsp; &nbsp; &nbsp;51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dwarf Scrub,&nbsp; &nbsp; &nbsp; &nbsp;52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Shrub/Scrub,&nbsp; &nbsp; &nbsp; &nbsp;71&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Grassland/Herbaceous,&nbsp; &nbsp; &nbsp; &nbsp;72&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Sedge/Herbaceous,&nbsp; &nbsp; &nbsp; &nbsp;73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lichens,&nbsp; &nbsp; &nbsp; &nbsp;74&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Moss,&nbsp; &nbsp; &nbsp; &nbsp;81&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Pasture/Hay,&nbsp; &nbsp; &nbsp; &nbsp;82&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cultivated Crops,&nbsp; &nbsp; &nbsp; &nbsp;90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Woody Wetlands,&nbsp; &nbsp; &nbsp; &nbsp;95&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Emergent Herbaceous Wetlands,<br>
&nbsp; &nbsp;],&nbsp; &nbsp;colors: [&nbsp; &nbsp; &nbsp; &nbsp;#466b9f, #d1def8, #dec5c5, #d99282, #eb0000,&nbsp; &nbsp; &nbsp; &nbsp;#ab0000,&nbsp; &nbsp; &nbsp; &nbsp;#b3ac9f, #68ab5f, #1c5f2c, #b5c58f, #af963c,&nbsp; &nbsp; &nbsp; &nbsp;#ccb879,&nbsp; &nbsp; &nbsp; &nbsp;#dfdfc2, #d1d182, #a3cc51, #82ba9e, #dcd939,&nbsp; &nbsp; &nbsp; &nbsp;#ab6c28,&nbsp; &nbsp; &nbsp; &nbsp;#b8d9eb, #6c9fb8,<br>
&nbsp; &nbsp;]<br>
};</p>
<p>With the legend dictionary defined above, we can now create a panel to hold the legend widget and add it to the Map. Each row on the legend widget is composed of a color box followed by its corresponding land cover type.</p>
<p>// Create a panel to hold the legend widget.<br>
var&nbsp;legend = ui.Panel({<br>
&nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp;position: position,<br>
&nbsp; &nbsp; &nbsp; &nbsp;padding: 8px 15px&nbsp; &nbsp;}<br>
});// Function to generate the legend.<br>
function&nbsp;addCategoricalLegend(panel, dict, title) {&nbsp; &nbsp;// Create and add the legend title.&nbsp; &nbsp;var&nbsp;legendTitle = ui.Label({<br>
&nbsp; &nbsp; &nbsp; &nbsp;value: title,<br>
&nbsp; &nbsp; &nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fontWeight: bold,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fontSize: 18px,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;margin: 0 0 4px 0,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;padding: 0&nbsp; &nbsp; &nbsp; &nbsp;}<br>
&nbsp; &nbsp;});<br>
&nbsp; &nbsp;panel.add(legendTitle);&nbsp; &nbsp;var&nbsp;loading = ui.Label(Loading legend…, {<br>
&nbsp; &nbsp; &nbsp; &nbsp;margin: 2px 0 4px 0&nbsp; &nbsp;});<br>
&nbsp; &nbsp;panel.add(loading);&nbsp; &nbsp;// Creates and styles 1 row of the legend.&nbsp; &nbsp;var&nbsp;makeRow = function(color, name) {&nbsp; &nbsp; &nbsp; &nbsp;// Create the label that is actually the colored box.&nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;colorBox = ui.Label({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;backgroundColor: color,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// Use padding to give the box height and width.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;padding: 8px,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;margin: 0 0 4px 0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br>
&nbsp; &nbsp; &nbsp; &nbsp;});&nbsp; &nbsp; &nbsp; &nbsp;// Create the label filled with the description text.&nbsp; &nbsp; &nbsp; &nbsp;var&nbsp;description = ui.Label({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;value: name,<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;margin: 0 0 4px 6px&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br>
&nbsp; &nbsp; &nbsp; &nbsp;});&nbsp; &nbsp; &nbsp; &nbsp;return&nbsp;ui.Panel({<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;widgets: [colorBox, description],<br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;layout: ui.Panel.Layout.Flow(horizontal)<br>
&nbsp; &nbsp; &nbsp; &nbsp;});<br>
&nbsp; &nbsp;};&nbsp; &nbsp;// Get the list of palette colors and class names from the image.&nbsp; &nbsp;var&nbsp;palette = dict.colors;&nbsp; &nbsp;var&nbsp;names = dict.names;<br>
&nbsp; &nbsp;loading.style().set(shown, false);&nbsp; &nbsp;for&nbsp;(var&nbsp;i = 0; i &lt; names.length; i++) {<br>
&nbsp; &nbsp; &nbsp; &nbsp;panel.add(makeRow(palette[i], names[i]));<br>
&nbsp; &nbsp;}</p>
<p>&nbsp; &nbsp;rightMap.add(panel);</p>
<p>}</p>
<p>The last step is to create a split-panel map to hold the linked maps (leftMap&nbsp;and rightMap) and tie everything together. When users pan and zoom one map, the other map will also be panned and zoomed to the same extent automatically. When users select a year from a dropdown list,&nbsp;the image layer will be updated accordingly. Users can use the slider to swipe through and visualize land cover change easily (Fig. F6.3.2). Please make sure you minimize the Code Editor and maximize the Map&nbsp;so that you can see the dropdown widget in the upper-right corner of the map.</p>
<p>addCategoricalLegend(legend, dict, title);</p>
<p>// Create a SplitPanel to hold the adjacent, linked maps.<br>
var&nbsp;splitPanel = ui.SplitPanel({<br>
&nbsp; &nbsp;firstPanel: leftMap,<br>
&nbsp; &nbsp;secondPanel: rightMap,<br>
&nbsp; &nbsp;wipe: true,<br>
&nbsp; &nbsp;style: {<br>
&nbsp; &nbsp; &nbsp; &nbsp;stretch: both&nbsp; &nbsp;}<br>
});// Set the SplitPanel as the only thing in the UI root.<br>
ui.root.widgets().reset([splitPanel]);<br>
var&nbsp;linker = ui.Map.Linker([leftMap, rightMap]);<br>
leftMap.setCenter(-100, 40, 4);</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F63a.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p><img src="F6/image32.png" class="img-fluid"></p>
<p>Fig. F6.3.2&nbsp;A split-panel map for visualizing land cover change using NLCD</p>
</section>
<section id="publishing-an-earth-engine-app-from-the-code-editor" class="level2" data-number="11.2">
<h2 data-number="11.2" class="anchored" data-anchor-id="publishing-an-earth-engine-app-from-the-code-editor"><span class="header-section-number">11.2</span> Publishing an Earth Engine App from the Code Editor</h2>
<p>The goal of this section is to publish the Earth Engine App that we created in Sect. 1. The look and feel of interfaces changes often; if the exact windows described below change over time, the concepts should remain stable to help you to publish your App. First, load the script (see the Code Checkpoint in Sect. 1) into the Code Editor. Then, open the Manage Apps&nbsp;panel by clicking the Apps&nbsp;button above the script panel in the Code Editor (Fig. F6.3.3).</p>
<p><img src="F6/image14.png" class="img-fluid"></p>
<p>Fig. F6.3.3&nbsp;The Apps&nbsp;button in the JavaScript Code Editor</p>
<p>Now click on the New App&nbsp;button (Fig. F6.3.4).</p>
<p><img src="F6/image61.png" class="img-fluid"></p>
<p>Fig. F6.3.4&nbsp;The New App&nbsp;button</p>
<p>In the Publish New App&nbsp;dialog (Fig. F6.3.5), choose a name for the App (e.g., NLCD Land Cover Change), select a Google Cloud Project, provide a thumbnail to be shown in the Public Apps Gallery, and specify the location of the Apps source code. You may restrict access to the App to a particular Google Group or make it publicly accessible. Check Feature this app in your Public Apps Gallery&nbsp;if you would like this App to appear in your public gallery of Apps available at https://USERNAME.users.earthengine.app. When all fields are filled out and validated, the Publish&nbsp;button will be enabled; click it to complete publishing the App.&nbsp;</p>
<p><img src="F6/image37.png" class="img-fluid"></p>
<p>Fig. F6.3.5&nbsp;The Publish New App&nbsp;dialog</p>
<p>To manage an App from the Code Editor, open the Manage Apps&nbsp;panel&nbsp;(Fig. F6.3.6)&nbsp;by clicking the Apps&nbsp;button above the script&nbsp;panel in the Code Editor (Fig. F6.3.3). There, you can update your Apps configuration or delete the App.</p>
<p><img src="F6/image65.png" class="img-fluid"></p>
<p>Fig. F6.3.6&nbsp;The Manage Apps&nbsp;panel</p>
</section>
<section id="developing-an-earth-engine-app-using-geemap" class="level2" data-number="11.3">
<h2 data-number="11.3" class="anchored" data-anchor-id="developing-an-earth-engine-app-using-geemap"><span class="header-section-number">11.3</span> Developing an Earth Engine App Using geemap</h2>
<p>In this section, you will learn how to develop an Earth Engine App using the geemap&nbsp;Python package and Jupyter Notebook. The geemap&nbsp;package is available on both <a href="https://www.google.com/url?q=https://pypi.org/project/leafmap&amp;sa=D&amp;source=editors&amp;ust=1671458841303040&amp;usg=AOvVaw25Lg4yxDI4b31VJovOcUOc">PyPI</a>&nbsp;(pip)&nbsp;and <a href="https://www.google.com/url?q=https://anaconda.org/conda-forge/leafmap&amp;sa=D&amp;source=editors&amp;ust=1671458841303462&amp;usg=AOvVaw267tbdQ3NyyHL53Jmk4W4k">conda-forge</a>. It is highly recommended that you create a fresh conda environment to install geemap.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F63b.&nbsp;The books repository contains information about setting up a conda environment and installing geemap.</p>
</div>
</div>
<p>Once you have launched a Jupyter notebook in your browser, you can continue with the next steps of the lab.</p>
<p>On the Jupyter Notebook&nbsp;interface, click the New&nbsp;button in the upper-right corner and select Python 3&nbsp;to create a new notebook. Change the name of the notebook from “Untitled” to something meaningful (e.g., “nlcd_app”). With the newly created notebook, we can start writing and executing Python code.</p>
<p>First, lets import the Earth Engine and geemap&nbsp;libraries. Press&nbsp;Alt + Enter&nbsp;to&nbsp;execute the code and create a new cell below.</p>
<p>import&nbsp;eeimport&nbsp;geemap</p>
<p>Create an interactive map by specifying the map center (latitude, longitude) and zoom level (118). If this is the first time you use geemap&nbsp;and the Earth Engine Python API, a new tab will open in your browser asking you to authenticate Earth Engine. Follow the on-screen instructions to authenticate Earth Engine.</p>
<p>Map = geemap.Map(center=[40, -100], zoom=4)<br>
Map</p>
<p>Retrieve the NLCD 2019 image by filtering the NLCD ImageCollection&nbsp;and selecting the landcover&nbsp;band. Display the NLCD 2019 image on the interactive Map&nbsp;by using Map.addLayer.</p>
</section>
</section>
<section id="import-the-nlcd-collection." class="level1" data-number="12">
<h1 data-number="12"><span class="header-section-number">12</span> Import the NLCD collection.</h1>
<p>dataset = ee.ImageCollection(USGS/NLCD_RELEASES/2019_REL/NLCD)</p>
</section>
<section id="filter-the-collection-to-the-2019-product." class="level1" data-number="13">
<h1 data-number="13"><span class="header-section-number">13</span> Filter the collection to the 2019 product.</h1>
<p>nlcd2019 = dataset.filter(ee.Filter.eq(system:index, 2019)).first()</p>
</section>
<section id="select-the-land-cover-band." class="level1" data-number="14">
<h1 data-number="14"><span class="header-section-number">14</span> Select the land cover band.</h1>
<p>landcover = nlcd2019.select(landcover)</p>
</section>
<section id="display-land-cover-on-the-map.map.addlayerlandcover-nlcd-2019" class="level1" data-number="15">
<h1 data-number="15"><span class="header-section-number">15</span> Display land cover on the map.Map.addLayer(landcover, {}, NLCD 2019)</h1>
<p>Map</p>
<p>Next, add the NLCD legend to the Map. The geemap&nbsp;package has several built-in legends, including the NLCD legend. Therefore, you can add the NLCD legend to the Map&nbsp;by using just one line of code (Map.add_legend).</p>
<p>title = NLCD Land Cover Classification<br>
Map.add_legend(legend_title=title, builtin_legend=NLCD)</p>
<p>Alternatively, if you want to add a custom legend, you can define a legend dictionary with labels as keys and colors as values, then you can use Map.add_legend&nbsp;to add the custom legend to the Map. &nbsp;</p>
<p>legend_dict = {&nbsp; &nbsp;11 Open Water: 466b9f,&nbsp; &nbsp;12 Perennial Ice/Snow: d1def8,&nbsp; &nbsp;21 Developed, Open Space: dec5c5,&nbsp; &nbsp;22 Developed, Low Intensity: d99282,&nbsp; &nbsp;23 Developed, Medium Intensity: eb0000,&nbsp; &nbsp;24 Developed High Intensity: ab0000,&nbsp; &nbsp;31 Barren Land (Rock/Sand/Clay): b3ac9f,&nbsp; &nbsp;41 Deciduous Forest: 68ab5f,&nbsp; &nbsp;42 Evergreen Forest: 1c5f2c,&nbsp; &nbsp;43 Mixed Forest: b5c58f,&nbsp; &nbsp;51 Dwarf Scrub: af963c,&nbsp; &nbsp;52 Shrub/Scrub: ccb879,&nbsp; &nbsp;71 Grassland/Herbaceous: dfdfc2,&nbsp; &nbsp;72 Sedge/Herbaceous: d1d182,&nbsp; &nbsp;73 Lichens: a3cc51,&nbsp; &nbsp;74 Moss: 82ba9e,&nbsp; &nbsp;81 Pasture/Hay: dcd939,&nbsp; &nbsp;82 Cultivated Crops: ab6c28,&nbsp; &nbsp;90 Woody Wetlands: b8d9eb,&nbsp; &nbsp;95 Emergent Herbaceous Wetlands: 6c9fb8}<br>
title = NLCD Land Cover Classification<br>
Map.add_legend(legend_title=title, legend_dict=legend_dict)</p>
<p>The Map&nbsp;with the NLCD 2019 image and legend should look like Fig. F6.3.7.</p>
<p><img src="F6/image60.png" class="img-fluid"></p>
<p>Fig. F6.3.7&nbsp;The NLCD 2019 image layer displayed in geemap</p>
<p>The Map&nbsp;above shows only the NLCD 2019 image. To create an Earth Engine App for visualizing land cover change, we need a stack of NLCD images. Lets print the list of system IDs of all available NLCD images.</p>
<p>dataset.aggregate_array(system:id).getInfo()</p>
<p>The output should look like this.</p>
<p>[USGS/NLCD_RELEASES/2019_REL/NLCD/2001,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2004,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2006,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2008,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2011,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2013,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2016,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2019]</p>
<p>Select the eight NLCD epochs after 2000.</p>
<p>years = [2001, 2004, 2006, 2008, 2011, 2013, 2016, 2019]</p>
<p>Define a function for filtering the NLCD ImageCollection&nbsp;by year and select the landcover&nbsp;band.</p>
</section>
<section id="get-an-nlcd-image-by-year." class="level1" data-number="16">
<h1 data-number="16"><span class="header-section-number">16</span> Get an NLCD image by year.</h1>
<p>def&nbsp;getNLCD(year):&nbsp; &nbsp;# Import the NLCD collection.&nbsp; &nbsp;dataset = ee.ImageCollection(USGS/NLCD_RELEASES/2019_REL/NLCD)&nbsp; &nbsp;# Filter the collection by year.&nbsp; &nbsp;nlcd = dataset.filter(ee.Filter.eq(system:index, year)).first()&nbsp;&nbsp; &nbsp;# Select the land cover band.&nbsp; &nbsp;landcover = nlcd.select(landcover);&nbsp; &nbsp;return&nbsp;landcover</p>
<p>Create an NLCD ImageCollection&nbsp;to be used in the split-panel map.</p>
</section>
<section id="create-an-nlcd-image-collection-for-the-selected-years." class="level1" data-number="17">
<h1 data-number="17"><span class="header-section-number">17</span> Create an NLCD image collection for the selected years.</h1>
<p>collection = ee.ImageCollection(ee.List(years).map(lambda&nbsp;year: getNLCD(year)))</p>
<p>Print out the list of system IDs of the selected NLCD images covering the contiguous United States.</p>
<p>collection.aggregate_array(system:id).getInfo()</p>
<p>The output should look like this.</p>
<p>[USGS/NLCD_RELEASES/2019_REL/NLCD/2001,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2004,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2006,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2008,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2011,</p>
<p>&nbsp;USGS/NLCD_RELEASES/2019_REL/NLCD/2013,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2016,<br>
USGS/NLCD_RELEASES/2019_REL/NLCD/2019]</p>
<p>Next, create a list of labels to populate the dropdown list.</p>
<p>labels = [fNLCD {year}&nbsp;for&nbsp;year in&nbsp;years]<br>
labels</p>
<p>The output should look like this.</p>
<p>[NLCD 2001,<br>
NLCD 2004,<br>
NLCD 2006,<br>
NLCD 2008,<br>
NLCD 2011,<br>
NLCD 2013,</p>
<p>&nbsp;NLCD 2016,<br>
NLCD 2019]</p>
<p>The last step is to create a split-panel map by passing the NLCD ImageCollection&nbsp;and list of labels to Map.ts_inspector.</p>
<p>Map.ts_inspector(left_ts=collection, right_ts=collection, left_names=labels, right_names=labels)<br>
Map</p>
<p>The split-panel map should look like Fig. F6.3.8.</p>
<p><img src="F6/image34.png" class="img-fluid"></p>
<p>Fig. F6.3.8&nbsp;A split-panel map for visualizing land cover change with geemap</p>
<p>To visualize land cover change, choose one NLCD image from the left dropdown list and another image from the right dropdown list, then use the slider to swipe through to visualize land cover change interactively. Click the close&nbsp;button in the bottom-right corner to close the split-panel map and return to the NLCD 2019 image shown in Fig. F6.3.7.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F63c.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<section id="publishing-an-earth-engine-app-using-a-local-web-server" class="level2" data-number="17.1">
<h2 data-number="17.1" class="anchored" data-anchor-id="publishing-an-earth-engine-app-using-a-local-web-server"><span class="header-section-number">17.1</span> Publishing an Earth Engine App Using a Local Web Server</h2>
<p>In this section, you will learn how to deploy an Earth Engine App using a local computer as a web server. Assume that you have completed Sect. 3 and created a Jupyter notebook named nlcd_app.ipynb. First, you need to download ngrok, a program that can turn your computer into a secure web server and connect it to the ngrok&nbsp;cloud service, which accepts traffic on a public address. Download ngrok&nbsp;from <a href="https://www.google.com/url?q=https://ngrok.com&amp;sa=D&amp;source=editors&amp;ust=1671458841326366&amp;usg=AOvVaw20VcUaADMd_4ptInlJt5X1">https://ngrok.com</a>&nbsp;and unzip it to a directory on your computer, then copy &nbsp;nlcd_app.ipynb&nbsp;to the same directory. Open the Anaconda Prompt (on Windows)&nbsp;or the Terminal (on macOS/Linux)&nbsp;and enter the following commands. Make sure you change /path/to/ngrok/dir&nbsp;to your computer directory where the ngrok&nbsp;executable is located, e.g., ~/Downloads. &nbsp;</p>
<p>cd /path/to/ngrok/dir</p>
<p>conda activate gee<br>
voila&nbsp;no-browser nlcd_app.ipynb</p>
<p>The output of the terminal should look like this.</p>
<p><img src="F6/image38.png" class="img-fluid"></p>
<p>Fig. F6.3.9&nbsp;The output of the terminal running Voilà</p>
<p><a href="https://www.google.com/url?q=https://voila.readthedocs.io/en/stable/&amp;sa=D&amp;source=editors&amp;ust=1671458841329454&amp;usg=AOvVaw16GCXDSkxu4fUl6f6LFafy">Voilà</a>&nbsp;can be used to run, convert, and serve a Jupyter notebook as a standalone app. Click the link (e.g., <a href="https://www.google.com/url?q=http://localhost:8866&amp;sa=D&amp;source=editors&amp;ust=1671458841329837&amp;usg=AOvVaw052EvlmJeJTl9zIHvFobLl">http://localhost:8866</a>) shown in the terminal window to launch the interactive dashboard. Note that the port number is 8866, which is needed in the next step to launch ngrok. Open another terminal and enter the following command.</p>
<p>cd /path/to/ngrok/dir</p>
<p>ngrok http 8866</p>
<p>The output of the terminal should look like Fig. F6.3.10. Click the link shown in the terminal window to launch the interactive dashboard. The link should look like https://random-string.ngrok.io, which is publicly accessible. Anyone with the link will be able to launch the interactive dashboard&nbsp;and use the split-panel map to visualize NLCD land cover change. Keep in mind that the dashboard might take several seconds to load, so please be patient.</p>
<p><img src="F6/image8.png" class="img-fluid"></p>
<p>Fig. F6.3.10&nbsp;The output of the terminal running ngrok</p>
<p>To stop the web server, press Ctrl + C&nbsp;on both terminal windows. See below for some optional settings for running Voilà&nbsp;and ngrok.</p>
<p>To show code cells from your App, run the following from the terminal.</p>
<p>voila no-browser strip_sources=False nlcd_app.ipynb</p>
<p>To protect your App with a password, run the following.</p>
<p>ngrok http -auth=“username:password”&nbsp;8866</p>
</section>
<section id="publish-an-earth-engine-app-using-cloud-platforms" class="level2" data-number="17.2">
<h2 data-number="17.2" class="anchored" data-anchor-id="publish-an-earth-engine-app-using-cloud-platforms"><span class="header-section-number">17.2</span> Publish an Earth Engine App Using Cloud Platforms</h2>
<p>In this section, you will learn how to deploy an Earth Engine App on cloud platforms, such as Heroku and Google App Engine. Heroku is a “platform as a service”&nbsp;that enables developers to build, run, and operate applications entirely in the cloud. It has a free tier with limited computing hours, which would be sufficient for this lab. Follow the steps below to deploy the Earth Engine App on Heroku.</p>
<p>First, go to <a href="https://www.google.com/url?q=https://github.com/signup&amp;sa=D&amp;source=editors&amp;ust=1671458841334585&amp;usg=AOvVaw3ZgJomY_82B7WiuJEZym5g">https://github.com/signup</a>&nbsp;to sign up for a GitHub account if you dont have one already. Once your GitHub account has been created, log into your account and navigate to the sample app repository: <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps&amp;sa=D&amp;source=editors&amp;ust=1671458841335025&amp;usg=AOvVaw1IQJyv6EFLU5bCmsOsYkfX">https://github.com/giswqs/earthengine-apps</a>. Click the Fork&nbsp;button in the top-right corner to fork this repository into your account. Two important files in the repository are worth mentioning here: <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps/blob/master/requirements.txt&amp;sa=D&amp;source=editors&amp;ust=1671458841335397&amp;usg=AOvVaw23DFUPWYa0pVU-kSzjGeHr">requirements.txt</a>&nbsp;lists the required packages (e.g., geemap) to run the App, while <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps/blob/master/Procfile&amp;sa=D&amp;source=editors&amp;ust=1671458841335755&amp;usg=AOvVaw2_QbFT8c3ahut0eHTdTmUh">Procfile</a>&nbsp;specifies the commands that are executed by the App on startup. The content of <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps/blob/master/Procfile&amp;sa=D&amp;source=editors&amp;ust=1671458841336035&amp;usg=AOvVaw2MdhcFIHFTRIbZBL-XZFCb">Procfile</a>&nbsp;should look like this.</p>
<p>web: voila port=$PORT&nbsp;no-browser strip_sources=True enable_nbextensions=True MappingKernelManager.cull_interval=60 MappingKernelManager.cull_idle_timeout=120 notebooks/</p>
<p>The above command instructs the server to hide the source code and periodically check for idle kernels—in this example, every 60 seconds—and cull them if they have been idle for more than 120 seconds. This can avoid idle kernels using up the server computing resources. The page served by Voilà will contain a list of any notebooks in the <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps/tree/master/notebooks&amp;sa=D&amp;source=editors&amp;ust=1671458841337601&amp;usg=AOvVaw1pHGG9qzGbdG7h7tVr7C9e">notebooks</a>&nbsp;directory.</p>
<p>Next, go to <a href="https://www.google.com/url?q=https://signup.heroku.com&amp;sa=D&amp;source=editors&amp;ust=1671458841338152&amp;usg=AOvVaw03SbXy3optdTU9A2yG-bBq">https://signup.heroku.com</a>&nbsp;to sign up for a Heroku account if you dont have one already. Log into your account and click the New button in the top-right corner, then choose&nbsp;Create new app&nbsp;from the dropdown list (Fig. F6.3.11).</p>
<p><img src="F6/image5.png" class="img-fluid"></p>
<p>Fig. F6.3.11&nbsp;Creating a new app in Heroku</p>
<p>Choose a name for your App (Fig. F6.3.12). Note that the App name must be unique; if an App name has already been taken, you wont be able to use it.</p>
<p><img src="F6/image73.png" class="img-fluid"></p>
<p>Fig. F6.3.12&nbsp;Choosing an App name</p>
<p>Once the Heroku App has been created, click the Deploy&nbsp;tab and choose GitHub&nbsp;as the deployment method. Connect to your GitHub account and enter earthengine-apps&nbsp;in the search box. The repository should be listed beneath the search box. Click the Connect&nbsp;button to connect the repository to Heroku (Fig. F6.3.13).</p>
<p><img src="F6/image27.png" class="img-fluid"></p>
<p>Fig. F6.3.13&nbsp;Connecting a GitHub account to Heroku</p>
<p>Under the same Deploy&nbsp;tab, scroll down and click Enable Automatic Deploys (Fig. F6.3.14). This will enable Heroku to deploy a new version of the App whenever the GitHub repository gets updated.</p>
<p><img src="F6/image31.png" class="img-fluid"></p>
<p>Fig. F6.3.14&nbsp;Enabling Automatic Deploys on Heroku</p>
<p>Since using Earth Engine requires authentication, we need to set the Earth Engine token as an environment variable so that the web App can pass the Earth Engine authentication. If you have completed Sect. 3, you have successfully authenticated Earth Engine on your computer and the token can be found in the following file path, depending on the operating system you are using. Note that you might need to show the hidden directories on your computer in order to see the .config&nbsp;folder under the home directory.</p>
<p>Windows: C:UsersUSERNAME.configearthenginecredentialsLinux: /home/USERNAME/.config/earthengine/credentials<br>
MacOS: /Users/USERNAME/.config/earthengine/credentials</p>
<p>Open the credentials&nbsp;file using a text editor. Select and copy the long string wrapped by the double quotes (Fig. F6.3.15). Do not include the double quotes in the copied string.</p>
<p><img src="F6/image68.png" class="img-fluid"></p>
<p>Fig. F6.3.15&nbsp;The Earth Engine authentication token</p>
<p>Next, navigate to your web App on Heroku. Click the Settings&nbsp;tab, then click Reveal Config Vars&nbsp;on the page. Enter EARTHENGINE_TOKEN&nbsp;as the key and paste the string copied&nbsp;above as the value. Click the Add&nbsp;button to set EARTHENGINE_TOKEN&nbsp;as an environment variable (Fig. F6.3.16) that will be used by geemap&nbsp;to authenticate Earth Engine.</p>
<p><img src="F6/image4.png" class="img-fluid"></p>
<p>Fig. F6.3.16&nbsp;Setting the Earth Engine token as an environment variable on Heroku</p>
<p>The last step is to commit some changes to your forked GitHub repository to trigger Heroku to build and deploy the App. If you are familiar with <a href="https://www.google.com/url?q=https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&amp;sa=D&amp;source=editors&amp;ust=1671458841348079&amp;usg=AOvVaw0sqY5Rw_8XGKlIcdI82G_f">Git</a>&nbsp;commands, you can push changes to the repository from your local computer to GitHub. If you have not used Git before, you can navigate to your repository on GitHub and make changes directly using the browser. For example, you can navigate to <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps/blob/master/README.md&amp;sa=D&amp;source=editors&amp;ust=1671458841348610&amp;usg=AOvVaw1JFdaBCpAIcitrc-P7DHhR">README.md</a>&nbsp;and click the Edit icon&nbsp;on the page to start editing the file. Simply place the cursor at the end of the file and press Enter&nbsp;to add an empty line to the file, then click the Commit changes&nbsp;button at the bottom of the page to save changes. This should trigger Heroku to build the App. Check the build status under the latest activities of the Overview&nbsp;tab. Once the App has been built and deployed successfully, you can click the Open app&nbsp;button in the top-right corner to launch the web App (Fig. F6.3.17). When the App is open in your browser, click nlcd_app.ipynb&nbsp;to launch the split-panel map for visualizing land cover change. This is the same notebook that we developed in Sect. 3. The App URL should look like this: <a href="https://www.google.com/url?q=https://app-name.herokuapp.com/voila/render/nlcd_app.ipynb&amp;sa=D&amp;source=editors&amp;ust=1671458841349532&amp;usg=AOvVaw2GAtxe9X-ymmUvBhjV_PBD">https://APP-NAME.herokuapp.com/voila/render/nlcd_app.ipynb</a>.</p>
<p>Congratulations! You have successfully deployed the Earth Engine App on Heroku.</p>
<p><img src="F6/image51.png" class="img-fluid"></p>
<p>Fig. F6.3.17&nbsp;Setting the Earth Engine token as an environment variable on Heroku</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F63d.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<p>Question 1.&nbsp;What are the pros and cons of designing Earth Engine Apps using geemap&nbsp;and ipywidgets, compared to the JavaScript Earth Engine User Interface&nbsp;API?</p>
<p>Question 2.&nbsp;What are the pros and cons of deploying Earth Engine Apps on Heroku, compared to a local web server and the Earth Engine Code Editor?</p>
</section>
<section id="synthesis-3" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="synthesis-3">Synthesis</h2>
<p>Assignment 1.&nbsp;Replace the NLCD datasets with other multitemporal land cover datasets (e.g., United States Department of Agriculture&nbsp;National Agricultural Statistics Service&nbsp;Cropland Data Layers, visible in the Earth Engine Data Catalog) and modify the web App for visualizing land cover change using the chosen land cover datasets. Deploy the web App using multiple platforms (i.e., JavaScript Code Editor, ngrok, and Heroku). More land cover datasets can be found at &nbsp;<a href="https://www.google.com/url?q=https://developers.google.com/earth-engine/datasets/tags/landcover&amp;sa=D&amp;source=editors&amp;ust=1671458841352828&amp;usg=AOvVaw0rYGxg8JD8U1d92jnx8V1i">https://developers.google.com/earth-engine/datasets/tags/landcover</a>.</p>
</section>
<section id="conclusion-2" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="conclusion-2">Conclusion</h2>
<p>In this chapter, you learned how to design Earth Engine Apps using both the Earth Engine User Interface&nbsp;API (JavaScript) and geemap&nbsp;(Python). You also learned how to deploy Earth Engine Apps on multiple platforms, such as the JavaScript Code Editor, a local web server, and Heroku. The skill of designing and deploying interactive Earth Engine Apps is essential for making your research and data products more accessible to the scientific community and the general public. Anyone with the link to your web App can analyze and visualize Earth Engine datasets without needing an Earth Engine account.</p>
</section>
<section id="references-3" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="references-3">References</h2>
<ul>
<li>Earth Engine User Interface API: <a href="https://www.google.com/url?q=https://developers.google.com/earth-engine/guides/ui&amp;sa=D&amp;source=editors&amp;ust=1671458841355402&amp;usg=AOvVaw1ZgOA5XvSnn5Uo83ccgHPT">https://developers.google.com/earth-engine/guides/ui</a>&nbsp;</li>
<li>Earth Engine Apps: <a href="https://www.google.com/url?q=https://developers.google.com/earth-engine/guides/apps&amp;sa=D&amp;source=editors&amp;ust=1671458841355890&amp;usg=AOvVaw1ZYwRsMtxZ2RPAn-dC-E0n">https://developers.google.com/earth-engine/guides/apps</a>&nbsp;</li>
<li>Voilà: <a href="https://www.google.com/url?q=https://voila.readthedocs.io&amp;sa=D&amp;source=editors&amp;ust=1671458841356320&amp;usg=AOvVaw1_h1OkQnXAvfEh717DURMY">https://voila.readthedocs.io</a></li>
<li>geemap: <a href="https://www.google.com/url?q=https://geemap.org&amp;sa=D&amp;source=editors&amp;ust=1671458841356762&amp;usg=AOvVaw3ednZ3gbVzDuptXKRZgKCT">https://geemap.org</a></li>
<li>ngrok: <a href="https://www.google.com/url?q=https://ngrok.com&amp;sa=D&amp;source=editors&amp;ust=1671458841357236&amp;usg=AOvVaw11-kfA1McHDaqztFMUrmtP">https://ngrok.com</a></li>
<li>Heroku: <a href="https://www.google.com/url?q=https://heroku.com&amp;sa=D&amp;source=editors&amp;ust=1671458841357713&amp;usg=AOvVaw15mVX-rHKo54H2oq0Msd2Z">https://heroku.com</a>&nbsp;</li>
<li>Earthengine-apps: <a href="https://www.google.com/url?q=https://github.com/giswqs/earthengine-apps&amp;sa=D&amp;source=editors&amp;ust=1671458841358120&amp;usg=AOvVaw2XD4jjXV9SfPmaC-oLbstc">https://github.com/giswqs/earthengine-apps</a>&nbsp; &nbsp;</li>
</ul>
<p>Chapter&nbsp;F6.4: Combining R and Earth Engine</p>
<div class="callout-tip callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Chapter Information
</div>
</div>
<div class="callout-body-container callout-body">
<section id="author-4" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="author-4">Author</h2>
<p>&nbsp;</p>
<p>Cesar Aybar, David Montero, Antony Barja, Fernando Herrera, Andrea Gonzales, and Wendy Espinoza</p>
</section>
<section id="overview-4" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="overview-4">Overview</h2>
<p>The purpose of this chapter is to introduce rgee, a non-official API for Earth Engine. You will explore the main features available in rgee&nbsp;and how to set up an environment that integrates rgee&nbsp;with third-party R and Python packages. After this chapter, you will be able to combine R, Python, and JavaScript in the same workflow.</p>
</section>
<section id="learning-outcomes-4" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="learning-outcomes-4">Learning Outcomes</h2>
<ul>
<li>Becoming familiar with rgee, the Earth Engine R API interface.</li>
<li>Integrating rgee&nbsp;with other R packages.</li>
<li>Displaying interactive maps.</li>
<li>Integrating Python and R packages using reticulate.</li>
<li>Combining Earth Engine JavaScript and Python APIs with R.</li>
</ul>
</section>
<section id="assumes-you-know-how-to-4" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="assumes-you-know-how-to-4">Assumes you know how to:</h2>
<ul>
<li>Install the Python environment (Chap. F6.3).</li>
<li>Use the require&nbsp;function to load code from existing modules (Chap. F6.1).</li>
<li>Use the basic functions and logic of Python.</li>
<li>Configure an environment variable and use .Renviron files.</li>
<li>Create Python virtual environments.</li>
</ul>
</section>
</div>
</div>
</section>
<section id="introduction-4" class="level2 unlisted unnumbered">
<h2 class="unlisted unnumbered anchored" data-anchor-id="introduction-4">Introduction</h2>
<p>R is a popular programming language established in statistical science with large support in reproducible research, geospatial analysis, data visualization, and much more. To get started with R, you will need to satisfy some extra software requirements. First, install an up-to-date R version&nbsp;(at least 4.0) in your work environment. The installation procedure will vary depending on your operating system (i.e., Windows, Mac, or Linux). Hands-On Programming with R&nbsp;(Garrett Grolemund 2014, Appendix A) explains step by step how to proceed. We strongly recommend that Windows users install Rtools&nbsp;to avoid compiling external static libraries.</p>
<p>If you are new to R, a good starting point is the book Geocomputation with R&nbsp;(Lovelace et al.&nbsp;2019)&nbsp;or&nbsp;Spatial Data Science with Application in R&nbsp;(Pebesma and Bivand 2021). In addition, we recommend using an integrated development environment (e.g., Rstudio)&nbsp;or a&nbsp;code editor (e.g., Visual Studio Code) to create a suitable setting to display and interact with R objects.</p>
<p>The following R packages must be installed (find more information in the R manual) in order to go through the practicum section.</p>
</section>
</section>
<section id="use-install.packages-to-install-r-packages-from-the-cran-repository." class="level1" data-number="18">
<h1 data-number="18"><span class="header-section-number">18</span> Use install.packages to install R packages from the CRAN repository.</h1>
<p>install.packages(reticulate) # Connect Python with R.<br>
install.packages(rayshader) # 2D and 3D data visualizations in R.<br>
install.packages(remotes) # Install R packages from remote repositories.<br>
remotes::install_github(r-earthengine/rgeeExtra) # rgee extended.<br>
install.packages(rgee) # GEE from within R.<br>
install.packages(sf) # Simple features in R.<br>
install.packages(stars) # Spatiotemporal Arrays and Vector Data Cubes.<br>
install.packages(geojsonio) # Convert data to GeoJSON from various R classes.<br>
install.packages(raster) # Reading, writing, manipulating, analyzing and modeling of spatial data.<br>
install.packages(magick) # Advanced Graphics and Image-Processing in R<br>
install.packages(leaflet.extras2) # Extra Functionality for leaflet<br>
install.packages(cptcity) # colour gradients from the cpt-city web archive</p>
<p>Earth Engine officially supports client libraries only for the JavaScript and Python programming languages. While the&nbsp;Earth Engine Code Editor&nbsp;offers a convenient environment for rapid prototyping in JavaScript, the lack of a mechanism for integration with local environments makes the development of complex scripts tricky. On the other hand, the Python client library offers much versatility, enabling support for third-party packages. However, not all Earth and environmental scientists code in Python. Hence, a significant number of professionals are not members of the Earth Engine community. In the R ecosystem, rgee&nbsp;(Aybar et al.&nbsp;2020) tries to fill this gap by wrapping the Earth Engine Python API via reticulate&nbsp;(Kevin Ushey et al.&nbsp;2021). The rgee&nbsp;package extends and supports all the Earth Engine classes, modules, and functions, working as fast as the other APIs.</p>
<p><img src="F6/image48.png" class="img-fluid"></p>
<p>Fig. F6.4.1&nbsp;A simplified diagram of rgee functionality</p>
<p>Figure F6.4.1&nbsp;illustrates how rgee&nbsp;bridges the Earth Engine platform with the R ecosystem. When an Earth Engine request is created in R, rgee&nbsp;transforms this piece of code into Python. Next, the Earth Engine Python API converts the Python code into JSON. Finally, the JSON file request is received by the server through a Web REST API. Users could get the response using the getInfo&nbsp;method by following the same path in reverse.</p>
<section id="installing-rgee" class="level2" data-number="18.1">
<h2 data-number="18.1" class="anchored" data-anchor-id="installing-rgee"><span class="header-section-number">18.1</span> Installing rgee&nbsp;</h2>
<p>To run, rgee needs a Python environment with two packages: NumPy&nbsp;and earthengine-api. Because instructions change frequently, installation is explained at the following checkpoint:</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64a.&nbsp;The books repository contains information about setting up the rgee&nbsp;environment.</p>
</div>
</div>
<p>After installing both the R and Python requirements, you can now initialize your Earth Engine account from within R. Consider that R, in contrast to Javascript and Python, supports three distinct Google APIs: Earth Engine, Google Drive, and Google Cloud Storage (GCS).</p>
<p>The Google Drive and GCS APIs will allow you to transfer your Earth Engine completed task exports to a local environment automatically. In these practice sessions, we will use only the Earth Engine and Google Drive APIs. Users that are interested in GCS can look up and explore the GCS vignette. To initialize your Earth Engine account alongside Google Drive, use the following commands.</p>
</section>
</section>
<section id="initialize-just-earth-engine" class="level1" data-number="19">
<h1 data-number="19"><span class="header-section-number">19</span> Initialize just Earth Engine</h1>
<p>ee_Initialize()</p>
</section>
<section id="initialize-earth-engine-and-gdee_initializedrive-true" class="level1" data-number="20">
<h1 data-number="20"><span class="header-section-number">20</span> Initialize Earth Engine and GDee_Initialize(drive = TRUE)</h1>
<p>If the Google account is verified and the permission is granted, you will be directed to an authentication token. Copy and paste this token into your R console. Consider that the GCS API requires setting up credentials manually; look up and explore the rgee vignette&nbsp;for more information. The verification step is only required once; after that, rgee saves the credentials in your system.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64b.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<section id="creating-a-3d-population-density-map-with-rgee-and-rayshader" class="level2" data-number="20.1">
<h2 data-number="20.1" class="anchored" data-anchor-id="creating-a-3d-population-density-map-with-rgee-and-rayshader"><span class="header-section-number">20.1</span> Creating&nbsp;a 3D Population Density Map with rgee and rayshader</h2>
<p>First, import the rgee, rayshader, and raster&nbsp;packages.</p>
<p>library(rayshader)<br>
library(raster)<br>
library(rgee)</p>
<p>Initialize the Earth Engine and Google Drive APIs using&nbsp;ee_Initialize. Both&nbsp;credentials must come from the same Google account.</p>
<p>ee_Initialize(drive = TRUE)</p>
<p>Then, we will access the WorldPop Global Project Population Data&nbsp;dataset. In rgee, the Earth Engine spatial classes (ee<span class="math inline">\(Image, ee\)</span>ImageCollection, and ee$FeatureCollection) have a special attribute called Dataset. Users can use it along with autocompletion to quickly find the desired dataset.</p>
<p>collections &lt;- ee<span class="math inline">\(ImageCollection\)</span>Dataset<br>
population_data &lt;- collections<span class="math inline">\(CIESIN_GPWv411_GPW_Population_Density population_data_max &lt;- population_data\)</span>max()</p>
<p>If you need more information about the Dataset, use ee_utils_dataset_display&nbsp;to go to the official documentation in the Earth Engine Data Catalog.</p>
<p>population_data %&gt;% ee_utils_dataset_display()</p>
<p>The rgee package provides various built-in functions to retrieve data from Earth Engine (Aybar et al.&nbsp;2020). In this example, we use ee_as_raster, which automatically converts an ee$Image&nbsp;(server object) into a RasterLayer&nbsp;(local object).</p>
<p>sa_extent &lt;- ee<span class="math inline">\(Geometry\)</span>Rectangle(<br>
&nbsp;coords = c(-100, -50, -20, 12),<br>
&nbsp;geodesic = TRUE,<br>
&nbsp;proj = “EPSG:4326”)</p>
<p>population_data_ly_local &lt;- ee_as_raster(<br>
&nbsp;image = population_data_max,<br>
&nbsp;region = sa_extent,<br>
&nbsp;dsn = “/home/pc-user01/population.tif”, # change for your own path.&nbsp;scale = 5000<br>
)</p>
<p>Now, turn a RasterLayer&nbsp;into a matrix suitable for rayshader.</p>
<p>pop_matrix &lt;- raster_to_matrix(population_data_ly_local)</p>
<p>Next, modify the matrix population density values, adding:</p>
<ul>
<li>Texture, based on five colors (lightcolor, shadowcolor, leftcolor, rightcolor, and centercolor; see rayshader::create_texture&nbsp;documentation)</li>
<li>Color and shadows (rayshader::sphere_shade)</li>
</ul>
<p>pop_matrix %&gt;%<br>
&nbsp;sphere_shade(<br>
&nbsp; &nbsp;texture = create_texture(“#FFFFFF”, “#0800F0”, “#FFFFFF”, “#FFFFFF”, “#FFFFFF”)<br>
&nbsp;) %&gt;%<br>
&nbsp;plot_3d(<br>
&nbsp; &nbsp;pop_matrix,<br>
&nbsp; &nbsp;zoom = 0.55, theta = 0, zscale = 100, soliddepth = -24,<br>
&nbsp; &nbsp;solidcolor = “#525252”, shadowdepth = -40, shadowcolor = “black”,<br>
&nbsp; &nbsp;shadowwidth = 25, windowsize = c(800, 720)<br>
&nbsp;)</p>
<p>Lastly, define a title and subtitle for the plot. Use rayshader::render_snapshot&nbsp;to export the final results (Fig. F6.4.2).</p>
<p>text &lt;- paste0(&nbsp;“South Americanpopulation density”,<br>
&nbsp;strrep(“n”, 27),&nbsp;“Source:GPWv411: Population Density (Gridded Population of the World Version 4.11)”)</p>
<p>render_snapshot(<br>
&nbsp;filename = “30_poblacionsudamerica.png”,<br>
&nbsp;title_text = text,<br>
&nbsp;title_size = 20,<br>
&nbsp;title_color = “black”,<br>
&nbsp;title_font = “Roboto bold”,<br>
&nbsp;clear = TRUE<br>
)</p>
<p><img src="F6/image2.png" class="img-fluid"></p>
<p>Fig. F6.4.2&nbsp;3D population density map of South America</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64c.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
</section>
<section id="displaying-maps-interactively" class="level2" data-number="20.2">
<h2 data-number="20.2" class="anchored" data-anchor-id="displaying-maps-interactively"><span class="header-section-number">20.2</span> Displaying Maps Interactively</h2>
<p>Similar to the Code Editor, rgee supports the interactive visualization of spatial Earth Engine objects by Map$addLayer. First, lets import the rgee&nbsp;and cptcity&nbsp;packages. The cptcity&nbsp;R package is a wrapper to the cpt-city&nbsp;color gradients web archive.</p>
<p>library(rgee)<br>
library(cptcity)<br>
ee_Initialize()</p>
<p>Well select an ee$Image; in this case, the Shuttle Radar Topography Mission 90&nbsp;m (SRTM-90) Version 4.</p>
<p>dem &lt;- ee<span class="math inline">\(Image\)</span>Dataset$CGIAR_SRTM90_V4</p>
<p>Then, well set the visualization parameters as a list with the following elements.</p>
<ul>
<li>min: value(s) to map to 0</li>
<li>max: value(s) to map to 1</li>
<li>palette: a list of CSS-style color strings</li>
</ul>
<p>viz &lt;- list(<br>
&nbsp;min = 600,<br>
&nbsp;max = 6000,<br>
&nbsp;palette = cpt(pal = grass_elevation, rev = TRUE)<br>
)</p>
<p>Then, well create a simple display using Map$addLayer.</p>
<p>m1 &lt;- Map$addLayer(dem, visParams = viz, name = “SRTM”, shown = TRUE)</p>
<p>Optionally, you could add a custom legend using Map$addLayer&nbsp;(Fig. F6.4.3).</p>
<p>pal &lt;- Map$addLegend(viz)<br>
m1 + pal</p>
<p><img src="F6/image28.png" class="img-fluid"></p>
<p>Fig. F6.4.3&nbsp;Interactive visualization of SRTM-90 Version 4 elevation values</p>
<p>The procedure to display ee<span class="math inline">\(Geometry, ee\)</span>Feature, and ee<span class="math inline">\(FeatureCollections&nbsp;objects is similar to the previous example effected&nbsp;on an ee\)</span>Image. Users just need to change the arguments:&nbsp;eeObject&nbsp;and visParams.</p>
<p>First, Earth Engine geometries (Fig. F6.4.4).</p>
<p>vector &lt;- ee<span class="math inline">\(Geometry\)</span>Point(-77.011,-11.98) %&gt;%<br>
&nbsp;ee<span class="math inline">\(Feature\)</span>buffer(50*1000)<br>
Map<span class="math inline">\(centerObject(vector) Map\)</span>addLayer(vector) # eeObject is a ee<span class="math inline">\(Geometry\)</span>Polygon.</p>
<p><img src="F6/image46.png" class="img-fluid"></p>
<p>Fig. F6.4.4&nbsp;A polygon buffer surrounding the city of Lima, Peru</p>
<p>Next, Earth Engine feature collections (Fig. F6.4.5).</p>
<p>building &lt;- ee<span class="math inline">\(FeatureCollection\)</span>Dataset$<br>
&nbsp;<code>GOOGLE_Research_open-buildings_v1_polygon</code><br>
Map<span class="math inline">\(setCenter(3.389, 6.492, 17) Map\)</span>addLayer(building) # eeObject is a ee$FeatureCollection</p>
<p><img src="F6/image23.png" class="img-fluid"></p>
<p>Fig. F6.4.5&nbsp;Building footprints in Lagos, Nigeria</p>
<p>The rgee&nbsp;functionality also supports the display of ee<span class="math inline">\(ImageCollection&nbsp;via Map\)</span>addLayers&nbsp;(note the extra “s” at the end). Map<span class="math inline">\(addLayers&nbsp;will use the same visualization parameters for all the images (Fig. F6.4.6). If you need different visualization parameters per image, use a Map\)</span>addLayer&nbsp;within a for&nbsp;loop.</p>
</section>
</section>
<section id="define-a-imagecollection" class="level1" data-number="21">
<h1 data-number="21"><span class="header-section-number">21</span> Define a ImageCollection</h1>
<p>etp &lt;- ee<span class="math inline">\(ImageCollection\)</span>Dataset<span class="math inline">\(MODIS_NTSG_MOD16A2_105 %&gt;% &nbsp;ee\)</span>ImageCollection<span class="math inline">\(select("ET") %&gt;% &nbsp;ee\)</span>ImageCollection$filterDate(2014-04-01, 2014-06-01)</p>
</section>
<section id="set-viz-paramsviz---list" class="level1" data-number="22">
<h1 data-number="22"><span class="header-section-number">22</span> Set viz paramsviz &lt;- list(</h1>
<p>&nbsp;min = 0, &nbsp;max = 300,<br>
&nbsp;palette = cpt(pal = “grass_bcyr”, rev = TRUE)<br>
)</p>
</section>
<section id="print-map-results-interactively" class="level1" data-number="23">
<h1 data-number="23"><span class="header-section-number">23</span> Print map results interactively</h1>
<p>Map<span class="math inline">\(setCenter(0, 0, 1) etpmap &lt;- Map\)</span>addLayers(etp, visParams = viz)<br>
etpmap</p>
<p><img src="F6/image21.png" class="img-fluid"></p>
<p>Fig. F6.4.6&nbsp;MOD16A2 total evapotranspiration values (kg/m^2/8day)</p>
<p>Another useful rgee&nbsp;feature is the comparison operator&nbsp;(|), which creates a slider in the middle of the canvas, permitting quick comparison of two maps. For instance, load a Landsat 4 image:</p>
<p>landsat &lt;- ee$Image(LANDSAT/LT04/C01/T1/LT04_008067_19890917)</p>
<p>Calculate the Normalized Difference Snow Index.</p>
<p>ndsi &lt;- landsat$normalizedDifference(c(B3, B5))</p>
<p>Define a constant value and use ee<span class="math inline">\(Image\)</span>gte&nbsp;to return a binary image where pixels greater than or equal&nbsp;to that value are set as 1 and the rest are set as 0. Next, filter 0 values using ee<span class="math inline">\(Image\)</span>updateMask.</p>
<p>ndsiMasked &lt;- ndsi<span class="math inline">\(updateMask(ndsi\)</span>gte(0.4))</p>
<p>Define the visualization parameters.</p>
<p>vizParams &lt;- list(<br>
&nbsp;bands &lt;- c(B5, B4, B3), # vector of three bands (R, G, B).&nbsp;min = 40,<br>
&nbsp;max = 240,<br>
&nbsp;gamma = c(0.95, 1.1, 1) # Gamma correction factor.)</p>
<p>ndsiViz &lt;- list(<br>
&nbsp;min = 0.5,<br>
&nbsp;max = 1,<br>
&nbsp;palette = c(00FFFF, 0000FF)<br>
)</p>
<p>Center the map on the Huayhuash mountain range in Peru.</p>
<p>Map$setCenter(lon = -77.20, lat = -9.85, zoom = 10)</p>
<p>Finally, visualize both maps using the |&nbsp;operator (Fig. F6.4.7).</p>
<p>m2 &lt;- Map<span class="math inline">\(addLayer(ndsiMasked, ndsiViz, 'NDSI masked') m1 &lt;- Map\)</span>addLayer(landsat, vizParams, false color composite)<br>
m2 &nbsp;| m1</p>
<p><img src="F6/image35.png" class="img-fluid"></p>
<p>Fig. F6.4.7&nbsp;False-color composite over the Huayhuash mountain range, Peru</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64d.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<p>##&nbsp;Integrating rgee&nbsp;with Other Python Packages</p>
<p>As noted in Sect. 1, rgee set up a Python environment with NumPy&nbsp;and earthengine-api&nbsp;in your system. However, there is no need to limit it to just two Python packages. In this section, you will learn how to use the Python package ndvi2gif&nbsp;to perform a Normalized Difference Vegetation Index (NDVI) multi-seasonal analysis in the Ocoña Valley without leaving R.</p>
<p>Whenever you want to install a Python package, you must run the following.</p>
<p>library(rgee)<br>
library(reticulate)<br>
ee_Initialize()</p>
<p>The ee_Initialize&nbsp;function not only authenticates your Earth Engine account but also helps reticulate&nbsp;to set up a Python environment compatible with rgee. After running ee_Initialize, use&nbsp;reticulate::install_python&nbsp;to install the desired Python package.</p>
<p>py_install(“ndvi2gif”)</p>
<p>The previous procedure is&nbsp;needed just once for each Python environment. Once installed, we simply load the package using reticulate::import.</p>
<p>ngif &lt;- import(“ndvi2gif”)</p>
<p>Then, we define our study area using ee<span class="math inline">\(Geometry\)</span>Rectangle&nbsp;(Fig. F6.4.8), and use the leaflet layers control to switch between basemaps.</p>
<p>colca &lt;- c(-73.15315, -16.46289, -73.07465, -16.37857)<br>
roi &lt;- ee<span class="math inline">\(Geometry\)</span>Rectangle(colca)<br>
Map<span class="math inline">\(centerObject(roi) Map\)</span>addLayer(roi)</p>
<p><img src="F6/image59.png" class="img-fluid"></p>
<p>Fig. F6.4.8&nbsp;A rectangle drawn over the Ocoña Valley, Peru</p>
<p>In ndvi2gif, there is just one class: NdviSeasonality. It has the following four public methods.&nbsp;</p>
<ul>
<li>get_export:&nbsp;Exports NDVI year composites in .GeoTIFF format to your local folder.</li>
<li>get_export_single:&nbsp;Exports single composite as .GeoTIFF to your local folder.</li>
<li>get_year_composite:&nbsp;Returns the NDVI composites for each year.</li>
<li>get_gif:&nbsp;Exports NDVI year composites as a .gif to your local folder.</li>
</ul>
<p>To run, the NdviSeasonality&nbsp;constructor needs to define the following arguments.</p>
<ul>
<li>roi:&nbsp;the region of interest</li>
<li>start_year:&nbsp;the initial year to start to create yearly composites</li>
<li>end_year:&nbsp;the end year to look for</li>
<li>sat:&nbsp;the satellite sensor</li>
<li>key:&nbsp;the aggregation rule that will be used to generate the yearly composite</li>
</ul>
<p>For each year, the get_year_composite&nbsp;method generates an NDVI ee$Image&nbsp;with four bands, one band per season. Color combination between images and bands will allow you to interpret the vegetation phenology over the seasons and years. In ndvi2gif, the seasons are defined as follows.</p>
<ul>
<li>winter&nbsp;= c(-01-01, -03-31)</li>
<li>spring = c(-04-01, -06-30)</li>
<li>summer = c(-07-01, -09-30)</li>
<li>autumn = c(-10-01, -12-31)</li>
</ul>
<p>myclass &lt;- ngif$NdviSeasonality(<br>
&nbsp;roi = roi,<br>
&nbsp;start_year = 2016L,<br>
&nbsp;end_year = 2020L,<br>
&nbsp;sat = Sentinel, # Sentinel, Landsat, MODIS, sar&nbsp;key = max&nbsp;# max, median, perc_90<br>
)</p>
</section>
<section id="estimate-the-median-of-the-yearly-composites-from-2016-to-2020." class="level1" data-number="24">
<h1 data-number="24"><span class="header-section-number">24</span> Estimate the median of the yearly composites from 2016 to 2020.</h1>
<p>median &lt;- myclass<span class="math inline">\(get_year_composite()\)</span>median()</p>
</section>
<section id="estimate-the-median-of-the-winter-season-composites-from-2016-to-2020." class="level1" data-number="25">
<h1 data-number="25"><span class="header-section-number">25</span> Estimate the median of the winter season composites from 2016 to 2020.</h1>
<p>wintermax &lt;- myclass<span class="math inline">\(get_year_composite()\)</span>select(winter)$max()</p>
<p>We can display maps interactively using the Map$addLayer&nbsp;(Fig. F6.4.9), and use the leaflet layers control to switch between basemaps.</p>
<p>Map<span class="math inline">\(addLayer(wintermax, list(min = 0.1, max = 0.8), 'winterMax') | Map\)</span>addLayer(median, list(min = 0.1, max = 0.8), median)</p>
<p><img src="F6/image24.png" class="img-fluid"></p>
<p>Fig. F6.4.9&nbsp;Comparison between the maximum historic winter NDVI and the mean historic NDVI. Colors represent the season when the maximum value occurred.</p>
<p>And we can export the results to a GIF format.</p>
<p>myclass$get_gif()</p>
<p>To get more information about the ndvi2gif package, visit its <a href="https://www.google.com/url?q=https://github.com/Digdgeo/Ndvi2Gif&amp;sa=D&amp;source=editors&amp;ust=1671458841429620&amp;usg=AOvVaw0cUiJ9wlrQB8a2-Whxs0zA">GitHub</a>&nbsp;repository.</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64e.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<p>##&nbsp;Converting JavaScript Modules to R</p>
<p>In recent&nbsp;years, the Earth Engine community has developed a lot of valuable third-party modules. Some incredible ones are geeSharp&nbsp;(Zuspan 2020), ee-palettes&nbsp;(Donchyts et al.&nbsp;2020), spectral&nbsp;(Montero 2021), and LandsatLST&nbsp;(Ermida et al.&nbsp;2020). While some of these modules have been implemented in Python and JavaScript (e.g., geeSharp&nbsp;and spectral), most are available only for JavaScript. This is a critical drawback, because it divides the Earth Engine community by programming languages. For example, if an R user wants to use tagee&nbsp;(Safanelli&nbsp;et al.&nbsp;2020), the user will have to first translate the entire module to R.</p>
<p>In order to close this breach, the ee_extra&nbsp;Python package has been developed to unify the Earth Engine community. The philosophy behind ee_extra&nbsp;is that all of its extended functions, classes, and methods must be functional for the JavaScript, Julia, R, and Python client libraries. Currently, ee_extra&nbsp;is the base of the rgeeExtra&nbsp;(Aybar et al.&nbsp;2021) and eemont&nbsp;(Montero 2021) packages.</p>
<p>To demonstrate the potential of ee_extra, lets study an example from the Landsat Land Surface Temperature (LST) JavaScript module. The Landsat LST module computes the land surface temperature for Landsat products (Ermida et al.&nbsp;2020). First we will run it in the Earth Engine Code Editor; then we will replicate those results in R.</p>
<p>First, JavaScript. In a new script in the Code Editor, we must require&nbsp;the Landsat LST module.</p>
<p>var&nbsp;LandsatLST = require(&nbsp; &nbsp;users/sofiaermida/landsat_smw_lst:modules/Landsat_LST.js);</p>
<p>The Landsat LST&nbsp;module contains a function named collection. This function receives the following parameters.</p>
<ul>
<li>The Landsat archive ID</li>
<li>The starting date of the Landsat collection</li>
<li>The ending date of the Landsat collection</li>
<li>The region of interest as geometry</li>
<li>A Boolean parameter specifying if we want to use the NDVI for computing a dynamic emissivity instead of using the emissivity from ASTER</li>
</ul>
<p>In the following code block, we are going to define all required parameters.</p>
<p>var&nbsp;geometry = ee.Geometry.Rectangle([-8.91, 40.0, -8.3, 40.4]);<br>
var&nbsp;satellite = L8;<br>
var&nbsp;date_start = 2018-05-15;<br>
var&nbsp;date_end = 2018-05-31;<br>
var&nbsp;use_ndvi = true;</p>
<p>Now, with all our parameters defined, we can compute the land surface temperature by using the collection method from Landsat LST.</p>
<p>var&nbsp;LandsatColl = LandsatLST.collection(satellite, date_start,<br>
&nbsp; &nbsp;date_end, geometry, use_ndvi);</p>
<p>The result is stored as an ImageCollection&nbsp;in the LandsatColl&nbsp;variable. Now select the first element of the collection as an example by using the first&nbsp;method.</p>
<p>var&nbsp;exImage = LandsatColl.first();</p>
<p>This example image is now stored in a variable named exImage. Lets display the LST result on the Map. For visualization purposes, well define a color palette.</p>
<p>var&nbsp;cmap = [blue, cyan, green, yellow, red];</p>
<p>Then, well center the map in the region of interest.</p>
<p>Map.centerObject(geometry);</p>
<p>Finally, lets display the LST with the cmap color palette by using the Map.addLayer&nbsp;method (Fig. F6.4.10). This method receives the image to visualize, the visualization parameters, the color palette, and the name of the layer to show in the layer control. The visualization parameters will be:</p>
<ul>
<li>min: 290 (a minimum LST value of 290 K)</li>
<li>max: 320 (a maximum LST value of 320 K)</li>
<li>palette: cmap&nbsp;(the color palette that was created some steps before)</li>
</ul>
<p>The name of the layer in the Map layer set will be LST.</p>
<p>Map.addLayer(exImage.select(LST), {<br>
&nbsp; &nbsp;min: 290,<br>
&nbsp; &nbsp;max: 320,<br>
&nbsp; &nbsp;palette: cmap<br>
}, LST)</p>
<p><img src="F6/image45.png" class="img-fluid"></p>
<p>Fig. F6.4.10&nbsp;A map illustrating LST, obtained by following the JavaScript example</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64f.&nbsp;The books repository contains a script that shows what your code should look like at this point.</p>
</div>
</div>
<p>Now, lets use R to implement the same logic. As in the previous sections, import the R packages: rgee and rgeeExtra. Then, initialize your Earth Engine session.</p>
<p>library(rgee)<br>
library(rgeeExtra)<br>
library(reticulate)</p>
<p>ee_Initialize()</p>
<p>Install rgeeExtra Python dependencies.</p>
<p>py_install(packages = c(“regex”, “ee_extra”, “jsbeautifier”))</p>
<p>Using&nbsp;the function rgeeExtra::module&nbsp;loads the JavaScript module.</p>
<p>LandsatLST &lt;- module(“users/sofiaermida/landsat_smw_lst:modules/Landsat_LST.js”)</p>
<p>The rest of the code is exactly the same as in JavaScript.</p>
<p>geometry &lt;- ee<span class="math inline">\(Geometry\)</span>Rectangle(c(-8.91, 40.0, -8.3, 40.4))<br>
satellite &lt;- L8date_start &lt;- 2018-05-15date_end &lt;- 2018-05-31use_ndvi &lt;- TRUE</p>
<p>LandsatColl &lt;- LandsatLST<span class="math inline">\(collection(satellite, date_start, date_end, geometry, use_ndvi) exImage &lt;- LandsatColl\)</span>first()<br>
cmap &lt;- c(blue, cyan, green, yellow, red)</p>
<p>lmod &lt;- list(min = 290, max = 320, palette = cmap)<br>
Map<span class="math inline">\(centerObject(geometry) Map\)</span>addLayer(exImage$select(LST), lmod, LST)</p>
<p><img src="F6/image7.png" class="img-fluid"></p>
<p>Fig. F6.4.11&nbsp;A map illustrating LST, obtained by following the R example</p>
<div class="callout-note callout callout-style-default callout-captioned">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-caption-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Code Checkpoint F64g.&nbsp;The books repository contains information about what your code should look like at this point.</p>
</div>
</div>
<p>Question 1. When and why might users prefer to use R instead of Python to connect to Earth Engine?</p>
<p>Question 2. What are the advantages and disadvantages of using rgee instead of the Earth Engine JavaScript Code Editor?</p>
<section id="synthesis-4" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="synthesis-4">Synthesis</h2>
<p>Assignment 1.&nbsp;Estimate the Gaussian curvature map from a digital elevation model using rgee and rgeeExtra. Hint:&nbsp;Use the module users/joselucassafanelli/TAGEE:TAGEE-functions.</p>
</section>
<section id="conclusion-3" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="conclusion-3">Conclusion</h2>
<p>In this chapter, you learned how to use Earth Engine and R in the same workflow. Since rgee uses reticulate, rgee also permits integration with third-party Earth Engine Python packages. You also learned how to use Map$addLayer, which works similarly to the Earth Engine User Interface API (Code Editor). Finally, we also introduced rgeeExtra, a new R package that extends the Earth Engine API and supports JavaScript module execution.</p>
</section>
<section id="references-4" class="level2 unnumbered">
<h2 class="unnumbered anchored" data-anchor-id="references-4">References</h2>
<p>Aybar C, Wu Q, Bautista L, et al (2020) rgee: An R package for interacting with Google Earth Engine. J Open Source Softw 5:2272. https://doi.org/10.21105/joss.02272</p>
<p>Ermida SL, Soares P, Mantas V, et al (2020) Google Earth Engine open-source code for land surface temperature estimation from the Landsat series. Remote Sens 12:1471. https://doi.org/10.3390/RS12091471</p>
<p>Grolemund G (2014) Hands-On Programming with R - Write Your Own Functions and Simulations. OReilly Media, Inc.</p>
<p>Lovelace R, Nowosad J, Muenchow J (2019) Geocomputation with R. Chapman and Hall/CRC</p>
<p>Montero D (2021) eemont: A Python package that extends Google Earth Engine. J Open Source Softw 6:3168. https://doi.org/10.21105/joss.03168</p>
<p>Pebesma E, Bivand R (2019) Spatial Data Science. https://r-spatial.org/book/</p>
</section>
</section>
</main> <!-- /main -->
<script id="quarto-html-after-body" type="application/javascript">
window.document.addEventListener("DOMContentLoaded", function (event) {
const toggleBodyColorMode = (bsSheetEl) => {
const mode = bsSheetEl.getAttribute("data-mode");
const bodyEl = window.document.querySelector("body");
if (mode === "dark") {
bodyEl.classList.add("quarto-dark");
bodyEl.classList.remove("quarto-light");
} else {
bodyEl.classList.add("quarto-light");
bodyEl.classList.remove("quarto-dark");
}
}
const toggleBodyColorPrimary = () => {
const bsSheetEl = window.document.querySelector("link#quarto-bootstrap");
if (bsSheetEl) {
toggleBodyColorMode(bsSheetEl);
}
}
toggleBodyColorPrimary();
const disableStylesheet = (stylesheets) => {
for (let i=0; i < stylesheets.length; i++) {
const stylesheet = stylesheets[i];
stylesheet.rel = 'prefetch';
}
}
const enableStylesheet = (stylesheets) => {
for (let i=0; i < stylesheets.length; i++) {
const stylesheet = stylesheets[i];
stylesheet.rel = 'stylesheet';
}
}
const manageTransitions = (selector, allowTransitions) => {
const els = window.document.querySelectorAll(selector);
for (let i=0; i < els.length; i++) {
const el = els[i];
if (allowTransitions) {
el.classList.remove('notransition');
} else {
el.classList.add('notransition');
}
}
}
const toggleColorMode = (alternate) => {
// Switch the stylesheets
const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate');
manageTransitions('#quarto-margin-sidebar .nav-link', false);
if (alternate) {
enableStylesheet(alternateStylesheets);
for (const sheetNode of alternateStylesheets) {
if (sheetNode.id === "quarto-bootstrap") {
toggleBodyColorMode(sheetNode);
}
}
} else {
disableStylesheet(alternateStylesheets);
toggleBodyColorPrimary();
}
manageTransitions('#quarto-margin-sidebar .nav-link', true);
// Switch the toggles
const toggles = window.document.querySelectorAll('.quarto-color-scheme-toggle');
for (let i=0; i < toggles.length; i++) {
const toggle = toggles[i];
if (toggle) {
if (alternate) {
toggle.classList.add("alternate");
} else {
toggle.classList.remove("alternate");
}
}
}
// Hack to workaround the fact that safari doesn't
// properly recolor the scrollbar when toggling (#1455)
if (navigator.userAgent.indexOf('Safari') > 0 && navigator.userAgent.indexOf('Chrome') == -1) {
manageTransitions("body", false);
window.scrollTo(0, 1);
setTimeout(() => {
window.scrollTo(0, 0);
manageTransitions("body", true);
}, 40);
}
}
const isFileUrl = () => {
return window.location.protocol === 'file:';
}
const hasAlternateSentinel = () => {
let styleSentinel = getColorSchemeSentinel();
if (styleSentinel !== null) {
return styleSentinel === "alternate";
} else {
return false;
}
}
const setStyleSentinel = (alternate) => {
const value = alternate ? "alternate" : "default";
if (!isFileUrl()) {
window.localStorage.setItem("quarto-color-scheme", value);
} else {
localAlternateSentinel = value;
}
}
const getColorSchemeSentinel = () => {
if (!isFileUrl()) {
const storageValue = window.localStorage.getItem("quarto-color-scheme");
return storageValue != null ? storageValue : localAlternateSentinel;
} else {
return localAlternateSentinel;
}
}
let localAlternateSentinel = 'alternate';
// Dark / light mode switch
window.quartoToggleColorScheme = () => {
// Read the current dark / light value
let toAlternate = !hasAlternateSentinel();
toggleColorMode(toAlternate);
setStyleSentinel(toAlternate);
};
// Ensure there is a toggle, if there isn't float one in the top right
if (window.document.querySelector('.quarto-color-scheme-toggle') === null) {
const a = window.document.createElement('a');
a.classList.add('top-right');
a.classList.add('quarto-color-scheme-toggle');
a.href = "";
a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; };
const i = window.document.createElement("i");
i.classList.add('bi');
a.appendChild(i);
window.document.body.appendChild(a);
}
// Switch to dark mode if need be
if (hasAlternateSentinel()) {
toggleColorMode(true);
} else {
toggleColorMode(false);
}
const icon = "";
const anchorJS = new window.AnchorJS();
anchorJS.options = {
placement: 'right',
icon: icon
};
anchorJS.add('.anchored');
const clipboard = new window.ClipboardJS('.code-copy-button', {
target: function(trigger) {
return trigger.previousElementSibling;
}
});
clipboard.on('success', function(e) {
// button target
const button = e.trigger;
// don't keep focus
button.blur();
// flash "checked"
button.classList.add('code-copy-button-checked');
var currentTitle = button.getAttribute("title");
button.setAttribute("title", "Copied!");
let tooltip;
if (window.bootstrap) {
button.setAttribute("data-bs-toggle", "tooltip");
button.setAttribute("data-bs-placement", "left");
button.setAttribute("data-bs-title", "Copied!");
tooltip = new bootstrap.Tooltip(button,
{ trigger: "manual",
customClass: "code-copy-button-tooltip",
offset: [0, -8]});
tooltip.show();
}
setTimeout(function() {
if (tooltip) {
tooltip.hide();
button.removeAttribute("data-bs-title");
button.removeAttribute("data-bs-toggle");
button.removeAttribute("data-bs-placement");
}
button.setAttribute("title", currentTitle);
button.classList.remove('code-copy-button-checked');
}, 1000);
// clear code selection
e.clearSelection();
});
function tippyHover(el, contentFn) {
const config = {
allowHTML: true,
content: contentFn,
maxWidth: 500,
delay: 100,
arrow: false,
appendTo: function(el) {
return el.parentElement;
},
interactive: true,
interactiveBorder: 10,
theme: 'quarto',
placement: 'bottom-start'
};
window.tippy(el, config);
}
const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
for (var i=0; i<noterefs.length; i++) {
const ref = noterefs[i];
tippyHover(ref, function() {
// use id or data attribute instead here
let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
try { href = new URL(href).hash; } catch {}
const id = href.replace(/^#\/?/, "");
const note = window.document.getElementById(id);
return note.innerHTML;
});
}
const findCites = (el) => {
const parentEl = el.parentElement;
if (parentEl) {
const cites = parentEl.dataset.cites;
if (cites) {
return {
el,
cites: cites.split(' ')
};
} else {
return findCites(el.parentElement)
}
} else {
return undefined;
}
};
var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
for (var i=0; i<bibliorefs.length; i++) {
const ref = bibliorefs[i];
const citeInfo = findCites(ref);
if (citeInfo) {
tippyHover(citeInfo.el, function() {
var popup = window.document.createElement('div');
citeInfo.cites.forEach(function(cite) {
var citeDiv = window.document.createElement('div');
citeDiv.classList.add('hanging-indent');
citeDiv.classList.add('csl-entry');
var biblioDiv = window.document.getElementById('ref-' + cite);
if (biblioDiv) {
citeDiv.innerHTML = biblioDiv.innerHTML;
}
popup.appendChild(citeDiv);
});
return popup.innerHTML;
});
}
}
});
</script>
<nav class="page-navigation">
<div class="nav-page nav-page-previous">
<a href="./F5.html" class="pagination-link">
<i class="bi bi-arrow-left-short"></i> <span class="nav-page-text"><span class="chapter-number">6</span>&nbsp; <span class="chapter-title">Vectors and Tables</span></span>
</a>
</div>
<div class="nav-page nav-page-next">
<a href="./lights.html" class="pagination-link">
<span class="nav-page-text">War at Night</span> <i class="bi bi-arrow-right-short"></i>
</a>
</div>
</nav>
</div> <!-- /content -->
</body></html>