339 lines
15 KiB
Python
339 lines
15 KiB
Python
|
import json
|
|||
|
import pandas as pd
|
|||
|
import math
|
|||
|
from scipy.optimize import fsolve
|
|||
|
from shapely.geometry import Point, shape
|
|||
|
|
|||
|
# 默认文件路径
|
|||
|
PV_EXCEL_PATH = r"./pv_product.xlsx" # 光伏组件数值
|
|||
|
TILT_EXCEL_PATH = r"./倾角_峰值小时数.xlsx" # 倾角和峰值小时数
|
|||
|
GEOJSON_PATH = r"./中国_市.geojson" # GeoJSON 文件
|
|||
|
|
|||
|
|
|||
|
def calculate_pv_metrics(component_name, electricity_price, pv_number, q, longitude, latitude,
|
|||
|
is_fixed=True, optimize=True, peak_load_hour=16, cost_per_kw=3.4, E_S=1.0, K=0.8):
|
|||
|
"""
|
|||
|
计算光伏项目的各项指标,包括装机容量、年发电量、等效小时数、环境收益和内部收益率(IRR)。
|
|||
|
|
|||
|
参数:
|
|||
|
component_name (str): 光伏组件名称,例如 "TWMHF-66HD715"
|
|||
|
electricity_price (float): 电价(元/kWh)
|
|||
|
pv_number (int): 光伏组件数量
|
|||
|
q (float): 运维成本占初始投资成本的比例(例如 0.02 表示 2%)
|
|||
|
longitude (float): 经度
|
|||
|
latitude (float): 纬度
|
|||
|
is_fixed (bool): 是否为固定式支架,True 为固定式,False 为跟踪式
|
|||
|
optimize (bool): 是否优化倾角和方位角
|
|||
|
peak_load_hour (int): 峰值负荷小时,默认 16
|
|||
|
cost_per_kw (float): 每 kW 投资成本(元/kW),默认 3.4 元/kW
|
|||
|
E_S (float): 标准辐射量,默认 1.0
|
|||
|
K (float): 系统效率,默认 0.8
|
|||
|
|
|||
|
返回:
|
|||
|
dict: 包含以下结果的字典:
|
|||
|
- component_name: 组件名称
|
|||
|
- tilt: 倾角 (度)
|
|||
|
- azimuth: 方位角 (度)
|
|||
|
- array_distance: 阵列间距 (米)
|
|||
|
- max_power: 单组件最大功率 (Wp)
|
|||
|
- capacity: 装机容量 (kW)
|
|||
|
- peak_sunshine_hours: 峰值日照小时数 (小时/天)
|
|||
|
- single_daily_energy: 一天单个组件发电量 (kWh)
|
|||
|
- annual_energy: 年发电量 (kWh)
|
|||
|
- equivalent_hours: 等效小时数 (小时)
|
|||
|
- coal_reduction: 标准煤减排量 (kg)
|
|||
|
- CO2_reduction: CO₂ 减排量 (kg)
|
|||
|
- SO2_reduction: SO₂ 减排量 (kg)
|
|||
|
- NOX_reduction: NOx 减排量 (kg)
|
|||
|
- IRR: 内部收益率 (%)
|
|||
|
"""
|
|||
|
|
|||
|
# 1. 加载 GeoJSON 数据
|
|||
|
def load_geojson(file_path):
|
|||
|
try:
|
|||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|||
|
data = json.load(file)
|
|||
|
return data
|
|||
|
except FileNotFoundError:
|
|||
|
raise FileNotFoundError(f"未找到GeoJSON文件:{file_path}")
|
|||
|
except Exception as e:
|
|||
|
raise Exception(f"加载GeoJSON出错:{e}")
|
|||
|
|
|||
|
# 2. 根据经纬度查找城市
|
|||
|
def find_city_by_coordinates(lat, lon, geojson_data):
|
|||
|
point = Point(lon, lat)
|
|||
|
for feature in geojson_data['features']:
|
|||
|
polygon = shape(feature['geometry'])
|
|||
|
if polygon.contains(point):
|
|||
|
city_name = feature['properties'].get('name', '未知城市')
|
|||
|
# 规范化城市名称(去除“市”或“地区”等后缀)
|
|||
|
if city_name.endswith('市') or city_name.endswith('地区'):
|
|||
|
city_name = city_name[:-1]
|
|||
|
return city_name
|
|||
|
return '未知城市'
|
|||
|
|
|||
|
# 3. 从 Excel 获取倾角和峰值日照小时数
|
|||
|
def get_tilt_and_peak_hours(lat, lon, excel_path=TILT_EXCEL_PATH, geojson_path=GEOJSON_PATH):
|
|||
|
try:
|
|||
|
# 加载 GeoJSON 数据
|
|||
|
geojson_data = load_geojson(geojson_path)
|
|||
|
# 查找城市
|
|||
|
city = find_city_by_coordinates(lat, lon, geojson_data)
|
|||
|
# 读取 Excel 文件
|
|||
|
df = pd.read_excel(excel_path)
|
|||
|
if len(df.columns) < 5:
|
|||
|
raise ValueError("Excel文件需包含至少5列:城市、倾角、峰值日照小时数等")
|
|||
|
# 匹配城市名称(前两个字)
|
|||
|
matched_row = df[df.iloc[:, 2].str.startswith(city, na=False)]
|
|||
|
if matched_row.empty:
|
|||
|
raise ValueError(f"未找到匹配的城市:{city}")
|
|||
|
# 提取并转换为浮点数
|
|||
|
tilt = float(matched_row.iloc[0, 3]) # 第四列:倾角
|
|||
|
peak_hours = float(matched_row.iloc[0, 4]) # 第五列:PSH
|
|||
|
return {
|
|||
|
"tilt": tilt,
|
|||
|
"peak_sunshine_hours": peak_hours
|
|||
|
}
|
|||
|
except FileNotFoundError:
|
|||
|
raise FileNotFoundError(f"未找到Excel文件:{excel_path}")
|
|||
|
except ValueError as e:
|
|||
|
raise ValueError(f"Excel数据转换错误:{e}")
|
|||
|
except Exception as e:
|
|||
|
raise Exception(f"读取Excel或GeoJSON出错:{e}")
|
|||
|
|
|||
|
# 4. 计算倾角和方位角
|
|||
|
def get_tilt_and_azimuth(is_fixed=True, optimize=True, longitude=116, latitude=None, peak_load_hour=16):
|
|||
|
if optimize and latitude is None:
|
|||
|
raise ValueError("优化模式下需提供纬度")
|
|||
|
|
|||
|
if is_fixed:
|
|||
|
if optimize:
|
|||
|
# 从 Excel 获取倾角
|
|||
|
tilt_data = get_tilt_and_peak_hours(latitude, longitude)
|
|||
|
tilt = tilt_data["tilt"]
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError(f"Excel中的倾角 {tilt:.2f}° 超出合理范围(0°-90°)")
|
|||
|
azimuth = (peak_load_hour - 12) * 15 + (longitude - 116) # 方位角公式
|
|||
|
azimuth = azimuth % 360 if azimuth >= 0 else azimuth + 360
|
|||
|
else:
|
|||
|
print("倾角:0°(水平)-90°(垂直) | 方位角:0°(正北)-180°(正南),顺时针")
|
|||
|
tilt = float(input("请输入倾角(度):"))
|
|||
|
azimuth = float(input("请输入方位角(度):"))
|
|||
|
if not (0 <= tilt <= 90) or not (0 <= azimuth <= 360):
|
|||
|
raise ValueError("倾角需在0-90°,方位角需在0-360°")
|
|||
|
else: # 跟踪式
|
|||
|
azimuth = 180 # 固定朝南
|
|||
|
if optimize:
|
|||
|
tilt_data = get_tilt_and_peak_hours(latitude, longitude)
|
|||
|
tilt = tilt_data["tilt"]
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError(f"Excel中的倾角 {tilt:.2f}° 超出合理范围(0°-90°)")
|
|||
|
else:
|
|||
|
print("倾角:0°(水平)-90°(垂直)")
|
|||
|
tilt = float(input("请输入倾角(度):"))
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError("倾角需在0-90°")
|
|||
|
return tilt, azimuth
|
|||
|
|
|||
|
# 5. 获取光伏组件信息
|
|||
|
def get_pv_product_info(component_name, excel_path=PV_EXCEL_PATH):
|
|||
|
try:
|
|||
|
df = pd.read_excel(excel_path)
|
|||
|
if len(df.columns) < 10:
|
|||
|
raise ValueError("Excel文件需包含至少10列:组件名称、尺寸、功率等")
|
|||
|
row = df[df.iloc[:, 1] == component_name]
|
|||
|
if row.empty:
|
|||
|
raise ValueError(f"未找到组件:{component_name}")
|
|||
|
return {
|
|||
|
"component_name": component_name,
|
|||
|
"max_power": row.iloc[0, 5],
|
|||
|
"efficiency": row.iloc[0, 9],
|
|||
|
"pv_size": row.iloc[0, 3]
|
|||
|
}
|
|||
|
except FileNotFoundError:
|
|||
|
raise FileNotFoundError(f"未找到Excel文件:{excel_path}")
|
|||
|
except Exception as e:
|
|||
|
raise Exception(f"读取Excel出错:{e}")
|
|||
|
|
|||
|
# 6. 计算光伏阵列间距
|
|||
|
def calculate_array_distance(L, tilt, latitude):
|
|||
|
beta_rad = math.radians(tilt)
|
|||
|
phi_rad = math.radians(latitude)
|
|||
|
return (L * math.cos(beta_rad) +
|
|||
|
L * math.sin(beta_rad) * 0.707 * math.tan(phi_rad) +
|
|||
|
0.4338 * math.tan(phi_rad))
|
|||
|
|
|||
|
# 7. 计算等效小时数
|
|||
|
def calculate_equivalent_hours(P, P_r):
|
|||
|
if P_r == 0:
|
|||
|
raise ValueError("额定功率不能为 0")
|
|||
|
h = P / P_r # 单位换算
|
|||
|
return h
|
|||
|
|
|||
|
# 8. 计算装机容量
|
|||
|
def calculate_installed_capacity(max_power, num_components):
|
|||
|
if max_power < 0 or num_components < 0 or not isinstance(num_components, int):
|
|||
|
raise ValueError("功率和数量需为非负数,数量需为整数")
|
|||
|
return (max_power * num_components) / 1000 # 单位:kW
|
|||
|
|
|||
|
# 9. 计算年发电量
|
|||
|
def calculate_annual_energy(peak_hours, capacity, E_S=E_S, K=K):
|
|||
|
if any(x < 0 for x in [peak_hours, capacity]) or E_S <= 0 or not 0 <= K <= 1:
|
|||
|
raise ValueError("输入参数需满足:辐射量、容量≥0,E_S>0,K∈[0,1]")
|
|||
|
return peak_hours * 365 * (capacity / E_S) * K # 单位:kWh
|
|||
|
|
|||
|
# 10. 计算环境收益
|
|||
|
def calculate_environmental_benefits(E_p_million_kwh):
|
|||
|
if E_p_million_kwh < 0:
|
|||
|
raise ValueError("年发电量需≥0")
|
|||
|
return {
|
|||
|
"coal_reduction": E_p_million_kwh * 0.404 * 10,
|
|||
|
"CO2_reduction": E_p_million_kwh * 0.977 * 10,
|
|||
|
"SO2_reduction": E_p_million_kwh * 0.03 * 10,
|
|||
|
"NOX_reduction": E_p_million_kwh * 0.015 * 10
|
|||
|
}
|
|||
|
|
|||
|
# 11. 计算净现值和内部收益率
|
|||
|
def calculate_reference_yield(E_p, electricity_price, IC, q, n=25):
|
|||
|
if E_p < 0 or electricity_price < 0 or IC <= 0 or not 0 <= q <= 1:
|
|||
|
raise ValueError("发电量、电价≥0,投资成本>0,回收比例∈[0,1]")
|
|||
|
|
|||
|
def npv_equation(irr, p, w, ic, q_val, n=n):
|
|||
|
term1 = (1 + irr) ** (-1)
|
|||
|
term2 = irr * (1 + irr) ** (-1) if irr != 0 else float('inf')
|
|||
|
pv_revenue = p * w * (term1 / term2) * (1 - (1 + irr) ** (-n))
|
|||
|
pv_salvage = q_val * ic * (term1 / term2) * (1 - (1 + irr) ** (-n))
|
|||
|
return pv_revenue - ic + pv_salvage
|
|||
|
|
|||
|
irr_guess = 0.1
|
|||
|
irr = float(fsolve(npv_equation, irr_guess, args=(E_p, electricity_price, IC, q))[0])
|
|||
|
if not 0 <= irr <= 1:
|
|||
|
raise ValueError(f"IRR计算结果{irr:.4f}不合理,请检查输入")
|
|||
|
npv = npv_equation(irr, E_p, electricity_price, IC, q)
|
|||
|
return {"NPV": npv, "IRR": irr * 100}
|
|||
|
|
|||
|
# 主计算流程
|
|||
|
try:
|
|||
|
# 验证经纬度
|
|||
|
if not (-90 <= latitude <= 90):
|
|||
|
raise ValueError("纬度必须在 -90 到 90 之间")
|
|||
|
if not (-180 <= longitude <= 180):
|
|||
|
raise ValueError("经度必须在 -180 到 180 之间")
|
|||
|
|
|||
|
# 获取倾角和方位角
|
|||
|
tilt, azimuth = get_tilt_and_azimuth(
|
|||
|
is_fixed=is_fixed,
|
|||
|
optimize=optimize,
|
|||
|
longitude=longitude,
|
|||
|
latitude=latitude,
|
|||
|
peak_load_hour=peak_load_hour
|
|||
|
)
|
|||
|
|
|||
|
# 获取峰值日照小时数
|
|||
|
peak_hours = get_tilt_and_peak_hours(latitude, longitude)["peak_sunshine_hours"]
|
|||
|
|
|||
|
# 获取组件信息
|
|||
|
pv_info = get_pv_product_info(component_name)
|
|||
|
width_mm = float(pv_info["pv_size"].split("×")[1])
|
|||
|
L = (width_mm / 1000) * 4
|
|||
|
array_distance = calculate_array_distance(L, tilt, latitude)
|
|||
|
|
|||
|
# 计算装机容量
|
|||
|
max_power = pv_info["max_power"]
|
|||
|
capacity = calculate_installed_capacity(max_power, pv_number) # 单位:kW
|
|||
|
|
|||
|
# 计算一天单个组件发电量
|
|||
|
single_daily_energy = peak_hours * (capacity / pv_number) * K # 单位:kWh
|
|||
|
|
|||
|
# 计算年发电量
|
|||
|
E_p = calculate_annual_energy(peak_hours, capacity, E_S, K) # 单位:kWh
|
|||
|
|
|||
|
# 计算等效小时数
|
|||
|
h = calculate_equivalent_hours(E_p, capacity) # P_r 单位为 kW,E_p 单位为 kWh
|
|||
|
|
|||
|
# 计算环境收益(转换为百万 kWh)
|
|||
|
E_p_million_kwh = E_p / 1000000 # 转换为百万 kWh
|
|||
|
env_benefits = calculate_environmental_benefits(E_p_million_kwh)
|
|||
|
|
|||
|
# 计算初始投资成本
|
|||
|
IC = capacity * cost_per_kw * 1000 # 单位:元
|
|||
|
|
|||
|
# 计算 IRR
|
|||
|
ref_yield = calculate_reference_yield(E_p, electricity_price, IC, q)
|
|||
|
|
|||
|
# 返回结果
|
|||
|
return {
|
|||
|
"component_name": component_name,
|
|||
|
"tilt": tilt,
|
|||
|
"azimuth": azimuth,
|
|||
|
"array_distance": array_distance,
|
|||
|
"max_power": max_power,
|
|||
|
"capacity": capacity,
|
|||
|
"peak_sunshine_hours": peak_hours,
|
|||
|
"single_daily_energy": single_daily_energy,
|
|||
|
"annual_energy": E_p,
|
|||
|
"equivalent_hours": h,
|
|||
|
"coal_reduction": env_benefits["coal_reduction"],
|
|||
|
"CO2_reduction": env_benefits["CO2_reduction"],
|
|||
|
"SO2_reduction": env_benefits["SO2_reduction"],
|
|||
|
"NOX_reduction": env_benefits["NOX_reduction"],
|
|||
|
"IRR": ref_yield["IRR"]
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
raise Exception(f"计算过程中发生错误: {str(e)}")
|
|||
|
|
|||
|
|
|||
|
# 示例用法
|
|||
|
if __name__ == "__main__":
|
|||
|
try:
|
|||
|
# 输入并验证经纬度
|
|||
|
latitude = float(input("请输入纬度(-90 到 90,例如 39.9042):"))
|
|||
|
if not (-90 <= latitude <= 90):
|
|||
|
raise ValueError("纬度必须在 -90 到 90 之间")
|
|||
|
longitude = float(input("请输入经度(-180 到 180,例如 116.4074):"))
|
|||
|
if not (-180 <= longitude <= 180):
|
|||
|
raise ValueError("经度必须在 -180 到 180 之间")
|
|||
|
|
|||
|
component_name = input("请输入组件名称:")
|
|||
|
electricity_price = float(input("请输入电价(元/kWh):"))
|
|||
|
pv_number = 1200 # 固定组件数量
|
|||
|
q = 0.02 # 运维成本占初始投资成本的比例
|
|||
|
choice = input("选择光伏支架(固定式/跟踪式):")
|
|||
|
optimize = input("是否优化倾角和方位角(是/否):")
|
|||
|
|
|||
|
# 调用主函数
|
|||
|
result = calculate_pv_metrics(
|
|||
|
component_name=component_name,
|
|||
|
electricity_price=electricity_price,
|
|||
|
pv_number=pv_number,
|
|||
|
q=q,
|
|||
|
is_fixed=(choice == "固定式"),
|
|||
|
optimize=(optimize.lower() == "是"),
|
|||
|
longitude=longitude,
|
|||
|
latitude=latitude
|
|||
|
)
|
|||
|
|
|||
|
# 打印结果
|
|||
|
print("\n")
|
|||
|
print(f"组件名称:{result['component_name']}")
|
|||
|
print(f"倾角:{result['tilt']:.2f}° | 方位角:{result['azimuth']:.2f}°")
|
|||
|
print(f"阵列间距:{result['array_distance']:.2f} 米")
|
|||
|
print(f"单个组件最大功率(Wp):{result['max_power']}")
|
|||
|
print(f"装机容量:{result['capacity']:.2f} kW")
|
|||
|
print(f"峰值日照小时数:{result['peak_sunshine_hours']:.2f} 小时/天")
|
|||
|
print(f"一天单个组件发电量:{result['single_daily_energy']:.0f} kWh")
|
|||
|
print(f"年发电量:{result['annual_energy']:,.0f} kWh")
|
|||
|
print(f"等效小时数:{result['equivalent_hours']:.0f} 小时")
|
|||
|
print("环境收益:")
|
|||
|
print(f"标准煤减排量:{result['coal_reduction']:,.0f} kg")
|
|||
|
print(f"CO₂减排量:{result['CO2_reduction']:,.0f} kg")
|
|||
|
print(f"SO₂减排量:{result['SO2_reduction']:,.0f} kg")
|
|||
|
print(f"NOx减排量:{result['NOX_reduction']:,.0f} kg")
|
|||
|
print(f"内部收益率 IRR:{result['IRR']:.2f}%")
|
|||
|
|
|||
|
|
|||
|
except ValueError as e:
|
|||
|
print(f"输入错误:{e}")
|
|||
|
except Exception as e:
|
|||
|
print(f"错误:{e}")
|