windDirectionFrequencyAnalyst.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import os
  2. import pandas as pd
  3. import numpy as np
  4. import plotly.graph_objects as go
  5. from plotly.subplots import make_subplots
  6. from behavior.analystNotFilter import AnalystNotFilter
  7. from utils.directoryUtil import DirectoryUtil as dir
  8. import matplotlib.pyplot as plt
  9. from algorithmContract.confBusiness import *
  10. from algorithmContract.contract import Contract
  11. from utils.jsonUtil import JsonUtil
  12. class WindDirectionFrequencyAnalyst(AnalystNotFilter):
  13. def typeAnalyst(self):
  14. return "wind_direction_frequency"
  15. '''
  16. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  17. dictionary=self.processTurbineData(turbineCodes,conf,[Field_DeviceCode,Field_Time,Field_WindDirection,Field_WindSpeed,Field_ActiverPower])
  18. # 获取机型信息
  19. turbrineInfos = self.common.getTurbineInfos(
  20. conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  21. dataFrameMerge=self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  22. frequency_data=self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, conf)
  23. returnDatas=[]
  24. returnDatas.append(frequency_data)
  25. returnJsonData= self.outputJsonData(conf,outputAnalysisDir,turbrineInfos,dataFrameMerge)
  26. returnDatas.append(returnJsonData)
  27. return returnDatas
  28. '''
  29. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  30. dictionary = self.processTurbineData(turbineCodes, conf, [Field_DeviceCode, Field_Time, Field_WindDirection, Field_WindSpeed, Field_ActiverPower])
  31. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  32. dataFrameMerge = self.userDataFrame(dictionary, conf.dataContract.configAnalysis, self)
  33. results=self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, conf)
  34. returnJsonData = self.outputJsonData(conf, outputAnalysisDir, turbineInfos, dataFrameMerge)
  35. returnDatas=pd.concat([results,returnJsonData], axis=0, ignore_index=True)
  36. return returnDatas
  37. '''
  38. def windRoseAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract):
  39. # 检查所需列是否存在
  40. required_columns = {Field_WindDirection, Field_WindSpeed}
  41. if not required_columns.issubset(dataFrameMerge.columns) or dataFrameMerge[Field_WindDirection].isna().all() or dataFrameMerge[Field_WindSpeed].isna().all():
  42. msg=f"DataFrame缺少必要的列及值。涉及的列有: {required_columns}"
  43. # raise ValueError(msg)
  44. self.logger.warning(msg)
  45. return pd.DataFrame()
  46. # 风速区间
  47. bins = [0, 3, 6, 9, np.inf]
  48. speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']
  49. wind_directions = np.arange(0, 360, 22.5)
  50. colorscale = {
  51. '[0,3)': 'rgba(247.0, 251.0, 255.0, 1.0)',
  52. '[3,6)': 'rgba(171.33333333333334, 207.66666666666666, 229.66666666666669, 1.0)',
  53. '[6,9)': 'rgba(55.0, 135.0, 192.33333333333334, 1.0)',
  54. '>=9': 'rgba(8.0, 48.0, 107.0, 1.0)'
  55. }
  56. # 按设备名分组数据
  57. grouped = dataFrameMerge.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  58. result_rows = []
  59. for name, group in grouped:
  60. speed_bins = pd.cut(
  61. group[Field_WindSpeed], bins=bins, labels=speed_labels)
  62. # 调整风向数据以使东方为0度
  63. # adjusted_wind_dir = (group[Field_WindDirection] - 90) % 360
  64. # group['风向分组'] = pd.cut(adjusted_wind_dir, bins=wind_directions, labels=wind_directions[:-1])
  65. group['风向分组'] = pd.cut(
  66. group[Field_WindDirection], bins=wind_directions, labels=wind_directions[:-1])
  67. # 初始化子图,设定为极坐标
  68. fig = make_subplots(rows=1, cols=1, specs=[[{'type': 'polar'}]])
  69. for label in speed_labels:
  70. subset = group[speed_bins == label]
  71. counts = subset['风向分组'].value_counts().reindex(
  72. wind_directions[:-1], fill_value=0)
  73. # 转换为百分比
  74. percentage = (counts / counts.sum()) * 100
  75. # 创建 Barpolar 跟踪,并应用单色渐变
  76. trace = go.Barpolar(
  77. r=percentage.values,
  78. theta=counts.index, # 这里的角度已经适配上北下南左西右东的布局
  79. name=label,
  80. marker_color=colorscale[label], # 应用颜色尺度
  81. marker_showscale=False, # 不显示颜色条
  82. marker_line_color='white', # 设置线条颜色,增加扇区之间的分隔
  83. marker_line_width=1 # 设置线条宽度
  84. )
  85. fig.add_trace(trace)
  86. # 设置图表的一些基本属性
  87. fig.update_layout(
  88. title={
  89. "text": f"机组: {name[0]}",
  90. #"x": 0.5
  91. },
  92. polar=dict(
  93. radialaxis=dict(visible=True),
  94. angularaxis=dict(
  95. tickmode="array",
  96. tickvals=wind_directions,
  97. # 明确标注北、东、南、西等方向,以适应以北为0度的布局
  98. ticktext=['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE',
  99. 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
  100. # 更新角度标签,以适应以东为0度的布局
  101. # ticktext=['E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNE', 'NE', 'ENE']
  102. )
  103. ),
  104. legend_title="风速",
  105. margin=dict(t=50, b=10) # t为顶部(top)间距,b为底部(bottom)间距
  106. )
  107. # 保存图像
  108. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  109. fig.write_image(filePathOfImage, scale=3)
  110. filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  111. fig.write_html(filePathOfHtml)
  112. result_rows.append({
  113. Field_Return_TypeAnalyst: self.typeAnalyst(),
  114. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  115. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  116. Field_CodeOfTurbine: name[1],
  117. Field_Return_FilePath: filePathOfImage,
  118. Field_Return_IsSaveDatabase: False
  119. })
  120. result_rows.append({
  121. Field_Return_TypeAnalyst: self.typeAnalyst(),
  122. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  123. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  124. Field_CodeOfTurbine: name[1],
  125. Field_Return_FilePath: filePathOfHtml,
  126. Field_Return_IsSaveDatabase: True
  127. })
  128. result_df = pd.DataFrame(result_rows)
  129. return result_df
  130. '''
  131. def windRoseAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract):
  132. # 检查所需列是否存在
  133. required_columns = {Field_WindDirection, Field_WindSpeed}
  134. if not required_columns.issubset(dataFrameMerge.columns) or dataFrameMerge[Field_WindDirection].isna().all() or dataFrameMerge[Field_WindSpeed].isna().all():
  135. msg = f"DataFrame缺少必要的列及值。涉及的列有: {required_columns}"
  136. self.logger.warning(msg)
  137. return pd.DataFrame()
  138. # 定义类属性 self.frequency_data
  139. self.frequency_data = {}
  140. # 风速区间
  141. bins = [0, 3, 6, 9, np.inf]
  142. speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']
  143. wind_directions = np.arange(0, 360, 22.5)
  144. # 设置颜色
  145. colorscale = {
  146. '[0,3)': 'rgba(247.0, 251.0, 255.0, 1.0)',
  147. '[3,6)': 'rgba(171.33333333333334, 207.66666666666666, 229.66666666666669, 1.0)',
  148. '[6,9)': 'rgba(55.0, 135.0, 192.33333333333334, 1.0)',
  149. '>=9': 'rgba(8.0, 48.0, 107.0, 1.0)'
  150. }
  151. # 按设备名分组数据
  152. grouped = dataFrameMerge.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  153. result_rows = []
  154. for name, group in grouped:
  155. turbine_code = name[1]
  156. self.frequency_data[turbine_code] = {}
  157. self.frequency_data[turbine_code]['title'] = f"机组: {name[0]}"
  158. # 调整风向数据以使北方为0度
  159. # adjusted_wind_dir = (group[Field_WindDirection] - 90) % 360
  160. group['风向分组'] = pd.cut( group[Field_WindDirection], bins=wind_directions, labels=wind_directions[:-1])
  161. # 初始化子图,设定为极坐标
  162. fig = make_subplots(rows=1, cols=1, specs=[[{'type': 'polar'}]])
  163. # 计算每个风向分组在各个风速区间内的频率
  164. for label in speed_labels:
  165. subset = group[pd.cut(group[Field_WindSpeed], bins=bins, labels=speed_labels) == label]
  166. counts = subset['风向分组'].value_counts().reindex(wind_directions[:-1], fill_value=0)
  167. percentage = (counts / counts.sum()) * 100
  168. self.frequency_data[turbine_code][label] = percentage.to_dict()
  169. # 创建 Barpolar 跟踪,并应用单色渐变
  170. trace = go.Barpolar(
  171. r=percentage.values,
  172. theta=counts.index, # 这里的角度已经适配上北下南左西右东的布局
  173. name=label,
  174. marker_color=colorscale[label], # 应用颜色尺度
  175. marker_showscale=False, # 不显示颜色条
  176. marker_line_color='white', # 设置线条颜色,增加扇区之间的分隔
  177. marker_line_width=1 # 设置线条宽度
  178. )
  179. fig.add_trace(trace)
  180. # 设置图表的一些基本属性
  181. fig.update_layout(
  182. title={
  183. "text": f"机组: {name[0]}",
  184. #"x": 0.5
  185. },
  186. polar=dict(
  187. radialaxis=dict(visible=True),
  188. angularaxis=dict(
  189. tickmode="array",
  190. tickvals=wind_directions,
  191. # 明确标注北、东、南、西等方向,以适应以北为0度的布局
  192. ticktext=['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE',
  193. 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
  194. # 更新角度标签,以适应以东为0度的布局
  195. # ticktext=['E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNE', 'NE', 'ENE']
  196. )
  197. ),
  198. legend_title="风速",
  199. margin=dict(t=50, b=10) # t为顶部(top)间距,b为底部(bottom)间距
  200. )
  201. # 保存图像
  202. # filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  203. # fig.write_image(filePathOfImage, scale=3)
  204. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  205. # fig.write_html(filePathOfHtml)
  206. # result_rows.append({
  207. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  208. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  209. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  210. # Field_CodeOfTurbine: name[1],
  211. # Field_Return_FilePath: filePathOfImage,
  212. # Field_Return_IsSaveDatabase: False
  213. # })
  214. # result_rows.append({
  215. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  216. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  217. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  218. # Field_CodeOfTurbine: name[1],
  219. # Field_Return_FilePath: filePathOfHtml,
  220. # Field_Return_IsSaveDatabase: True
  221. # })
  222. result_df = pd.DataFrame(result_rows)
  223. # 返回结果数据框
  224. return result_df
  225. def outputJsonData(self, conf: Contract, outputAnalysisDir: str, turbineModelInfo: pd.Series, dataFrameMerge: pd.DataFrame) -> pd.DataFrame:
  226. turbineCodes = dataFrameMerge[Field_CodeOfTurbine].unique()
  227. result_rows = []
  228. for turbineCode in turbineCodes:
  229. jsonDictionary = self.convert2Json(turbineModelInfo, turbineCodes=turbineCode, dataFrameOfTurbines=dataFrameMerge)
  230. jsonFileName = f"风向玫瑰图{turbineCode}.json"
  231. jsonFilePath = os.path.join(outputAnalysisDir, jsonFileName)
  232. JsonUtil.write_json(jsonDictionary, file_path=jsonFilePath)
  233. result_rows.append({
  234. Field_Return_TypeAnalyst: self.typeAnalyst(),
  235. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  236. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  237. Field_CodeOfTurbine: turbineCode,
  238. Field_Return_FilePath: jsonFilePath,
  239. Field_Return_IsSaveDatabase: True
  240. })
  241. returnDatas = pd.DataFrame(result_rows)
  242. return returnDatas
  243. def outputJsonData(self, conf: Contract, outputAnalysisDir: str, turbineModelInfo: pd.Series, dataFrameMerge: pd.DataFrame) -> pd.DataFrame:
  244. turbineCodes = dataFrameMerge[Field_CodeOfTurbine].unique()
  245. result_rows = []
  246. for turbineCode in turbineCodes:
  247. if turbineCode in self.frequency_data:
  248. # 确保 enginName 是具体的值
  249. enginName = dataFrameMerge[dataFrameMerge[Field_CodeOfTurbine] == turbineCode][Field_NameOfTurbine].iloc[0]
  250. if isinstance(enginName, pd.Series):
  251. enginName = enginName.iloc[0]
  252. jsonDictionary = self.convert2Json(turbineModelInfo, turbineCode, dataFrameMerge)
  253. jsonFileName = f"wind_direction_frequency{enginName}.json"
  254. jsonFilePath = os.path.join(outputAnalysisDir, jsonFileName)
  255. JsonUtil.write_json(jsonDictionary, file_path=jsonFilePath)
  256. result_rows.append({
  257. Field_Return_TypeAnalyst: self.typeAnalyst(),
  258. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  259. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  260. Field_CodeOfTurbine: turbineCode,
  261. Field_Return_FilePath: jsonFilePath,
  262. Field_Return_IsSaveDatabase: True
  263. })
  264. returnDatas = pd.DataFrame(result_rows)
  265. return returnDatas
  266. def convert2Json(self, turbineModelInfo: pd.Series, turbineCode, dataFrameOfTurbines: pd.DataFrame):
  267. # 确保从 Series 中提取的是具体的值
  268. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  269. if isinstance(engineTypeCode, pd.Series):
  270. engineTypeCode = engineTypeCode.iloc[0]
  271. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  272. if isinstance(engineTypeName, pd.Series):
  273. engineTypeName = engineTypeName.iloc[0]
  274. if turbineCode in self.frequency_data:
  275. # 确保 enginName 是具体的值
  276. enginName = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine] == turbineCode][Field_NameOfTurbine].iloc[0]
  277. if isinstance(enginName, pd.Series):
  278. enginName = enginName.iloc[0]
  279. turbine_info = {
  280. "enginName": enginName,
  281. "enginCode": turbineCode,
  282. "title": self.frequency_data[turbineCode]['title'],
  283. "windRoseData": []
  284. }
  285. for speed_label, direction_data in self.frequency_data[turbineCode].items():
  286. if speed_label == 'title':
  287. continue
  288. for direction, freq in direction_data.items():
  289. turbine_info["windRoseData"].append({
  290. "windDirection": float(direction),
  291. "windSpeedRange": speed_label,
  292. "frequency": float(freq)
  293. })
  294. result = {
  295. "analysisTypeCode": "风向玫瑰分析",
  296. "engineTypeCode": engineTypeCode,
  297. "engineTypeName": engineTypeName,
  298. "axes": {
  299. "radial": "频率百分比(%)",
  300. "angular": "风向",
  301. "levelname": "风速(m/s)"
  302. },
  303. "data": [turbine_info]
  304. }
  305. return result
  306. return {}