app.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. from flask import Flask, render_template, request, jsonify
  2. import psycopg2
  3. from psycopg2.extras import DictCursor
  4. import logging
  5. import ollama
  6. import json
  7. import datetime
  8. import uuid
  9. import os
  10. from vocal import voice_text
  11. from voice_translation_test import vocal_text
  12. from flask_cors import CORS
  13. from dotenv import load_dotenv
  14. from embed import embed
  15. from query import query
  16. from get_vector_db import get_vector_db
  17. import time
  18. load_dotenv()
  19. TEMP_FOLDER = os.getenv('TEMP_FOLDER', './_temp')
  20. os.makedirs(TEMP_FOLDER, exist_ok=True)
  21. app = Flask(__name__)
  22. CORS(app)
  23. # 配置日志
  24. logging.basicConfig(level=logging.INFO)
  25. logger = logging.getLogger(__name__)
  26. # 连接数据库
  27. conn = psycopg2.connect(
  28. dbname="real3d",
  29. user="postgres",
  30. password="postgis",
  31. # host="192.168.100.30",
  32. host="192.168.60.2",
  33. port="5432"
  34. )
  35. # 文件保存路径
  36. UPLOAD_FOLDER = 'data/audio'
  37. os.makedirs(UPLOAD_FOLDER, exist_ok=True)
  38. # 后台接口
  39. @app.route('/embed', methods=['POST'])
  40. def route_embed():
  41. start_time = time.time()
  42. if 'file' not in request.files:
  43. return jsonify({"error": "No file part"}), 400
  44. file = request.files['file']
  45. if file.filename == '':
  46. return jsonify({"error": "No selected file"}), 400
  47. embedded = embed(file)
  48. end_time = time.time()
  49. print("Time taken for embedding: ", end_time - start_time)
  50. if embedded:
  51. return jsonify({"message": "File embedded successfully"}), 200
  52. return jsonify({"error": "File embedded unsuccessfully"}), 400
  53. def route_query(msg):
  54. response = query(msg)
  55. if response:
  56. resObj = {}
  57. resObj["data"] = response
  58. resObj["code"] = 200
  59. resObj["type"] = "answer"
  60. return resObj
  61. return {"error": "Something went wrong"}, 400
  62. @app.route('/delete', methods=['DELETE'])
  63. def route_delete():
  64. db = get_vector_db()
  65. db.delete_collection()
  66. return jsonify({"message": "Collection deleted successfully"}), 200
  67. @app.route("/")
  68. def home():
  69. return render_template('index.html')
  70. # 后台接口
  71. @app.route("/hello")
  72. def hello():
  73. return "Hello, World!"
  74. # 文件上传
  75. @app.route('/upload', methods=['POST'])
  76. def upload_file():
  77. if 'file' not in request.files:
  78. return jsonify({"error": "No file part in the request"}), 400
  79. file = request.files['file']
  80. if file.filename == '':
  81. return jsonify({"error": "No file selected for uploading"}), 400
  82. # 生成UUID文件名
  83. file_ext = os.path.splitext(file.filename)[1]
  84. filename = f"{uuid.uuid4()}{file_ext}"
  85. # 保存文件
  86. file_path = os.path.join(UPLOAD_FOLDER, filename)
  87. file.save(file_path)
  88. msg = vocal_text(file_path)
  89. msg = msg.replace("爆破", "抱坡")
  90. return jsonify({"msg": "上传成功",
  91. "code": 200,
  92. "filename": filename,
  93. "voiceMsg": msg
  94. }), 200
  95. # 接收消息,大模型解析
  96. chat_history = "用户:你好,我是智能助手,请问有什么可以帮助您?\\n智能助手:好的,请问您有什么需求?"
  97. sys = """请扮演文本提取工具,根据输入和聊天上下文信息,基于以下因子选择、选址范围和用地类型提取这句话中的关键信息,提取到的结果请严格将json格式字符串输出并保障寄送格式正确无误,如果没有提取到相关数据则不用以json格式字符串进行输出,只输出提示:"未找到相关数据"。
  98. 选址范围 = ['抱坡区','天涯区','崖州区','海棠区','吉阳区' ],
  99. 因子选择 = ["园地", "耕地", "林地", "草地", "湿地","公共卫生用地", "老年人社会福利用地", "儿童社会福利用地", "残疾人社会福利用地", "其他社会福利用地","零售商业用地", "批发市场用地", "餐饮用地", "旅馆用地", "公用设施营业网点用地","娱乐用地", "康体用地", "一类工业用地", "二类工业用地", "广播电视设施用地","环卫用地", "消防用地", "干渠", "水工设施用地", "其他公用设施用地","公园绿地", "防护绿地", "广场用地", "军事设施用地"],
  100. landType是用地类型
  101. districtName是选址范围
  102. area是用地大小,单位统一转换为亩
  103. factors是因子选择
  104. 其他公里、千米的单位转换为米
  105. json格式数据如下:
  106. {
  107.     "districtName": "抱坡区",
  108. "landType": "居住用地",
  109.     "area": {
  110.         "min": 30,
  111.         "max": 50
  112.     },
  113.     "factors": [
  114.         {
  115.             "type": "医疗卫生设施",
  116.             "condition": "lt",
  117.             "value": "500"
  118.         },
  119.         {
  120.             "type": "永久基本农田",
  121.             "condition": "not_intersect"
  122.         },
  123.         {
  124.             "type": "火葬场",
  125.             "condition": "gt",
  126.             "value": "1000"
  127.         },
  128. {
  129.             "type": "幼儿园服务半径",
  130.             "condition": "lt",
  131.             "value": "1000"
  132.         },
  133. {
  134.             "type": "小学服务半径",
  135.             "condition": "lt",
  136.             "value": "1000"
  137.         },
  138.     ]
  139. }
  140. json中"condition"的值为"gt"、"lt"、"get"、"let"、"between","not_intersect"、"intersect"、"not_contain"、"contain"、"between"
  141. """
  142. def update_chat_history(user_message):
  143. global chat_history # 使用全局变量以便更新
  144. prompt = chat_history + "\\n用户:" + user_message
  145. # 生成回复,并加入聊天上下文
  146. res = ollama.generate(
  147. model="qwen2:7b",
  148. stream=False,
  149. system=sys,
  150. prompt=prompt,
  151. options={"temperature": 0, "num_ctx": 32000, },
  152. keep_alive=-1
  153. )
  154. # 获取机器人回复
  155. bot_message = res["response"]
  156. # 更新聊天历史
  157. chat_history += "\\n智能助手:" + bot_message
  158. # 返回机器人的回复
  159. return bot_message
  160. @app.route('/closeMsg', methods=['DELETE'])
  161. def delMsg():
  162. chat_history = ""
  163. return jsonify({"msg": "清除成功",
  164. "code": 200,
  165. "chat_history": chat_history
  166. })
  167. @app.route('/msg', methods=['POST'])
  168. def inputMsg():
  169. # 从请求中获取JSON数据
  170. data = request.get_json()
  171. # 检查是否接收到数据
  172. if not data:
  173. return jsonify({"error": "No data received"}), 400
  174. # 打印接收到的消息
  175. print(data['msg'])
  176. msg = data['msg']
  177. type = data['type']
  178. if type == 'selectLand':
  179. # 调用大模型解析
  180. # 这里调用大模型,并返回解析结果
  181. # 示例:用户输入一条消息
  182. # msg= "我计划在抱坡区选取适宜地块作为工业用地,要求其在城市开发边界内,离小学大于1000m,坡度小于25度,用地面积在80-100亩之间。"
  183. res = update_chat_history(msg)
  184. print(res) # 打印生成的回复
  185. addtress = ['抱坡区', '天涯区', '崖州区', '海棠区', '吉阳区']
  186. land = ['园地', '耕地', '林地', '草地', '湿地', '公共卫生用地', '老年人社会福利用地', '儿童社会福利用地', '残疾人社会福利用地', '其他社会福利用地', '零售商业用地', '居住用地', '批发市场用地', '餐饮用地', '旅馆用地', '公用设施营业网点用地', '娱乐用地', '康体用地', '一类工业用地', '二类工业用地', '广播电视设施用地', '环卫用地', '消防用地', '干渠', '水工设施用地', '其他公用设施用地', '公园绿地', '防护绿地', '广场用地', '军事设施用地', '使领馆用地', '宗教用地', '文物古迹用地', '监教场所用地', '殡葬用地', '其他特殊用地', '河流水面', '湖泊水面', '水库水面', '坑塘水面', '沟渠', '冰川及常年积雪', '渔业基础设施用海', '增养殖用海', '捕捞海域', '工业用海', '盐田用海', '固体矿产用海', '油气用海', '可再生能源用海', '海底电缆管道用海', '港口用海', '农业设施建设用地', '耕地', '园地', '林地', '工矿用地', '畜禽养殖设施建设用地', '水产养殖设施建设用地', '城镇住宅用地', '草地', '湿地', '留白用地', '陆地水域', '游憩用海', '特殊用海', '特殊用地', '其他海域', '绿地与开敞空间用地', '水田', '水浇地', '旱地', '果园', '茶园', '橡胶园', '其他园地', '乔木林地', '竹林地', '城镇社区服务设施用地', '农村宅基地', '农村社区服务设施用地', '机关团体用地', '科研用地', '文化用地', '教育用地', '体育用地', '医疗卫生用地', '社会福利用地', '商业用地', '商务金融用地',
  187. '二类农村宅基地', '图书与展览用地', '文化活动用地', '高等教育用地', '中等职业教育用地', '体育训练用地', '其他交通设施用地', '供水用地', '排水用地', '供电用地', '供燃气用地', '供热用地', '通信用地', '邮政用地', '医院用地', '基层医疗卫生设施用地', '田间道', '盐碱地', '沙地', '裸土地', '裸岩石砾地', '村道用地', '村庄内部道路用地', '渔业用海', '工矿通信用海', '其他土地', '公共管理与公共服务用地', '仓储用地', '交通运输用地', '公用设施用地', '交通运输用海', '航运用海', '路桥隧道用海', '风景旅游用海', '文体休闲娱乐用海', '军事用海', '其他特殊用海', '空闲地', '田坎', '港口码头用地', '管道运输用地', '城市轨道交通用地', '城镇道路用地', '交通场站用地', '一类城镇住宅用地', '二类城镇住宅用地', '三类城镇住宅用地', '一类农村宅基地', '商业服务业用地', '三类工业用地', '一类物流仓储用地', '二类物流仓储用地', '三类物流仓储用地', '盐田', '对外交通场站用地', '公共交通场站用地', '社会停车场用地', '中小学用地', '幼儿园用地', '其他教育用地', '体育场馆用地', '灌木林地', '其他林地', '天然牧草地', '人工牧草地', '其他草地', '森林沼泽', '灌丛沼泽', '沼泽草地', '其他沼泽地', '沿海滩涂', '内陆滩涂', '红树林地', '乡村道路用地', '种植设施建设用地', '娱乐康体用地', '其他商业服务业用地', '工业用地', '采矿用地', '物流仓储用地', '储备库用地', '铁路用地', '公路用地', '机场用地']
  188. json_res = res
  189. if json_res != "未找到相关数据":
  190. try:
  191. json_res = json.loads(json_res)
  192. districtName = json_res["districtName"]
  193. landType = json_res["landType"]
  194. # if landType != "未找到相关数据" and landType != "" and districtName != "未找到相关数据"and districtName != "":
  195. if landType in land and districtName in addtress:
  196. json_res = jsonResToDict(json_res)
  197. else:
  198. json_res = "未找到相关数据"
  199. json_res = jsonResToDict_wrong(json_res)
  200. except:
  201. json_res = "未找到相关数据"
  202. json_res = jsonResToDict_wrong(json_res)
  203. else:
  204. json_res = "未找到相关数据"
  205. json_res = jsonResToDict_wrong(json_res)
  206. elif type == 'answer':
  207. json_res = route_query(msg)
  208. # 返回响应
  209. return jsonify(json_res)
  210. # 将大模型解析的结果转换为选址需要的数据格式
  211. def jsonResToDict(json_res):
  212. # 1.查询选址范围信息
  213. districtName = json_res["districtName"]
  214. ewkt = getAiDistrict(districtName)
  215. # 2.保存选址范围信息
  216. geomId = saveGeom(ewkt)
  217. # 3.获取用地类型信息
  218. landType = json_res["landType"]
  219. landType = getLandType(landType, "YDYHFLDM")
  220. # 4.获取模板信息
  221. factorTemplates = getTemplateByCode(landType)
  222. # TODO 以哪个因子列表为准,模版和因子个数怎么匹配
  223. now = datetime.datetime.now()
  224. formatted_time = now.strftime("%Y%m%d%H%M%S")
  225. res = {
  226. "xzmj": 1500,
  227. "xmmc": "规划选址项目_"+formatted_time,
  228. "jsdw": "建设单位",
  229. "ydxz_bsm": landType,
  230. "ydmjbegin": json_res["area"]["min"],
  231. "ydmjend": json_res["area"]["max"],
  232. "geomId": geomId,
  233. "yxyz": [],
  234. # TODO: 循环遍历
  235. # "yxyz": [
  236. # {
  237. # "id": "259e5bbaab434dbfb9c679bd44d4bfa4",
  238. # "name": "幼儿园服务半径",
  239. # "bsm": "TB_YEY",
  240. # "conditionInfo": {
  241. # "spatial_type": "distance",
  242. # "default": "lt",
  243. # "hasValue": true,
  244. # "defaultValue": "300",
  245. # "unit": "米",
  246. # "clip": false
  247. # }
  248. # }
  249. # ],
  250. # "useMultiple": json_res["useMultiple"],
  251. "useLandType": True,
  252. # "multipleDistance": json_res["multipleDistance"]
  253. }
  254. # 循环遍历输入因子
  255. factors = json_res["factors"]
  256. input_factors = {}
  257. for factor in factors:
  258. factorInfo = getFactorByName(factor["type"])
  259. if factorInfo == None:
  260. continue
  261. factorId = factorInfo["id"]
  262. factorBsm = factorInfo["bsm"]
  263. conditionInfo = factorInfo["condition_info"]
  264. conditionObj = json.loads(conditionInfo)
  265. defaultValue = '0'
  266. if "value" in factor:
  267. defaultValue = str(factor["value"])
  268. # if defaultValue == '':
  269. # defaultValue = '0'
  270. factor_info = {
  271. "id": factorId,
  272. "name": factor["type"],
  273. "bsm": factorBsm,
  274. "conditionInfo": {
  275. "spatial_type": conditionObj["spatial_type"],
  276. "default": factor["condition"],
  277. "hasValue": conditionObj["hasValue"],
  278. "defaultValue": defaultValue,
  279. "unit": conditionObj["unit"],
  280. "clip": conditionObj["clip"]
  281. }
  282. }
  283. input_factors[factor_info["id"]] = factor_info
  284. # 循环遍历模板
  285. for factorTemplate in factorTemplates:
  286. factorId = factorTemplate["id"]
  287. if factorId in input_factors:
  288. res["yxyz"].append(input_factors[factorId])
  289. else:
  290. factorTemplate["conditionInfo"] = json.loads(
  291. factorTemplate["conditionInfo"])
  292. res["yxyz"].append(factorTemplate)
  293. resObj = {}
  294. resObj["data"] = res
  295. resObj["code"] = 200
  296. resObj["type"] = "selectLand"
  297. return resObj
  298. # 返回错误信息
  299. def jsonResToDict_wrong(json_res):
  300. resObj = {}
  301. resObj["data"] = json_res
  302. resObj["code"] = 500
  303. resObj["type"] = "selectLand"
  304. return resObj
  305. # 获取因子信息
  306. def getFactorByName(name):
  307. with conn.cursor(cursor_factory=DictCursor) as cur:
  308. sql = "SELECT * FROM base.t_fzss_fzxz_factor WHERE name = %s"
  309. complete_sql = cur.mogrify(sql, (name,)).decode('utf-8')
  310. logger.info(f"Executing SQL: {complete_sql}")
  311. cur.execute(sql, (name,))
  312. res = cur.fetchone()
  313. return res
  314. # 获取内置模板信息
  315. def getTemplateByCode(code):
  316. with conn.cursor(cursor_factory=DictCursor) as cur:
  317. sql = 'SELECT factor_id as id,factor_name as name,factor_bsm as bsm,condition_info as "conditionInfo" FROM base.t_fzss_fzxz_factor_temp WHERE land_type_code = %s'
  318. complete_sql = cur.mogrify(sql, (code,)).decode('utf-8')
  319. logger.info(f"Executing SQL: {complete_sql}")
  320. cur.execute(sql, (code,))
  321. res = cur.fetchall()
  322. # 将查询结果转换为字典列表
  323. result_list = [dict(row) for row in res]
  324. return result_list
  325. # 获取选址范围信息
  326. def getAiDistrict(name):
  327. with conn.cursor(cursor_factory=DictCursor) as cur:
  328. sql = "SELECT public.st_asewkt(geom) as geom FROM base.t_fzss_fzxz_ai_district WHERE name = %s"
  329. complete_sql = cur.mogrify(sql, (name,)).decode('utf-8')
  330. logger.info(f"Executing SQL: {complete_sql}")
  331. cur.execute(sql, (name,))
  332. res = cur.fetchone()
  333. return res["geom"]
  334. # 保存选址范围信息
  335. def saveGeom(ewkt):
  336. new_uuid = str(uuid.uuid4()) # 生成一个新的 UUID
  337. from_type = 3
  338. with conn.cursor() as cur:
  339. sql = "INSERT INTO base.t_fzss_zhxz_file(id,geom,from_type,create_time,area) VALUES (%s,public.st_geomfromewkt(%s),%s,now(),public.st_area(public.st_geomfromewkt(%s)::public.geography))"
  340. complete_sql = cur.mogrify(
  341. sql, (new_uuid, ewkt, from_type, ewkt)).decode('utf-8')
  342. logger.info(f"Executing SQL: {complete_sql}")
  343. cur.execute(sql, (new_uuid, ewkt, from_type, ewkt))
  344. conn.commit()
  345. return new_uuid
  346. # 获取用地类型信息
  347. def getLandType(landName, fzbs):
  348. with conn.cursor(cursor_factory=DictCursor) as cur:
  349. sql = "SELECT dm,mc,fzbs FROM base.t_fzss_fzxz_dict WHERE mc = %s and fzbs=%s"
  350. complete_sql = cur.mogrify(sql, (landName, fzbs)).decode('utf-8')
  351. logger.info(f"Executing SQL: {complete_sql}")
  352. cur.execute(sql, (landName, fzbs))
  353. res = cur.fetchone()
  354. return res["dm"]
  355. # getTemplateByCode("08")
  356. # getAiDistrict("抱坡区")
  357. # ewkt="SRID=4326;POLYGON ((109.568515723151 18.2729002407864, 109.564270326708 18.2607742953866, 109.580087492139 18.2571512198688, 109.588461804591 18.2570597503377, 109.58884305979 18.2645363088176, 109.582107142538 18.2732736518031, 109.568515723151 18.2729002407864))"
  358. # saveGeom(ewkt)
  359. # getFactorByName("幼儿园服务半径")
  360. # msg=voice_text('data/audio/1364627f-5a9b-42d7-b7f6-b99c094606cd.mp3')
  361. # msg=vocal_text('data/audio/1364627f-5a9b-42d7-b7f6-b99c094606cd.mp3')
  362. # print(msg)
  363. if __name__ == '__main__':
  364. # app.run()
  365. app.run(
  366. host='0.0.0.0',
  367. port=4000
  368. )