diff --git a/pepsico/app_calc.py b/pepsico/app_calc.py index 169a54b5..f7ecb461 100644 --- a/pepsico/app_calc.py +++ b/pepsico/app_calc.py @@ -1,6 +1,7 @@ import xarray as xr import numpy as np from scipy.stats import norm +import pingrid #This is what we should need for the app @@ -22,30 +23,40 @@ def read_data( - scenario, model, variable, region, time_res="monthly", unit_convert=False + scenario, model, variable, region, time_res="monthly", unit_convert=False, + time_dim=True, years=None, ): - if region == "US-CA": - xslice = slice(-154, -45) - yslice = slice(60, 15) - elif region == "SAMER": - xslice = slice(-86, -34) - yslice = slice(16, -60) - elif region == "SASIA": - xslice = slice(59, 94) - yslice = slice(42, 7) - elif region == "Thailand": - xslice = slice(85, 115) - yslice = slice(28, 2) data = xr.open_zarr( f'/Data/data24/ISIMIP3b/InputData/climate/atmosphere/bias-adjusted/global' f'/{time_res}/{scenario}/{model}/zarr/{variable}' - )[variable].sel(X=xslice, Y=yslice) + )[variable] + if isinstance(region, tuple): + data = pingrid.sel_snap(data, region[0], region[1]) + else: + if region == "US-CA": + xslice = slice(-154, -45) + yslice = slice(60, 15) + elif region == "SAMER": + xslice = slice(-86, -34) + yslice = slice(16, -60) + elif region == "SASIA": + xslice = slice(59, 94) + yslice = slice(42, 7) + elif region == "Thailand": + xslice = slice(85, 115) + yslice = slice(28, 2) + data = data.sel(X=xslice, Y=yslice) + #Turns out that some models are centered on noon + if time_dim: + if years is not None : + data = data.sel(T=years) + if time_res == "daily" : + if data["T"][0].dt.hour == 12 : + data = data.assign_coords({"T" : data["T"] - np.timedelta64(12, "h")}) + else: + data = data.isel(T=0) if unit_convert : data = unit_conversion(data) - #Turns out that some models are centered on noon - if time_res == "daily" : - if data["T"][0].dt.hour == 12 : - data = data.assign_coords({"T" : data["T"] - np.timedelta64(12, "h")}) return data diff --git a/pepsico/layout_utilities.py b/pepsico/layout_utilities.py index 0e0c28e2..48c4c66b 100644 --- a/pepsico/layout_utilities.py +++ b/pepsico/layout_utilities.py @@ -122,7 +122,7 @@ def map( "margin-top":"3px", "margin-bottom":"3px", }, ), type="dot"), - dcc.Loading(dlf.Map( + dcc.Loading(dlf.MapContainer( [ dlf.LayersControl( id="layers_control", position=layers_control_position @@ -151,7 +151,7 @@ def map( ), ], id="map", - center=(0,0), + center=[0, 0], zoom=default_zoom, style={"width": "100%", "height": "50vh"}, ), type="dot"), diff --git a/pepsico/maproom_utilities.py b/pepsico/maproom_utilities.py index 10924d65..be6dbadb 100644 --- a/pepsico/maproom_utilities.py +++ b/pepsico/maproom_utilities.py @@ -246,7 +246,7 @@ def make_adm_overlay( dlf.GeoJSON( id=border_id, data=adm_geojson, - options={ + style={ "fill": True, "color": adm_color, "weight": adm_weight, @@ -259,7 +259,7 @@ def make_adm_overlay( ) -def initialize_map(data): +def initialize_map(data, zoom): """ Map initialization based on `data` spatial domain @@ -267,6 +267,8 @@ def initialize_map(data): ---------- data: xr.DataArray spatial data of which longitude and latitude coordinates are X and Y + zoom: int + dash-leaglet zoom value Returns ------- @@ -275,8 +277,8 @@ def initialize_map(data): control, center of the map coordinates values as a list """ center_of_the_map = [ - ((data["Y"][int(data["Y"].size/2)].values)), - ((data["X"][int(data["X"].size/2)].values)), + data["Y"][int(data["Y"].size/2)].values.item(), + data["X"][int(data["X"].size/2)].values.item(), ] lat_res = (data["Y"][0 ]- data["Y"][1]).values lat_min = str((data["Y"][-1] - lat_res/2).values) @@ -289,7 +291,7 @@ def initialize_map(data): return ( lat_min, lat_max, lat_label, lon_min, lon_max, lon_label, - center_of_the_map + {"center": center_of_the_map, "zoom": zoom, "transition": "flyTo"}, ) @@ -303,7 +305,7 @@ def picked_location(data, initialization_cases, click_lat_lng, latitude, longitu spatial data of which longitude and latitude coordinates are X and Y initialization_cases: list[str] list of Input of which changes reinitialize the map - click_lat_lng: list[str] + click_lat_lng: dict dlf Input from clicking map (lat and lon) latitude: str Input from latitude pick a point control @@ -318,8 +320,8 @@ def picked_location(data, initialization_cases, click_lat_lng, latitude, longitu lng = data["X"][int(data["X"].size/2)].values else: if dash.ctx.triggered_id == "map": - lat = click_lat_lng[0] - lng = click_lat_lng[1] + lat = click_lat_lng["lat"] + lng = click_lat_lng["lng"] else: lat = latitude lng = longitude diff --git a/pepsico/proj_wwc/maproom.py b/pepsico/proj_wwc/maproom.py index 9bdab37c..59e3d5d5 100644 --- a/pepsico/proj_wwc/maproom.py +++ b/pepsico/proj_wwc/maproom.py @@ -44,8 +44,7 @@ def register(FLASK, config): Output("lng_input", "min"), Output("lng_input", "max"), Output("lng_input_tooltip", "children"), - Output("map", "center"), - Output("map", "zoom"), + Output("map", "viewport"), Input("region", "value"), Input("location", "pathname"), ) @@ -53,9 +52,11 @@ def initialize(region, path): scenario = "ssp126" model = "GFDL-ESM4" variable = "pr" - data = ac.read_data(scenario, model, variable, region, time_res="daily") + data = ac.read_data( + scenario, model, variable, region, time_res="daily", time_dim=False + ) zoom = {"US-CA": 4, "Thailand": 5} - return mru.initialize_map(data) + (zoom[region],) + return mru.initialize_map(data, zoom[region]) @APP.callback( @@ -63,17 +64,22 @@ def initialize(region, path): Output("lat_input", "value"), Output("lng_input", "value"), Input("submit_lat_lng","n_clicks"), - Input("map", "click_lat_lng"), + Input("map", "clickData"), Input("region", "value"), State("lat_input", "value"), State("lng_input", "value"), ) def pick_location(n_clicks, click_lat_lng, region, latitude, longitude): # Reading + click_lat_lng = ( + click_lat_lng["latlng"] if click_lat_lng is not None else None + ) scenario = "ssp126" model = "GFDL-ESM4" variable = "pr" - data = ac.read_data(scenario, model, variable, region, time_res="daily") + data = ac.read_data( + scenario, model, variable, region, time_res="daily", time_dim=False + ) initialization_cases = ["region"] return mru.picked_location( data, initialization_cases, click_lat_lng, latitude, longitude @@ -112,7 +118,7 @@ def select_var(variable): def local_data( - lat, lng, region, + lat, lng, model, variable, start_day, start_month, end_day, end_month, frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, dry_spell, @@ -121,6 +127,7 @@ def local_data( "GFDL-ESM4", "IPSL-CM6A-LR", "MPI-ESM1-2-HR","MRI-ESM2-0", "UKESM1-0-LL" ] data_var = select_var(variable) + region = (lat, lng) data_ds = xr.concat([xr.Dataset({ "histo" : ac.read_data( "historical", m, data_var, region, @@ -150,11 +157,6 @@ def local_data( #would mean something happened to that data data_ds = missing_ds error_msg="Data missing for this model or variable" - try: - data_ds = pingrid.sel_snap(data_ds, lat, lng) - except KeyError: - data_ds = missing_ds - error_msg="Grid box out of data domain" if error_msg == None : data_ds = ac.seasonal_wwc( ac.daily_tobegroupedby_season( @@ -250,7 +252,7 @@ def send_data_as_csv( dry_spell = int(dry_spell) model = "Multi-Model-Average" data_ds, error_msg = local_data( - lat, lng, region, model, variable, + lat, lng, model, variable, start_day, start_month, end_day, end_month, frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, dry_spell, @@ -299,78 +301,93 @@ def local_plots( ): lat = marker_pos[0] lng = marker_pos[1] - start_day = int(start_day) - end_day = int(end_day) - start_month = ac.strftimeb2int(start_month) - end_month = ac.strftimeb2int(end_month) - frost_threshold = float(frost_threshold) - wet_threshold = float(wet_threshold) - hot_threshold = float(hot_threshold) - warm_nights_spell = int(warm_nights_spell) - dry_spell = int(dry_spell) - data_ds, error_msg = local_data( - lat, lng, region, model, variable, - start_day, start_month, end_day, end_month, - frost_threshold, wet_threshold, hot_threshold, - warm_nights_spell, dry_spell, - ) - if error_msg != None : - local_graph = pingrid.error_fig(error_msg) - else : - if (end_month < start_month) : - start_format = "%d %b %Y - " - else: - start_format = "%d %b-" - local_graph = pgo.Figure() - data_color = { - "histo": "blue", "picontrol": "green", - "ssp126": "yellow", "ssp370": "orange", "ssp585": "red", - } - lng_units = "˚E" if (lng >= 0) else "˚W" - lat_units = "˚N" if (lat >= 0) else "˚S" - for var in data_ds.data_vars: - local_graph.add_trace(plot_ts( - data_ds[var].mean("M", keep_attrs=True), var, data_color[var], - start_format, data_ds[var].attrs["units"] - )) - add_period_shape( - local_graph, - data_ds, - start_year_ref, - end_year_ref, - "blue", - "RoyalBlue", - "reference period", - ) - add_period_shape( - local_graph, - data_ds, - start_year, - end_year, - "LightPink", - "Crimson", - "projected period", + if region == "US-CA": + xslice = (-154, -45) + yslice = (60, 15) + elif region == "SAMER": + xslice = (-86, -34) + yslice = (16, -60) + elif region == "SASIA": + xslice = (59, 94) + yslice = (42, 7) + elif region == "Thailand": + xslice = (85, 115) + yslice = (28, 2) + if lat > yslice[0] or lat < yslice[1] or lng < xslice[0] or lng > xslice[1] : + local_graph = pingrid.error_fig(error_msg="Grid box out of data domain") + else: + start_day = int(start_day) + end_day = int(end_day) + start_month = ac.strftimeb2int(start_month) + end_month = ac.strftimeb2int(end_month) + frost_threshold = float(frost_threshold) + wet_threshold = float(wet_threshold) + hot_threshold = float(hot_threshold) + warm_nights_spell = int(warm_nights_spell) + dry_spell = int(dry_spell) + data_ds, error_msg = local_data( + lat, lng, model, variable, + start_day, start_month, end_day, end_month, + frost_threshold, wet_threshold, hot_threshold, + warm_nights_spell, dry_spell, ) - local_graph.update_layout( - xaxis_title="Time", - yaxis_title=( - # Compared to the monthly case, I presume that groupby dropped - # the long_name, which is fine since it's indeed transformed. Can - # consider resetting it at some point in the calculation - #f'{data_ds["histo"].attrs["long_name"]} ' - f'({data_ds["histo"].attrs["units"]})' - ), - title={ - "text": ( - f'{data_ds["histo"]["T"].dt.strftime("%d %b")[0].values}-' - f'{data_ds["histo"]["seasons_ends"].dt.strftime("%d %b")[0].values}' - f' {variable} from model {model} ' - f'at ({abs(lat)}{lat_units}, {abs(lng)}{lng_units})' + if error_msg != None : + local_graph = pingrid.error_fig(error_msg) + else: + if (end_month < start_month) : + start_format = "%d %b %Y - " + else: + start_format = "%d %b-" + local_graph = pgo.Figure() + data_color = { + "histo": "blue", "picontrol": "green", + "ssp126": "yellow", "ssp370": "orange", "ssp585": "red", + } + lng_units = "˚E" if (lng >= 0) else "˚W" + lat_units = "˚N" if (lat >= 0) else "˚S" + for var in data_ds.data_vars: + local_graph.add_trace(plot_ts( + data_ds[var].mean("M", keep_attrs=True), var, data_color[var], + start_format, data_ds[var].attrs["units"] + )) + add_period_shape( + local_graph, + data_ds, + start_year_ref, + end_year_ref, + "blue", + "RoyalBlue", + "reference period", + ) + add_period_shape( + local_graph, + data_ds, + start_year, + end_year, + "LightPink", + "Crimson", + "projected period", + ) + local_graph.update_layout( + xaxis_title="Time", + yaxis_title=( + # Compared to the monthly case, I presume that groupby dropped + # the long_name, which is fine since it's indeed transformed. Can + # consider resetting it at some point in the calculation + #f'{data_ds["histo"].attrs["long_name"]} ' + f'({data_ds["histo"].attrs["units"]})' ), - "font": dict(size=14), - }, - margin=dict(l=30, r=30, t=30, b=30), - ) + title={ + "text": ( + f'{data_ds["histo"]["T"].dt.strftime("%d %b")[0].values}-' + f'{data_ds["histo"]["seasons_ends"].dt.strftime("%d %b")[0].values}' + f' {variable} from model {model} ' + f'at ({abs(lat)}{lat_units}, {abs(lng)}{lng_units})' + ), + "font": dict(size=14), + }, + margin=dict(l=30, r=30, t=30, b=30), + ) return local_graph @@ -484,9 +501,8 @@ def seasonal_change( ac.daily_tobegroupedby_season( ac.read_data( "historical", m, data_var, region, - time_res="daily", unit_convert=True - ).sel( - T=slice(str(start_year_ref), str(end_year_ref)) + time_res="daily", unit_convert=True, + years=slice(str(start_year_ref), str(end_year_ref)), ).to_dataset(), start_day, start_month, end_day, end_month, ), @@ -499,9 +515,8 @@ def seasonal_change( ac.daily_tobegroupedby_season( ac.read_data( scenario, m, data_var, region, - time_res="daily", unit_convert=True - ).sel( - T=slice(str(start_year), str(end_year)) + time_res="daily", unit_convert=True, + years=slice(str(start_year), str(end_year)), ).to_dataset(), start_day, start_month, end_day, end_month, ), diff --git a/pepsico/projections/maproom.py b/pepsico/projections/maproom.py index 2a9d6107..3d35d99b 100644 --- a/pepsico/projections/maproom.py +++ b/pepsico/projections/maproom.py @@ -44,8 +44,7 @@ def register(FLASK, config): Output("lng_input", "min"), Output("lng_input", "max"), Output("lng_input_tooltip", "children"), - Output("map", "center"), - Output("map", "zoom"), + Output("map", "viewport"), Input("region", "value"), Input("location", "pathname"), ) @@ -55,7 +54,7 @@ def initialize(region, path): variable = "pr" data = ac.read_data(scenario, model, variable, region) zoom = {"SAMER": 3, "US-CA": 4, "SASIA": 4, "Thailand": 5} - return mru.initialize_map(data) + (zoom[region],) + return mru.initialize_map(data, zoom[region]) @APP.callback( @@ -63,13 +62,16 @@ def initialize(region, path): Output("lat_input", "value"), Output("lng_input", "value"), Input("submit_lat_lng","n_clicks"), - Input("map", "click_lat_lng"), + Input("map", "clickData"), Input("region", "value"), State("lat_input", "value"), State("lng_input", "value"), ) def pick_location(n_clicks, click_lat_lng, region, latitude, longitude): # Reading + click_lat_lng = ( + click_lat_lng["latlng"] if click_lat_lng is not None else None + ) scenario = "ssp126" model = "GFDL-ESM4" variable = "pr" diff --git a/pingrid/impl.py b/pingrid/impl.py index 2eddb839..8d42f099 100644 --- a/pingrid/impl.py +++ b/pingrid/impl.py @@ -94,14 +94,12 @@ def sel_snap(spatial_array, lat, lng, dim_y="Y", dim_x="X"): def error_fig(error_msg="error"): return pgo.Figure().add_annotation( - x=2, - y=2, + xref="paper", + yref="paper", text=error_msg, font=dict(family="sans serif", size=30, color="crimson"), showarrow=False, - yshift=10, - xshift=60, - ) + ).update_xaxes(visible=False).update_yaxes(visible=False) FuncInterp2d = Callable[[Iterable[np.ndarray]], np.ndarray]