GreenTransPowerCalculate/deeplabv3sdRenewable/tools/山东省地貌识别tools/PV/1.1-光伏电板-(分最多最少)可装机面积到环境逻辑经...

583 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pandas as pd
import math
import numpy as np
import requests
from scipy.optimize import fsolve
from tabulate import tabulate
# 默认文件路径
PV_EXCEL_PATH = r"./pv_product.xlsx" # 请确保此文件存在或更改为正确路径
# 地形类型与复杂性因子范围
TERRAIN_COMPLEXITY_RANGES = {
"distributed": {
"耕地": (1.0, 1.2), "裸地": (1.0, 1.2), "草地": (1.1, 1.3),
"灌木": (1.3, 1.5), "湿地": (1.5, 1.8), "林地": (1.5, 1.8), "建筑": (1.2, 1.5)
},
"centralized": {
"耕地": (1.0, 1.2), "裸地": (1.0, 1.2), "草地": (1.1, 1.3),
"灌木": (1.3, 1.6), "湿地": (1.5, 1.8), "林地": (1.6, 2.0)
},
"floating": {"水域": (1.2, 1.5)}
}
# 地形类型与土地可用性、发电效率的映射
TERRAIN_ADJUSTMENTS = {
"耕地": {"land_availability": 0.85, "K": 0.8}, "裸地": {"land_availability": 0.85, "K": 0.8},
"草地": {"land_availability": 0.85, "K": 0.8}, "灌木": {"land_availability": 0.75, "K": 0.75},
"湿地": {"land_availability": 0.65, "K": 0.75}, "水域": {"land_availability": 0.85, "K": 0.8},
"林地": {"land_availability": 0.65, "K": 0.7}, "建筑": {"land_availability": 0.6, "K": 0.75}
}
# 光伏类型的装机容量上限 (MW/平方千米)
CAPACITY_LIMITS = {
"distributed": 25.0, "centralized": 50.0, "floating": 25.0
}
# 实际面板间距系数
PANEL_SPACING_FACTORS = {
"distributed": 1.5, "centralized": 1.2, "floating": 1.3
}
def calculate_psh_average(lat, lon, start_year=2010, end_year=2023):
"""
从 NASA POWER API 获取峰值日照小时数PSH
返回:平均 PSH小时/天),失败时返回默认值 4.0
"""
print("DEBUG: Starting calculate_psh_average (version 2025-04-28)")
url = "https://power.larc.nasa.gov/api/temporal/monthly/point"
params = {
"parameters": "ALLSKY_SFC_SW_DWN",
"community": "RE",
"longitude": lon,
"latitude": lat,
"format": "JSON",
"start": str(start_year),
"end": str(end_year)
}
try:
print(f"DEBUG: Requesting NASA POWER API for lat={lat}, lon={lon}")
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
print("DEBUG: API response received")
data = response.json()
print("DEBUG: Validating API data")
if "properties" not in data or "parameter" not in data["properties"]:
print("ERROR: NASA POWER API returned invalid data format")
return 4.0
ghi_data = data["properties"]["parameter"].get("ALLSKY_SFC_SW_DWN", {})
if not ghi_data:
print("ERROR: No GHI data found in API response")
return 4.0
print("DEBUG: Filtering GHI data")
print(f"DEBUG: Raw GHI data keys: {list(ghi_data.keys())}")
ghi_data = {k: v for k, v in ghi_data.items() if not k.endswith("13")}
if not ghi_data:
print("ERROR: No valid GHI data after filtering")
return 4.0
print(f"DEBUG: Filtered GHI data keys: {list(ghi_data.keys())}")
print("DEBUG: Creating DataFrame")
df = pd.DataFrame.from_dict(ghi_data, orient="index", columns=["GHI (kWh/m²/day)"])
print(f"DEBUG: Original DataFrame index: {list(df.index)}")
print("DEBUG: Reformatting DataFrame index")
new_index = []
for k in df.index:
try:
# 验证索引格式
if not (isinstance(k, str) and len(k) == 6 and k.isdigit()):
print(f"ERROR: Invalid index format for {k}")
return 4.0
year = k[:4]
month = k[-2:]
formatted_index = f"{year}-{month:0>2}" # 使用 :0>2 确保两位数字
print(f"DEBUG: Formatting index {k} -> {formatted_index}")
new_index.append(formatted_index)
except Exception as e:
print(f"ERROR: Failed to format index {k}: {e}")
return 4.0
df.index = new_index
print(f"DEBUG: Reformatted DataFrame index: {list(df.index)}")
if df.empty:
print("ERROR: DataFrame is empty")
return 4.0
print("DEBUG: Calculating PSH")
df["PSH (hours/day)"] = df["GHI (kWh/m²/day)"]
if df["PSH (hours/day)"].isna().any():
print("ERROR: PSH data contains invalid values")
return 4.0
print("DEBUG: Grouping by year")
df['Year'] = df.index.str[:4]
print(f"DEBUG: Year column: {list(df['Year'])}")
annual_avg = df.groupby('Year')['PSH (hours/day)'].mean()
print(f"DEBUG: Annual averages: {annual_avg.to_dict()}")
if annual_avg.empty:
print("ERROR: Annual average PSH data is empty")
return 4.0
print("DEBUG: Calculating final PSH")
psh = annual_avg.mean()
if math.isnan(psh):
print("ERROR: PSH calculation resulted in NaN")
return 4.0
print(f"DEBUG: PSH calculated successfully, value={psh:.2f}")
print(f"获取成功平均PSH: {psh:.2f} 小时/天")
return psh
except requests.exceptions.RequestException as e:
print(f"ERROR: NASA POWER API request failed: {e}")
return 4.0
except Exception as e:
print(f"ERROR: Error processing API data: {e}")
return 4.0
def calculate_optimal_tilt(lat):
"""根据纬度计算最佳倾角(单位:度)"""
try:
lat_abs = abs(lat)
if lat_abs < 25:
optimal_tilt = lat_abs * 0.87
elif lat_abs <= 50:
optimal_tilt = lat_abs * 0.76 + 3.1
else:
optimal_tilt = lat_abs * 0.5 + 16.3
return optimal_tilt
except ValueError as e:
raise Exception(f"倾角计算错误: {e}")
def pv_area(panel_capacity, slope_deg, shading_factor=0.1, land_compactness=1.0, terrain_complexity=1.0):
"""计算单块光伏组件占地面积"""
base_area = panel_capacity * 6
slope_factor = 1 + (slope_deg / 50) if slope_deg <= 15 else 1.5
shade_factor = 1 + shading_factor * 2
compact_factor = 1 / land_compactness if land_compactness > 0 else 1.5
terrain_factor = terrain_complexity
return base_area * slope_factor * shade_factor * compact_factor * terrain_factor
def calculate_pv_potential(available_area_sq_km, component_name, longitude, latitude, slope_deg=10,
shading_factor=0.1, land_compactness=0.8, terrain_complexity=1.2,
terrain_type="耕地", pv_type="centralized", land_availability=0.85,
min_irradiance=800, max_slope=25, electricity_price=0.65, q=0.02,
is_fixed=True, optimize=True, peak_load_hour=16, cost_per_kw=3.4,
E_S=1.0, K=0.8, project_lifetime=25, discount_rate=0.06):
"""计算最小和最大组件数量的光伏系统潜力"""
# 输入验证
if available_area_sq_km <= 0:
raise ValueError("可用面积必须大于0")
if slope_deg < 0 or slope_deg > max_slope:
raise ValueError(f"坡度必须在0-{max_slope}度之间")
# 转换为公顷
available_area_hectares = available_area_sq_km * 100
# 验证光伏类型与地形类型
valid_terrains = TERRAIN_COMPLEXITY_RANGES.get(pv_type, {})
if terrain_type not in valid_terrains:
raise ValueError(f"{pv_type} 光伏不支持 {terrain_type} 地形。可选地形:{list(valid_terrains.keys())}")
# 获取地形调整参数
terrain_adjustments = TERRAIN_ADJUSTMENTS.get(terrain_type, {"land_availability": 0.85, "K": 0.8})
adjusted_land_availability = terrain_adjustments["land_availability"] / max(1.0, terrain_complexity)
adjusted_K = terrain_adjustments["K"] / max(1.0, terrain_complexity)
# 获取组件信息
pv_info = get_pv_product_info(component_name)
single_panel_capacity = pv_info["max_power"] / 1000 # kWp
pv_size = pv_info["pv_size"].split("×")
panel_length = float(pv_size[0]) / 1000 # 米
panel_width = float(pv_size[1]) / 1000 # 米
panel_area_sqm = panel_length * panel_width
# 获取阵列间距
tilt, azimuth = get_tilt_and_azimuth(is_fixed, optimize, longitude, latitude, peak_load_hour)
array_distance = calculate_array_distance(panel_width * 1.1, tilt, latitude)
spacing_factor = PANEL_SPACING_FACTORS.get(pv_type, 1.2)
adjusted_array_distance = array_distance * spacing_factor
# 计算有效面积
effective_area_hectares = available_area_hectares * adjusted_land_availability
effective_area_sqm = effective_area_hectares * 10000
# 计算每MW占地面积
area_per_mw = 10000 * (1 + slope_deg / 50 if slope_deg <= 15 else 1.5) * (
1 + shading_factor * 2) * terrain_complexity * spacing_factor
# 容量密度限制 (kW/m²)
capacity_density_limit = CAPACITY_LIMITS.get(pv_type, 5.0) / 1000
max_capacity_by_density = effective_area_sqm * capacity_density_limit
# 计算单块组件占地面积
row_spacing = panel_length * math.sin(math.radians(tilt)) + adjusted_array_distance
effective_panel_area = panel_area_sqm * (row_spacing / panel_length) * 1.2
# 调整 min/max 布局以确保差异
min_area_per_panel = effective_panel_area * 0.8 # 密集布局
max_area_per_panel = effective_panel_area * 1.5 # 稀疏布局
max_panels = math.floor(effective_area_sqm / min_area_per_panel)
min_panels = math.floor(effective_area_sqm / max_area_per_panel)
# 计算装机容量
max_capacity_raw = calculate_installed_capacity(pv_info["max_power"], max_panels)
min_capacity_raw = calculate_installed_capacity(pv_info["max_power"], min_panels)
# 应用容量密度限制
max_capacity = min(max_capacity_raw, max_capacity_by_density)
min_capacity = min(min_capacity_raw, max_capacity_by_density * 0.8) # 稀疏布局取80%
# 检查理论上限
theoretical_max_capacity_mw = available_area_sq_km * CAPACITY_LIMITS.get(pv_type, 5.0)
if max_capacity / 1000 > theoretical_max_capacity_mw:
max_capacity = theoretical_max_capacity_mw * 1000
max_panels = math.floor(max_capacity * 1000 / pv_info["max_power"])
if min_capacity / 1000 > theoretical_max_capacity_mw * 0.8:
min_capacity = theoretical_max_capacity_mw * 1000 * 0.8
min_panels = math.floor(min_capacity * 1000 / pv_info["max_power"])
# 计算指标
min_metrics = calculate_pv_metrics(
component_name=component_name, electricity_price=electricity_price, pv_number=min_panels,
q=q, longitude=longitude, latitude=latitude, is_fixed=is_fixed, optimize=optimize,
peak_load_hour=peak_load_hour, cost_per_kw=cost_per_kw * terrain_complexity, E_S=E_S, K=adjusted_K,
override_capacity=min_capacity
)
min_lcoe = calculate_lcoe(
capacity=min_metrics["capacity"], annual_energy=min_metrics["annual_energy"],
cost_per_kw=cost_per_kw * terrain_complexity, q=q, project_lifetime=project_lifetime,
discount_rate=discount_rate
)
max_metrics = calculate_pv_metrics(
component_name=component_name, electricity_price=electricity_price, pv_number=max_panels,
q=q, longitude=longitude, latitude=latitude, is_fixed=is_fixed, optimize=optimize,
peak_load_hour=peak_load_hour, cost_per_kw=cost_per_kw * terrain_complexity, E_S=E_S, K=adjusted_K,
override_capacity=max_capacity
)
max_lcoe = calculate_lcoe(
capacity=max_metrics["capacity"], annual_energy=max_metrics["annual_energy"],
cost_per_kw=cost_per_kw * terrain_complexity, q=q, project_lifetime=project_lifetime,
discount_rate=discount_rate
)
# 警告
if min_panels == max_panels:
print(f"警告:最小和最大组件数量相同 ({min_panels}),请检查地形复杂性或面积是否过小")
if min_panels == 0 or max_panels == 0:
print(f"警告组件数量为0请检查输入参数")
# 返回结果
return {
"min_case": {
**min_metrics, "lcoe": min_lcoe, "actual_panels": min_panels,
"available_area_sq_km": available_area_sq_km, "available_area_hectares": available_area_hectares,
"effective_area_hectares": effective_area_hectares, "panel_area_sqm": max_area_per_panel,
"terrain_type": terrain_type, "pv_type": pv_type, "theoretical_max_capacity_mw": theoretical_max_capacity_mw
},
"max_case": {
**max_metrics, "lcoe": max_lcoe, "actual_panels": max_panels,
"available_area_sq_km": available_area_sq_km, "available_area_hectares": available_area_hectares,
"effective_area_hectares": effective_area_hectares, "panel_area_sqm": min_area_per_panel,
"terrain_type": terrain_type, "pv_type": pv_type, "theoretical_max_capacity_mw": theoretical_max_capacity_mw
}
}
def calculate_lcoe(capacity, annual_energy, cost_per_kw, q, project_lifetime=25, discount_rate=0.06):
"""计算平准化度电成本(LCOE)"""
total_investment = capacity * cost_per_kw * 1000
annual_om_cost = total_investment * q
discount_factors = [(1 + discount_rate) ** -t for t in range(1, project_lifetime + 1)]
discounted_energy = sum(annual_energy * discount_factors[t] for t in range(project_lifetime))
discounted_costs = total_investment + sum(annual_om_cost * discount_factors[t] for t in range(project_lifetime))
if discounted_energy == 0:
return float('inf')
return discounted_costs / discounted_energy
def get_pv_product_info(component_name, excel_path=PV_EXCEL_PATH):
"""从Excel获取光伏组件信息"""
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}")
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:
tilt = calculate_optimal_tilt(latitude)
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 = calculate_optimal_tilt(latitude)
else:
print("倾角0°(水平)-90°(垂直)")
tilt = float(input("请输入倾角(度)"))
if not (0 <= tilt <= 90):
raise ValueError("倾角需在0-90°")
return tilt, azimuth
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))
def calculate_equivalent_hours(P, P_r):
"""计算等效小时数"""
if P_r == 0:
raise ValueError("额定功率不能为 0")
return P / P_r
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
def calculate_annual_energy(peak_hours, capacity, E_S=1.0, K=0.8):
"""计算年发电量"""
if any(x < 0 for x in [peak_hours, capacity]) or E_S <= 0 or not 0 <= K <= 1:
raise ValueError("输入参数需满足辐射量、容量≥0E_S>0K∈[0,1]")
return peak_hours * 365 * (capacity / E_S) * K # 单位kWh
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
}
def calculate_reference_yield(E_p, electricity_price, IC, q, n=25):
"""计算净现值(NPV)和内部收益率(IRR)"""
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}
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,
override_capacity=None):
"""计算光伏项目的各项指标"""
try:
tilt, azimuth = get_tilt_and_azimuth(is_fixed, optimize, longitude, latitude, peak_load_hour)
pv_info = get_pv_product_info(component_name)
width_mm = float(pv_info["pv_size"].split("×")[1])
L = (width_mm / 1000) * 1.1
array_distance = calculate_array_distance(L, tilt, latitude)
max_power = pv_info["max_power"]
# 使用提供的容量或计算容量
if override_capacity is not None:
capacity = override_capacity
else:
capacity = calculate_installed_capacity(max_power, pv_number)
# 使用NASA API获取峰值日照小时数
peak_hours = calculate_psh_average(latitude, longitude)
single_daily_energy = peak_hours * (capacity / pv_number) * K if pv_number > 0 else 0
E_p = calculate_annual_energy(peak_hours, capacity, E_S, K)
h = calculate_equivalent_hours(E_p, capacity) if capacity > 0 else 0
E_p_million_kwh = E_p / 1000000
env_benefits = calculate_environmental_benefits(E_p_million_kwh)
IC = capacity * cost_per_kw * 1000
ref_yield = calculate_reference_yield(E_p, electricity_price, IC, q)
return {
"longitude": longitude,
"latitude": latitude,
"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)}")
def print_result(min_case, max_case):
"""优化输出格式,使用表格展示结果"""
headers = ["指标", "最小组件数量", "最大组件数量"]
table_data = [
["经度", f"{min_case['longitude']:.2f}", f"{max_case['longitude']:.2f}"],
["纬度", f"{min_case['latitude']:.2f}", f"{max_case['latitude']:.2f}"],
["光伏类型", min_case["pv_type"], max_case["pv_type"]],
["地形类型", min_case["terrain_type"], max_case["terrain_type"]],
["组件型号", min_case["component_name"], max_case["component_name"]],
["识别面积 (平方千米)", f"{min_case['available_area_sq_km']:.2f}", f"{max_case['available_area_sq_km']:.2f}"],
["识别面积 (公顷)", f"{min_case['available_area_hectares']:.2f}", f"{max_case['available_area_hectares']:.2f}"],
["有效面积 (公顷)", f"{min_case['effective_area_hectares']:.2f}", f"{max_case['effective_area_hectares']:.2f}"],
["理论最大容量 (MW)", f"{min_case['theoretical_max_capacity_mw']:.2f}",
f"{max_case['theoretical_max_capacity_mw']:.2f}"],
["单块组件占地 (m²)", f"{min_case['panel_area_sqm']:.2f}", f"{max_case['panel_area_sqm']:.2f}"],
["组件数量", f"{min_case['actual_panels']:,}", f"{max_case['actual_panels']:,}"],
["倾角 (度)", f"{min_case['tilt']:.2f}", f"{max_case['tilt']:.2f}"],
["方位角 (度)", f"{min_case['azimuth']:.2f}", f"{max_case['azimuth']:.2f}"],
["阵列间距 (m)", f"{min_case['array_distance']:.2f}", f"{max_case['array_distance']:.2f}"],
["单块功率 (Wp)", f"{min_case['max_power']}", f"{max_case['max_power']}"],
["装机容量 (MW)", f"{min_case['capacity'] / 1000:.2f}", f"{max_case['capacity'] / 1000:.2f}"],
["峰值日照 (小时/天)", f"{min_case['peak_sunshine_hours']:.2f}", f"{max_case['peak_sunshine_hours']:.2f}"],
["年发电量 (kWh)", f"{min_case['annual_energy']:,.0f}", f"{max_case['annual_energy']:,.0f}"],
["等效小时数", f"{min_case['equivalent_hours']:.2f}", f"{max_case['equivalent_hours']:.2f}"],
["LCOE (元/kWh)", f"{min_case['lcoe']:.4f}", f"{max_case['lcoe']:.4f}"],
["标准煤减排 (kg)", f"{min_case['coal_reduction']:,.0f}", f"{max_case['coal_reduction']:,.0f}"],
["CO₂减排 (kg)", f"{min_case['CO2_reduction']:,.0f}", f"{max_case['CO2_reduction']:,.0f}"],
["SO₂减排 (kg)", f"{min_case['SO2_reduction']:,.0f}", f"{max_case['SO2_reduction']:,.0f}"],
["NOx减排 (kg)", f"{min_case['NOX_reduction']:,.0f}", f"{max_case['NOX_reduction']:,.0f}"],
["IRR (%)", f"{min_case['IRR']:.2f}", f"{max_case['IRR']:.2f}"]
]
print("\n===== 光伏系统潜力评估结果 =====")
print(tabulate(table_data, headers=headers, tablefmt="grid"))
# 主程序
if __name__ == "__main__":
while True:
try:
# 输入参数
print("\n======= 光伏系统潜力评估 =======")
print("请输入以下必要参数:")
# 输入经纬度
latitude = float(input("请输入纬度(-90到90例如39.9"))
if not -90 <= latitude <= 90:
raise ValueError("纬度必须在-90到90之间")
longitude = float(input("请输入经度(-180到180例如116.4"))
if not -180 <= longitude <= 180:
raise ValueError("经度必须在-180到180之间")
# 输入可用面积
available_area_sq_km = float(input("请输入识别面积平方千米例如10"))
if available_area_sq_km <= 0:
raise ValueError("识别面积必须大于0")
# 输入光伏类型
pv_type = input("请输入光伏类型distributed, centralized, floating").lower()
if pv_type not in ["distributed", "centralized", "floating"]:
raise ValueError("光伏类型必须是 distributed, centralized 或 floating")
# 输入地形类型
valid_terrains = list(TERRAIN_COMPLEXITY_RANGES.get(pv_type, {}).keys())
print(f"支持的地形类型:{valid_terrains}")
terrain_type = input("请输入地形类型:")
if terrain_type not in valid_terrains:
raise ValueError(f"不支持的地形类型:{terrain_type}")
# 输入组件型号
component_name = input("请输入光伏组件型号需在Excel中存在例如M10-72H")
# 输入其他参数
slope_deg = float(input("请输入地形坡度0-25例如10"))
if not 0 <= slope_deg <= 25:
raise ValueError("坡度必须在0-25度之间")
terrain_complexity = float(input("请输入地形复杂性因子参考范围1.0-2.0例如1.2"))
min_complexity, max_complexity = TERRAIN_COMPLEXITY_RANGES[pv_type][terrain_type]
if not min_complexity <= terrain_complexity <= max_complexity:
raise ValueError(f"地形复杂性因子必须在 {min_complexity}-{max_complexity} 之间")
electricity_price = float(input("请输入电价(元/kWh例如0.65"))
if electricity_price < 0:
raise ValueError("电价必须非负")
# 计算光伏潜力
result = calculate_pv_potential(
available_area_sq_km=available_area_sq_km,
component_name=component_name,
longitude=longitude,
latitude=latitude,
slope_deg=slope_deg,
terrain_complexity=terrain_complexity,
terrain_type=terrain_type,
pv_type=pv_type,
electricity_price=electricity_price
)
# 输出结果
print_result(result["min_case"], result["max_case"])
# 询问是否继续
if input("\n是否继续评估?(y/n)").lower() != 'y':
break
except ValueError as ve:
print(f"输入错误:{ve}")
except FileNotFoundError as fe:
print(f"文件错误:{fe}")
except Exception as e:
print(f"发生错误:{e}")
print("请重新输入参数或检查错误。\n")