123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575 |
- """
- Cd预测模型API接口
- @description: 提供作物Cd和有效态Cd的预测与可视化功能
- """
- from fastapi import APIRouter, HTTPException, BackgroundTasks, UploadFile, File, Form
- from fastapi.responses import FileResponse
- from typing import Dict, Any, Optional, List
- import os
- import logging
- import io
- import pandas as pd
- from ..services.cd_prediction_service_v3 import CdPredictionServiceV3
- from ..services.cd_prediction_database_service import CdPredictionDatabaseService
- router = APIRouter()
- # 设置日志
- logger = logging.getLogger(__name__)
- # =============================================================================
- # 一键生成并获取地图接口
- # =============================================================================
- @router.post("/crop-cd/generate-and-get-map",
- summary="一键生成并获取作物Cd预测地图",
- description="根据区域名称和行政级别生成作物Cd预测地图并直接返回图片文件,优先使用数据库数据,也支持CSV文件上传")
- async def generate_and_get_crop_cd_map(
- area: str = Form(..., description="区域名称,如:乐昌市"),
- level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
- use_database: Optional[bool] = Form(True, description="是否使用数据库数据,默认为True"),
- data_file: Optional[UploadFile] = File(None, description="可选的CSV格式环境因子数据文件,仅在use_database=False时使用"),
- enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
- interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"),
- resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
- ):
- """
- 一键生成并获取作物Cd预测地图
-
- @param area: 区域名称
- @param level: 行政级别
- @param use_database: 是否使用数据库数据
- @param data_file: 可选的CSV数据文件,仅在use_database=False时使用
- @returns {FileResponse} 预测地图文件
- """
- try:
- logger.info(f"开始为{area}({level})一键生成作物Cd预测地图(数据源:{'数据库' if use_database else 'CSV文件'})")
-
- # 构建栅格配置参数
- raster_params = {}
- if enable_interpolation is not None:
- raster_params['enable_interpolation'] = enable_interpolation
- if interpolation_method is not None:
- raster_params['interpolation_method'] = interpolation_method
- if resolution_factor is not None:
- raster_params['resolution_factor'] = resolution_factor
-
- if use_database:
- # 使用数据库数据生成预测
- db_service = CdPredictionDatabaseService()
-
- result = await db_service.generate_crop_cd_prediction_from_database(
- area=area,
- level=level,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
-
- logger.info(f"使用数据库数据为{area}({level})生成作物Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
-
- else:
- # 使用上传的CSV文件生成预测
- if not data_file:
- raise HTTPException(status_code=400, detail="当use_database=False时,必须提供data_file")
-
- # 验证文件格式
- if not data_file.filename.endswith('.csv'):
- raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
-
- # 读取CSV数据
- content = await data_file.read()
- df = pd.read_csv(io.StringIO(content.decode('utf-8')))
-
- # 验证数据格式
- if df.shape[1] < 3:
- raise HTTPException(
- status_code=400,
- detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
- )
-
- # 重命名前两列为标准的经纬度列名
- df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
-
- service = CdPredictionServiceV3()
-
- # 验证数据
- validation_result = service.validate_input_data(df, area)
- if not validation_result['valid']:
- raise HTTPException(
- status_code=400,
- detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
- )
-
- # 保存临时数据文件
- temp_file_path = service.save_temp_data(df, area)
-
- # 生成预测结果
- result = await service.generate_crop_cd_prediction_for_county(
- county_name=area,
- data_file=temp_file_path,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于CSV文件的地图文件生成失败")
-
- logger.info(f"使用CSV文件为{area}({level})生成作物Cd预测地图成功")
-
- return FileResponse(
- path=result['map_path'],
- filename=f"{area}_crop_cd_prediction_map.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"为{area}({level})一键生成作物Cd预测地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"为{area}({level})一键生成作物Cd预测地图失败: {str(e)}"
- )
- @router.post("/crop-cd/generate-from-database",
- summary="基于数据库数据生成作物Cd预测地图",
- description="直接从数据库读取数据生成作物Cd预测地图")
- async def generate_crop_cd_map_from_database(
- area: str = Form(..., description="区域名称,如:乐昌市"),
- level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
- enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
- interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"),
- resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
- ):
- """
- 基于数据库数据生成作物Cd预测地图
-
- @param area: 区域名称
- @param level: 行政级别
- @returns {FileResponse} 预测地图文件
- """
- try:
- logger.info(f"开始基于数据库数据为{area}({level})生成作物Cd预测地图")
-
- # 构建栅格配置参数
- raster_params = {}
- if enable_interpolation is not None:
- raster_params['enable_interpolation'] = enable_interpolation
- if interpolation_method is not None:
- raster_params['interpolation_method'] = interpolation_method
- if resolution_factor is not None:
- raster_params['resolution_factor'] = resolution_factor
-
- # 使用数据库数据生成预测
- db_service = CdPredictionDatabaseService()
-
- result = await db_service.generate_crop_cd_prediction_from_database(
- area=area,
- level=level,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
-
- logger.info(f"基于数据库数据为{area}({level})生成作物Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
-
- return FileResponse(
- path=result['map_path'],
- filename=f"{area}_crop_cd_prediction_map_database.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"基于数据库数据为{area}({level})生成作物Cd预测地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"基于数据库数据为{area}({level})生成作物Cd预测地图失败: {str(e)}"
- )
- @router.post("/effective-cd/generate-and-get-map",
- summary="一键生成并获取有效态Cd预测地图",
- description="根据区域名称和行政级别生成有效态Cd预测地图并直接返回图片文件,优先使用数据库数据,也支持CSV文件上传")
- async def generate_and_get_effective_cd_map(
- area: str = Form(..., description="区域名称,如:乐昌市"),
- level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
- use_database: Optional[bool] = Form(True, description="是否使用数据库数据,默认为True"),
- data_file: Optional[UploadFile] = File(None, description="可选的CSV格式环境因子数据文件,仅在use_database=False时使用"),
- enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
- interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"),
- resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
- ):
- """
- 一键生成并获取有效态Cd预测地图
-
- @param area: 区域名称
- @param level: 行政级别
- @param use_database: 是否使用数据库数据
- @param data_file: 可选的CSV数据文件,仅在use_database=False时使用
- @returns {FileResponse} 预测地图文件
- """
- try:
- logger.info(f"开始为{area}({level})一键生成有效态Cd预测地图(数据源:{'数据库' if use_database else 'CSV文件'})")
-
- # 构建栅格配置参数
- raster_params = {}
- if enable_interpolation is not None:
- raster_params['enable_interpolation'] = enable_interpolation
- if interpolation_method is not None:
- raster_params['interpolation_method'] = interpolation_method
- if resolution_factor is not None:
- raster_params['resolution_factor'] = resolution_factor
-
- if use_database:
- # 使用数据库数据生成预测
- db_service = CdPredictionDatabaseService()
-
- result = await db_service.generate_effective_cd_prediction_from_database(
- area=area,
- level=level,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
-
- logger.info(f"使用数据库数据为{area}({level})生成有效态Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
-
- else:
- # 使用上传的CSV文件生成预测
- if not data_file:
- raise HTTPException(status_code=400, detail="当use_database=False时,必须提供data_file")
-
- # 验证文件格式
- if not data_file.filename.endswith('.csv'):
- raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
-
- # 读取CSV数据
- content = await data_file.read()
- df = pd.read_csv(io.StringIO(content.decode('utf-8')))
-
- # 验证数据格式
- if df.shape[1] < 3:
- raise HTTPException(
- status_code=400,
- detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
- )
-
- # 重命名前两列为标准的经纬度列名
- df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
-
- service = CdPredictionServiceV3()
-
- # 验证数据
- validation_result = service.validate_input_data(df, area)
- if not validation_result['valid']:
- raise HTTPException(
- status_code=400,
- detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
- )
-
- # 保存临时数据文件
- temp_file_path = service.save_temp_data(df, area)
-
- # 生成预测结果
- result = await service.generate_effective_cd_prediction_for_county(
- county_name=area,
- data_file=temp_file_path,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于CSV文件的地图文件生成失败")
-
- logger.info(f"使用CSV文件为{area}({level})生成有效态Cd预测地图成功")
-
- return FileResponse(
- path=result['map_path'],
- filename=f"{area}_effective_cd_prediction_map.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"为{area}({level})一键生成有效态Cd预测地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"为{area}({level})一键生成有效态Cd预测地图失败: {str(e)}"
- )
- @router.post("/effective-cd/generate-from-database",
- summary="基于数据库数据生成有效态Cd预测地图",
- description="直接从数据库读取数据生成有效态Cd预测地图")
- async def generate_effective_cd_map_from_database(
- area: str = Form(..., description="区域名称,如:乐昌市"),
- level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
- enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
- interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"),
- resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
- ):
- """
- 基于数据库数据生成有效态Cd预测地图
-
- @param area: 区域名称
- @param level: 行政级别
- @returns {FileResponse} 预测地图文件
- """
- try:
- logger.info(f"开始基于数据库数据为{area}({level})生成有效态Cd预测地图")
-
- # 构建栅格配置参数
- raster_params = {}
- if enable_interpolation is not None:
- raster_params['enable_interpolation'] = enable_interpolation
- if interpolation_method is not None:
- raster_params['interpolation_method'] = interpolation_method
- if resolution_factor is not None:
- raster_params['resolution_factor'] = resolution_factor
-
- # 使用数据库数据生成预测
- db_service = CdPredictionDatabaseService()
-
- result = await db_service.generate_effective_cd_prediction_from_database(
- area=area,
- level=level,
- raster_config_override=raster_params if raster_params else None
- )
-
- if not result['map_path'] or not os.path.exists(result['map_path']):
- raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
-
- logger.info(f"基于数据库数据为{area}({level})生成有效态Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
-
- return FileResponse(
- path=result['map_path'],
- filename=f"{area}_effective_cd_prediction_map_database.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"基于数据库数据为{area}({level})生成有效态Cd预测地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"基于数据库数据为{area}({level})生成有效态Cd预测地图失败: {str(e)}"
- )
- # =============================================================================
- # 获取最新预测结果接口(无需重新计算)
- # =============================================================================
- @router.get("/crop-cd/latest-map/{area_name}",
- summary="获取作物Cd最新地图",
- description="直接返回指定区域的最新作物Cd预测地图,无需重新计算")
- async def get_latest_crop_cd_map(area_name: str):
- """
- 获取指定区域的最新作物Cd预测地图
-
- @param area_name: 区域名称,如:乐昌市
- @returns {FileResponse} 最新的预测地图文件
- """
- try:
- logger.info(f"获取{area_name}的最新作物Cd预测地图")
-
- service = CdPredictionServiceV3()
-
- # 查找最新的地图文件
- map_pattern = f"prediction_map_crop_cd_{area_name}_*.jpg"
- map_files = []
-
- # 在输出目录中查找相关文件
- import glob
- output_dir = service.output_figures_dir
- search_pattern = os.path.join(output_dir, map_pattern)
- map_files = glob.glob(search_pattern)
-
- if not map_files:
- raise HTTPException(
- status_code=404,
- detail=f"未找到{area_name}的作物Cd预测地图,请先执行预测"
- )
-
- # 选择最新的文件(按修改时间排序)
- latest_map = max(map_files, key=os.path.getmtime)
-
- if not os.path.exists(latest_map):
- raise HTTPException(status_code=404, detail="地图文件不存在")
-
- return FileResponse(
- path=latest_map,
- filename=f"{area_name}_latest_crop_cd_map.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"获取{area_name}最新作物Cd地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取{area_name}最新作物Cd地图失败: {str(e)}"
- )
- @router.get("/effective-cd/latest-map/{area_name}",
- summary="获取有效态Cd最新地图",
- description="直接返回指定区域的最新有效态Cd预测地图,无需重新计算")
- async def get_latest_effective_cd_map(area_name: str):
- """
- 获取指定区域的最新有效态Cd预测地图
-
- @param area_name: 区域名称,如:乐昌市
- @returns {FileResponse} 最新的预测地图文件
- """
- try:
- logger.info(f"获取{area_name}的最新有效态Cd预测地图")
-
- service = CdPredictionServiceV3()
-
- # 查找最新的地图文件
- map_pattern = f"prediction_map_effective_cd_{area_name}_*.jpg"
- map_files = []
-
- # 在输出目录中查找相关文件
- import glob
- output_dir = service.output_figures_dir
- search_pattern = os.path.join(output_dir, map_pattern)
- map_files = glob.glob(search_pattern)
-
- if not map_files:
- raise HTTPException(
- status_code=404,
- detail=f"未找到{area_name}的有效态Cd预测地图,请先执行预测"
- )
-
- # 选择最新的文件(按修改时间排序)
- latest_map = max(map_files, key=os.path.getmtime)
-
- if not os.path.exists(latest_map):
- raise HTTPException(status_code=404, detail="地图文件不存在")
-
- return FileResponse(
- path=latest_map,
- filename=f"{area_name}_latest_effective_cd_map.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"获取{area_name}最新有效态Cd地图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取{area_name}最新有效态Cd地图失败: {str(e)}"
- )
- @router.get("/crop-cd/latest-histogram/{area_name}",
- summary="获取作物Cd最新直方图",
- description="直接返回指定区域的最新作物Cd预测直方图,无需重新计算")
- async def get_latest_crop_cd_histogram(area_name: str):
- """
- 获取指定区域的最新作物Cd预测直方图
-
- @param area_name: 区域名称,如:乐昌市
- @returns {FileResponse} 最新的预测直方图文件
- """
- try:
- logger.info(f"获取{area_name}的最新作物Cd预测直方图")
-
- service = CdPredictionServiceV3()
-
- # 查找最新的直方图文件
- histogram_pattern = f"prediction_histogram_crop_cd_{area_name}_*.jpg"
- histogram_files = []
-
- # 在输出目录中查找相关文件
- import glob
- output_dir = service.output_figures_dir
- search_pattern = os.path.join(output_dir, histogram_pattern)
- histogram_files = glob.glob(search_pattern)
-
- if not histogram_files:
- raise HTTPException(
- status_code=404,
- detail=f"未找到{area_name}的作物Cd预测直方图,请先执行预测"
- )
-
- # 选择最新的文件(按修改时间排序)
- latest_histogram = max(histogram_files, key=os.path.getmtime)
-
- if not os.path.exists(latest_histogram):
- raise HTTPException(status_code=404, detail="直方图文件不存在")
-
- return FileResponse(
- path=latest_histogram,
- filename=f"{area_name}_latest_crop_cd_histogram.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"获取{area_name}最新作物Cd直方图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取{area_name}最新作物Cd直方图失败: {str(e)}"
- )
- @router.get("/effective-cd/latest-histogram/{area_name}",
- summary="获取有效态Cd最新直方图",
- description="直接返回指定区域的最新有效态Cd预测直方图,无需重新计算")
- async def get_latest_effective_cd_histogram(area_name: str):
- """
- 获取指定区域的最新有效态Cd预测直方图
-
- @param area_name: 区域名称,如:乐昌市
- @returns {FileResponse} 最新的预测直方图文件
- """
- try:
- logger.info(f"获取{area_name}的最新有效态Cd预测直方图")
-
- service = CdPredictionServiceV3()
-
- # 查找最新的直方图文件
- histogram_pattern = f"prediction_histogram_effective_cd_{area_name}_*.jpg"
- histogram_files = []
-
- # 在输出目录中查找相关文件
- import glob
- output_dir = service.output_figures_dir
- search_pattern = os.path.join(output_dir, histogram_pattern)
- histogram_files = glob.glob(search_pattern)
-
- if not histogram_files:
- raise HTTPException(
- status_code=404,
- detail=f"未找到{area_name}的有效态Cd预测直方图,请先执行预测"
- )
-
- # 选择最新的文件(按修改时间排序)
- latest_histogram = max(histogram_files, key=os.path.getmtime)
-
- if not os.path.exists(latest_histogram):
- raise HTTPException(status_code=404, detail="直方图文件不存在")
-
- return FileResponse(
- path=latest_histogram,
- filename=f"{area_name}_latest_effective_cd_histogram.jpg",
- media_type="image/jpeg"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"获取{area_name}最新有效态Cd直方图失败: {str(e)}")
- raise HTTPException(
- status_code=500,
- detail=f"获取{area_name}最新有效态Cd直方图失败: {str(e)}"
- )
|