Skip to content

Commit eb0bd37

Browse files
Electricity Market Structures Map
The inclusion of a Electricity Market Structures Map and supporting data
1 parent 3c7d8c1 commit eb0bd37

44 files changed

Lines changed: 860 additions & 2590 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build_emde_map.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from pathlib import Path
2+
3+
import pandas as pd
4+
import geopandas as gpd
5+
import folium
6+
7+
8+
# =========================
9+
# Paths
10+
# =========================
11+
12+
ROOT = Path(__file__).resolve().parent # ffrm_python/
13+
DOCS = ROOT / "docs"
14+
15+
# Your CSV in docs/data/
16+
DATA_CSV = DOCS / "data" / "EMDE_fossil-fuel_regimes___repopulated_inclusive_mapping.csv"
17+
18+
# Output HTML map
19+
OUT_HTML = DOCS / "_static" / "emde_regime_map.html"
20+
21+
22+
# =========================
23+
# Load and prepare data
24+
# =========================
25+
26+
print(f"Reading CSV from: {DATA_CSV}")
27+
df = pd.read_csv(DATA_CSV)
28+
29+
print("Columns in CSV:", list(df.columns))
30+
31+
# Use PrimaryBucket for colouring; fallback to 'unknown'
32+
df["DisplayBucket"] = df["PrimaryBucket"].fillna("unknown")
33+
34+
# Flag mixed systems: more than one of Has_Market / Has_PPA / Has_Regulated = True
35+
def classify_mixed(row):
36+
flags = [
37+
bool(row.get("Has_Market", False)),
38+
bool(row.get("Has_PPA", False)),
39+
bool(row.get("Has_Regulated", False)),
40+
]
41+
return "Yes" if sum(flags) > 1 else "No"
42+
43+
df["MixedSystem"] = df.apply(classify_mixed, axis=1)
44+
45+
print("Unique PrimaryBucket values:", df["PrimaryBucket"].dropna().unique())
46+
print("Mixed system counts:", df["MixedSystem"].value_counts())
47+
48+
49+
# =========================
50+
# Load world geometry & join
51+
# =========================
52+
53+
print("Loading Natural Earth world geometries...")
54+
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
55+
56+
# Join on name (Natural Earth) vs Country (CSV)
57+
gdf = world.merge(df, left_on="name", right_on="Country", how="inner")
58+
59+
print(f"Joined rows: {len(gdf)} (out of {len(df)} in CSV)")
60+
missing = set(df["Country"]) - set(gdf["Country"])
61+
if missing:
62+
print("Countries in CSV not matched in Natural Earth:", missing)
63+
64+
gdf = gpd.GeoDataFrame(gdf, crs=world.crs)
65+
66+
67+
# =========================
68+
# Build Folium map
69+
# =========================
70+
71+
m = folium.Map(location=[10, 10], zoom_start=2, tiles="cartodbpositron")
72+
73+
colors = {
74+
"market": "#1f77b4",
75+
"ppa": "#ff7f0e",
76+
"regulated": "#2ca02c",
77+
"unknown": "#b0b0b0", # unclassified
78+
"_default": "#e0e0e0",
79+
}
80+
81+
def style_function(feature):
82+
bucket = feature["properties"].get("DisplayBucket", "_default")
83+
color = colors.get(bucket, colors["_default"])
84+
return {
85+
"fillColor": color,
86+
"color": "white",
87+
"weight": 0.5,
88+
"fillOpacity": 0.8,
89+
}
90+
91+
def highlight_function(_feature):
92+
return {
93+
"weight": 2,
94+
"color": "#000000",
95+
"fillOpacity": 0.9,
96+
}
97+
98+
# Hover: simple
99+
tooltip = folium.GeoJsonTooltip(
100+
fields=["Country", "PrimaryBucket"],
101+
aliases=["Country:", "Electricity market regime:"],
102+
localize=True,
103+
sticky=True,
104+
)
105+
106+
# Click popup: show mixed system + flags
107+
popup = folium.GeoJsonPopup(
108+
fields=[
109+
"Country",
110+
"PrimaryBucket",
111+
"MixedSystem",
112+
"Has_Market",
113+
"Has_PPA",
114+
"Has_Regulated",
115+
"Notes",
116+
],
117+
aliases=[
118+
"Country:",
119+
"Primary regime:",
120+
"Mixed system (multiple mechanisms):",
121+
"Has market?",
122+
"Has PPA?",
123+
"Has regulated tariffs?",
124+
"Notes:",
125+
],
126+
localize=True,
127+
labels=True,
128+
parse_html=True,
129+
)
130+
131+
geojson = folium.GeoJson(
132+
gdf,
133+
style_function=style_function,
134+
highlight_function=highlight_function,
135+
tooltip=tooltip,
136+
popup=popup,
137+
)
138+
139+
geojson.add_to(m)
140+
141+
# Legend (no title, just colour → label)
142+
legend_html = """
143+
<div style="
144+
position: fixed;
145+
bottom: 30px;
146+
left: 30px;
147+
z-index: 9999;
148+
background: white;
149+
padding: 10px;
150+
border: 1px solid #ccc;
151+
font-size: 12px;
152+
">
153+
<span style="background:#1f77b4;width:12px;height:12px;display:inline-block;margin-right:4px;"></span> Market-based<br>
154+
<span style="background:#ff7f0e;width:12px;height:12px;display:inline-block;margin-right:4px;"></span> PPA-based<br>
155+
<span style="background:#2ca02c;width:12px;height:12px;display:inline-block;margin-right:4px;"></span> Regulated<br>
156+
<span style="background:#b0b0b0;width:12px;height:12px;display:inline-block;margin-right:4px;"></span> Unclassified<br>
157+
</div>
158+
"""
159+
m.get_root().html.add_child(folium.Element(legend_html))
160+
161+
162+
# =========================
163+
# Save map
164+
# =========================
165+
166+
OUT_HTML.parent.mkdir(parents=True, exist_ok=True)
167+
m.save(str(OUT_HTML))
168+
print(f"Saved map to: {OUT_HTML}")
169+
16 Bytes
Binary file not shown.
16 Bytes
Binary file not shown.
16 Bytes
Binary file not shown.
16 Bytes
Binary file not shown.
16 Bytes
Binary file not shown.
-264 Bytes
Binary file not shown.
-8.59 KB
Binary file not shown.
16 Bytes
Binary file not shown.
16 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)