Example #1
0
class Map(ipyleaflet.Map):
    def __init__(self, **kwargs):
        # Change defaults
        kwargs.setdefault('center', [0, 0])
        kwargs.setdefault('zoom', 2)
        super(Map, self).__init__(**kwargs)
        # self.added_geometries = {}
        # self.added_images = {}
        self.is_shown = False
        self.EELayers = {}

        # CREATE TABS
        self.tabs = Tab()
        tab_names = ['Inspector', 'Assets', 'Tasks']

        ## widgets
        self.inspectorWid = Accordion()  # Inspector Widget
        self.assetsWid = Accordion()  # Assets Widget
        self.tasksWid = HTML()  # Tasks Widget

        childrenName = ['Inspector', 'Assets', 'Tasks']
        childrenWid = [self.inspectorWid, self.assetsWid, self.tasksWid]

        # Dictonary to hold tab's widgets
        # (tab's name:widget)
        self.childrenDict = OrderedDict(zip(childrenName, childrenWid))

        # Set tabs children
        self.tabs.children = self.childrenDict.values()
        # Set tabs names
        for i, name in enumerate(tab_names):
            self.tabs.set_title(i, name)

        # Handlers
        self.tabs.observe(self.handle_change_tab)
        self.handlers = {'Inspector': self.handle_inspector}

        # First handler: Inspector
        self.on_interaction(self.handlers['Inspector'])

    @property
    def added_images(self):
        return sum(
            [1 for val in self.EELayers.values() if val['type'] == 'Image'])

    @property
    def added_geometries(self):
        return sum(
            [1 for val in self.EELayers.values() if val['type'] == 'Geometry'])

    def create_assets_tab(self):
        # ASSETS TAB
        # Get assets root
        rootid = ee.data.getAssetRoots()[0]['id']
        assets_list = ee.data.getList({'id': rootid})
        widlist = []
        namelist = []
        for asset in assets_list:
            wid = HTML('')
            widlist.append(wid)
            name = asset['id'].split('/')[-1]
            ty = asset['type']
            namelist.append('{} ({})'.format(name, ty))

        self.assetsWid.children = widlist
        for i, name in enumerate(namelist):
            self.assetsWid.set_title(i, name)

    def show(self, inspector=True):
        """ Show the Map on the Notebook """
        if not self.is_shown:
            # Layers Control
            lc = ipyleaflet.LayersControl()
            self.add_control(lc)
            self.is_shown = True

            if inspector:
                # Create Assets Tab
                self.create_assets_tab()
                # Display
                display(self, self.tabs)
            else:
                display(self)
        elif inspector:
            display(self, self.tabs)
        else:
            display(self)

    def addLayer(self,
                 eeObject,
                 visParams=None,
                 name=None,
                 show=True,
                 opacity=None,
                 inspect={
                     'data': None,
                     'reducer': None,
                     'scale': None
                 }):
        """ Adds a given EE object to the map as a layer.

        :param eeObject: Earth Engine object to add to map
        :type eeObject: ee.Image || ee.Geometry || ee.Feature
        :param visParams: visualization parameters. For Images can have the
            following arguments: bands, min, max.
        :type visParams: dict
        :param name: name for the layer
        :type name: str
        :param inspect: when adding a geometry or a feature you can pop up data
            from a desired layer. Params are:
            :data: the EEObject where to get the data from
            :reducer: the reducer to use
            :scale: the scale to reduce
        :return: the added layer
        :rtype:
        """
        def addImage(image, name):
            # Check if layer exists
            if name in self.EELayers.keys():
                print("Layer with name {} exists already, please choose" +
                      "another name".format(name))
                return

            params = get_image_tile(image, visParams, show, opacity)

            layer = ipyleaflet.TileLayer(url=params['url'],
                                         attribution=params['attribution'],
                                         name=name)
            self.add_layer(layer)
            self.EELayers[name] = {
                'type': 'Image',
                'object': image,
                'visParams': visParams,
                'layer': layer
            }
            return layer

        def addGeoJson(geometry, name):
            # Check if layer exists
            if name in self.EELayers.keys():
                print("Layer with name {} exists already, please choose" +
                      "another name".format(name))
                return

            params = get_geojson_tile(geometry, inspect)
            layer = ipyleaflet.GeoJSON(data=params['geojson'],
                                       name=name,
                                       popup=HTML(params['pop']))
            self.add_layer(layer)
            self.EELayers[name] = {
                'type': 'Geometry',
                'object': geometry,
                'visParams': None,
                'layer': layer
            }
            return layer

        # CASE: ee.Image
        if isinstance(eeObject, ee.Image):
            thename = name if name else 'Image {}'.format(self.added_images)
            addImage(eeObject, thename)
        elif isinstance(eeObject, ee.Geometry):
            thename = name if name else 'Geometry {}'.format(
                self.added_geometries)
            addGeoJson(eeObject, thename)
        elif isinstance(eeObject, ee.Feature):
            geom = eeObject.geometry()
            addGeoJson(geom)
        elif isinstance(eeObject, ee.ImageCollection):
            proxy = eeObject.sort('system:time_start')
            mosaic = ee.Image(proxy.mosaic())
            thename = name if name else 'Mosaic {}'.format(self.added_images)
            addImage(mosaic, thename)
        else:
            print("`addLayer` doesn't support adding the specified object to"
                  "the map")

    def removeLayer(self, name):
        """ Remove a layer by its name """
        if name in self.EELayers.keys():
            layer = self.EELayers[name]['layer']
            self.remove_layer(layer)
            self.EELayers.pop(name)
        else:
            print('Layer {} is not present in the map'.format(name))
            return

    def centerObject(self, eeObject, zoom=None, method=1):
        """ Center an eeObject

        :param eeObject:
        :param zoom:
        :param method: experimetal methods to estimate zoom for fitting bounds
            Currently: 1 or 2
        :type: int
        """
        bounds = get_bounds(eeObject)
        if isinstance(eeObject, ee.Geometry):
            centroid = eeObject.centroid().getInfo()['coordinates']
        elif isinstance(eeObject, ee.Feature) or isinstance(
                eeObject, ee.Image):
            centroid = eeObject.geometry().centroid().getInfo()['coordinates']
        elif isinstance(eeObject, list):
            pol = ee.Geometry.Polygon(inverse_coordinates(list))
            centroid = pol.centroid().getInfo()['coordinates']

        self.center = inverse_coordinates(centroid)
        if zoom:
            self.zoom = zoom
        else:
            self.zoom = get_zoom(bounds, method)

    def getCenter(self):
        """ Returns the coordinates at the center of the map.

        No arguments.
        Returns: Geometry.Point

        :return:
        """
        center = self.center
        coords = inverse_coordinates(center)
        return ee.Geometry.Point(coords)

    def getBounds(self, asGeoJSON=True):
        """ Returns the bounds of the current map view, as a list in the
        format [west, south, east, north] in degrees.

        Arguments:
        asGeoJSON (Boolean, optional):
        If true, returns map bounds as GeoJSON.

        Returns: GeoJSONGeometry|List<Number>|String
        """
        bounds = inverse_coordinates(self.bounds)
        if asGeoJSON:
            return ee.Geometry.Rectangle(bounds)
        else:
            return bounds

    def addTab(self, name, handler, widget=None):
        """ Add a Tab to the Panel. The handler is for the Map

        :param name: name for the new tab
        :type name: str
        :param handler: handle function for the new tab. Arguments of the
            function are:

            :type: the type of the event (click, mouseover, etc..)
            :coordinates: coordinates where the event occured [lon, lat]
            :widget: the widget inside the Tab
            :map: the Map instance

        :param widget: widget inside the Tab. Defaults to HTML('')
        :type widget: ipywidgets.Widget
        """
        # Widget
        wid = widget if widget else HTML('')
        # Get tab's children as a list
        tab_children = list(self.tabs.children)
        # Get a list of tab's titles
        titles = [
            self.tabs.get_title(i) for i, child in enumerate(tab_children)
        ]
        # Check if tab already exists
        if name not in titles:
            ntabs = len(tab_children)
            # Add widget as a new children
            self.childrenDict[name] = wid
            tab_children.append(wid)
            # Overwrite tab's children
            self.tabs.children = tab_children
            # Set name of the new tab
            self.tabs.set_title(ntabs, name)

            # Set the handler for the new tab
            def proxy_handler(f):
                def wrap(**kwargs):
                    # Add widget to handler arguments
                    kwargs['widget'] = self.childrenDict[name]
                    coords = kwargs['coordinates']
                    kwargs['coordinates'] = inverse_coordinates(coords)
                    kwargs['map'] = self
                    return f(**kwargs)

                return wrap

            self.handlers[name] = proxy_handler(handler)
        else:
            print('Tab {} already exists, please choose another name'.format(
                name))

    def handle_change_tab(self, change):
        """ Handle function to trigger when tab changes """
        # Remove all handlers
        if change['name'] == 'selected_index':
            old = change['old']
            new = change['new']
            old_name = self.tabs.get_title(old)
            new_name = self.tabs.get_title(new)
            # Remove all handlers
            for handl in self.handlers.values():
                self.on_interaction(handl, True)
            # Set new handler
            if new_name in self.handlers.keys():
                self.on_interaction(self.handlers[new_name])

    def handle_inspector(self, **change):
        """ Handle function for the Inspector Widget """
        # Get click coordinates
        coords = inverse_coordinates(change['coordinates'])

        event = change['type']  # event type
        if event == 'click':  # If the user clicked
            # Clear children // Loading
            self.inspectorWid.children = [HTML('wait a second please..')]
            self.inspectorWid.set_title(0, 'Loading...')

            # create a point where the user clicked
            point = ee.Geometry.Point(coords)

            # First Accordion row text (name)
            first = 'Point {} at {} zoom'.format(coords, self.zoom)
            namelist = [first]
            wids4acc = [HTML('')]  # first row has no content

            for name, obj in self.EELayers.items():  # for every added layer
                # name = obj['name']
                # IMAGES
                if obj['type'] == 'Image':
                    # Get the image's values
                    image = obj['object']
                    values = tools.get_value(image, point, 10, 'client')
                    values = tools.sort_dict(values)
                    # Create the content
                    img_html = ''
                    for band, value in values.items():
                        img_html += '<b>{}</b>: {}</br>'.format(band, value)
                    wid = HTML(img_html)
                    # append widget to list of widgets
                    wids4acc.append(wid)
                    namelist.append(name)
                # GEOMETRIES
                elif obj['type'] == 'Geometry':
                    geom = obj['object']
                    data = str(geom.getInfo())
                    wid = HTML(data)
                    wids4acc.append(wid)
                    namelist.append(name)

            # Set children and children's name of inspector widget
            self.inspectorWid.children = wids4acc
            for i, n in enumerate(namelist):
                self.inspectorWid.set_title(i, n)