城市用地分类标准解析:从.doc到数据字典的GIS与控规校验指南
2026/9/19 13:46:46 网站建设 项目流程

简介:新版城市用地分类及规划建设用地标准是城乡规划与用地管理的基础性文件,适用于城市和县人民政府所在地镇的总体规划与控制性详细规划编制、用地统计和用地管理工作。这份doc文档为单文件资源,约211KB,集中呈现标准条文,涵盖城乡用地分类、城市建设用地分类、人口规模统计、人均用地指标及城市建设用地规模与指标等核心内容。读者可据此快速查阅居住用地、公共管理与公共服务用地、商业服务业设施用地、工业用地、物流仓储用地、交通设施用地、公用设施用地、绿地等八类用地的定义与归类方法,并理解人均居住用地、人均公共管理与公共服务用地、人均交通设施用地等指标的计算口径。文档适合城市规划编制人员、用地管理工作者及城乡规划专业学生作为查询和研读参考,现有79人学习使用,便于方案编制、备案审查与教学参考。

1. 一份 .doc 里的新版标准,凭什么让 GIS 表、控规和数据库都围着它转

拿到“新版城市用地分类及规划建设用地标准.doc”,很多人习惯把它归档就完事。但真正做规划信息化的人清楚,这份文档里的每一个分类代码和比例区间,都会变成用地数据库里的字段约束、GIS 图层的属性域、控规说明书的平衡表公式。代码差一位,面积统计就串到别的类别;比例记错一格,审批系统就敢把不符合强制性要求的方案放进去。无论是自然资源和规划局的数据库管理员,还是做用地数据分析的工程师,都得先把这套分类和指标拆开揉碎。这篇就顺着“读文档→抽代码→建映射→写校验”的顺序,把它做成一份能直接跑的数据字典。

2. 先从新版城市用地分类与规划建设用地标准里,把代码分层和指标口径这两条主线揪出来

2.1 分类代码的“大类-中类-小类”三级结构,决定字段长度和层级归属

新版城市用地分类标准通常按“大类、中类、小类”三个层次组织,大类用一个英文大写字母表示,中类在大写字母后加一位数字,小类再加一位数字。以城市建设用地为例,常见的大类有:R 居住用地、A 公共管理与公共服务用地、B 商业服务业设施用地、M 工业用地、W 物流仓储用地、S 道路与交通设施用地、U 公用设施用地、G 绿地与广场用地。注意,这份标准还会出现 H 和 E 开头的大类,前者多用于城乡用地分类里的建设用地,后者是非建设用地,搞混了会把水域和林地都算进建设用地。

层级代码示例名称示例存储建议
大类R居住用地varchar(3),level=1
中类R2二类居住用地varchar(3),level=2
小类R21住宅用地varchar(3),level=3

在把标准落成数据表时,我一般会把代码统一存成字符串而不是数字。原因很简单:R2 不是 2,B11 也不是 11,GIS 属性表里一旦用数值型字段,前导字母就没了。中类和小类代码统一用三位字符串存,大类代码可以靠长度区分,或者单独加一个 level 字段,1 表示大类、2 表示中类、3 表示小类。判断层级不一定非要靠 code 长度,因为大类 R 和 R1 长度不同,但最稳妥的方式还是从标准文档的标题缩进里判断。

拿文档里的段落举例,我常用的识别脚本长这样:

import re lines = [ "R 居住用地", " R1 一类居住用地", " R11 住宅用地", " R12 服务设施用地", ] for line in lines: # 先去掉前导空格和全角空格 text = line.strip().replace("\u3000", "") m = re.match(r"^([A-Z])(\d{0,2})\s+(\S.*)$", text) if m: letter, digits, name = m.groups() code = letter + digits level = 1 + len(digits) print(code, level, name)

这里正则把“一个字母开头、后面跟着零到两个数字、再跟名称”的文字抽出来。level 的计算规则是:没有任何数字就是大类,一个数字是中类,两个数字是小类。对于从 Word 里复制出来的段落,这个正则能覆盖大多数分类条目。但你要留心文档里还有“表 2.1 城市建设用地分类和代码”这样的表格,表格内容用 paragraph 拿不到,后面第三章会专门处理。

