(本文仅作抛砖引玉之用,如诸位有更好的观点或方法可在本博客下方留言,谢谢)
如果填写表单时国家行政区划数据并非手工填写,而是依靠级联菜单或弹窗选择,就会有一些问题,主要涉及以下三个要点:
- 原始数据的准确性问题。
- 此类数据的存储问题。
- 因行政区划每年都会变更,因此存在数据的更新问题。
首先说下数据的准确性问题。
一般编写系统需要使用到的区划要素有两个,一个是地区的名称,比如北京市、朝阳区等等,另一个是地区码,比如北京市是110000,朝阳区是110105。
数据的准确性包括三点:
- 地区名称的准确性,如北京市下的通县现已更名为通州区,不宜再使用老名称。
- 地区名称与地区码对应关系的准确性,这个对应关系是一一对应的。
- 地区因各种原因存在增设、撤销等问题,有些老的地区码已不再使用,有些新的地区码是新启用的。
一般来说,获取准确的数据需要从官方渠道获取。借助搜索引擎,我发现了两组可说得上权威的
1)国家标准:中华人民共和国行政区划代码,共发布四次
- GB 2260-1991(1992年1月1日实施)
- GB/T 2260-1999(1999年7月1日实施,国家质量技术监督局发布)
- GB/T 2260-2002(2002年10月1日实施,国家质量监督检验检疫局发布)
- GB/T 2260-2007(2008年2月1日实施,国家质量监督检验检疫局发布)
2)中国国家统计局发布的县及县以上行政区划代码(链接地址:http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/)发布频率为每年发布一次,最近三次发布为:
- 2016年8月9日发布,截止2015年9月30日
- 2015年4月15日发布,截止2014年10月31日
- 2014年1月17日发布,截止2013年8月31日
因为国标更新速度很慢,最近的版本仅为2008年发布的GB/T 2260-2007,因此建议采用国家统计局发布的行政区划代码。
再说下行政区划数据的存储问题。数据存储有两种方式,存储地区名称和存储地区码。
一般来说如果不涉及使用地区数据进行匹配、汇总等问题,可选择直接存储地区名称而不是地区码。因为这样可以做到无需数据字典转换,提升程序效率。
不过很多时候因为涉及到统计学需求等情况,我们还是需要存储地区码。
在GB/T 2260-2007中列出了2007版国标与2002版国标的差异。经过分析我发现地区名称和地区码的变化情况主要有以下几类:
- 撤市设区、撤县建区。如撤销唐山市丰南市(130282),设立唐山市丰南区(130207)
- 区划合并。如撤销唐山市丰润县(130221)、新区(130206),设立唐山市丰润区(130208)
- 区划更名。如沈阳市新城子区(210113)更名为沈阳市沈北新区(210113)
- 区划设立。如新设哈尔滨市松北区(230109)
- 区划撤销。如厦门市鼓浪屿区(350202)、开元区(350204)被撤销,并入思明区(305023)
- 区划析设。如厦门市同安区(350212)析设翔安区(350213)
- 地区转市。如撤销平凉地区(622700)设立平凉市(620800),平凉地区下各县都被重新分配地区码
可分析出以下地区码分配原则:
- 区划更名:A更名为B,仅名称变动,地区码不变
- 区划撤销:A并入B,A被撤销,B的地区码不变
- 区划合并:区划A、B合并成C,为C分配新的地区码
- 地区转市:A地区变为A市,为A市分配新的地区码,同时为A地区下各县分配新的地区码
- 区划新设、析设等:为新的区域分配新的地区码
可以看到,除区划更名外,地区名称与地区码的一一对应关系是不会因区划的修改而改变的。
所以说,如果在数据库中要保存地区码,可采用以下策略:
- 如无更精确的数据源,建议以国家统计局历年数据为准。
- 每年国家统计局更新数据后,采集数据,经评估后对原数据字典进行替换。每两次行政区划代码表的异同比较,可编写脚本查找,比肉眼看要有效率得多。评估无问题后再行升级。
- 因要保证既有数据有效,所以要建立两套数据字典:一套为最新区划数据字典,用于填写表单时选择区划用,每次只能选择最新的行政区划;·一套为全部区划数据字典,其下对应关系只增不减,每次更新时仍保留被撤销县市地区的区划码,用于系统查看历史信息时不致出现因无相应对应关系导致数据无法正确显示的问题。
- 对此类数据的修改,建议由用户自行改动,自行保证这些数据的准确性。如一定要系统运营方更新,则需评估每个区划的具体情况,再具体区划具体修改。
下面是一个DEMO,读取从国家统计局网站获取的的最新县及县以上行政区划代码(截止2015年9月30日)
http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201608/t20160809_1386477.html
将区划代码粘贴到txt文件中,保存成UNICODE格式。
添加命令行参数“-r C:\\Users\\Tsybius\\Desktop\\20160930_area_config.txt”(txt文件名以实际为准),运行下面这段Java代码:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
/**
* 行政区划转换工具
* @author Tsybius2014
* @date 2016年10月10日
* @time 下午3:53:03
* @remark
*/
public class AreaConfigTool {
public static void main(String[] args) {
String behaviorType = BehaviorTypeEnum.BEHAVIOR_TYPE_DEFAULT.getCode();
if (args.length == 0 || args[0].equals("-h") || args[0].equals("--help")) {
behaviorType = BehaviorTypeEnum.BEHAVIOR_TYPE_HELP.getCode();
} else if (args[0].equals("-r") || args[0].equals("--read")) {
behaviorType = BehaviorTypeEnum.BEHAVIOR_TYPE_READ.getCode();
} else {
behaviorType = BehaviorTypeEnum.BEHAVIOR_TYPE_HELP.getCode();
}
if (behaviorType.equals(BehaviorTypeEnum.BEHAVIOR_TYPE_READ.getCode())) {
if (args.length < 2 || !(new File(args[1]).exists())) {
System.out.println("未找到要读取的文件!");
} else {
System.out.println("读取文件:" + args[1]);
System.out.println("------------------");
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(args[1]), "Unicode"));
String lastProvince = ""; //记录上一个读取到的省
String lastCity = ""; //记录上一个读取到的市
String description = "";
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (line.trim().equals("")) {
continue;
} else {
String[] areaInfo = line.split("\\s{2,}");
if (areaInfo.length >= 2) {
String areaCode = areaInfo[0];
String areaName = areaInfo[1].replace(" ", ""); //替换全角空格
if (areaCode.length() == 6) {
//System.out.println(areaCode + "|" + areaName);
if (areaCode.substring(2, 6).equals("0000")) {
//省、直辖市
lastProvince = areaName;
description = MessageFormat.format("[{0}] {1}", areaCode, areaName);
System.out.println(description);
} else if (areaCode.substring(4, 6).equals("00")) {
//地区、地级市
lastCity = areaName;
description = MessageFormat.format("[{0}] {1}, {2}", areaCode, lastProvince, areaName);
System.out.println(description);
} else {
//县、县级市
description = MessageFormat.format("[{0}] {1}, {2}, {3}", areaCode, lastProvince, lastCity, areaName);
System.out.println(description);
}
}
}
}
}
reader.close();
} catch (Exception ex) {
StringWriter stringWriter = new StringWriter(); //打印错误信息
PrintWriter printWriter = new PrintWriter(stringWriter);
ex.printStackTrace(printWriter);
System.out.println(stringWriter.toString());
}
}
} else if (behaviorType.equals(BehaviorTypeEnum.BEHAVIOR_TYPE_HELP.getCode())) {
System.out.println("显示帮助");
System.out.println("AreaConfigTool -h|--help 显示帮助");
System.out.println("AreaConfigTool -r|--read 读取数据");
}
System.out.println("程序执行完毕");
}
}
枚举类BehaviorTypeEnum代码如下:
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public enum BehaviorTypeEnum {
BEHAVIOR_TYPE_DEFAULT("0", "空"),
BEHAVIOR_TYPE_HELP("1", "查看帮助"),
BEHAVIOR_TYPE_READ("2", "读取脚本");
private String code;
private String name;
private BehaviorTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public static String getName(String code) {
for (BehaviorTypeEnum each : values()) {
if (each.getCode().equals(code)) {
return each.getName();
}
}
return null;
}
public static Map<String, String> toMap() {
Map<String, String> enumMap = new LinkedHashMap<String, String>();
for (BehaviorTypeEnum each : values()) {
enumMap.put(each.getCode(), each.getName());
}
return enumMap;
}
public static List<BehaviorTypeEnum> toList() {
List<BehaviorTypeEnum> enumList = new java.util.ArrayList<BehaviorTypeEnum>();
for (BehaviorTypeEnum each : values()) {
enumList.add(each);
}
return enumList;
}
}
程序执行结果如下:
在写读取区划数据代码时有几个需要注意的地方:
1、原网页上的数据保存到记事本时,编码要选择Unicode编码,与代码中创建InputStreamReader时使用的编码保持一致
2、原网页中使用的空格为全角空格“ ”,所以直接使用Split函数无法实现完美的分隔,我用了replace函数专门处理一下
3、本段代码是可通过添加命令行参数进行扩展,目前只实现了一个功能,即读取区划名称和地区码并打印
END