mirror of
https://git.victorphan.net/basketballcantho/CSIROBoeingPhase5-Vietnam.git
synced 2026-08-05 05:43:10 +07:00
389 lines
13 KiB
Python
389 lines
13 KiB
Python
"""
|
|
Wetlands insight tool widget, which can be used to run an interactive
|
|
version of the wetlands insight tool.
|
|
"""
|
|
|
|
# Import required packages
|
|
|
|
# Force GeoPandas to use Shapely instead of PyGEOS
|
|
# In a future release, GeoPandas will switch to using Shapely by default.
|
|
import os
|
|
os.environ['USE_PYGEOS'] = '0'
|
|
|
|
import datacube
|
|
import warnings
|
|
import seaborn as sns
|
|
import matplotlib.pyplot as plt
|
|
from datacube.utils.geometry import CRS
|
|
from ipyleaflet import (
|
|
WMSLayer,
|
|
basemaps,
|
|
basemap_to_tiles,
|
|
Map,
|
|
DrawControl,
|
|
WidgetControl,
|
|
LayerGroup,
|
|
LayersControl,
|
|
)
|
|
from traitlets import Unicode
|
|
from ipywidgets import (
|
|
GridspecLayout,
|
|
Button,
|
|
Layout,
|
|
HBox,
|
|
VBox,
|
|
HTML,
|
|
Output,
|
|
)
|
|
import json
|
|
import geopandas as gpd
|
|
from io import BytesIO
|
|
from dask.diagnostics import ProgressBar
|
|
|
|
import deafrica_tools
|
|
from deafrica_tools.dask import create_local_dask_cluster
|
|
from deafrica_tools.wetlands import WIT_drill
|
|
import deafrica_tools.app.widgetconstructors as deawidgets
|
|
|
|
|
|
def make_box_layout():
|
|
return Layout(
|
|
#border='solid 1px black',
|
|
margin='0px 10px 10px 0px',
|
|
padding='5px 5px 5px 5px',
|
|
width='100%',
|
|
height='100%',
|
|
)
|
|
|
|
|
|
def create_expanded_button(description, button_style):
|
|
return Button(
|
|
description=description,
|
|
button_style=button_style,
|
|
layout=Layout(width="auto", height="auto"),
|
|
)
|
|
|
|
|
|
class wit_app(HBox):
|
|
def __init__(self, lang=None):
|
|
super().__init__()
|
|
|
|
deafrica_tools.set_lang(lang)
|
|
|
|
##########################################################
|
|
# INITIAL ATTRIBUTES #
|
|
|
|
self.startdate = "2020-01-01"
|
|
self.enddate = "2020-03-01"
|
|
self.mingooddata = 0.0
|
|
self.resamplingfreq = "1M"
|
|
self.out_csv = "example_WIT.csv"
|
|
self.out_plot = "example_WIT.png"
|
|
self.product_list = [
|
|
(_("None"), "none"),
|
|
(_("ESRI World Imagery"), "esri_world_imagery"),
|
|
(_("Sentinel-2 Geomedian"), "gm_s2_annual"),
|
|
(_("Water Observations from Space"), "wofs_ls_summary_annual"),
|
|
|
|
]
|
|
self.product = self.product_list[0][1]
|
|
self.product_year = "2020-01-01"
|
|
self.target = None
|
|
self.action = None
|
|
self.gdf_drawn = None
|
|
|
|
##########################################################
|
|
# HEADER FOR APP #
|
|
|
|
# Create the Header widget
|
|
header_title_text = _("Wetlands Insight Tool")
|
|
instruction_text = _("Select parameters and AOI")
|
|
self.header = deawidgets.create_html(f"<h3>{header_title_text}</h3><p>{instruction_text}</p>")
|
|
self.header.layout = make_box_layout()
|
|
|
|
##########################################################
|
|
# HANDLER FUNCTION FOR DRAW CONTROL #
|
|
|
|
# Define the action to take once something is drawn on the map
|
|
def update_geojson(target, action, geo_json):
|
|
|
|
self.action = action
|
|
|
|
json_data = json.dumps(geo_json)
|
|
binary_data = json_data.encode()
|
|
io = BytesIO(binary_data)
|
|
io.seek(0)
|
|
|
|
gdf = gpd.read_file(io)
|
|
gdf.crs = "EPSG:4326"
|
|
self.gdf_drawn = gdf
|
|
|
|
gdf_drawn_epsg6933 = gdf.copy().to_crs("EPSG:6933")
|
|
m2_per_km2 = 10 ** 6
|
|
area = gdf_drawn_epsg6933.area.values[0] / m2_per_km2
|
|
polyarea_label = _('Total polygon area')
|
|
polyarea_text = f"<p><b>{polyarea_label}</b>: {area:.2f} km<sup>2</sup></p>"
|
|
|
|
if area <= 3000:
|
|
confirmation_text = '<p style="color:#33cc33;">' + _('Area falls within recommended limit') + '</p>'
|
|
self.header.value = header_title_text + polyarea_text + confirmation_text
|
|
else:
|
|
warning_text = '<p style="color:#ff5050;">' + _('Area is too large, please update your polygon') + '</p>'
|
|
self.header.value = header_title_text + polyarea_text + warning_text
|
|
|
|
##########################################################
|
|
# WIDGETS FOR APP OUTPUTS #
|
|
|
|
self.dask_client = Output(layout=make_box_layout())
|
|
self.progress_bar = Output(layout=make_box_layout())
|
|
self.wit_plot = Output(layout=make_box_layout())
|
|
self.progress_header = deawidgets.create_html("")
|
|
|
|
##########################################################
|
|
# MAP WIDGET, DRAWING TOOLS, WMS LAYERS #
|
|
|
|
# Create drawing tools
|
|
desired_drawtools = ['rectangle', 'polygon']
|
|
draw_control = deawidgets.create_drawcontrol(desired_drawtools)
|
|
|
|
# Begin by displaying an empty layer group, and update the group with desired WMS on interaction.
|
|
self.deafrica_layers = LayerGroup(layers=())
|
|
self.deafrica_layers.name = _('Map Overlays')
|
|
|
|
# Create map widget
|
|
self.m = deawidgets.create_map()
|
|
|
|
self.m.layout = make_box_layout()
|
|
|
|
# Add tools to map widget
|
|
self.m.add_control(draw_control)
|
|
self.m.add_layer(self.deafrica_layers)
|
|
|
|
# Store current basemap for future use
|
|
self.basemap = self.m.basemap
|
|
|
|
##########################################################
|
|
# WIDGETS FOR APP CONTROLS #
|
|
|
|
# Create parameter widgets
|
|
startdate_picker = deawidgets.create_datepicker()
|
|
enddate_picker = deawidgets.create_datepicker()
|
|
min_good_data = deawidgets.create_boundedfloattext(self.mingooddata, 0.0, 1.0, 0.05)
|
|
resampling_freq = deawidgets.create_inputtext(self.resamplingfreq, self.resamplingfreq)
|
|
output_csv = deawidgets.create_inputtext(self.out_csv, self.out_csv)
|
|
output_plot = deawidgets.create_inputtext(self.out_plot, self.out_plot)
|
|
deaoverlay_dropdown = deawidgets.create_dropdown(self.product_list, self.product_list[0][1])
|
|
run_button = create_expanded_button(_("Run"), "info")
|
|
|
|
##########################################################
|
|
# COLLECTION OF ALL APP CONTROLS #
|
|
|
|
parameter_selection = VBox(
|
|
[
|
|
HTML("<b>" + _("Map Overlay:") + "</b>"),
|
|
deaoverlay_dropdown,
|
|
HTML("<b>" + _("Start Date:") + "</b>"),
|
|
startdate_picker,
|
|
HTML("<b>" + _("End Date:") + "</b>"),
|
|
enddate_picker,
|
|
HTML("<b>" + _("Minimum Good Data:") + "</b>"),
|
|
min_good_data,
|
|
HTML("<b>" + _("Resampling Frequency:") + "</b>"),
|
|
resampling_freq,
|
|
HTML("<b>" + _("Output CSV:") + "</b>"),
|
|
output_csv,
|
|
HTML("<b>" + _("Output Plot:") + "</b>"),
|
|
output_plot,
|
|
]
|
|
)
|
|
parameter_selection.layout = make_box_layout()
|
|
|
|
##########################################################
|
|
# SPECIFICATION OF APP LAYOUT #
|
|
|
|
# Create the layout #[rowspan, colspan]
|
|
grid = GridspecLayout(11, 10, height="1100px", width="auto")
|
|
|
|
# Controls and Status
|
|
grid[0, :] = self.header
|
|
grid[1:6, 0:2] = parameter_selection
|
|
grid[6, 0:2] = run_button
|
|
|
|
# Dask and Progress info
|
|
grid[1, 7:] = self.dask_client
|
|
grid[2:7, 7:] = self.progress_bar
|
|
|
|
# Map
|
|
grid[1:7, 2:7] = self.m
|
|
|
|
# Plot
|
|
grid[7:, :] = self.wit_plot
|
|
|
|
# Display using HBox children attribute
|
|
self.children = [grid]
|
|
|
|
##########################################################
|
|
# SPECIFICATION UPDATE FUNCTIONS FOR EACH WIDGET #
|
|
|
|
# Run update functions whenever various widgets are changed.
|
|
startdate_picker.observe(self.update_startdate, "value")
|
|
enddate_picker.observe(self.update_enddate, "value")
|
|
min_good_data.observe(self.update_mingooddata, "value")
|
|
resampling_freq.observe(self.update_resamplingfreq, "value")
|
|
output_csv.observe(self.update_outputcsv, "value")
|
|
output_plot.observe(self.update_outputplot, "value")
|
|
deaoverlay_dropdown.observe(self.update_deaoverlay, "value")
|
|
run_button.on_click(self.run_app)
|
|
draw_control.on_draw(update_geojson)
|
|
|
|
##############################################################
|
|
# DEFINITION OF ALL UPDATE FUNCTIONS #
|
|
|
|
# set the start date to the new edited date
|
|
def update_startdate(self, change):
|
|
self.startdate = change.new
|
|
|
|
# set the end date to the new edited date
|
|
def update_enddate(self, change):
|
|
self.enddate = change.new
|
|
|
|
# set the min good data
|
|
def update_mingooddata(self, change):
|
|
self.mingooddata = change.new
|
|
|
|
# set the resampling frequency
|
|
def update_resamplingfreq(self, change):
|
|
self.resamplingfreq = change.new
|
|
|
|
# set the output csv
|
|
def update_outputcsv(self, change):
|
|
self.out_csv = change.new
|
|
|
|
# set the output plot
|
|
def update_outputplot(self, change):
|
|
self.out_plot = change.new
|
|
|
|
# Update product
|
|
def update_deaoverlay(self, change):
|
|
|
|
self.product = change.new
|
|
|
|
if self.product == "none":
|
|
self.deafrica_layers.clear_layers()
|
|
elif self.product == "esri_world_imagery":
|
|
self.deafrica_layers.clear_layers()
|
|
layer = basemap_to_tiles(basemaps.Esri.WorldImagery)
|
|
self.deafrica_layers.add_layer(layer)
|
|
else:
|
|
self.deafrica_layers.clear_layers()
|
|
layer = deawidgets.create_dea_wms_layer(self.product, self.product_year)
|
|
self.deafrica_layers.add_layer(layer)
|
|
|
|
def run_app(self, change):
|
|
|
|
# Clear progress bar and output areas before running
|
|
self.dask_client.clear_output()
|
|
self.progress_bar.clear_output()
|
|
self.wit_plot.clear_output()
|
|
|
|
# Connect to datacube database
|
|
dc = datacube.Datacube(app="wetland_app")
|
|
|
|
# Configure local dask cluster
|
|
with self.dask_client:
|
|
client = create_local_dask_cluster(
|
|
return_client=True, display_client=True
|
|
)
|
|
|
|
# Set any defaults
|
|
TCW_threshold = -0.035
|
|
dask_chunks = dict(x=1000, y=1000, time=1)
|
|
|
|
#check resampling freq
|
|
if self.resamplingfreq == 'None':
|
|
rsf = None
|
|
else:
|
|
rsf = self.resamplingfreq
|
|
|
|
self.progress_header.value = f"<h3>"+_("Progress")+"</h3>"
|
|
|
|
# run wetlands polygon drill
|
|
with self.progress_bar:
|
|
# with ProgressBar():
|
|
warnings.filterwarnings("ignore")
|
|
try:
|
|
df = WIT_drill(
|
|
gdf=self.gdf_drawn,
|
|
time=(self.startdate, self.enddate),
|
|
min_gooddata=self.mingooddata,
|
|
resample_frequency=rsf,
|
|
TCW_threshold=TCW_threshold,
|
|
export_csv=self.out_csv,
|
|
dask_chunks=dask_chunks,
|
|
verbose=False,
|
|
verbose_progress=True,
|
|
)
|
|
print(_("WIT complete"))
|
|
except AttributeError:
|
|
print(_("No polygon selected"))
|
|
|
|
# close down the dask client
|
|
client.shutdown()
|
|
|
|
# save the csv
|
|
if self.out_csv:
|
|
df.to_csv(self.out_csv, index_label="Datetime")
|
|
|
|
# ---Plotting------------------------------
|
|
|
|
with self.wit_plot:
|
|
|
|
fontsize = 17
|
|
plt.rcParams.update({"font.size": fontsize})
|
|
# set up color palette
|
|
pal = [
|
|
sns.xkcd_rgb["cobalt blue"],
|
|
sns.xkcd_rgb["neon blue"],
|
|
sns.xkcd_rgb["grass"],
|
|
sns.xkcd_rgb["beige"],
|
|
sns.xkcd_rgb["brown"],
|
|
]
|
|
|
|
# make a stacked area plot
|
|
plt.close("all")
|
|
|
|
fig, ax = plt.subplots(constrained_layout=True, figsize=(20, 6))
|
|
|
|
ax.stackplot(
|
|
df.index,
|
|
df.wofs_area_percent,
|
|
df.wet_percent,
|
|
df.green_veg_percent,
|
|
df.dry_veg_percent,
|
|
df.bare_soil_percent,
|
|
labels=[
|
|
_("open water"),
|
|
_("wet"),
|
|
_("green veg"),
|
|
_("dry veg"),
|
|
_("bare soil"),
|
|
],
|
|
colors=pal,
|
|
alpha=0.6,
|
|
)
|
|
|
|
# set axis limits to the min and max
|
|
ax.set_ylim(0, 100)
|
|
ax.set_xlim(df.index[0], df.index[-1])
|
|
ax.tick_params(axis="x", labelsize=fontsize)
|
|
|
|
# add a legend and a tight plot box
|
|
ax.legend(loc="lower left", framealpha=0.6)
|
|
ax.set_title(_("Percentage Fractional Cover, Wetness, and Water"))
|
|
# plt.tight_layout()
|
|
plt.show()
|
|
|
|
if self.out_plot:
|
|
# save the figure
|
|
fig.savefig(f"{self.out_plot}")
|