cygnus26 commited on
Commit
55c093d
·
verified ·
1 Parent(s): d976fb8

Upload 2 files

Browse files
Files changed (2) hide show
  1. Vitalens.py +754 -290
  2. 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
- from shapely.geometry import shape, Polygon
 
 
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: wrap !important;
55
  align-items: center;
56
  }
57
 
@@ -70,34 +106,50 @@ hr.dashed {
70
  width: fit-content;
71
  }
72
 
73
- .bk-btn-warning{
74
- background-position: center;
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
- demand_capita = 0.1560
 
 
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"] * 0.1560 * 365 / 1000000,
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
- total = active_wells_df[active_wells_df["Active"]]["Value"].sum()
 
203
  return total
204
 
205
- def calculate_industrial():
206
- total = industrial["Current_Extraction_2019"].sum()
 
 
 
 
 
 
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
- industrial_total = industrial["Licensed"].sum() - industrial["Current_Extraction_2019"].sum()
221
- return total + industrial_total
 
 
 
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/1000000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- Update OPEX indicators for balance areas.
260
- """
261
- balance_opex = calculate_total_OPEX_by_balance()
262
- for balance, indicator in balance_opex_indicators.items():
263
- indicator.value = balance_opex.get(balance, 0)
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
- return total * 629387.503078 / 100000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- elif event.new == "Agreement":
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 = update_well_Name(well_name)
411
  update_indicators()
412
 
413
  def update_scenarios(event):
414
- if event.new == "Autonomous Growth +10% Demand":
415
  Scenario1()
416
  print('scenario 1 active')
417
- if event.new == "Accelerated Growth +35% Demand":
418
  print('scenario 2 active')
419
  Scenario2()
420
- if event.new == 'Current state - 2024':
421
- demand_capita = 0.156
422
- hexagons_filterd["Water Demand"] = (
423
- hexagons_filterd["Pop2022"] * demand_capita * 365
424
- ) / 1000000
425
- update_scenarioTitle("VITALENS - Current Situation 2024")
426
  update_indicators()
427
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
- def update_well_Name(well_name):
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
- # Function to update the yearCal variable
443
- # def update_year(event):
444
- # if event.new == 2024:
445
- # hexagons_filterd["Current Pop"] = round(
446
- # hexagons_filterd["Pop2022"] * ((1 + growRate) ** float((2024 - 2022))), 0
447
- # )
448
- # if event.new == 2035:
449
- # hexagons_filterd["Current Pop"] = round(
450
- # hexagons_filterd["Pop2022"] * ((1 + growRate) ** float((2035 - 2022))), 0
451
- # )
452
- # hexagons_filterd["Water Demand"] = (
453
- # hexagons_filterd["Current Pop"] * 0.1560 * 365
454
- # ) / 1000000
455
- # update_indicators() # Update the total demand indicator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- total_demand = hexagons_filterd[
513
- hexagons_filterd["Balance Area"] == area
514
- ][ # Demand per area
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=["Balance Area", "Water Demand", "Current Pop", "Type"],
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=hexagons_filterd["Water Demand"].quantile(0.0),
585
- vmax=hexagons_filterd["Water Demand"].quantile(1),
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
- global active_wells_df
610
- m = folium.Map(
611
- location=[52.38, 6.7], zoom_start=10,
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="#F9F6EE", icon="arrow-up-from-ground-water", prefix="fa"
625
  )
626
  ),
627
  ).add_to(m)
628
 
629
  folium.GeoJson(
630
- industrial,
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
- hexagons_filterd,
645
- name="Hexagons",
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"]["Balance Area"] is not None
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": "#ce9ad6",
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 = "VITALENS - Current Situation 2024"
728
- if Scenario_Button.value == "Autonomous Growth +10% Demand":
729
- if ("Accelerated Growth" or base_title) in text:
730
  text.remove("Accelerated Growth")
 
 
731
  text.append(new_title)
732
- if Scenario_Button.value == "Accelerated Growth +35% Demand":
733
- if ("Autonomous Growth" or base_title) in text:
734
  text.remove("Autonomous Growth")
 
 
735
  text.append(new_title)
736
- if Scenario_Button.value == "Current State - 2024":
737
  if "Accelerated Growth" in text:
738
  text.remove("Accelerated Growth")
739
  if "Autonomous Growth" in text:
740
  text.remove("Autonomous Growth")
741
- text.append(new_title)
742
-
 
 
743
  app_title.object = " - ".join(text)
744
  print (text)
745
 
746
 
747
  def update_title(event):
748
- global active_wells_df
749
- global text
750
- # if (Button1.value or Measures_Button.value == "Autonomous Growth" ):
751
- # if "Accelerated Growth" in text:
752
- # text.remove("Accelerated Growth")
753
- # text.append("Autonomous Growth")
754
- # if (Button2.value or Measures_Button.value == "Accelerated Growth"):
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
- try: text.remove("Closed Small Wells")
769
- except: print("Text not there")
770
- if Button4.value:
771
- text.append("Closed Natura Wells")
772
- Measure2On()
773
- if Button4.value == False:
 
 
 
 
 
774
  Measure2Off()
775
- try: text.remove("Closed Natura Wells")
776
- except: print("Text not there")
777
- if Button5.value:
778
- text.append("Use of Smart Meters")
779
- Measure3On()
780
- if Button5.value == False:
781
- Measure3Off()
782
- try: text.remove("Use of Smart Meters")
783
- except: print("Text not there")
784
- if Button6.value:
785
- text.append("Import Water")
786
- Measure4On()
787
- if Button6.value == False:
788
  Measure4Off()
789
- try: text.remove("Import Water")
790
- except: print("Text not there")
 
 
 
 
 
 
 
 
 
 
 
 
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 10%.
801
 
802
  Args:
803
  event: The event object.
804
  """
805
- global hexagons_filterd
806
- demand_capita = 0.156*1.1
 
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 35%.
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 = 0.156 * 0.9
863
  hexagons_filterd["Water Demand"] = (
864
- hexagons_filterd["Pop2022"] * demand_capita * 365
865
  ) / 1000000
866
 
867
  def Measure3Off():
868
  """
869
  Deactivate the third measure (using smart meters).
870
  """
871
- demand_capita = 0.156
872
  hexagons_filterd["Water Demand"] = (
873
- hexagons_filterd["Pop2022"] * demand_capita * 365
874
  ) / 1000000
875
 
876
  def Measure4On():
877
  """
878
  Activate the fourth measure (importing water).
879
  """
880
- active_wells_df.loc[active_wells_df.shape[0]] = ["Imports", 3,0, 4.5, "Imported", True, 4.38, 0,0,0,0,0,0, "POINT (253802.6,498734.2)"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
 
882
  def Measure4Off():
883
  """
884
  Deactivate the fourth measure (importing water).
885
  """
886
- try:
887
- active_wells_df.drop(active_wells_df[active_wells_df["Name"]=='Imports'].index, inplace=True)
888
- except:
 
 
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.156
 
 
900
  hexagons_filterd["Water Demand"] = (
901
- hexagons_filterd["Pop2022"] * demand_capita * 365
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
- Button1.value, Button2.value, Button3.value, Button4.value, Button5.value = False, False, False, False, False
925
- update_scenarioTitle("VITALENS - Current Situation")
 
 
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", "Agreement"]
 
 
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.Checkbox(name="Active", value=True)
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
- NamePane = pn.pane.Str(update_well_Name(wellName), styles={
989
- 'font-family': 'Roboto',
990
- 'font-size': "16px",
991
- 'font-weight': 'bold'
992
- })
993
- NameState = pn.Row(NameP, pn.Spacer(), checkbox)
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": NamePane}
 
 
 
 
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': '97%', 'color':'#151931'})
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
- firstColumn = pn.Column(balance_area_Text,radioButton_layout)
1016
 
1017
- Scenario_Button =pn.widgets.RadioButtonGroup(name="Measures Button Group", options=['Current state - 2024','Autonomous Growth +10% Demand','Accelerated Growth +35% Demand'], button_type='warning', styles={
1018
- 'width': '97%', }
1019
  )
1020
  Scenario_Button.param.watch(update_scenarios, "value")
1021
 
1022
- Button1 = pn.widgets.Button(
1023
- name='Autonomous growth', button_type="primary", width=300, margin=10,
1024
- )
1025
- Button1.param.watch(update_title, 'value')
1026
- Button1.on_click(Scenario1)
1027
 
1028
- Button2 = pn.widgets.Button(
1029
- name="Accelerated growth", button_type="primary", width=300, margin=10,
1030
- )
1031
- Button2.param.watch(update_title, 'value')
1032
- Button2.on_click(Scenario2)
 
 
 
 
 
 
1033
 
1034
- Button3 = pn.widgets.Toggle(
1035
  name='Close Small Wells', button_type="primary", button_style="outline", width=300, margin=10,
1036
  )
1037
- Button3.param.watch(update_title, 'value')
1038
 
1039
- Button4 = pn.widgets.Toggle(
1040
  name='Close Natura 2000 Wells', button_type="primary", button_style="outline", width=300, margin=10,
1041
  )
1042
- Button4.param.watch(update_title, 'value')
1043
 
1044
- Button5 = pn.widgets.Toggle(
1045
- name='Include Smart Meters', button_type="primary", button_style="outline", width=300, margin=10,
1046
- )
1047
- Button5.param.watch(update_title, 'value')
 
 
1048
 
1049
- Button6 = pn.widgets.Toggle(
1050
  name='Import Water', button_type="primary", button_style="outline", width=300, margin=10,
1051
  )
1052
- Button6.param.watch(update_title, 'value')
1053
 
1054
- ButtonR = pn.widgets.Button(
 
 
 
1055
  name='Reset', button_type='danger', width=300, margin=10
1056
  )
1057
- ButtonR.on_click(Reset)
1058
 
1059
- textYears = pn.pane.HTML(
1060
- '''
1061
- <h3 align= "center" style="margin: 5px;"> Year Selection</h3><hr>
1062
- ''', width=300, align="start", styles={"margin": "5px"}
1063
- )
1064
 
1065
- textB1 = pn.pane.HTML(
 
 
1066
  '''
1067
- <h3 align= "center" style="margin: 5px;"> Scenarios</h3><hr>'''
1068
  # <b>Scenario with demand increase of 10% &#8628;</b>'''
1069
  , width=300, align="start"
1070
  )
1071
  textB2 = pn.pane.HTML(
1072
  '''<b>Scenario with demand increase of 35% &#8628;</b>''', width=300, align="start"
 
1073
  )
1074
- textB3 = pn.pane.HTML(
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 &#8628;</b>''', width=300, align="start", styles={}
1077
  )
1078
- textB4 = pn.pane.HTML(
1079
  '''
1080
  <b>Close down all well locations in less than 100m from a Natura 2000 Area &#8628;</b>''', width=300, align="start", styles={}
1081
  )
1082
 
1083
- textB5 = pn.pane.HTML(
1084
- '''
1085
- <b>Installation of Water Smartmeters: Reduction of 10% on demand &#8628;</b>''', width=300, align="start", styles={}
1086
  )
1087
 
1088
- textB6 = pn.pane.HTML(
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
- scenario_layout = pn.Column(textB1, Scenario_Button, textB3, Button3, textB4, Button4, textB5, Button5, textB6, Button6, textEnd, ButtonR)
 
 
 
 
1099
 
1100
- tabs = pn.Tabs(("Well Capacities", firstColumn), ("Scenarios", scenario_layout))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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="28pt",
1111
- title_size="18pt",
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="28pt",
1121
- title_size="18pt",
1122
  align="center",
1123
  sizing_mode="stretch_width"
1124
  )
1125
 
1126
- balance_opex = calculate_total_OPEX_by_balance()
1127
- balance_opex_indicators = {
1128
- balance: pn.indicators.Number(
1129
- name=f"OPEX {balance}",
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="20pt",
1146
- title_size="12pt",
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="20pt",
1157
- title_size="12pt",
 
 
 
 
 
 
 
 
 
 
 
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
- natura_pane = pn.indicators.Number(
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="20pt",
1180
- title_size="12pt",
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="20pt",
1194
- title_size="12pt",
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="20pt",
1203
- title_size="12pt",
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 \n Leveringszekerheid",
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, 500),
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
- lzhTabs = pn.Tabs(lzh, *balance_lzh_gauges.values(), align=("center", "center"), sizing_mode="scale_width"
1252
- )
1253
 
1254
- opexTabs = pn.Tabs(
1255
- total_opex, *balance_opex_indicators.values(), align=("center", "center")
 
1256
  )
1257
 
1258
- Supp_dem = pn.Row(
1259
- total_extraction, pn.Spacer(width=50), total_demand, sizing_mode="stretch_width"
 
 
1260
  )
1261
 
1262
- Env_pane = pn.Column(co2_pane, drought_pane, sizing_mode="scale_width")
1263
- Extra_water_pane = pn.Column(industrial_cap,excess_cap, sizing_mode="scale_width")
 
1264
 
1265
  app_title = pn.pane.Markdown("## Scenario: Current State - 2024", styles={
1266
  "text-align": "right",
1267
  "color": "#00B893"
1268
  })
1269
 
1270
- main1 = pn.GridSpec(sizing_mode="stretch_both")
 
 
1271
  main1[0, 0] = pn.Row(map_pane)
1272
 
1273
- main2 = pn.Row(
 
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 = 325
 
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
- total_demand.value = calculate_total_Demand()
1301
- update_balance_opex()
1302
  update_balance_lzh_gauges()
1303
  update_indicators()
1304
- natura_pane.value = calculate_affected_Natura()
1305
- map_pane.object = update_layers()
 
 
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% &#8628;</b>'''
1391
  , width=300, align="start"
1392
  )
1393
  textB2 = pn.pane.HTML(
1394
  '''<b>Scenario with demand increase of 35% &#8628;</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 &#8628;</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 &#8628;</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')