在实际建模时,我还会给代码表加一个 is_valid 标志,用于区分标准表里的正式代码和附录里的“规划可参考代码”。这个标志能帮助后续校验时忽略一些实验性代码,避免把还不是正式标准的代码写进审批规则。

2.2 规划建设用地标准里的指标,不是参考值而是强制校验阈值

文档里除了分类代码,另一块硬内容是规划建设用地标准。常见做法是把“人均建设用地面积”“住宅用地占城市建设用地的比例”“绿地与广场用地比例”等做成阈值表。新版标准对城市建设用地的结构有明确区间要求,不同城市规模可能对应不同档次,例如“规划人均城市建设用地面积”会被分成多个档次,每个档次还分现状和规划两个口径。

指标名称代码口径是否强制性备注
居住用地比例R 类之和新版标准按城市规模给区间
公共管理与公共服务用地比例A 类之和与旧版 C 类对照看
工业用地比例M 类之和大城市与中小城市区间可能不同
道路与交通设施用地比例S 类之和不含铁路/公路区域设施
绿地与广场用地比例G 类之和应扣除生产绿地

注意,我不在表格里写死具体数,是因为新版标准在不同省市的实施细则里会有微调,而且规划报批阶段的“强制性内容”以最新发文为准。你在落地规则引擎时,最好把阈值单独存一张 indicator_standard 表,不要写死在存储过程里。否则标准更新时,你得去翻每一个 CHECK 约束。

指标口径里的“现状”和“规划”两套数也经常被混用。现状建设用地面积来自现状调查,规划建设用地面积来自用地布局方案。校验方案时只能用规划口径,拿现状比例套规划标准会误报。这一章读文档时的任务,是把代码层级和阈值口径先确立下来。代码层级的直接产出是一个 land_use_code 清单,指标口径的产出是一个 indicator_standard 阈值表。后边的所有转换和校验,都以这两份结构化数据为基准。

3. 把新版城市用地分类标准 .doc 转成 CSV/SQL:解析步骤和避坑点

3.1 先用 LibreOffice 把 .doc 批量转 .docx,避免在服务器上依赖 Office

拿到手的文档是 .doc 老格式,Python 的 python-docx 不支持,win32com 又只能在 Windows 且装了 Word 的机器上用。我一般在 Linux 服务器上走 LibreOffice headless 模式,先把 .doc 批量转成 .docx,再用 python-docx 解析。转换命令是这样:

soffice --headless --convert-to docx --outdir ./converted "/data/inputs/新版城市用地分类及规划建设用地标准.doc"

参数说明:--headless让 LibreOffice 不弹界面;--convert-to docx指定输出格式;--outdir指定输出目录;最后一个参数是源文件路径。转出来的 docx 会和原文件放在同一个目录,文件名相同,后缀改成 .docx。注意,转换前要保证运行 LibreOffice 的系统用户有权限读写源目录,否则 soffice 会静默失败。加一个文件存在性检查会更稳。

if [ -s "./converted/新版城市用地分类及规划建设用地标准.docx" ]; then echo "converted"; else echo "failed"; fi

如果服务器没有 LibreOffice,也可以先在本机 Word 里另存为 docx 再上传,但批量定时更新时用命令行最省事。文档里带大量图片和表格时,转换耗时会长一些,但单文档一般几十秒内能完成。

3.2 用 python-docx 同时读取段落和表格,别漏了表里的分类代码

转成 docx 之后,我的解析策略是分三条线:第一条读 paragraphs 拿正文段落里的分类代码;第二条读 document.tables 拿“分类和代码”表里的小类;第三条读页眉页脚,把文档标题和发布机构留作元数据。python-docx 里 tables 不是 paragraphs 的一部分,所以常见踩坑是只遍历了 paragraph 就以为拿全了代码,遇到表格里的小类全没有。

import docx, re doc = docx.Document("converted/新版城市用地分类及规划建设用地标准.docx") records = [] # 从正文段落抽取 for para in doc.paragraphs: text = para.text.strip().replace("\u3000", "") m = re.match(r"^([A-Z]\d{0,2})\s+(.+)$", text) if m: records.append((m.group(1), m.group(2).strip(), "paragraph")) # 从所有表格抽取:第一列往往是代码,第二列是名称 for table in doc.tables: for row in table.rows: cells = [cell.text.strip() for cell in row.cells] if len(cells) >= 2: code = cells[0] name = cells[1] if re.match(r"^[A-Z]\d{0,2}$", code) and name: records.append((code, name, "table"))

