├── ee_jupyter ├── __init__.py ├── core.py ├── colab.py ├── layout.py ├── _modidx.py └── ipyleaflet.py ├── sidebar.yml ├── custom.yml ├── MANIFEST.in ├── index_files └── figure-commonmark │ └── cell-10-output-1.png ├── nbs ├── sidebar.yml ├── nbdev.yml ├── _quarto.yml ├── 00_colab.ipynb ├── 00_core.ipynb ├── 02_layout.ipynb ├── index.ipynb └── 01_ipyleaflet.ipynb ├── nbdev.yml ├── .github └── workflows │ ├── test.yaml │ └── deploy.yaml ├── styles.css ├── settings.ini ├── docs ├── contributing.md └── code-of-conduct.md ├── CHANGELOG.md ├── .gitignore ├── setup.py ├── README.md └── LICENSE /ee_jupyter/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.7" 2 | -------------------------------------------------------------------------------- /sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | sidebar: 3 | contents: 4 | -------------------------------------------------------------------------------- /custom.yml: -------------------------------------------------------------------------------- 1 | website: 2 | repo-url: "https://github.com/google/earthengine-jupyter" -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-exclude * __pycache__ 6 | -------------------------------------------------------------------------------- /index_files/figure-commonmark/cell-10-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/earthengine-jupyter/HEAD/index_files/figure-commonmark/cell-10-output-1.png -------------------------------------------------------------------------------- /nbs/sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | sidebar: 3 | contents: 4 | - index.ipynb 5 | - 00_colab.ipynb 6 | - 00_core.ipynb 7 | - 01_ipyleaflet.ipynb 8 | - 02_layout.ipynb 9 | - 03_rich_display.ipynb -------------------------------------------------------------------------------- /nbs/nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "earthengine-jupyter" 6 | site-url: "https://google.github.io/earthengine-jupyter" 7 | description: "Tools for working with the Earth Engine from a Jupyter development environment" 8 | repo-branch: main 9 | repo-url: "https://github.com/google/earthengine-jupyter" 10 | -------------------------------------------------------------------------------- /nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "earthengine-jupyter" 6 | site-url: "https://googlestaging.github.io/earthengine-jupyter" 7 | description: "Tools for working with the Earth Engine from a Jupyter development environment" 8 | repo-branch: main 9 | repo-url: "https://github.com/googlestaging/earthengine-jupyter" 10 | -------------------------------------------------------------------------------- /nbs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | format: 5 | html: 6 | theme: cosmo 7 | css: styles.css 8 | toc: true 9 | 10 | website: 11 | twitter-card: true 12 | open-graph: true 13 | repo-actions: [issue] 14 | navbar: 15 | background: primary 16 | search: true 17 | sidebar: 18 | style: floating 19 | 20 | metadata-files: [nbdev.yml, sidebar.yml] -------------------------------------------------------------------------------- /ee_jupyter/core.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['authenticate_if_needed'] 5 | 6 | # %% ../nbs/00_core.ipynb 5 7 | def authenticate_if_needed(): 8 | """Authenticate Earth Engine, if credentials do not exist. 9 | 10 | * Authenticate, if needed.""" 11 | 12 | import ee 13 | import os 14 | 15 | if os.path.exists(ee.oauth.get_credentials_path()): 16 | print('\N{check mark} Authentication credentials were found.') 17 | else: 18 | ee.Authenticate() 19 | -------------------------------------------------------------------------------- /ee_jupyter/colab.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_colab.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['set_colab_output_cell_height'] 5 | 6 | # %% ../nbs/00_colab.ipynb 3 7 | def set_colab_output_cell_height(max_height): 8 | """Sets the maximum height for the current notebook cell's output.""" 9 | import google 10 | 11 | from IPython.display import Javascript 12 | from string import Template 13 | s = Template('google.colab.output.setIframeHeight(0, true, {maxHeight: $height})') 14 | display(Javascript(s.substitute(height=max_height))) 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [workflow_dispatch, pull_request, push] 3 | 4 | jobs: 5 | test: 6 | environment: CI 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Write Earth Engine credentials file 10 | run: | 11 | mkdir -p /home/runner/.config/earthengine 12 | ls -l /home/runner/.config/earthengine/ 13 | echo ${{ secrets.SECRET_CREDENTIALS }} | base64 --decode > /home/runner/.config/earthengine/credentials 14 | ls -l /home/runner/.config/earthengine/ 15 | - uses: fastai/workflows/nbdev-ci@master 16 | with: {pre: 1} 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: [ "main", "master" ] 5 | workflow_dispatch: 6 | jobs: 7 | deploy: 8 | environment: CI 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Write Earth Engine credentials file 12 | run: | 13 | mkdir -p /home/runner/.config/earthengine 14 | ls -l /home/runner/.config/earthengine/ 15 | echo ${{ secrets.SECRET_CREDENTIALS }} | base64 --decode > /home/runner/.config/earthengine/credentials 16 | ls -l /home/runner/.config/earthengine/ 17 | - uses: fastai/workflows/quarto-ghp@master 18 | with: {pre: 1} 19 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 0; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 0; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | repo = earthengine-jupyter 3 | lib_name = earthengine-jupyter 4 | version = 0.0.7 5 | min_python = 3.7 6 | license = apache2 7 | doc_path = _docs 8 | lib_path = ee_jupyter 9 | nbs_path = nbs 10 | recursive = False 11 | tst_flags = notest 12 | branch = main 13 | custom_sidebar = False 14 | doc_host = https://google.github.io 15 | doc_baseurl = /earthengine-jupyter 16 | git_url = https://github.com/google/earthengine-jupyter 17 | title = earthengine-jupyter 18 | audience = Developers 19 | author = tylere 20 | author_email = tylere@google.com 21 | copyright = 2022 ownwards, tylere 22 | description = Tools for working with the Earth Engine from a Jupyter development environment 23 | keywords = nbdev jupyter notebook python 24 | language = English 25 | status = 3 26 | user = google 27 | requirements = earthengine-api jupyter ipyleaflet ipytree 28 | black_formatting = False 29 | readme_nb = index.ipynb 30 | jupyter_hooks = True 31 | allowed_metadata_keys = widgets 32 | allowed_cell_metadata_keys = 33 | clean_ids = True 34 | clear_all = False 35 | put_version_in_init = True 36 | 37 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code Reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /ee_jupyter/layout.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_layout.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['MapWithInspector'] 5 | 6 | # %% ../nbs/02_layout.ipynb 4 7 | from .ipyleaflet import Map 8 | from .ipyleaflet import Inspector 9 | import ee 10 | import ipyleaflet 11 | import ipywidgets as widgets 12 | import logging 13 | 14 | # %% ../nbs/02_layout.ipynb 8 15 | class MapWithInspector(widgets.HBox): 16 | """A map with a tobbleable inspector.""" 17 | 18 | # Font Awesome icons: https://fontawesome.com/v4/examples/ 19 | ICON_INSPECTOR_SHOWN = 'info-circle 2x fw inverse' 20 | ICON_INSPECTOR_HIDDEN = 'info-circle 2x fw' 21 | 22 | def __init__(self, **kwargs): 23 | 24 | self.map = Map(**kwargs) 25 | 26 | button_inspector_toggle = widgets.Button( 27 | icon = self.ICON_INSPECTOR_SHOWN, 28 | layout = widgets.Layout(width='45px', height='45px', padding='0px') 29 | ) 30 | 31 | def show_inspector(visible): 32 | if visible: 33 | self.inspector.layout.width = None 34 | self.inspector.layout.min_width = f'300px' 35 | self.inspector.disabled = False 36 | else: 37 | self.inspector.layout.width = f'0%' 38 | self.inspector.layout.min_width = None 39 | self.inspector.disabled = True 40 | 41 | def on_button_clicked(_b): 42 | if _b.icon == self.ICON_INSPECTOR_HIDDEN: 43 | show_inspector(False) 44 | _b.icon = self.ICON_INSPECTOR_SHOWN 45 | else: 46 | show_inspector(True) 47 | _b.icon = self.ICON_INSPECTOR_HIDDEN 48 | button_inspector_toggle.on_click(on_button_clicked) 49 | 50 | widget_control1 = ipyleaflet.WidgetControl(widget=button_inspector_toggle, 51 | position='topright') 52 | self.map.add_control(widget_control1) 53 | self.inspector = Inspector(self.map) 54 | 55 | kwargs['children'] = [self.map, self.inspector] 56 | show_inspector(False) 57 | 58 | super().__init__(**kwargs) 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | 4 | 5 | ## 0.0.7 6 | 7 | - Corrected nbdev settings (GitHub repo owner) 8 | 9 | 10 | ## 0.0.6 11 | 12 | ### New Features 13 | 14 | - Make map-inspector layout panels resizable ([#22](https://github.com/google/earthengine-jupyter/issues/22)) 15 | 16 | 17 | ## 0.0.4 18 | 19 | ### New Features 20 | 21 | - Inspector: display scrollbar when exceeding maximum height or width ([#11](https://github.com/tylere/earthengine-jupyter/issues/11)) 22 | 23 | - Inspector: Simplify the object display by using OrderedDict ([#10](https://github.com/tylere/earthengine-jupyter/issues/10)) 24 | - The object folder's display logic currently hardcodes the keys that it expects to display. It could be better to order the keys that are returned by ee.Object.getInfo(). 25 | 26 | - Map: Display crosshairs when cursor is over the map ([#9](https://github.com/tylere/earthengine-jupyter/issues/9)) 27 | 28 | - Provide map view details in the Inspector's Point folder ([#5](https://github.com/tylere/earthengine-jupyter/issues/5)) 29 | 30 | - Inspector: return "No unmasked pixels at clicked point." for layers with fully masked bands ([#2](https://github.com/tylere/earthengine-jupyter/issues/2)) 31 | 32 | - Inspector: disable Pixels & Objects folders if there are non map layers ([#1](https://github.com/tylere/earthengine-jupyter/issues/1)) 33 | - Similar to the Code Editor, only display the Pixels and Objects folders if the map has layers 34 | 35 | 36 | ## 0.0.3 37 | 38 | 39 | ### Bugs Squashed 40 | 41 | - Having code in package to install itself doesn't work ([#12](https://github.com/google/earthengine-jupyter/issues/12)) 42 | 43 | 44 | ## 0.0.2 45 | 46 | ### New Features 47 | 48 | - Add colab setup function ([#11](https://github.com/google/earthengine-jupyter/issues/11)) 49 | - Add a function to simplify setting up Colab (install packages, authenticate). 50 | 51 | 52 | 53 | ## 0.0.1 54 | 55 | ### New Features 56 | 57 | - Rename library to be more consistent with Earth Engine client library ([#3](https://github.com/google/earthengine-jupyter/issues/3)) 58 | - earthengine_jupyter -> ee_jupyter 59 | 60 | - Create initial content ([#2](https://github.com/google/earthengine-jupyter/issues/2)) 61 | - An initial issue intended to exercising the changelog, release, and push to PyPI. 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _docs/ 2 | _proc/ 3 | 4 | *.bak 5 | .gitattributes 6 | .last_checked 7 | .gitconfig 8 | *.bak 9 | *.log 10 | *~ 11 | ~* 12 | _tmp* 13 | tmp* 14 | tags 15 | *.pkg 16 | 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | env/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # dotenv 99 | .env 100 | 101 | # virtualenv 102 | .venv 103 | venv/ 104 | ENV/ 105 | 106 | # Spyder project settings 107 | .spyderproject 108 | .spyproject 109 | 110 | # Rope project settings 111 | .ropeproject 112 | 113 | # mkdocs documentation 114 | /site 115 | 116 | # mypy 117 | .mypy_cache/ 118 | 119 | .vscode 120 | *.swp 121 | 122 | # osx generated files 123 | .DS_Store 124 | .DS_Store? 125 | .Trashes 126 | ehthumbs.db 127 | Thumbs.db 128 | .idea 129 | 130 | # pytest 131 | .pytest_cache 132 | 133 | # tools/trust-doc-nbs 134 | docs_src/.last_checked 135 | 136 | # symlinks to fastai 137 | docs_src/fastai 138 | tools/fastai 139 | 140 | # link checker 141 | checklink/cookies.txt 142 | 143 | # .gitconfig is now autogenerated 144 | .gitconfig 145 | 146 | # Quarto installer 147 | .deb 148 | .pkg 149 | 150 | # Quarto 151 | .quarto 152 | 153 | /.quarto/ 154 | token 155 | deps/ 156 | token 157 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | from configparser import ConfigParser 3 | import setuptools 4 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 5 | 6 | # note: all settings are in settings.ini; edit there, not here 7 | config = ConfigParser(delimiters=['=']) 8 | config.read('settings.ini') 9 | cfg = config['DEFAULT'] 10 | 11 | cfg_keys = 'version description keywords author author_email'.split() 12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 14 | setup_cfg = {o:cfg[o] for o in cfg_keys} 15 | 16 | licenses = { 17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 21 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 22 | } 23 | statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 24 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 25 | py_versions = '3.6 3.7 3.8 3.9 3.10'.split() 26 | 27 | requirements = cfg.get('requirements','').split() 28 | if cfg.get('pip_requirements'): requirements += cfg.get('pip_requirements','').split() 29 | min_python = cfg['min_python'] 30 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 31 | dev_requirements = (cfg.get('dev_requirements') or '').split() 32 | 33 | setuptools.setup( 34 | name = cfg['lib_name'], 35 | license = lic[0], 36 | classifiers = [ 37 | 'Development Status :: ' + statuses[int(cfg['status'])], 38 | 'Intended Audience :: ' + cfg['audience'].title(), 39 | 'Natural Language :: ' + cfg['language'].title(), 40 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 41 | url = cfg['git_url'], 42 | packages = setuptools.find_packages(), 43 | include_package_data = True, 44 | install_requires = requirements, 45 | extras_require={ 'dev': dev_requirements }, 46 | dependency_links = cfg.get('dep_links','').split(), 47 | python_requires = '>=' + cfg['min_python'], 48 | long_description = open('README.md').read(), 49 | long_description_content_type = 'text/markdown', 50 | zip_safe = False, 51 | entry_points = { 52 | 'console_scripts': cfg.get('console_scripts','').split(), 53 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 54 | }, 55 | **setup_cfg) 56 | 57 | 58 | -------------------------------------------------------------------------------- /nbs/00_colab.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "05d86507-c00c-4f70-96a5-e47b56eb54e7", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "#|hide\n", 11 | "# Copyright 2022 Google LLC\n", 12 | "#\n", 13 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 14 | "# you may not use this file except in compliance with the License.\n", 15 | "# You may obtain a copy of the License at\n", 16 | "# \n", 17 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 18 | "# \n", 19 | "# Unless required by applicable law or agreed to in writing, software\n", 20 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 21 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 22 | "# See the License for the specific language governing permissions and\n", 23 | "# limitations under the License." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "5427930d-da44-425f-adac-6a9a2ca769e5", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| default_exp colab" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "85662442-f26a-400f-9656-005d342855de", 39 | "metadata": {}, 40 | "source": [ 41 | "# Colab module\n", 42 | "\n", 43 | "> Tools specific to working within a Colab environment." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "e682b176-cf69-4cf2-9d4c-d2acc18f93b3", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#|export\n", 54 | "def set_colab_output_cell_height(max_height):\n", 55 | " \"\"\"Sets the maximum height for the current notebook cell's output.\"\"\"\n", 56 | " import google\n", 57 | " \n", 58 | " from IPython.display import Javascript\n", 59 | " from string import Template\n", 60 | " s = Template('google.colab.output.setIframeHeight(0, true, {maxHeight: $height})')\n", 61 | " display(Javascript(s.substitute(height=max_height)))" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "id": "e6cb06b8-a5ee-4f12-bab6-a292581be7de", 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "#|eval: false\n", 72 | "set_output_cell_height(max_height=200)" 73 | ] 74 | } 75 | ], 76 | "metadata": { 77 | "kernelspec": { 78 | "display_name": "Python 3 (ipykernel)", 79 | "language": "python", 80 | "name": "python3" 81 | }, 82 | "widgets": { 83 | "application/vnd.jupyter.widget-state+json": { 84 | "state": {}, 85 | "version_major": 2, 86 | "version_minor": 0 87 | } 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 5 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | earthengine-jupyter 2 | ================ 3 | 4 | 5 | 6 | **NOTICE: This is an experimental project and is not an officially 7 | supported Google project. You are welcome to use it, but we do not 8 | guarantee stability.** 9 | 10 | ## Setup 11 | 12 | ``` python 13 | try: 14 | import ee_jupyter 15 | print('ee_jupyter was already installed.') 16 | except ModuleNotFoundError: 17 | print('ee_jupyter was not found. Installing now...') 18 | import os 19 | result = os.system('pip -q install earthengine-jupyter') 20 | ``` 21 | 22 | ## How to use 23 | 24 | This lib contains a 25 | [`Map`](https://google.github.io/earthengine-jupyter/ipyleaflet.html#map) 26 | class that can be used to display an interactive map. 27 | 28 | ``` python 29 | import ee 30 | from ee_jupyter.core import authenticate_if_needed 31 | from ee_jupyter.ipyleaflet import Map 32 | from ee_jupyter.layout import MapWithInspector 33 | import ipywidgets as widgets 34 | ``` 35 | 36 | ``` python 37 | authenticate_if_needed() 38 | ``` 39 | 40 | ``` python 41 | # Intialize the Earth Engine client library. 42 | ee.Initialize() 43 | ``` 44 | 45 | ``` python 46 | map1 = Map(center=(37.5924, -122.09), zoom=8) 47 | map1 48 | ``` 49 | 50 | Define an Earth Engine image layer, and add it to the interactive map. 51 | 52 | ``` python 53 | img1 = ee.Image("LANDSAT/LC09/C02/T1_L2/LC09_044034_20220127") 54 | visualization = { 55 | 'bands': ['SR_B4', 'SR_B3', 'SR_B2'], 56 | 'min': 0.2 / 0.0000275, 57 | 'max': 0.4 / 0.0000275, 58 | } 59 | map1.addLayer(eeObject=img1, visParams=visualization, name='Landsat scene') 60 | ``` 61 | 62 | We can also create an inspector object and associate it with the 63 | previously created map. 64 | 65 | ``` python 66 | from ee_jupyter.ipyleaflet import Inspector 67 | 68 | inspector1 = Inspector(map_object=map1) 69 | inspector1 70 | ``` 71 | 72 | Typically when you create a inspector object, you will want to display 73 | it with the map. The `MapWithInpsector` object adds a button that 74 | toggles the inspector functionality. 75 | 76 | The map below shows a Sentinel-2 image covering Paris. Click on the 77 | inspector toggle button to open the inspector. 78 | 79 | ``` python 80 | map_init_paris = {'center':(49.4, 2.3), 'zoom':8} 81 | m = MapWithInspector(**map_init_paris) 82 | image = ee.Image('COPERNICUS/S2_SR_HARMONIZED/20220604T104619_20220604T104620_T31UDQ') 83 | m.map.addLayer(image, {'bands': ['B4', 'B3', 'B2'], 'max': 2500}, 'Landsat image') 84 | m 85 | ``` 86 | 87 |
88 | 89 | > **Tip With Caption** 90 | > 91 | > Note that when viewed on GitHub Pages you can manipulate Jupyter 92 | > widgets independently, but the widgets do not interact with each 93 | > other. To experience the cross-widget interactivity, open up this 94 | > notebook in a Jupyter environment. 95 | 96 |
97 | 98 | # Displaying a Map Image 99 | 100 | If you want to display a static (non-interactive) image, you can do that 101 | as well. The `embed=True` parameter will allow the image to be saved 102 | within the notebook. 103 | 104 | ``` python 105 | from IPython.display import Image 106 | 107 | 108 | visualization['dimensions'] = 400 # maximum dimension for the image 109 | url = img1.getThumbUrl(visualization) 110 | 111 | Image(url=url, format='png', embed=True) 112 | ``` 113 | -------------------------------------------------------------------------------- /nbs/00_core.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "a299e0c1-82bf-4290-aab9-38397b05b533", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "#|hide\n", 11 | "# Copyright 2022 Google LLC\n", 12 | "#\n", 13 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 14 | "# you may not use this file except in compliance with the License.\n", 15 | "# You may obtain a copy of the License at\n", 16 | "# \n", 17 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 18 | "# \n", 19 | "# Unless required by applicable law or agreed to in writing, software\n", 20 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 21 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 22 | "# See the License for the specific language governing permissions and\n", 23 | "# limitations under the License." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "93bd9280-7fdc-47b3-9ab6-e5892cbc8a4b", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| default_exp core" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "00063c01-2b45-4a56-b50a-aa20d10f4803", 39 | "metadata": {}, 40 | "source": [ 41 | "# core module\n", 42 | "\n", 43 | "> Core tools for working in Jupyter environments." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "3d11593e-2715-4345-8831-2b1ceccac939", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#|hide\n", 54 | "from nbdev.showdoc import *" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "id": "0a1fbd0b-4b5d-493b-bda2-9aa0603da7d1", 60 | "metadata": {}, 61 | "source": [ 62 | "## Tools" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "id": "c34615c6-17a5-476e-89ff-9b195f8564ed", 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "#|export\n", 73 | "def authenticate_if_needed():\n", 74 | " \"\"\"Authenticate Earth Engine, if credentials do not exist.\n", 75 | "\n", 76 | " * Authenticate, if needed.\"\"\"\n", 77 | "\n", 78 | " import ee\n", 79 | " import os\n", 80 | "\n", 81 | " if os.path.exists(ee.oauth.get_credentials_path()):\n", 82 | " print('\\N{check mark} Authentication credentials were found.')\n", 83 | " else:\n", 84 | " ee.Authenticate()" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "id": "7f1a180e-2348-4d3b-9b87-3d1c7471713c", 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "✓ Authentication credentials were found.\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "authenticate_if_needed()" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "a36ef3eb-7720-47cc-92e4-23faa6141baf", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "#| hide\n", 113 | "import nbdev; nbdev.nbdev_export()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "widgets": { 124 | "application/vnd.jupyter.widget-state+json": { 125 | "state": {}, 126 | "version_major": 2, 127 | "version_minor": 0 128 | } 129 | } 130 | }, 131 | "nbformat": 4, 132 | "nbformat_minor": 5 133 | } 134 | -------------------------------------------------------------------------------- /docs/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the 73 | Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /ee_jupyter/_modidx.py: -------------------------------------------------------------------------------- 1 | # Autogenerated by nbdev 2 | 3 | d = { 'settings': { 'branch': 'main', 4 | 'doc_baseurl': '/earthengine-jupyter', 5 | 'doc_host': 'https://google.github.io', 6 | 'git_url': 'https://github.com/google/earthengine-jupyter', 7 | 'lib_path': 'ee_jupyter'}, 8 | 'syms': { 'ee_jupyter.colab': { 'ee_jupyter.colab.set_colab_output_cell_height': ( 'colab.html#set_colab_output_cell_height', 9 | 'ee_jupyter/colab.py')}, 10 | 'ee_jupyter.core': {'ee_jupyter.core.authenticate_if_needed': ('core.html#authenticate_if_needed', 'ee_jupyter/core.py')}, 11 | 'ee_jupyter.ipyleaflet': { 'ee_jupyter.ipyleaflet.Inspector': ('ipyleaflet.html#inspector', 'ee_jupyter/ipyleaflet.py'), 12 | 'ee_jupyter.ipyleaflet.Inspector.__init__': ( 'ipyleaflet.html#inspector.__init__', 13 | 'ee_jupyter/ipyleaflet.py'), 14 | 'ee_jupyter.ipyleaflet.Inspector.disabled': ( 'ipyleaflet.html#inspector.disabled', 15 | 'ee_jupyter/ipyleaflet.py'), 16 | 'ee_jupyter.ipyleaflet.Inspector.get_map': ( 'ipyleaflet.html#inspector.get_map', 17 | 'ee_jupyter/ipyleaflet.py'), 18 | 'ee_jupyter.ipyleaflet.Inspector.objects_node': ( 'ipyleaflet.html#inspector.objects_node', 19 | 'ee_jupyter/ipyleaflet.py'), 20 | 'ee_jupyter.ipyleaflet.Inspector.pixels_node': ( 'ipyleaflet.html#inspector.pixels_node', 21 | 'ee_jupyter/ipyleaflet.py'), 22 | 'ee_jupyter.ipyleaflet.Inspector.point_node': ( 'ipyleaflet.html#inspector.point_node', 23 | 'ee_jupyter/ipyleaflet.py'), 24 | 'ee_jupyter.ipyleaflet.Inspector.register_map': ( 'ipyleaflet.html#inspector.register_map', 25 | 'ee_jupyter/ipyleaflet.py'), 26 | 'ee_jupyter.ipyleaflet.Inspector.set_map': ( 'ipyleaflet.html#inspector.set_map', 27 | 'ee_jupyter/ipyleaflet.py'), 28 | 'ee_jupyter.ipyleaflet.Inspector.update_inspector': ( 'ipyleaflet.html#inspector.update_inspector', 29 | 'ee_jupyter/ipyleaflet.py'), 30 | 'ee_jupyter.ipyleaflet.Map': ('ipyleaflet.html#map', 'ee_jupyter/ipyleaflet.py'), 31 | 'ee_jupyter.ipyleaflet.Map.__init__': ('ipyleaflet.html#map.__init__', 'ee_jupyter/ipyleaflet.py'), 32 | 'ee_jupyter.ipyleaflet.Map.addLayer': ('ipyleaflet.html#map.addlayer', 'ee_jupyter/ipyleaflet.py'), 33 | 'ee_jupyter.ipyleaflet.StructureTree': ('ipyleaflet.html#structuretree', 'ee_jupyter/ipyleaflet.py'), 34 | 'ee_jupyter.ipyleaflet.StructureTree.__init__': ( 'ipyleaflet.html#structuretree.__init__', 35 | 'ee_jupyter/ipyleaflet.py'), 36 | 'ee_jupyter.ipyleaflet.StructureTree.__ipytreeify': ( 'ipyleaflet.html#structuretree.__ipytreeify', 37 | 'ee_jupyter/ipyleaflet.py'), 38 | 'ee_jupyter.ipyleaflet.TileLayerEE': ('ipyleaflet.html#tilelayeree', 'ee_jupyter/ipyleaflet.py'), 39 | 'ee_jupyter.ipyleaflet.TileLayerEE.__init__': ( 'ipyleaflet.html#tilelayeree.__init__', 40 | 'ee_jupyter/ipyleaflet.py')}, 41 | 'ee_jupyter.layout': { 'ee_jupyter.layout.MapWithInspector': ('layout.html#mapwithinspector', 'ee_jupyter/layout.py'), 42 | 'ee_jupyter.layout.MapWithInspector.__init__': ( 'layout.html#mapwithinspector.__init__', 43 | 'ee_jupyter/layout.py')}, 44 | 'ee_jupyter.rich_display': { 'ee_jupyter.rich_display.ee.Element.__repr__': ( 'rich_display.html#ee.element.__repr__', 45 | 'ee_jupyter/rich_display.py'), 46 | 'ee_jupyter.rich_display.ee.Element.__str__': ( 'rich_display.html#ee.element.__str__', 47 | 'ee_jupyter/rich_display.py'), 48 | 'ee_jupyter.rich_display.ee.Image.__add__': ( 'rich_display.html#ee.image.__add__', 49 | 'ee_jupyter/rich_display.py'), 50 | 'ee_jupyter.rich_display.ee.Image.__getitem__': ( 'rich_display.html#ee.image.__getitem__', 51 | 'ee_jupyter/rich_display.py'), 52 | 'ee_jupyter.rich_display.ee.Image.__mul__': ( 'rich_display.html#ee.image.__mul__', 53 | 'ee_jupyter/rich_display.py'), 54 | 'ee_jupyter.rich_display.ee.Image.__sub__': ( 'rich_display.html#ee.image.__sub__', 55 | 'ee_jupyter/rich_display.py'), 56 | 'ee_jupyter.rich_display.ee.Image.__truediv__': ( 'rich_display.html#ee.image.__truediv__', 57 | 'ee_jupyter/rich_display.py'), 58 | 'ee_jupyter.rich_display.ee.Image._ipython_display_': ( 'rich_display.html#ee.image._ipython_display_', 59 | 'ee_jupyter/rich_display.py')}}} 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /ee_jupyter/ipyleaflet.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_ipyleaflet.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['logger', 'DEFAULT_MAP_HEIGHT', 'SCALE_LEVEL_0', 'TileLayerEE', 'Map', 'StructureTree', 'Inspector'] 5 | 6 | # %% ../nbs/01_ipyleaflet.ipynb 4 7 | import ee 8 | import json 9 | import ipyleaflet 10 | import ipytree 11 | import ipywidgets as widgets 12 | import logging 13 | import traitlets 14 | 15 | # %% ../nbs/01_ipyleaflet.ipynb 5 16 | logger = logging.getLogger() 17 | logger.setLevel(logging.WARNING) 18 | 19 | # %% ../nbs/01_ipyleaflet.ipynb 7 20 | DEFAULT_MAP_HEIGHT = '400px' 21 | 22 | class TileLayerEE(ipyleaflet.TileLayer): 23 | """Class for a tile layer generated by Earth Engine. 24 | 25 | Attributes: 26 | ee_object: An Earth Engine object. 27 | """ 28 | def __init__(self, 29 | ee_object:ee.Image, # An Earth Engine Image object 30 | *args, 31 | **kwargs): 32 | 33 | self.ee_object = ee_object 34 | 35 | super(TileLayerEE, self).__init__(*args, **kwargs) 36 | 37 | 38 | class Map(ipyleaflet.Map): 39 | """An interactive map class for Jupyter clients. 40 | 41 | Attributes: 42 | layers_control: a boolean indicating whether to display a layers control. 43 | """ 44 | 45 | layers_control = traitlets.Bool(True) 46 | 47 | def __init__(self, *args, **kwargs): 48 | 49 | self.layers_control_instance = None 50 | 51 | # Set default values for the map. 52 | if 'zoom' not in kwargs: 53 | kwargs['zoom'] = 4 54 | 55 | if 'basemap' not in kwargs: 56 | kwargs['basemap'] = ipyleaflet.basemap_to_tiles(ipyleaflet.basemaps.Stamen.Watercolor) 57 | 58 | if 'height' not in kwargs: 59 | kwargs['height'] = DEFAULT_MAP_HEIGHT 60 | 61 | super(Map, self).__init__(*args, **kwargs) 62 | 63 | if self.layers_control: 64 | self.layers_control_instance = ipyleaflet.LayersControl(position='topright') 65 | self.add_control(self.layers_control_instance) 66 | 67 | self.default_style.cursor = 'crosshair' 68 | 69 | self.layout.width = '100%' 70 | 71 | 72 | def addLayer(self, eeObject, visParams={}, name=None, shown=True, opacity=1): 73 | """Adds a layer for an Earth Engine object.""" 74 | 75 | if name is None: 76 | # Provide a default name for the layer in the form "Layer ##" 77 | name = f'Layer {len(self.layers)}' 78 | 79 | def get_tile_layer_url(ee_image_object): 80 | map_id_dict = ee.Image(ee_image_object).getMapId() 81 | return map_id_dict['tile_fetcher'].url_format 82 | 83 | # Assume that the eeObject is an ee.Image. 84 | # TODO: Generalize this to other EE objects. 85 | ee_image = eeObject 86 | 87 | tile_url = get_tile_layer_url( 88 | ee_image.visualize(**visParams) 89 | ) 90 | self.add_layer(TileLayerEE(ee_object=eeObject, url=tile_url, name=name, visible=shown)) 91 | 92 | # %% ../nbs/01_ipyleaflet.ipynb 15 93 | class StructureTree(ipytree.Tree): 94 | 95 | JSON_PREFIX = 'JSON: ' 96 | 97 | def __init__(self, data, **kwargs): 98 | self.data = data 99 | super().__init__(StructureTree.__ipytreeify(data)) 100 | 101 | @staticmethod 102 | def __ipytreeify(data) -> tuple: 103 | """Return a sequence of nodes""" 104 | 105 | def is_json(in_str): 106 | '''Determines if a string is JSON.''' 107 | return in_str.startswith(StructureTree.JSON_PREFIX) 108 | 109 | def inline_style(x, color='blue'): 110 | '''Wrap a string with inline HTML styling.''' 111 | return f'{x}' 112 | 113 | def handle_node_open(change): 114 | if change['new']: 115 | nodes_unpacked = [] 116 | for node in change['owner'].nodes: 117 | # If there no subnodes, try to populate the subnodes. 118 | if len(node.nodes) == 0: 119 | if is_json(node.name): 120 | unpacked_json = json.loads(node.name[len(StructureTree.JSON_PREFIX):]) 121 | if isinstance(unpacked_json, list): 122 | nodes_unpacked = StructureTree.__ipytreeify(unpacked_json) 123 | elif isinstance(unpacked_json, dict): 124 | nodes_unpacked = StructureTree.__ipytreeify(unpacked_json) 125 | else: 126 | raise 127 | else: # ~is_json(node.name) 128 | nodes_unpacked.append(node) 129 | change['owner'].nodes = nodes_unpacked 130 | 131 | if isinstance(data, list): 132 | node_list = [] 133 | for count, el in enumerate(data): 134 | if isinstance(el, list): 135 | subnode = ipytree.Node( 136 | name=f'{inline_style("List")} ({len(el)} elements)', 137 | nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(el)}')], 138 | opened=False) 139 | subnode.observe(handle_node_open, names='opened') 140 | elif isinstance(el, dict): 141 | subnode = ipytree.Node( 142 | name=f'{inline_style("Object")} ({len(el)} elements)', 143 | nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(el)}')], 144 | opened=False) 145 | subnode.observe(handle_node_open, names='opened') 146 | else: 147 | subnode = ipytree.Node(f'{el}') 148 | node_list.append(subnode) 149 | return node_list 150 | elif isinstance(data, dict): 151 | node_list = [] 152 | for key, value in data.items(): 153 | if isinstance(value, list): 154 | subnode = ipytree.Node( 155 | name=f'{inline_style(key)}: List ({len(value)} elements)', 156 | nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(value)}')], 157 | opened=False) 158 | subnode.observe(handle_node_open, names='opened') 159 | elif isinstance(value, dict): 160 | subnode = ipytree.Node( 161 | name=f'{inline_style(key)}: Object ({len(value)} elements)', 162 | nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(value)}')], 163 | opened=False) 164 | subnode.observe(handle_node_open, names='opened') 165 | else: 166 | subnode = ipytree.Node(f'{inline_style(key)}: {value}') 167 | node_list.append(subnode) 168 | return node_list 169 | else: 170 | return (data, ) 171 | 172 | 173 | # %% ../nbs/01_ipyleaflet.ipynb 19 174 | # Map scale at Level 0 in meters/pixel 175 | SCALE_LEVEL_0 = 156543.03392 176 | 177 | #class Inspector(ipytree.Tree): 178 | class Inspector(StructureTree): 179 | """Class representing an inspector tool that responds to map events.""" 180 | 181 | def __init__(self, 182 | map_object=None, # An Earth Engine Image object 183 | *args, 184 | **kwargs): 185 | 186 | self.point_data = None 187 | # self.tree_data = None 188 | self.objects_data = {} 189 | self.pixels_data = {} 190 | 191 | self._disabled = False 192 | 193 | self.map_object = map_object 194 | self.layout.width = '100%' 195 | self.layout.max_height = '400px' 196 | self.layout.overflow = 'scroll' 197 | 198 | super(Inspector, self).__init__( 199 | data=['Click on the map to inspect the layers.'], 200 | *args, 201 | **kwargs 202 | ) 203 | 204 | if map_object: 205 | self.set_map(map_object) 206 | 207 | # self.update_inspector() 208 | 209 | @property 210 | def point_node(self): 211 | return self.nodes[0] 212 | 213 | @point_node.setter 214 | def point_node(self, new_point_node): 215 | #(lat, lon) = new_coords 216 | _temp_nodes = list(self.nodes) 217 | _temp_nodes[0] = new_point_node 218 | self.nodes = _temp_nodes 219 | 220 | @property 221 | def pixels_node(self): 222 | return self.nodes[1] 223 | 224 | @property 225 | def objects_node(self): 226 | return self.nodes[2] 227 | 228 | @property 229 | def disabled(self): 230 | return self._disabled 231 | 232 | @disabled.setter 233 | def disabled(self, state): 234 | self._disabled = state 235 | 236 | def update_inspector(self, coords=None): 237 | """Update information in the inspector tree.""" 238 | 239 | def _order_items(item_dict, ordering_list): 240 | """Orders dictionary items in a specified order.""" 241 | list_of_tuples = [(key, item_dict[key]) for key in [x for x in ordering_list if x in item_dict.keys()]] 242 | return dict(list_of_tuples) 243 | 244 | if self.map_object: 245 | if not self.disabled and coords: 246 | 247 | self.nodes = [ipytree.Node('Loading...')] 248 | 249 | (lat, lon) = coords 250 | 251 | scale_approx = SCALE_LEVEL_0 / 2**self.map_object.zoom 252 | self.point_data = { 253 | 'Longitude': f'{lon:.6f}', 254 | 'Latitude': f'{lat:.6f}', 255 | 'Zoom Level': f'{self.map_object.zoom:.0f}', 256 | 'Scale (approx. m/px)': f'{scale_approx:.2f}' 257 | } 258 | 259 | # Update the Pixels folder 260 | for layer in self.map_object.layers: 261 | if not layer.base: 262 | ee_type = ee.Algorithms.ObjectType(layer.ee_object).getInfo() 263 | 264 | if ee_type == 'Image': 265 | value_dict = layer.ee_object.reduceRegion( 266 | reducer=ee.Reducer.mean(), 267 | geometry=ee.Geometry.Point(lon, lat), 268 | scale=scale_approx, 269 | bestEffort=True 270 | ).getInfo() 271 | num_bands = len(value_dict.keys()) 272 | 273 | band_dict = {} 274 | has_unmasked_pixel = False 275 | for bandname in layer.ee_object.bandNames().getInfo(): 276 | if value_dict[bandname] is not None: 277 | has_unmasked_pixel = True 278 | band_dict.update({f'{bandname}': f'{value_dict[bandname]:.4f}'}) 279 | else: 280 | band_dict.update({f'{bandname}': f'masked'}) 281 | 282 | # if not has_unmasked_pixel: 283 | # layer_node.nodes = [ 284 | # ipytree.Node(f'No unmasked pixels at clicked point.'), 285 | # ] 286 | # pixel_nodes.append(layer_node) 287 | self.pixels_data.update({f'{layer.name}': band_dict}) 288 | 289 | # Update the Objects folder 290 | for layer in self.map_object.layers: 291 | if not layer.base: 292 | ee_type = ee.Algorithms.ObjectType(layer.ee_object).getInfo() 293 | layer_info = layer.ee_object.getInfo() 294 | 295 | # Order the layer information. 296 | ordering_list = ['type', 'id', 'version', 'bands', 'properties'] 297 | layer_info = _order_items(layer_info, ordering_list) 298 | layer_dict = {f'{layer.name}': layer_info} 299 | 300 | self.objects_data.update(layer_dict) 301 | 302 | self.nodes = [ 303 | ipytree.Node('Point', nodes=StructureTree(self.point_data).nodes), 304 | ipytree.Node('Pixels', nodes=StructureTree(self.pixels_data).nodes, opened=False), 305 | ipytree.Node('Objects', nodes=StructureTree(self.objects_data).nodes, opened=False), 306 | ] 307 | 308 | def register_map(self, map_object): 309 | def handle_interaction(type, event, coordinates): 310 | if type == 'click': 311 | self.update_inspector(coordinates) 312 | map_object.on_interaction(handle_interaction) 313 | 314 | def set_map(self, map_object): 315 | self.map_object = map_object 316 | self.register_map(map_object) 317 | 318 | def get_map(self): 319 | return self.map_object 320 | -------------------------------------------------------------------------------- /nbs/02_layout.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "94288810-bd72-4341-8a86-96ae2ae5848d", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "#|hide\n", 11 | "# Copyright 2022 Google LLC\n", 12 | "#\n", 13 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 14 | "# you may not use this file except in compliance with the License.\n", 15 | "# You may obtain a copy of the License at\n", 16 | "# \n", 17 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 18 | "# \n", 19 | "# Unless required by applicable law or agreed to in writing, software\n", 20 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 21 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 22 | "# See the License for the specific language governing permissions and\n", 23 | "# limitations under the License." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "5bde49e7-42e2-4483-a14f-3f7ec7f32fc1", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| default_exp layout" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "7ace02d3-fb7d-4128-ba94-5c2a883b0fd2", 39 | "metadata": {}, 40 | "source": [ 41 | "# layout module\n", 42 | "\n", 43 | "> Provides panels of common layouts of map & inspector widgets." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "b9f705ad-d869-413f-8eb6-3890a47d6440", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#|hide\n", 54 | "from nbdev.showdoc import *" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "d5d1a7e1-46f9-4bc4-adbb-0db383d453b1", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "#|export\n", 65 | "from ee_jupyter.ipyleaflet import Map\n", 66 | "from ee_jupyter.ipyleaflet import Inspector\n", 67 | "import ee\n", 68 | "import ipyleaflet\n", 69 | "import ipywidgets as widgets\n", 70 | "import logging" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "id": "b45a5144-7064-4e79-b8b2-8380a1424266", 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "logger = logging.getLogger()\n", 81 | "logger.setLevel(logging.WARNING)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "id": "bbd4e260-f0a7-49e9-b61f-faeb6647a4f9", 87 | "metadata": {}, 88 | "source": [ 89 | "## Inspector layouts" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "e69e6c11-318d-4375-b4cd-e91a787cae6f", 95 | "metadata": {}, 96 | "source": [ 97 | "### Map with Inspector" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "id": "da6d0c44-3929-4c6f-826c-ab73fe72efa9", 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "#|export\n", 108 | "class MapWithInspector(widgets.HBox):\n", 109 | " \"\"\"A map with a tobbleable inspector.\"\"\"\n", 110 | " \n", 111 | " # Font Awesome icons: https://fontawesome.com/v4/examples/\n", 112 | " ICON_INSPECTOR_SHOWN = 'info-circle 2x fw inverse'\n", 113 | " ICON_INSPECTOR_HIDDEN = 'info-circle 2x fw'\n", 114 | " \n", 115 | " def __init__(self, **kwargs):\n", 116 | " \n", 117 | " self.map = Map(**kwargs)\n", 118 | " \n", 119 | " button_inspector_toggle = widgets.Button(\n", 120 | " icon = self.ICON_INSPECTOR_SHOWN,\n", 121 | " layout = widgets.Layout(width='45px', height='45px', padding='0px')\n", 122 | " )\n", 123 | " \n", 124 | " def show_inspector(visible):\n", 125 | " if visible:\n", 126 | " self.inspector.layout.width = None\n", 127 | " self.inspector.layout.min_width = f'300px'\n", 128 | " self.inspector.disabled = False\n", 129 | " else:\n", 130 | " self.inspector.layout.width = f'0%'\n", 131 | " self.inspector.layout.min_width = None\n", 132 | " self.inspector.disabled = True\n", 133 | " \n", 134 | " def on_button_clicked(_b):\n", 135 | " if _b.icon == self.ICON_INSPECTOR_HIDDEN:\n", 136 | " show_inspector(False)\n", 137 | " _b.icon = self.ICON_INSPECTOR_SHOWN\n", 138 | " else:\n", 139 | " show_inspector(True)\n", 140 | " _b.icon = self.ICON_INSPECTOR_HIDDEN\n", 141 | " button_inspector_toggle.on_click(on_button_clicked)\n", 142 | " \n", 143 | " widget_control1 = ipyleaflet.WidgetControl(widget=button_inspector_toggle,\n", 144 | " position='topright')\n", 145 | " self.map.add_control(widget_control1)\n", 146 | " self.inspector = Inspector(self.map)\n", 147 | " \n", 148 | " kwargs['children'] = [self.map, self.inspector]\n", 149 | " show_inspector(False)\n", 150 | " \n", 151 | " super().__init__(**kwargs)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "id": "eb92e252-d2dd-41ca-ac63-f5a9cb38a964", 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "ee.Initialize()\n", 162 | "\n", 163 | "mt = MapWithInspector(center=(37.5924, -122.09), zoom=8)\n", 164 | "mt.map.addLayer(ee.Image.pixelLonLat(), {'min':-90, 'max':90, 'opacity':0.5}, 'LonLat')\n", 165 | "mt.map.addLayer(\n", 166 | " ee.Image('LANDSAT/LC09/C02/T1_L2/LC09_187058_20220105'),\n", 167 | " {'min':0, 'max':90, 'opacity':0.5},\n", 168 | " 'Landsat')\n", 169 | "mt" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "id": "8408c133-dced-405b-8f7f-f64fbfd9642b", 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "#| hide\n", 180 | "import nbdev; nbdev.nbdev_export()" 181 | ] 182 | } 183 | ], 184 | "metadata": { 185 | "kernelspec": { 186 | "display_name": "Python 3 (ipykernel)", 187 | "language": "python", 188 | "name": "python3" 189 | }, 190 | "widgets": { 191 | "application/vnd.jupyter.widget-state+json": { 192 | "state": { 193 | "0348e638d952476886c1b67898ce2efa": { 194 | "model_module": "@jupyter-widgets/controls", 195 | "model_module_version": "2.0.0", 196 | "model_name": "HBoxModel", 197 | "state": { 198 | "children": [ 199 | "IPY_MODEL_4475643b95a645ef9dd0394099021742", 200 | "IPY_MODEL_060e7aa9d80a41ef96c4f3e1c4784cdb" 201 | ], 202 | "layout": "IPY_MODEL_a8a8fbbb1a344d06bfc8bfb3f5071260" 203 | } 204 | }, 205 | "060e7aa9d80a41ef96c4f3e1c4784cdb": { 206 | "model_module": "ipytree", 207 | "model_module_version": "^0.2", 208 | "model_name": "TreeModel", 209 | "state": { 210 | "_model_module_version": "^0.2", 211 | "_view_module_version": "^0.2", 212 | "layout": "IPY_MODEL_ca0daae1124d4046ad85c02468a5d4cd", 213 | "nodes": [ 214 | "IPY_MODEL_205b1e193efa4e7aa16aa7557f1d3e84" 215 | ] 216 | } 217 | }, 218 | "09abd7603e1947ad9ea2e33c3a052f79": { 219 | "model_module": "@jupyter-widgets/controls", 220 | "model_module_version": "2.0.0", 221 | "model_name": "ButtonModel", 222 | "state": { 223 | "icon": "info-circle 2x fw inverse", 224 | "layout": "IPY_MODEL_369ad0d1a60b4823ae9d7b7bd8a3fae0", 225 | "style": "IPY_MODEL_a06a49b1f62b4560a16551db1afb383d", 226 | "tooltip": null 227 | } 228 | }, 229 | "121ce27b46384c9e9cdd8690ee4f8233": { 230 | "model_module": "jupyter-leaflet", 231 | "model_module_version": "^0.17", 232 | "model_name": "LeafletTileLayerModel", 233 | "state": { 234 | "_model_module_version": "^0.17", 235 | "_view_module_version": "^0.17", 236 | "name": "LonLat", 237 | "options": [ 238 | "attribution", 239 | "bounds", 240 | "detect_retina", 241 | "max_native_zoom", 242 | "max_zoom", 243 | "min_native_zoom", 244 | "min_zoom", 245 | "no_wrap", 246 | "tile_size", 247 | "tms", 248 | "zoom_offset" 249 | ], 250 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/94684f9069ec559fbd6d13a6e0b6ee1c-47daebef121db141d808c72d796843fa/tiles/{z}/{x}/{y}" 251 | } 252 | }, 253 | "1aa0513de1d240d7ac0ed5276913584c": { 254 | "model_module": "jupyter-leaflet", 255 | "model_module_version": "^0.17", 256 | "model_name": "LeafletTileLayerModel", 257 | "state": { 258 | "_model_module_version": "^0.17", 259 | "_view_module_version": "^0.17", 260 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 261 | "base": true, 262 | "max_zoom": 16, 263 | "min_zoom": 1, 264 | "name": "Stamen.Watercolor", 265 | "options": [ 266 | "attribution", 267 | "bounds", 268 | "detect_retina", 269 | "max_native_zoom", 270 | "max_zoom", 271 | "min_native_zoom", 272 | "min_zoom", 273 | "no_wrap", 274 | "tile_size", 275 | "tms", 276 | "zoom_offset" 277 | ], 278 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 279 | } 280 | }, 281 | "1d339c7b95cb4b6eb2d891acb9a530bd": { 282 | "model_module": "jupyter-leaflet", 283 | "model_module_version": "^0.17", 284 | "model_name": "LeafletZoomControlModel", 285 | "state": { 286 | "_model_module_version": "^0.17", 287 | "_view_module_version": "^0.17", 288 | "options": [ 289 | "position", 290 | "zoom_in_text", 291 | "zoom_in_title", 292 | "zoom_out_text", 293 | "zoom_out_title" 294 | ] 295 | } 296 | }, 297 | "205b1e193efa4e7aa16aa7557f1d3e84": { 298 | "model_module": "ipytree", 299 | "model_module_version": "^0.2", 300 | "model_name": "NodeModel", 301 | "state": { 302 | "_id": "c3987ec6-b01e-4ea4-91cc-fe0916058119", 303 | "_model_module_version": "^0.2", 304 | "_view_module_version": "^0.2", 305 | "name": "Click on the map to inspect the layers." 306 | } 307 | }, 308 | "2ddbf75121c14d5bb7a9e5c846862ed4": { 309 | "model_module": "jupyter-leaflet", 310 | "model_module_version": "^0.17", 311 | "model_name": "LeafletMapStyleModel", 312 | "state": { 313 | "_model_module_version": "^0.17" 314 | } 315 | }, 316 | "369ad0d1a60b4823ae9d7b7bd8a3fae0": { 317 | "model_module": "@jupyter-widgets/base", 318 | "model_module_version": "2.0.0", 319 | "model_name": "LayoutModel", 320 | "state": { 321 | "height": "45px", 322 | "padding": "0px", 323 | "width": "45px" 324 | } 325 | }, 326 | "4475643b95a645ef9dd0394099021742": { 327 | "model_module": "jupyter-leaflet", 328 | "model_module_version": "^0.17", 329 | "model_name": "LeafletMapModel", 330 | "state": { 331 | "_model_module_version": "^0.17", 332 | "_view_module_version": "^0.17", 333 | "bottom": 25573, 334 | "center": [ 335 | 37.5924, 336 | -122.09 337 | ], 338 | "controls": [ 339 | "IPY_MODEL_1d339c7b95cb4b6eb2d891acb9a530bd", 340 | "IPY_MODEL_a805777a1c7a420eb215fcc95e05f946", 341 | "IPY_MODEL_8cc930b450344fbbb377c4b48070e8b2", 342 | "IPY_MODEL_46a8df608a1f410aaeaaed4792610361" 343 | ], 344 | "default_style": "IPY_MODEL_51d11b01ff444bbba4da6337a723ae83", 345 | "dragging_style": "IPY_MODEL_d8c9de1b0ffb404aba9f15f8ece44906", 346 | "east": -119.65759277343751, 347 | "fullscreen": false, 348 | "interpolation": "bilinear", 349 | "layers": [ 350 | "IPY_MODEL_1aa0513de1d240d7ac0ed5276913584c", 351 | "IPY_MODEL_121ce27b46384c9e9cdd8690ee4f8233", 352 | "IPY_MODEL_6b453cf26a674bd0816ffaa1b4abce61" 353 | ], 354 | "layout": "IPY_MODEL_50b2449c808e448196c573a1f49050da", 355 | "left": 10099, 356 | "modisdate": "2023-01-10", 357 | "north": 38.45789034424927, 358 | "options": [ 359 | "bounce_at_zoom_limits", 360 | "box_zoom", 361 | "center", 362 | "close_popup_on_click", 363 | "double_click_zoom", 364 | "dragging", 365 | "fullscreen", 366 | "inertia", 367 | "inertia_deceleration", 368 | "inertia_max_speed", 369 | "interpolation", 370 | "keyboard", 371 | "keyboard_pan_offset", 372 | "keyboard_zoom_offset", 373 | "max_zoom", 374 | "min_zoom", 375 | "prefer_canvas", 376 | "scroll_wheel_zoom", 377 | "tap", 378 | "tap_tolerance", 379 | "touch_zoom", 380 | "world_copy_jump", 381 | "zoom", 382 | "zoom_animation_threshold", 383 | "zoom_delta", 384 | "zoom_snap" 385 | ], 386 | "prefer_canvas": false, 387 | "right": 10985, 388 | "south": 36.71687068791304, 389 | "style": "IPY_MODEL_2ddbf75121c14d5bb7a9e5c846862ed4", 390 | "top": 25173, 391 | "west": -124.52453613281251, 392 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/02_layout.ipynb", 393 | "zoom": 8 394 | } 395 | }, 396 | "46a8df608a1f410aaeaaed4792610361": { 397 | "model_module": "jupyter-leaflet", 398 | "model_module_version": "^0.17", 399 | "model_name": "LeafletWidgetControlModel", 400 | "state": { 401 | "_model_module": "jupyter-leaflet", 402 | "_model_module_version": "^0.17", 403 | "_view_count": null, 404 | "_view_module": "jupyter-leaflet", 405 | "_view_module_version": "^0.17", 406 | "options": [ 407 | "position", 408 | "transparent_bg" 409 | ], 410 | "position": "topright", 411 | "widget": "IPY_MODEL_09abd7603e1947ad9ea2e33c3a052f79" 412 | } 413 | }, 414 | "50b2449c808e448196c573a1f49050da": { 415 | "model_module": "@jupyter-widgets/base", 416 | "model_module_version": "2.0.0", 417 | "model_name": "LayoutModel", 418 | "state": { 419 | "width": "100%" 420 | } 421 | }, 422 | "51d11b01ff444bbba4da6337a723ae83": { 423 | "model_module": "jupyter-leaflet", 424 | "model_module_version": "^0.17", 425 | "model_name": "LeafletMapStyleModel", 426 | "state": { 427 | "_model_module_version": "^0.17", 428 | "cursor": "crosshair" 429 | } 430 | }, 431 | "6b453cf26a674bd0816ffaa1b4abce61": { 432 | "model_module": "jupyter-leaflet", 433 | "model_module_version": "^0.17", 434 | "model_name": "LeafletTileLayerModel", 435 | "state": { 436 | "_model_module_version": "^0.17", 437 | "_view_module_version": "^0.17", 438 | "name": "Landsat", 439 | "options": [ 440 | "attribution", 441 | "bounds", 442 | "detect_retina", 443 | "max_native_zoom", 444 | "max_zoom", 445 | "min_native_zoom", 446 | "min_zoom", 447 | "no_wrap", 448 | "tile_size", 449 | "tms", 450 | "zoom_offset" 451 | ], 452 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/6ad5740ea0606cba020bc83b7def6383-d6df261b4d34a08af9f991ac81f4f5c2/tiles/{z}/{x}/{y}" 453 | } 454 | }, 455 | "8cc930b450344fbbb377c4b48070e8b2": { 456 | "model_module": "jupyter-leaflet", 457 | "model_module_version": "^0.17", 458 | "model_name": "LeafletLayersControlModel", 459 | "state": { 460 | "_model_module_version": "^0.17", 461 | "_view_module_version": "^0.17", 462 | "options": [ 463 | "position" 464 | ], 465 | "position": "topright" 466 | } 467 | }, 468 | "a06a49b1f62b4560a16551db1afb383d": { 469 | "model_module": "@jupyter-widgets/controls", 470 | "model_module_version": "2.0.0", 471 | "model_name": "ButtonStyleModel", 472 | "state": { 473 | "font_family": null, 474 | "font_size": null, 475 | "font_style": null, 476 | "font_variant": null, 477 | "font_weight": null, 478 | "text_color": null, 479 | "text_decoration": null 480 | } 481 | }, 482 | "a805777a1c7a420eb215fcc95e05f946": { 483 | "model_module": "jupyter-leaflet", 484 | "model_module_version": "^0.17", 485 | "model_name": "LeafletAttributionControlModel", 486 | "state": { 487 | "_model_module_version": "^0.17", 488 | "_view_module_version": "^0.17", 489 | "options": [ 490 | "position", 491 | "prefix" 492 | ], 493 | "position": "bottomright", 494 | "prefix": "ipyleaflet" 495 | } 496 | }, 497 | "a8a8fbbb1a344d06bfc8bfb3f5071260": { 498 | "model_module": "@jupyter-widgets/base", 499 | "model_module_version": "2.0.0", 500 | "model_name": "LayoutModel", 501 | "state": {} 502 | }, 503 | "ca0daae1124d4046ad85c02468a5d4cd": { 504 | "model_module": "@jupyter-widgets/base", 505 | "model_module_version": "2.0.0", 506 | "model_name": "LayoutModel", 507 | "state": { 508 | "max_height": "400px", 509 | "overflow": "scroll", 510 | "width": "0%" 511 | } 512 | }, 513 | "d8c9de1b0ffb404aba9f15f8ece44906": { 514 | "model_module": "jupyter-leaflet", 515 | "model_module_version": "^0.17", 516 | "model_name": "LeafletMapStyleModel", 517 | "state": { 518 | "_model_module_version": "^0.17", 519 | "cursor": "move" 520 | } 521 | } 522 | }, 523 | "version_major": 2, 524 | "version_minor": 0 525 | } 526 | } 527 | }, 528 | "nbformat": 4, 529 | "nbformat_minor": 5 530 | } 531 | -------------------------------------------------------------------------------- /nbs/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "raw", 5 | "metadata": {}, 6 | "source": [ 7 | "---\n", 8 | "execute:\n", 9 | " enabled: true\n", 10 | "---" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "#|hide\n", 20 | "# Copyright 2022 Google LLC\n", 21 | "#\n", 22 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 23 | "# you may not use this file except in compliance with the License.\n", 24 | "# You may obtain a copy of the License at\n", 25 | "# \n", 26 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 27 | "# \n", 28 | "# Unless required by applicable law or agreed to in writing, software\n", 29 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 30 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 31 | "# See the License for the specific language governing permissions and\n", 32 | "# limitations under the License." 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "# earthengine-jupyter\n", 40 | "\n", 41 | "> Tools for working with the Earth Engine from a Jupyter development environment" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "**NOTICE: This is an experimental project and is not an officially supported Google project. You are welcome to use it, but we do not guarantee stability.**" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "## Setup" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "try:\n", 65 | " import ee_jupyter\n", 66 | " print('ee_jupyter was already installed.')\n", 67 | "except ModuleNotFoundError:\n", 68 | " print('ee_jupyter was not found. Installing now...')\n", 69 | " import os\n", 70 | " result = os.system('pip -q install earthengine-jupyter')" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "## How to use" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "This lib contains a `Map` class that can be used to display an interactive map." 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "import ee\n", 94 | "from ee_jupyter.core import authenticate_if_needed\n", 95 | "from ee_jupyter.ipyleaflet import Map\n", 96 | "from ee_jupyter.layout import MapWithInspector\n", 97 | "import ipywidgets as widgets" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "authenticate_if_needed()" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "# Intialize the Earth Engine client library.\n", 116 | "ee.Initialize()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "map1 = Map(center=(37.5924, -122.09), zoom=8)\n", 126 | "map1" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "Define an Earth Engine image layer, and add it to the interactive map." 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "img1 = ee.Image(\"LANDSAT/LC09/C02/T1_L2/LC09_044034_20220127\")\n", 143 | "visualization = {\n", 144 | " 'bands': ['SR_B4', 'SR_B3', 'SR_B2'],\n", 145 | " 'min': 0.2 / 0.0000275,\n", 146 | " 'max': 0.4 / 0.0000275,\n", 147 | "}\n", 148 | "map1.addLayer(eeObject=img1, visParams=visualization, name='Landsat scene')" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "We can also create an inspector object and associate it with the previously created map." 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": null, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "from ee_jupyter.ipyleaflet import Inspector\n", 165 | "\n", 166 | "inspector1 = Inspector(map_object=map1)\n", 167 | "inspector1" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "Typically when you create a inspector object, you will want to display it with the map. The `MapWithInpsector` object adds a button that toggles the inspector functionality." 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "The map below shows a Sentinel-2 image covering Paris. Click on the inspector toggle button to open the inspector." 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "map_init_paris = {'center':(49.4, 2.3), 'zoom':8}\n", 191 | "m = MapWithInspector(**map_init_paris)\n", 192 | "image = ee.Image('COPERNICUS/S2_SR_HARMONIZED/20220604T104619_20220604T104620_T31UDQ')\n", 193 | "m.map.addLayer(image, {'bands': ['B4', 'B3', 'B2'], 'max': 2500}, 'Landsat image')\n", 194 | "m" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | ":::{.callout-tip}\n", 202 | "## Tip With Caption\n", 203 | "\n", 204 | "Note that when viewed on GitHub Pages you can manipulate Jupyter widgets independently, but the widgets do not interact with each other. To experience the cross-widget interactivity, open up this notebook in a Jupyter environment. \n", 205 | ":::" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "metadata": {}, 211 | "source": [ 212 | "# Displaying a Map Image" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "If you want to display a static (non-interactive) image, you can do that as well. The `embed=True` parameter will allow the image to be saved within the notebook." 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "from IPython.display import Image\n", 229 | "\n", 230 | "\n", 231 | "visualization['dimensions'] = 400 # maximum dimension for the image\n", 232 | "url = img1.getThumbUrl(visualization)\n", 233 | "\n", 234 | "Image(url=url, format='png', embed=True)" 235 | ] 236 | } 237 | ], 238 | "metadata": { 239 | "kernelspec": { 240 | "display_name": "Python 3 (ipykernel)", 241 | "language": "python", 242 | "name": "python3" 243 | }, 244 | "widgets": { 245 | "application/vnd.jupyter.widget-state+json": { 246 | "state": { 247 | "18534294b7404458989fce691d6dd928": { 248 | "model_module": "jupyter-leaflet", 249 | "model_module_version": "^0.17", 250 | "model_name": "LeafletAttributionControlModel", 251 | "state": { 252 | "_model_module_version": "^0.17", 253 | "_view_module_version": "^0.17", 254 | "options": [ 255 | "position", 256 | "prefix" 257 | ], 258 | "position": "bottomright", 259 | "prefix": "ipyleaflet" 260 | } 261 | }, 262 | "1e1aec83e42045bf9ccff2e7ea1f3552": { 263 | "model_module": "jupyter-leaflet", 264 | "model_module_version": "^0.17", 265 | "model_name": "LeafletWidgetControlModel", 266 | "state": { 267 | "_model_module": "jupyter-leaflet", 268 | "_model_module_version": "^0.17", 269 | "_view_count": null, 270 | "_view_module": "jupyter-leaflet", 271 | "_view_module_version": "^0.17", 272 | "options": [ 273 | "position", 274 | "transparent_bg" 275 | ], 276 | "position": "topright", 277 | "widget": "IPY_MODEL_e63129a6b6214b86b55161f9b3b01707" 278 | } 279 | }, 280 | "2319a7c35eb146fab3231d5b3c2d223f": { 281 | "model_module": "ipytree", 282 | "model_module_version": "^0.2", 283 | "model_name": "NodeModel", 284 | "state": { 285 | "_id": "6615fdb1-b4b4-4271-bdd5-1a3df55b6834", 286 | "_model_module_version": "^0.2", 287 | "_view_module_version": "^0.2", 288 | "name": "Click on the map to inspect the layers." 289 | } 290 | }, 291 | "2625695324c1472cbab709507ae2db40": { 292 | "model_module": "jupyter-leaflet", 293 | "model_module_version": "^0.17", 294 | "model_name": "LeafletZoomControlModel", 295 | "state": { 296 | "_model_module_version": "^0.17", 297 | "_view_module_version": "^0.17", 298 | "options": [ 299 | "position", 300 | "zoom_in_text", 301 | "zoom_in_title", 302 | "zoom_out_text", 303 | "zoom_out_title" 304 | ] 305 | } 306 | }, 307 | "29efe12ec397463da27a60cd3b558f3f": { 308 | "model_module": "jupyter-leaflet", 309 | "model_module_version": "^0.17", 310 | "model_name": "LeafletMapStyleModel", 311 | "state": { 312 | "_model_module_version": "^0.17", 313 | "cursor": "crosshair" 314 | } 315 | }, 316 | "2da9251158dd4165bdd4dbef7cef2d5b": { 317 | "model_module": "ipytree", 318 | "model_module_version": "^0.2", 319 | "model_name": "NodeModel", 320 | "state": { 321 | "_id": "e6535316-8fcc-48fa-99f7-402223e43e54", 322 | "_model_module_version": "^0.2", 323 | "_view_module_version": "^0.2", 324 | "name": "Click on the map to inspect the layers." 325 | } 326 | }, 327 | "336ea372a26f40318a5efb5d3594ffbd": { 328 | "model_module": "@jupyter-widgets/base", 329 | "model_module_version": "2.0.0", 330 | "model_name": "LayoutModel", 331 | "state": { 332 | "width": "100%" 333 | } 334 | }, 335 | "3c36a4f289d04fb2886e978be5b06a17": { 336 | "model_module": "@jupyter-widgets/base", 337 | "model_module_version": "2.0.0", 338 | "model_name": "LayoutModel", 339 | "state": { 340 | "width": "100%" 341 | } 342 | }, 343 | "47bb304079cd400bbe098184343ad277": { 344 | "model_module": "@jupyter-widgets/base", 345 | "model_module_version": "2.0.0", 346 | "model_name": "LayoutModel", 347 | "state": { 348 | "max_height": "400px", 349 | "overflow": "scroll", 350 | "width": "0%" 351 | } 352 | }, 353 | "4f8b472c81eb412f9f90be778d5b046f": { 354 | "model_module": "@jupyter-widgets/base", 355 | "model_module_version": "2.0.0", 356 | "model_name": "LayoutModel", 357 | "state": { 358 | "max_height": "400px", 359 | "overflow": "scroll", 360 | "width": "100%" 361 | } 362 | }, 363 | "505a42fde6424bdfb971ae73d8a26c74": { 364 | "model_module": "@jupyter-widgets/base", 365 | "model_module_version": "2.0.0", 366 | "model_name": "LayoutModel", 367 | "state": { 368 | "height": "45px", 369 | "padding": "0px", 370 | "width": "45px" 371 | } 372 | }, 373 | "51d3cd5c24bb40f48c1508c09a932f11": { 374 | "model_module": "jupyter-leaflet", 375 | "model_module_version": "^0.17", 376 | "model_name": "LeafletTileLayerModel", 377 | "state": { 378 | "_model_module_version": "^0.17", 379 | "_view_module_version": "^0.17", 380 | "name": "Landsat image", 381 | "options": [ 382 | "attribution", 383 | "bounds", 384 | "detect_retina", 385 | "max_native_zoom", 386 | "max_zoom", 387 | "min_native_zoom", 388 | "min_zoom", 389 | "no_wrap", 390 | "tile_size", 391 | "tms", 392 | "zoom_offset" 393 | ], 394 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/4c51a3d4f17a77f561c5d23cff888f0b-53fbcb0a51533daa78bd3a67b97d62d9/tiles/{z}/{x}/{y}" 395 | } 396 | }, 397 | "5d27cf14d73a41aeab2ea720a257ca70": { 398 | "model_module": "jupyter-leaflet", 399 | "model_module_version": "^0.17", 400 | "model_name": "LeafletMapStyleModel", 401 | "state": { 402 | "_model_module_version": "^0.17", 403 | "cursor": "move" 404 | } 405 | }, 406 | "6339a07fac08447aa064cf7417a55f6e": { 407 | "model_module": "jupyter-leaflet", 408 | "model_module_version": "^0.17", 409 | "model_name": "LeafletMapStyleModel", 410 | "state": { 411 | "_model_module_version": "^0.17", 412 | "cursor": "crosshair" 413 | } 414 | }, 415 | "682f82c511af4356a71ab51f4288b6e9": { 416 | "model_module": "jupyter-leaflet", 417 | "model_module_version": "^0.17", 418 | "model_name": "LeafletMapStyleModel", 419 | "state": { 420 | "_model_module_version": "^0.17", 421 | "cursor": "move" 422 | } 423 | }, 424 | "6e2f1f92660c40d3b3dd67547e65f278": { 425 | "model_module": "ipytree", 426 | "model_module_version": "^0.2", 427 | "model_name": "TreeModel", 428 | "state": { 429 | "_model_module_version": "^0.2", 430 | "_view_module_version": "^0.2", 431 | "layout": "IPY_MODEL_47bb304079cd400bbe098184343ad277", 432 | "nodes": [ 433 | "IPY_MODEL_2da9251158dd4165bdd4dbef7cef2d5b" 434 | ] 435 | } 436 | }, 437 | "75f3da4f32104f3b99e6f0886fdd01fb": { 438 | "model_module": "@jupyter-widgets/controls", 439 | "model_module_version": "2.0.0", 440 | "model_name": "HBoxModel", 441 | "state": { 442 | "children": [ 443 | "IPY_MODEL_b305b8311ddd4319920041f8c51cdc69", 444 | "IPY_MODEL_6e2f1f92660c40d3b3dd67547e65f278" 445 | ], 446 | "layout": "IPY_MODEL_7aef74e0d48b416eaab11172e171c3ee" 447 | } 448 | }, 449 | "775711c799aa4a1e83a8871807e4e87a": { 450 | "model_module": "jupyter-leaflet", 451 | "model_module_version": "^0.17", 452 | "model_name": "LeafletTileLayerModel", 453 | "state": { 454 | "_model_module_version": "^0.17", 455 | "_view_module_version": "^0.17", 456 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 457 | "base": true, 458 | "max_zoom": 16, 459 | "min_zoom": 1, 460 | "name": "Stamen.Watercolor", 461 | "options": [ 462 | "attribution", 463 | "bounds", 464 | "detect_retina", 465 | "max_native_zoom", 466 | "max_zoom", 467 | "min_native_zoom", 468 | "min_zoom", 469 | "no_wrap", 470 | "tile_size", 471 | "tms", 472 | "zoom_offset" 473 | ], 474 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 475 | } 476 | }, 477 | "7aef74e0d48b416eaab11172e171c3ee": { 478 | "model_module": "@jupyter-widgets/base", 479 | "model_module_version": "2.0.0", 480 | "model_name": "LayoutModel", 481 | "state": {} 482 | }, 483 | "8e5a271e635948deb4e3362a6b1b91b2": { 484 | "model_module": "jupyter-leaflet", 485 | "model_module_version": "^0.17", 486 | "model_name": "LeafletLayersControlModel", 487 | "state": { 488 | "_model_module_version": "^0.17", 489 | "_view_module_version": "^0.17", 490 | "options": [ 491 | "position" 492 | ], 493 | "position": "topright" 494 | } 495 | }, 496 | "99287d20cfba47949e23c59052a876f5": { 497 | "model_module": "jupyter-leaflet", 498 | "model_module_version": "^0.17", 499 | "model_name": "LeafletMapModel", 500 | "state": { 501 | "_model_module_version": "^0.17", 502 | "_view_module_version": "^0.17", 503 | "bottom": 25573, 504 | "center": [ 505 | 37.5924, 506 | -122.09 507 | ], 508 | "controls": [ 509 | "IPY_MODEL_9abf5ae099f048f5beaeeba9754b737e", 510 | "IPY_MODEL_18534294b7404458989fce691d6dd928", 511 | "IPY_MODEL_c2ab078daf744726a06e51b73d99e2ee" 512 | ], 513 | "default_style": "IPY_MODEL_6339a07fac08447aa064cf7417a55f6e", 514 | "dragging_style": "IPY_MODEL_682f82c511af4356a71ab51f4288b6e9", 515 | "east": -119.64660644531251, 516 | "fullscreen": false, 517 | "interpolation": "bilinear", 518 | "layers": [ 519 | "IPY_MODEL_775711c799aa4a1e83a8871807e4e87a", 520 | "IPY_MODEL_b2350e4a5d2b4f199144a098c8452edf" 521 | ], 522 | "layout": "IPY_MODEL_336ea372a26f40318a5efb5d3594ffbd", 523 | "left": 10097, 524 | "modisdate": "2023-01-10", 525 | "north": 38.45789034424927, 526 | "options": [ 527 | "bounce_at_zoom_limits", 528 | "box_zoom", 529 | "center", 530 | "close_popup_on_click", 531 | "double_click_zoom", 532 | "dragging", 533 | "fullscreen", 534 | "inertia", 535 | "inertia_deceleration", 536 | "inertia_max_speed", 537 | "interpolation", 538 | "keyboard", 539 | "keyboard_pan_offset", 540 | "keyboard_zoom_offset", 541 | "max_zoom", 542 | "min_zoom", 543 | "prefer_canvas", 544 | "scroll_wheel_zoom", 545 | "tap", 546 | "tap_tolerance", 547 | "touch_zoom", 548 | "world_copy_jump", 549 | "zoom", 550 | "zoom_animation_threshold", 551 | "zoom_delta", 552 | "zoom_snap" 553 | ], 554 | "prefer_canvas": false, 555 | "right": 10987, 556 | "south": 36.71687068791304, 557 | "style": "IPY_MODEL_bf58ec5d94f4449d9e9aea3c82654540", 558 | "top": 25173, 559 | "west": -124.53552246093751, 560 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/index.ipynb", 561 | "zoom": 8 562 | } 563 | }, 564 | "9abf5ae099f048f5beaeeba9754b737e": { 565 | "model_module": "jupyter-leaflet", 566 | "model_module_version": "^0.17", 567 | "model_name": "LeafletZoomControlModel", 568 | "state": { 569 | "_model_module_version": "^0.17", 570 | "_view_module_version": "^0.17", 571 | "options": [ 572 | "position", 573 | "zoom_in_text", 574 | "zoom_in_title", 575 | "zoom_out_text", 576 | "zoom_out_title" 577 | ] 578 | } 579 | }, 580 | "a0f91845ebba48dbbf3f6bdd79323d6d": { 581 | "model_module": "jupyter-leaflet", 582 | "model_module_version": "^0.17", 583 | "model_name": "LeafletAttributionControlModel", 584 | "state": { 585 | "_model_module_version": "^0.17", 586 | "_view_module_version": "^0.17", 587 | "options": [ 588 | "position", 589 | "prefix" 590 | ], 591 | "position": "bottomright", 592 | "prefix": "ipyleaflet" 593 | } 594 | }, 595 | "b2350e4a5d2b4f199144a098c8452edf": { 596 | "model_module": "jupyter-leaflet", 597 | "model_module_version": "^0.17", 598 | "model_name": "LeafletTileLayerModel", 599 | "state": { 600 | "_model_module_version": "^0.17", 601 | "_view_module_version": "^0.17", 602 | "name": "Landsat scene", 603 | "options": [ 604 | "attribution", 605 | "bounds", 606 | "detect_retina", 607 | "max_native_zoom", 608 | "max_zoom", 609 | "min_native_zoom", 610 | "min_zoom", 611 | "no_wrap", 612 | "tile_size", 613 | "tms", 614 | "zoom_offset" 615 | ], 616 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/6937350118cc01964c2600788ca9670e-11210a861d8ab34a09eab48e6043fc82/tiles/{z}/{x}/{y}" 617 | } 618 | }, 619 | "b305b8311ddd4319920041f8c51cdc69": { 620 | "model_module": "jupyter-leaflet", 621 | "model_module_version": "^0.17", 622 | "model_name": "LeafletMapModel", 623 | "state": { 624 | "_model_module_version": "^0.17", 625 | "_view_module_version": "^0.17", 626 | "bottom": 22595, 627 | "center": [ 628 | 49.4, 629 | 2.3 630 | ], 631 | "controls": [ 632 | "IPY_MODEL_2625695324c1472cbab709507ae2db40", 633 | "IPY_MODEL_a0f91845ebba48dbbf3f6bdd79323d6d", 634 | "IPY_MODEL_8e5a271e635948deb4e3362a6b1b91b2", 635 | "IPY_MODEL_1e1aec83e42045bf9ccff2e7ea1f3552" 636 | ], 637 | "default_style": "IPY_MODEL_29efe12ec397463da27a60cd3b558f3f", 638 | "dragging_style": "IPY_MODEL_5d27cf14d73a41aeab2ea720a257ca70", 639 | "east": 4.735107421875001, 640 | "fullscreen": false, 641 | "interpolation": "bilinear", 642 | "layers": [ 643 | "IPY_MODEL_e60c35ff7a49458ca1dc33059ad86f72", 644 | "IPY_MODEL_51d3cd5c24bb40f48c1508c09a932f11" 645 | ], 646 | "layout": "IPY_MODEL_3c36a4f289d04fb2886e978be5b06a17", 647 | "left": 32744, 648 | "modisdate": "2023-01-10", 649 | "north": 50.11001070896015, 650 | "options": [ 651 | "bounce_at_zoom_limits", 652 | "box_zoom", 653 | "center", 654 | "close_popup_on_click", 655 | "double_click_zoom", 656 | "dragging", 657 | "fullscreen", 658 | "inertia", 659 | "inertia_deceleration", 660 | "inertia_max_speed", 661 | "interpolation", 662 | "keyboard", 663 | "keyboard_pan_offset", 664 | "keyboard_zoom_offset", 665 | "max_zoom", 666 | "min_zoom", 667 | "prefer_canvas", 668 | "scroll_wheel_zoom", 669 | "tap", 670 | "tap_tolerance", 671 | "touch_zoom", 672 | "world_copy_jump", 673 | "zoom", 674 | "zoom_animation_threshold", 675 | "zoom_delta", 676 | "zoom_snap" 677 | ], 678 | "prefer_canvas": false, 679 | "right": 33630, 680 | "south": 48.680080770292875, 681 | "style": "IPY_MODEL_f110c4c3bbe345469b89eebd1016fef0", 682 | "top": 22195, 683 | "west": -0.13183593750000003, 684 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/index.ipynb", 685 | "zoom": 8 686 | } 687 | }, 688 | "bf58ec5d94f4449d9e9aea3c82654540": { 689 | "model_module": "jupyter-leaflet", 690 | "model_module_version": "^0.17", 691 | "model_name": "LeafletMapStyleModel", 692 | "state": { 693 | "_model_module_version": "^0.17" 694 | } 695 | }, 696 | "c0e0a057b273444c867bb91d3d2b8166": { 697 | "model_module": "@jupyter-widgets/controls", 698 | "model_module_version": "2.0.0", 699 | "model_name": "ButtonStyleModel", 700 | "state": { 701 | "font_family": null, 702 | "font_size": null, 703 | "font_style": null, 704 | "font_variant": null, 705 | "font_weight": null, 706 | "text_color": null, 707 | "text_decoration": null 708 | } 709 | }, 710 | "c2ab078daf744726a06e51b73d99e2ee": { 711 | "model_module": "jupyter-leaflet", 712 | "model_module_version": "^0.17", 713 | "model_name": "LeafletLayersControlModel", 714 | "state": { 715 | "_model_module_version": "^0.17", 716 | "_view_module_version": "^0.17", 717 | "options": [ 718 | "position" 719 | ], 720 | "position": "topright" 721 | } 722 | }, 723 | "c53792be7e3c4833b3dc6315bbfba4b4": { 724 | "model_module": "ipytree", 725 | "model_module_version": "^0.2", 726 | "model_name": "TreeModel", 727 | "state": { 728 | "_model_module_version": "^0.2", 729 | "_view_module_version": "^0.2", 730 | "layout": "IPY_MODEL_4f8b472c81eb412f9f90be778d5b046f", 731 | "nodes": [ 732 | "IPY_MODEL_2319a7c35eb146fab3231d5b3c2d223f" 733 | ] 734 | } 735 | }, 736 | "e60c35ff7a49458ca1dc33059ad86f72": { 737 | "model_module": "jupyter-leaflet", 738 | "model_module_version": "^0.17", 739 | "model_name": "LeafletTileLayerModel", 740 | "state": { 741 | "_model_module_version": "^0.17", 742 | "_view_module_version": "^0.17", 743 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 744 | "base": true, 745 | "max_zoom": 16, 746 | "min_zoom": 1, 747 | "name": "Stamen.Watercolor", 748 | "options": [ 749 | "attribution", 750 | "bounds", 751 | "detect_retina", 752 | "max_native_zoom", 753 | "max_zoom", 754 | "min_native_zoom", 755 | "min_zoom", 756 | "no_wrap", 757 | "tile_size", 758 | "tms", 759 | "zoom_offset" 760 | ], 761 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 762 | } 763 | }, 764 | "e63129a6b6214b86b55161f9b3b01707": { 765 | "model_module": "@jupyter-widgets/controls", 766 | "model_module_version": "2.0.0", 767 | "model_name": "ButtonModel", 768 | "state": { 769 | "icon": "info-circle 2x fw inverse", 770 | "layout": "IPY_MODEL_505a42fde6424bdfb971ae73d8a26c74", 771 | "style": "IPY_MODEL_c0e0a057b273444c867bb91d3d2b8166", 772 | "tooltip": null 773 | } 774 | }, 775 | "f110c4c3bbe345469b89eebd1016fef0": { 776 | "model_module": "jupyter-leaflet", 777 | "model_module_version": "^0.17", 778 | "model_name": "LeafletMapStyleModel", 779 | "state": { 780 | "_model_module_version": "^0.17" 781 | } 782 | } 783 | }, 784 | "version_major": 2, 785 | "version_minor": 0 786 | } 787 | } 788 | }, 789 | "nbformat": 4, 790 | "nbformat_minor": 4 791 | } 792 | -------------------------------------------------------------------------------- /nbs/01_ipyleaflet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "a299e0c1-82bf-4290-aab9-38397b05b533", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "#|hide\n", 11 | "# Copyright 2022 Google LLC\n", 12 | "#\n", 13 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 14 | "# you may not use this file except in compliance with the License.\n", 15 | "# You may obtain a copy of the License at\n", 16 | "# \n", 17 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 18 | "# \n", 19 | "# Unless required by applicable law or agreed to in writing, software\n", 20 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 21 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 22 | "# See the License for the specific language governing permissions and\n", 23 | "# limitations under the License." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "93bd9280-7fdc-47b3-9ab6-e5892cbc8a4b", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| default_exp ipyleaflet" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "00063c01-2b45-4a56-b50a-aa20d10f4803", 39 | "metadata": {}, 40 | "source": [ 41 | "# ipyleaflet module\n", 42 | "\n", 43 | "> Extends ipyleaflet objects to work with Earth Engine generated data." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "3d11593e-2715-4345-8831-2b1ceccac939", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#|hide\n", 54 | "from nbdev.showdoc import *" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "20e9ed75-20a9-43a8-9ae8-9232ed911f9d", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "#|export\n", 65 | "import ee\n", 66 | "import json\n", 67 | "import ipyleaflet\n", 68 | "import ipytree\n", 69 | "import ipywidgets as widgets\n", 70 | "import logging\n", 71 | "import traitlets" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "id": "c7082e8a-0627-4f05-836c-84cb1ad63fe5", 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "#|export\n", 82 | "logger = logging.getLogger()\n", 83 | "logger.setLevel(logging.WARNING)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "id": "0a1fbd0b-4b5d-493b-bda2-9aa0603da7d1", 89 | "metadata": {}, 90 | "source": [ 91 | "## Map" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "c34615c6-17a5-476e-89ff-9b195f8564ed", 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "#|export\n", 102 | "DEFAULT_MAP_HEIGHT = '400px'\n", 103 | "\n", 104 | "class TileLayerEE(ipyleaflet.TileLayer):\n", 105 | " \"\"\"Class for a tile layer generated by Earth Engine.\n", 106 | " \n", 107 | " Attributes:\n", 108 | " ee_object: An Earth Engine object.\n", 109 | " \"\"\"\n", 110 | " def __init__(self,\n", 111 | " ee_object:ee.Image, # An Earth Engine Image object\n", 112 | " *args,\n", 113 | " **kwargs):\n", 114 | "\n", 115 | " self.ee_object = ee_object\n", 116 | "\n", 117 | " super(TileLayerEE, self).__init__(*args, **kwargs)\n", 118 | "\n", 119 | "\n", 120 | "class Map(ipyleaflet.Map):\n", 121 | " \"\"\"An interactive map class for Jupyter clients.\n", 122 | " \n", 123 | " Attributes:\n", 124 | " layers_control: a boolean indicating whether to display a layers control.\n", 125 | " \"\"\"\n", 126 | "\n", 127 | " layers_control = traitlets.Bool(True)\n", 128 | "\n", 129 | " def __init__(self, *args, **kwargs):\n", 130 | "\n", 131 | " self.layers_control_instance = None\n", 132 | "\n", 133 | " # Set default values for the map.\n", 134 | " if 'zoom' not in kwargs:\n", 135 | " kwargs['zoom'] = 4\n", 136 | " \n", 137 | " if 'basemap' not in kwargs:\n", 138 | " kwargs['basemap'] = ipyleaflet.basemap_to_tiles(ipyleaflet.basemaps.Stamen.Watercolor)\n", 139 | "\n", 140 | " if 'height' not in kwargs:\n", 141 | " kwargs['height'] = DEFAULT_MAP_HEIGHT\n", 142 | " \n", 143 | " super(Map, self).__init__(*args, **kwargs)\n", 144 | " \n", 145 | " if self.layers_control:\n", 146 | " self.layers_control_instance = ipyleaflet.LayersControl(position='topright')\n", 147 | " self.add_control(self.layers_control_instance)\n", 148 | " \n", 149 | " self.default_style.cursor = 'crosshair'\n", 150 | " \n", 151 | " self.layout.width = '100%'\n", 152 | " \n", 153 | " \n", 154 | " def addLayer(self, eeObject, visParams={}, name=None, shown=True, opacity=1):\n", 155 | " \"\"\"Adds a layer for an Earth Engine object.\"\"\"\n", 156 | " \n", 157 | " if name is None:\n", 158 | " # Provide a default name for the layer in the form \"Layer ##\"\n", 159 | " name = f'Layer {len(self.layers)}'\n", 160 | "\n", 161 | " def get_tile_layer_url(ee_image_object):\n", 162 | " map_id_dict = ee.Image(ee_image_object).getMapId()\n", 163 | " return map_id_dict['tile_fetcher'].url_format\n", 164 | "\n", 165 | " # Assume that the eeObject is an ee.Image.\n", 166 | " # TODO: Generalize this to other EE objects.\n", 167 | " ee_image = eeObject\n", 168 | "\n", 169 | " tile_url = get_tile_layer_url(\n", 170 | " ee_image.visualize(**visParams)\n", 171 | " )\n", 172 | " self.add_layer(TileLayerEE(ee_object=eeObject, url=tile_url, name=name, visible=shown))" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "4483948b-28c0-4869-bab6-2e2aad9b1db4", 178 | "metadata": {}, 179 | "source": [ 180 | "Display the default interactive map." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "4e7223f7-4446-4ce2-9ffe-cd255617ff93", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "map = Map()\n", 191 | "map" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "id": "e9efdbb6-6bc8-462b-87e0-6f370c8e198e", 197 | "metadata": {}, 198 | "source": [ 199 | "Display an Earth Engine raster layer." 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "id": "5cea2a36-e760-4d6f-948d-fedbafcf4bf0", 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "ee.Initialize()" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "id": "f99b3d91-8e37-4d36-8c6d-8bbc3d43c91a", 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "dem = ee.Image('USGS/SRTMGL1_003')\n", 220 | "sample_image = dem.updateMask(dem.gt(0))\n", 221 | "\n", 222 | "map2 = Map()\n", 223 | "map2.addLayer(sample_image, {'min':0, 'max':4000}, name=\"My layer\", shown=True)\n", 224 | "map2" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "83fb71ea-2e64-4506-8699-b1a0a75bfe8f", 230 | "metadata": {}, 231 | "source": [ 232 | "## Map Inspector" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "id": "d8cc2f1d-0df7-4ff0-b54d-b37790593f27", 238 | "metadata": {}, 239 | "source": [ 240 | "The following defines a `StructureTree` class, which builds a tree structure corresponding to data structure provided as input. The tree branches are lazily populated as the tree is expanded, so that large data structures can be efficiently rendered." 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "id": "32ba4716-701d-4a0b-9f5c-16e79b0db054", 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "#|export\n", 251 | "class StructureTree(ipytree.Tree):\n", 252 | "\n", 253 | " JSON_PREFIX = 'JSON: '\n", 254 | " \n", 255 | " def __init__(self, data, **kwargs):\n", 256 | " self.data = data\n", 257 | " super().__init__(StructureTree.__ipytreeify(data))\n", 258 | " \n", 259 | " @staticmethod\n", 260 | " def __ipytreeify(data) -> tuple:\n", 261 | " \"\"\"Return a sequence of nodes\"\"\"\n", 262 | "\n", 263 | " def is_json(in_str):\n", 264 | " '''Determines if a string is JSON.'''\n", 265 | " return in_str.startswith(StructureTree.JSON_PREFIX)\n", 266 | "\n", 267 | " def inline_style(x, color='blue'):\n", 268 | " '''Wrap a string with inline HTML styling.'''\n", 269 | " return f'{x}'\n", 270 | " \n", 271 | " def handle_node_open(change):\n", 272 | " if change['new']:\n", 273 | " nodes_unpacked = []\n", 274 | " for node in change['owner'].nodes:\n", 275 | " # If there no subnodes, try to populate the subnodes.\n", 276 | " if len(node.nodes) == 0:\n", 277 | " if is_json(node.name):\n", 278 | " unpacked_json = json.loads(node.name[len(StructureTree.JSON_PREFIX):])\n", 279 | " if isinstance(unpacked_json, list):\n", 280 | " nodes_unpacked = StructureTree.__ipytreeify(unpacked_json)\n", 281 | " elif isinstance(unpacked_json, dict):\n", 282 | " nodes_unpacked = StructureTree.__ipytreeify(unpacked_json)\n", 283 | " else:\n", 284 | " raise\n", 285 | " else: # ~is_json(node.name)\n", 286 | " nodes_unpacked.append(node)\n", 287 | " change['owner'].nodes = nodes_unpacked \n", 288 | " \n", 289 | " if isinstance(data, list):\n", 290 | " node_list = []\n", 291 | " for count, el in enumerate(data):\n", 292 | " if isinstance(el, list):\n", 293 | " subnode = ipytree.Node(\n", 294 | " name=f'{inline_style(\"List\")} ({len(el)} elements)',\n", 295 | " nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(el)}')],\n", 296 | " opened=False)\n", 297 | " subnode.observe(handle_node_open, names='opened')\n", 298 | " elif isinstance(el, dict):\n", 299 | " subnode = ipytree.Node(\n", 300 | " name=f'{inline_style(\"Object\")} ({len(el)} elements)',\n", 301 | " nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(el)}')],\n", 302 | " opened=False)\n", 303 | " subnode.observe(handle_node_open, names='opened') \n", 304 | " else:\n", 305 | " subnode = ipytree.Node(f'{el}')\n", 306 | " node_list.append(subnode)\n", 307 | " return node_list\n", 308 | " elif isinstance(data, dict):\n", 309 | " node_list = []\n", 310 | " for key, value in data.items():\n", 311 | " if isinstance(value, list):\n", 312 | " subnode = ipytree.Node(\n", 313 | " name=f'{inline_style(key)}: List ({len(value)} elements)',\n", 314 | " nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(value)}')],\n", 315 | " opened=False)\n", 316 | " subnode.observe(handle_node_open, names='opened') \n", 317 | " elif isinstance(value, dict):\n", 318 | " subnode = ipytree.Node(\n", 319 | " name=f'{inline_style(key)}: Object ({len(value)} elements)',\n", 320 | " nodes=[ipytree.Node(f'{StructureTree.JSON_PREFIX}{json.dumps(value)}')],\n", 321 | " opened=False)\n", 322 | " subnode.observe(handle_node_open, names='opened') \n", 323 | " else:\n", 324 | " subnode = ipytree.Node(f'{inline_style(key)}: {value}')\n", 325 | " node_list.append(subnode)\n", 326 | " return node_list\n", 327 | " else:\n", 328 | " return (data, )\n" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "id": "04e768bf-c32d-4fdf-9077-fd9cd1add7aa", 334 | "metadata": {}, 335 | "source": [ 336 | "Here is a `StructureTree` object that demonstrates how lists, dictionaries, and nested objects are rendered:" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": null, 342 | "id": "09c534ab-3d4d-4811-8427-3955954335d1", 343 | "metadata": {}, 344 | "outputs": [], 345 | "source": [ 346 | "st = StructureTree({'none': None,\n", 347 | " 'number': 1,\n", 348 | " 'string': 'hello!',\n", 349 | " 'list': [1, 'item1'], \n", 350 | " 'dictionary': {'a key': 'a value'},\n", 351 | " 'nesting': [\n", 352 | " ['nested', 'list'],\n", 353 | " {'a nested': 'dictionary'}\n", 354 | " ]})\n", 355 | "st" 356 | ] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "id": "e8be360e-8a7c-4485-9e62-172b27df8979", 361 | "metadata": {}, 362 | "source": [ 363 | "The following defines an `Inspector` class, which is used to display information obtained from querying a `Map` object." 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": null, 369 | "id": "14151217-fb5b-448c-826c-1a23e468361d", 370 | "metadata": {}, 371 | "outputs": [], 372 | "source": [ 373 | "#|export\n", 374 | "\n", 375 | "# Map scale at Level 0 in meters/pixel\n", 376 | "SCALE_LEVEL_0 = 156543.03392\n", 377 | "\n", 378 | "#class Inspector(ipytree.Tree):\n", 379 | "class Inspector(StructureTree): \n", 380 | " \"\"\"Class representing an inspector tool that responds to map events.\"\"\"\n", 381 | " \n", 382 | " def __init__(self,\n", 383 | " map_object=None, # An Earth Engine Image object\n", 384 | " *args,\n", 385 | " **kwargs):\n", 386 | " \n", 387 | " self.point_data = None\n", 388 | " # self.tree_data = None\n", 389 | " self.objects_data = {}\n", 390 | " self.pixels_data = {}\n", 391 | " \n", 392 | " self._disabled = False\n", 393 | " \n", 394 | " self.map_object = map_object\n", 395 | " self.layout.width = '100%'\n", 396 | " self.layout.max_height = '400px'\n", 397 | " self.layout.overflow = 'scroll'\n", 398 | "\n", 399 | " super(Inspector, self).__init__(\n", 400 | " data=['Click on the map to inspect the layers.'],\n", 401 | " *args, \n", 402 | " **kwargs\n", 403 | " )\n", 404 | "\n", 405 | " if map_object:\n", 406 | " self.set_map(map_object)\n", 407 | " \n", 408 | " # self.update_inspector()\n", 409 | " \n", 410 | " @property\n", 411 | " def point_node(self):\n", 412 | " return self.nodes[0]\n", 413 | " \n", 414 | " @point_node.setter\n", 415 | " def point_node(self, new_point_node):\n", 416 | " #(lat, lon) = new_coords\n", 417 | " _temp_nodes = list(self.nodes)\n", 418 | " _temp_nodes[0] = new_point_node\n", 419 | " self.nodes = _temp_nodes\n", 420 | " \n", 421 | " @property\n", 422 | " def pixels_node(self):\n", 423 | " return self.nodes[1]\n", 424 | " \n", 425 | " @property\n", 426 | " def objects_node(self):\n", 427 | " return self.nodes[2]\n", 428 | " \n", 429 | " @property\n", 430 | " def disabled(self):\n", 431 | " return self._disabled\n", 432 | " \n", 433 | " @disabled.setter\n", 434 | " def disabled(self, state):\n", 435 | " self._disabled = state\n", 436 | " \n", 437 | " def update_inspector(self, coords=None):\n", 438 | " \"\"\"Update information in the inspector tree.\"\"\"\n", 439 | " \n", 440 | " def _order_items(item_dict, ordering_list):\n", 441 | " \"\"\"Orders dictionary items in a specified order.\"\"\"\n", 442 | " list_of_tuples = [(key, item_dict[key]) for key in [x for x in ordering_list if x in item_dict.keys()]]\n", 443 | " return dict(list_of_tuples) \n", 444 | " \n", 445 | " if self.map_object:\n", 446 | " if not self.disabled and coords:\n", 447 | " \n", 448 | " self.nodes = [ipytree.Node('Loading...')]\n", 449 | " \n", 450 | " (lat, lon) = coords\n", 451 | "\n", 452 | " scale_approx = SCALE_LEVEL_0 / 2**self.map_object.zoom\n", 453 | " self.point_data = {\n", 454 | " 'Longitude': f'{lon:.6f}',\n", 455 | " 'Latitude': f'{lat:.6f}',\n", 456 | " 'Zoom Level': f'{self.map_object.zoom:.0f}',\n", 457 | " 'Scale (approx. m/px)': f'{scale_approx:.2f}'\n", 458 | " }\n", 459 | " \n", 460 | " # Update the Pixels folder\n", 461 | " for layer in self.map_object.layers:\n", 462 | " if not layer.base:\n", 463 | " ee_type = ee.Algorithms.ObjectType(layer.ee_object).getInfo()\n", 464 | "\n", 465 | " if ee_type == 'Image':\n", 466 | " value_dict = layer.ee_object.reduceRegion(\n", 467 | " reducer=ee.Reducer.mean(),\n", 468 | " geometry=ee.Geometry.Point(lon, lat),\n", 469 | " scale=scale_approx,\n", 470 | " bestEffort=True\n", 471 | " ).getInfo()\n", 472 | " num_bands = len(value_dict.keys())\n", 473 | " \n", 474 | " band_dict = {}\n", 475 | " has_unmasked_pixel = False\n", 476 | " for bandname in layer.ee_object.bandNames().getInfo(): \n", 477 | " if value_dict[bandname] is not None:\n", 478 | " has_unmasked_pixel = True\n", 479 | " band_dict.update({f'{bandname}': f'{value_dict[bandname]:.4f}'})\n", 480 | " else: \n", 481 | " band_dict.update({f'{bandname}': f'masked'})\n", 482 | " \n", 483 | " # if not has_unmasked_pixel:\n", 484 | " # layer_node.nodes = [\n", 485 | " # ipytree.Node(f'No unmasked pixels at clicked point.'),\n", 486 | " # ] \n", 487 | " # pixel_nodes.append(layer_node) \n", 488 | " self.pixels_data.update({f'{layer.name}': band_dict})\n", 489 | " \n", 490 | " # Update the Objects folder\n", 491 | " for layer in self.map_object.layers:\n", 492 | " if not layer.base:\n", 493 | " ee_type = ee.Algorithms.ObjectType(layer.ee_object).getInfo() \n", 494 | " layer_info = layer.ee_object.getInfo()\n", 495 | "\n", 496 | " # Order the layer information.\n", 497 | " ordering_list = ['type', 'id', 'version', 'bands', 'properties']\n", 498 | " layer_info = _order_items(layer_info, ordering_list)\n", 499 | " layer_dict = {f'{layer.name}': layer_info}\n", 500 | " \n", 501 | " self.objects_data.update(layer_dict)\n", 502 | " \n", 503 | " self.nodes = [\n", 504 | " ipytree.Node('Point', nodes=StructureTree(self.point_data).nodes),\n", 505 | " ipytree.Node('Pixels', nodes=StructureTree(self.pixels_data).nodes, opened=False),\n", 506 | " ipytree.Node('Objects', nodes=StructureTree(self.objects_data).nodes, opened=False),\n", 507 | " ] \n", 508 | " \n", 509 | " def register_map(self, map_object):\n", 510 | " def handle_interaction(type, event, coordinates):\n", 511 | " if type == 'click':\n", 512 | " self.update_inspector(coordinates)\n", 513 | " map_object.on_interaction(handle_interaction)\n", 514 | " \n", 515 | " def set_map(self, map_object):\n", 516 | " self.map_object = map_object\n", 517 | " self.register_map(map_object)\n", 518 | "\n", 519 | " def get_map(self):\n", 520 | " return self.map_object" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": null, 526 | "id": "06a2a1e3-a6f4-445a-bfac-dbe84119d277", 527 | "metadata": {}, 528 | "outputs": [], 529 | "source": [ 530 | "# Testing it out...\n", 531 | "map1 = Map()\n", 532 | "map1.addLayer(ee.Image.pixelLonLat(), {'min':-90, 'max':90, 'opacity':0.5}, 'pixelLatLon')\n", 533 | "# map1.addLayer(ee.Image(\"CGIAR/SRTM90_V4\"), {'opacity':0.5}, 'SRTM')\n", 534 | "inspector1 = Inspector(map_object = map1)\n", 535 | "\n", 536 | "zoom_slider = widgets.IntSlider(description='Zoom level:', min=0, max=15, value=7)\n", 537 | "widget_control1 = ipyleaflet.WidgetControl(widget=inspector1,\n", 538 | " position='bottomright',\n", 539 | " layout=widgets.Layout(overflow='scroll'))\n", 540 | "map1.add_control(widget_control1)\n", 541 | "map1" 542 | ] 543 | }, 544 | { 545 | "cell_type": "markdown", 546 | "id": "cda35d5d-e749-49b2-8e05-655d1e0e28eb", 547 | "metadata": {}, 548 | "source": [ 549 | "## Examples" 550 | ] 551 | }, 552 | { 553 | "cell_type": "markdown", 554 | "id": "37a58bbd-fe2d-4561-a36b-0ff77d6bc60e", 555 | "metadata": {}, 556 | "source": [ 557 | "### Inspector object only" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": null, 563 | "id": "f52e5a58-4855-4708-ab51-479ffc5e757b", 564 | "metadata": {}, 565 | "outputs": [], 566 | "source": [ 567 | "inspector1 = Inspector()\n", 568 | "inspector1" 569 | ] 570 | }, 571 | { 572 | "cell_type": "markdown", 573 | "id": "d69651a9-cb29-4989-b51b-95078d584c68", 574 | "metadata": {}, 575 | "source": [ 576 | "### Inspector with a Map" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": null, 582 | "id": "a544bd7f-489e-4a15-989f-7fb4eb77ed8d", 583 | "metadata": {}, 584 | "outputs": [], 585 | "source": [ 586 | "ee.Initialize()" 587 | ] 588 | }, 589 | { 590 | "cell_type": "code", 591 | "execution_count": null, 592 | "id": "d8b89564-1107-4203-83cf-1109dc4da34a", 593 | "metadata": {}, 594 | "outputs": [], 595 | "source": [ 596 | "map = Map()\n", 597 | "inspector2 = Inspector(map_object=map)\n", 598 | "display(\n", 599 | " widgets.HBox([\n", 600 | " map,\n", 601 | " inspector2\n", 602 | " ],\n", 603 | " layout=widgets.Layout(border='1px solid black')))\n", 604 | "\n", 605 | "map.addLayer(ee.Image.pixelLonLat(), {'min':-90, 'max':90, 'opacity':0.5}, 'LonLat')\n", 606 | "map.addLayer(\n", 607 | " ee.Image('LANDSAT/LC09/C02/T1_L2/LC09_187058_20220105'),\n", 608 | " {'min':0, 'max':90, 'opacity':0.5},\n", 609 | " 'Landsat')" 610 | ] 611 | }, 612 | { 613 | "cell_type": "code", 614 | "execution_count": null, 615 | "id": "2b56d9f5-ad90-4ecd-a122-652abed3c501", 616 | "metadata": {}, 617 | "outputs": [], 618 | "source": [ 619 | "#| hide\n", 620 | "import nbdev; nbdev.nbdev_export()" 621 | ] 622 | } 623 | ], 624 | "metadata": { 625 | "kernelspec": { 626 | "display_name": "Python 3 (ipykernel)", 627 | "language": "python", 628 | "name": "python3" 629 | }, 630 | "widgets": { 631 | "application/vnd.jupyter.widget-state+json": { 632 | "state": { 633 | "07ae7d0268784ae1a137cb50a08767a3": { 634 | "model_module": "ipytree", 635 | "model_module_version": "^0.2", 636 | "model_name": "NodeModel", 637 | "state": { 638 | "_id": "4b83bc8a-326a-4de3-bba7-5588e394c1d4", 639 | "_model_module_version": "^0.2", 640 | "_view_module_version": "^0.2", 641 | "name": "number: 1" 642 | } 643 | }, 644 | "0a0f61d71c324cce8a8724a97449ba20": { 645 | "model_module": "@jupyter-widgets/base", 646 | "model_module_version": "2.0.0", 647 | "model_name": "LayoutModel", 648 | "state": {} 649 | }, 650 | "0dcf84bfbf3c42e9b71bdda3ad4f67f2": { 651 | "model_module": "jupyter-leaflet", 652 | "model_module_version": "^0.17", 653 | "model_name": "LeafletTileLayerModel", 654 | "state": { 655 | "_model_module_version": "^0.17", 656 | "_view_module_version": "^0.17", 657 | "name": "pixelLatLon", 658 | "options": [ 659 | "attribution", 660 | "bounds", 661 | "detect_retina", 662 | "max_native_zoom", 663 | "max_zoom", 664 | "min_native_zoom", 665 | "min_zoom", 666 | "no_wrap", 667 | "tile_size", 668 | "tms", 669 | "zoom_offset" 670 | ], 671 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/94684f9069ec559fbd6d13a6e0b6ee1c-a6fd349ff172d45b9398f3efbb324a0a/tiles/{z}/{x}/{y}" 672 | } 673 | }, 674 | "1488a7962d1b498785e5d342e1f9e145": { 675 | "model_module": "jupyter-leaflet", 676 | "model_module_version": "^0.17", 677 | "model_name": "LeafletAttributionControlModel", 678 | "state": { 679 | "_model_module_version": "^0.17", 680 | "_view_module_version": "^0.17", 681 | "options": [ 682 | "position", 683 | "prefix" 684 | ], 685 | "position": "bottomright", 686 | "prefix": "ipyleaflet" 687 | } 688 | }, 689 | "160f1f17eda549e794967ad36033535f": { 690 | "model_module": "jupyter-leaflet", 691 | "model_module_version": "^0.17", 692 | "model_name": "LeafletLayersControlModel", 693 | "state": { 694 | "_model_module_version": "^0.17", 695 | "_view_module_version": "^0.17", 696 | "options": [ 697 | "position" 698 | ], 699 | "position": "topright" 700 | } 701 | }, 702 | "198b0f2603dd44619a55e5835c97b8de": { 703 | "model_module": "@jupyter-widgets/controls", 704 | "model_module_version": "2.0.0", 705 | "model_name": "HBoxModel", 706 | "state": { 707 | "children": [ 708 | "IPY_MODEL_f335e46a0a3a4446873a52efb6bd3d99", 709 | "IPY_MODEL_9f173486cd994f3ab7b1d4451a0a9ce3" 710 | ], 711 | "layout": "IPY_MODEL_782599a241fb435abac9527c114b1f98" 712 | } 713 | }, 714 | "19e735ec76674632b8e00ded8400cdc7": { 715 | "model_module": "jupyter-leaflet", 716 | "model_module_version": "^0.17", 717 | "model_name": "LeafletLayersControlModel", 718 | "state": { 719 | "_model_module_version": "^0.17", 720 | "_view_module_version": "^0.17", 721 | "options": [ 722 | "position" 723 | ], 724 | "position": "topright" 725 | } 726 | }, 727 | "2230df77a880469cb030cb1cdd046a6b": { 728 | "model_module": "jupyter-leaflet", 729 | "model_module_version": "^0.17", 730 | "model_name": "LeafletZoomControlModel", 731 | "state": { 732 | "_model_module_version": "^0.17", 733 | "_view_module_version": "^0.17", 734 | "options": [ 735 | "position", 736 | "zoom_in_text", 737 | "zoom_in_title", 738 | "zoom_out_text", 739 | "zoom_out_title" 740 | ] 741 | } 742 | }, 743 | "2844db030e5d4cf48dc4184d9d199e01": { 744 | "model_module": "jupyter-leaflet", 745 | "model_module_version": "^0.17", 746 | "model_name": "LeafletMapStyleModel", 747 | "state": { 748 | "_model_module_version": "^0.17" 749 | } 750 | }, 751 | "2d81e9ccd8c14032b275563f33f194e2": { 752 | "model_module": "jupyter-leaflet", 753 | "model_module_version": "^0.17", 754 | "model_name": "LeafletAttributionControlModel", 755 | "state": { 756 | "_model_module_version": "^0.17", 757 | "_view_module_version": "^0.17", 758 | "options": [ 759 | "position", 760 | "prefix" 761 | ], 762 | "position": "bottomright", 763 | "prefix": "ipyleaflet" 764 | } 765 | }, 766 | "324543c15533487ba043c58c913c23cf": { 767 | "model_module": "jupyter-leaflet", 768 | "model_module_version": "^0.17", 769 | "model_name": "LeafletZoomControlModel", 770 | "state": { 771 | "_model_module_version": "^0.17", 772 | "_view_module_version": "^0.17", 773 | "options": [ 774 | "position", 775 | "zoom_in_text", 776 | "zoom_in_title", 777 | "zoom_out_text", 778 | "zoom_out_title" 779 | ] 780 | } 781 | }, 782 | "33a61cf764cb4927920c8222e90ccb60": { 783 | "model_module": "jupyter-leaflet", 784 | "model_module_version": "^0.17", 785 | "model_name": "LeafletTileLayerModel", 786 | "state": { 787 | "_model_module_version": "^0.17", 788 | "_view_module_version": "^0.17", 789 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 790 | "base": true, 791 | "max_zoom": 16, 792 | "min_zoom": 1, 793 | "name": "Stamen.Watercolor", 794 | "options": [ 795 | "attribution", 796 | "bounds", 797 | "detect_retina", 798 | "max_native_zoom", 799 | "max_zoom", 800 | "min_native_zoom", 801 | "min_zoom", 802 | "no_wrap", 803 | "tile_size", 804 | "tms", 805 | "zoom_offset" 806 | ], 807 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 808 | } 809 | }, 810 | "34eccbbe46bb4e7582ff83f2167a587d": { 811 | "model_module": "jupyter-leaflet", 812 | "model_module_version": "^0.17", 813 | "model_name": "LeafletAttributionControlModel", 814 | "state": { 815 | "_model_module_version": "^0.17", 816 | "_view_module_version": "^0.17", 817 | "options": [ 818 | "position", 819 | "prefix" 820 | ], 821 | "position": "bottomright", 822 | "prefix": "ipyleaflet" 823 | } 824 | }, 825 | "3b75beaea6204c1ab2773feba0fc4f44": { 826 | "model_module": "jupyter-leaflet", 827 | "model_module_version": "^0.17", 828 | "model_name": "LeafletMapModel", 829 | "state": { 830 | "_model_module_version": "^0.17", 831 | "_view_module_version": "^0.17", 832 | "bottom": 2248, 833 | "controls": [ 834 | "IPY_MODEL_324543c15533487ba043c58c913c23cf", 835 | "IPY_MODEL_1488a7962d1b498785e5d342e1f9e145", 836 | "IPY_MODEL_19e735ec76674632b8e00ded8400cdc7", 837 | "IPY_MODEL_3f213aba22b14136a5b821ca7259d3fc" 838 | ], 839 | "default_style": "IPY_MODEL_91f2a10b7d3b448988309cca16a310d7", 840 | "dragging_style": "IPY_MODEL_d0d292fd509a42199487515df00981f0", 841 | "east": 83.05664062500001, 842 | "fullscreen": false, 843 | "interpolation": "bilinear", 844 | "layers": [ 845 | "IPY_MODEL_f25225841c5d4e5ca0c9f57ba5f01c4a", 846 | "IPY_MODEL_0dcf84bfbf3c42e9b71bdda3ad4f67f2" 847 | ], 848 | "layout": "IPY_MODEL_54ad4bc247f0492cb40132748c9b3e7d", 849 | "left": 1104, 850 | "modisdate": "2023-01-10", 851 | "north": 17.308687886770034, 852 | "options": [ 853 | "bounce_at_zoom_limits", 854 | "box_zoom", 855 | "center", 856 | "close_popup_on_click", 857 | "double_click_zoom", 858 | "dragging", 859 | "fullscreen", 860 | "inertia", 861 | "inertia_deceleration", 862 | "inertia_max_speed", 863 | "interpolation", 864 | "keyboard", 865 | "keyboard_pan_offset", 866 | "keyboard_zoom_offset", 867 | "max_zoom", 868 | "min_zoom", 869 | "prefer_canvas", 870 | "scroll_wheel_zoom", 871 | "tap", 872 | "tap_tolerance", 873 | "touch_zoom", 874 | "world_copy_jump", 875 | "zoom", 876 | "zoom_animation_threshold", 877 | "zoom_delta", 878 | "zoom_snap" 879 | ], 880 | "prefer_canvas": false, 881 | "right": 2993, 882 | "south": -17.308687886770024, 883 | "style": "IPY_MODEL_aa7270cfa51341e2ba6ac0c04873ca0c", 884 | "top": 1848, 885 | "west": -82.96875000000001, 886 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/01_ipyleaflet.ipynb", 887 | "zoom": 4 888 | } 889 | }, 890 | "3cd16605239f401f80016b0709e90fd0": { 891 | "model_module": "jupyter-leaflet", 892 | "model_module_version": "^0.17", 893 | "model_name": "LeafletMapStyleModel", 894 | "state": { 895 | "_model_module_version": "^0.17", 896 | "cursor": "crosshair" 897 | } 898 | }, 899 | "3f213aba22b14136a5b821ca7259d3fc": { 900 | "model_module": "jupyter-leaflet", 901 | "model_module_version": "^0.17", 902 | "model_name": "LeafletWidgetControlModel", 903 | "state": { 904 | "_model_module": "jupyter-leaflet", 905 | "_model_module_version": "^0.17", 906 | "_view_count": null, 907 | "_view_module": "jupyter-leaflet", 908 | "_view_module_version": "^0.17", 909 | "options": [ 910 | "position", 911 | "transparent_bg" 912 | ], 913 | "position": "bottomright", 914 | "widget": "IPY_MODEL_6044e7e5893e4994a9d1bbdb0d17db25" 915 | } 916 | }, 917 | "46ec4ffb94c54ddf9b7a01b8567ef4e5": { 918 | "model_module": "@jupyter-widgets/base", 919 | "model_module_version": "2.0.0", 920 | "model_name": "LayoutModel", 921 | "state": { 922 | "width": "100%" 923 | } 924 | }, 925 | "47097130e74244829c4536b3a89300b6": { 926 | "model_module": "ipytree", 927 | "model_module_version": "^0.2", 928 | "model_name": "NodeModel", 929 | "state": { 930 | "_id": "f4deb5e0-ea37-4574-94e9-b3143a3e65af", 931 | "_model_module_version": "^0.2", 932 | "_view_module_version": "^0.2", 933 | "name": "none: None" 934 | } 935 | }, 936 | "4e33a3e56de54a788e6516a373dd16cb": { 937 | "model_module": "jupyter-leaflet", 938 | "model_module_version": "^0.17", 939 | "model_name": "LeafletTileLayerModel", 940 | "state": { 941 | "_model_module_version": "^0.17", 942 | "_view_module_version": "^0.17", 943 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 944 | "base": true, 945 | "max_zoom": 16, 946 | "min_zoom": 1, 947 | "name": "Stamen.Watercolor", 948 | "options": [ 949 | "attribution", 950 | "bounds", 951 | "detect_retina", 952 | "max_native_zoom", 953 | "max_zoom", 954 | "min_native_zoom", 955 | "min_zoom", 956 | "no_wrap", 957 | "tile_size", 958 | "tms", 959 | "zoom_offset" 960 | ], 961 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 962 | } 963 | }, 964 | "51dc8cde17a84b6897839090ec65fa08": { 965 | "model_module": "jupyter-leaflet", 966 | "model_module_version": "^0.17", 967 | "model_name": "LeafletMapStyleModel", 968 | "state": { 969 | "_model_module_version": "^0.17", 970 | "cursor": "crosshair" 971 | } 972 | }, 973 | "530c702061914f549aa756a040896abb": { 974 | "model_module": "jupyter-leaflet", 975 | "model_module_version": "^0.17", 976 | "model_name": "LeafletMapStyleModel", 977 | "state": { 978 | "_model_module_version": "^0.17" 979 | } 980 | }, 981 | "54732d9ee511484c86c640b59f9d1f61": { 982 | "model_module": "jupyter-leaflet", 983 | "model_module_version": "^0.17", 984 | "model_name": "LeafletZoomControlModel", 985 | "state": { 986 | "_model_module_version": "^0.17", 987 | "_view_module_version": "^0.17", 988 | "options": [ 989 | "position", 990 | "zoom_in_text", 991 | "zoom_in_title", 992 | "zoom_out_text", 993 | "zoom_out_title" 994 | ] 995 | } 996 | }, 997 | "54ad4bc247f0492cb40132748c9b3e7d": { 998 | "model_module": "@jupyter-widgets/base", 999 | "model_module_version": "2.0.0", 1000 | "model_name": "LayoutModel", 1001 | "state": { 1002 | "width": "100%" 1003 | } 1004 | }, 1005 | "5d3303f3faf04d21b9d362a4d4d77d75": { 1006 | "model_module": "ipytree", 1007 | "model_module_version": "^0.2", 1008 | "model_name": "NodeModel", 1009 | "state": { 1010 | "_id": "1e586d04-c1dc-4c33-9da4-3cb1b4f9939d", 1011 | "_model_module_version": "^0.2", 1012 | "_view_module_version": "^0.2", 1013 | "name": "Click on the map to inspect the layers." 1014 | } 1015 | }, 1016 | "6044e7e5893e4994a9d1bbdb0d17db25": { 1017 | "model_module": "ipytree", 1018 | "model_module_version": "^0.2", 1019 | "model_name": "TreeModel", 1020 | "state": { 1021 | "_model_module_version": "^0.2", 1022 | "_view_module_version": "^0.2", 1023 | "layout": "IPY_MODEL_8b4f1b315674492aa2aa0304fb09dbe9", 1024 | "nodes": [ 1025 | "IPY_MODEL_b5842c361b8a4929ac798f49ebbf8216" 1026 | ] 1027 | } 1028 | }, 1029 | "67489456c53340f980a0bead87bddeea": { 1030 | "model_module": "ipytree", 1031 | "model_module_version": "^0.2", 1032 | "model_name": "NodeModel", 1033 | "state": { 1034 | "_id": "b905e615-a770-4437-839d-075958201131", 1035 | "_model_module_version": "^0.2", 1036 | "_view_module_version": "^0.2", 1037 | "name": "dictionary: Object (1 elements)", 1038 | "nodes": [ 1039 | "IPY_MODEL_f2c6e70ba9084957aa70d6e157e77c0e" 1040 | ], 1041 | "opened": false 1042 | } 1043 | }, 1044 | "68f7ab1013b843a0a0bc5ee31c5e93ca": { 1045 | "model_module": "@jupyter-widgets/base", 1046 | "model_module_version": "2.0.0", 1047 | "model_name": "LayoutModel", 1048 | "state": { 1049 | "overflow": "scroll" 1050 | } 1051 | }, 1052 | "6a2efe109de649ceb140ff466c5ee86f": { 1053 | "model_module": "jupyter-leaflet", 1054 | "model_module_version": "^0.17", 1055 | "model_name": "LeafletTileLayerModel", 1056 | "state": { 1057 | "_model_module_version": "^0.17", 1058 | "_view_module_version": "^0.17", 1059 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 1060 | "base": true, 1061 | "max_zoom": 16, 1062 | "min_zoom": 1, 1063 | "name": "Stamen.Watercolor", 1064 | "options": [ 1065 | "attribution", 1066 | "bounds", 1067 | "detect_retina", 1068 | "max_native_zoom", 1069 | "max_zoom", 1070 | "min_native_zoom", 1071 | "min_zoom", 1072 | "no_wrap", 1073 | "tile_size", 1074 | "tms", 1075 | "zoom_offset" 1076 | ], 1077 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 1078 | } 1079 | }, 1080 | "6f2ea328676a49879861b6b4bcc561d7": { 1081 | "model_module": "jupyter-leaflet", 1082 | "model_module_version": "^0.17", 1083 | "model_name": "LeafletMapStyleModel", 1084 | "state": { 1085 | "_model_module_version": "^0.17" 1086 | } 1087 | }, 1088 | "77f396b373cf41c3a590a7e5f445813a": { 1089 | "model_module": "jupyter-leaflet", 1090 | "model_module_version": "^0.17", 1091 | "model_name": "LeafletZoomControlModel", 1092 | "state": { 1093 | "_model_module_version": "^0.17", 1094 | "_view_module_version": "^0.17", 1095 | "options": [ 1096 | "position", 1097 | "zoom_in_text", 1098 | "zoom_in_title", 1099 | "zoom_out_text", 1100 | "zoom_out_title" 1101 | ] 1102 | } 1103 | }, 1104 | "782599a241fb435abac9527c114b1f98": { 1105 | "model_module": "@jupyter-widgets/base", 1106 | "model_module_version": "2.0.0", 1107 | "model_name": "LayoutModel", 1108 | "state": { 1109 | "border_bottom": "1px solid black", 1110 | "border_left": "1px solid black", 1111 | "border_right": "1px solid black", 1112 | "border_top": "1px solid black" 1113 | } 1114 | }, 1115 | "798b4009a670401eb10bb0cd4a2d5902": { 1116 | "model_module": "jupyter-leaflet", 1117 | "model_module_version": "^0.17", 1118 | "model_name": "LeafletTileLayerModel", 1119 | "state": { 1120 | "_model_module_version": "^0.17", 1121 | "_view_module_version": "^0.17", 1122 | "name": "LonLat", 1123 | "options": [ 1124 | "attribution", 1125 | "bounds", 1126 | "detect_retina", 1127 | "max_native_zoom", 1128 | "max_zoom", 1129 | "min_native_zoom", 1130 | "min_zoom", 1131 | "no_wrap", 1132 | "tile_size", 1133 | "tms", 1134 | "zoom_offset" 1135 | ], 1136 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/94684f9069ec559fbd6d13a6e0b6ee1c-04378696088ee9cf631f26a06269232d/tiles/{z}/{x}/{y}" 1137 | } 1138 | }, 1139 | "8473a051cc414f46b4d978eaa18859eb": { 1140 | "model_module": "jupyter-leaflet", 1141 | "model_module_version": "^0.17", 1142 | "model_name": "LeafletMapStyleModel", 1143 | "state": { 1144 | "_model_module_version": "^0.17", 1145 | "cursor": "crosshair" 1146 | } 1147 | }, 1148 | "849b7220f4514883a28ad460340c4cac": { 1149 | "model_module": "jupyter-leaflet", 1150 | "model_module_version": "^0.17", 1151 | "model_name": "LeafletMapModel", 1152 | "state": { 1153 | "_model_module_version": "^0.17", 1154 | "_view_module_version": "^0.17", 1155 | "bottom": 2248, 1156 | "controls": [ 1157 | "IPY_MODEL_77f396b373cf41c3a590a7e5f445813a", 1158 | "IPY_MODEL_ee54cabd174f47ae94402d42c8a5c41b", 1159 | "IPY_MODEL_c0c3c520dd564b9a990b367c80d02320" 1160 | ], 1161 | "default_style": "IPY_MODEL_8473a051cc414f46b4d978eaa18859eb", 1162 | "dragging_style": "IPY_MODEL_c7fb9ecc8e80462e9b9bf33a74800c51", 1163 | "east": 83.05664062500001, 1164 | "fullscreen": false, 1165 | "interpolation": "bilinear", 1166 | "layers": [ 1167 | "IPY_MODEL_6a2efe109de649ceb140ff466c5ee86f", 1168 | "IPY_MODEL_f0c9cd6fe4a84c5db48cb2e45895a965" 1169 | ], 1170 | "layout": "IPY_MODEL_d0b665c8f34149b59bab95267946ec4b", 1171 | "left": 1104, 1172 | "modisdate": "2023-01-10", 1173 | "north": 17.308687886770034, 1174 | "options": [ 1175 | "bounce_at_zoom_limits", 1176 | "box_zoom", 1177 | "center", 1178 | "close_popup_on_click", 1179 | "double_click_zoom", 1180 | "dragging", 1181 | "fullscreen", 1182 | "inertia", 1183 | "inertia_deceleration", 1184 | "inertia_max_speed", 1185 | "interpolation", 1186 | "keyboard", 1187 | "keyboard_pan_offset", 1188 | "keyboard_zoom_offset", 1189 | "max_zoom", 1190 | "min_zoom", 1191 | "prefer_canvas", 1192 | "scroll_wheel_zoom", 1193 | "tap", 1194 | "tap_tolerance", 1195 | "touch_zoom", 1196 | "world_copy_jump", 1197 | "zoom", 1198 | "zoom_animation_threshold", 1199 | "zoom_delta", 1200 | "zoom_snap" 1201 | ], 1202 | "prefer_canvas": false, 1203 | "right": 2993, 1204 | "south": -17.308687886770024, 1205 | "style": "IPY_MODEL_2844db030e5d4cf48dc4184d9d199e01", 1206 | "top": 1848, 1207 | "west": -82.96875000000001, 1208 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/01_ipyleaflet.ipynb", 1209 | "zoom": 4 1210 | } 1211 | }, 1212 | "8a55fcdecff644bd895f52388ed1105f": { 1213 | "model_module": "jupyter-leaflet", 1214 | "model_module_version": "^0.17", 1215 | "model_name": "LeafletTileLayerModel", 1216 | "state": { 1217 | "_model_module_version": "^0.17", 1218 | "_view_module_version": "^0.17", 1219 | "name": "Landsat", 1220 | "options": [ 1221 | "attribution", 1222 | "bounds", 1223 | "detect_retina", 1224 | "max_native_zoom", 1225 | "max_zoom", 1226 | "min_native_zoom", 1227 | "min_zoom", 1228 | "no_wrap", 1229 | "tile_size", 1230 | "tms", 1231 | "zoom_offset" 1232 | ], 1233 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/6ad5740ea0606cba020bc83b7def6383-8c928b0f3ff829c4c984ad3ff1766542/tiles/{z}/{x}/{y}" 1234 | } 1235 | }, 1236 | "8b4f1b315674492aa2aa0304fb09dbe9": { 1237 | "model_module": "@jupyter-widgets/base", 1238 | "model_module_version": "2.0.0", 1239 | "model_name": "LayoutModel", 1240 | "state": { 1241 | "max_height": "400px", 1242 | "overflow": "scroll", 1243 | "width": "100%" 1244 | } 1245 | }, 1246 | "8d15fb3843af4c09a8df2e0c75b36237": { 1247 | "model_module": "@jupyter-widgets/controls", 1248 | "model_module_version": "2.0.0", 1249 | "model_name": "SliderStyleModel", 1250 | "state": { 1251 | "description_width": "" 1252 | } 1253 | }, 1254 | "91f2a10b7d3b448988309cca16a310d7": { 1255 | "model_module": "jupyter-leaflet", 1256 | "model_module_version": "^0.17", 1257 | "model_name": "LeafletMapStyleModel", 1258 | "state": { 1259 | "_model_module_version": "^0.17", 1260 | "cursor": "crosshair" 1261 | } 1262 | }, 1263 | "928f6d23fc54418cab73043865b95bab": { 1264 | "model_module": "@jupyter-widgets/controls", 1265 | "model_module_version": "2.0.0", 1266 | "model_name": "IntSliderModel", 1267 | "state": { 1268 | "behavior": "drag-tap", 1269 | "description": "Zoom level:", 1270 | "layout": "IPY_MODEL_0a0f61d71c324cce8a8724a97449ba20", 1271 | "max": 15, 1272 | "style": "IPY_MODEL_8d15fb3843af4c09a8df2e0c75b36237", 1273 | "value": 7 1274 | } 1275 | }, 1276 | "9dc137b26471496981796af08159c660": { 1277 | "model_module": "@jupyter-widgets/base", 1278 | "model_module_version": "2.0.0", 1279 | "model_name": "LayoutModel", 1280 | "state": { 1281 | "max_height": "400px", 1282 | "overflow": "scroll", 1283 | "width": "100%" 1284 | } 1285 | }, 1286 | "9e3fd2116ffe4fc5b8faac5584ff6a88": { 1287 | "model_module": "@jupyter-widgets/base", 1288 | "model_module_version": "2.0.0", 1289 | "model_name": "LayoutModel", 1290 | "state": { 1291 | "width": "100%" 1292 | } 1293 | }, 1294 | "9f173486cd994f3ab7b1d4451a0a9ce3": { 1295 | "model_module": "ipytree", 1296 | "model_module_version": "^0.2", 1297 | "model_name": "TreeModel", 1298 | "state": { 1299 | "_model_module_version": "^0.2", 1300 | "_view_module_version": "^0.2", 1301 | "layout": "IPY_MODEL_c1a6ad8f1f51414696d19cb2a79f36c9", 1302 | "nodes": [ 1303 | "IPY_MODEL_f7a808655c114082b3c5c4c9d8263d89" 1304 | ] 1305 | } 1306 | }, 1307 | "a6463125bcf344ffaa11dd7c575fe8f6": { 1308 | "model_module": "ipytree", 1309 | "model_module_version": "^0.2", 1310 | "model_name": "NodeModel", 1311 | "state": { 1312 | "_id": "b067a614-f714-4875-b7bf-3b014808b902", 1313 | "_model_module_version": "^0.2", 1314 | "_view_module_version": "^0.2", 1315 | "name": "string: hello!" 1316 | } 1317 | }, 1318 | "aa7270cfa51341e2ba6ac0c04873ca0c": { 1319 | "model_module": "jupyter-leaflet", 1320 | "model_module_version": "^0.17", 1321 | "model_name": "LeafletMapStyleModel", 1322 | "state": { 1323 | "_model_module_version": "^0.17" 1324 | } 1325 | }, 1326 | "aa81a18f0da34b6aa80763a9d1de3136": { 1327 | "model_module": "ipytree", 1328 | "model_module_version": "^0.2", 1329 | "model_name": "NodeModel", 1330 | "state": { 1331 | "_id": "19a08d4f-ad31-4145-9cd6-5338c2031d47", 1332 | "_model_module_version": "^0.2", 1333 | "_view_module_version": "^0.2", 1334 | "name": "JSON: [[\"nested\", \"list\"], {\"a nested\": \"dictionary\"}]" 1335 | } 1336 | }, 1337 | "ac161b0be8ea49ccb0c2dd62cd51757c": { 1338 | "model_module": "jupyter-leaflet", 1339 | "model_module_version": "^0.17", 1340 | "model_name": "LeafletMapStyleModel", 1341 | "state": { 1342 | "_model_module_version": "^0.17", 1343 | "cursor": "move" 1344 | } 1345 | }, 1346 | "b5842c361b8a4929ac798f49ebbf8216": { 1347 | "model_module": "ipytree", 1348 | "model_module_version": "^0.2", 1349 | "model_name": "NodeModel", 1350 | "state": { 1351 | "_id": "46ec3138-89c7-45dc-8893-941421b09f16", 1352 | "_model_module_version": "^0.2", 1353 | "_view_module_version": "^0.2", 1354 | "name": "Click on the map to inspect the layers." 1355 | } 1356 | }, 1357 | "b8c74a6b8f8e452b95178758be26c015": { 1358 | "model_module": "@jupyter-widgets/base", 1359 | "model_module_version": "2.0.0", 1360 | "model_name": "LayoutModel", 1361 | "state": {} 1362 | }, 1363 | "c0c3c520dd564b9a990b367c80d02320": { 1364 | "model_module": "jupyter-leaflet", 1365 | "model_module_version": "^0.17", 1366 | "model_name": "LeafletLayersControlModel", 1367 | "state": { 1368 | "_model_module_version": "^0.17", 1369 | "_view_module_version": "^0.17", 1370 | "options": [ 1371 | "position" 1372 | ], 1373 | "position": "topright" 1374 | } 1375 | }, 1376 | "c1a6ad8f1f51414696d19cb2a79f36c9": { 1377 | "model_module": "@jupyter-widgets/base", 1378 | "model_module_version": "2.0.0", 1379 | "model_name": "LayoutModel", 1380 | "state": { 1381 | "max_height": "400px", 1382 | "overflow": "scroll", 1383 | "width": "100%" 1384 | } 1385 | }, 1386 | "c6ecac66ad9b4db3b345f2ee7596a468": { 1387 | "model_module": "ipytree", 1388 | "model_module_version": "^0.2", 1389 | "model_name": "NodeModel", 1390 | "state": { 1391 | "_id": "5e228c35-05e9-440b-9a88-6ffb1534ab21", 1392 | "_model_module_version": "^0.2", 1393 | "_view_module_version": "^0.2", 1394 | "name": "nesting: List (2 elements)", 1395 | "nodes": [ 1396 | "IPY_MODEL_aa81a18f0da34b6aa80763a9d1de3136" 1397 | ], 1398 | "opened": false 1399 | } 1400 | }, 1401 | "c7fb9ecc8e80462e9b9bf33a74800c51": { 1402 | "model_module": "jupyter-leaflet", 1403 | "model_module_version": "^0.17", 1404 | "model_name": "LeafletMapStyleModel", 1405 | "state": { 1406 | "_model_module_version": "^0.17", 1407 | "cursor": "move" 1408 | } 1409 | }, 1410 | "cfab475a798448b1916786263b9a8e90": { 1411 | "model_module": "jupyter-leaflet", 1412 | "model_module_version": "^0.17", 1413 | "model_name": "LeafletMapModel", 1414 | "state": { 1415 | "_model_module_version": "^0.17", 1416 | "_view_module_version": "^0.17", 1417 | "bottom": 2248, 1418 | "controls": [ 1419 | "IPY_MODEL_2230df77a880469cb030cb1cdd046a6b", 1420 | "IPY_MODEL_2d81e9ccd8c14032b275563f33f194e2", 1421 | "IPY_MODEL_160f1f17eda549e794967ad36033535f" 1422 | ], 1423 | "default_style": "IPY_MODEL_51dc8cde17a84b6897839090ec65fa08", 1424 | "dragging_style": "IPY_MODEL_e2272cc617f34d7aa3d0c60c8b564f96", 1425 | "east": 83.05664062500001, 1426 | "fullscreen": false, 1427 | "interpolation": "bilinear", 1428 | "layers": [ 1429 | "IPY_MODEL_33a61cf764cb4927920c8222e90ccb60" 1430 | ], 1431 | "layout": "IPY_MODEL_9e3fd2116ffe4fc5b8faac5584ff6a88", 1432 | "left": 1104, 1433 | "modisdate": "2023-01-10", 1434 | "north": 17.308687886770034, 1435 | "options": [ 1436 | "bounce_at_zoom_limits", 1437 | "box_zoom", 1438 | "center", 1439 | "close_popup_on_click", 1440 | "double_click_zoom", 1441 | "dragging", 1442 | "fullscreen", 1443 | "inertia", 1444 | "inertia_deceleration", 1445 | "inertia_max_speed", 1446 | "interpolation", 1447 | "keyboard", 1448 | "keyboard_pan_offset", 1449 | "keyboard_zoom_offset", 1450 | "max_zoom", 1451 | "min_zoom", 1452 | "prefer_canvas", 1453 | "scroll_wheel_zoom", 1454 | "tap", 1455 | "tap_tolerance", 1456 | "touch_zoom", 1457 | "world_copy_jump", 1458 | "zoom", 1459 | "zoom_animation_threshold", 1460 | "zoom_delta", 1461 | "zoom_snap" 1462 | ], 1463 | "prefer_canvas": false, 1464 | "right": 2993, 1465 | "south": -17.308687886770024, 1466 | "style": "IPY_MODEL_6f2ea328676a49879861b6b4bcc561d7", 1467 | "top": 1848, 1468 | "west": -82.96875000000001, 1469 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/01_ipyleaflet.ipynb", 1470 | "zoom": 4 1471 | } 1472 | }, 1473 | "d0b665c8f34149b59bab95267946ec4b": { 1474 | "model_module": "@jupyter-widgets/base", 1475 | "model_module_version": "2.0.0", 1476 | "model_name": "LayoutModel", 1477 | "state": { 1478 | "width": "100%" 1479 | } 1480 | }, 1481 | "d0d292fd509a42199487515df00981f0": { 1482 | "model_module": "jupyter-leaflet", 1483 | "model_module_version": "^0.17", 1484 | "model_name": "LeafletMapStyleModel", 1485 | "state": { 1486 | "_model_module_version": "^0.17", 1487 | "cursor": "move" 1488 | } 1489 | }, 1490 | "d2f995acae1443b6b935acc8a8c400b6": { 1491 | "model_module": "ipytree", 1492 | "model_module_version": "^0.2", 1493 | "model_name": "NodeModel", 1494 | "state": { 1495 | "_id": "add92e92-ffbe-40bf-9e54-c39aad2c2b17", 1496 | "_model_module_version": "^0.2", 1497 | "_view_module_version": "^0.2", 1498 | "name": "JSON: [1, \"item1\"]" 1499 | } 1500 | }, 1501 | "d47faa88d79c4959a37169e1e38e9241": { 1502 | "model_module": "ipytree", 1503 | "model_module_version": "^0.2", 1504 | "model_name": "TreeModel", 1505 | "state": { 1506 | "_model_module_version": "^0.2", 1507 | "_view_module_version": "^0.2", 1508 | "layout": "IPY_MODEL_b8c74a6b8f8e452b95178758be26c015", 1509 | "nodes": [ 1510 | "IPY_MODEL_47097130e74244829c4536b3a89300b6", 1511 | "IPY_MODEL_07ae7d0268784ae1a137cb50a08767a3", 1512 | "IPY_MODEL_a6463125bcf344ffaa11dd7c575fe8f6", 1513 | "IPY_MODEL_faf90a6ceecc4f77915f9255bf74b49c", 1514 | "IPY_MODEL_67489456c53340f980a0bead87bddeea", 1515 | "IPY_MODEL_c6ecac66ad9b4db3b345f2ee7596a468" 1516 | ] 1517 | } 1518 | }, 1519 | "e09fd56d9fdc4a4096bb45a016011000": { 1520 | "model_module": "ipytree", 1521 | "model_module_version": "^0.2", 1522 | "model_name": "TreeModel", 1523 | "state": { 1524 | "_model_module_version": "^0.2", 1525 | "_view_module_version": "^0.2", 1526 | "layout": "IPY_MODEL_9dc137b26471496981796af08159c660", 1527 | "nodes": [ 1528 | "IPY_MODEL_5d3303f3faf04d21b9d362a4d4d77d75" 1529 | ] 1530 | } 1531 | }, 1532 | "e2272cc617f34d7aa3d0c60c8b564f96": { 1533 | "model_module": "jupyter-leaflet", 1534 | "model_module_version": "^0.17", 1535 | "model_name": "LeafletMapStyleModel", 1536 | "state": { 1537 | "_model_module_version": "^0.17", 1538 | "cursor": "move" 1539 | } 1540 | }, 1541 | "e4512b0df41443fda0e94002f8796ed4": { 1542 | "model_module": "jupyter-leaflet", 1543 | "model_module_version": "^0.17", 1544 | "model_name": "LeafletLayersControlModel", 1545 | "state": { 1546 | "_model_module_version": "^0.17", 1547 | "_view_module_version": "^0.17", 1548 | "options": [ 1549 | "position" 1550 | ], 1551 | "position": "topright" 1552 | } 1553 | }, 1554 | "ee54cabd174f47ae94402d42c8a5c41b": { 1555 | "model_module": "jupyter-leaflet", 1556 | "model_module_version": "^0.17", 1557 | "model_name": "LeafletAttributionControlModel", 1558 | "state": { 1559 | "_model_module_version": "^0.17", 1560 | "_view_module_version": "^0.17", 1561 | "options": [ 1562 | "position", 1563 | "prefix" 1564 | ], 1565 | "position": "bottomright", 1566 | "prefix": "ipyleaflet" 1567 | } 1568 | }, 1569 | "f0c9cd6fe4a84c5db48cb2e45895a965": { 1570 | "model_module": "jupyter-leaflet", 1571 | "model_module_version": "^0.17", 1572 | "model_name": "LeafletTileLayerModel", 1573 | "state": { 1574 | "_model_module_version": "^0.17", 1575 | "_view_module_version": "^0.17", 1576 | "name": "My layer", 1577 | "options": [ 1578 | "attribution", 1579 | "bounds", 1580 | "detect_retina", 1581 | "max_native_zoom", 1582 | "max_zoom", 1583 | "min_native_zoom", 1584 | "min_zoom", 1585 | "no_wrap", 1586 | "tile_size", 1587 | "tms", 1588 | "zoom_offset" 1589 | ], 1590 | "url": "https://earthengine.googleapis.com/v1alpha/projects/earthengine-legacy/maps/c89c7860f777996759fa533ed2e86632-6aa722b7b97fa5b2a6b6e188d7946079/tiles/{z}/{x}/{y}" 1591 | } 1592 | }, 1593 | "f25225841c5d4e5ca0c9f57ba5f01c4a": { 1594 | "model_module": "jupyter-leaflet", 1595 | "model_module_version": "^0.17", 1596 | "model_name": "LeafletTileLayerModel", 1597 | "state": { 1598 | "_model_module_version": "^0.17", 1599 | "_view_module_version": "^0.17", 1600 | "attribution": "Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors", 1601 | "base": true, 1602 | "max_zoom": 16, 1603 | "min_zoom": 1, 1604 | "name": "Stamen.Watercolor", 1605 | "options": [ 1606 | "attribution", 1607 | "bounds", 1608 | "detect_retina", 1609 | "max_native_zoom", 1610 | "max_zoom", 1611 | "min_native_zoom", 1612 | "min_zoom", 1613 | "no_wrap", 1614 | "tile_size", 1615 | "tms", 1616 | "zoom_offset" 1617 | ], 1618 | "url": "https://stamen-tiles-a.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg" 1619 | } 1620 | }, 1621 | "f2c6e70ba9084957aa70d6e157e77c0e": { 1622 | "model_module": "ipytree", 1623 | "model_module_version": "^0.2", 1624 | "model_name": "NodeModel", 1625 | "state": { 1626 | "_id": "06abaae4-b026-4c70-9fe7-d068df811456", 1627 | "_model_module_version": "^0.2", 1628 | "_view_module_version": "^0.2", 1629 | "name": "JSON: {\"a key\": \"a value\"}" 1630 | } 1631 | }, 1632 | "f335e46a0a3a4446873a52efb6bd3d99": { 1633 | "model_module": "jupyter-leaflet", 1634 | "model_module_version": "^0.17", 1635 | "model_name": "LeafletMapModel", 1636 | "state": { 1637 | "_model_module_version": "^0.17", 1638 | "_view_module_version": "^0.17", 1639 | "bottom": 2248, 1640 | "controls": [ 1641 | "IPY_MODEL_54732d9ee511484c86c640b59f9d1f61", 1642 | "IPY_MODEL_34eccbbe46bb4e7582ff83f2167a587d", 1643 | "IPY_MODEL_e4512b0df41443fda0e94002f8796ed4" 1644 | ], 1645 | "default_style": "IPY_MODEL_3cd16605239f401f80016b0709e90fd0", 1646 | "dragging_style": "IPY_MODEL_ac161b0be8ea49ccb0c2dd62cd51757c", 1647 | "east": 41.39648437500001, 1648 | "fullscreen": false, 1649 | "interpolation": "bilinear", 1650 | "layers": [ 1651 | "IPY_MODEL_4e33a3e56de54a788e6516a373dd16cb", 1652 | "IPY_MODEL_798b4009a670401eb10bb0cd4a2d5902", 1653 | "IPY_MODEL_8a55fcdecff644bd895f52388ed1105f" 1654 | ], 1655 | "layout": "IPY_MODEL_46ec4ffb94c54ddf9b7a01b8567ef4e5", 1656 | "left": 1577, 1657 | "modisdate": "2023-01-10", 1658 | "north": 17.308687886770034, 1659 | "options": [ 1660 | "bounce_at_zoom_limits", 1661 | "box_zoom", 1662 | "center", 1663 | "close_popup_on_click", 1664 | "double_click_zoom", 1665 | "dragging", 1666 | "fullscreen", 1667 | "inertia", 1668 | "inertia_deceleration", 1669 | "inertia_max_speed", 1670 | "interpolation", 1671 | "keyboard", 1672 | "keyboard_pan_offset", 1673 | "keyboard_zoom_offset", 1674 | "max_zoom", 1675 | "min_zoom", 1676 | "prefer_canvas", 1677 | "scroll_wheel_zoom", 1678 | "tap", 1679 | "tap_tolerance", 1680 | "touch_zoom", 1681 | "world_copy_jump", 1682 | "zoom", 1683 | "zoom_animation_threshold", 1684 | "zoom_delta", 1685 | "zoom_snap" 1686 | ], 1687 | "prefer_canvas": false, 1688 | "right": 2519, 1689 | "south": -17.308687886770024, 1690 | "style": "IPY_MODEL_530c702061914f549aa756a040896abb", 1691 | "top": 1848, 1692 | "west": -41.39648437500001, 1693 | "window_url": "http://localhost:8888/lab/workspaces/auto-c/tree/google/earthengine-jupyter/nbs/01_ipyleaflet.ipynb", 1694 | "zoom": 4 1695 | } 1696 | }, 1697 | "f7a808655c114082b3c5c4c9d8263d89": { 1698 | "model_module": "ipytree", 1699 | "model_module_version": "^0.2", 1700 | "model_name": "NodeModel", 1701 | "state": { 1702 | "_id": "9612679a-cf1b-4641-bb42-14117f09d52d", 1703 | "_model_module_version": "^0.2", 1704 | "_view_module_version": "^0.2", 1705 | "name": "Click on the map to inspect the layers." 1706 | } 1707 | }, 1708 | "faf90a6ceecc4f77915f9255bf74b49c": { 1709 | "model_module": "ipytree", 1710 | "model_module_version": "^0.2", 1711 | "model_name": "NodeModel", 1712 | "state": { 1713 | "_id": "b9848e8a-faf4-4862-8a7c-bc2055cc94bb", 1714 | "_model_module_version": "^0.2", 1715 | "_view_module_version": "^0.2", 1716 | "name": "list: List (2 elements)", 1717 | "nodes": [ 1718 | "IPY_MODEL_d2f995acae1443b6b935acc8a8c400b6" 1719 | ], 1720 | "opened": false 1721 | } 1722 | } 1723 | }, 1724 | "version_major": 2, 1725 | "version_minor": 0 1726 | } 1727 | } 1728 | }, 1729 | "nbformat": 4, 1730 | "nbformat_minor": 5 1731 | } 1732 | --------------------------------------------------------------------------------