From 7e46a039db06e0b271ba10af7ccc98fb8b6e9fba Mon Sep 17 00:00:00 2001 From: Victoria Neema <39700992+vikineema@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:32:42 +0300 Subject: [PATCH] Update planetbasemaps app (#528) * Add dev dependency group * Simplify dev conda env setup * Update gitignore * Rename environment file * Update conda env setup * Add date filtering for timeseries * minor update * Remove unused imports * Move imports to top level * Move imports to top level * Minor changes * Add error handling for coastlines overlay * Remove unused imports * minor edit * Bump version * Bump version * Fix broken notebook --- Datasets/Planet_basemap.ipynb | 70 +++--- Tools/deafrica_tools/__init__.py | 2 +- Tools/deafrica_tools/app/planetbasemap.py | 246 ++++++++++++---------- Tools/pyproject.toml | 2 +- 4 files changed, 179 insertions(+), 141 deletions(-) diff --git a/Datasets/Planet_basemap.ipynb b/Datasets/Planet_basemap.ipynb index e32342ed..25e822a4 100644 --- a/Datasets/Planet_basemap.ipynb +++ b/Datasets/Planet_basemap.ipynb @@ -106,7 +106,7 @@ "metadata": {}, "outputs": [], "source": [ - "#pip install localtileserver" + "# pip install localtileserver" ] }, { @@ -170,11 +170,11 @@ "# Method 1: Specify the latitude, longitude, and buffer\n", "aoi = define_area(lat=6.74248, lon=-1.69340, buffer=0.05)\n", "\n", - "# Method 2: Use a polygon as a GeoJSON or Esri Shapefile. \n", + "# Method 2: Use a polygon as a GeoJSON or Esri Shapefile.\n", "# aoi = define_area(vector_path='aoi.shp')\n", "\n", "geopolygon = Geometry(aoi[\"features\"][0][\"geometry\"], crs=\"epsg:4326\")\n", - "geopolygon_gdf = gpd.GeoDataFrame(geometry=[geopolygon], crs=geopolygon.crs)\n", + "geopolygon_gdf = gpd.GeoDataFrame(geometry=[geopolygon.geom], crs=geopolygon.crs)\n", "\n", "# Get the latitude and longitude range of the geopolygon\n", "lon_range = (geopolygon_gdf.total_bounds[0], geopolygon_gdf.total_bounds[2])\n", @@ -214,41 +214,42 @@ " \n", " <style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>\n", " <style>#map {position:absolute;top:0;bottom:0;right:0;left:0;}</style>\n", - " <script src="https://cdn.jsdelivr.net/npm/leaflet@1.6.0/dist/leaflet.js"></script>\n", - " <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>\n", - " <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>\n", + " <script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>\n", + " <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>\n", + " <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>\n", " <script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>\n", - " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.6.0/dist/leaflet.css"/>\n", - " <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>\n", - " <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"/>\n", - " <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>\n", + " <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>\n", + " <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.0/css/all.min.css"/>\n", " <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>\n", " <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>\n", " \n", " <meta name="viewport" content="width=device-width,\n", " initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n", " <style>\n", - " #map_d1c7256035306816612c48d549e6b74e {\n", + " #map_2d9926387269a65daae70d8b70f7e5e2 {\n", " position: relative;\n", " width: 100.0%;\n", " height: 100.0%;\n", " left: 0.0%;\n", " top: 0.0%;\n", " }\n", + " .leaflet-container { font-size: 1rem; }\n", " </style>\n", " \n", "</head>\n", "<body>\n", " \n", " \n", - " <div class="folium-map" id="map_d1c7256035306816612c48d549e6b74e" ></div>\n", + " <div class="folium-map" id="map_2d9926387269a65daae70d8b70f7e5e2" ></div>\n", " \n", "</body>\n", "<script>\n", " \n", " \n", - " var map_d1c7256035306816612c48d549e6b74e = L.map(\n", - " "map_d1c7256035306816612c48d549e6b74e",\n", + " var map_2d9926387269a65daae70d8b70f7e5e2 = L.map(\n", + " "map_2d9926387269a65daae70d8b70f7e5e2",\n", " {\n", " center: [6.7424800000000005, -1.6934],\n", " crs: L.CRS.EPSG3857,\n", @@ -262,33 +263,36 @@ "\n", " \n", " \n", - " var tile_layer_0649989f976cbce99af9417821c3dee0 = L.tileLayer(\n", + " var tile_layer_b1d70385ccba87b2b11bc25afee35111 = L.tileLayer(\n", " "http://mt1.google.com/vt/lyrs=y\\u0026z={z}\\u0026x={x}\\u0026y={y}",\n", - " {"attribution": "Google", "detectRetina": false, "maxNativeZoom": 18, "maxZoom": 18, "minZoom": 0, "noWrap": false, "opacity": 1, "subdomains": "abc", "tms": false}\n", - " ).addTo(map_d1c7256035306816612c48d549e6b74e);\n", + " {"attribution": "Google", "detectRetina": false, "maxZoom": 18, "minZoom": 0, "noWrap": false, "opacity": 1, "subdomains": "abc", "tms": false}\n", + " );\n", + " \n", + " \n", + " tile_layer_b1d70385ccba87b2b11bc25afee35111.addTo(map_2d9926387269a65daae70d8b70f7e5e2);\n", " \n", " \n", - " var poly_line_34df01c46c687967c97f8f33c653d79e = L.polyline(\n", + " var poly_line_3798352bdd75c346bc2a5920ac7a3568 = L.polyline(\n", " [[6.69248, -1.7434], [6.69248, -1.6434], [6.79248, -1.6434], [6.79248, -1.7434], [6.69248, -1.7434]],\n", " {"bubblingMouseEvents": true, "color": "red", "dashArray": null, "dashOffset": null, "fill": false, "fillColor": "red", "fillOpacity": 0.2, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "noClip": false, "opacity": 0.8, "smoothFactor": 1.0, "stroke": true, "weight": 3}\n", - " ).addTo(map_d1c7256035306816612c48d549e6b74e);\n", + " ).addTo(map_2d9926387269a65daae70d8b70f7e5e2);\n", " \n", " \n", - " var lat_lng_popup_3a35bedd0b11fee338fe5473d26f57a2 = L.popup();\n", + " var lat_lng_popup_61d430de38276b1b92c9852d5396cd9e = L.popup();\n", " function latLngPop(e) {\n", - " lat_lng_popup_3a35bedd0b11fee338fe5473d26f57a2\n", + " lat_lng_popup_61d430de38276b1b92c9852d5396cd9e\n", " .setLatLng(e.latlng)\n", " .setContent("Latitude: " + e.latlng.lat.toFixed(4) +\n", " "<br>Longitude: " + e.latlng.lng.toFixed(4))\n", - " .openOn(map_d1c7256035306816612c48d549e6b74e);\n", + " .openOn(map_2d9926387269a65daae70d8b70f7e5e2);\n", " }\n", - " map_d1c7256035306816612c48d549e6b74e.on('click', latLngPop);\n", + " map_2d9926387269a65daae70d8b70f7e5e2.on('click', latLngPop);\n", " \n", "</script>\n", "</html>\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" allowfullscreen webkitallowfullscreen mozallowfullscreen>" ], "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -341,18 +345,20 @@ "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option DTYPE\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option CRS\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option TRANSFORM\n", + "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option NODATA\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option WIDTH\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option HEIGHT\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option COUNT\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option DTYPE\n", "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option CRS\n", - "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option TRANSFORM\n" + "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option TRANSFORM\n", + "WARNING:rasterio._env:CPLE_NotSupported in driver GTiff does not support creation option NODATA\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6ba997cfc5744b44ac1b78c74300a5d9", + "model_id": "6d1b25c35c91445f89c800c2a84c4e85", "version_major": 2, "version_minor": 0 }, @@ -366,9 +372,9 @@ } ], "source": [ - "#NDVI threshold can be adjusted below; the NDVI value range is between 0 and 1, default value used is 0.6\n", + "# NDVI threshold can be adjusted below; the NDVI value range is between 0 and 1, default value used is 0.6\n", "threshold_nvdi = 0.6\n", - "#BUI threshold can be adjsuted below, BUI value range is between -1 and 1, default value used is 0\n", + "# BUI threshold can be adjsuted below, BUI value range is between -1 and 1, default value used is 0\n", "threshold_bui = 0\n", "loadplanet(lon_range, lat_range, threshold_nvdi, threshold_bui)" ] @@ -403,12 +409,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "1.8.15\n" + "1.8.19\n" ] } ], "source": [ "import datacube\n", + "\n", "print(datacube.__version__)" ] }, @@ -429,7 +436,7 @@ { "data": { "text/plain": [ - "'2024-07-02'" + "'2024-09-03'" ] }, "execution_count": 7, @@ -439,7 +446,8 @@ ], "source": [ "from datetime import datetime\n", - "datetime.today().strftime('%Y-%m-%d')" + "\n", + "datetime.today().strftime(\"%Y-%m-%d\")" ] } ], diff --git a/Tools/deafrica_tools/__init__.py b/Tools/deafrica_tools/__init__.py index 42003619..149c0ec7 100644 --- a/Tools/deafrica_tools/__init__.py +++ b/Tools/deafrica_tools/__init__.py @@ -1,6 +1,6 @@ __locales__ = __path__[0] + "/locales" -__version__ = "2.4.7" +__version__ = "2.4.8" def set_lang(lang=None): diff --git a/Tools/deafrica_tools/app/planetbasemap.py b/Tools/deafrica_tools/app/planetbasemap.py index 6b058db1..61999da8 100644 --- a/Tools/deafrica_tools/app/planetbasemap.py +++ b/Tools/deafrica_tools/app/planetbasemap.py @@ -1,156 +1,186 @@ -''' +""" Functions for planet imagery and interacting with DE Africa dataset. -''' - +""" import os -os.environ['LOCALTILESERVER_CLIENT_PREFIX'] = f"{os.environ['JUPYTERHUB_SERVICE_PREFIX']}/proxy/{{port}}" - -import warnings -import numpy as np -import geopandas as gpd -import ipywidgets -from ipywidgets import IntSlider, ColorPicker, jslink -from datetime import datetime, timedelta -from IPython.display import HTML, display - -from ipyleaflet import Map, LayersControl, TileLayer, basemap_to_tiles, basemaps, GeoData, FullScreenControl, WidgetControl, LegendControl -from localtileserver import get_leaflet_tile_layer, TileClient +os.environ["LOCALTILESERVER_CLIENT_PREFIX"] = ( + f"{os.environ['JUPYTERHUB_SERVICE_PREFIX']}/proxy/{{port}}" +) +import warnings +from datetime import datetime import datacube -from deafrica_tools.datahandling import load_ard -from deafrica_tools.plotting import rgb, display_map -from deafrica_tools.bandindices import calculate_indices +import numpy as np from datacube.utils.cog import write_cog -from datacube.utils.geometry import Geometry - -from deafrica_tools.waterbodies import ( - get_geohashes, - get_waterbodies, - get_waterbody, - get_time_series, - display_time_series, +from ipyleaflet import ( + FullScreenControl, + GeoData, + LayersControl, + LegendControl, + Map, + TileLayer, + basemap_to_tiles, + basemaps, ) +from localtileserver import TileClient, get_leaflet_tile_layer + +from deafrica_tools.bandindices import calculate_indices +from deafrica_tools.waterbodies import get_waterbodies # Turn off all warnings. warnings.filterwarnings("ignore") warnings.simplefilter("ignore") - -#Getting -def get_last_calendar_month(): +def get_last_calendar_month() -> tuple[int, int]: """ - Get the last month and year + Get the year and month of the previous calendar month. """ # Get the current date today = datetime.today() - - # Calculate the first day of the current month - first_day_of_current_month = today.replace(day=1) - # Subtract one day to get the last day of the previous month - if today.day > 4: - pre_month = 1 + + current_year = today.year + current_month = today.month + + # Extract the year and month of the previous month + if current_month == 1: + year = current_year - 1 + month = 12 else: - pre_month = 45 - last_day_of_last_month = first_day_of_current_month - timedelta(days=pre_month) - - # Extract the year and month from the last day of the previous month - year = last_day_of_last_month.year - month = last_day_of_last_month.month - + year = current_year + month = current_month - 1 + return year, month -## Load map to display -def loadplanet(lon_range, lat_range, threshold_nvdi, threshold_bui): + +# Load map to display +def loadplanet(lon_range: tuple, lat_range: tuple, threshold_nvdi: float, threshold_bui: float): """ - Loading of planet imagery and rolling geomad + Loading of planet imagery and S2 R """ - #Connect to the datacube - dc = datacube.Datacube(app='planet') + # Connect to the datacube + dc = datacube.Datacube(app="planet") # Create a bounding box from study area coordinates bbox = (lon_range[0], lat_range[0], lon_range[1], lat_range[1]) # Select all water bodies located within the bounding box polygons = get_waterbodies(bbox, crs="EPSG:4326") - - #get previous month and year + + # Get the year and month of the previous calendar month. year, month = get_last_calendar_month() - # load data - ds = dc.load(product="gm_s2_annual", - measurements=['red', 'green', 'blue', 'nir','swir_1'], - y=lat_range, - x=lon_range, - resolution=(-10, 10), - time=(f"{year:04d}"), - ) - #Check if there was any curent data in the previous load + # Load the S2 Annual GeoMAD + ds = dc.load( + product="gm_s2_annual", + measurements=["red", "green", "blue", "nir", "swir_1"], + y=lat_range, + x=lon_range, + resolution=(-10, 10), + time=(f"{year:04d}"), + ) + # Check if there was any current data in the previous load if len(ds) == 0: - #if not then load with the manual date provider - ds = dc.load(product="gm_s2_annual", - measurements=['red', 'green', 'blue', 'nir','swir_1'], + # if not then load with the manual date provider + ds = dc.load( + product="gm_s2_annual", + measurements=["red", "green", "blue", "nir", "swir_1"], y=lat_range, - x=lon_range, - resolution=(-10, 10), - time=("2022", f"{year:04d}"), - ) - - #select the last best image + x=lon_range, + resolution=(-10, 10), + time=("2022", f"{year:04d}"), + ) + + # select the last best image ds = ds.isel(time=-1) - - #calculate NDVI and BUI - ds = calculate_indices(ds, index=['NDVI','BUI'], satellite_mission='s2') - - #Select NDVI value greater than threshold_nvdi value - ds_ndvi = ds.where(ds.NDVI>=threshold_nvdi, np.nan).NDVI - - #Save the ndvi raster file + + # calculate NDVI and BUI + ds = calculate_indices(ds, index=["NDVI", "BUI"], satellite_mission="s2") + + # Select NDVI value greater than threshold_nvdi value + ds_ndvi = ds.where(ds.NDVI >= threshold_nvdi, np.nan).NDVI + + # Save the ndvi raster file write_cog(ds_ndvi, fname="ndvi.tif", overwrite=True) - - #Select BUI greater than 0 - ds_bui = ds.where(ds.BUI>= threshold_bui, np.nan).BUI - - #Save the bui raster file + + # Select BUI greater than 0 + ds_bui = ds.where(ds.BUI >= threshold_bui, np.nan).BUI + + # Save the bui raster file write_cog(ds_bui, fname="bui.tif", overwrite=True) - - #Defining the planet api - planet_ = f"https://api.digitalearth.africa/planet/tiles/basemaps/v1/planet-tiles/planet_medres_visual_{year:04d}-{month:02d}_mosaic/gmap/"+"{z}/{x}/{y}.png" - - #Converting the api to tilelaye - planet_tile = TileLayer(url=planet_, name=f"Planet NICFI-{year:04d}-{month:02d}", show_loading=True, attribution="Planet NICFI") + + # Defining the planet api + planet_ = ( + f"https://api.digitalearth.africa/planet/tiles/basemaps/v1/planet-tiles/planet_medres_visual_{year:04d}-{month:02d}_mosaic/gmap/" + + "{z}/{x}/{y}.png" + ) + + # Converting the api to tilelayer + planet_tile = TileLayer( + url=planet_, + name=f"Planet NICFI-{year:04d}-{month:02d}", + show_loading=True, + attribution="Planet NICFI", + ) planet_tile.base = True - - #Converting the water polygon to GeoData - geo_data = GeoData(geo_dataframe = polygons, - style={'color': 'black', 'fillColor': '#3366cc', 'opacity':0.05, 'weight':1.9, 'dashArray':'2', 'fillOpacity':0.6}, - name = 'Water Body') + + # Converting the water polygon to GeoData + geo_data = GeoData( + geo_dataframe=polygons, + style={ + "color": "black", + "fillColor": "#3366cc", + "opacity": 0.05, + "weight": 1.9, + "dashArray": "2", + "fillOpacity": 0.6, + }, + name="Water Body", + ) # Create a tile server from local raster file - bui_client = TileClient('bui.tif') - ndvi_client = TileClient('ndvi.tif') + bui_client = TileClient("bui.tif") + ndvi_client = TileClient("ndvi.tif") # Create ipyleaflet tile layer from that server - tile_info = f' DE Africa Sentinel-2 Annual GeoMAD {ds.time.dt.year.values} ' - bui = get_leaflet_tile_layer(bui_client, nodata=np.nan, name='Built up area', colormap='Reds', vmin=-1, vmax=1, attribution=tile_info) - ndvi = get_leaflet_tile_layer(ndvi_client, nodata=np.nan, name='Vegetation', colormap='Greens', vmin=0, vmax=1) - - #Openstreet basemap + tile_info = f" DE Africa Sentinel-2 Annual GeoMAD {ds.time.dt.year.values} " + bui = get_leaflet_tile_layer( + bui_client, + nodata=np.nan, + name="Built up area", + colormap="Reds", + vmin=-1, + vmax=1, + attribution=tile_info, + ) + ndvi = get_leaflet_tile_layer( + ndvi_client, nodata=np.nan, name="Vegetation", colormap="Greens", vmin=0, vmax=1 + ) + + # Openstreet basemap openstreet = basemap_to_tiles(basemaps.OpenStreetMap.Mapnik) openstreet.base = True - + # Create ipyleaflet map, add tile layer, and display - map_con = Map(center=bui_client.center(), zoom=bui_client.default_zoom+2) - - control = LayersControl(position='topright', collapsed=False) - - legend = LegendControl({"Built-up Area":"#ff8533", "\n":"#fff", "Vegetation":"#34a832", " ":"#fff", "Water body":"#1d18c4"}, - title = "Legend", position = "bottomleft") - - #add the created layer and control to the map + map_con = Map(center=bui_client.center(), zoom=bui_client.default_zoom + 2) + + control = LayersControl(position="topright", collapsed=False) + + legend = LegendControl( + { + "Built-up Area": "#ff8533", + "\n": "#fff", + "Vegetation": "#34a832", + " ": "#fff", + "Water body": "#1d18c4", + }, + title="Legend", + position="bottomleft", + ) + + # add the created layer and control to the map map_con.add(bui) map_con.add(ndvi) map_con.add(geo_data) diff --git a/Tools/pyproject.toml b/Tools/pyproject.toml index b3e8479a..37323028 100644 --- a/Tools/pyproject.toml +++ b/Tools/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "deafrica-tools" # reflect version changes in deafrica_tools/__init__.py -version = "2.4.7" +version = "2.4.8" description = "Functions and algorithms for analysing Digital Earth Africa data." authors = [{name = "Digital Earth Africa", email = "systems@digitalearthafrica.org"}] maintainers = [{name = "Digital Earth Africa", email = "systems@digitalearthafrica.org"}]