Upload 2 files
Browse files- Vitalens.py +754 -290
- printingReport.py +229 -0
Vitalens.py
CHANGED
@@ -5,9 +5,12 @@ import numpy as np
|
|
5 |
import fiona
|
6 |
from bokeh.models.formatters import PrintfTickFormatter
|
7 |
import folium
|
8 |
-
|
|
|
|
|
9 |
import branca
|
10 |
from functools import partial
|
|
|
11 |
|
12 |
# Styling
|
13 |
globalCss_route= "Stylesheet.css"
|
@@ -26,6 +29,34 @@ cssStyle = ['''
|
|
26 |
--panel-primary-color: #151931 !important;
|
27 |
--panel-background-color: #f2f2ed !important;
|
28 |
--panel-on-background-color: #151931 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
}
|
30 |
|
31 |
#sidebar, #main {
|
@@ -40,6 +71,11 @@ hr.dashed {
|
|
40 |
.title {
|
41 |
font-weight: 600 !important;
|
42 |
}
|
|
|
|
|
|
|
|
|
|
|
43 |
.bk-btn {
|
44 |
border-radius: 0.5em !important;
|
45 |
}
|
@@ -51,7 +87,7 @@ hr.dashed {
|
|
51 |
.bk-btn-group {
|
52 |
height: 100%;
|
53 |
display: flex;
|
54 |
-
flex-wrap:
|
55 |
align-items: center;
|
56 |
}
|
57 |
|
@@ -70,34 +106,50 @@ hr.dashed {
|
|
70 |
width: fit-content;
|
71 |
}
|
72 |
|
73 |
-
.bk-btn-warning{
|
74 |
-
|
75 |
-
font-weight: 400 !important;
|
76 |
-
font-size: small !important;
|
77 |
-
line-height: 1;
|
78 |
-
margin: 3px 3px;
|
79 |
-
padding: 5px 10px !important;
|
80 |
-
transition: background 0.8s;
|
81 |
-
width: fit-content;
|
82 |
}
|
83 |
|
84 |
.accordion-header button{
|
85 |
color: #151931;
|
86 |
background-color: #B4BFE4;
|
87 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
'''
|
89 |
]
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
# Initialize extensions
|
92 |
pn.config.global_css = cssStyle
|
93 |
pn.config.css_files = cssStyle
|
94 |
pn.config.loading_spinner = 'petal'
|
95 |
pn.extension(sizing_mode="stretch_width")
|
96 |
pn.extension("plotly")
|
|
|
97 |
pn.extension("echarts")
|
98 |
pn.extension(
|
99 |
"tabulator", "ace", css_files=["https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"]
|
100 |
)
|
|
|
|
|
101 |
|
102 |
# Load the GeoPackage file
|
103 |
GPKG_FILE = "./Assets/Thematic_Data.gpkg"
|
@@ -106,6 +158,8 @@ layers = fiona.listlayers(GPKG_FILE) # Load all layers
|
|
106 |
# Get Wells Attributes
|
107 |
wells = gpd.read_file(GPKG_FILE, layer="Well_Capacity_Cost")
|
108 |
industrial = gpd.read_file(GPKG_FILE, layer="Industrial_Extraction")
|
|
|
|
|
109 |
|
110 |
# Convert the capacity columns to numeric, setting errors='coerce' will replace non-numeric values with NaN
|
111 |
wells["Permit__Mm3_per_jr_"] = pd.to_numeric(
|
@@ -137,6 +191,7 @@ active_wells_df = gpd.GeoDataFrame(
|
|
137 |
"Max_permit": wells["Permit__Mm3_per_jr_"],
|
138 |
"Balance area": wells["Balansgebied"],
|
139 |
"Active": [True] * len(wells),
|
|
|
140 |
"Value": wells["Extraction_2023__Mm3_per_jr_"],
|
141 |
"OPEX_m3": wells["totOpex_m3"],
|
142 |
"Drought_m3": wells["DroughtDamage_EUR_m3"],
|
@@ -146,14 +201,30 @@ active_wells_df = gpd.GeoDataFrame(
|
|
146 |
* wells["Extraction_2023__Mm3_per_jr_"]
|
147 |
* 1000000,
|
148 |
"OPEX": wells["totOpex_m3"] * wells["Extraction_2023__Mm3_per_jr_"] * 1000000,
|
|
|
149 |
"geometry": wells["geometry"],
|
150 |
}
|
151 |
)
|
152 |
active_wells_df.astype({"Num_Wells": "int32", "Ownership": "int32"}, copy=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
|
154 |
yearCal = 2022
|
155 |
growRate = 0.0062
|
156 |
-
|
|
|
|
|
157 |
|
158 |
# Get Destination Attributes
|
159 |
hexagons = gpd.read_file(GPKG_FILE, layer="H3_Lvl8")
|
@@ -183,7 +254,7 @@ hexagons_filterd = gpd.GeoDataFrame(
|
|
183 |
"Pop2022": hexagons["Pop_2022"],
|
184 |
"Current Pop": hexagons["Pop_2022"],
|
185 |
"Industrial Demand": hexagons["Ind_Demand"],
|
186 |
-
"Water Demand": hexagons["Pop_2022"] *
|
187 |
"Type": hexagons["Type_T"],
|
188 |
"Source_Name": hexagons["Source_Name"],
|
189 |
"geometry": hexagons["geometry"],
|
@@ -192,6 +263,8 @@ hexagons_filterd = gpd.GeoDataFrame(
|
|
192 |
|
193 |
balance_areas= hexagons_filterd.dissolve(by="Balance Area", as_index=False)
|
194 |
|
|
|
|
|
195 |
def calculate_total_extraction():
|
196 |
"""
|
197 |
Calculate the total water extraction from active wells.
|
@@ -199,11 +272,18 @@ def calculate_total_extraction():
|
|
199 |
Returns:
|
200 |
float: Total water extraction in Mm3/yr.
|
201 |
"""
|
202 |
-
|
|
|
203 |
return total
|
204 |
|
205 |
-
def
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
return total
|
208 |
|
209 |
def calculate_available():
|
@@ -216,9 +296,12 @@ def calculate_available():
|
|
216 |
total = (
|
217 |
active_wells_df[active_wells_df["Active"]==True]["Max_permit"].sum()
|
218 |
- active_wells_df[active_wells_df["Active"]==True]["Value"].sum()
|
219 |
-
)
|
220 |
-
|
221 |
-
|
|
|
|
|
|
|
222 |
|
223 |
def calculate_ownership():
|
224 |
"""
|
@@ -240,8 +323,24 @@ def calculate_total_OPEX():
|
|
240 |
Returns:
|
241 |
float: Total OPEX in million EUR/yr.
|
242 |
"""
|
|
|
|
|
243 |
total = (active_wells_df[active_wells_df["Active"]]["OPEX"]).sum()
|
244 |
-
return total
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
def calculate_total_OPEX_by_balance():
|
247 |
"""
|
@@ -254,13 +353,13 @@ def calculate_total_OPEX_by_balance():
|
|
254 |
active_wells_df[active_wells_df["Active"]].groupby("Balance area")["OPEX"].sum()
|
255 |
)/1000000
|
256 |
|
257 |
-
def update_balance_opex():
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
|
265 |
def calculate_total_envCost():
|
266 |
"""
|
@@ -298,7 +397,46 @@ def calculate_affected_Natura():
|
|
298 |
& (hexagons_filterd["Type"] == "Source and Restricted")
|
299 |
]
|
300 |
total = restricted.shape[0]
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
|
303 |
def calculate_total_CO2_cost():
|
304 |
"""
|
@@ -345,7 +483,7 @@ def toggle_well(event, well_name):
|
|
345 |
"""
|
346 |
active_wells_df.loc[active_wells_df["Name"] == well_name, "Active"] = event.new
|
347 |
update_indicators()
|
348 |
-
map_pane.object = update_layers()
|
349 |
|
350 |
def toggle_industrial(event, location):
|
351 |
"""
|
@@ -357,7 +495,7 @@ def toggle_industrial(event, location):
|
|
357 |
"""
|
358 |
industrial.loc[industrial["Location"] == location, "Active"] = event.new
|
359 |
update_indicators()
|
360 |
-
map_pane.object = update_layers()
|
361 |
|
362 |
def update_slider(event, well_name):
|
363 |
"""
|
@@ -388,7 +526,7 @@ def update_radio(event, well_name):
|
|
388 |
"""
|
389 |
current_value = wells.loc[wells["Name"] == well_name, "Extraction_2023__Mm3_per_jr_"].values[0]
|
390 |
max_value = wells.loc[wells["Name"] == well_name, "Permit__Mm3_per_jr_"].values[0]
|
391 |
-
agreement = wells.loc[wells["Name"]== well_name, "Agreement__Mm3_per_jr_"].values[0]
|
392 |
|
393 |
if event.new == "-10%":
|
394 |
new_value = current_value * 0.9
|
@@ -402,31 +540,41 @@ def update_radio(event, well_name):
|
|
402 |
new_value = current_value * 1.2
|
403 |
elif event.new == "Maximum Permit":
|
404 |
new_value = max_value
|
405 |
-
|
406 |
-
new_value = agreement
|
407 |
|
408 |
active_wells_df.loc[active_wells_df["Name"] == well_name, "Value"] = new_value
|
|
|
|
|
|
|
409 |
name_pane = active_wells[well_name]["name_pane"]
|
410 |
-
name_pane.object =
|
411 |
update_indicators()
|
412 |
|
413 |
def update_scenarios(event):
|
414 |
-
if event.new == "
|
415 |
Scenario1()
|
416 |
print('scenario 1 active')
|
417 |
-
if event.new == "
|
418 |
print('scenario 2 active')
|
419 |
Scenario2()
|
420 |
-
if event.new ==
|
421 |
-
|
422 |
-
|
423 |
-
hexagons_filterd["Pop2022"] * demand_capita * 365
|
424 |
-
) / 1000000
|
425 |
-
update_scenarioTitle("VITALENS - Current Situation 2024")
|
426 |
update_indicators()
|
427 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
|
429 |
-
def
|
430 |
"""
|
431 |
Update the well name display.
|
432 |
|
@@ -437,22 +585,53 @@ def update_well_Name(well_name):
|
|
437 |
str: Updated well name display.
|
438 |
"""
|
439 |
current_extraction = active_wells_df[active_wells_df["Name"]==well_name]["Value"].values[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
return f"{current_extraction:.2f} Mm\u00b3/yr"
|
441 |
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
#
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
456 |
|
457 |
def calculate_total_Demand():
|
458 |
"""
|
@@ -461,6 +640,10 @@ def calculate_total_Demand():
|
|
461 |
Returns:
|
462 |
float: Total water demand in Mm3/yr.
|
463 |
"""
|
|
|
|
|
|
|
|
|
464 |
total = ((hexagons_filterd["Water Demand"]).sum()) + (
|
465 |
(hexagons_filterd["Industrial Demand"]).sum()
|
466 |
)
|
@@ -503,22 +686,23 @@ def calculate_lzh_by_balance():
|
|
503 |
"""
|
504 |
lzh_by_balance = {}
|
505 |
balance_areas = active_wells_df["Balance area"].unique()
|
|
|
506 |
for area in balance_areas:
|
507 |
-
total_extraction = active_wells_df[
|
508 |
-
active_wells_df["Balance area"] == area
|
509 |
-
][ # Extraction per area
|
510 |
-
"Value"
|
511 |
].sum()
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
"Water Demand"
|
516 |
].sum()
|
|
|
517 |
lzh_by_balance[area] = (
|
518 |
round((total_extraction / total_demand) * 100, 2) if total_demand else 0
|
519 |
)
|
|
|
520 |
return lzh_by_balance
|
521 |
|
|
|
522 |
def update_balance_lzh_gauges():
|
523 |
"""
|
524 |
Update Leveringszekerheid gauges for balance areas.
|
@@ -567,7 +751,7 @@ popup_well = folium.GeoJsonPopup(
|
|
567 |
aliases=["Well Name", "Balance Area", "Extraction in Mm\u00b3/yr"],
|
568 |
)
|
569 |
popup_hex = folium.GeoJsonPopup(
|
570 |
-
fields=["
|
571 |
)
|
572 |
popup_industrial = folium.GeoJsonPopup(
|
573 |
fields=["Place", "Licensed", "Current_Extraction_2019"],
|
@@ -581,8 +765,8 @@ icon = folium.CustomIcon(
|
|
581 |
|
582 |
colormap = branca.colormap.LinearColormap(
|
583 |
["#caf0f8", "#90e0ef", "#00b4d8", "#0077b6", "#03045e"],
|
584 |
-
vmin=
|
585 |
-
vmax=
|
586 |
caption="Total water demand in Mm\u00b3/yr",
|
587 |
)
|
588 |
|
@@ -599,19 +783,16 @@ def calculate_centroid(coordinates):
|
|
599 |
polygon = Polygon(coordinates)
|
600 |
return polygon.centroid.y, polygon.centroid.x
|
601 |
|
602 |
-
def update_layers():
|
603 |
"""
|
604 |
Update the layers on the map.
|
605 |
|
606 |
Returns:
|
607 |
folium.Map: Updated Folium map.
|
608 |
"""
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
tiles="Cartodb Positron"
|
613 |
-
)
|
614 |
-
active = active_wells_df[active_wells_df["Active"]==True]
|
615 |
|
616 |
folium.GeoJson(
|
617 |
active,
|
@@ -621,28 +802,28 @@ def update_layers():
|
|
621 |
tooltip=folium.GeoJsonTooltip(fields=["Name"], aliases=["Well Name:"]),
|
622 |
marker=folium.Marker(
|
623 |
icon=folium.Icon(
|
624 |
-
icon_color="#
|
625 |
)
|
626 |
),
|
627 |
).add_to(m)
|
628 |
|
629 |
folium.GeoJson(
|
630 |
-
|
631 |
name="Industrial Water Extraction",
|
632 |
zoom_on_click=True,
|
633 |
popup=popup_industrial,
|
634 |
tooltip=folium.GeoJsonTooltip(fields=["Place"], aliases=["Place:"]),
|
635 |
marker=folium.Marker(
|
636 |
icon=folium.Icon(
|
637 |
-
icon_color="#d9534f", icon="industry", prefix="fa"
|
638 |
)
|
639 |
),
|
640 |
).add_to(m)
|
641 |
|
642 |
|
643 |
hex = folium.GeoJson(
|
644 |
-
|
645 |
-
name="
|
646 |
style_function=lambda x: {
|
647 |
"fillColor": (
|
648 |
colormap(x["properties"]["Water Demand"])
|
@@ -651,7 +832,7 @@ def update_layers():
|
|
651 |
),
|
652 |
"color": (
|
653 |
"darkgray"
|
654 |
-
if x["properties"]["
|
655 |
else "transparent"
|
656 |
),
|
657 |
"fillOpacity": 0.8,
|
@@ -661,6 +842,19 @@ def update_layers():
|
|
661 |
).add_to(m)
|
662 |
|
663 |
m.add_child(colormap)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
664 |
|
665 |
folium.GeoJson(
|
666 |
hexagons_filterd,
|
@@ -702,110 +896,145 @@ def update_layers():
|
|
702 |
show= False,
|
703 |
).add_to(m)
|
704 |
|
|
|
|
|
705 |
folium.GeoJson(
|
706 |
balance_areas,
|
707 |
name="Balance Areas",
|
708 |
style_function=lambda x: {
|
709 |
"fillColor": "transparent",
|
710 |
-
"color": "#
|
711 |
"weight": 3
|
712 |
},
|
713 |
show=True,
|
714 |
tooltip=folium.GeoJsonTooltip(fields=['Balance Area'], labels=True)
|
715 |
).add_to(m)
|
716 |
|
|
|
|
|
717 |
folium.LayerControl().add_to(m)
|
718 |
|
719 |
return m
|
720 |
|
|
|
|
|
|
|
|
|
|
|
|
|
721 |
active_scenarios = set()
|
722 |
text = ["## Scenario"]
|
723 |
|
724 |
|
725 |
def update_scenarioTitle(new_title):
|
726 |
global text
|
727 |
-
base_title = "
|
728 |
-
if Scenario_Button.value == "
|
729 |
-
if
|
730 |
text.remove("Accelerated Growth")
|
|
|
|
|
731 |
text.append(new_title)
|
732 |
-
if Scenario_Button.value == "
|
733 |
-
if
|
734 |
text.remove("Autonomous Growth")
|
|
|
|
|
735 |
text.append(new_title)
|
736 |
-
if Scenario_Button.value == "Current
|
737 |
if "Accelerated Growth" in text:
|
738 |
text.remove("Accelerated Growth")
|
739 |
if "Autonomous Growth" in text:
|
740 |
text.remove("Autonomous Growth")
|
741 |
-
|
742 |
-
|
|
|
|
|
743 |
app_title.object = " - ".join(text)
|
744 |
print (text)
|
745 |
|
746 |
|
747 |
def update_title(event):
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
# if "Autonomous Growth" in text:
|
756 |
-
# text.remove("Autonomous Growth")
|
757 |
-
# text.append("Accelerated Growth")
|
758 |
-
# if Measures_Button.value == "Current State - 2024":
|
759 |
-
# if "Accelerated Growth" in text:
|
760 |
-
# text.remove("Accelerated Growth")
|
761 |
-
# if "Autonomous Growth" in text:
|
762 |
-
# text.remove("Autonomous Growth")
|
763 |
-
if Button3.value:
|
764 |
-
text.append("Closed Small Wells")
|
765 |
-
Measure1On()
|
766 |
-
if Button3.value == False:
|
767 |
Measure1Off()
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
|
|
|
|
|
|
|
|
|
|
774 |
Measure2Off()
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
if
|
785 |
-
text.append("Import Water")
|
786 |
-
Measure4On()
|
787 |
-
if Button6.value == False:
|
788 |
Measure4Off()
|
789 |
-
|
790 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
791 |
|
792 |
app_title.object = " - ".join(text)
|
793 |
print(text)
|
794 |
-
print(active_wells_df.head())
|
795 |
update_indicators()
|
796 |
|
797 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
798 |
def Scenario1():
|
799 |
"""
|
800 |
-
Implement the first scenario with a demand increase of
|
801 |
|
802 |
Args:
|
803 |
event: The event object.
|
804 |
"""
|
805 |
-
global
|
806 |
-
|
|
|
807 |
hexagons_filterd["Water Demand"] = (
|
808 |
-
hexagons_filterd["Current Pop"] * demand_capita * 365
|
809 |
) / 1000000
|
810 |
update_scenarioTitle("Autonomous Growth")
|
811 |
print("Scenario 1 ran perfectly")
|
@@ -813,33 +1042,55 @@ def Scenario1():
|
|
813 |
|
814 |
def Scenario2():
|
815 |
"""
|
816 |
-
Implement the second scenario with a demand increase of
|
817 |
|
818 |
|
819 |
Args:
|
820 |
event: The event object.
|
821 |
"""
|
|
|
822 |
|
823 |
-
def Scenario2():
|
824 |
-
global hexagons_filterd
|
825 |
-
demand_capita = 0.156*1.35
|
826 |
hexagons_filterd["Water Demand"] = (
|
827 |
-
hexagons_filterd["Current Pop"] * demand_capita * 365
|
828 |
) / 1000000
|
|
|
829 |
update_scenarioTitle("Accelerated Growth")
|
830 |
update_indicators()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
831 |
|
832 |
def Measure1On():
|
833 |
-
global active_wells_df
|
834 |
condition = active_wells_df["Max_permit"] < 5.00
|
835 |
active_wells_df.loc[condition, "Active"] = False
|
836 |
|
|
|
|
|
|
|
837 |
|
838 |
def Measure1Off():
|
839 |
-
global active_wells_df
|
840 |
condition = active_wells_df["Max_permit"] >= 5.00
|
841 |
active_wells_df.loc[condition, "Active"] = True
|
842 |
|
|
|
|
|
|
|
843 |
|
844 |
def Measure2On():
|
845 |
"""
|
@@ -847,6 +1098,10 @@ def Measure2On():
|
|
847 |
"""
|
848 |
active_wells_df.loc[active_wells_df["Name"] == "Archemerberg", "Active"] = False
|
849 |
active_wells_df.loc[active_wells_df["Name"] == "Nijverdal", "Active"] = False
|
|
|
|
|
|
|
|
|
850 |
|
851 |
def Measure2Off():
|
852 |
"""
|
@@ -855,39 +1110,87 @@ def Measure2Off():
|
|
855 |
active_wells_df.loc[active_wells_df["Name"] == "Archemerberg", "Active"] = True
|
856 |
active_wells_df.loc[active_wells_df["Name"] == "Nijverdal", "Active"] = True
|
857 |
|
|
|
|
|
|
|
|
|
858 |
def Measure3On():
|
859 |
"""
|
860 |
Activate the third measure (using smart meters).
|
861 |
"""
|
862 |
-
demand_capita
|
863 |
hexagons_filterd["Water Demand"] = (
|
864 |
-
hexagons_filterd["
|
865 |
) / 1000000
|
866 |
|
867 |
def Measure3Off():
|
868 |
"""
|
869 |
Deactivate the third measure (using smart meters).
|
870 |
"""
|
871 |
-
demand_capita
|
872 |
hexagons_filterd["Water Demand"] = (
|
873 |
-
hexagons_filterd["
|
874 |
) / 1000000
|
875 |
|
876 |
def Measure4On():
|
877 |
"""
|
878 |
Activate the fourth measure (importing water).
|
879 |
"""
|
880 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
881 |
|
882 |
def Measure4Off():
|
883 |
"""
|
884 |
Deactivate the fourth measure (importing water).
|
885 |
"""
|
886 |
-
try:
|
887 |
-
|
888 |
-
|
|
|
|
|
889 |
print("Row does not exist")
|
890 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
891 |
|
892 |
def Reset(event):
|
893 |
"""
|
@@ -896,9 +1199,11 @@ def Reset(event):
|
|
896 |
Args:
|
897 |
event: The event object.
|
898 |
"""
|
899 |
-
demand_capita = 0.
|
|
|
|
|
900 |
hexagons_filterd["Water Demand"] = (
|
901 |
-
hexagons_filterd["
|
902 |
) / 1000000
|
903 |
global active_wells_df
|
904 |
active_wells_df = gpd.GeoDataFrame(
|
@@ -909,6 +1214,7 @@ def Reset(event):
|
|
909 |
"Max_permit": wells["Permit__Mm3_per_jr_"],
|
910 |
"Balance area": wells["Balansgebied"],
|
911 |
"Active": [True] * len(wells),
|
|
|
912 |
"Value": wells["Extraction_2023__Mm3_per_jr_"],
|
913 |
"OPEX_m3": wells["totOpex_m3"],
|
914 |
"Drought_m3": wells["DroughtDamage_EUR_m3"],
|
@@ -918,54 +1224,52 @@ def Reset(event):
|
|
918 |
* wells["Extraction_2023__Mm3_per_jr_"]
|
919 |
* 1000000,
|
920 |
"OPEX": wells["totOpex_m3"] * wells["Extraction_2023__Mm3_per_jr_"] * 1000000,
|
|
|
921 |
"geometry": wells["geometry"],
|
922 |
}
|
923 |
)
|
924 |
-
|
925 |
-
|
|
|
|
|
926 |
update_indicators()
|
927 |
|
928 |
def update_indicators(arg=None):
|
929 |
total_extraction.value = calculate_total_extraction()
|
930 |
total_opex.value = calculate_total_OPEX()
|
|
|
931 |
excess_cap.value = calculate_available()
|
|
|
932 |
own_pane.value = calculate_ownership()
|
933 |
-
natura_pane.value = calculate_affected_Natura()
|
934 |
-
industrial_cap.value=calculate_industrial()
|
935 |
co2_pane.value= calculate_total_CO2_cost()
|
936 |
drought_pane.value = calculate_total_Drought_cost()
|
937 |
-
update_balance_opex()
|
938 |
update_balance_lzh_gauges()
|
939 |
total_demand.value = calculate_total_Demand()
|
|
|
940 |
lzh.value = calculate_lzh()
|
941 |
|
942 |
|
|
|
943 |
# Initialize a dictionary to hold the active state and slider references
|
944 |
active_wells = {}
|
945 |
|
946 |
-
miniBox_style = {
|
947 |
-
'background': '#e9e9e1',
|
948 |
-
'border': '0.7px solid',
|
949 |
-
'margin': '10px',
|
950 |
-
"box-shadow": '4px 2px 6px #2a407e',
|
951 |
-
"display": "flex"
|
952 |
-
}
|
953 |
-
|
954 |
-
buttonGroup_style = {
|
955 |
-
'flex-wrap': 'wrap',
|
956 |
-
'display': 'flex'
|
957 |
-
}
|
958 |
-
|
959 |
# Initialize a dictionary to hold the balance area layouts
|
960 |
balance_area_buttons = {}
|
961 |
|
|
|
|
|
|
|
962 |
# Setup Well Radio Buttons
|
963 |
Radio_buttons = []
|
964 |
Well_radioB = []
|
965 |
-
options = ["-10%", "-20%", "Current", "+10%", "+20%", "Maximum Permit"
|
|
|
|
|
966 |
for index, row in wells.iterrows():
|
967 |
wellName = row["Name"]
|
968 |
current_value = row["Extraction_2023__Mm3_per_jr_"]
|
|
|
969 |
balance_area = row["Balansgebied"]
|
970 |
radio_group = pn.widgets.RadioButtonGroup(
|
971 |
name=wellName,
|
@@ -975,23 +1279,25 @@ for index, row in wells.iterrows():
|
|
975 |
)
|
976 |
|
977 |
# Add Checkbox and listeners
|
978 |
-
checkbox = pn.widgets.
|
979 |
checkbox.param.watch(partial(toggle_well, well_name=wellName), "value")
|
980 |
radio_group.param.watch(partial(update_radio, well_name=wellName), "value")
|
981 |
|
|
|
|
|
|
|
982 |
NameP = pn.pane.Str(wellName, styles={
|
983 |
'font-size': "14px",
|
984 |
'font-family': "Barlow",
|
985 |
'font-weight': 'bold',
|
986 |
})
|
987 |
|
988 |
-
|
989 |
-
|
990 |
-
|
991 |
-
|
992 |
-
|
993 |
-
|
994 |
-
Well_radioB = pn.Column(NameState, NamePane, radio_group, styles=miniBox_style)
|
995 |
|
996 |
# Add the well layout to the appropriate balance area layout
|
997 |
if balance_area not in balance_area_buttons:
|
@@ -999,7 +1305,11 @@ for index, row in wells.iterrows():
|
|
999 |
balance_area_buttons[balance_area].append(Well_radioB)
|
1000 |
|
1001 |
# Store the active state and radio group reference along with the NamePane
|
1002 |
-
active_wells[wellName] = {"active": True, "value": current_value, "radio_group": radio_group, "name_pane":
|
|
|
|
|
|
|
|
|
1003 |
|
1004 |
# Create HTML Text for Wells Tab
|
1005 |
balance_area_Text = pn.pane.HTML('''
|
@@ -1007,109 +1317,208 @@ balance_area_Text = pn.pane.HTML('''
|
|
1007 |
, width=300, align="start")
|
1008 |
|
1009 |
# Create a layout for the radio buttons
|
1010 |
-
radioButton_layout = pn.Accordion(styles={'width': '
|
1011 |
for balance_area, layouts in balance_area_buttons.items():
|
1012 |
balance_area_column = pn.Column(*layouts)
|
1013 |
radioButton_layout.append((balance_area, balance_area_column))
|
1014 |
|
1015 |
-
|
1016 |
|
1017 |
-
Scenario_Button =pn.widgets.RadioButtonGroup(name="Measures Button Group", options=['Current state - 2024','
|
1018 |
-
'width': '
|
1019 |
)
|
1020 |
Scenario_Button.param.watch(update_scenarios, "value")
|
1021 |
|
1022 |
-
|
1023 |
-
|
1024 |
-
)
|
1025 |
-
|
1026 |
-
Button1.on_click(Scenario1)
|
1027 |
|
1028 |
-
|
1029 |
-
|
1030 |
-
)
|
1031 |
-
|
1032 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1033 |
|
1034 |
-
|
1035 |
name='Close Small Wells', button_type="primary", button_style="outline", width=300, margin=10,
|
1036 |
)
|
1037 |
-
|
1038 |
|
1039 |
-
|
1040 |
name='Close Natura 2000 Wells', button_type="primary", button_style="outline", width=300, margin=10,
|
1041 |
)
|
1042 |
-
|
1043 |
|
1044 |
-
|
1045 |
-
|
1046 |
-
)
|
1047 |
-
|
|
|
|
|
1048 |
|
1049 |
-
|
1050 |
name='Import Water', button_type="primary", button_style="outline", width=300, margin=10,
|
1051 |
)
|
1052 |
-
|
1053 |
|
1054 |
-
|
|
|
|
|
|
|
1055 |
name='Reset', button_type='danger', width=300, margin=10
|
1056 |
)
|
1057 |
-
|
1058 |
|
1059 |
-
textYears = pn.pane.HTML(
|
1060 |
-
|
1061 |
-
|
1062 |
-
|
1063 |
-
)
|
1064 |
|
1065 |
-
|
|
|
|
|
1066 |
'''
|
1067 |
-
<h3 align= "center" style="margin: 5px;">
|
1068 |
# <b>Scenario with demand increase of 10% ↴</b>'''
|
1069 |
, width=300, align="start"
|
1070 |
)
|
1071 |
textB2 = pn.pane.HTML(
|
1072 |
'''<b>Scenario with demand increase of 35% ↴</b>''', width=300, align="start"
|
|
|
1073 |
)
|
1074 |
-
|
1075 |
-
'''<hr class="dashed"><h3 align= "center" style="margin: 5px;"> Measures </h3> <hr>
|
1076 |
<b>Close down all well locations with production less than 5Mm\u00b3/yr ↴</b>''', width=300, align="start", styles={}
|
1077 |
)
|
1078 |
-
|
1079 |
'''
|
1080 |
<b>Close down all well locations in less than 100m from a Natura 2000 Area ↴</b>''', width=300, align="start", styles={}
|
1081 |
)
|
1082 |
|
1083 |
-
|
1084 |
-
'''
|
1085 |
-
<b>
|
1086 |
)
|
1087 |
|
1088 |
-
|
1089 |
'''
|
1090 |
<b>Importing water from WAZ Getelo, NVB Nordhorn and Haaksbergen</b>''', width=300, align="start", styles={}
|
1091 |
)
|
1092 |
|
|
|
|
|
|
|
|
|
1093 |
textEnd = pn.pane.HTML(
|
1094 |
'''<hr class="dashed">
|
1095 |
''', width=300, align="start", styles={}
|
1096 |
)
|
1097 |
|
1098 |
-
|
|
|
|
|
|
|
|
|
1099 |
|
1100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1101 |
|
1102 |
# MAIN WINDOW
|
|
|
1103 |
map_pane = pn.pane.plot.Folium(update_layers(), sizing_mode="stretch_both")
|
1104 |
|
|
|
|
|
|
|
|
|
|
|
1105 |
total_extraction = pn.indicators.Number(
|
1106 |
name="Total Supply",
|
1107 |
value=calculate_total_extraction(),
|
1108 |
format="{value:.2f} Mm\u00b3/yr",
|
1109 |
default_color='#3850a0',
|
1110 |
-
font_size="
|
1111 |
-
title_size="
|
1112 |
-
sizing_mode="stretch_width"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1113 |
)
|
1114 |
|
1115 |
total_opex = pn.indicators.Number(
|
@@ -1117,48 +1526,61 @@ total_opex = pn.indicators.Number(
|
|
1117 |
value=calculate_total_OPEX(),
|
1118 |
format="{value:0,.2f} M\u20AC/yr",
|
1119 |
default_color='#3850a0',
|
1120 |
-
font_size="
|
1121 |
-
title_size="
|
1122 |
align="center",
|
1123 |
sizing_mode="stretch_width"
|
1124 |
)
|
1125 |
|
1126 |
-
|
1127 |
-
|
1128 |
-
|
1129 |
-
|
1130 |
-
value=value,
|
1131 |
-
format="{value:0,.2f} M\u20AC/yr",
|
1132 |
-
default_color='#3850a0',
|
1133 |
-
font_size="28pt",
|
1134 |
-
title_size="18pt",
|
1135 |
-
align="center",
|
1136 |
-
)
|
1137 |
-
for balance, value in balance_opex.items()
|
1138 |
-
}
|
1139 |
-
|
1140 |
-
industrial_cap = pn.indicators.Number(
|
1141 |
-
name="Industrial Extraction",
|
1142 |
-
value=calculate_industrial(),
|
1143 |
-
format="{value:0.2f} Mm\u00b3/yr",
|
1144 |
default_color='#3850a0',
|
1145 |
-
font_size="
|
1146 |
-
title_size="
|
1147 |
align="center",
|
1148 |
sizing_mode="stretch_width"
|
1149 |
)
|
1150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1151 |
excess_cap = pn.indicators.Number(
|
1152 |
name="Excess Capacity",
|
1153 |
value=calculate_available(),
|
1154 |
format="{value:0.2f} Mm\u00b3/yr",
|
1155 |
default_color='#3850a0',
|
1156 |
-
font_size="
|
1157 |
-
title_size="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1158 |
align="center",
|
1159 |
sizing_mode="stretch_width"
|
1160 |
)
|
1161 |
|
|
|
|
|
1162 |
own_pane = pn.indicators.Number(
|
1163 |
name="Landownership",
|
1164 |
value=calculate_ownership(),
|
@@ -1171,13 +1593,13 @@ own_pane = pn.indicators.Number(
|
|
1171 |
sizing_mode="stretch_width"
|
1172 |
)
|
1173 |
|
1174 |
-
|
1175 |
name="Approximate Natura 2000 \n Affected area",
|
1176 |
value=calculate_affected_Natura(),
|
1177 |
format="{value:0.2f} Ha",
|
1178 |
default_color='#3850a0',
|
1179 |
-
font_size="
|
1180 |
-
title_size="
|
1181 |
align="center",
|
1182 |
sizing_mode="stretch_width",
|
1183 |
styles = {
|
@@ -1185,13 +1607,20 @@ natura_pane = pn.indicators.Number(
|
|
1185 |
}
|
1186 |
)
|
1187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1188 |
co2_pane = pn.indicators.Number(
|
1189 |
name="CO\u2082 Emmission Cost",
|
1190 |
value=calculate_total_CO2_cost(),
|
1191 |
format="{value:0,.2f} M\u20AC/yr",
|
1192 |
default_color='#3850a0',
|
1193 |
-
font_size="
|
1194 |
-
title_size="
|
1195 |
)
|
1196 |
|
1197 |
drought_pane = pn.indicators.Number(
|
@@ -1199,25 +1628,17 @@ drought_pane = pn.indicators.Number(
|
|
1199 |
value=calculate_total_Drought_cost(),
|
1200 |
format="{value:0,.2f} M\u20AC/yr",
|
1201 |
default_color='#3850a0',
|
1202 |
-
font_size="
|
1203 |
-
title_size="
|
1204 |
)
|
1205 |
|
1206 |
# df_display = pn.pane.Markdown(update_df_display())
|
1207 |
-
df_Hexagons = pn.pane.DataFrame(hexagons_filterd.head(), name="Hexagons data")
|
|
|
1208 |
|
1209 |
-
total_demand = pn.indicators.Number(
|
1210 |
-
name="Total Water Demand",
|
1211 |
-
value=calculate_total_Demand(),
|
1212 |
-
format="{value:0,.2f} Mm\u00b3/yr",
|
1213 |
-
font_size="28pt",
|
1214 |
-
title_size="18pt",
|
1215 |
-
default_color='#3850a0',
|
1216 |
-
sizing_mode="stretch_width"
|
1217 |
-
)
|
1218 |
|
1219 |
lzh = pn.indicators.Gauge(
|
1220 |
-
name=f"Overall
|
1221 |
value=calculate_lzh(),
|
1222 |
bounds=(0, 150),
|
1223 |
format="{value} %",
|
@@ -1226,7 +1647,7 @@ lzh = pn.indicators.Gauge(
|
|
1226 |
"pointer": {"interStyle": {"color": "auto"}},
|
1227 |
"detail": {"valueAnimation": True, "color": "inherit"},
|
1228 |
},
|
1229 |
-
align=("center",'center')
|
1230 |
)
|
1231 |
lzh.param.watch(update_indicators, "value")
|
1232 |
|
@@ -1236,49 +1657,88 @@ for area, value in balance_lzh_values.items():
|
|
1236 |
gauge = pn.indicators.Gauge(
|
1237 |
name=f"LZH \n{area}",
|
1238 |
value=value,
|
1239 |
-
bounds=(0,
|
1240 |
format="{value} %",
|
1241 |
colors=[(0.2, "#D9534F"), (0.24, "#f2bf57"),(0.27, "#92C25B"), (1, "#8DCEC0")],
|
1242 |
custom_opts={
|
1243 |
"pointer": {"interStyle": {"color": "auto"}},
|
1244 |
"detail": {"valueAnimation": True, "color": "inherit"},
|
1245 |
},
|
1246 |
-
align=("center",'center'),
|
1247 |
)
|
1248 |
balance_lzh_gauges[area] = gauge
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1249 |
|
|
|
1250 |
|
1251 |
-
|
1252 |
-
|
1253 |
|
1254 |
-
|
1255 |
-
|
|
|
1256 |
)
|
1257 |
|
1258 |
-
|
1259 |
-
|
|
|
|
|
1260 |
)
|
1261 |
|
1262 |
-
|
1263 |
-
|
|
|
1264 |
|
1265 |
app_title = pn.pane.Markdown("## Scenario: Current State - 2024", styles={
|
1266 |
"text-align": "right",
|
1267 |
"color": "#00B893"
|
1268 |
})
|
1269 |
|
1270 |
-
|
|
|
|
|
1271 |
main1[0, 0] = pn.Row(map_pane)
|
1272 |
|
1273 |
-
main2 = pn.
|
|
|
1274 |
natura_pane,
|
1275 |
-
Env_pane,
|
1276 |
-
Extra_water_pane,
|
1277 |
sizing_mode="scale_width",
|
1278 |
scroll=True
|
1279 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1280 |
|
1281 |
-
main1[0, 1] = pn.Column(app_title, lzhTabs, Supp_dem, opexTabs, main2, sizing_mode="stretch_width")
|
1282 |
|
1283 |
Box = pn.template.MaterialTemplate(
|
1284 |
title="Vitalens",
|
@@ -1287,25 +1747,29 @@ Box = pn.template.MaterialTemplate(
|
|
1287 |
main=[main1],
|
1288 |
header_background= '#3850a0',
|
1289 |
header_color= '#f2f2ed',
|
1290 |
-
sidebar_width =
|
|
|
1291 |
)
|
1292 |
|
|
|
|
|
|
|
1293 |
def total_extraction_update():
|
1294 |
"""
|
1295 |
Update the total extraction and related indicators.
|
1296 |
"""
|
1297 |
total_extraction.value = calculate_total_extraction()
|
1298 |
-
# df_display.object = update_df_display()
|
1299 |
total_opex.value = calculate_total_OPEX()
|
1300 |
-
|
1301 |
-
update_balance_opex()
|
1302 |
update_balance_lzh_gauges()
|
1303 |
update_indicators()
|
1304 |
-
|
1305 |
-
|
|
|
|
|
1306 |
co2_pane.value = calculate_total_CO2_cost()
|
1307 |
drought_pane.value = calculate_total_Drought_cost()
|
1308 |
|
1309 |
|
1310 |
total_extraction_update()
|
1311 |
-
Box.servable()
|
|
|
5 |
import fiona
|
6 |
from bokeh.models.formatters import PrintfTickFormatter
|
7 |
import folium
|
8 |
+
import keplergl
|
9 |
+
from shapely.geometry import shape, Polygon, Point
|
10 |
+
from lonboard import Map, PathLayer, ScatterplotLayer
|
11 |
import branca
|
12 |
from functools import partial
|
13 |
+
import printingReport
|
14 |
|
15 |
# Styling
|
16 |
globalCss_route= "Stylesheet.css"
|
|
|
29 |
--panel-primary-color: #151931 !important;
|
30 |
--panel-background-color: #f2f2ed !important;
|
31 |
--panel-on-background-color: #151931 !important;
|
32 |
+
--sidebar-width: 350px;
|
33 |
+
}
|
34 |
+
|
35 |
+
:host(.active) .bar {
|
36 |
+
background-color: #c2d5f7;
|
37 |
+
}
|
38 |
+
|
39 |
+
:host(.bk-above) .bk-header .bk-tab{
|
40 |
+
border: #F2F2ED !important;
|
41 |
+
background: #00000014 !important
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
::-webkit-scrollbar-track
|
46 |
+
{
|
47 |
+
background-color: #F5F5F5;
|
48 |
+
}
|
49 |
+
|
50 |
+
::-webkit-scrollbar
|
51 |
+
{
|
52 |
+
width: 5px !important;
|
53 |
+
background-color: #F5F5F5;
|
54 |
+
}
|
55 |
+
|
56 |
+
::-webkit-scrollbar-thumb
|
57 |
+
{
|
58 |
+
background-color: #CCC5B9 !important;
|
59 |
+
radius: 1px
|
60 |
}
|
61 |
|
62 |
#sidebar, #main {
|
|
|
71 |
.title {
|
72 |
font-weight: 600 !important;
|
73 |
}
|
74 |
+
|
75 |
+
.bar {
|
76 |
+
background-color: #b1b1c9;
|
77 |
+
}
|
78 |
+
|
79 |
.bk-btn {
|
80 |
border-radius: 0.5em !important;
|
81 |
}
|
|
|
87 |
.bk-btn-group {
|
88 |
height: 100%;
|
89 |
display: flex;
|
90 |
+
flex-wrap: inherit !important;
|
91 |
align-items: center;
|
92 |
}
|
93 |
|
|
|
106 |
width: fit-content;
|
107 |
}
|
108 |
|
109 |
+
.bk-btn-warning {
|
110 |
+
margin: 3px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
}
|
112 |
|
113 |
.accordion-header button{
|
114 |
color: #151931;
|
115 |
background-color: #B4BFE4;
|
116 |
}
|
117 |
+
|
118 |
+
|
119 |
+
.bk-tab.bk-active {
|
120 |
+
background: #d3d3cf !imporant;
|
121 |
+
color: #d9534f !important;
|
122 |
+
}
|
123 |
'''
|
124 |
]
|
125 |
|
126 |
+
miniBox_style = {
|
127 |
+
'background': '#e9e9e1',
|
128 |
+
'border': '0.7px solid',
|
129 |
+
'margin': '10px',
|
130 |
+
"box-shadow": '4px 2px 6px #2a407e',
|
131 |
+
"display": "flex"
|
132 |
+
}
|
133 |
+
|
134 |
+
buttonGroup_style = {
|
135 |
+
'flex-wrap': 'wrap',
|
136 |
+
'display': 'flex'
|
137 |
+
}
|
138 |
+
|
139 |
+
|
140 |
# Initialize extensions
|
141 |
pn.config.global_css = cssStyle
|
142 |
pn.config.css_files = cssStyle
|
143 |
pn.config.loading_spinner = 'petal'
|
144 |
pn.extension(sizing_mode="stretch_width")
|
145 |
pn.extension("plotly")
|
146 |
+
pn.extension("ipywidgets")
|
147 |
pn.extension("echarts")
|
148 |
pn.extension(
|
149 |
"tabulator", "ace", css_files=["https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"]
|
150 |
)
|
151 |
+
pn.extension(notifications=True)
|
152 |
+
|
153 |
|
154 |
# Load the GeoPackage file
|
155 |
GPKG_FILE = "./Assets/Thematic_Data.gpkg"
|
|
|
158 |
# Get Wells Attributes
|
159 |
wells = gpd.read_file(GPKG_FILE, layer="Well_Capacity_Cost")
|
160 |
industrial = gpd.read_file(GPKG_FILE, layer="Industrial_Extraction")
|
161 |
+
mainPipes = gpd.read_file(GPKG_FILE, layer="Pipes_Smooth")
|
162 |
+
|
163 |
|
164 |
# Convert the capacity columns to numeric, setting errors='coerce' will replace non-numeric values with NaN
|
165 |
wells["Permit__Mm3_per_jr_"] = pd.to_numeric(
|
|
|
191 |
"Max_permit": wells["Permit__Mm3_per_jr_"],
|
192 |
"Balance area": wells["Balansgebied"],
|
193 |
"Active": [True] * len(wells),
|
194 |
+
"Current Extraction": wells["Extraction_2023__Mm3_per_jr_"],
|
195 |
"Value": wells["Extraction_2023__Mm3_per_jr_"],
|
196 |
"OPEX_m3": wells["totOpex_m3"],
|
197 |
"Drought_m3": wells["DroughtDamage_EUR_m3"],
|
|
|
201 |
* wells["Extraction_2023__Mm3_per_jr_"]
|
202 |
* 1000000,
|
203 |
"OPEX": wells["totOpex_m3"] * wells["Extraction_2023__Mm3_per_jr_"] * 1000000,
|
204 |
+
"CAPEX": 0,
|
205 |
"geometry": wells["geometry"],
|
206 |
}
|
207 |
)
|
208 |
active_wells_df.astype({"Num_Wells": "int32", "Ownership": "int32"}, copy=False)
|
209 |
+
active_wells_df.set_crs(epsg=28992)
|
210 |
+
|
211 |
+
cities = gpd.read_file(GPKG_FILE, layer="CitiesHexagonal")
|
212 |
+
|
213 |
+
cities_clean = gpd.GeoDataFrame(
|
214 |
+
{
|
215 |
+
"cityName" : cities["statnaam"],
|
216 |
+
"Population 2022": cities["SUM_Pop_2022"],
|
217 |
+
"Water Demand": cities["SUM_Water_Demand_m3_YR"]/ 1000000,
|
218 |
+
"geometry" : cities["geometry"]
|
219 |
+
})
|
220 |
+
|
221 |
+
cities_clean.loc[cities_clean["cityName"].isna(), "Water Demand"] = None
|
222 |
|
223 |
yearCal = 2022
|
224 |
growRate = 0.0062
|
225 |
+
smallBussiness = 1.2
|
226 |
+
demand_capita = 0.135
|
227 |
+
|
228 |
|
229 |
# Get Destination Attributes
|
230 |
hexagons = gpd.read_file(GPKG_FILE, layer="H3_Lvl8")
|
|
|
254 |
"Pop2022": hexagons["Pop_2022"],
|
255 |
"Current Pop": hexagons["Pop_2022"],
|
256 |
"Industrial Demand": hexagons["Ind_Demand"],
|
257 |
+
"Water Demand": hexagons["Pop_2022"] * demand_capita * smallBussiness * 365 / 1000000,
|
258 |
"Type": hexagons["Type_T"],
|
259 |
"Source_Name": hexagons["Source_Name"],
|
260 |
"geometry": hexagons["geometry"],
|
|
|
263 |
|
264 |
balance_areas= hexagons_filterd.dissolve(by="Balance Area", as_index=False)
|
265 |
|
266 |
+
industrialExcess = 0
|
267 |
+
|
268 |
def calculate_total_extraction():
|
269 |
"""
|
270 |
Calculate the total water extraction from active wells.
|
|
|
272 |
Returns:
|
273 |
float: Total water extraction in Mm3/yr.
|
274 |
"""
|
275 |
+
global industrialExcess
|
276 |
+
total = active_wells_df[active_wells_df["Active"]]["Value"].sum() + industrialExcess
|
277 |
return total
|
278 |
|
279 |
+
def calculate_difference():
|
280 |
+
"""
|
281 |
+
Calculate the total water extraction from active wells.
|
282 |
+
|
283 |
+
Returns:
|
284 |
+
float: Total water extraction in Mm3/yr.
|
285 |
+
"""
|
286 |
+
total = calculate_total_extraction() - calculate_total_Demand()
|
287 |
return total
|
288 |
|
289 |
def calculate_available():
|
|
|
296 |
total = (
|
297 |
active_wells_df[active_wells_df["Active"]==True]["Max_permit"].sum()
|
298 |
- active_wells_df[active_wells_df["Active"]==True]["Value"].sum()
|
299 |
+
)
|
300 |
+
return total
|
301 |
+
|
302 |
+
def calculate_industrial_extract():
|
303 |
+
total = industrial["Current_Extraction_2019"].sum()
|
304 |
+
return total
|
305 |
|
306 |
def calculate_ownership():
|
307 |
"""
|
|
|
323 |
Returns:
|
324 |
float: Total OPEX in million EUR/yr.
|
325 |
"""
|
326 |
+
active_wells_df["OPEX"] = active_wells_df["OPEX_m3"] * active_wells_df["Value"]
|
327 |
+
|
328 |
total = (active_wells_df[active_wells_df["Active"]]["OPEX"]).sum()
|
329 |
+
return total
|
330 |
+
|
331 |
+
def calculate_total_CAPEX():
|
332 |
+
# CAPEX is the difference between Value and current extraction, if Value is higher
|
333 |
+
active_wells_df["CAPEX"] = np.where(
|
334 |
+
active_wells_df["Value"] > active_wells_df["Current Extraction"],
|
335 |
+
(active_wells_df["Value"] - active_wells_df["Current Extraction"]) * 10, # You can adjust the multiplier as needed
|
336 |
+
0 # CAPEX is 0 if Value is less than or equal to current extraction
|
337 |
+
)
|
338 |
+
|
339 |
+
# Sum the CAPEX for all active wells
|
340 |
+
total = active_wells_df[active_wells_df["Active"]]["CAPEX"].sum()
|
341 |
+
return total # Convert to million EUR
|
342 |
+
#
|
343 |
+
|
344 |
|
345 |
def calculate_total_OPEX_by_balance():
|
346 |
"""
|
|
|
353 |
active_wells_df[active_wells_df["Active"]].groupby("Balance area")["OPEX"].sum()
|
354 |
)/1000000
|
355 |
|
356 |
+
# def update_balance_opex():
|
357 |
+
# """
|
358 |
+
# Update OPEX indicators for balance areas.
|
359 |
+
# """
|
360 |
+
# balance_opex = calculate_total_OPEX_by_balance()
|
361 |
+
# for balance, indicator in balance_opex_indicators.items():
|
362 |
+
# indicator.value = balance_opex.get(balance, 0)
|
363 |
|
364 |
def calculate_total_envCost():
|
365 |
"""
|
|
|
397 |
& (hexagons_filterd["Type"] == "Source and Restricted")
|
398 |
]
|
399 |
total = restricted.shape[0]
|
400 |
+
ha = total * 629387.503078 / 100000
|
401 |
+
return ha
|
402 |
+
|
403 |
+
def generate_area_SVG (n):
|
404 |
+
SVG = '<svg width="64px" height="64px" viewBox="0 -199.5 1423 1423" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M105.124 107.703h1142.109v796.875h-1142.109v-796.875z" fill="#C0D36F"></path><path d="M1069.108 336.219h157.266v353.672h-157.266zM128.562 336.219h157.266v353.672h-157.266z" fill="#FFFFFF"></path><path d="M1120.438 90.125h-887.813c-78.646 0.133-142.367 63.853-142.5 142.488v558.528c0.133 78.647 63.853 142.367 142.488 142.5h887.826c78.646-0.133 142.367-63.853 142.5-142.488v-558.293c0 0 0 0 0 0 0-78.747-63.771-142.601-142.488-142.733zM651.688 626.844c-53.93-11.239-93.867-58.377-93.867-114.844s39.938-103.605 93.106-114.711l0.761 229.554zM698.563 397.156c53.93 11.239 93.867 58.377 93.867 114.844s-39.938 103.605-93.106 114.711l-0.761-229.554zM136.062 347.937h101.25c0 0 0 0 0 0 23.429 0 42.421 18.992 42.421 42.421v243.516c0 0 0 0 0 0 0 23.429-18.992 42.421-42.421 42.421 0 0 0 0 0 0h-101.25v-328.125zM136.062 791.375v-68.438h101.25c49.317 0 89.297-39.98 89.297-89.297v-242.578c0-49.317-39.98-89.297-89.297-89.297 0 0 0 0 0 0h-101.25v-68.438c0.133-52.759 42.867-95.492 95.613-95.625h420.011v212.813c-79.347 12.438-139.329 80.308-139.329 162.188 0 81.879 59.982 149.75 138.403 162.068l0.927 212.932h-419.063c-0.209 0.002-0.457 0.003-0.705 0.003-52.942 0-95.859-42.918-95.859-95.859 0-0.165 0.001-0.331 0.002-0.497zM1120.438 887h-421.875v-212.813c79.347-12.438 139.329-80.308 139.329-162.188 0-81.879-59.982-149.75-138.403-162.068l-0.927-212.932h421.875c52.759 0.133 95.492 42.866 95.625 95.613v68.45h-95.625c0 0 0 0 0 0-49.317 0-89.297 39.98-89.297 89.297v243.516c0 49.317 39.979 89.297 89.297 89.297 0 0 0 0 0 0h93.75v68.438c-0.249 52.012-41.883 94.217-93.648 95.39zM1216.063 347.937v328.125h-95.625c0 0 0 0 0 0-23.429 0-42.421-18.992-42.421-42.421 0 0 0 0 0 0v-242.578c0 0 0 0 0 0 0-23.429 18.992-42.421 42.421-42.421 0 0 0 0 0 0h93.75z" fill="#25274B"></path></g></svg>'
|
405 |
+
|
406 |
+
HaSVG = 0.5
|
407 |
+
full = int(n)
|
408 |
+
leftover = n - full
|
409 |
+
|
410 |
+
partialSVG = '''
|
411 |
+
<svg height="210" width="500">
|
412 |
+
<defs>
|
413 |
+
<filter id="fillpartial" primitiveUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
|
414 |
+
<feFlood x="0%" y="0%" width="100%" height="100%" flood-color="red" />
|
415 |
+
<feOffset dy="{leftover}">
|
416 |
+
<animate attributeName="dy" from="1" to=".5" dur="3s" />
|
417 |
+
</feOffset>
|
418 |
+
<feComposite operator="in" in2="SourceGraphic" />
|
419 |
+
<feComposite operator="over" in2="SourceGraphic" />
|
420 |
+
</filter>
|
421 |
+
</defs>
|
422 |
+
'<svg filter="url(#fillpartial)" width="64px" height="64px" viewBox="0 -199.5 1423 1423" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M105.124 107.703h1142.109v796.875h-1142.109v-796.875z" fill="#C0D36F"></path><path d="M1069.108 336.219h157.266v353.672h-157.266zM128.562 336.219h157.266v353.672h-157.266z" fill="#FFFFFF"></path><path d="M1120.438 90.125h-887.813c-78.646 0.133-142.367 63.853-142.5 142.488v558.528c0.133 78.647 63.853 142.367 142.488 142.5h887.826c78.646-0.133 142.367-63.853 142.5-142.488v-558.293c0 0 0 0 0 0 0-78.747-63.771-142.601-142.488-142.733zM651.688 626.844c-53.93-11.239-93.867-58.377-93.867-114.844s39.938-103.605 93.106-114.711l0.761 229.554zM698.563 397.156c53.93 11.239 93.867 58.377 93.867 114.844s-39.938 103.605-93.106 114.711l-0.761-229.554zM136.062 347.937h101.25c0 0 0 0 0 0 23.429 0 42.421 18.992 42.421 42.421v243.516c0 0 0 0 0 0 0 23.429-18.992 42.421-42.421 42.421 0 0 0 0 0 0h-101.25v-328.125zM136.062 791.375v-68.438h101.25c49.317 0 89.297-39.98 89.297-89.297v-242.578c0-49.317-39.98-89.297-89.297-89.297 0 0 0 0 0 0h-101.25v-68.438c0.133-52.759 42.867-95.492 95.613-95.625h420.011v212.813c-79.347 12.438-139.329 80.308-139.329 162.188 0 81.879 59.982 149.75 138.403 162.068l0.927 212.932h-419.063c-0.209 0.002-0.457 0.003-0.705 0.003-52.942 0-95.859-42.918-95.859-95.859 0-0.165 0.001-0.331 0.002-0.497zM1120.438 887h-421.875v-212.813c79.347-12.438 139.329-80.308 139.329-162.188 0-81.879-59.982-149.75-138.403-162.068l-0.927-212.932h421.875c52.759 0.133 95.492 42.866 95.625 95.613v68.45h-95.625c0 0 0 0 0 0-49.317 0-89.297 39.98-89.297 89.297v243.516c0 49.317 39.979 89.297 89.297 89.297 0 0 0 0 0 0h93.75v68.438c-0.249 52.012-41.883 94.217-93.648 95.39zM1216.063 347.937v328.125h-95.625c0 0 0 0 0 0-23.429 0-42.421-18.992-42.421-42.421 0 0 0 0 0 0v-242.578c0 0 0 0 0 0 0-23.429 18.992-42.421 42.421-42.421 0 0 0 0 0 0h93.75z" fill="#25274B"></path></g></svg>'
|
423 |
+
'''
|
424 |
+
|
425 |
+
|
426 |
+
fig = pn.pane.HTML(SVG*(int(n/HaSVG)))
|
427 |
+
fig2 = pn.pane.HTML(partialSVG)
|
428 |
+
|
429 |
+
composite = pn.Column (fig)
|
430 |
+
|
431 |
+
return composite
|
432 |
+
|
433 |
+
|
434 |
+
def generate_pipes_SVG(origin, destination, n):
|
435 |
+
SVG = '''<svg height="35px" width="35px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#fcfcfc" stroke-width="22.528"> <path style="fill:#C0FCFF;" d="M402.286,186.514H109.714c-12.118,0-21.943,9.825-21.943,21.943v146.286 c0,12.118,9.825,21.943,21.943,21.943h292.571c12.118,0,21.943-9.825,21.943-21.943V208.457 C424.229,196.339,414.404,186.514,402.286,186.514z"></path> <path style="fill:#89D2E8;" d="M402.286,186.514H256v190.171h146.286c12.118,0,21.943-9.825,21.943-21.943V208.457 C424.229,196.339,414.404,186.514,402.286,186.514z"></path> <path style="fill:#A9A9AE;" d="M256,98.743c-12.118,0-21.943,9.825-21.943,21.943v87.771c0,12.118,9.825,21.943,21.943,21.943 c12.118,0,21.943-9.825,21.943-21.943v-87.771C277.943,108.567,268.118,98.743,256,98.743z"></path> <path style="fill:#7AC0DE;" d="M490.057,149.943h-87.771c-12.118,0-21.943,9.825-21.943,21.943v219.429 c0,12.118,9.825,21.943,21.943,21.943h87.771c12.118,0,21.943-9.825,21.943-21.943V171.886 C512,159.767,502.175,149.943,490.057,149.943z"></path> <path style="fill:#89D2E8;" d="M109.714,149.943H21.943C9.825,149.943,0,159.767,0,171.886v219.429 c0,12.118,9.825,21.943,21.943,21.943h87.771c12.118,0,21.943-9.825,21.943-21.943V171.886 C131.657,159.767,121.833,149.943,109.714,149.943z"></path> <path style="fill:#DA3981;" d="M299.886,98.743h-87.771c-12.118,0-21.943,9.825-21.943,21.943s9.825,21.943,21.943,21.943h87.771 c12.118,0,21.943-9.825,21.943-21.943S312.004,98.743,299.886,98.743z"></path> <path style="fill:#A91E62;" d="M321.829,120.686c0-12.118-9.825-21.943-21.943-21.943H256v43.886h43.886 C312.004,142.629,321.829,132.804,321.829,120.686z"></path> </g><g id="SVGRepo_iconCarrier"> <path style="fill:#C0FCFF;" d="M402.286,186.514H109.714c-12.118,0-21.943,9.825-21.943,21.943v146.286 c0,12.118,9.825,21.943,21.943,21.943h292.571c12.118,0,21.943-9.825,21.943-21.943V208.457 C424.229,196.339,414.404,186.514,402.286,186.514z"></path> <path style="fill:#89D2E8;" d="M402.286,186.514H256v190.171h146.286c12.118,0,21.943-9.825,21.943-21.943V208.457 C424.229,196.339,414.404,186.514,402.286,186.514z"></path> <path style="fill:#A9A9AE;" d="M256,98.743c-12.118,0-21.943,9.825-21.943,21.943v87.771c0,12.118,9.825,21.943,21.943,21.943 c12.118,0,21.943-9.825,21.943-21.943v-87.771C277.943,108.567,268.118,98.743,256,98.743z"></path> <path style="fill:#7AC0DE;" d="M490.057,149.943h-87.771c-12.118,0-21.943,9.825-21.943,21.943v219.429 c0,12.118,9.825,21.943,21.943,21.943h87.771c12.118,0,21.943-9.825,21.943-21.943V171.886 C512,159.767,502.175,149.943,490.057,149.943z"></path> <path style="fill:#89D2E8;" d="M109.714,149.943H21.943C9.825,149.943,0,159.767,0,171.886v219.429 c0,12.118,9.825,21.943,21.943,21.943h87.771c12.118,0,21.943-9.825,21.943-21.943V171.886 C131.657,159.767,121.833,149.943,109.714,149.943z"></path> <path style="fill:#DA3981;" d="M299.886,98.743h-87.771c-12.118,0-21.943,9.825-21.943,21.943s9.825,21.943,21.943,21.943h87.771 c12.118,0,21.943-9.825,21.943-21.943S312.004,98.743,299.886,98.743z"></path> <path style="fill:#A91E62;" d="M321.829,120.686c0-12.118-9.825-21.943-21.943-21.943H256v43.886h43.886 C312.004,142.629,321.829,132.804,321.829,120.686z"></path> </g></svg>'''
|
436 |
+
fig =pn.pane.HTML(SVG*n)
|
437 |
+
OD = pn.pane.HTML(f'<p style="color:#3850A0; font-size:14px; margin:4px;">Pipes from <b>{origin} to {destination}</b>: ')
|
438 |
+
pane = pn.Row(OD, fig)
|
439 |
+
return pane
|
440 |
|
441 |
def calculate_total_CO2_cost():
|
442 |
"""
|
|
|
483 |
"""
|
484 |
active_wells_df.loc[active_wells_df["Name"] == well_name, "Active"] = event.new
|
485 |
update_indicators()
|
486 |
+
#map_pane.object = update_layers()
|
487 |
|
488 |
def toggle_industrial(event, location):
|
489 |
"""
|
|
|
495 |
"""
|
496 |
industrial.loc[industrial["Location"] == location, "Active"] = event.new
|
497 |
update_indicators()
|
498 |
+
#map_pane.object = update_layers()
|
499 |
|
500 |
def update_slider(event, well_name):
|
501 |
"""
|
|
|
526 |
"""
|
527 |
current_value = wells.loc[wells["Name"] == well_name, "Extraction_2023__Mm3_per_jr_"].values[0]
|
528 |
max_value = wells.loc[wells["Name"] == well_name, "Permit__Mm3_per_jr_"].values[0]
|
529 |
+
# agreement = wells.loc[wells["Name"]== well_name, "Agreement__Mm3_per_jr_"].values[0]
|
530 |
|
531 |
if event.new == "-10%":
|
532 |
new_value = current_value * 0.9
|
|
|
540 |
new_value = current_value * 1.2
|
541 |
elif event.new == "Maximum Permit":
|
542 |
new_value = max_value
|
543 |
+
|
|
|
544 |
|
545 |
active_wells_df.loc[active_wells_df["Name"] == well_name, "Value"] = new_value
|
546 |
+
opex_m3 = active_wells_df.loc[active_wells_df["Name"] == well_name, "OPEX_m3"]
|
547 |
+
active_wells_df.loc[active_wells_df["Name"] == well_name, "OPEX"] = new_value * opex_m3
|
548 |
+
|
549 |
name_pane = active_wells[well_name]["name_pane"]
|
550 |
+
name_pane.object = update_well_Value_formatted(well_name)
|
551 |
update_indicators()
|
552 |
|
553 |
def update_scenarios(event):
|
554 |
+
if event.new == "Population 2030":
|
555 |
Scenario1()
|
556 |
print('scenario 1 active')
|
557 |
+
if event.new == "Population 2035":
|
558 |
print('scenario 2 active')
|
559 |
Scenario2()
|
560 |
+
if event.new == "Current state - 2024":
|
561 |
+
print("Orginal Scenario")
|
562 |
+
ScenarioBase()
|
|
|
|
|
|
|
563 |
update_indicators()
|
564 |
|
565 |
+
def update_scenariosSmall(event):
|
566 |
+
if event.new == "Small Bussiness +10% Demand":
|
567 |
+
ScenarioSmallBussiness1()
|
568 |
+
print('scenario 1 Small active')
|
569 |
+
if event.new == "Small Bussiness +35% Demand":
|
570 |
+
print('scenario 2 small active')
|
571 |
+
ScenarioSmallBussiness2()
|
572 |
+
if event.new == "Current state - 2024":
|
573 |
+
print("Orginal Scenario")
|
574 |
+
ScenarioSmallBussinessBase()
|
575 |
+
update_indicators()
|
576 |
|
577 |
+
def update_well_Value(well_name):
|
578 |
"""
|
579 |
Update the well name display.
|
580 |
|
|
|
585 |
str: Updated well name display.
|
586 |
"""
|
587 |
current_extraction = active_wells_df[active_wells_df["Name"]==well_name]["Value"].values[0]
|
588 |
+
|
589 |
+
return current_extraction
|
590 |
+
|
591 |
+
def update_well_Value_formatted(well_name):
|
592 |
+
"""
|
593 |
+
Update the well name display.
|
594 |
+
|
595 |
+
Args:
|
596 |
+
well_name (str): The name of the well.
|
597 |
+
|
598 |
+
Returns:
|
599 |
+
str: Updated well name display.
|
600 |
+
"""
|
601 |
+
current_extraction = active_wells_df[active_wells_df["Name"]==well_name]["Value"].values[0]
|
602 |
+
|
603 |
return f"{current_extraction:.2f} Mm\u00b3/yr"
|
604 |
|
605 |
+
def styleWellValue (Wellvalue, maxValue):
|
606 |
+
if Wellvalue > maxValue:
|
607 |
+
valueStyle = {
|
608 |
+
'font-family': 'Roboto',
|
609 |
+
'font-size': "14px",
|
610 |
+
'font-weight': 'bold',
|
611 |
+
'color': '#d9534f'
|
612 |
+
}
|
613 |
+
else:
|
614 |
+
valueStyle = {
|
615 |
+
'font-family': 'Roboto',
|
616 |
+
'font-size': "14px",
|
617 |
+
'font-weight': "bold",
|
618 |
+
'color': '#2d4c4d'
|
619 |
+
}
|
620 |
+
return valueStyle
|
621 |
+
|
622 |
+
|
623 |
+
def current_demand(event):
|
624 |
+
global demand_capita
|
625 |
+
if event.new == 90:
|
626 |
+
demand_capita = 0.09*smallBussiness
|
627 |
+
if event.new == 100:
|
628 |
+
demand_capita = 0.1*smallBussiness
|
629 |
+
if event.new == 120:
|
630 |
+
demand_capita = 0.12*smallBussiness
|
631 |
+
if event.new == 135:
|
632 |
+
demand_capita = 0.135*smallBussiness
|
633 |
+
update_indicators()
|
634 |
+
|
635 |
|
636 |
def calculate_total_Demand():
|
637 |
"""
|
|
|
640 |
Returns:
|
641 |
float: Total water demand in Mm3/yr.
|
642 |
"""
|
643 |
+
hexagons_filterd["Water Demand"] = (
|
644 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
645 |
+
) / 1000000
|
646 |
+
|
647 |
total = ((hexagons_filterd["Water Demand"]).sum()) + (
|
648 |
(hexagons_filterd["Industrial Demand"]).sum()
|
649 |
)
|
|
|
686 |
"""
|
687 |
lzh_by_balance = {}
|
688 |
balance_areas = active_wells_df["Balance area"].unique()
|
689 |
+
|
690 |
for area in balance_areas:
|
691 |
+
total_extraction = active_wells_df.loc[
|
692 |
+
active_wells_df["Balance area"] == area, "Value"
|
|
|
|
|
693 |
].sum()
|
694 |
+
|
695 |
+
total_demand = hexagons_filterd.loc[
|
696 |
+
hexagons_filterd["Balance Area"] == area, "Water Demand"
|
|
|
697 |
].sum()
|
698 |
+
|
699 |
lzh_by_balance[area] = (
|
700 |
round((total_extraction / total_demand) * 100, 2) if total_demand else 0
|
701 |
)
|
702 |
+
|
703 |
return lzh_by_balance
|
704 |
|
705 |
+
|
706 |
def update_balance_lzh_gauges():
|
707 |
"""
|
708 |
Update Leveringszekerheid gauges for balance areas.
|
|
|
751 |
aliases=["Well Name", "Balance Area", "Extraction in Mm\u00b3/yr"],
|
752 |
)
|
753 |
popup_hex = folium.GeoJsonPopup(
|
754 |
+
fields=["cityName", "Water Demand", "Population 2022"],
|
755 |
)
|
756 |
popup_industrial = folium.GeoJsonPopup(
|
757 |
fields=["Place", "Licensed", "Current_Extraction_2019"],
|
|
|
765 |
|
766 |
colormap = branca.colormap.LinearColormap(
|
767 |
["#caf0f8", "#90e0ef", "#00b4d8", "#0077b6", "#03045e"],
|
768 |
+
vmin=cities_clean["Water Demand"].quantile(0.0),
|
769 |
+
vmax=cities_clean["Water Demand"].quantile(1),
|
770 |
caption="Total water demand in Mm\u00b3/yr",
|
771 |
)
|
772 |
|
|
|
783 |
polygon = Polygon(coordinates)
|
784 |
return polygon.centroid.y, polygon.centroid.x
|
785 |
|
786 |
+
def update_layers(wellsLayer=active_wells_df,industryLayer=industrial):
|
787 |
"""
|
788 |
Update the layers on the map.
|
789 |
|
790 |
Returns:
|
791 |
folium.Map: Updated Folium map.
|
792 |
"""
|
793 |
+
m
|
794 |
+
|
795 |
+
active = wellsLayer[wellsLayer["Active"]==True]
|
|
|
|
|
|
|
796 |
|
797 |
folium.GeoJson(
|
798 |
active,
|
|
|
802 |
tooltip=folium.GeoJsonTooltip(fields=["Name"], aliases=["Well Name:"]),
|
803 |
marker=folium.Marker(
|
804 |
icon=folium.Icon(
|
805 |
+
icon_color="#f3f3f3", icon="arrow-up-from-ground-water", prefix="fa", color='cadetblue'
|
806 |
)
|
807 |
),
|
808 |
).add_to(m)
|
809 |
|
810 |
folium.GeoJson(
|
811 |
+
industryLayer,
|
812 |
name="Industrial Water Extraction",
|
813 |
zoom_on_click=True,
|
814 |
popup=popup_industrial,
|
815 |
tooltip=folium.GeoJsonTooltip(fields=["Place"], aliases=["Place:"]),
|
816 |
marker=folium.Marker(
|
817 |
icon=folium.Icon(
|
818 |
+
icon_color="#d9534f", icon="industry", prefix="fa", color='lightred'
|
819 |
)
|
820 |
),
|
821 |
).add_to(m)
|
822 |
|
823 |
|
824 |
hex = folium.GeoJson(
|
825 |
+
cities_clean,
|
826 |
+
name="City Demand",
|
827 |
style_function=lambda x: {
|
828 |
"fillColor": (
|
829 |
colormap(x["properties"]["Water Demand"])
|
|
|
832 |
),
|
833 |
"color": (
|
834 |
"darkgray"
|
835 |
+
if x["properties"]["cityName"] is not None
|
836 |
else "transparent"
|
837 |
),
|
838 |
"fillOpacity": 0.8,
|
|
|
842 |
).add_to(m)
|
843 |
|
844 |
m.add_child(colormap)
|
845 |
+
|
846 |
+
folium.GeoJson(
|
847 |
+
mainPipes,
|
848 |
+
name="Main Pipelines",
|
849 |
+
style_function= lambda x:{
|
850 |
+
"color": "#d9534f",
|
851 |
+
"weight": (4 if x["properties"]["Diameter_mm"]>350
|
852 |
+
else(2 if x["properties"]["Diameter_mm"]>250
|
853 |
+
else 1)),
|
854 |
+
"Opacity": 0.6,
|
855 |
+
},
|
856 |
+
show=False
|
857 |
+
).add_to(m)
|
858 |
|
859 |
folium.GeoJson(
|
860 |
hexagons_filterd,
|
|
|
896 |
show= False,
|
897 |
).add_to(m)
|
898 |
|
899 |
+
|
900 |
+
|
901 |
folium.GeoJson(
|
902 |
balance_areas,
|
903 |
name="Balance Areas",
|
904 |
style_function=lambda x: {
|
905 |
"fillColor": "transparent",
|
906 |
+
"color": "#93419F",
|
907 |
"weight": 3
|
908 |
},
|
909 |
show=True,
|
910 |
tooltip=folium.GeoJsonTooltip(fields=['Balance Area'], labels=True)
|
911 |
).add_to(m)
|
912 |
|
913 |
+
|
914 |
+
|
915 |
folium.LayerControl().add_to(m)
|
916 |
|
917 |
return m
|
918 |
|
919 |
+
def create_map(wellsLayer=active_wells_df,industryLayer=industrial):
|
920 |
+
w1 = keplergl.KeplerGl(height=500)
|
921 |
+
w1.add_data(data=wellsLayer, name='Wells')
|
922 |
+
w1.add_data(data=industryLayer, name='Industrial Wells')
|
923 |
+
return w1
|
924 |
+
|
925 |
active_scenarios = set()
|
926 |
text = ["## Scenario"]
|
927 |
|
928 |
|
929 |
def update_scenarioTitle(new_title):
|
930 |
global text
|
931 |
+
base_title = "Current state - 2024"
|
932 |
+
if Scenario_Button.value == "Population 2030":
|
933 |
+
if "Accelerated Growth" in text:
|
934 |
text.remove("Accelerated Growth")
|
935 |
+
if base_title in text:
|
936 |
+
text.remove(base_title)
|
937 |
text.append(new_title)
|
938 |
+
if Scenario_Button.value == "Population 2035":
|
939 |
+
if "Autonomous Growth" in text:
|
940 |
text.remove("Autonomous Growth")
|
941 |
+
if base_title in text:
|
942 |
+
text.remove(base_title)
|
943 |
text.append(new_title)
|
944 |
+
if Scenario_Button.value == "Current state - 2024":
|
945 |
if "Accelerated Growth" in text:
|
946 |
text.remove("Accelerated Growth")
|
947 |
if "Autonomous Growth" in text:
|
948 |
text.remove("Autonomous Growth")
|
949 |
+
else:
|
950 |
+
if Scenario_Button.value in text:
|
951 |
+
print(text)
|
952 |
+
else: text.append(new_title)
|
953 |
app_title.object = " - ".join(text)
|
954 |
print (text)
|
955 |
|
956 |
|
957 |
def update_title(event):
|
958 |
+
if ButtonSmallWells.value:
|
959 |
+
if "Closed Small Wells" in text:
|
960 |
+
print("Text already there")
|
961 |
+
else:
|
962 |
+
text.append("Closed Small Wells")
|
963 |
+
Measure1On()
|
964 |
+
if ButtonSmallWells.value == False:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
965 |
Measure1Off()
|
966 |
+
if "Closed Small Wells" in text:
|
967 |
+
text.remove("Closed Small Wells")
|
968 |
+
else:
|
969 |
+
print("Text not there")
|
970 |
+
if ButtonCloseNatura.value:
|
971 |
+
if "Closed Natura Wells" in text:
|
972 |
+
print("Text already there")
|
973 |
+
else:
|
974 |
+
text.append("Closed Natura Wells")
|
975 |
+
Measure2On()
|
976 |
+
if ButtonCloseNatura.value == False:
|
977 |
Measure2Off()
|
978 |
+
if "Closed Natura Wells" in text:
|
979 |
+
text.remove("Closed Natura Wells")
|
980 |
+
else: print("Text not there")
|
981 |
+
if ButtonImportWater.value:
|
982 |
+
if "Import Water" in text:
|
983 |
+
print("Text already there")
|
984 |
+
else:
|
985 |
+
text.append("Import Water")
|
986 |
+
Measure4On()
|
987 |
+
if ButtonImportWater.value == False:
|
|
|
|
|
|
|
988 |
Measure4Off()
|
989 |
+
if "Import Water" in text:
|
990 |
+
text.remove("Import Water")
|
991 |
+
else: print("Text not there")
|
992 |
+
if ButtonAddExtraIndustrial.value:
|
993 |
+
if "Use industrial exccess" in text:
|
994 |
+
print("Text already there")
|
995 |
+
else: text.append("Use industrial exccess")
|
996 |
+
Measure5On()
|
997 |
+
if ButtonAddExtraIndustrial.value == False:
|
998 |
+
Measure5Off()
|
999 |
+
if "Use industrial exccess" in text:
|
1000 |
+
text.remove("Use industrial exccess")
|
1001 |
+
else: print("Text not there")
|
1002 |
+
|
1003 |
|
1004 |
app_title.object = " - ".join(text)
|
1005 |
print(text)
|
|
|
1006 |
update_indicators()
|
1007 |
|
1008 |
|
1009 |
+
|
1010 |
+
def ScenarioBase():
|
1011 |
+
"""
|
1012 |
+
Implement the base scenario with a demand equal to year 2022.
|
1013 |
+
|
1014 |
+
Args:
|
1015 |
+
event: The event object.
|
1016 |
+
"""
|
1017 |
+
global demand_capita
|
1018 |
+
hexagons_filterd["Current Pop"]= hexagons_filterd["Pop2022"]
|
1019 |
+
hexagons_filterd["Water Demand"] = (
|
1020 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1021 |
+
) / 1000000
|
1022 |
+
update_scenarioTitle("Current state - 2024")
|
1023 |
+
print("Scenario Base restored")
|
1024 |
+
update_indicators()
|
1025 |
+
|
1026 |
def Scenario1():
|
1027 |
"""
|
1028 |
+
Implement the first scenario with a demand increase of 1.6%.
|
1029 |
|
1030 |
Args:
|
1031 |
event: The event object.
|
1032 |
"""
|
1033 |
+
global demand_capita
|
1034 |
+
hexagons_filterd["Current Pop"]= hexagons_filterd["Pop2022"]*1.0163
|
1035 |
+
|
1036 |
hexagons_filterd["Water Demand"] = (
|
1037 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1038 |
) / 1000000
|
1039 |
update_scenarioTitle("Autonomous Growth")
|
1040 |
print("Scenario 1 ran perfectly")
|
|
|
1042 |
|
1043 |
def Scenario2():
|
1044 |
"""
|
1045 |
+
Implement the second scenario with a demand increase of 2.09%.
|
1046 |
|
1047 |
|
1048 |
Args:
|
1049 |
event: The event object.
|
1050 |
"""
|
1051 |
+
hexagons_filterd["Current Pop"] = hexagons_filterd["Pop2022"]*1.0209
|
1052 |
|
|
|
|
|
|
|
1053 |
hexagons_filterd["Water Demand"] = (
|
1054 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1055 |
) / 1000000
|
1056 |
+
|
1057 |
update_scenarioTitle("Accelerated Growth")
|
1058 |
update_indicators()
|
1059 |
+
|
1060 |
+
def ScenarioSmallBussinessBase():
|
1061 |
+
global smallBussiness
|
1062 |
+
global demand_capita
|
1063 |
+
smallBussiness = 1.2
|
1064 |
+
update_indicators()
|
1065 |
+
|
1066 |
+
def ScenarioSmallBussiness1():
|
1067 |
+
global smallBussiness
|
1068 |
+
global demand_capita
|
1069 |
+
smallBussiness = 1.2*1.1
|
1070 |
+
update_indicators()
|
1071 |
+
|
1072 |
+
def ScenarioSmallBussiness2():
|
1073 |
+
global smallBussiness
|
1074 |
+
global demand_capita
|
1075 |
+
smallBussiness = 1.2*1.35
|
1076 |
+
update_indicators()
|
1077 |
+
|
1078 |
|
1079 |
def Measure1On():
|
|
|
1080 |
condition = active_wells_df["Max_permit"] < 5.00
|
1081 |
active_wells_df.loc[condition, "Active"] = False
|
1082 |
|
1083 |
+
# Update the checkboxes to reflect the new state
|
1084 |
+
for well_name in active_wells_df.loc[condition, "Name"]:
|
1085 |
+
checkboxes[well_name].value = False # Uncheck the checkbox
|
1086 |
|
1087 |
def Measure1Off():
|
|
|
1088 |
condition = active_wells_df["Max_permit"] >= 5.00
|
1089 |
active_wells_df.loc[condition, "Active"] = True
|
1090 |
|
1091 |
+
# Update the checkboxes to reflect the new state
|
1092 |
+
for well_name in active_wells_df.loc[condition, "Name"]:
|
1093 |
+
checkboxes[well_name].value = True # Check the checkbox
|
1094 |
|
1095 |
def Measure2On():
|
1096 |
"""
|
|
|
1098 |
"""
|
1099 |
active_wells_df.loc[active_wells_df["Name"] == "Archemerberg", "Active"] = False
|
1100 |
active_wells_df.loc[active_wells_df["Name"] == "Nijverdal", "Active"] = False
|
1101 |
+
|
1102 |
+
# Update the checkboxes to reflect the new state
|
1103 |
+
checkboxes["Archemerberg"].value = False
|
1104 |
+
checkboxes["Nijverdal"].value = False
|
1105 |
|
1106 |
def Measure2Off():
|
1107 |
"""
|
|
|
1110 |
active_wells_df.loc[active_wells_df["Name"] == "Archemerberg", "Active"] = True
|
1111 |
active_wells_df.loc[active_wells_df["Name"] == "Nijverdal", "Active"] = True
|
1112 |
|
1113 |
+
# Update the checkboxes to reflect the new state
|
1114 |
+
checkboxes["Archemerberg"].value = True
|
1115 |
+
checkboxes["Nijverdal"].value = True
|
1116 |
+
|
1117 |
def Measure3On():
|
1118 |
"""
|
1119 |
Activate the third measure (using smart meters).
|
1120 |
"""
|
1121 |
+
demand_capita = 0.135 * 0.9
|
1122 |
hexagons_filterd["Water Demand"] = (
|
1123 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1124 |
) / 1000000
|
1125 |
|
1126 |
def Measure3Off():
|
1127 |
"""
|
1128 |
Deactivate the third measure (using smart meters).
|
1129 |
"""
|
1130 |
+
demand_capita = 0.135
|
1131 |
hexagons_filterd["Water Demand"] = (
|
1132 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1133 |
) / 1000000
|
1134 |
|
1135 |
def Measure4On():
|
1136 |
"""
|
1137 |
Activate the fourth measure (importing water).
|
1138 |
"""
|
1139 |
+
# Assign the geometry directly with proper coordinates
|
1140 |
+
new_geometry = Point(253802.6, 498734.2) # Projected coordinates
|
1141 |
+
active_wells_df.loc[active_wells_df.shape[0]] = ["Imports", 3,0, 4.5, "Imported", True, 4.38, 4.38, 0,0,0,0,0,0.262,0, new_geometry]
|
1142 |
+
new_well = active_wells_df.loc[active_wells_df["Name"] == 'Imports']
|
1143 |
+
print(active_wells_df.tail())
|
1144 |
+
|
1145 |
+
new_well_gdf = gpd.GeoDataFrame(new_well, geometry='geometry')
|
1146 |
+
new_well_gdf = new_well_gdf.to_json()
|
1147 |
+
|
1148 |
+
folium.GeoJson(
|
1149 |
+
new_well_gdf,
|
1150 |
+
name="Import Water",
|
1151 |
+
zoom_on_click=True,
|
1152 |
+
popup=popup_well,
|
1153 |
+
tooltip=folium.GeoJsonTooltip(fields=["Name"], aliases=["Well Name:"]),
|
1154 |
+
marker=folium.Marker(
|
1155 |
+
icon=folium.Icon(
|
1156 |
+
icon_color="#f3f3f3", icon="arrow-up-from-ground-water", prefix="fa", color='cadetblue'
|
1157 |
+
)
|
1158 |
+
),
|
1159 |
+
show = True
|
1160 |
+
).add_to(m)
|
1161 |
+
# folium.GeoJson(
|
1162 |
+
# mainPipes,
|
1163 |
+
# name="Main Pipelines",
|
1164 |
+
# style_function= lambda x:{
|
1165 |
+
# "color": "#d9534f",
|
1166 |
+
# "weight": (4 if x["properties"]["Diameter_mm"]>350
|
1167 |
+
# else(2 if x["properties"]["Diameter_mm"]>250
|
1168 |
+
# else 1)),
|
1169 |
+
# "Opacity": 0.6,
|
1170 |
+
# },
|
1171 |
+
# show=False
|
1172 |
+
# )
|
1173 |
|
1174 |
def Measure4Off():
|
1175 |
"""
|
1176 |
Deactivate the fourth measure (importing water).
|
1177 |
"""
|
1178 |
+
try:
|
1179 |
+
# Use .loc to identify rows where 'Name' is 'Imports' and drop them
|
1180 |
+
active_wells_df.drop(active_wells_df.loc[active_wells_df["Name"] == 'Imports'].index, inplace=True)
|
1181 |
+
print(active_wells_df.tail())
|
1182 |
+
except KeyError:
|
1183 |
print("Row does not exist")
|
1184 |
|
1185 |
+
def Measure5On():
|
1186 |
+
global industrialExcess
|
1187 |
+
industrialExcess = industrial["Licensed"].sum()-industrial["Current_Extraction_2019"].sum()
|
1188 |
+
|
1189 |
+
def Measure5Off():
|
1190 |
+
global industrialExcess
|
1191 |
+
industrialExcess = 0
|
1192 |
+
|
1193 |
+
|
1194 |
|
1195 |
def Reset(event):
|
1196 |
"""
|
|
|
1199 |
Args:
|
1200 |
event: The event object.
|
1201 |
"""
|
1202 |
+
demand_capita = 0.135
|
1203 |
+
smallBussiness = 1.2
|
1204 |
+
hexagons_filterd["Current Pop"] = hexagons_filterd["Pop2022"]
|
1205 |
hexagons_filterd["Water Demand"] = (
|
1206 |
+
hexagons_filterd["Current Pop"] * demand_capita * smallBussiness * 365
|
1207 |
) / 1000000
|
1208 |
global active_wells_df
|
1209 |
active_wells_df = gpd.GeoDataFrame(
|
|
|
1214 |
"Max_permit": wells["Permit__Mm3_per_jr_"],
|
1215 |
"Balance area": wells["Balansgebied"],
|
1216 |
"Active": [True] * len(wells),
|
1217 |
+
"Current Extraction" : wells["Extraction_2023__Mm3_per_jr_"],
|
1218 |
"Value": wells["Extraction_2023__Mm3_per_jr_"],
|
1219 |
"OPEX_m3": wells["totOpex_m3"],
|
1220 |
"Drought_m3": wells["DroughtDamage_EUR_m3"],
|
|
|
1224 |
* wells["Extraction_2023__Mm3_per_jr_"]
|
1225 |
* 1000000,
|
1226 |
"OPEX": wells["totOpex_m3"] * wells["Extraction_2023__Mm3_per_jr_"] * 1000000,
|
1227 |
+
"CAPEX": 0,
|
1228 |
"geometry": wells["geometry"],
|
1229 |
}
|
1230 |
)
|
1231 |
+
Scenario_Button.value = 'Current state - 2024'
|
1232 |
+
ButtonDemand.value = 135
|
1233 |
+
ButtonSmallWells.value, ButtonCloseNatura.value, = False, False
|
1234 |
+
update_scenarioTitle("Current state - 2024")
|
1235 |
update_indicators()
|
1236 |
|
1237 |
def update_indicators(arg=None):
|
1238 |
total_extraction.value = calculate_total_extraction()
|
1239 |
total_opex.value = calculate_total_OPEX()
|
1240 |
+
total_capex.value = calculate_total_CAPEX()
|
1241 |
excess_cap.value = calculate_available()
|
1242 |
+
natura_value.value=calculate_affected_Natura()
|
1243 |
own_pane.value = calculate_ownership()
|
|
|
|
|
1244 |
co2_pane.value= calculate_total_CO2_cost()
|
1245 |
drought_pane.value = calculate_total_Drought_cost()
|
1246 |
+
# update_balance_opex()
|
1247 |
update_balance_lzh_gauges()
|
1248 |
total_demand.value = calculate_total_Demand()
|
1249 |
+
total_difference.value = calculate_difference()
|
1250 |
lzh.value = calculate_lzh()
|
1251 |
|
1252 |
|
1253 |
+
|
1254 |
# Initialize a dictionary to hold the active state and slider references
|
1255 |
active_wells = {}
|
1256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1257 |
# Initialize a dictionary to hold the balance area layouts
|
1258 |
balance_area_buttons = {}
|
1259 |
|
1260 |
+
# Initialize a dictionary to hold the sliders
|
1261 |
+
checkboxes = {}
|
1262 |
+
|
1263 |
# Setup Well Radio Buttons
|
1264 |
Radio_buttons = []
|
1265 |
Well_radioB = []
|
1266 |
+
options = ["-10%", "-20%", "Current", "+10%", "+20%", "Maximum Permit"]
|
1267 |
+
|
1268 |
+
|
1269 |
for index, row in wells.iterrows():
|
1270 |
wellName = row["Name"]
|
1271 |
current_value = row["Extraction_2023__Mm3_per_jr_"]
|
1272 |
+
maxValue = row["Permit__Mm3_per_jr_"]
|
1273 |
balance_area = row["Balansgebied"]
|
1274 |
radio_group = pn.widgets.RadioButtonGroup(
|
1275 |
name=wellName,
|
|
|
1279 |
)
|
1280 |
|
1281 |
# Add Checkbox and listeners
|
1282 |
+
checkbox = pn.widgets.Switch(name="Active", value=True)
|
1283 |
checkbox.param.watch(partial(toggle_well, well_name=wellName), "value")
|
1284 |
radio_group.param.watch(partial(update_radio, well_name=wellName), "value")
|
1285 |
|
1286 |
+
# Store the checkbox in the dictionary for later updates
|
1287 |
+
checkboxes[wellName] = checkbox
|
1288 |
+
|
1289 |
NameP = pn.pane.Str(wellName, styles={
|
1290 |
'font-size': "14px",
|
1291 |
'font-family': "Barlow",
|
1292 |
'font-weight': 'bold',
|
1293 |
})
|
1294 |
|
1295 |
+
Wellvalue = update_well_Value(wellName)
|
1296 |
+
well_style=styleWellValue(Wellvalue,maxValue)
|
1297 |
+
|
1298 |
+
extractionPerWell = pn.pane.HTML(object=update_well_Value_formatted(wellName), styles=well_style)
|
1299 |
+
NameState = pn.Row(NameP, checkbox)
|
1300 |
+
Well_radioB = pn.Column(NameState, extractionPerWell, radio_group, styles=miniBox_style)
|
|
|
1301 |
|
1302 |
# Add the well layout to the appropriate balance area layout
|
1303 |
if balance_area not in balance_area_buttons:
|
|
|
1305 |
balance_area_buttons[balance_area].append(Well_radioB)
|
1306 |
|
1307 |
# Store the active state and radio group reference along with the NamePane
|
1308 |
+
active_wells[wellName] = {"active": True, "value": current_value, "radio_group": radio_group, "name_pane": extractionPerWell}
|
1309 |
+
|
1310 |
+
|
1311 |
+
|
1312 |
+
|
1313 |
|
1314 |
# Create HTML Text for Wells Tab
|
1315 |
balance_area_Text = pn.pane.HTML('''
|
|
|
1317 |
, width=300, align="start")
|
1318 |
|
1319 |
# Create a layout for the radio buttons
|
1320 |
+
radioButton_layout = pn.Accordion(styles={'width': '80%', 'color':'#151931'})
|
1321 |
for balance_area, layouts in balance_area_buttons.items():
|
1322 |
balance_area_column = pn.Column(*layouts)
|
1323 |
radioButton_layout.append((balance_area, balance_area_column))
|
1324 |
|
1325 |
+
|
1326 |
|
1327 |
+
Scenario_Button =pn.widgets.RadioButtonGroup(name="Measures Button Group", options=['Current state - 2024','Population 2030','Population 2035'], button_type='warning', styles={
|
1328 |
+
'width': '93%', 'border': '3px' }, orientation='vertical'
|
1329 |
)
|
1330 |
Scenario_Button.param.watch(update_scenarios, "value")
|
1331 |
|
1332 |
+
ScenarioSmall_Button = pn.widgets.RadioButtonGroup(name="Measures Button Group", options=['Current state - 2024','Small Bussiness +10% Demand','Small Bussiness +35% Demand'], button_type='warning', styles={
|
1333 |
+
'width': '93%', 'border': '3px' }, orientation='vertical'
|
1334 |
+
)
|
1335 |
+
ScenarioSmall_Button.param.watch(update_scenariosSmall, "value")
|
|
|
1336 |
|
1337 |
+
# Button1 = pn.widgets.Button(
|
1338 |
+
# name='Autonomous growth', button_type="primary", width=300, margin=10,
|
1339 |
+
# )
|
1340 |
+
# Button1.param.watch(update_title, 'value')
|
1341 |
+
# Button1.on_click(Scenario1)
|
1342 |
+
|
1343 |
+
# Button2 = pn.widgets.Button(
|
1344 |
+
# name="Accelerated growth", button_type="primary", width=300, margin=10,
|
1345 |
+
# )
|
1346 |
+
# Button2.param.watch(update_title, 'value')
|
1347 |
+
# Button2.on_click(Scenario2)
|
1348 |
|
1349 |
+
ButtonSmallWells = pn.widgets.Toggle(
|
1350 |
name='Close Small Wells', button_type="primary", button_style="outline", width=300, margin=10,
|
1351 |
)
|
1352 |
+
ButtonSmallWells.param.watch(update_title, 'value')
|
1353 |
|
1354 |
+
ButtonCloseNatura = pn.widgets.Toggle(
|
1355 |
name='Close Natura 2000 Wells', button_type="primary", button_style="outline", width=300, margin=10,
|
1356 |
)
|
1357 |
+
ButtonCloseNatura.param.watch(update_title, 'value')
|
1358 |
|
1359 |
+
ButtonDemand = pn.widgets.RadioButtonGroup(name='Water Demand per Capita', options=[135,120,100,90], button_type='warning',
|
1360 |
+
width=80, orientation='horizontal', styles={
|
1361 |
+
'width': '97%', 'flex-wrap': 'no-wrap' }, align=("center", "center"))
|
1362 |
+
ButtonDemand.param.watch(current_demand, 'value')
|
1363 |
+
|
1364 |
+
# Button5= pn.Row(ButtonDemand, align=("center", "center"))
|
1365 |
|
1366 |
+
ButtonImportWater = pn.widgets.Toggle(
|
1367 |
name='Import Water', button_type="primary", button_style="outline", width=300, margin=10,
|
1368 |
)
|
1369 |
+
ButtonImportWater.param.watch(update_title, 'value')
|
1370 |
|
1371 |
+
ButtonAddExtraIndustrial = pn.widgets.Toggle(name="Add Industrial water", button_type="primary", button_style="outline", width=300, margin=10,)
|
1372 |
+
ButtonAddExtraIndustrial.param.watch(update_title, 'value')
|
1373 |
+
|
1374 |
+
ButtonReset = pn.widgets.Button(
|
1375 |
name='Reset', button_type='danger', width=300, margin=10
|
1376 |
)
|
1377 |
+
ButtonReset.on_click(Reset)
|
1378 |
|
1379 |
+
# textYears = pn.pane.HTML(
|
1380 |
+
# '''
|
1381 |
+
# <h3 align= "center" style="margin: 5px;"> Year Selection</h3><hr>
|
1382 |
+
# ''', width=300, align="start", styles={"margin": "5px"}
|
1383 |
+
# )
|
1384 |
|
1385 |
+
textDivider3 = pn.pane.HTML('''<hr class="dashed"> <h3 align= "center" style="margin: 5px;">Scenarios Small bussiness</h3><hr>''')
|
1386 |
+
|
1387 |
+
textScenarioPop = pn.pane.HTML(
|
1388 |
'''
|
1389 |
+
<h3 align= "center" style="margin: 5px;">Scenarios Population</h3><hr>'''
|
1390 |
# <b>Scenario with demand increase of 10% ↴</b>'''
|
1391 |
, width=300, align="start"
|
1392 |
)
|
1393 |
textB2 = pn.pane.HTML(
|
1394 |
'''<b>Scenario with demand increase of 35% ↴</b>''', width=300, align="start"
|
1395 |
+
|
1396 |
)
|
1397 |
+
textMeasureSupp = pn.pane.HTML(
|
1398 |
+
'''<hr class="dashed"><h3 align= "center" style="margin: 5px;"> Supply Measures </h3> <hr>
|
1399 |
<b>Close down all well locations with production less than 5Mm\u00b3/yr ↴</b>''', width=300, align="start", styles={}
|
1400 |
)
|
1401 |
+
textCloseNatura = pn.pane.HTML(
|
1402 |
'''
|
1403 |
<b>Close down all well locations in less than 100m from a Natura 2000 Area ↴</b>''', width=300, align="start", styles={}
|
1404 |
)
|
1405 |
|
1406 |
+
textMeasureDemand = pn.pane.HTML(
|
1407 |
+
'''<hr class="dashed"><h3 align= "center" style="margin: 5px;"> Demand Measures </h3> <hr>
|
1408 |
+
<b>Water Conusmption per Capita in L/d</b>''', width=300, align="start", styles={}
|
1409 |
)
|
1410 |
|
1411 |
+
textImport = pn.pane.HTML(
|
1412 |
'''
|
1413 |
<b>Importing water from WAZ Getelo, NVB Nordhorn and Haaksbergen</b>''', width=300, align="start", styles={}
|
1414 |
)
|
1415 |
|
1416 |
+
textIndustrial = pn.pane.HTML(
|
1417 |
+
'''
|
1418 |
+
<b>Add unused water from industrial permitse </b>''', width=300, align="start", styles={})
|
1419 |
+
|
1420 |
textEnd = pn.pane.HTML(
|
1421 |
'''<hr class="dashed">
|
1422 |
''', width=300, align="start", styles={}
|
1423 |
)
|
1424 |
|
1425 |
+
textDivider0 = pn.pane.HTML('''<hr class="dashed">''')
|
1426 |
+
textDivider1 = pn.pane.HTML('''<hr class="dashed">''')
|
1427 |
+
textDivider2 = pn.pane.HTML('''<hr class="dashed">''')
|
1428 |
+
|
1429 |
+
file_create = pn.widgets.Button(name='Create Report', button_type='primary', width=300, margin=10,)
|
1430 |
|
1431 |
+
file_download = pn.widgets.FileDownload(file="Vitalens_report.pdf", button_type="primary" , width=300, margin=10,)
|
1432 |
+
|
1433 |
+
# Create a spinner
|
1434 |
+
spinner = pn.indicators.LoadingSpinner(width=50, height=50, value=False)
|
1435 |
+
|
1436 |
+
def spacer(size):
|
1437 |
+
spacerVertical = pn.Spacer(height=size)
|
1438 |
+
return spacerVertical
|
1439 |
+
|
1440 |
+
disclaimer = pn.pane.HTML('''
|
1441 |
+
<div style="font-family: Arial, sans-serif; padding: 20px; color: #333;">
|
1442 |
+
<h1 style="color: #3850A0;">Welcome to Vitalens App</h1>
|
1443 |
+
<p>
|
1444 |
+
This application provides a comprehensive tool for managing and analyzing water wells within the Overijssel Zuid region. It enables users to monitor well extraction capacities, operational costs, environmental impact, and other critical factors affecting water supply planning.
|
1445 |
+
</p>
|
1446 |
+
|
1447 |
+
<h2>Key Features</h2>
|
1448 |
+
<ul>
|
1449 |
+
<li><strong>Real-Time Data Visualization:</strong> View and interact with dynamic maps that display well locations, extraction levels, and environmental restrictions.</li>
|
1450 |
+
<li><strong>Scenario Analysis:</strong> Simulate different water demand scenarios, including changes in population or small bussiness usage, to understand their potential impact on water supply and operational costs.</li>
|
1451 |
+
<li><strong>Environmental Cost Assessment:</strong> Estimate environmental costs associated with CO2 emissions and drought impact for each well, and assess potential restrictions due to protected areas like Natura2000.</li>
|
1452 |
+
<li><strong>Custom Well Management:</strong> Adjust well extraction levels and status (active or inactive) to optimize water resources and operational efficiency.</li>
|
1453 |
+
<li><strong>Interactive Data Exploration:</strong> Easily explore detailed well data, including security of supply, OPEX, environmental costs, and performance per balance area.</li>
|
1454 |
+
</ul>
|
1455 |
+
|
1456 |
+
<h2>Disclaimer</h2>
|
1457 |
+
<p>
|
1458 |
+
While this application provides valuable insights and data visualization for water management, the data used within this tool is based on various assumptions and estimates. Actual well performance, environmental impact, and operational costs may vary due to a range of factors such as real-time environmental conditions, regulatory changes, or unforeseen operational challenges.
|
1459 |
+
</p>
|
1460 |
+
<p>
|
1461 |
+
<strong>Please note:</strong> The results and outputs provided by this app should be used as indicative guidance rather than precise measurements. Users are advised to consult with local experts and use verified data sources for critical decision-making.
|
1462 |
+
</p>
|
1463 |
+
|
1464 |
+
<p style="color: #666; font-size: 14px;">
|
1465 |
+
© 2024 Vitalens App. Vitens and University of Twente. All rights reserved.
|
1466 |
+
</p>
|
1467 |
+
</div>
|
1468 |
+
|
1469 |
+
''', width=300)
|
1470 |
+
|
1471 |
+
# flaotingDisclaimer = pn.layout.html(disclaimer, name= "Welcome", margin=20)
|
1472 |
+
|
1473 |
+
|
1474 |
+
|
1475 |
+
scenario_layout = pn.Column(textScenarioPop, Scenario_Button, textDivider3, ScenarioSmall_Button, textEnd, ButtonReset, spacer(300), file_create, spinner, file_download, width=320)
|
1476 |
+
|
1477 |
+
measures_layout = pn.Column(textMeasureSupp, ButtonSmallWells,textCloseNatura, ButtonCloseNatura, textImport, ButtonImportWater, textIndustrial, ButtonAddExtraIndustrial, spacer(30), textMeasureDemand, ButtonDemand, textEnd, ButtonReset, file_create, spinner, file_download, width=320)
|
1478 |
+
|
1479 |
+
firstColumn = pn.Column(balance_area_Text,radioButton_layout,spacer(50), file_create, spinner, file_download)
|
1480 |
+
|
1481 |
+
tabs = pn.Tabs(("Welcome", disclaimer),("1. Scenarios", scenario_layout), ("2. Measures", measures_layout),("3. Well Capacities", firstColumn), closable=True, width = 320)
|
1482 |
|
1483 |
# MAIN WINDOW
|
1484 |
+
|
1485 |
map_pane = pn.pane.plot.Folium(update_layers(), sizing_mode="stretch_both")
|
1486 |
|
1487 |
+
|
1488 |
+
minusSVG= pn.pane.SVG('<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M6 12L18 12" stroke="#4139a7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>', max_width=40,sizing_mode='stretch_width', align='center')
|
1489 |
+
|
1490 |
+
equalSVG = pn.pane.SVG('<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 8C2.44772 8 2 8.44772 2 9C2 9.55228 2.44772 10 3 10H21C21.5523 10 22 9.55228 22 9C22 8.44772 21.5523 8 21 8H3Z" fill="#4139a7"></path> <path d="M3 14C2.44772 14 2 14.4477 2 15C2 15.5523 2.44772 16 3 16H21C21.5523 16 22 15.5523 22 15C22 14.4477 21.5523 14 21 14H3Z" fill="#4139a7"></path> </g></svg>', max_width=40,sizing_mode='stretch_width', align='center')
|
1491 |
+
|
1492 |
total_extraction = pn.indicators.Number(
|
1493 |
name="Total Supply",
|
1494 |
value=calculate_total_extraction(),
|
1495 |
format="{value:.2f} Mm\u00b3/yr",
|
1496 |
default_color='#3850a0',
|
1497 |
+
font_size="24pt",
|
1498 |
+
title_size="16pt",
|
1499 |
+
sizing_mode="stretch_width",
|
1500 |
+
align='center'
|
1501 |
+
)
|
1502 |
+
|
1503 |
+
total_demand = pn.indicators.Number(
|
1504 |
+
name="Total Water Demand",
|
1505 |
+
value=calculate_total_Demand,
|
1506 |
+
format="{value:0,.2f} Mm\u00b3/yr",
|
1507 |
+
font_size="24pt",
|
1508 |
+
title_size="16pt",
|
1509 |
+
default_color='#3850a0',
|
1510 |
+
sizing_mode="stretch_width", align='center'
|
1511 |
+
)
|
1512 |
+
|
1513 |
+
total_difference = pn.indicators.Number(
|
1514 |
+
name="Water Balance",
|
1515 |
+
value=calculate_difference(),
|
1516 |
+
format="{value:.2f} Mm\u00b3/yr",
|
1517 |
+
colors=[(0, '#d9534f'), (10, '#f2bf58'), (100, '#92c25b')],
|
1518 |
+
default_color='#3850a0',
|
1519 |
+
font_size="24pt",
|
1520 |
+
title_size="16pt",
|
1521 |
+
sizing_mode="stretch_width", align='center'
|
1522 |
)
|
1523 |
|
1524 |
total_opex = pn.indicators.Number(
|
|
|
1526 |
value=calculate_total_OPEX(),
|
1527 |
format="{value:0,.2f} M\u20AC/yr",
|
1528 |
default_color='#3850a0',
|
1529 |
+
font_size="24pt",
|
1530 |
+
title_size="16pt",
|
1531 |
align="center",
|
1532 |
sizing_mode="stretch_width"
|
1533 |
)
|
1534 |
|
1535 |
+
total_capex = pn.indicators.Number(
|
1536 |
+
name="Total CAPEX",
|
1537 |
+
value=calculate_total_CAPEX(),
|
1538 |
+
format="{value:0,.2f} M\u20AC/yr",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1539 |
default_color='#3850a0',
|
1540 |
+
font_size="24pt",
|
1541 |
+
title_size="16pt",
|
1542 |
align="center",
|
1543 |
sizing_mode="stretch_width"
|
1544 |
)
|
1545 |
|
1546 |
+
# balance_opex = calculate_total_OPEX_by_balance()
|
1547 |
+
# balance_opex_indicators = {
|
1548 |
+
# balance: pn.indicators.Number(
|
1549 |
+
# name=f"OPEX {balance}",
|
1550 |
+
# value=value,
|
1551 |
+
# format="{value:0,.2f} M\u20AC/yr",
|
1552 |
+
# default_color='#3850a0',
|
1553 |
+
# font_size="28pt",
|
1554 |
+
# title_size="18pt",
|
1555 |
+
# align="center",
|
1556 |
+
# )
|
1557 |
+
# for balance, value in balance_opex.items()
|
1558 |
+
# }
|
1559 |
+
|
1560 |
excess_cap = pn.indicators.Number(
|
1561 |
name="Excess Capacity",
|
1562 |
value=calculate_available(),
|
1563 |
format="{value:0.2f} Mm\u00b3/yr",
|
1564 |
default_color='#3850a0',
|
1565 |
+
font_size="16pt",
|
1566 |
+
title_size="10pt",
|
1567 |
+
align="center",
|
1568 |
+
sizing_mode="stretch_width"
|
1569 |
+
)
|
1570 |
+
|
1571 |
+
industrial_extract = pn.indicators.Number(
|
1572 |
+
name="Industrial Water Extraction",
|
1573 |
+
value=calculate_industrial_extract(),
|
1574 |
+
format="{value:0.2f} Mm\u00b3/yr",
|
1575 |
+
default_color='#3850a0',
|
1576 |
+
font_size="16pt",
|
1577 |
+
title_size="10pt",
|
1578 |
align="center",
|
1579 |
sizing_mode="stretch_width"
|
1580 |
)
|
1581 |
|
1582 |
+
right_pane = pn.Column(excess_cap,industrial_extract, align=("center", "center"), sizing_mode="stretch_height")
|
1583 |
+
|
1584 |
own_pane = pn.indicators.Number(
|
1585 |
name="Landownership",
|
1586 |
value=calculate_ownership(),
|
|
|
1593 |
sizing_mode="stretch_width"
|
1594 |
)
|
1595 |
|
1596 |
+
natura_value = pn.indicators.Number(
|
1597 |
name="Approximate Natura 2000 \n Affected area",
|
1598 |
value=calculate_affected_Natura(),
|
1599 |
format="{value:0.2f} Ha",
|
1600 |
default_color='#3850a0',
|
1601 |
+
font_size="14pt",
|
1602 |
+
title_size="10pt",
|
1603 |
align="center",
|
1604 |
sizing_mode="stretch_width",
|
1605 |
styles = {
|
|
|
1607 |
}
|
1608 |
)
|
1609 |
|
1610 |
+
# Use pn.bind to dynamically bind the number of stars to the pane
|
1611 |
+
football_svg_pane = pn.bind(generate_area_SVG, natura_value)
|
1612 |
+
natura_pane = pn.Column(natura_value, football_svg_pane)
|
1613 |
+
|
1614 |
+
|
1615 |
+
pipes_pane =pn.Column(generate_pipes_SVG("Reggeland","Stedenband",1), generate_pipes_SVG("Reggeland","Hof van Twente", 2), generate_pipes_SVG("Reggeland","Dinkelland", 1), generate_pipes_SVG("Hof van Twente", "Stedeban", 3), generate_pipes_SVG("Dinkelland", "Stedeband", 1), width=250)
|
1616 |
+
|
1617 |
co2_pane = pn.indicators.Number(
|
1618 |
name="CO\u2082 Emmission Cost",
|
1619 |
value=calculate_total_CO2_cost(),
|
1620 |
format="{value:0,.2f} M\u20AC/yr",
|
1621 |
default_color='#3850a0',
|
1622 |
+
font_size="24pt",
|
1623 |
+
title_size="16pt",
|
1624 |
)
|
1625 |
|
1626 |
drought_pane = pn.indicators.Number(
|
|
|
1628 |
value=calculate_total_Drought_cost(),
|
1629 |
format="{value:0,.2f} M\u20AC/yr",
|
1630 |
default_color='#3850a0',
|
1631 |
+
font_size="24pt",
|
1632 |
+
title_size="16pt",
|
1633 |
)
|
1634 |
|
1635 |
# df_display = pn.pane.Markdown(update_df_display())
|
1636 |
+
#df_Hexagons = pn.pane.DataFrame(hexagons_filterd.head(), name="Hexagons data")
|
1637 |
+
|
1638 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1639 |
|
1640 |
lzh = pn.indicators.Gauge(
|
1641 |
+
name=f"Overall LZH",
|
1642 |
value=calculate_lzh(),
|
1643 |
bounds=(0, 150),
|
1644 |
format="{value} %",
|
|
|
1647 |
"pointer": {"interStyle": {"color": "auto"}},
|
1648 |
"detail": {"valueAnimation": True, "color": "inherit"},
|
1649 |
},
|
1650 |
+
align=("center",'center'), height = 300, title_size = 14
|
1651 |
)
|
1652 |
lzh.param.watch(update_indicators, "value")
|
1653 |
|
|
|
1657 |
gauge = pn.indicators.Gauge(
|
1658 |
name=f"LZH \n{area}",
|
1659 |
value=value,
|
1660 |
+
bounds=(0, 630),
|
1661 |
format="{value} %",
|
1662 |
colors=[(0.2, "#D9534F"), (0.24, "#f2bf57"),(0.27, "#92C25B"), (1, "#8DCEC0")],
|
1663 |
custom_opts={
|
1664 |
"pointer": {"interStyle": {"color": "auto"}},
|
1665 |
"detail": {"valueAnimation": True, "color": "inherit"},
|
1666 |
},
|
1667 |
+
align=("center",'center'), height = 300, title_size = 14
|
1668 |
)
|
1669 |
balance_lzh_gauges[area] = gauge
|
1670 |
+
|
1671 |
+
|
1672 |
+
|
1673 |
+
def printResults(filename1):
|
1674 |
+
print("Button clicked, generating report...")
|
1675 |
+
|
1676 |
+
printingReport.styledWells(active_wells_df)
|
1677 |
+
printingReport.generate_matplotlib_stackbars(active_wells_df, filename1)
|
1678 |
+
# printingReport.generate_image_fromInd(pane=lzh, filename=filename2)
|
1679 |
+
printingReport.createPDF(filename1, Scenario_Button, ScenarioSmall_Button, ButtonSmallWells, ButtonCloseNatura, ButtonImportWater, ButtonAddExtraIndustrial, ButtonDemand,total_demand,total_extraction,total_opex,total_capex, co2_pane,drought_pane,natura_value)
|
1680 |
+
return print("File Created")
|
1681 |
+
|
1682 |
+
# When clicking the button, show the spinner and run the function
|
1683 |
+
def on_button_click(event):
|
1684 |
+
spinner.value = True # Show the spinner
|
1685 |
+
printResults("wells_Distribution.png")
|
1686 |
+
spinner.value = False # Hide the spinner when done
|
1687 |
+
pn.state.notifications.success('Report File created, you can download it now', duration=4000)
|
1688 |
+
|
1689 |
+
|
1690 |
+
file_create.on_click(on_button_click)
|
1691 |
+
|
1692 |
+
|
1693 |
+
# lzhTabs = pn.Tabs(lzh, *balance_lzh_gauges.values(), align=("center", "center"))
|
1694 |
+
Env_pane = pn.Column(co2_pane, drought_pane, sizing_mode="stretch_height")
|
1695 |
|
1696 |
+
indicatorsArea = pn.GridSpec(sizing_mode="scale_both")
|
1697 |
|
1698 |
+
indicatorsArea[0,0:2] = pn.Tabs(lzh, *balance_lzh_gauges.values(), align=("center", "center"), sizing_mode="stretch_height")
|
1699 |
+
indicatorsArea[0,3] = Env_pane
|
1700 |
|
1701 |
+
|
1702 |
+
CostPane = pn.Row(
|
1703 |
+
total_opex, total_capex, align=("center", "center")
|
1704 |
)
|
1705 |
|
1706 |
+
verticalLine = pn.pane.HTML(
|
1707 |
+
'''
|
1708 |
+
<hr style="width: 1px; height: 200px; display: inline-block;">
|
1709 |
+
''', sizing_mode="stretch_height"
|
1710 |
)
|
1711 |
|
1712 |
+
Supp_dem = pn.Row(
|
1713 |
+
total_extraction, minusSVG, total_demand, equalSVG, total_difference, verticalLine, right_pane, sizing_mode="scale_both"
|
1714 |
+
)
|
1715 |
|
1716 |
app_title = pn.pane.Markdown("## Scenario: Current State - 2024", styles={
|
1717 |
"text-align": "right",
|
1718 |
"color": "#00B893"
|
1719 |
})
|
1720 |
|
1721 |
+
|
1722 |
+
|
1723 |
+
main1 = pn.GridSpec(sizing_mode="scale_both")
|
1724 |
main1[0, 0] = pn.Row(map_pane)
|
1725 |
|
1726 |
+
main2 = pn.GridSpec(sizing_mode="scale_both")
|
1727 |
+
main2[0,0:2] = pn.Row(
|
1728 |
natura_pane,
|
|
|
|
|
1729 |
sizing_mode="scale_width",
|
1730 |
scroll=True
|
1731 |
)
|
1732 |
+
main2[0,2] = pn.Column(
|
1733 |
+
pipes_pane,
|
1734 |
+
sizing_mode="scale_width",
|
1735 |
+
scroll=True
|
1736 |
+
)
|
1737 |
+
|
1738 |
+
|
1739 |
+
|
1740 |
+
main1[0, 1] = pn.Column(app_title, indicatorsArea, textDivider0, Supp_dem, textDivider1, CostPane, textDivider2, main2, sizing_mode="stretch_both")
|
1741 |
|
|
|
1742 |
|
1743 |
Box = pn.template.MaterialTemplate(
|
1744 |
title="Vitalens",
|
|
|
1747 |
main=[main1],
|
1748 |
header_background= '#3850a0',
|
1749 |
header_color= '#f2f2ed',
|
1750 |
+
sidebar_width = 350,
|
1751 |
+
collapsed_sidebar = False
|
1752 |
)
|
1753 |
|
1754 |
+
|
1755 |
+
|
1756 |
+
|
1757 |
def total_extraction_update():
|
1758 |
"""
|
1759 |
Update the total extraction and related indicators.
|
1760 |
"""
|
1761 |
total_extraction.value = calculate_total_extraction()
|
|
|
1762 |
total_opex.value = calculate_total_OPEX()
|
1763 |
+
# update_balance_opex()
|
|
|
1764 |
update_balance_lzh_gauges()
|
1765 |
update_indicators()
|
1766 |
+
total_demand.value = calculate_total_Demand()
|
1767 |
+
total_difference.value = calculate_difference()
|
1768 |
+
calculate_affected_Natura()
|
1769 |
+
map_pane
|
1770 |
co2_pane.value = calculate_total_CO2_cost()
|
1771 |
drought_pane.value = calculate_total_Drought_cost()
|
1772 |
|
1773 |
|
1774 |
total_extraction_update()
|
1775 |
+
Box.servable()
|
printingReport.py
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import fpdf
|
4 |
+
from fpdf import FPDF
|
5 |
+
import time
|
6 |
+
import pandas as pd
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
import dataframe_image as dfi
|
9 |
+
from html2image import Html2Image
|
10 |
+
import panel as pn
|
11 |
+
|
12 |
+
|
13 |
+
|
14 |
+
TITLE = "VITALENS Report"
|
15 |
+
WIDTH = 210
|
16 |
+
HEIGHT = 297
|
17 |
+
|
18 |
+
class PDF(FPDF):
|
19 |
+
|
20 |
+
def footer(self):
|
21 |
+
self.set_y(-15)
|
22 |
+
self.add_font('DejaVu', 'I', r'Assets\fonts\DejaVuSansCondensed-Oblique.ttf', uni=True)
|
23 |
+
self.set_font('DejaVu', 'I', 8)
|
24 |
+
self.set_text_color(128)
|
25 |
+
self.cell(0, 10, 'Page ' + str(self.page_no()), 0, 0, 'C')
|
26 |
+
|
27 |
+
def color_pos_neg_value(value):
|
28 |
+
if value < 0:
|
29 |
+
color = 'red'
|
30 |
+
elif value > 0:
|
31 |
+
color = 'green'
|
32 |
+
else:
|
33 |
+
color = 'black'
|
34 |
+
return 'color: %s' % color
|
35 |
+
|
36 |
+
def styledWells(df):
|
37 |
+
df["CO2 cost"] = df.loc[df["Active"] == True, "Value"] * df.loc[df["Active"] == True, "CO2_m3"]
|
38 |
+
df["Draught damage cost"] = df.loc[df["Active"] == True, "Value"] * df.loc[df["Active"] == True, "Drought_m3"]
|
39 |
+
df["New Extraction"]= df.loc[df["Active"] == True, "Value"]
|
40 |
+
|
41 |
+
df["Extraction PCT Change"]= ((df["New Extraction"]-df["Current Extraction"])/df["Current Extraction"])*100
|
42 |
+
|
43 |
+
df = df.drop(columns=["Num_Wells",'Ownership','OPEX_m3','Drought_m3', "CO2_m3", 'Env_m3','envCost',"geometry"])
|
44 |
+
df = df.rename(columns={'Max_permit': 'Maximum permit' })
|
45 |
+
df = df[["Name","Balance area","Active","Current Extraction","New Extraction", "Maximum permit", "CO2 cost", "Draught damage cost", "Extraction PCT Change" ]]
|
46 |
+
|
47 |
+
|
48 |
+
# Apply styling to the DataFrame
|
49 |
+
styled_df = df.style.format({
|
50 |
+
'Maximum permit': "{:.2f} Mm\u00b3/yr",
|
51 |
+
'Current Extraction': '{:.2f} Mm\u00b3/yr',
|
52 |
+
'New Extraction': '{:.2f} Mm\u00b3/yr',
|
53 |
+
'CO2 cost': "{:.2f} M\u20AC/yr",
|
54 |
+
'Draught damage cost': "{:.2f} M\u20AC/yr",
|
55 |
+
'OPEX': "{:0,.0f} M\u20AC",
|
56 |
+
'CAPEX': "{:0,.0f} M\u20AC",
|
57 |
+
'Extraction PCT Change': "{:.2f}%",
|
58 |
+
}).background_gradient(subset=['Extraction PCT Change'], cmap='RdYlGn') \
|
59 |
+
.set_caption("Water Extraction and Cost Overview")
|
60 |
+
|
61 |
+
# Convert the styled DataFrame to HTML
|
62 |
+
html = styled_df.to_html()
|
63 |
+
|
64 |
+
# Use html2image to convert the HTML to an image
|
65 |
+
hti = Html2Image(size=(800, 600))
|
66 |
+
|
67 |
+
# Save the HTML content as an image (adjust file path as needed)
|
68 |
+
hti.screenshot(html_str=html, save_as='Wells_DF.png')
|
69 |
+
|
70 |
+
def generate_matplotlib_stackbars(df, filename):
|
71 |
+
|
72 |
+
# Create subplot and bar
|
73 |
+
fig, ax = plt.subplots()
|
74 |
+
ax.plot(df['Name'].values, df['Value'].values, color="#E63946", marker='D')
|
75 |
+
|
76 |
+
# Set Title
|
77 |
+
ax.set_title('Water extraction per Location', fontweight="bold")
|
78 |
+
|
79 |
+
# Set xticklabels
|
80 |
+
ax.set_xticks(range(len(df['Name']))) # Set the tick positions based on the number of labels
|
81 |
+
ax.set_xticklabels(df['Name'].values, rotation=60)
|
82 |
+
plt.xticks(df['Name'].values)
|
83 |
+
|
84 |
+
# Set ylabel
|
85 |
+
ax.set_ylabel('Total Water extractedn in Mm\u00b3/yr')
|
86 |
+
|
87 |
+
# Save the plot as a PNG
|
88 |
+
plt.savefig(filename, dpi=300, bbox_inches='tight', pad_inches=0)
|
89 |
+
|
90 |
+
|
91 |
+
def generate_image_fromInd(pane, filename):
|
92 |
+
# Serve or save the panel as HTML first
|
93 |
+
|
94 |
+
lzh_pane =pn.Column(pane)
|
95 |
+
html_content = lzh_pane.save("./Assets/lzh_panel.html", embed=True)
|
96 |
+
|
97 |
+
# Use html2image to convert the HTML to an image
|
98 |
+
hti = Html2Image()
|
99 |
+
|
100 |
+
# Capture screenshot from the saved HTML file
|
101 |
+
hti.screenshot(html_file="./Assets/lzh_panel.html", save_as=filename)
|
102 |
+
|
103 |
+
|
104 |
+
|
105 |
+
def create_letterhead(pdf, WIDTH):
|
106 |
+
pdf.image("https://uavonline.nl/wp-content/uploads/2020/11/vitens-logo-1.png", 0, 0, WIDTH/5)
|
107 |
+
|
108 |
+
def create_title(title, pdf):
|
109 |
+
|
110 |
+
# Add main title
|
111 |
+
pdf.set_font('DejaVu', 'b', 20)
|
112 |
+
pdf.ln(20)
|
113 |
+
pdf.write(5, title)
|
114 |
+
pdf.ln(10)
|
115 |
+
|
116 |
+
# Add date of report
|
117 |
+
pdf.set_font('DejaVu', '', 10)
|
118 |
+
pdf.set_text_color(r=128,g=128,b=128)
|
119 |
+
today = time.strftime("%d/%m/%Y")
|
120 |
+
pdf.write(4, f'{today}')
|
121 |
+
|
122 |
+
# Add line break
|
123 |
+
pdf.ln(10)
|
124 |
+
|
125 |
+
# def write_to_pdf(pdf, words):
|
126 |
+
|
127 |
+
# # Set text colour, font size, and font type
|
128 |
+
# pdf.set_text_color(r=0,g=0,b=0)
|
129 |
+
# pdf.set_font('DejaVu', '', 12)
|
130 |
+
|
131 |
+
# pdf.write(5, words)
|
132 |
+
|
133 |
+
def createPDF(filename1, popScenario, smallScenario, button3, button4, button6, button7, ButtonDemand, TotalDemand, totalSupply, OPEX, CAPEX, CO2, ENVDmg,Natura):
|
134 |
+
pdf = PDF() # A4 (210 by 297 mm)
|
135 |
+
|
136 |
+
|
137 |
+
# Add a Unicode free font
|
138 |
+
# Get the directory where the script is being run
|
139 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
140 |
+
fontPath= os.path.join(script_dir, 'Assets', 'fonts', 'DejaVuSansCondensed.ttf')
|
141 |
+
fontPathBold = os.path.join(script_dir, 'Assets', 'fonts', 'DejaVuSansCondensed-Bold.ttf')
|
142 |
+
pdf.add_font('DejaVu', '', fontPath, uni=True)
|
143 |
+
pdf.add_font('DejaVu', 'b',fontPathBold, uni=True)
|
144 |
+
'''
|
145 |
+
First Page of PDF
|
146 |
+
'''
|
147 |
+
# Add Page
|
148 |
+
pdf.add_page()
|
149 |
+
|
150 |
+
# Add lettterhead and title
|
151 |
+
create_letterhead(pdf, WIDTH)
|
152 |
+
create_title(TITLE, pdf)
|
153 |
+
|
154 |
+
pdf.set_text_color(r=30,g=30,b=30)
|
155 |
+
## Add Scenario Text
|
156 |
+
pdf.set_font('DejaVu', 'b', 16)
|
157 |
+
pdf.write(5, "1. Secenario Configuration:")
|
158 |
+
pdf.ln(15)
|
159 |
+
pdf.set_font('DejaVu', '', 11)
|
160 |
+
pdf.write(10, ("\u2022 Population Scenario: " +popScenario.value))
|
161 |
+
pdf.ln(10)
|
162 |
+
pdf.write(10, ("\u2022 Small Business Scenario: " +smallScenario.value))
|
163 |
+
pdf.ln(15)
|
164 |
+
|
165 |
+
##ADD some measures text
|
166 |
+
pdf.set_font('DejaVu', 'b', 16)
|
167 |
+
pdf.write(5, "2. Measures configuration:")
|
168 |
+
pdf.ln(15)
|
169 |
+
pdf.set_font('DejaVu', '', 11)
|
170 |
+
pdf.write(10, ("\u2022 Closed Small wells: " +str(button3.value)))
|
171 |
+
pdf.ln(10)
|
172 |
+
pdf.write(10, ("\u2022 Closed Natura 2000 Wells: " +str(button4.value)))
|
173 |
+
pdf.ln(10)
|
174 |
+
pdf.write(10, ("\u2022 Imported Water from WAZ Getelo, NVB Nordhorn and Haaksbergen " +str(button6.value)))
|
175 |
+
pdf.ln(10)
|
176 |
+
pdf.write(10, ("\u2022 Water demand per capita: " +str(ButtonDemand.value) + " L/d"))
|
177 |
+
pdf.ln(10)
|
178 |
+
pdf.write(10, ("\u2022 Using Industrial water permits excess: " +str(button7.value)))
|
179 |
+
pdf.ln(15)
|
180 |
+
|
181 |
+
|
182 |
+
# Add some words to PDF
|
183 |
+
pdf.set_font('DejaVu', 'b', 16)
|
184 |
+
pdf.write(5, "3. Wells Configuration:")
|
185 |
+
pdf.ln(15)
|
186 |
+
pdf.set_font('DejaVu', '', 11)
|
187 |
+
pdf.write(10, "The table below illustrates the Water extraction configuration for each wells location:")
|
188 |
+
pdf.ln(10)
|
189 |
+
|
190 |
+
# Add table
|
191 |
+
pdf.image("Wells_DF.png", w=WIDTH-40)
|
192 |
+
pdf.ln(5)
|
193 |
+
|
194 |
+
# Add some words to PDF
|
195 |
+
pdf.set_font('DejaVu', 'b', 16)
|
196 |
+
pdf.write(5, "4. Indicators Report:")
|
197 |
+
pdf.ln(15)
|
198 |
+
pdf.set_font('DejaVu', '', 11)
|
199 |
+
|
200 |
+
|
201 |
+
pdf.write(10, ("\u2022 Total Supply: " +f"{totalSupply.value:.2f} Mm\u00b3/yr"))
|
202 |
+
pdf.ln(10)
|
203 |
+
pdf.write(10, ("\u2022 Total Demand: " +f"{TotalDemand.value:.2f} Mm\u00b3/yr"))
|
204 |
+
pdf.ln(10)
|
205 |
+
pdf.write(10, ("\u2022 Leverenszekerheid " +f"{totalSupply.value * 100 / TotalDemand.value:.2f}"+"%"))
|
206 |
+
pdf.ln(10)
|
207 |
+
pdf.write(10, ("\u2022 OPEX " +f'{OPEX.value:.2f}'+ " M\u20AC/yr"))
|
208 |
+
pdf.ln(10)
|
209 |
+
pdf.write(10, ("\u2022 CAPEX " +f'{CAPEX.value:0,.2f}' + " M\u20AC/yr"))
|
210 |
+
pdf.ln(10)
|
211 |
+
pdf.write(10, ("\u2022 CO2 emission cost " +f'{CO2.value:0,.2f}' + " M\u20AC/yr"))
|
212 |
+
pdf.ln(10)
|
213 |
+
pdf.write(10, ("\u2022 Drought Damage cost " +f'{ENVDmg.value:0,.2f}' + " M\u20AC/yr"))
|
214 |
+
pdf.ln(10)
|
215 |
+
pdf.write(10, ("\u2022 Natura200 affected area " +f'{Natura.value:.2f}'+ " Ha"))
|
216 |
+
pdf.ln(10)
|
217 |
+
|
218 |
+
# Add some words to PDF
|
219 |
+
pdf.write(5, "Water extraction per location:")
|
220 |
+
pdf.ln(10)
|
221 |
+
|
222 |
+
# Add the generated visualisations to the PDF
|
223 |
+
pdf.image(filename1, w=WIDTH-20)
|
224 |
+
# pdf.image(filename2, 5, 200, WIDTH/2-10)
|
225 |
+
|
226 |
+
pdf.ln(10)
|
227 |
+
|
228 |
+
# Generate the PDF
|
229 |
+
pdf.output("Vitalens_report.pdf", 'F')
|