<jupyter_start><jupyter_text>Polygon Selector<jupyter_code>import odc.algo
import odc.ui

from datacube import Datacube
from datacube.storage.masking import mask_invalid_data
import ipyleaflet
import datacube
import sys
import xarray as xr
import numpy as np
from typing import Tuple, Optional, List
from sidecar import Sidecar
from ipywidgets import IntSlider, widgets as w
from matplotlib import cm
import matplotlib.pyplot as plt
from IPython.display import Image, display
from matplotlib.colors import Normalize
import os

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

sys.path.append('../../../Scripts')
from dea_datahandling import load_ard
from dea_plotting import rgb
from dea_plotting import display_map
from datacube.utils.rio import set_default_rio_config

# Only run if executing in the cloud, will successfully do nothing on NCI
if 'AWS_ACCESS_KEY_ID' in os.environ:
    set_default_rio_config(aws={'region_name': 'auto'},
                           cloud_defaults=True)
dc = Datacube(app='viz')
# Create sidecar and map widget
sc = Sidecar(title='Map')

# Create map and add it to sidecar
m = ipyleaflet.Map(basemap=ipyleaflet.basemaps.Esri.WorldImagery, center=(-25, 133), zoom=3, scroll_wheel_zoom=True)

# Add Full Screen and Layers Controls
m.add_control(ipyleaflet.FullScreenControl())
m.add_control(ipyleaflet.LayersControl())

# Add the opactity slider
slider = w.FloatSlider(min=0, max=1, value=1,        # Opacity is valid in [0,1] range
                       orientation='vertical',       # Vertical slider is what we want
                       readout=False,                # No need to show exact value
                       layout=w.Layout(width='2em')) # Fine tune display layout: make it thinner
m.add_control(ipyleaflet.WidgetControl(widget=slider))

# Add map to sidecar
with sc:
  display(m)
# Define product and red/green/blue bands in the given product
product = 'ls8_nbart_geomedian_annual'
RGB = ('red', 'green', 'blue')

# Region and time of interest
query = dict(
    lat=(-27.60, -27.665),
    lon=(153.33, 153.425),
    time='2018',
)

dss = dc.find_datasets(product=product, **query)
print(f"Found {len(dss)} datasets")

ds = dc.load(
    product=product,             # dc.load always needs product supplied, this needs to be fixed in `dc.load` code
    datasets=dss,                # Datasets we found earlier
    measurements=RGB,            # Only load red,green,blue bands
    group_by='solar_day',        # Fuse all datasets captured on the same day into one raster plane
    output_crs='EPSG:3857',      # Default projection used by Leaflet and most webmaps
    resolution=(-30, 30),      # 200m pixels (1/20 of the native
    progress_cbk=odc.ui.with_ui_cbk())  # Display load progress

ds = mask_invalid_data(ds)
image = odc.ui.mk_image_overlay(ds, clamp=3000, layer_name='Image1')
zoom = odc.ui.zoom_from_bbox(ds.geobox.geographic_extent.boundingbox)
# Add layer to map
m.zoom = zoom
# Center map on new image
m.center = (query['lat'][0], query['lon'][0])

# Add the opacity slider to the new image
w.jslink((slider, 'value'),         
         (image, 'opacity') )
m.add_layer(image)
from ipyleaflet import Map, basemaps, basemap_to_tiles, DrawControl


draw_control = DrawControl()
draw_control.polyline =  {
    "shapeOptions": {
        "color": "#6bc2e5",
        "weight": 8,
        "opacity": 1.0
    }
}
draw_control.polygon = {
    "shapeOptions": {
        "fillColor": "#6be5c3",
        "color": "#6be5c3",
        "fillOpacity": 1.0
    },
    "drawError": {
        "color": "#dd253b",
        "message": "Oups!"
    },
    "allowIntersection": False
}
draw_control.circle = {
    "shapeOptions": {
        "fillColor": "#efed69",
        "color": "#efed69",
        "fillOpacity": 1.0
    }
}
draw_control.rectangle = {
    "shapeOptions": {
        "fillColor": "#fca45d",
        "color": "#fca45d",
        "fillOpacity": 1.0
    }
}
#m.add_control(draw_control)
#def handle_draw(self, action, geo_json):
#    print(action)
#    print(geo_json)

