cd_flux_removal.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. """
  2. Cd通量移除计算API接口
  3. @description: 提供籽粒移除和秸秆移除的Cd通量计算功能
  4. """
  5. from fastapi import APIRouter, HTTPException, Query, Path
  6. from fastapi.responses import FileResponse
  7. from pydantic import BaseModel, Field
  8. from typing import Dict, Any, Optional
  9. import logging
  10. import os
  11. from ..services.cd_flux_removal_service import CdFluxRemovalService
  12. router = APIRouter()
  13. # =============================================================================
  14. # 数据模型定义
  15. # =============================================================================
  16. class CdFluxRemovalResponse(BaseModel):
  17. """
  18. Cd通量移除计算响应模型
  19. @description: 标准化的API响应格式
  20. """
  21. success: bool = Field(..., description="是否成功")
  22. message: str = Field(..., description="响应消息")
  23. data: Optional[Dict[str, Any]] = Field(None, description="计算结果数据")
  24. class VisualizationResponse(BaseModel):
  25. """
  26. 可视化结果响应模型
  27. @description: 绘图接口的响应格式
  28. """
  29. success: bool = Field(..., description="是否成功")
  30. message: str = Field(..., description="响应消息")
  31. data: Optional[Dict[str, Any]] = Field(None, description="可视化结果数据")
  32. files: Optional[Dict[str, str]] = Field(None, description="生成的文件路径")
  33. # 设置日志
  34. logger = logging.getLogger(__name__)
  35. # =============================================================================
  36. # Cd通量移除计算接口
  37. # =============================================================================
  38. @router.get("/grain-removal",
  39. summary="计算籽粒移除Cd通量",
  40. description="根据指定地区计算籽粒移除Cd通量,公式:EXP(LnCropCd)*F11*0.5*15/1000",
  41. response_model=CdFluxRemovalResponse)
  42. async def calculate_grain_removal(
  43. area: str = Query(..., description="地区名称,如:韶关")
  44. ) -> Dict[str, Any]:
  45. """
  46. 计算籽粒移除Cd通量
  47. @param area: 地区名称
  48. @returns: 籽粒移除Cd通量计算结果
  49. 计算公式:籽粒移除(g/ha/a) = EXP(LnCropCd) * F11 * 0.5 * 15 / 1000
  50. 数据来源:
  51. - LnCropCd: CropCd_output_data表
  52. - F11: Parameters表(作物亩产量)
  53. """
  54. try:
  55. service = CdFluxRemovalService()
  56. result = service.calculate_grain_removal_by_area(area)
  57. if not result["success"]:
  58. raise HTTPException(
  59. status_code=404,
  60. detail=result["message"]
  61. )
  62. return result
  63. except HTTPException:
  64. raise
  65. except Exception as e:
  66. logger.error(f"计算地区 '{area}' 的籽粒移除Cd通量失败: {str(e)}")
  67. raise HTTPException(
  68. status_code=500,
  69. detail=f"计算失败: {str(e)}"
  70. )
  71. @router.get("/straw-removal",
  72. summary="计算秸秆移除Cd通量",
  73. description="根据指定地区计算秸秆移除Cd通量,公式:[EXP(LnCropCd)/(EXP(LnCropCd)*0.76-0.0034)]*F11*0.5*15/1000",
  74. response_model=CdFluxRemovalResponse)
  75. async def calculate_straw_removal(
  76. area: str = Query(..., description="地区名称,如:韶关")
  77. ) -> Dict[str, Any]:
  78. """
  79. 计算秸秆移除Cd通量
  80. @param area: 地区名称
  81. @returns: 秸秆移除Cd通量计算结果
  82. 计算公式:秸秆移除(g/ha/a) = [EXP(LnCropCd)/(EXP(LnCropCd)*0.76-0.0034)] * F11 * 0.5 * 15 / 1000
  83. 数据来源:
  84. - LnCropCd: CropCd_output_data表
  85. - F11: Parameters表(作物亩产量)
  86. """
  87. try:
  88. service = CdFluxRemovalService()
  89. result = service.calculate_straw_removal_by_area(area)
  90. if not result["success"]:
  91. raise HTTPException(
  92. status_code=404,
  93. detail=result["message"]
  94. )
  95. return result
  96. except HTTPException:
  97. raise
  98. except Exception as e:
  99. logger.error(f"计算地区 '{area}' 的秸秆移除Cd通量失败: {str(e)}")
  100. raise HTTPException(
  101. status_code=500,
  102. detail=f"计算失败: {str(e)}"
  103. )
  104. # =============================================================================
  105. # Cd通量移除可视化接口
  106. # =============================================================================
  107. @router.get("/grain-removal/visualize",
  108. summary="生成籽粒移除Cd通量可视化图表",
  109. description="计算籽粒移除Cd通量并生成栅格地图并返回图片文件")
  110. async def visualize_grain_removal(
  111. area: str = Query(..., description="地区名称,如:韶关"),
  112. level: str = Query(..., description="行政层级,必须为: county | city | province"),
  113. colormap: str = Query("green_yellow_red_purple", description="色彩方案"),
  114. resolution_factor: float = Query(4.0, description="分辨率因子(默认4.0,更快)"),
  115. enable_interpolation: bool = Query(False, description="是否启用空间插值(默认关闭以提升性能)"),
  116. cleanup_intermediate: bool = Query(True, description="是否清理中间文件(默认是)")
  117. ):
  118. """
  119. 生成籽粒移除Cd通量可视化图表
  120. @param area: 地区名称
  121. @returns: 栅格地图文件
  122. 功能包括:
  123. 1. 计算籽粒移除Cd通量
  124. 2. 生成栅格地图
  125. 3. 直接返回图片文件
  126. """
  127. try:
  128. service = CdFluxRemovalService()
  129. # 行政层级校验(不允许模糊)
  130. if level not in ("county", "city", "province"):
  131. raise HTTPException(status_code=400, detail="参数 level 必须为 'county' | 'city' | 'province'")
  132. # 计算籽粒移除Cd通量(传入严格层级以便参数表查找做后缀标准化精确匹配)
  133. calc_result = service.calculate_grain_removal_by_area(area, level=level)
  134. if not calc_result["success"]:
  135. raise HTTPException(
  136. status_code=404,
  137. detail=calc_result["message"]
  138. )
  139. # 获取包含坐标的结果数据
  140. results_with_coords = service.get_coordinates_for_results(calc_result["data"])
  141. if not results_with_coords:
  142. raise HTTPException(
  143. status_code=404,
  144. detail=f"未找到地区 '{area}' 的坐标数据,无法生成可视化"
  145. )
  146. # 创建可视化
  147. visualization_files = service.create_flux_visualization(
  148. area=area,
  149. level=level,
  150. calculation_type="grain_removal",
  151. results_with_coords=results_with_coords,
  152. colormap=colormap,
  153. resolution_factor=resolution_factor,
  154. enable_interpolation=enable_interpolation,
  155. cleanup_intermediate=cleanup_intermediate
  156. )
  157. # 检查地图文件是否生成成功
  158. map_file = visualization_files.get("map")
  159. if not map_file or not os.path.exists(map_file):
  160. raise HTTPException(status_code=500, detail="地图文件生成失败")
  161. return FileResponse(
  162. path=map_file,
  163. filename=f"{area}_grain_removal_cd_flux_map.jpg",
  164. media_type="image/jpeg"
  165. )
  166. except HTTPException:
  167. raise
  168. except Exception as e:
  169. logger.error(f"生成地区 '{area}' 的籽粒移除Cd通量可视化失败: {str(e)}")
  170. raise HTTPException(
  171. status_code=500,
  172. detail=f"可视化生成失败: {str(e)}"
  173. )
  174. @router.get("/straw-removal/visualize",
  175. summary="生成秸秆移除Cd通量可视化图表",
  176. description="计算秸秆移除Cd通量并生成栅格地图并返回图片文件")
  177. async def visualize_straw_removal(
  178. area: str = Query(..., description="地区名称,如:韶关"),
  179. level: str = Query(..., description="行政层级,必须为: county | city | province"),
  180. colormap: str = Query("green_yellow_red_purple", description="色彩方案"),
  181. resolution_factor: float = Query(4.0, description="分辨率因子(默认4.0,更快)"),
  182. enable_interpolation: bool = Query(False, description="是否启用空间插值(默认关闭以提升性能)"),
  183. cleanup_intermediate: bool = Query(True, description="是否清理中间文件(默认是)")
  184. ):
  185. """
  186. 生成秸秆移除Cd通量可视化图表
  187. @param area: 地区名称
  188. @returns: 栅格地图文件
  189. 功能包括:
  190. 1. 计算秸秆移除Cd通量
  191. 2. 生成栅格地图
  192. 3. 直接返回图片文件
  193. """
  194. try:
  195. service = CdFluxRemovalService()
  196. # 行政层级校验(不允许模糊)
  197. if level not in ("county", "city", "province"):
  198. raise HTTPException(status_code=400, detail="参数 level 必须为 'county' | 'city' | 'province'")
  199. # 计算秸秆移除Cd通量(传入严格层级以便参数表查找做后缀标准化精确匹配)
  200. calc_result = service.calculate_straw_removal_by_area(area, level=level)
  201. if not calc_result["success"]:
  202. raise HTTPException(
  203. status_code=404,
  204. detail=calc_result["message"]
  205. )
  206. # 获取包含坐标的结果数据
  207. results_with_coords = service.get_coordinates_for_results(calc_result["data"])
  208. if not results_with_coords:
  209. raise HTTPException(
  210. status_code=404,
  211. detail=f"未找到地区 '{area}' 的坐标数据,无法生成可视化"
  212. )
  213. # 创建可视化
  214. visualization_files = service.create_flux_visualization(
  215. area=area,
  216. level=level,
  217. calculation_type="straw_removal",
  218. results_with_coords=results_with_coords,
  219. colormap=colormap,
  220. resolution_factor=resolution_factor,
  221. enable_interpolation=enable_interpolation,
  222. cleanup_intermediate=cleanup_intermediate
  223. )
  224. # 检查地图文件是否生成成功
  225. map_file = visualization_files.get("map")
  226. if not map_file or not os.path.exists(map_file):
  227. raise HTTPException(status_code=500, detail="地图文件生成失败")
  228. return FileResponse(
  229. path=map_file,
  230. filename=f"{area}_straw_removal_cd_flux_map.jpg",
  231. media_type="image/jpeg"
  232. )
  233. except HTTPException:
  234. raise
  235. except Exception as e:
  236. logger.error(f"生成地区 '{area}' 的秸秆移除Cd通量可视化失败: {str(e)}")
  237. raise HTTPException(
  238. status_code=500,
  239. detail=f"可视化生成失败: {str(e)}"
  240. )
  241. @router.get("/export-data",
  242. summary="导出Cd通量移除计算数据",
  243. description="导出籽粒移除或秸秆移除的计算结果为CSV文件",
  244. response_model=CdFluxRemovalResponse)
  245. async def export_flux_data(
  246. area: str = Query(..., description="地区名称,如:韶关"),
  247. calculation_type: str = Query(..., description="计算类型:grain_removal 或 straw_removal")
  248. ) -> Dict[str, Any]:
  249. """
  250. 导出Cd通量移除计算数据
  251. @param area: 地区名称
  252. @param calculation_type: 计算类型(grain_removal 或 straw_removal)
  253. @returns: 导出结果和文件路径
  254. """
  255. try:
  256. if calculation_type not in ["grain_removal", "straw_removal"]:
  257. raise HTTPException(
  258. status_code=400,
  259. detail="计算类型必须是 'grain_removal' 或 'straw_removal'"
  260. )
  261. service = CdFluxRemovalService()
  262. # 根据类型计算相应的Cd通量
  263. if calculation_type == "grain_removal":
  264. calc_result = service.calculate_grain_removal_by_area(area)
  265. else:
  266. calc_result = service.calculate_straw_removal_by_area(area)
  267. if not calc_result["success"]:
  268. raise HTTPException(
  269. status_code=404,
  270. detail=calc_result["message"]
  271. )
  272. # 导出数据
  273. csv_path = service.export_results_to_csv(calc_result["data"])
  274. return {
  275. "success": True,
  276. "message": f"地区 '{area}' 的 {calculation_type} 数据导出成功",
  277. "data": {
  278. "area": area,
  279. "calculation_type": calculation_type,
  280. "exported_file": csv_path,
  281. "total_records": len(calc_result["data"]["results"]),
  282. "statistics": calc_result["data"]["statistics"]
  283. }
  284. }
  285. except HTTPException:
  286. raise
  287. except Exception as e:
  288. logger.error(f"导出地区 '{area}' 的 {calculation_type} 数据失败: {str(e)}")
  289. raise HTTPException(
  290. status_code=500,
  291. detail=f"数据导出失败: {str(e)}"
  292. )