app.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. app = Flask(__name__)
  11. # 配置日志
  12. logging.basicConfig(level=logging.INFO)
  13. logger = logging.getLogger(__name__)
  14. # 连接数据库
  15. conn = psycopg2.connect(
  16. dbname="real3d",
  17. user="postgres",
  18. password="postgis",
  19. host="192.168.100.30",
  20. # host="192.168.60.2",
  21. port="5432"
  22. )
  23. # 文件保存路径
  24. UPLOAD_FOLDER = 'data/audio'
  25. os.makedirs(UPLOAD_FOLDER, exist_ok=True)
  26. # 后台接口
  27. @app.route("/")
  28. def home():
  29. return render_template('index.html')
  30. # 后台接口
  31. @app.route("/hello")
  32. def hello():
  33. return "Hello, World!"
  34. # 文件上传
  35. @app.route('/upload', methods=['POST'])
  36. def upload_file():
  37. if 'file' not in request.files:
  38. return jsonify({"error": "No file part in the request"}), 400
  39. file = request.files['file']
  40. if file.filename == '':
  41. return jsonify({"error": "No file selected for uploading"}), 400
  42. # 生成UUID文件名
  43. file_ext = os.path.splitext(file.filename)[1]
  44. filename = f"{uuid.uuid4()}{file_ext}"
  45. # 保存文件
  46. file_path = os.path.join(UPLOAD_FOLDER, filename)
  47. file.save(file_path)
  48. return jsonify({"msg": "上传成功", "code": 200, "filename": filename}), 200
  49. # 接收消息,大模型解析
  50. @app.route('/msg', methods=['POST'])
  51. def inputMsg():
  52. # 从请求中获取JSON数据
  53. data = request.get_json()
  54. # 检查是否接收到数据
  55. if not data:
  56. return jsonify({"error": "No data received"}), 400
  57. # 打印接收到的消息
  58. print(data['msg'])
  59. msg = data['msg']
  60. # 调用大模型解析
  61. # 这里调用大模型,并返回解析结果
  62. # 生成提示信息
  63. # 定义输入信息变量
  64. # 生成提示信息
  65. # 生成提示信息
  66. prompt = f"""请扮演文本提取工具,把这句话:"{msg}",基于以下因子选择、选址范围和用地类型提取其对应的相关数据,提取结果请严格将json格式字符串输出并保障寄送格式正确无误,
  67. 选址范围 = ['抱坡区','天涯区','崖州区','海棠区','吉阳区',"青浦区","静安区","浦东新区","松江区','海淀区', '昌平区', '朝阳区' ],
  68. 因子选择 = [
  69. "高程",
  70. "坡度",
  71. "永久基本农田",
  72. "城镇开发边界内",
  73. "生态保护红线",
  74. "文化保护区",
  75. "自然保护地",
  76. "风景名胜区",
  77. "国有使用权",
  78. "河道管理线",
  79. "水库",
  80. "公益林",
  81. "火葬场",
  82. "垃圾处理场",
  83. "污水处理场",
  84. "高压线",
  85. "变电站",
  86. "古树",
  87. "城市道路",
  88. "主要出入口",
  89. "文化活动设施",
  90. "体育运动场所",
  91. "排水",
  92. "供水",
  93. "燃气",
  94. "电力",
  95. "电信",
  96. "十五分钟社区生活圈邻里中心",
  97. "社区服务设施",
  98. "零售商业场所",
  99. "医疗卫生设施",
  100. "幼儿园服务半径",
  101. "小学服务半径",
  102. "为老服务设施"
  103. ]
  104. 用地类型=['园地', '耕地', '林地', '草地', '湿地', '公共卫生用地', '老年人社会福利用地', '儿童社会福利用地', '残疾人社会福利用地', '其他社会福利用地', '零售商业用地', '批发市场用地', '餐饮用地', '旅馆用地', '公用设施营业网点用地', '娱乐用地', '康体用地', '一类工业用地', '二类工业用地', '广播电视设施用地', '环卫用地', '消防用地', '干渠', '水工设施用地', '其他公用设施用地', '公园绿地', '防护绿地', '广场用地', '军事设施用地', '使领馆用地', '宗教用地', '文物古迹用地', '监教场所用地', '殡葬用地', '其他特殊用地', '河流水面', '湖泊水面', '水库水面', '坑塘水面', '沟渠', '冰川及常年积雪', '渔业基础设施用海', '增养殖用海', '捕捞海域', '工业用海', '盐田用海', '固体矿产用海', '油气用海', '可再生能源用海', '海底电缆管道用海', '港口用海', '农业设施建设用地', '耕地', '园地', '林地', '工矿用地', '畜禽养殖设施建设用地', '水产养殖设施建设用地', '城镇住宅用地', '草地', '湿地', '留白用地', '陆地水域', '游憩用海', '特殊用海', '特殊用地', '其他海域', '居住用地', '绿地与开敞空间用地', '水田', '水浇地', '旱地', '果园', '茶园', '橡胶园', '其他园地', '乔木林地', '竹林地', '城镇社区服务设施用地', '农村宅基地', '农村社区服务设施用地', '机关团体用地', '科研用地', '文化用地', '教育用地', '体育用地', '医疗卫生用地', '社会福利用地', '商业用地', '商务金融用地', '二类农村宅基地', '图书与展览用地', '文化活动用地', '高等教育用地', '中等职业教育用地', '体育训练用地', '其他交通设施用地', '供水用地', '排水用地', '供电用地', '供燃气用地', '供热用地', '通信用地', '邮政用地', '医院用地', '基层医疗卫生设施用地', '田间道', '盐碱地', '沙地', '裸土地', '裸岩石砾地', '村道用地', '村庄内部道路用地', '渔业用海', '工矿通信用海', '其他土地', '公共管理与公共服务用地', '仓储用地', '交通运输用地', '公用设施用地', '交通运输用海', '航运用海', '路桥隧道用海', '风景旅游用海', '文体休闲娱乐用海', '军事用海', '其他特殊用海', '空闲地', '田坎', '港口码头用地', '管道运输用地', '城市轨道交通用地', '城镇道路用地', '交通场站用地', '一类城镇住宅用地', '二类城镇住宅用地', '三类城镇住宅用地', '一类农村宅基地', '商业服务业用地', '三类工业用地', '一类物流仓储用地', '二类物流仓储用地', '三类物流仓储用地', '盐田', '对外交通场站用地', '公共交通场站用地', '社会停车场用地', '中小学用地', '幼儿园用地', '其他教育用地', '体育场馆用地', '灌木林地', '其他林地', '天然牧草地', '人工牧草地', '其他草地', '森林沼泽', '灌丛沼泽', '沼泽草地', '其他沼泽地', '沿海滩涂', '内陆滩涂', '红树林地', '乡村道路用地', '种植设施建设用地', '娱乐康体用地', '其他商业服务业用地', '工业用地', '采矿用地', '物流仓储用地', '储备库用地', '铁路用地', '公路用地', '机场用地']
  105. landType是用地类型
  106. districtName是选址范围
  107. area是用地大小,单位统一转换为亩
  108. factors.type是因子选择
  109. 其他公里、千米的单位转换为米
  110. 输出的json格式数据如下:
  111. {{
  112. "districtName": "抱坡区",
  113. "landType": "耕地",
  114. "area": {{
  115. "min": 30,
  116. "max": 50
  117. }},
  118. "factors": [
  119. {{
  120. "type": "水库",
  121. "condition": "大于",
  122. "value": "100"
  123. }},
  124. {{
  125. "type": "永久基本农田",
  126. "condition": "不相交"
  127. }},
  128. {{
  129. "type": "城镇开发边界内",
  130. "condition": "包含"
  131. }},
  132. {{
  133. "type": "医疗卫生设施",
  134. "condition": "小于",
  135. "value": "500"
  136. }},
  137. ]
  138. }}
  139. 把json中"condition"的值改为"gt"、"lt"、"get"、"let"、"between","not_intersect"、"intersect"、"not_contain"、"contain"、"between"
  140. """
  141. try:
  142. res = ollama.generate(
  143. model="qwen2:7b",
  144. stream=False,
  145. prompt=prompt,
  146. options={"temperature": 0},
  147. format="json",
  148. keep_alive=-1
  149. )
  150. print(res["response"])
  151. except Exception as e:
  152. print(f"生成过程中出现错误: {e}")
  153. json_res = res["response"]
  154. json_res = json.loads(json_res)
  155. # 组织成选址需要的数据格式
  156. json_res = jsonResToDict(json_res)
  157. # 返回响应
  158. return jsonify(json_res)
  159. # 将大模型解析的结果转换为选址需要的数据格式
  160. def jsonResToDict(json_res):
  161. # 1.查询选址范围信息
  162. districtName = json_res["districtName"]
  163. ewkt = getAiDistrict(districtName)
  164. # 2.保存选址范围信息
  165. geomId = saveGeom(ewkt)
  166. # 3.获取用地类型信息
  167. landType = json_res["landType"]
  168. landType = getLandType(landType, "YDYHFLDM")
  169. # 4.获取模板信息
  170. factorTemplates = getTemplateByCode(landType)
  171. # TODO 以哪个因子列表为准,模版和因子个数怎么匹配
  172. now = datetime.datetime.now()
  173. formatted_time = now.strftime("%Y%m%d%H%M%S")
  174. res = {
  175. "xzmj": 1500,
  176. "xmmc": "规划选址项目_"+formatted_time,
  177. "jsdw": "建设单位",
  178. "ydxz_bsm": landType,
  179. "ydmjbegin": json_res["area"]["min"],
  180. "ydmjend": json_res["area"]["max"],
  181. "geomId": geomId,
  182. "yxyz": [],
  183. # TODO: 循环遍历
  184. # "yxyz": [
  185. # {
  186. # "id": "259e5bbaab434dbfb9c679bd44d4bfa4",
  187. # "name": "幼儿园服务半径",
  188. # "bsm": "TB_YEY",
  189. # "conditionInfo": {
  190. # "spatial_type": "distance",
  191. # "default": "lt",
  192. # "hasValue": true,
  193. # "defaultValue": "300",
  194. # "unit": "米",
  195. # "clip": false
  196. # }
  197. # }
  198. # ],
  199. # "useMultiple": json_res["useMultiple"],
  200. # "useLandType": json_res["useLandType"],
  201. # "multipleDistance": json_res["multipleDistance"]
  202. }
  203. # 循环遍历输入因子
  204. factors = json_res["factors"]
  205. input_factors = {}
  206. for factor in factors:
  207. factorInfo = getFactorByName(factor["type"])
  208. if factorInfo == None:
  209. continue
  210. factorId = factorInfo["id"]
  211. factorBsm = factorInfo["bsm"]
  212. conditionInfo = factorInfo["condition_info"]
  213. conditionObj = json.loads(conditionInfo)
  214. defaultValue = str(factor["value"])
  215. if defaultValue == '':
  216. defaultValue = '0'
  217. factor_info = {
  218. "id": factorId,
  219. "name": factor["type"],
  220. "bsm": factorBsm,
  221. "conditionInfo": {
  222. "spatial_type": conditionObj["spatial_type"],
  223. "default": factor["condition"],
  224. "hasValue": conditionObj["hasValue"],
  225. "defaultValue": defaultValue,
  226. "unit": conditionObj["unit"],
  227. "clip": conditionObj["clip"]
  228. }
  229. }
  230. input_factors[factor_info["id"]] = factor_info
  231. # 循环遍历模板
  232. for factorTemplate in factorTemplates:
  233. factorId = factorTemplate["id"]
  234. if factorId in input_factors:
  235. res["yxyz"].append(input_factors[factorId])
  236. else:
  237. factorTemplate["conditionInfo"] = json.loads(
  238. factorTemplate["conditionInfo"])
  239. res["yxyz"].append(factorTemplate)
  240. resObj = {}
  241. resObj["data"] = res
  242. resObj["code"] = 200
  243. resObj["type"] = "selectLand"
  244. return resObj
  245. # 获取因子信息
  246. def getFactorByName(name):
  247. with conn.cursor(cursor_factory=DictCursor) as cur:
  248. sql = "SELECT * FROM base.t_fzss_fzxz_factor WHERE name = %s"
  249. complete_sql = cur.mogrify(sql, (name,)).decode('utf-8')
  250. logger.info(f"Executing SQL: {complete_sql}")
  251. cur.execute(sql, (name,))
  252. res = cur.fetchone()
  253. return res
  254. # 获取内置模板信息
  255. def getTemplateByCode(code):
  256. with conn.cursor(cursor_factory=DictCursor) as cur:
  257. 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'
  258. complete_sql = cur.mogrify(sql, (code,)).decode('utf-8')
  259. logger.info(f"Executing SQL: {complete_sql}")
  260. cur.execute(sql, (code,))
  261. res = cur.fetchall()
  262. # 将查询结果转换为字典列表
  263. result_list = [dict(row) for row in res]
  264. return result_list
  265. # 获取选址范围信息
  266. def getAiDistrict(name):
  267. with conn.cursor(cursor_factory=DictCursor) as cur:
  268. sql = "SELECT public.st_asewkt(geom) as geom FROM base.t_fzss_fzxz_ai_district WHERE name = %s"
  269. complete_sql = cur.mogrify(sql, (name,)).decode('utf-8')
  270. logger.info(f"Executing SQL: {complete_sql}")
  271. cur.execute(sql, (name,))
  272. res = cur.fetchone()
  273. return res["geom"]
  274. # 保存选址范围信息
  275. def saveGeom(ewkt):
  276. new_uuid = str(uuid.uuid4()) # 生成一个新的 UUID
  277. from_type = 3
  278. with conn.cursor() as cur:
  279. 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))"
  280. complete_sql = cur.mogrify(
  281. sql, (new_uuid, ewkt, from_type, ewkt)).decode('utf-8')
  282. logger.info(f"Executing SQL: {complete_sql}")
  283. cur.execute(sql, (new_uuid, ewkt, from_type, ewkt))
  284. conn.commit()
  285. return new_uuid
  286. # 获取用地类型信息
  287. def getLandType(landName, fzbs):
  288. with conn.cursor(cursor_factory=DictCursor) as cur:
  289. sql = "SELECT dm,mc,fzbs FROM base.t_fzss_fzxz_dict WHERE mc = %s and fzbs=%s"
  290. complete_sql = cur.mogrify(sql, (landName, fzbs)).decode('utf-8')
  291. logger.info(f"Executing SQL: {complete_sql}")
  292. cur.execute(sql, (landName, fzbs))
  293. res = cur.fetchone()
  294. return res["dm"]
  295. # getTemplateByCode("08")
  296. # getAiDistrict("抱坡区")
  297. # 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))"
  298. # saveGeom(ewkt)
  299. # getFactorByName("幼儿园服务半径")
  300. if __name__ == '__main__':
  301. # app.run()
  302. app.run(
  303. host='0.0.0.0',
  304. port=4000
  305. )