jsyd.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import sys
  4. import json
  5. import log
  6. import appconfig
  7. import arcpy
  8. import utils
  9. from arcpy import env
  10. from db.oracle import Oracle
  11. reload(sys)
  12. sys.setdefaultencoding('utf-8')
  13. class Jsyd:
  14. """
  15. 建设用地开发完成情况分析(规划 vs 三调)
  16. 分析内容:
  17. 1. 分析年度:根据国土调查数据的年份确定
  18. 2. 规划建设用地面积:统计规划地块中用地性质为建设用地的面积
  19. 3. 现状建设用地面积:统计现状调查数据中地类为建设用地的面积
  20. 4. 规划内的建设用地面积:现状建设用地与规划建设用地空间进行空间叠加,统计相交的面积
  21. 5. 规划外的建设用地面积:用规划建设用地空间擦除现状建设用地,统计剩余现状建设用地的面积
  22. 6. 建设用地开发完成情况:规划内的建设用地面积/规划建设用地面积
  23. 分析范围:
  24. - 分析范围为市时,建设用地开发完成情况结果显示到旗县
  25. - 分析范围为旗县时,建设用地开发完成情况结果显示到乡镇
  26. """
  27. def __init__(self, bsm):
  28. self.bsm = bsm
  29. self.db = Oracle(appconfig.DB_CONN)
  30. self.outputname = "JSYD_KFQK_" + bsm
  31. log.info("任务BSM=" + bsm)
  32. # 1. 读任务
  33. self.task()
  34. # 2. 临时 GDB
  35. self.root = os.path.dirname(os.path.abspath(__file__))
  36. path = os.path.join(self.root, "out")
  37. if not os.path.exists(path):
  38. os.makedirs(path)
  39. self.outGdb = os.path.join(path, "{}.gdb".format(self.outputname))
  40. arcpy.Delete_management(self.outGdb)
  41. arcpy.CreateFileGDB_management(path, "{}.gdb".format(self.outputname))
  42. env.overwriteOutput = True
  43. # ---------- 读任务 ----------
  44. def task(self):
  45. # 根据BSM获取任务信息
  46. # 查询返回字段说明:
  47. # sjy: 实际源表名
  48. # xzqdm: 行政区代码
  49. # fxyz: 分析因子ID
  50. # rwzt: 任务状态
  51. # fxtable: 分析表名
  52. # fxyear: 分析年份
  53. # CXTJ: 约束条件(现状条件=规划条件)
  54. # YZNAME: 因子名称
  55. # SJYNAME: 数据源名称
  56. # rwkssj: 任务开始时间
  57. sql = "select t.sjy,t.xzqdm,t.fxyz,t.rwzt,t.fxtable,t.fxyear,to_char(yz.cxtj) as \"CXTJ\",yz.name as \"YZNAME\",sjy.name as \"SJYNAME\",to_char(t.rwkssj, 'yyyy-mm-dd hh24:mi:ss') as \"RWKSSJ\" from t_fxpj_ctfx_main t left join t_fxpj_ctfx_yz yz on yz.id = t.fxyz left join t_fxpj_ctfx_sjy sjy on sjy.tablename = t.sjy where t.id = '{0}'".format(
  58. self.bsm)
  59. tasks = self.db.query(sql)
  60. if not tasks:
  61. raise Exception("任务标识错误[{}]".format(self.bsm))
  62. self.task = tasks[0]
  63. # 解析CXTJ参数,格式支持"2调现状条件=规划条件;3调现状条件=规划条件",根据年份选择使用的条件
  64. self.task["CXTJ"] = str(self.task["CXTJ"] or "")
  65. # 按分号分割不同调查类型的条件
  66. selected_condition = self.task["CXTJ"]
  67. if ";" in self.task["CXTJ"]:
  68. conditions = self.task["CXTJ"].split(";")
  69. # 根据分析年份选择使用哪个条件
  70. if int(self.task["FXYEAR"]) > 2018:
  71. # 使用3调条件(分号后)
  72. selected_condition = conditions[1]
  73. else:
  74. # 使用2调条件(分号前)
  75. selected_condition = conditions[0]
  76. # 解析选定条件中的等号分隔的现状和规划条件,保持原有逻辑不变
  77. # error_msg = "CXTJ参数格式错误,必须包含等号分隔的现状条件和规划条件,格式:现状条件=规划条件"
  78. error_msg = "后台数据不足,无法统计!"
  79. if "=" in selected_condition:
  80. conditions = selected_condition.split("=")
  81. # 现状条件判断,如果为空字符串则报错
  82. if not conditions[0] or not conditions[0].strip():
  83. self.updateFxzt(self.bsm, "3", error_msg)
  84. raise Exception(error_msg)
  85. # 规划条件判断,如果为空字符串则报错
  86. if not conditions[1] or not conditions[1].strip():
  87. self.updateFxzt(self.bsm, "3", error_msg)
  88. raise Exception(error_msg)
  89. self.task["XZ_CXTJ"] = conditions[0] if (conditions[0] and conditions[0].strip()) else None # 现状查询条件,None表示无约束
  90. self.task["GH_CXTJ"] = conditions[1] if (conditions[1] and conditions[1].strip()) else None # 规划查询条件,None表示无约束
  91. else:
  92. # 如果没有等号分隔,则抛出异常,因为格式必须是"现状条件=规划条件"
  93. self.updateFxzt(self.bsm, "3", error_msg)
  94. raise Exception(error_msg)
  95. # ---------- 主流程 ----------
  96. def run(self):
  97. try:
  98. # 1. 分析年度:根据国土调查数据的年份确定
  99. analysis_year = self.task["FXYEAR"]
  100. # 2. 规划建设用地面积:统计规划地块中用地性质为建设用地的面积
  101. gh_layer = self.get_gh_jsyd()
  102. gh_total = self.get_area(gh_layer) # 规划建设用地面积
  103. # 3. 现状建设用地面积:统计现状调查数据中地类为建设用地的面积
  104. xz_layer = self.get_xz_jsyd()
  105. xz_total = self.get_area(xz_layer) # 现状建设用地面积
  106. # 4. 规划内的建设用地面积:现状建设用地与规划建设用地空间进行空间叠加,统计相交的面积
  107. gh_nc = arcpy.analysis.Intersect([gh_layer, xz_layer],
  108. r"{}\GH_NC".format(self.outGdb))
  109. # 重新计算相交后的面积
  110. self.add_tbmj(gh_nc)
  111. nc_total = self.get_area(gh_nc) # 规划内建设用地面积
  112. # 5. 规划外的建设用地面积:现状建设用地减去规划内建设用地,统计剩余现状建设用地的面积
  113. gh_wai = arcpy.analysis.Erase(xz_layer, gh_nc,
  114. r"{}\GH_WAI".format(self.outGdb))
  115. # 重新计算擦除后的面积
  116. self.add_tbmj(gh_wai)
  117. gh_wai_total = self.get_area(gh_wai) # 规划外建设用地面积
  118. # 6. 数据验证:确保规划内+规划外=现状总计
  119. calculated_total = nc_total + gh_wai_total
  120. tolerance = 0.01 # 允许0.01平方米的误差
  121. if abs(calculated_total - xz_total) > tolerance:
  122. log.warning("数据验证失败:规划内({:.2f}) + 规划外({:.2f}) = {:.2f} ≠ 现状总计({:.2f})".format(
  123. nc_total, gh_wai_total, calculated_total, xz_total))
  124. print("警告:面积计算可能有误,请检查数据")
  125. # 7. 建设用地开发完成情况:规划内的建设用地面积/规划建设用地面积
  126. development_rate = (nc_total / gh_total * 100) if gh_total > 0 else 0
  127. # 7. 行政区统计逻辑
  128. # 分析范围为市时,建设用地开发完成情况结果显示到旗县
  129. # 分析范围为旗县时,建设用地开发完成情况结果显示到乡镇
  130. result_data = []
  131. sub_len = self.getSubXzqLength()
  132. # 为规划内和规划外图层添加行政区统计字段
  133. arcpy.AddField_management(gh_nc, "STATICS", "TEXT")
  134. arcpy.CalculateField_management(gh_nc, "STATICS",
  135. "!ZLDWDM![0:{}]".format(sub_len),
  136. "PYTHON_9.3")
  137. # 为规划图层添加行政区统计字段,用于计算各下级行政区的规划总面积
  138. arcpy.AddField_management(gh_layer, "STATICS", "TEXT")
  139. arcpy.CalculateField_management(gh_layer, "STATICS",
  140. "!XZQDM![0:{}]".format(sub_len),
  141. "PYTHON_9.3")
  142. # 统计各下级行政区的规划建设用地面积
  143. gh_stats = r"{}\GH_STATS".format(self.outGdb)
  144. arcpy.Statistics_analysis(gh_layer, gh_stats,
  145. [["TBMJ", "SUM"]], "STATICS")
  146. # 统计各下级行政区的规划内建设用地面积
  147. nc_stats = r"{}\NC_STATS".format(self.outGdb)
  148. arcpy.Statistics_analysis(gh_nc, nc_stats,
  149. [["TBMJ", "SUM"]], "STATICS")
  150. # 为现状图层添加行政区统计字段,用于计算各下级行政区的现状总面积
  151. arcpy.AddField_management(xz_layer, "STATICS", "TEXT")
  152. arcpy.CalculateField_management(xz_layer, "STATICS",
  153. "!ZLDWDM![0:{}]".format(sub_len),
  154. "PYTHON_9.3")
  155. # 统计各下级行政区的现状建设用地面积
  156. xz_stats = r"{}\XZ_STATS".format(self.outGdb)
  157. arcpy.Statistics_analysis(xz_layer, xz_stats,
  158. [["TBMJ", "SUM"]], "STATICS")
  159. # 为规划外图层添加行政区统计字段,用于计算各下级行政区的规划外面积
  160. arcpy.AddField_management(gh_wai, "STATICS", "TEXT")
  161. arcpy.CalculateField_management(gh_wai, "STATICS",
  162. "!ZLDWDM![0:{}]".format(sub_len),
  163. "PYTHON_9.3")
  164. # 统计各下级行政区的规划外建设用地面积
  165. gh_wai_stats = r"{}\GH_WAI_STATS".format(self.outGdb)
  166. arcpy.Statistics_analysis(gh_wai, gh_wai_stats,
  167. [["TBMJ", "SUM"]], "STATICS")
  168. # 查询行政区名称
  169. xzq_names = self.get_xzq_names()
  170. # 创建各类面积字典
  171. gh_area_dict = {} # 规划建设用地面积
  172. with arcpy.da.SearchCursor(gh_stats, ["STATICS", "SUM_TBMJ"]) as cur:
  173. for row in cur:
  174. gh_area_dict[row[0]] = row[1]
  175. xz_area_dict = {} # 现状建设用地面积
  176. with arcpy.da.SearchCursor(xz_stats, ["STATICS", "SUM_TBMJ"]) as cur:
  177. for row in cur:
  178. xz_area_dict[row[0]] = row[1]
  179. nc_area_dict = {} # 规划内建设用地面积
  180. with arcpy.da.SearchCursor(nc_stats, ["STATICS", "SUM_TBMJ"]) as cur:
  181. for row in cur:
  182. nc_area_dict[row[0]] = row[1]
  183. gh_wai_area_dict = {} # 规划外建设用地面积
  184. with arcpy.da.SearchCursor(gh_wai_stats, ["STATICS", "SUM_TBMJ"]) as cur:
  185. for row in cur:
  186. gh_wai_area_dict[row[0]] = row[1]
  187. # 构建各下级行政区的开发完成情况结果数据
  188. all_xzdm = set(gh_area_dict.keys()) | set(xz_area_dict.keys()) | set(nc_area_dict.keys()) | set(gh_wai_area_dict.keys())
  189. for xzdm in all_xzdm:
  190. gh_sub_total = gh_area_dict.get(xzdm, 0) # 规划建设用地面积
  191. xz_sub_total = xz_area_dict.get(xzdm, 0) # 现状建设用地面积
  192. nc_sub_total = nc_area_dict.get(xzdm, 0) # 规划内建设用地面积
  193. gh_wai_sub_total = gh_wai_area_dict.get(xzdm, 0) # 规划外建设用地面积
  194. name = xzq_names.get(xzdm, "未知区域")
  195. # 计算该下级行政区的建设用地开发完成情况
  196. sub_development_rate = (nc_sub_total / gh_sub_total * 100) if gh_sub_total > 0 else 0
  197. result_data.append({
  198. "xzqmc": name,
  199. "xzqdm": xzdm,
  200. "proportion": round(sub_development_rate, 2),
  201. "tbmj": round(gh_sub_total, 2),
  202. "xzqmj": round(xz_sub_total, 2),
  203. "ghnmj": round(nc_sub_total, 2),
  204. "ghwmj": round(gh_wai_sub_total, 2)
  205. })
  206. # 8. 回写结果
  207. self.rwjssj = utils.getNowTimeStr("%Y-%m-%d %H:%M:%S")
  208. # 使用分析因子名称
  209. yz_name = self.task["YZNAME"] if self.task["YZNAME"] else "建设用地"
  210. # 获取行政区名称
  211. xzq_names = self.get_xzq_names()
  212. xzq_name = xzq_names.get(self.task["XZQDM"], "未知行政区")
  213. # 构建分析结果描述(按照用户要求的格式)
  214. fxjg = "{0}规划{1}约{2},{1}现状约有{3},其中:规划内{1}现状约有{4},规划外{1}现状约{5},{1}开发完成总体约{6:.0f}%。".format(
  215. xzq_name,
  216. yz_name,
  217. self.area_transform(gh_total),
  218. self.area_transform(xz_total),
  219. self.area_transform(nc_total),
  220. self.area_transform(gh_wai_total),
  221. development_rate)
  222. self.updateFxztAndStatist(self.bsm, "2", fxjg, json.dumps(result_data, ensure_ascii=False))
  223. log.info("####OK####")
  224. print("####OK####")
  225. except arcpy.ExecuteError:
  226. msg = arcpy.GetMessages()
  227. log.error(msg)
  228. self.updateFxzt(self.bsm, "3", msg)
  229. except:
  230. msg = str(sys.exc_info()).decode('string-escape')
  231. log.error(msg)
  232. self.updateFxzt(self.bsm, "3", msg)
  233. # ---------- 读规划建设用地 ----------
  234. def get_gh_jsyd(self):
  235. env.workspace = appconfig.getSDE("SDE") # 规划数据在SDE库中
  236. out = r"{}\GH_JSYD".format(self.outGdb)
  237. # 构建查询条件:结合行政区域代码和规划条件
  238. xzqdm = self.task["XZQDM"]
  239. xzqdm_condition = "XZQDM LIKE '{}%'".format(xzqdm)
  240. # 如果有规划条件,则组合条件;否则只使用行政区域条件
  241. if self.task["GH_CXTJ"] and self.task["GH_CXTJ"].strip():
  242. combined_condition = "({}) AND ({})".format(xzqdm_condition, self.task["GH_CXTJ"])
  243. else:
  244. combined_condition = xzqdm_condition
  245. arcpy.Select_analysis("GHYDYH", out, combined_condition)
  246. self.add_tbmj(out)
  247. return out
  248. # ---------- 读现状建设用地 ----------
  249. def get_xz_jsyd(self):
  250. env.workspace = appconfig.getSDE("SDE") # 三调库
  251. out = r"{}\XZ_JSYD".format(self.outGdb)
  252. # 构建查询条件:结合行政区域代码和现状条件
  253. xzqdm = self.task["XZQDM"]
  254. xzqdm_condition = "ZLDWDM LIKE '{}%'".format(xzqdm)
  255. # 如果有现状条件,则组合条件;否则只使用行政区域条件
  256. if self.task["XZ_CXTJ"] and self.task["XZ_CXTJ"].strip():
  257. combined_condition = "({}) AND ({})".format(xzqdm_condition, self.task["XZ_CXTJ"])
  258. else:
  259. combined_condition = xzqdm_condition
  260. arcpy.Select_analysis(self.task["FXTABLE"], out, combined_condition)
  261. self.add_tbmj(out)
  262. return out
  263. # ---------- 查询行政区名称 ----------
  264. def get_xzq_names(self):
  265. """
  266. 从表v_yzt_zysxcx中查询行政区域名称
  267. 只查询与当前任务行政区代码相关的记录,即id等于该行政区代码或者pid等于该行政区代码的记录
  268. 并且type必须是XZQ
  269. :return: 字典,键为行政区代码,值为行政区名称
  270. """
  271. xzqdm = self.task["XZQDM"]
  272. sql = "SELECT id, name FROM v_yzt_zysxcx WHERE (id = '{0}' OR pid = '{0}') AND type = 'XZQ'".format(xzqdm)
  273. results = self.db.query(sql)
  274. xzq_dict = {}
  275. for row in results:
  276. xzq_dict[row["ID"]] = row["NAME"]
  277. return xzq_dict
  278. # ---------- 小工具 ----------
  279. def add_tbmj(self, fc):
  280. arcpy.AddField_management(fc, "TBMJ", "DOUBLE")
  281. arcpy.CalculateField_management(fc, "TBMJ",
  282. "!shape.area@SQUAREMETERS!",
  283. "PYTHON_9.3")
  284. def get_area(self, fc):
  285. total = 0
  286. with arcpy.da.SearchCursor(fc, ["TBMJ"]) as cur:
  287. for row in cur:
  288. total += row[0]
  289. return total
  290. def getSubXzqLength(self):
  291. """
  292. 根据行政区代码长度确定下级行政区统计粒度
  293. 分析范围为市时,建设用地开发完成情况结果显示到旗县
  294. 分析范围为旗县时,建设用地开发完成情况结果显示到乡镇
  295. 最大级别只到乡镇(9位行政区代码)
  296. """
  297. curlen = len(self.task["XZQDM"])
  298. if curlen == 4: # 市级(如:1501)
  299. return 6 # 统计到县级(如:150102)
  300. elif curlen == 6: # 县级(如:150102)
  301. return 9 # 统计到乡级(如:150102001)
  302. else:
  303. # 对于其他情况,根据当前级别确定下级统计粒度,但最大只到乡镇级别
  304. if curlen <= 4:
  305. return 6 # 统计到县级
  306. elif curlen <= 6:
  307. return 9 # 统计到乡级
  308. else:
  309. return 9 # 最大只到乡镇级别(9位)
  310. def area_transform(self, area, unit="km2"):
  311. """
  312. 面积单位转换
  313. :param area: 面积值(平方米)
  314. :param unit: 目标单位,支持 "hectare"(公顷)、"mu"(亩)、"km2"(平方千米),默认为平方千米
  315. :return: 格式化的面积字符串
  316. """
  317. if unit == "hectare":
  318. # 转换为公顷
  319. hectare_value = area / 10000
  320. return "{:.2f}公顷".format(hectare_value)
  321. elif unit == "mu":
  322. # 转换为亩
  323. mu_value = area * 0.0015
  324. return "{:.2f}亩".format(mu_value)
  325. elif unit == "km2":
  326. # 转换为平方千米
  327. km2_value = area / 1000000
  328. return "{:.2f}平方千米".format(km2_value)
  329. else:
  330. # 默认返回平方千米
  331. km2_value = area / 1000000
  332. return "{:.2f}平方千米".format(km2_value)
  333. # ---------- 任务状态更新 ----------
  334. def updateFxzt(self, bsm, fxzt, msg):
  335. self.rwjssj = utils.getNowTimeStr("%Y-%m-%d %H:%M:%S")
  336. # 清理消息中的特殊字符,防止SQL注入和语法错误
  337. clean_msg = msg.replace("'", "''") if msg else ""
  338. # 确保中文字符串正确编码
  339. if isinstance(clean_msg, unicode):
  340. clean_msg = clean_msg.encode('utf-8')
  341. sql = ("update t_fxpj_ctfx_main t set rwzt='{0}',"
  342. "rwkssj=to_date('{1}','yyyy-mm-dd hh24:mi:ss'),"
  343. "rwjssj=to_date('{2}','yyyy-mm-dd hh24:mi:ss'),"
  344. "fxjg='{3}',fxjgtable='{4}',workspace='{5}' "
  345. "where t.id='{6}'").format(
  346. fxzt, self.task["RWKSSJ"], self.rwjssj,
  347. clean_msg, self.outputname, '', bsm)
  348. log.info(sql)
  349. self.db.insert(sql)
  350. def updateFxztAndStatist(self, bsm, fxzt, msg, statist):
  351. self.rwjssj = utils.getNowTimeStr("%Y-%m-%d %H:%M:%S")
  352. # 清理消息中的特殊字符,防止SQL注入和语法错误
  353. clean_msg = msg.replace("'", "''") if msg else ""
  354. clean_statist = statist.replace("'", "''") if statist else ""
  355. # 确保中文字符串正确编码
  356. if isinstance(clean_msg, unicode):
  357. clean_msg = clean_msg.encode('utf-8')
  358. if isinstance(clean_statist, unicode):
  359. clean_statist = clean_statist.encode('utf-8')
  360. sql = ("update t_fxpj_ctfx_main t set rwzt='{0}',"
  361. "rwkssj=to_date('{1}','yyyy-mm-dd hh24:mi:ss'),"
  362. "rwjssj=to_date('{2}','yyyy-mm-dd hh24:mi:ss'),"
  363. "fxjg='{3}',fxjgtable='{4}',workspace='{5}',statist='{7}' "
  364. "where t.id='{6}'").format(
  365. fxzt, self.task["RWKSSJ"], self.rwjssj,
  366. clean_msg, self.outputname, self.outGdb, bsm, clean_statist)
  367. self.db.insert(sql)
  368. # ---------------- 入口 ----------------
  369. if __name__ == "__main__":
  370. Jsyd("01eb2b2c332a45478e61ae5155b2d867").run()