这段代码里,正文正则用^([A-Z]\d{0,2})\s+(.+)$,可以同时匹配 R、R1、R21 这类代码。再看表格:row.cells会把每一列都取出来,通常第一列是代码,第二列是名称。但很多标准表格的表头在第一行,比如“代码 名称 说明”,所以循环里要先判断 code 是不是“代码”这个词,是就直接跳过。另外,合并单元格会导致 cells 重复,需要自己用dict.fromkeys(cells)去重后再判断长度。

抽取出来的 records 只是内存列表,下一步写入 PostgreSQL:

CREATE TABLE land_use_code ( code varchar(3) PRIMARY KEY, name varchar(100) NOT NULL, level smallint NOT NULL, source text );

level 按上一章规则从代码长度推导,source 用来区分“正文段落”和“表格”。这样以后追溯某条代码是来自正文还是表格,不必重新翻文档。

3.3 处理“说明”和“注”里藏着的例外条款,别让注释变成脏数据

标准文档里常见“注:面积小于……的用地可并入……”或“混合用地按主导功能归类”这类说明。它们不是分类代码,但会影响代码归类逻辑。在生成 SQL 时,我会把注释单独放到 land_use_note 表,与代码表通过 code 关联。比如:

CREATE TABLE land_use_note ( id serial PRIMARY KEY, code varchar(3) REFERENCES land_use_code(code), note text );

解析时如果遇到以“注”“备注”开头的段落,先拿出来,不要直接丢掉。实际问题里,很多规划人员就是看了正文,忽略表格下面的脚注,导致停车场用地被错分到“交通场站用地”还是“社会停车场用地”之间来回摇摆。新版标准里的注释往往能解决这种边界问题。

注意:标准文档里的“注”内容,特别是“混合用地按主导功能归类”“面积小于5000平方米的……可并入……”这类表述,必须保留到 land_use_note 表,后续做自动归类时优先读它。

4. 用地分类标准落到 GIS 和控规审核时的三个实操点:映射、闭合、比例校验

4.1 新旧代码映射表:迁移旧数据前先做一次代码体检

规划信息化最头疼的是历史数据。旧标准里的“C2 商业金融业用地”在新版里可能被拆成 B11、B21 等;旧标准里的“M3 三类工业用地”也可能因为环保要求被重新归类。直接改原数据会污染历史分析,常见做法是新建一张 code_mapping 表,只存转换关系,不覆盖原表。

旧代码旧名称新代码新名称映射类型
C2商业金融业用地B11零售商业用地拆分
C3文化娱乐用地A2文化设施用地改名
G12街头绿地G2防护绿地合并

注意:映射类型分成“一对一改名”“一对多拆分”“多对一合并”“无法自动映射”四种。前三种可以直接用 SQL 更新,最后一种要人工审核。你在执行 UPDATE 之前,先统计每种类型的记录数,防止把“无法映射”的记录一股脑更新成 NULL。

UPDATE land_parcel p SET land_code = cm.new_code, land_code_note = cm.mapping_type FROM code_mapping cm WHERE p.land_code = cm.old_code AND cm.mapping_type IN ('改名', '拆分', '合并');

这段 SQL 的核心是 FROM 子句关联映射表,只更新可自动映射的记录。执行前先做备份表CREATE TABLE land_parcel_bak AS SELECT * FROM land_parcel;。注意,涉及拆分的旧代码,面积也要按地块边界重新切分,不能直接把面积原样挂到新代码上,否则汇总时会和 GIS 地块边界对不上。

映射表必须保留旧代码,不能直接把 land_code 更新后丢失旧值,否则以后做历史回溯没法查。常见做法是加一个 valid_from/valid_to 版本区间,这样同一个地块在不同年份能对应不同的标准版本。

4.2 用地平衡表的闭合校验和比例区间校验,一条 SQL 就能做粗查

