123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- """
- 农业投入Cd通量计算API接口
- @description: 提供各个地区的农业投入输入Cd通量预测计算功能
- """
- from fastapi import APIRouter, HTTPException, Query, Body
- from fastapi.responses import FileResponse
- from pydantic import BaseModel, Field
- from typing import Dict, Any, Optional
- import logging
- import os
- from ..services.agricultural_input_service import AgriculturalInputService
- router = APIRouter()
- # =============================================================================
- # 数据模型定义
- # =============================================================================
- class VisualizationResponse(BaseModel):
- """
- 可视化结果响应模型
-
- @description: 绘图接口的响应格式
- """
- success: bool = Field(..., description="是否成功")
- message: str = Field(..., description="响应消息")
- data: Optional[Dict[str, Any]] = Field(None, description="可视化结果数据")
- files: Optional[Dict[str, str]] = Field(None, description="生成的文件路径")
- class CdFluxCalculationRequest(BaseModel):
- """
- 农业投入Cd通量计算请求模型
-
- @description: 用户提供的计算参数
- """
- # 镉含量参数 (mg/kg)
- f3_nitrogen_cd_content: float = Field(..., description="氮肥镉含量平均值 (mg/kg)", ge=0)
- f4_phosphorus_cd_content: float = Field(..., description="磷肥镉含量平均值 (mg/kg)", ge=0)
- f5_potassium_cd_content: float = Field(..., description="钾肥镉含量平均值 (mg/kg)", ge=0)
- f6_compound_cd_content: float = Field(..., description="复合肥镉含量平均值 (mg/kg)", ge=0)
- f7_organic_cd_content: float = Field(..., description="有机肥镉含量平均值 (mg/kg)", ge=0)
- f8_pesticide_cd_content: float = Field(..., description="农药镉含量 (mg/kg)", ge=0)
- f9_farmyard_cd_content: float = Field(..., description="农家肥镉含量 (mg/kg)", ge=0)
- f10_film_cd_content: float = Field(..., description="农膜镉含量 (mg/kg)", ge=0)
-
- # 使用量参数 (t/ha/a)
- nf_nitrogen_usage: float = Field(..., description="氮肥单位面积使用量 (t/ha/a)", ge=0)
- pf_phosphorus_usage: float = Field(..., description="磷肥单位面积使用量 (t/ha/a)", ge=0)
- kf_potassium_usage: float = Field(..., description="钾肥单位面积使用量 (t/ha/a)", ge=0)
- cf_compound_usage: float = Field(..., description="复合肥单位面积使用量 (t/ha/a)", ge=0)
- of_organic_usage: float = Field(..., description="有机肥单位面积使用量 (t/ha/a)", ge=0)
- p_pesticide_usage: float = Field(..., description="农药单位面积使用量 (t/ha/a)", ge=0)
- ff_farmyard_usage: float = Field(..., description="农家肥单位面积使用量 (t/ha/a)", ge=0)
- af_film_usage: float = Field(..., description="农膜(存留)单位面积使用量 (t/ha/a)", ge=0)
-
- # 可选的标识信息
- description: Optional[str] = Field(None, description="计算描述信息")
- model_config = {
- "json_schema_extra": {
- "example": {
- "f3_nitrogen_cd_content": 0.12,
- "f4_phosphorus_cd_content": 0.85,
- "f5_potassium_cd_content": 0.05,
- "f6_compound_cd_content": 0.45,
- "f7_organic_cd_content": 0.22,
- "f8_pesticide_cd_content": 0.08,
- "f9_farmyard_cd_content": 0.15,
- "f10_film_cd_content": 0.03,
- "nf_nitrogen_usage": 0.25,
- "pf_phosphorus_usage": 0.15,
- "kf_potassium_usage": 0.12,
- "cf_compound_usage": 0.30,
- "of_organic_usage": 2.50,
- "p_pesticide_usage": 0.02,
- "ff_farmyard_usage": 1.80,
- "af_film_usage": 0.05,
- "description": "测试地区农业投入Cd通量计算"
- }
- }
- }
- # 设置日志
- logger = logging.getLogger(__name__)
- # =============================================================================
- # 农业投入Cd通量计算接口
- # =============================================================================
- @router.get("/calculate-by-area",
- summary="根据地区计算农业投入Cd通量",
- description="根据指定地区从Parameters表中获取数据并计算农业投入输入Cd通量")
- async def calculate_cd_flux_by_area(
- area: str = Query(..., description="地区名称,如:韶关")
- ) -> Dict[str, Any]:
- """
- 根据地区计算农业投入输入Cd通量
-
- @param area: 地区名称
- @returns: 计算结果包括总通量和各项明细
-
- 计算公式:农业投入输入Cd(g/ha/a) = F3*NF + F4*PF + F5*KF + F6*CF + F7*OF + F8*P + F9*FF + F10*AF
- """
- try:
- service = AgriculturalInputService()
- result = service.calculate_cd_flux_by_area(area)
-
- if not result["success"]:
- raise HTTPException(
- status_code=404,
- detail=result["message"]
- )
-
- return result
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"计算地区 '{area}' 的Cd通量失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"计算失败: {str(e)}"
- )
- @router.get("/calculate-all-areas",
- summary="计算所有地区的农业投入Cd通量",
- description="计算数据库中所有地区的农业投入输入Cd通量,并提供统计汇总")
- async def calculate_all_areas_cd_flux() -> Dict[str, Any]:
- """
- 计算所有地区的农业投入输入Cd通量
-
- @returns: 所有地区的计算结果,按通量从高到低排序,包含统计汇总
- """
- try:
- service = AgriculturalInputService()
- result = service.calculate_all_areas_cd_flux()
-
- if not result["success"]:
- raise HTTPException(
- status_code=500,
- detail=result["message"]
- )
-
- return result
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"计算所有地区Cd通量失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"计算失败: {str(e)}"
- )
- @router.get("/available-areas",
- summary="获取可用地区列表",
- description="获取数据库中所有可用于计算的地区列表")
- async def get_available_areas() -> Dict[str, Any]:
- """
- 获取数据库中所有可用的地区列表
-
- @returns: 可用地区列表
- """
- try:
- service = AgriculturalInputService()
- result = service.get_available_areas()
-
- if not result["success"]:
- raise HTTPException(
- status_code=500,
- detail=result["message"]
- )
-
- return result
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"获取可用地区列表失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取失败: {str(e)}"
- )
- @router.post("/calculate-with-custom-data",
- summary="使用自定义数据计算农业投入Cd通量",
- description="接收用户提供的参数数据,直接进行农业投入输入Cd通量计算")
- async def calculate_cd_flux_with_custom_data(
- request: CdFluxCalculationRequest = Body(..., description="计算所需的参数数据")
- ) -> Dict[str, Any]:
- """
- 使用用户提供的自定义数据计算农业投入输入Cd通量
-
- @param request: 包含所有计算参数的请求体
- @returns: 计算结果包括总通量和各项明细
-
- 计算公式:农业投入输入Cd(g/ha/a) = F3*NF + F4*PF + F5*KF + F6*CF + F7*OF + F8*P + F9*FF + F10*AF
- """
- try:
- service = AgriculturalInputService()
- result = service.calculate_cd_flux_with_custom_data(request)
-
- if not result["success"]:
- raise HTTPException(
- status_code=400,
- detail=result["message"]
- )
-
- return result
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"使用自定义数据计算Cd通量失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"计算失败: {str(e)}"
- )
- @router.get("/calculation-formula",
- summary="获取计算公式说明",
- description="获取农业投入Cd通量的计算公式和参数说明")
- async def get_calculation_formula() -> Dict[str, Any]:
- """
- 获取农业投入Cd通量的计算公式和参数说明
-
- @returns: 公式和参数详细说明
- """
- try:
- formula_info = {
- "success": True,
- "message": "农业投入Cd通量计算公式",
- "data": {
- "formula": "农业投入输入Cd(g/ha/a) = F3*NF + F4*PF + F5*KF + F6*CF + F7*OF + F8*P + F9*FF + F10*AF",
- "unit": "g/ha/a",
- "parameters": {
- "F3": "氮肥镉含量平均值(mg/kg)",
- "F4": "磷肥镉含量平均值(mg/kg)",
- "F5": "钾肥镉含量平均值(mg/kg)",
- "F6": "复合肥镉含量平均值(mg/kg)",
- "F7": "有机肥镉含量平均值(mg/kg)",
- "F8": "农药镉含量(mg/kg)",
- "F9": "农家肥镉含量(mg/kg)",
- "F10": "农膜镉含量(mg/kg)",
- "NF": "氮肥单位面积使用量(t/ha/a)",
- "PF": "磷肥单位面积使用量(t/ha/a)",
- "KF": "钾肥单位面积使用量(t/ha/a)",
- "CF": "复合肥单位面积使用量(t/ha/a)",
- "OF": "有机肥单位面积使用量(t/ha/a)",
- "P": "农药单位面积使用量(t/ha/a)",
- "FF": "农家肥单位面积使用量(t/ha/a)",
- "AF": "农膜(存留)单位面积使用量(t/ha/a)"
- },
- "components": {
- "nitrogen_fertilizer": "氮肥贡献量 = F3 × NF",
- "phosphorus_fertilizer": "磷肥贡献量 = F4 × PF",
- "potassium_fertilizer": "钾肥贡献量 = F5 × KF",
- "compound_fertilizer": "复合肥贡献量 = F6 × CF",
- "organic_fertilizer": "有机肥贡献量 = F7 × OF",
- "pesticide": "农药贡献量 = F8 × P",
- "farmyard_manure": "农家肥贡献量 = F9 × FF",
- "agricultural_film": "农膜贡献量 = F10 × AF"
- }
- }
- }
-
- return formula_info
-
- except Exception as e:
- logger.error(f"获取计算公式失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取失败: {str(e)}"
- )
- # =============================================================================
- # 农业投入Cd通量可视化接口
- # =============================================================================
- @router.get("/visualize",
- summary="生成农业投入Cd通量可视化图表",
- description="计算农业投入Cd通量并生成栅格地图,参数固定使用韶关数据,area用于地图边界")
- async def visualize_agricultural_input(
- area: str = Query(..., description="地区名称,如:乐昌市(用于地图边界,参数固定使用韶关)"),
- level: str = Query(..., description="行政层级,必须为: county | city | province"),
- colormap: str = Query("green_yellow_red_purple", description="色彩方案"),
- resolution_factor: float = Query(4.0, description="分辨率因子(默认4.0,更快)"),
- enable_interpolation: bool = Query(False, description="是否启用空间插值(默认关闭以提升性能)"),
- cleanup_intermediate: bool = Query(True, description="是否清理中间文件(默认是)")
- ):
- """
- 生成农业投入Cd通量可视化图表
-
- @param area: 地区名称(用于地图边界,参数数据固定使用韶关)
- @param level: 行政层级
- @returns: 栅格地图文件
-
- 功能包括:
- 1. 计算农业投入Cd通量(参数固定使用韶关)
- 2. 生成指定地区边界的栅格地图
- 3. 直接返回图片文件
- """
- try:
- service = AgriculturalInputService()
- # 行政层级校验(不允许模糊)
- if level not in ("county", "city", "province"):
- raise HTTPException(status_code=400, detail="参数 level 必须为 'county' | 'city' | 'province'")
-
- # 计算农业投入Cd通量(使用韶关参数,area仅用于边界)
- calc_result = service.calculate_cd_flux_for_visualization(area)
-
- if not calc_result["success"]:
- raise HTTPException(
- status_code=404,
- detail=calc_result["message"]
- )
-
- # 获取包含坐标的结果数据
- results_with_coords = service.get_coordinates_for_results(calc_result["data"], area)
-
- if not results_with_coords:
- raise HTTPException(
- status_code=404,
- detail=f"未找到地区 '{area}' 的坐标数据,无法生成可视化"
- )
-
- # 创建可视化
- visualization_files = service.create_agricultural_input_visualization(
- area=area,
- level=level,
- calculation_type="agricultural_input",
- results_with_coords=results_with_coords,
- colormap=colormap,
- resolution_factor=resolution_factor,
- enable_interpolation=enable_interpolation,
- cleanup_intermediate=cleanup_intermediate
- )
-
- # 检查地图文件是否生成成功
- map_file = visualization_files.get("map")
- if not map_file or not os.path.exists(map_file):
- raise HTTPException(status_code=500, detail="地图文件生成失败")
-
- return FileResponse(
- path=map_file,
- filename=f"{area}_agricultural_input_cd_flux_map.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"生成地区 '{area}' 的农业投入Cd通量可视化失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"可视化生成失败: {str(e)}"
- )
- @router.get("/export-data",
- summary="导出农业投入Cd通量计算数据",
- description="导出指定地区的农业投入Cd通量计算结果为CSV文件")
- async def export_agricultural_input_data(
- area: str = Query(..., description="地区名称,如:韶关")
- ) -> Dict[str, Any]:
- """
- 导出农业投入Cd通量计算数据
-
- @param area: 地区名称
- @returns: 导出结果和文件路径
- """
- try:
- service = AgriculturalInputService()
-
- # 计算农业投入Cd通量(使用韶关参数,area仅用于边界)
- calc_result = service.calculate_cd_flux_for_visualization(area)
-
- if not calc_result["success"]:
- raise HTTPException(
- status_code=404,
- detail=calc_result["message"]
- )
-
- # 导出数据
- csv_path = service.export_results_to_csv(calc_result["data"])
-
- return {
- "success": True,
- "message": f"地区 '{area}' 的农业投入Cd通量数据导出成功",
- "data": {
- "area": area,
- "calculation_type": "agricultural_input",
- "exported_file": csv_path,
- "total_cd_flux": calc_result["data"]["total_cd_flux"],
- "unit": calc_result["data"]["unit"]
- }
- }
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"导出地区 '{area}' 的农业投入Cd通量数据失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"数据导出失败: {str(e)}"
- )
|