Here Comes the Sun¶
Code for showing were batteries are in CAISO, and how they grew over time
In [ ]:
#import modules
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
import holoviews as hv
import geoviews as gv
import hvplot
import hvplot.pandas
import pathlib
import os
from glob import glob
import geopandas as gpd
import requests
import io
import zipfile
import panel as pn
import calendar
import warnings
In [ ]:
#set up data directory
map_dir = os.path.join(
pathlib.Path.home(),
'code-projects',
'generation-analysis',
'map-data'
)
os.makedirs(map_dir, exist_ok=True)
map_data = os.path.join (map_dir, 'CA-batteries-data.geojson')
In [ ]:
# retrieve power plant data
# Set up URL
pp_url = (
"https://services7.arcgis.com/"
"FGr1D95XCGALKXqM/arcgis/rest/services"
"/Power_Plants_Testing/FeatureServer"
"/0/query?where=State%20%3D%20'CALIFORNIA'"
"%20AND%20PrimSource%20%3D%20'BATTERIES'&outFields=*&outSR=4326&f=json"
)
#download data
if not os.path.exists(map_data):
#Get File with requests
gdf=gpd.read_file(pp_url)
gdf.to_file(map_data, driver="GeoJSON")
pp_gdf = gpd.read_file(map_data)
#caiso_gdf['StartDate'] = pd.to_datetime(caiso_gdf['StartDate'], unit= 'ms')
pp_gdf
In [ ]:
#download EIA Data
# set path
eai_path = os.path.join(map_dir,'eia_files')
#data for EIA Form 860 for 202023
eia_url = (
"https://www.eia.gov/electricity"
"/data/eia860/xls/eia8602023.zip"
)
#download and extract data
if not os.path.exists(eai_path):
response = requests.get(eia_url)
if response.status_code == 200:
os.makedirs(eai_path, exist_ok=True)
with zipfile.ZipFile(io.BytesIO(response.content)) as z:
z.extractall(eai_path)
print("Download and extraction complete!")
else:
print("Failed to download the file.")
#Grab Energy Storage Spreadsheet
storage_path = os.path.join(eai_path, '3_4_Energy_Storage_Y2023.xlsx')
storage_df = pd.read_excel(storage_path, sheet_name='Operable', header=1)
#Filter so only project from CA are shown
storage_df = storage_df[storage_df['State'] == 'CA']
#drop duplicates
storage_df = storage_df.drop_duplicates(subset=["Plant Code"], keep='first')
fil_storage_df = storage_df[['Plant Code', 'Plant Name', 'Operating Month', 'Operating Year', 'Nameplate Capacity (MW)', 'Nameplate Energy Capacity (MWh)', 'Storage Technology 1']]
fil_storage_df = fil_storage_df.rename(columns={'Plant Code':'Plant_Code'})
fil_storage_df
In [ ]:
#join data frame with geo data frame
gdf_batteries_raw = pp_gdf.merge(fil_storage_df, on="Plant_Code", how= "inner")
#filter out so only
gdf_batteries = gdf_batteries_raw[['Plant_Name', 'Utility_Name', 'Operating Year','Nameplate Capacity (MW)', 'Nameplate Energy Capacity (MWh)', 'Storage Technology 1', 'geometry']]
In [ ]:
# Set up Map
# Get map bounds
xmin, ymin, xmax, ymax = gdf_batteries.total_bounds
# Create an IntSlider for year selection
year_widget = pn.widgets.IntSlider(
name="Year",
start=gdf_batteries["Operating Year"].min(),
end=gdf_batteries["Operating Year"].max(),
value=gdf_batteries["Operating Year"].min(),
step =1
)
# Function to filter and update the plot
def update_plot(selected_year):
# Select all rows where 'Operating Year' is <= selected year (cumulative)
filtered_gdf = gdf_batteries[gdf_batteries["Operating Year"] <= selected_year]
# Generate hvPlot map
return filtered_gdf.hvplot(
geo=True,
c='Plant Name',
tiles="EsriWorldTopo",
color = 'red',
alpha =.70,
size=50,
title=f"California Battery Additions, Up to {selected_year} (🔴)",
hover_cols = ['Plant Name', 'Nameplate Capacity (MW)', 'Nameplate Energy Capacity (MWh)', 'Operating Year'],
xlim=(xmin-3, xmax+1), ylim=(ymin, ymax+2),
frame_height=600,
frame_width=400
)
# Display the slider and interactive plot in a Panel layout
app = pn.Column(year_widget, pn.bind(update_plot, year_widget))
app.servable()
In [ ]:
#Save the file
#Create a list of years for the map to show up in panels
year_range = list(range(
int(gdf_batteries["Operating Year"].min()),
int(gdf_batteries["Operating Year"].max()) +1
))
# save each of the year maps in one html
pn.panel(app).save(
"battery_additions_map.html",
embed=True,
embed_states={"Year": year_range},
max_states=12,
max_opts= 200,
verbose=True
)