water.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. from fastapi import APIRouter, Form, HTTPException, BackgroundTasks, Query
  2. from fastapi.responses import FileResponse, JSONResponse
  3. import os
  4. import logging
  5. import tempfile
  6. import shutil
  7. from typing import Dict, Any, Optional
  8. from datetime import datetime
  9. import json
  10. from pathlib import Path
  11. # 导入服务层函数
  12. from ..services.water_service import (
  13. process_land_to_visualization,
  14. get_land_statistics,
  15. get_base_dir
  16. )
  17. # 配置日志 - 避免重复日志输出
  18. logger = logging.getLogger(__name__)
  19. # 避免重复添加处理器导致重复日志
  20. if not logger.handlers:
  21. logger.setLevel(logging.INFO)
  22. handler = logging.StreamHandler()
  23. formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  24. handler.setFormatter(formatter)
  25. logger.addHandler(handler)
  26. # 关闭日志传播,避免与父级日志处理器重复输出
  27. logger.propagate = False
  28. router = APIRouter()
  29. # 清理临时路径的函数
  30. def cleanup_temp_path(path: str) -> None:
  31. """删除临时文件或整个临时目录"""
  32. try:
  33. if os.path.exists(path):
  34. if os.path.isfile(path):
  35. os.unlink(path)
  36. elif os.path.isdir(path):
  37. shutil.rmtree(path)
  38. except Exception as e:
  39. logger.warning(f"清理临时路径失败: {path}, 错误: {e}")
  40. @router.get("/default-map",
  41. summary="获取默认地图图片",
  42. description="返回指定土地类型的默认地图图片")
  43. async def get_default_map(
  44. land_type: str = Query("水田", description="土地类型,如:'水田'、'旱地'或'水浇地'")
  45. ) -> FileResponse:
  46. """返回默认地图图片"""
  47. try:
  48. logger.info(f"获取默认地图: {land_type}")
  49. # 获取基础目录
  50. base_dir = get_base_dir()
  51. raster_dir = os.path.join(base_dir, "..", "static", "water", "Raster")
  52. map_path = os.path.join(raster_dir, f"{land_type}_Cd含量地图.jpg")
  53. if not os.path.exists(map_path):
  54. logger.warning(f"默认地图文件不存在: {map_path}")
  55. raise HTTPException(status_code=404, detail="地图文件不存在")
  56. return FileResponse(
  57. path=map_path,
  58. filename=f"{land_type}_Cd含量地图.jpg",
  59. media_type="image/jpeg"
  60. )
  61. except HTTPException:
  62. raise
  63. except Exception as e:
  64. logger.error(f"获取默认地图失败: {str(e)}")
  65. raise HTTPException(
  66. status_code=500,
  67. detail=f"获取默认地图失败: {str(e)}"
  68. )
  69. @router.get("/default-histogram",
  70. summary="获取默认直方图",
  71. description="返回指定土地类型的默认直方图图片")
  72. async def get_default_histogram(
  73. land_type: str = Query("水田", description="土地类型,如:'水田'、'旱地'或'水浇地'")
  74. ) -> FileResponse:
  75. """返回默认直方图图片"""
  76. try:
  77. logger.info(f"获取默认直方图: {land_type}")
  78. # 获取基础目录
  79. base_dir = get_base_dir()
  80. raster_dir = os.path.join(base_dir, "..", "static", "water", "Raster")
  81. hist_path = os.path.join(raster_dir, f"{land_type}_Cd含量直方图.jpg")
  82. if not os.path.exists(hist_path):
  83. logger.warning(f"默认直方图文件不存在: {hist_path}")
  84. raise HTTPException(status_code=404, detail="直方图文件不存在")
  85. return FileResponse(
  86. path=hist_path,
  87. filename=f"{land_type}_Cd含量直方图.jpg",
  88. media_type="image/jpeg"
  89. )
  90. except HTTPException:
  91. raise
  92. except Exception as e:
  93. logger.error(f"获取默认直方图失败: {str(e)}")
  94. raise HTTPException(
  95. status_code=500,
  96. detail=f"获取默认直方图失败: {str(e)}"
  97. )
  98. @router.post("/calculate",
  99. summary="重新计算土地数据",
  100. description="根据输入的土地类型和系数重新计算土地数据,支持通过area和level参数控制地图边界,支持插值控制")
  101. async def recalculate_land_data(
  102. background_tasks: BackgroundTasks,
  103. land_type: str = Form(..., description="土地类型,如:'水田'、'旱地'或'水浇地'"),
  104. param1: float = Form(711, description="土地类型系数的第一个参数"),
  105. param2: float = Form(0.524, description="土地类型系数的第二个参数"),
  106. color_map_name: str = Form("绿-黄-红-紫", description="使用的色彩方案"),
  107. output_size: int = Form(8, description="输出图片的尺寸"),
  108. area: Optional[str] = Form(None, description="可选的地区名称,如:'乐昌市',用于动态控制地图边界"),
  109. level: Optional[str] = Form(None, description="可选的行政层级,必须为: county | city | province"),
  110. enable_interpolation: Optional[bool] = Form(False, description="是否启用空间插值,默认启用"),
  111. interpolation_method: Optional[str] = Form("linear", description="插值方法: nearest | linear | cubic"),
  112. resolution_factor: Optional[float] = Form(4.0, description="分辨率因子,默认4.0,越大分辨率越高"),
  113. save_csv: Optional[bool] = Form(True, description="是否生成CSV文件,默认生成")
  114. ) -> Dict[str, Any]:
  115. """重新计算土地数据并返回结果路径,支持动态边界控制和插值控制"""
  116. try:
  117. logger.info(f"重新计算土地数据: {land_type}")
  118. if area and level:
  119. logger.info(f"使用动态边界: {area} ({level})")
  120. else:
  121. logger.info("使用默认边界")
  122. logger.info(f"插值设置: 启用={enable_interpolation}, 方法={interpolation_method}, 分辨率因子={resolution_factor}")
  123. # 获取默认目录
  124. base_dir = get_base_dir()
  125. raster_dir = os.path.join(base_dir, "..", "static", "water", "Raster")
  126. # 确保目录存在
  127. os.makedirs(raster_dir, exist_ok=True)
  128. # 构建系数参数
  129. coefficient_params = {
  130. land_type: (param1, param2)
  131. }
  132. # 调用统一的处理函数,CSV生成作为可选参数
  133. results = process_land_to_visualization(
  134. land_type=land_type,
  135. coefficient_params=coefficient_params,
  136. color_map_name=color_map_name,
  137. output_size=output_size,
  138. area=area,
  139. level=level,
  140. enable_interpolation=enable_interpolation,
  141. interpolation_method=interpolation_method,
  142. resolution_factor=resolution_factor,
  143. save_csv=save_csv # 将CSV生成选项传递给处理函数
  144. )
  145. if not results:
  146. logger.error(f"重新计算土地数据失败: {land_type}")
  147. raise HTTPException(
  148. status_code=500,
  149. detail=f"重新计算土地数据失败: {land_type}"
  150. )
  151. cleaned_csv, shapefile, tif_file, map_output, hist_output, used_coeff = results
  152. # 检查关键文件是否存在(shapefile可以为None,因为使用的是内存处理)
  153. if not tif_file or not map_output or not hist_output:
  154. logger.error(f"重新计算土地数据失败,关键文件缺失: {land_type}")
  155. logger.error(f"GeoTIFF: {tif_file}, 地图: {map_output}, 直方图: {hist_output}")
  156. raise HTTPException(
  157. status_code=500,
  158. detail=f"重新计算土地数据失败: {land_type}"
  159. )
  160. # 定义默认路径
  161. default_map_path = os.path.join(raster_dir, f"{land_type}_Cd含量地图.jpg")
  162. default_hist_path = os.path.join(raster_dir, f"{land_type}_Cd含量直方图.jpg")
  163. # 移动文件到默认目录(覆盖旧文件)
  164. shutil.move(map_output, default_map_path)
  165. shutil.move(hist_output, default_hist_path)
  166. return {
  167. "map_path": default_map_path,
  168. "histogram_path": default_hist_path,
  169. "used_coeff": used_coeff
  170. }
  171. except HTTPException:
  172. raise
  173. except Exception as e:
  174. logger.error(f"重新计算土地数据失败: {str(e)}")
  175. raise HTTPException(
  176. status_code=500,
  177. detail=f"重新计算土地数据失败: {str(e)}"
  178. )
  179. @router.get("/statistics",
  180. summary="获取土地类型统计数据",
  181. description="返回指定土地类型的Cd预测结果统计信息")
  182. async def get_land_statistics_endpoint(
  183. land_type: str = Query("水田", description="土地类型,如:'水田'、'旱地'或'水浇地'")
  184. ) -> JSONResponse:
  185. """返回土地类型Cd预测结果的统计信息"""
  186. try:
  187. logger.info(f"获取土地类型统计数据: {land_type}")
  188. # 调用服务层函数获取统计数据
  189. stats = get_land_statistics(land_type)
  190. if not stats:
  191. logger.warning(f"未找到{land_type}的土地类型统计数据")
  192. raise HTTPException(status_code=404, detail="统计数据不存在")
  193. return JSONResponse(content=stats)
  194. except HTTPException:
  195. raise
  196. except Exception as e:
  197. logger.error(f"获取土地类型统计数据失败: {str(e)}")
  198. raise HTTPException(
  199. status_code=500,
  200. detail=f"获取土地类型统计数据失败: {str(e)}"
  201. )