控规图则里一般会有一张“城市建设用地平衡表”,里面列出各类用地面积和占比。新版标准对这些占比有强制性区间,所以我要做两道校验:第一道,所有小类面积之和必须等于建设用地总面积;第二道,按大类汇总的面积占比必须在阈值区间里。

WITH parcel_area AS ( SELECT left(land_code, 1) AS category, sum(area) AS area FROM land_parcel WHERE land_code ~ '^[A-Z][0-9]*$' GROUP BY left(land_code, 1) ), total AS ( SELECT sum(area) AS total_area FROM parcel_area ) SELECT category, area, round(area * 100.0 / total_area, 2) AS ratio_percent FROM parcel_area, total ORDER BY category;

这里left(land_code, 1)把 R21 截成 R,再用category关联指标表。area * 100.0 / total_area算出占比,round 保留两位。如果 total_area 为 0,SQL 会除零,所以在执行前要先查非建设用地记录。常见坑是有些 GIS 图层的面积字段单位是平方米,有些是公顷,比例校验之前必须统一成同一个单位。

4.3 把“绿地与广场用地”这类组合大类单独校验

有些标准类别不是单一代码,比如“绿地与广场用地”在代码体系里是 G 大类,但要拆开看“公园绿地”“防护绿地”“广场用地”三个小类的比例。我在做校验函数时,会先把代码字典和指标字典加载成内存 map,再为组合指标写专门的聚合逻辑,这个比在 SQL 里处理更直观。

indicator = {"G": (10, 15), "R": (25, 40)} area_by_category = {} for code, area in parcels: category = code[0] area_by_category[category] = area_by_category.get(category, 0) + area for cat, (low, high) in indicator.items(): ratio = area_by_category.get(cat, 0) / total_area * 100 print(f"{cat}: {ratio:.2f}% -> {'PASS' if low <= ratio <= high else 'FAIL'}")

这个片段的核心是把“标准文档里的阈值”变成 Python dict,然后拿实际面积占比去比。参数 low/high 就来自第二章建好的 indicator_standard 表。一旦标准更新,只改表里的数据,不改这段代码。注意,这里的 category 是code[0],如果 land_code 里有小写字母或者带空格,需要先统一code.strip().upper()

5. 收尾技巧:把新版城市用地分类标准 .doc 生成一份 JSON 数据字典,让前后端共用

你如果只是自己分析,导出 CSV 就够了。但一旦做信息系统,前端下拉框、后端校验、GIS 符号化需要同一份代码表,我一般会把刚才的 land_use_code 表 dump 成一个 JSON 文件,放到后端服务的静态配置目录,前端通过接口拉取,后端直接用同一个文件做校验。流程是:

import json, psycopg2 conn = psycopg2.connect("dbname=planning user=gis") cur = conn.cursor() cur.execute("SELECT code, name, level, parent_code FROM land_use_code ORDER BY code") rows = cur.fetchall() data = [{"code": r[0], "name": r[1], "level": r[2], "parent": r[3]} for r in rows] with open("land_use_code.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)

这个 JSON 同时服务于三个场景:前端表单里的“用地代码”下拉选项由它生成;后端接口在接收用地面积时用校验代码是否存在;ArcGIS/QGIS 的符号化也可以根据 JSON 生成 style 文件里的字段值。关键是不要在后端重新手写一份代码表,那样会出“前端是新标准、后端是旧标准”的分裂。

验证 JSON 是否完整,我常用一个极简脚本:检查所有小类代码的父级中类和大类是否都存在,以及是否存在孤儿代码。

codes = {d["code"]: d for d in data} for code, d in codes.items(): if d["level"] == 3 and d["parent"] not in codes: print("orphan:", code, d["name"])

这里 parent 字段在导入时根据代码前缀生成。比如 R21 的 parent 是 R2,R2 的 parent 是 R。生成 parent 的规则是:parent = code[:-1] if len(code) > 1 else None。注意,小类代码可能存在“直接挂在大类下”的特殊情况,标准里说明为“其他用地”时,parent 就是大类,不能生硬地按长度截断。所以跑完上面这段验证脚本,看到孤儿代码先查源文档,不要改脚本掩盖问题。数据字典和后端校验函数共用这一份 JSON,后续标准修订时只需要重新跑一遍“doc → CSV → JSON”,所有依赖方自动跟着变。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询