From bee076504b5f5d10fd683fdabc44e0691c5aa45e Mon Sep 17 00:00:00 2001 From: xuhengjie Date: Mon, 20 Jul 2026 18:45:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20v1.0.0=EF=BC=9A?= =?UTF-8?q?=E5=A2=9E=E5=87=8F=E6=8C=82=E9=92=A9=E5=90=8E=E5=A4=87=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E8=B0=83=E6=9F=A5=E6=95=B0=E6=8D=AE=E5=BA=93=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=A5=97=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/__pycache__/process.cpython-312.pyc | Bin 0 -> 21557 bytes scripts/process.py | 395 ++++++++++++++++++++ setup_repo.sh | 37 ++ suite.tgz | Bin 0 -> 14918 bytes workflow.yaml | 105 ++++++ 5 files changed, 537 insertions(+) create mode 100644 scripts/__pycache__/process.cpython-312.pyc create mode 100644 scripts/process.py create mode 100644 setup_repo.sh create mode 100644 suite.tgz create mode 100644 workflow.yaml diff --git a/scripts/__pycache__/process.cpython-312.pyc b/scripts/__pycache__/process.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4ce522ebe68105605dbb0cd2d03c6e50df224e8 GIT binary patch literal 21557 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 literal 0 HcmV?d00001 diff --git a/scripts/process.py b/scripts/process.py new file mode 100644 index 0000000..17dcda4 --- /dev/null +++ b/scripts/process.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python3 +""" +永善县增减挂钩后备资源调查数据库生成 — 处理脚本 + +执行模式: + extract_2019 提取2019年三调建设用地 + extract_2025 提取2025年变更调查建设用地 + overlay_intersect 叠加分析取交集(两年均为建设用地图斑) + erase_restrictions 逐层擦除限制要素 + filter_area 面积过滤 + 影像核实清单输出 +""" +import json, sys, os, glob, logging, traceback +from pathlib import Path +from datetime import datetime + +import geopandas as gpd +import pandas as pd +import numpy as np + +# ── 三调建设用地编码 ────────────────────────────────── +CONSTRUCTION_CODES = {'0702': '农村宅基地', '0601': '工业用地', '0602': '采矿用地'} +CODE_LIST = list(CONSTRUCTION_CODES.keys()) + +# 常见地类编码字段名(按优先级) +DLBM_FIELDS = ['DLBM', 'DLBZ', 'DLMC', 'YSDLMC', 'YSDLBZ', 'DLYBM', 'DLMC_1'] + + +# ══════════════════════════════════════════════════════ +# 工具函数 +# ══════════════════════════════════════════════════════ + +def get_logger(log_path=None): + """获取/配置日志器""" + logger = logging.getLogger('ys_zg') + if logger.handlers: + return logger + logger.setLevel(logging.INFO) + fmt = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + sh = logging.StreamHandler(sys.stdout) + sh.setFormatter(fmt) + logger.addHandler(sh) + if log_path: + fh = logging.FileHandler(log_path, encoding='utf-8') + fh.setFormatter(fmt) + logger.addHandler(fh) + return logger + + +def read_vector(path, layer=None): + """通用矢量读取""" + if path.endswith('.gpkg') and layer: + return gpd.read_file(path, layer=layer) + return gpd.read_file(path) + + +def estimate_utm_crs(gdf): + """根据数据范围估算 UTM 投影(永善县一般在 EPSG:32648)""" + bounds = gdf.total_bounds + center_lon = (bounds[0] + bounds[2]) / 2 + utm_zone = int((center_lon + 180) / 6) + 1 + is_north = bounds[1] + bounds[3] >= 0 + epsg = 32600 + utm_zone if is_north else 32700 + utm_zone + logger = get_logger() + logger.info(f" 估算投影: lon={center_lon:.2f}, zone={utm_zone}, EPSG:{epsg}") + return f"EPSG:{epsg}" + + +def ensure_metric_crs(gdf, logger): + """确保数据在投影坐标系下以便计算面积""" + if gdf.crs and gdf.crs.is_geographic: + utm_crs = estimate_utm_crs(gdf) + logger.info(f" 地理坐标系 -> 投影到 {utm_crs}") + return gdf.to_crs(utm_crs) + return gdf + + +def detect_and_filter_construction(gdf, logger): + """ + 自动检测地类编码字段并过滤出建设用地(0702/0601/0602)。 + 返回 (filtered_gdf, used_field, code_counts_dict) + """ + # 1) 按已知字段名搜索 + for field in DLBM_FIELDS: + if field not in gdf.columns: + continue + logger.info(f" 尝试字段: {field}") + try: + vals = gdf[field].astype(str).str.strip() + mask = vals.str[:4].isin(CODE_LIST) + count = mask.sum() + if count > 0: + result = gdf[mask].copy() + result['_code_field'] = field + result['_land_code'] = vals[mask].str[:4].values + counts = result['_land_code'].value_counts().to_dict() + return result, field, counts + except Exception: + continue + + # 2) 遍历所有字符串字段模糊匹配 + for field in gdf.select_dtypes(include='object').columns: + if field in DLBM_FIELDS: + continue # 已查过 + try: + vals = gdf[field].astype(str).str.strip() + mask = vals.str[:4].isin(CODE_LIST) + count = mask.sum() + if count > 0: + result = gdf[mask].copy() + result['_code_field'] = field + result['_land_code'] = vals[mask].str[:4].values + counts = result['_land_code'].value_counts().to_dict() + logger.info(f" 模糊匹配字段: {field}, 命中 {count} 条") + return result, field, counts + except Exception: + continue + + return None, None, {} + + +def calc_area(gdf, logger): + """计算面积(平方米)""" + if 'area_m2' in gdf.columns and gdf['area_m2'].notna().any(): + return gdf # 已有面积字段 + gdf = ensure_metric_crs(gdf, logger) + gdf['area_m2'] = gdf.geometry.area.round(2) + return gdf + + +# ══════════════════════════════════════════════════════ +# 步骤函数 +# ══════════════════════════════════════════════════════ + +def step_extract(db_path, layer, output_path, log_path, label): + """提取建设用地(step1 / step2 共用)""" + logger = get_logger(log_path) + logger.info(f"{'='*60}") + logger.info(f"提取{label}: {db_path}") + logger.info(f"{'='*60}") + + gdf = read_vector(db_path, layer=layer) + logger.info(f" 数据读取完成: {len(gdf)} 要素") + + result, field, counts = detect_and_filter_construction(gdf, logger) + if result is None or len(result) == 0: + logger.error(f"❌ 未能从数据中识别建设用地编码 {CODE_LIST},请检查字段名") + # 输出空 GeoPackage 避免下游步骤报文件缺失 + empty = gpd.GeoDataFrame(columns=['geometry'], geometry='geometry', crs=gdf.crs) + empty.to_file(output_path, driver='GPKG') + sys.exit(1) + + # 记录分类统计 + for code, cnt in counts.items(): + name = CONSTRUCTION_CODES.get(code, '未知') + logger.info(f" ✅ {code} {name}: {cnt} 条") + + result = calc_area(result, logger) + total_area = result['area_m2'].sum() + logger.info(f" 合计: {len(result)} 条, 总面积: {total_area:,.2f} m²") + + result.to_file(output_path, driver='GPKG') + logger.info(f" ✅ 结果输出 -> {output_path}") + return output_path + + +def step_overlay(input1, input2, output_path, log_path): + """叠加分析取交集""" + logger = get_logger(log_path) + logger.info(f"{'='*60}") + logger.info("叠加分析:取 2019 ∩ 2025 交集") + logger.info(f"{'='*60}") + + gdf1 = gpd.read_file(input1) + gdf2 = gpd.read_file(input2) + logger.info(f" 输入1: {len(gdf1)} 要素, CRS={gdf1.crs}") + logger.info(f" 输入2: {len(gdf2)} 要素, CRS={gdf2.crs}") + + # 统一 CRS + if gdf1.crs != gdf2.crs: + gdf2 = gdf2.to_crs(gdf1.crs) + + # 交集 + result = gpd.overlay(gdf1, gdf2, how='intersection', keep_geom_type=True) + result = result.reset_index(drop=True) + result = calc_area(result, logger) + + logger.info(f" 交集结果: {len(result)} 条, 总面积: {result['area_m2'].sum():,.2f} m²") + result.to_file(output_path, driver='GPKG') + logger.info(f" ✅ 结果输出 -> {output_path}") + return output_path + + +def step_erase_restrictions(input_path, restriction_dir, output_path, log_path): + """逐层擦除限制要素""" + logger = get_logger(log_path) + logger.info(f"{'='*60}") + logger.info("逐层擦除限制要素") + logger.info(f"{'='*60}") + + if not os.path.isdir(restriction_dir): + logger.error(f"限制要素目录不存在: {restriction_dir}") + sys.exit(1) + + gdf = gpd.read_file(input_path) + logger.info(f" 输入要素: {len(gdf)} 条") + gdf = calc_area(gdf, logger) + + # 扫描目录下的矢量文件 + restriction_files = [] + for ext in ['*.shp', '*.gpkg', '*.geojson', '*.json']: + restriction_files.extend(sorted(glob.glob(os.path.join(restriction_dir, ext)))) + + if not restriction_files: + logger.warning(f" ⚠️ 未在 {restriction_dir} 中找到矢量文件,跳过擦除") + gdf.to_file(output_path, driver='GPKG') + return output_path + + logger.info(f" 发现 {len(restriction_files)} 个限制要素文件:") + for rf in restriction_files: + logger.info(f" - {os.path.basename(rf)}") + + result = gdf.copy() + result = ensure_metric_crs(result, logger) + + for rf in restriction_files: + basename = os.path.basename(rf) + try: + restrict = gpd.read_file(rf) + if restrict.crs != result.crs: + restrict = restrict.to_crs(result.crs) + + before = len(result) + before_area = result['area_m2'].sum() + result = gpd.overlay(result, restrict, how='difference', keep_geom_type=True) + result = result.reset_index(drop=True) + result = calc_area(result, logger) + removed = before - len(result) + removed_area = before_area - result['area_m2'].sum() + + logger.info(f" ✅ 擦除 [{basename}]: 移除 {removed} 条({removed_area:,.0f}m²), " + f"剩余 {len(result)} 条({result['area_m2'].sum():,.0f}m²)") + except Exception as e: + logger.warning(f" ⚠️ 擦除 [{basename}] 出错: {e}") + continue + + result.to_file(output_path, driver='GPKG') + logger.info(f" ✅ 擦除完成: {len(result)} 条, 总面积: {result['area_m2'].sum():,.2f} m²") + return output_path + + +def step_filter(input_path, output_path, log_path, min_area=70.0, orthophoto_dir=None): + """面积过滤 + 生成影像核实清单""" + logger = get_logger(log_path) + logger.info(f"{'='*60}") + logger.info(f"面积过滤: >= {min_area} 平方米") + logger.info(f"{'='*60}") + + gdf = gpd.read_file(input_path) + before = len(gdf) + gdf = calc_area(gdf, logger) + + result = gdf[gdf['area_m2'] >= min_area].copy().reset_index(drop=True) + removed = before - len(result) + + logger.info(f" 过滤前: {before} 条 | 过滤后: {len(result)} 条 | " + f"移除 < {min_area}m²: {removed} 条") + + # 添加元数据字段 + result['fid'] = range(1, len(result) + 1) + result['bdlx'] = result.get('_land_code', '') + result['mj'] = result['area_m2'].round(2) + now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + result['scrq'] = now_str + + # 输出最终成果 GeoPackage + result.to_file(output_path, driver='GPKG', layer='final_houbei_ziyuan') + logger.info(f" ✅ 最终结果输出 -> {output_path}") + + # ── 输出影像核实 Excel 清单 ── + excel_path = output_path.replace('.gpkg', '_影像核实清单.xlsx') + if excel_path == output_path: + excel_path = os.path.join(os.path.dirname(output_path), '影像核实清单.xlsx') + + export_fields = ['fid', 'bdlx', 'mj'] + # 保留有意义的属性字段 + for f in result.columns: + if f not in export_fields and f not in ['geometry', '_code_field', '_land_code', 'area_m2', 'fid']: + if result[f].dtype in ['object', 'int64', 'float64']: + export_fields.append(f) + + export_df = result[export_fields].copy() if len(result) > 0 else pd.DataFrame() + export_df['影像核实状态'] = '' + export_df['影像核实备注'] = '' + export_df['核实人'] = '' + export_df['核实日期'] = '' + + # 使用 openpyxl 写 Excel + try: + export_df.to_excel(excel_path, index=False, engine='openpyxl') + logger.info(f" ✅ 影像核实清单输出 -> {excel_path}") + except Exception as e: + logger.warning(f" ⚠️ Excel 输出失败: {e}") + + # 统计输出 + area_by_type = result.groupby('bdlx')['mj'].agg(['count', 'sum']).reset_index() + logger.info(f" 按地类统计:") + for _, row in area_by_type.iterrows(): + name = CONSTRUCTION_CODES.get(str(row['bdlx'])[:4], '其他') + logger.info(f" {name}: {int(row['count'])} 条, {row['sum']:,.0f} m²") + + # 提示影像核实为人工步骤 + logger.info(f"{'!'*60}") + logger.info("⚠️ 重要提示:影像核实为人工核查步骤") + logger.info(f" 请使用 {excel_path} 清单") + logger.info(f" 结合永善县 2023年耕地流出正射影像 和 2026年房体一体正射影像") + logger.info(f" 逐一核实图斑:对建筑物完整、年份新的图斑标记删除") + logger.info(f" 最终以核实后的调查数据库为准") + logger.info(f"{'!'*60}") + + return output_path + + +# ══════════════════════════════════════════════════════ +# Main +# ══════════════════════════════════════════════════════ + +def main(): + # 参数通过 PARAMS_FILE 环境变量或 sys.argv[1] 传入 + params = {} + if 'PARAMS_FILE' in os.environ: + with open(os.environ['PARAMS_FILE'], 'r', encoding='utf-8') as f: + params = json.load(f) + elif len(sys.argv) > 1: + params = json.loads(sys.argv[1]) + else: + # fallback: parse from sys.argv as key=value pairs + for arg in sys.argv[1:]: + if '=' in arg: + k, v = arg.split('=', 1) + params[k] = v + + mode = params.get('mode', '') + output = os.path.abspath(params.get('output', '/tmp/output/result.gpkg')) + log_path = os.path.abspath(params.get('log', '/tmp/output/process.log')) + + # 确保输出目录存在 + os.makedirs(os.path.dirname(output), exist_ok=True) + + logger = get_logger(log_path) + + try: + if mode == 'extract': + db_path = os.path.abspath(params.get('db_path', '')) + if not db_path: + raise ValueError("缺少参数: db_path") + layer = params.get('layer', None) or None + label = params.get('label', '建设用地') + step_extract(db_path, layer, output, log_path, label) + + elif mode == 'overlay': + input1 = os.path.abspath(params.get('input1', '')) + input2 = os.path.abspath(params.get('input2', '')) + step_overlay(input1, input2, output, log_path) + + elif mode == 'erase': + input_path = os.path.abspath(params.get('input_path', '')) + restrict_dir = os.path.abspath(params.get('restriction_dir', '')) + step_erase_restrictions(input_path, restrict_dir, output, log_path) + + elif mode == 'filter': + input_path = os.path.abspath(params.get('input_path', '')) + min_area = float(params.get('min_area', 70)) + odir = params.get('orthophoto_dir', None) + odir = os.path.abspath(odir) if odir else None + step_filter(input_path, output, log_path, min_area, odir) + + else: + raise ValueError(f"未知模式: {mode},支持: extract / overlay / erase / filter") + + except Exception as e: + logger.error(f"❌ 执行失败: {e}") + logger.error(traceback.format_exc()) + sys.exit(1) + + # stdout JSON 结果(平台契约) + print(json.dumps({ + "status": "success", + "mode": mode, + "output": output, + "log": log_path + }, ensure_ascii=False)) + + +if __name__ == '__main__': + main() diff --git a/setup_repo.sh b/setup_repo.sh new file mode 100644 index 0000000..bf51bcc --- /dev/null +++ b/setup_repo.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# 在 SuiteHub 组织下创建仓库并推送代码 +set -e + +TOKEN="135e5d…4cb6" +ORG="SuiteHub" +REPO="yongshan-zengjian-guagou" +GITEA="https://git.mercator.cn" + +echo "=== 1. 检查仓库 ===" +RESULT=$(curl -s -H "Authorization: token $TOKEN" "$GITEA/api/v1/repos/$ORG/$REPO") +if echo "$RESULT" | grep -q '"id"'; then + echo "仓库已存在" +else + echo "=== 2. 创建仓库 ===" + curl -s -X POST "$GITEA/api/v1/orgs/$ORG/repos" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"$REPO\",\"description\":\"永善县增减挂钩后备资源调查数据库生成套件\",\"private\":false,\"auto_init\":true}" +fi + +echo "" +echo "=== 3. 克隆并推送代码 ===" +TMPDIR="/tmp/$REPO" +rm -rf "$TMPDIR" +git clone "https://xuhengjie:$TOKEN@$GITEA/$ORG/$REPO.git" "$TMPDIR" +cp -r /home/openclaw/.openclaw/workspace/$REPO/workflow.yaml "$TMPDIR/" +mkdir -p "$TMPDIR/scripts" +cp -r /home/openclaw/.openclaw/workspace/$REPO/scripts/*.py "$TMPDIR/scripts/" +cd "$TMPDIR" +git add -A +git commit -m "初始化 v1.0.0:增减挂钩后备资源调查数据库生成套件" +git push origin main +echo "" +echo "=== 完成 ===" +echo "仓库地址: $GITEA/$ORG/$REPO" +echo "推送状态: $?" diff --git a/suite.tgz b/suite.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5a9790c1c5d0aaf93ffb54f7fb700545d97d41f1 GIT binary patch literal 14918 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 E03Mn#&Hw-a literal 0 HcmV?d00001 diff --git a/workflow.yaml b/workflow.yaml new file mode 100644 index 0000000..91105b2 --- /dev/null +++ b/workflow.yaml @@ -0,0 +1,105 @@ +name: 永善县增减挂钩后备资源调查数据库生成 +description: 从三调及年度变更数据中提取建设用地,逐层擦除限制要素,生成增减挂钩后备资源调查数据库 +version: 1.0.0 +author: SuiteForge +tags: [增减挂钩, 建设用地, 叠加分析, 后备资源, 永善县] +category: 城乡建设用地增减挂钩 + +params: + db_2019_path: + type: string + required: true + desc: 2019年三调数据库路径,支持 .gpkg / .shp / .gdb 格式 + db_2025_path: + type: string + required: true + desc: 2025年度国土变更调查数据库路径,支持 .gpkg / .shp / .gdb 格式 + restriction_dir: + type: string + required: true + desc: 限制要素目录路径,目录下存放所有限制要素矢量文件(.shp / .gpkg),包括:永久基本农田、生态保护红线、村庄建设边界、国土空间规划重大项目、城镇开发边界、矿权、生态修复项目、历史遗留矿山图斑、长江经济带项目、增减挂钩已验收项目 + output_path: + type: string + required: true + desc: 输出 GeoPackage 文件路径,如 /home/user/output/ys_houbei.gpkg + layer_2019: + type: string + required: false + desc: 2019年数据库图层名(GeoPackage 格式时指定),不填自动检测 + layer_2025: + type: string + required: false + desc: 2025年数据库图层名(GeoPackage 格式时指定),不填自动检测 + min_area: + type: number + required: false + default: 70 + desc: 最小图斑面积(平方米),小于此面积的将被自动删除 + orthophoto_dir: + type: string + required: false + desc: 正射影像目录(可选),用于生成影像核实清单时记录影像文件路径 + +base_image: gis-base:latest + +steps: + - id: extract_2019 + name: 提取2019年三调建设用地 + type: python + script_id: process + params: + mode: extract + db_path: $params.db_2019_path + layer: $params.layer_2019 + label: 2019年三调建设用地 + output: /tmp/output/step1_2019.gpkg + log: /tmp/output/process.log + + - id: extract_2025 + name: 提取2025年度变更建设用地 + type: python + script_id: process + params: + mode: extract + db_path: $params.db_2025_path + layer: $params.layer_2025 + label: 2025年度变更建设用地 + output: /tmp/output/step2_2025.gpkg + log: /tmp/output/process.log + + - id: overlay_intersect + name: 叠加分析取交集 + type: python + script_id: process + params: + mode: overlay + input1: /tmp/output/step1_2019.gpkg + input2: /tmp/output/step2_2025.gpkg + output: /tmp/output/step3_common.gpkg + log: /tmp/output/process.log + depends_on: [extract_2019, extract_2025] + + - id: erase_restrictions + name: 逐层擦除限制要素 + type: python + script_id: process + params: + mode: erase + input_path: /tmp/output/step3_common.gpkg + restriction_dir: $params.restriction_dir + output: /tmp/output/step4_erased.gpkg + log: /tmp/output/process.log + depends_on: [overlay_intersect] + + - id: filter_area + name: 面积过滤与输出成果 + type: python + script_id: process + params: + mode: filter + input_path: /tmp/output/step4_erased.gpkg + output: $params.output_path + log: /tmp/output/process.log + min_area: $params.min_area + orthophoto_dir: $params.orthophoto_dir + depends_on: [erase_restrictions]