#draw_control.on_draw(handle_draw)
#m.add_control(draw_control)
def mk_map_region_selector(**kwargs):
    from ipyleaflet import Map, WidgetControl, FullScreenControl, DrawControl
    from ipywidgets.widgets import Layout, Button, HTML
    from types import SimpleNamespace

    state = SimpleNamespace(selection=None,
                            bounds=None,
                            done=False)

    btn_done = Button(description='done',
                      layout=Layout(width='5em'))
    btn_done.style.button_color = 'green'
    btn_done.disabled = True

    html_info = HTML(layout=Layout(flex='1 0 20em',
                                   width='20em',
                                   height='3em'))

    def update_info(txt):
        html_info.value = '<pre style="color:grey">' + txt + '</pre>'

    #m = Map(**kwargs) if len(kwargs) else Map(zoom=2)
    #m.scroll_wheel_zoom = True
    #m.layout.height = height
    
    m = kwargs['current_map']

    widgets = [
        WidgetControl(widget=btn_done, position='topright'),
        WidgetControl(widget=html_info, position='bottomleft'),
    ]
    for w in widgets:
        m.add_control(w)

    draw = DrawControl()

    draw.circle = {}
    draw.polyline = {}
    draw.circlemarker = {}

    shape_opts = {"fillColor": "#fca45d",
                  "color": "#000000",
                  "fillOpacity": 0.1}
    draw.rectangle = {"shapeOptions": shape_opts}
    poly_opts = {"shapeOptions": dict(**shape_opts)}
    poly_opts["shapeOptions"]["original"] = dict(**shape_opts)
    poly_opts["shapeOptions"]["editing"] = dict(**shape_opts)

    draw.polygon = poly_opts
    draw.edit = True
    draw.remove = True
    m.add_control(draw)

    def on_done(btn):
        state.done = True
        btn_done.disabled = True
        m.remove_control(draw)
        for w in widgets:
            m.remove_control(w)

    def bounds_handler(event):
        (lat1, lon1), (lat2, lon2) = event['new']
        txt = 'lat: [{:.{n}f}, {:.{n}f}]\nlon: [{:.{n}f}, {:.{n}f}]'.format(
            lat1, lat2, lon1, lon2, n=4)
        update_info(txt)
        state.bounds = dict(lat=(lat1, lat2),
                            lon=(lon1, lon2))

    def on_draw(event):
        v = event['new']
        action = event['name']
        if action == 'last_draw':
            state.selection = v['geometry']
        elif action == 'last_action' and v == 'deleted':
            state.selection = None

        btn_done.disabled = state.selection is None

    draw.observe(on_draw)
    m.observe(bounds_handler, ('bounds',))
    btn_done.on_click(on_done)

    return state


def select_on_a_map(**kwargs):
    """ Display a map and block execution until user selects a region of interest.
    Returns selected region as datacube Geometry class.
        polygon = select_on_map()
    **kwargs**
      height -- height of the map, for example "500px", "10el"
    Any parameter ipyleaflet.Map(..) accepts:
      zoom   Int
      center (lat: Float, lon: Float)
      ...
    """
    from IPython.display import display
    from odc.ui import ui_poll

    state = mk_map_region_selector(**kwargs)

    def extract_geometry(state):
        from datacube.utils.geometry import Geometry
        from datacube.testutils.geom import epsg4326

        return Geometry(state.selection, epsg4326)

    return ui_poll(lambda: extract_geometry(state) if state.done else None)
extents = select_on_a_map(current_map=m)
print(extents)
polygons = extents
print(polygons)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1187961985216473, 'fraction_numerical': 0.017423442449841606, 'mean_word_length': 2.9198137609932746, 'pattern_counts': {'":': 25, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '361668', 'license': 'Apache-2.0', 'n_tokens_mistral': 2533, 'n_tokens_neox': 2421, 'n_words': 603, 'path': 'odc_accessibility/Polygon_Selector.ipynb', 'repo_name': 'opendatacube/datacube-conference-2020', 'size': '7576'}