1"""Utility functions for plotting."""
2
3import os
4
5import branca.colormap as cm
6import folium
7import geopandas as gpd
8import numpy as np
9import pandas as pd
10import seaborn as sns
11from branca.element import MacroElement
12from folium.plugins import (
13 Fullscreen,
14 GroupedLayerControl,
15 MeasureControl,
16 MousePosition,
17 StripePattern,
18)
19from jinja2 import Template
20
21from h2ss import compare
22from h2ss import data as rd
23
24
[docs]
25class BindColormap(MacroElement):
26 """Binds a colormap to a given layer on Folium.
27
28 Parameters
29 ----------
30 colormap : branca.colormap.ColorMap
31 The colormap to bind.
32
33 Notes
34 -----
35 Source:
36 https://nbviewer.org/gist/BibMartin/f153aa957ddc5fadc64929abdee9ff2e
37 """
38
39 def __init__(self, layer, colormap):
40 super(BindColormap, self).__init__()
41 self.layer = layer
42 self.colormap = colormap
43 self._template = Template(
44 """
45 {% macro script(this, kwargs) %}
46 {{this.colormap.get_name()}}.svg[0][0].style.display = 'block';
47 {{this._parent.get_name()}}.on('overlayadd', function (eventLayer) {
48 if (eventLayer.layer == {{this.layer.get_name()}}) {
49 {{this.colormap.get_name()}}.svg[0][0].style.display = 'block';
50 }});
51 {{this._parent.get_name()}}.on('overlayremove', function (eventLayer) {
52 if (eventLayer.layer == {{this.layer.get_name()}}) {
53 {{this.colormap.get_name()}}.svg[0][0].style.display = 'none';
54 }});
55 {% endmacro %}
56 """
57 ) # noqa
58
59
[docs]
60def plot_interactive_map():
61 """Plot an interactive map of the results using Folium."""
62 ds, extent, exclusions = compare.load_all_data(keep_orig=True)
63
64 caverns, zones, weibull_df, injection_point = (
65 compare.optimisation_function(
66 ds=ds,
67 extent=extent,
68 exclusions=exclusions,
69 cavern_diameter=85,
70 cavern_height=120,
71 )
72 )
73
74 shape = rd.halite_shape(dat_xr=ds).buffer(1000).buffer(-1000)
75
76 col_list = [
77 "geometry",
78 "cavern_depth",
79 "capacity",
80 "Bathymetry",
81 "NetToGross",
82 "ThicknessNet",
83 "dist_Codling_Wind_Park",
84 "dist_Dublin_Array",
85 "dist_North_Irish_Sea_Array",
86 "LCOT_Codling_Wind_Park",
87 "LCOT_Dublin_Array",
88 "LCOT_North_Irish_Sea_Array",
89 ]
90 caverns_plt = caverns[col_list].copy()
91 caverns_plt["LCOT_mean"] = caverns_plt[
92 [
93 "LCOT_Codling_Wind_Park",
94 "LCOT_Dublin_Array",
95 "LCOT_North_Irish_Sea_Array",
96 ]
97 ].mean(axis=1)
98 for f in ["capacity", "LCOT_mean"]:
99 caverns_plt[f"{f}_str"] = caverns_plt[f].copy()
100 caverns_plt[f"{f}_str"] = [
101 f"{x:,.3f}" for x in caverns_plt[f"{f}_str"]
102 ]
103 for f in col_list:
104 if f == "NetToGross":
105 caverns_plt[f] = [f"{x * 100:.0f}" for x in caverns[f]]
106 elif f == "geometry":
107 caverns_plt[f] = caverns_plt[f].buffer(100)
108 elif f == "Bathymetry":
109 caverns_plt[f] = [f"{-x:.3f}" for x in caverns[f]]
110 elif f not in ["capacity", "LCOT_mean"]:
111 caverns_plt[f] = [f"{x:,.3f}" for x in caverns[f]]
112
113 # exclusions["wind_farms"] = pd.concat(
114 # [
115 # exclusions["wind_farms"][["geometry"]],
116 # weibull_df.drop(columns=["integral", "abserr"])
117 # ],
118 # axis=1
119 # )
120 exclusions["wind_farms"] = exclusions["wind_farms"][
121 ["name", "geometry", "cap"]
122 ]
123 exclusions["wind_farms"]["cap"] = [
124 f"{x:,.0f}" for x in exclusions["wind_farms"]["cap"]
125 ]
126
127 exclusions["shipwrecks"] = exclusions["shipwrecks"][
128 ["VESSELNAME", "VESSELTYPE", "geometry"]
129 ]
130 exclusions["shipwrecks"]["VESSELNAME"] = exclusions["shipwrecks"][
131 "VESSELNAME"
132 ].fillna("Unknown")
133
134 exclusions["wells"] = exclusions["wells"][
135 ["RIG_NAME", "OPERATOR", "geometry"]
136 ]
137
138 exclusions["cables"] = exclusions["cables"].dissolve()[["geometry"]]
139
140 exclusions["shipping"] = exclusions["shipping"].dissolve()[["geometry"]]
141
142 # create exclusion buffer
143 buffer = (
144 pd.concat(
145 [
146 exclusions["wells_b"],
147 exclusions["shipwrecks_b"],
148 exclusions["shipping_b"],
149 exclusions["cables_b"],
150 ]
151 )
152 .dissolve()
153 .overlay(gpd.GeoDataFrame(geometry=shape))
154 )
155
156 minx, miny, maxx, maxy = shape.to_crs(4326).bounds.values[0]
157 m = folium.Map(
158 tiles="cartodbvoyager",
159 control_scale=True,
160 location=[np.mean((miny, maxy)), np.mean((minx, maxx))],
161 zoom_start=10,
162 min_lat=miny,
163 max_lat=maxy,
164 min_lon=minx,
165 max_lon=maxx,
166 )
167
168 # shipwrecks
169 folium.GeoJson(
170 exclusions["shipwrecks"].to_crs(4326),
171 name="Shipwrecks",
172 tooltip=folium.GeoJsonTooltip(
173 fields=["VESSELNAME", "VESSELTYPE"],
174 aliases=["Shipwreck", "Vessel type"],
175 ),
176 marker=folium.Marker(icon=folium.Icon(icon="sailboat", prefix="fa")),
177 style_function=lambda feature: {"markerColor": "gray"},
178 ).add_to(m)
179
180 # exploration wells
181 folium.GeoJson(
182 exclusions["wells"].to_crs(4326),
183 name="Exploration wells",
184 marker=folium.Marker(icon=folium.Icon(icon="oil-well", prefix="fa")),
185 style_function=lambda feature: {"markerColor": "gray"},
186 tooltip=folium.GeoJsonTooltip(
187 fields=["RIG_NAME", "OPERATOR"],
188 aliases=["Exploration well", "Operator"],
189 ),
190 ).add_to(m)
191
192 # shipping routes
193 folium.GeoJson(
194 exclusions["shipping"].to_crs(4326),
195 name="Frequent shipping routes",
196 color="crimson",
197 tooltip="Frequent shipping route",
198 ).add_to(m)
199
200 # subsea cables
201 folium.GeoJson(
202 exclusions["cables"].to_crs(4326),
203 name="Subsea cables",
204 color="royalblue",
205 tooltip="Subsea cable",
206 ).add_to(m)
207
208 # wind farms
209 folium.GeoJson(
210 exclusions["wind_farms"].to_crs(4326),
211 name="Wind farms",
212 tooltip=folium.GeoJsonTooltip(
213 fields=["name", "cap"], aliases=["Wind farm", "Capacity [MW]"]
214 ),
215 style_function=lambda feature: {
216 "fillColor": "none",
217 "color": "seagreen",
218 "fillPattern": StripePattern(
219 angle=135, color="seagreen", space_color="none"
220 ),
221 "weight": 1,
222 },
223 ).add_to(m)
224
225 # caverns - capacity
226 fg1 = folium.FeatureGroup(name="Cavern capacity")
227 cm1 = cm.StepColormap(
228 list(sns.color_palette("rocket_r")),
229 vmin=caverns_plt["capacity"].min(),
230 vmax=caverns_plt["capacity"].max(),
231 )
232 caverns_dict = caverns_plt.reset_index().set_index("index")["capacity"]
233 cd1 = {key: cm1(caverns_dict[key]) for key in caverns_dict.keys()}
234 folium.GeoJson(
235 caverns_plt.to_crs(4326),
236 style_function=lambda feature: {
237 "fillColor": cd1[int(feature["id"])],
238 "color": "darkslategrey",
239 "weight": 0.5,
240 "fillOpacity": 1,
241 },
242 tooltip=folium.GeoJsonTooltip(
243 fields=[
244 "capacity_str",
245 "cavern_depth",
246 "ThicknessNet",
247 "NetToGross",
248 "Bathymetry",
249 ],
250 aliases=[
251 "Cavern capacity [GWh]",
252 "Cavern top depth [m]",
253 "Net halite thickness [m]",
254 "Net-to-gross ratio [%]",
255 "Water depth [m]",
256 ],
257 ),
258 smooth_factor=0,
259 ).add_to(fg1)
260 m.add_child(fg1)
261 cm1.caption = "Cavern capacity [GWh]"
262
263 # caverns - LCOT
264 fg2 = folium.FeatureGroup(name="Pipeline LCOT")
265 cm2 = cm.StepColormap(
266 list(sns.color_palette("mako_r")),
267 vmin=caverns_plt["LCOT_mean"].min(),
268 vmax=caverns_plt["LCOT_mean"].max(),
269 )
270 caverns_dict = caverns_plt.reset_index().set_index("index")["LCOT_mean"]
271 cd2 = {key: cm2(caverns_dict[key]) for key in caverns_dict.keys()}
272 folium.GeoJson(
273 caverns_plt.to_crs(4326),
274 style_function=lambda feature: {
275 "fillColor": cd2[int(feature["id"])],
276 "color": "darkslategrey",
277 "weight": 0.5,
278 "fillOpacity": 1,
279 },
280 tooltip=folium.GeoJsonTooltip(
281 fields=[
282 "LCOT_mean_str",
283 "LCOT_Codling_Wind_Park",
284 "LCOT_Dublin_Array",
285 "LCOT_North_Irish_Sea_Array",
286 ],
287 aliases=[
288 "Pipeline LCOT [€ kg⁻¹]<br>Mean",
289 "Codling Wind Park",
290 "Dublin Array",
291 "North Irish Sea Array",
292 ],
293 ),
294 smooth_factor=0,
295 ).add_to(fg2)
296 m.add_child(fg2)
297 cm2.caption = "Mean pipeline LCOT [€ kg⁻¹]"
298
299 # basin boundary
300 folium.GeoJson(
301 shape.to_crs(4326),
302 name="Kish Basin boundary",
303 style_function=lambda feature: {
304 "fillColor": "none",
305 "color": "darkslategrey",
306 "weight": 1,
307 },
308 tooltip="Kish Basin boundary",
309 ).add_to(m)
310
311 # exclusion buffer
312 folium.GeoJson(
313 buffer.to_crs(4326),
314 name="Exclusion buffer",
315 style_function=lambda feature: {
316 "fillPattern": StripePattern(
317 angle=45, color="slategrey", space_color="none"
318 ),
319 "color": "slategrey",
320 "weight": 0.5,
321 },
322 tooltip="Exclusion buffer",
323 ).add_to(m)
324
325 # zones of interest
326 folium.GeoJson(
327 zones.to_crs(4326),
328 name="Area of interest",
329 style_function=lambda feature: {
330 "fillColor": "none",
331 "color": "darkslategrey",
332 "weight": 0.5,
333 "dashArray": "5, 5",
334 },
335 tooltip="Area of interest",
336 ).add_to(m)
337
338 # injection point
339 folium.GeoJson(
340 injection_point.to_crs(4326),
341 name="Gas grid injection point",
342 tooltip="Gas grid injection point<br>Dublin Port",
343 marker=folium.Marker(icon=folium.Icon(icon="anchor", prefix="fa")),
344 style_function=lambda feature: {"markerColor": "red"},
345 ).add_to(m)
346
347 m.add_child(cm1).add_child(cm2)
348 m.add_child(BindColormap(fg1, cm1)).add_child(BindColormap(fg2, cm2))
349 folium.LayerControl(collapsed=False).add_to(m)
350 GroupedLayerControl(
351 groups={"Caverns": [fg1, fg2]}, collapsed=False
352 ).add_to(m)
353
354 MeasureControl().add_to(m)
355 MousePosition().add_to(m)
356 Fullscreen().add_to(m)
357
358 return m