feat: 添加 Linux/Windows 双平台测试套件(云南省界叠加分析)

- test-linux: platform=linux, docker + python3
- test-windows: platform=windows, arcpy + python3
- 附示例 GeoJSON: 云南/昆明/大理/百色

Ref: SuiteHub/agent-profiles#22
This commit is contained in:
SuiteForge
2026-07-21 00:10:50 +08:00
commit 967fa0f59c
16 changed files with 766 additions and 0 deletions
@@ -0,0 +1,63 @@
#!/usr/bin/env python
"""
Step 1: GeoJSON → SHP (arcpy)
将省界和城市 GeoJSON 文件转换为 Shapefile
使用 arcpy 的 JSON To Feature Class 工具
"""
import sys
import json
import os
import arcpy
# 允许覆写输出
arcpy.env.overwriteOutput = True
def main():
province_path = os.environ.get("PARAM_PROVINCE", "")
cities_param = os.environ.get("PARAM_CITIES", "")
output_dir = os.environ.get("PARAM_OUTPUT_DIR", r"C:\temp\output\shp")
if not province_path:
print json.dumps({"error": "缺少 province 参数", "success": False})
sys.exit(1)
if not cities_param:
print json.dumps({"error": "缺少 cities 参数", "success": False})
sys.exit(1)
if not os.path.isdir(output_dir):
os.makedirs(output_dir)
# 转换省界
print json.dumps({"msg": "转换省界: " + province_path})
province_name = os.path.splitext(os.path.basename(province_path))[0]
province_shp = os.path.join(output_dir, "province.shp")
# GeoJSON → Feature Class → Shapefile
arcpy.JSONToFeatures_conversion(province_path, province_shp)
print json.dumps({"msg": "省界已转为 SHP: " + province_shp})
# 转换每个城市
city_files = [c.strip() for c in cities_param.split(",") if c.strip()]
converted = []
for cf in city_files:
if not os.path.isfile(cf):
print json.dumps({"warn": "文件不存在,跳过: " + cf})
continue
print json.dumps({"msg": "转换城市: " + cf})
base = os.path.splitext(os.path.basename(cf))[0]
shp_path = os.path.join(output_dir, base + ".shp")
arcpy.JSONToFeatures_conversion(cf, shp_path)
converted.append(base)
print json.dumps({"msg": "已转换: " + cf + " -> " + shp_path})
result = {
"success": True,
"province_shp": province_shp,
"city_count": len(converted),
"cities": converted
}
print json.dumps(result)
if __name__ == "__main__":
main()
@@ -0,0 +1,119 @@
#!/usr/bin/env python
"""
Step 2: 叠加分析 (arcpy)
判断各城市是否在省界内,输出分析结果 JSON
"""
import sys
import json
import os
import glob
import arcpy
arcpy.env.overwriteOutput = True
def main():
province_shp = os.environ.get("PARAM_PROVINCE_SHP", "")
cities_dir = os.environ.get("PARAM_CITIES_DIR", r"C:\temp\output\shp")
output_dir = os.environ.get("PARAM_OUTPUT_DIR", r"C:\temp\output\result")
if not province_shp or not os.path.isfile(province_shp):
print json.dumps({"error": "省界 SHP 文件不存在: " + str(province_shp), "success": False})
sys.exit(1)
if not os.path.isdir(output_dir):
os.makedirs(output_dir)
# 查找所有城市 SHP
city_shp_files = glob.glob(os.path.join(cities_dir, "*.shp"))
city_shp_files = [f for f in city_shp_files
if os.path.basename(f) != "province.shp"]
if not city_shp_files:
print json.dumps({"error": "未找到城市 SHP 文件", "success": False})
sys.exit(1)
# 创建省界图层
province_lyr = "province_layer"
arcpy.MakeFeatureLayer_management(province_shp, province_lyr)
results = []
desc = arcpy.Describe(province_shp)
sr = desc.spatialReference
for shp in city_shp_files:
city_name = os.path.splitext(os.path.basename(shp))[0]
print json.dumps({"msg": "分析城市: " + city_name})
# 创建城市图层
city_lyr = "city_layer_" + city_name
arcpy.MakeFeatureLayer_management(shp, city_lyr)
# 空间选择:城市中心点在省界内
# 先获取城市几何中心
city_centroid_shp = os.path.join(output_dir, city_name + "_centroid.shp")
arcpy.FeatureToPoint_management(shp, city_centroid_shp, "INSIDE")
centroid_lyr = "centroid_layer_" + city_name
arcpy.MakeFeatureLayer_management(city_centroid_shp, centroid_lyr)
# Select by location: 中心点在省界内
arcpy.SelectLayerByLocation_management(centroid_lyr, "WITHIN", province_lyr)
count = int(arcpy.GetCount_management(centroid_lyr).getOutput(0))
in_province = count > 0
# 获取属性
city_name_prop = city_name
city_code = ""
population = 0
try:
with arcpy.da.SearchCursor(shp, ["name", "code", "population"]) as cursor:
for row in cursor:
if row[0]:
city_name_prop = str(row[0])
if row[1]:
city_code = str(row[1])
if row[2]:
population = int(row[2])
break
except Exception:
pass
status = u"通过 \u2705" if in_province else u"不通过 \u274c"
results.append({
"city": city_name,
"city_code": city_code,
"city_name": city_name_prop,
"population": population,
"in_province": in_province,
"intersects": in_province,
"status": status
})
print json.dumps({"msg": city_name + ": " + status})
# 清理
arcpy.Delete_management(city_lyr)
arcpy.Delete_management(centroid_lyr)
arcpy.Delete_management(city_centroid_shp)
arcpy.Delete_management(province_lyr)
# 输出 JSON
result_json = os.path.join(output_dir, "overlay_results.json")
with open(result_json, "w") as f:
json.dump(results, f, indent=2)
summary = {
"success": True,
"total_cities": len(results),
"in_province": sum(1 for r in results if r["in_province"]),
"not_in_province": sum(1 for r in results if not r["in_province"]),
"results": results,
"result_json": result_json
}
print json.dumps(summary)
if __name__ == "__main__":
main()
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Step 3: 输出 Excel
将叠加分析结果写入 Excel 文件
"""
import sys
import json
import os
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
def main():
result_dir = os.environ.get("PARAM_RESULT_DIR", "/tmp/output/result")
output_path = os.environ.get("PARAM_OUTPUT_PATH", "")
if not output_path:
print(json.dumps({"error": "缺少 output_path 参数", "success": False}))
sys.exit(1)
# 读取分析结果
result_json = os.path.join(result_dir, "overlay_results.json")
if not os.path.isfile(result_json):
print(json.dumps({"error": f"结果文件不存在: {result_json}", "success": False}))
sys.exit(1)
with open(result_json, "r", encoding="utf-8") as f:
results = json.load(f)
# 创建工作簿
wb = Workbook()
ws = wb.active
ws.title = "叠加分析结果"
# 标题样式
header_font = Font(bold=True, size=12, color="FFFFFF")
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_align = Alignment(horizontal="center", vertical="center")
# 表头
headers = ["城市", "城市代码", "城市名称", "人口", "在省内", "相交", "状态"]
for col, h in enumerate(headers, 1):
cell = ws.cell(row=1, column=col, value=h)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
# 数据行
pass_fill = PatternFill(start_color="E2EFDA", end_color="E2EFDA", fill_type="solid")
fail_fill = PatternFill(start_color="FCE4EC", end_color="FCE4EC", fill_type="solid")
for row_idx, r in enumerate(results, 2):
ws.cell(row=row_idx, column=1, value=r.get("city", ""))
ws.cell(row=row_idx, column=2, value=r.get("city_code", ""))
ws.cell(row=row_idx, column=3, value=r.get("city_name", ""))
ws.cell(row=row_idx, column=4, value=r.get("population", 0))
ws.cell(row=row_idx, column=5, value="" if r.get("in_province") else "")
ws.cell(row=row_idx, column=6, value="" if r.get("intersects") else "")
ws.cell(row=row_idx, column=7, value=r.get("status", ""))
# 条件高亮
fill = pass_fill if r.get("in_province") else fail_fill
for col in range(1, 8):
ws.cell(row=row_idx, column=col).fill = fill
# 调整列宽
ws.column_dimensions["A"].width = 15
ws.column_dimensions["B"].width = 12
ws.column_dimensions["C"].width = 15
ws.column_dimensions["D"].width = 12
ws.column_dimensions["E"].width = 10
ws.column_dimensions["F"].width = 10
ws.column_dimensions["G"].width = 15
# 确保输出目录存在
out_dir = os.path.dirname(output_path)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
wb.save(output_path)
print(json.dumps({
"success": True,
"output_path": output_path,
"total": len(results),
"msg": f"Excel 已输出到: {output_path}"
}))
if __name__ == "__main__":
main()