跳到主要内容 基于 Java 标准库读取 CSV 实现天地图 POI 分类导入 PostGIS 数据库 | 极客日志
Java java 算法
基于 Java 标准库读取 CSV 实现天地图 POI 分类导入 PostGIS 数据库 天地图 POI 分类层级扁平且编码规范,本文演示使用 Java 标准库解析 CSV 文件,通过 LinkedHashMap 去重构建树形结构,结合 Mybatis 批量写入 PostGIS 数据库。对比高德百度分类差异,提供地理信息系统数据入库方案。
前言
在之前的文章中,曾经对高德地图和百度地图的 POI 分类以及使用 PostGIS 数据库来进行管理的模式进行了详细的介绍。虽然天地图在移动端的使用市场没有前两者的份额多,但是作为官方的标准,天地图还是拥有自己得天独厚的优势,除了本身最具权威的地理数据,同时还有承载着官方标准的执行。因此天地图的 POI 数据也是非常重要的,可以作为日常数据分析和处理的一个可靠信息来源。不同的平台对 POI 的分级分类都有所不同,不同的厂商对于 POI 分类时,它的大类和小类的定义一定是不一样的。天地图 POI 的分类存在非常大的差异,层次结构也是不一样的。
天地图的 POI 分类从大类来说就跟高德和百度不一样。单从一级大类的数量来说,百度拥有 32 个,而高德只区分了 25 个,天地图居然有 58 个,在数量上天地图是比较多的。另外从层级上来说,高德通常只区分了 3 级分类,而百度竟然有 5 级分类,然而天地图在层级上非常简单,只有 2 级展示,从扁平的角度来说,天地图的扁平化做的不错。
本文重点讲解天地图 POI 分类与高德 POI 分类以及百度 POI 分类存在什么不一样的地方,同时结合代码深入讲解使用 Java 标准库来读取天地图的 POI 分类并进行数据导入到 PostGIS 空间数据库中。
一、天地图 POI 分类简介
本节将首先重点介绍天地图地图的 POI 分类信息,在之前的博客中我们设计了用于 POI 管理的物理表,这里可以继续用来存储天地图对应的 POI 分类信息。然后使用数据库脚本的方法对 POI 分类信息进行录入管理。对于天地图而言,其 POI 的分类较多,但是层级简单,因此这一节我们来详细的解读一下天地图的 POI 分类,让大家对分类信息有进一步的了解,为下一步对数据层级组装和批量解析入库打下牢固的基础。
1、数据表格
与之前介绍的内容一样,大家可以从天地图的地图开放平台中获取其最新的 POI 分类的 CSV 表格(是的,你没有看错,官方提供的确实是 CSV 而不是 Excel,如果需要使用 Excel 也可以将 CSV 转一下格式),这里我将从官网下载的类型截取一部分给大家参考。下载链接传送门:分类编码表 。
参数值 参数说明 参数类型 是否必备 备注(值域) keyWord 搜索的关键字 String 必填 无 specify 指定行政区的国标码(行政区划编码表)严格按照行政区划编码表中的(名称,gb 码) String 必填 下载行政区划编码表。9 位国标码,如:北京:156110000 或北京。 queryType 服务查询类型参数 String 必填 12:行政区划区域搜索服务。 start 返回结果起始位(用于分页和缓存)默认 0 String 必填 0-300,表示返回结果的起始位置。 count 返回的结果数量(用于分页和缓存) String 必填 1-300,返回结果的条数。 dataTypes 数据分类(分类编码表) String 可选 下载分类编码表,参数可以分类名称或分类编码。多个分类用","隔开 (英文逗号)。 show 返回 poi 结果信息类别 String 可选
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
取值为 1,则返回基本 poi 信息;取值为 2,则返回详细 poi 信息
需要原始 CSV 表格的,可以去网站上下载。打开下载后的表格数据如下:
从上面这张图可以看出天地图的 POI 分类确实分的比较简单。同时也能看到一个比较明显的区别,与百度的 POI 分类不一样的是,天地图有编码的概念。同样的,基于天地图地图的 POI 检索可以从返回接口中看到其对应的 POI 分类值为:
{ "eaddress" : "" , "address" : "荣滨南路东段 11 号附 28 附近" , "city" : "" , "provinceCode" : "156500000" , "cityCode" : "" , "county" : "荣昌区" , "typeName" : "副食专卖店" , "source" : "0" , "typeCode" : "130207" , "lonlat" : "105.590250,29.416870" , "countyCode" : "156500153" , "ename" : "" , "province" : "重庆市" , "phone" : "18716232760" , "poiType" : "101" , "name" : "李氏卤鹅" , "hotPointID" : "50166082CE55CFF5" }
其中 typeCode 对应 poi 分类的类别编码,而 typeName 则表示具体的类别名称。
2、分类结构 在了解了天地图的 POI 分类之后,下面我们基于之前设计的数据库物理表和分类信息构建树形的信息。因此需要对其分类采取细致的分类管理。在进行树形层次构建时,我们根据分类名称来进行统一管理:
这个结构是天地图 POI 分类管理的基础,也是后面的数据程序解析的基础。我们将使用编码来进行数据的解析及入库。在 CSV 中,很大的大类和种类都是重复的,因此需要在入库时将类别进行去重分类,最终构建一棵完整的 POI 分类树。
二、从 CSV 导入到 PG 数据库 本节将详细介绍在 Java 中使用标准库实现从 CSV 中解析到存储至 PostgreGIS 中,主要包含两个方面,第一个是如何使用 Java 标准库来进行 CSV 解析。第二个方面是如何基于 Mybatis 实现程序的批量入库。关于数据处理流程与高德和百度 POI 入库的流程一致,基本分为三个步骤:第一步是批量读取数据源,第二步是将数据源解析出 POI 分类数据,最后将分类好的数据导入到 PG 数据库中。
1、CSV 解析流程 首先还是对天地图下载的 CSV 格式的 POI 分类进行解析,在进行 POI 的分类进行构建时尤其重要,为了防止各层级在构建时出现重复的情况,这里采用 LinkedHashMap 集合来进行重复判断,在存储集合对象时,将分类编码作为 map 的 key,而具体分类对象作为 value。在天地图的 POI 分类编码中,可以发现将编码的前四位和后两位可以拆开,后两位如果是 00 的一般都是父类节点,通过我们的观察可以看到在后续的对象去重判断时,key 就是重复的标记。为了实现从 CSV 格式的文件中读取数据,这里首先介绍一下 CSV 的解析流程。在正式讲解数据导入时,需要明确一个信息,就是在 CSV 数据的每一行中,只要遇到 - 这个字符就基本表示再往后读一个单元格,当前行的数据读取任务结束。
第一步:一些不要的辅助信息,比如需要定义需要处理的文件本地路径地址。代码如下:
@Autowired private IPoiCategoryService poiCateGoryService;
private static final String TDT_POI_CSV_FILE = "C:/Users/Administrator/Desktop/天地图及相关信息/天地图 Type-数据分类(分类编码表).csv" ;
private static final String TRIGGER_CHAR = "-" ;
为了能够正确的解析 CSV 数据,这里需要额外定义两个参数,以此保证数据的准确解析。关于 CSV 的解析实现代码如下:
private static String[] parseCsvLine(String line) {
java.util.List<String> fields = new java .util.ArrayList<>();
StringBuilder field = new StringBuilder ();
boolean inQuotes = false ;
for (char c : line.toCharArray()) {
if (c == '"' ) {
inQuotes = !inQuotes;
} else if (c == ',' && !inQuotes) {
fields.add(field.toString());
field.setLength(0 );
} else {
field.append(c);
}
}
fields.add(field.toString());
return fields.toArray(new String [0 ]);
}
private static String cleanCellValue (String value) {
value = value.trim();
if (value.startsWith("\"" ) && value.endsWith("\"" )) {
value = value.substring(1 , value.length() - 1 );
}
return value.replace("\"\"" , "\"" );
}
第三步:CSV 数据解析,实现最基本的数据解析读取和基础配置之后,下面就可以来实现对 CSV 数据的具体读取,这里采取的是直接的 Java 标准库解析的方法。实例代码如下:
private static LinkedHashMap<String,PoiCategory> csv2Map () {
Charset charset = Charset.forName("GBK" );
LinkedHashMap<String,PoiCategory> amapPoiTypeMap = new LinkedHashMap <String, PoiCategory>();
try (BufferedReader br = new BufferedReader (new InputStreamReader (new FileInputStream (TDT_POI_CSV_FILE), charset))) {
int rowNum = 0 ;
while ((line = br.readLine()) != null ) {
rowNum++;
String[] cells = parseCsvLine(line);
boolean foundTrigger = false ;
List<String> poiCategories = new ArrayList <String>();
for (int i = 0 ; i < cells.length; i++) {
String cellValue = cleanCellValue(cells[i]);
if (cellValue.contains(TRIGGER_CHAR)) {
foundTrigger = true ;
if (i + 1 < cells.length) {
String nextValue = cleanCellValue(cells[i + 1 ]);
String poiCategoryCode = nextValue;
} else {
System.out.println("→ 终止单元格:(空)" );
}
break ;
}else {
poiCategories.add(cellValue);
}
}
if (!foundTrigger) {
System.out.println("行 " + rowNum + ": 未找到触发字符" );
}
if (foundTrigger) {
String prefix_head = poiCategoryCode.substring(0 ,4 );
String prefix_tail = poiCategoryCode.substring(4 );
if (prefix_tail.equalsIgnoreCase("00" )) {
String levelFirst = poiCategoryCode;
if (!amapPoiTypeMap.containsKey(levelFirst)) {
PoiCategory category = new PoiCategory (IdWorker.getId(),1944421516292726785L ,"0,100,1944421516292726785" ,String.join("/" , poiCategories),StringUtils.EMPTY,levelFirst);
amapPoiTypeMap.put(levelFirst, category);
}
}else {
if (!amapPoiTypeMap.containsKey(poiCategoryCode)) {
String _parentKey = prefix_head + "00" ;
PoiCategory parentCategory = amapPoiTypeMap.get(_parentKey);
String ancestors = parentCategory.getAncestors() + "," + parentCategory.getPkId();
PoiCategory category = new PoiCategory (IdWorker.getId(),parentCategory.getPkId(),ancestors,String.join("/" , poiCategories),StringUtils.EMPTY,poiCategoryCode);
amapPoiTypeMap.put(poiCategoryCode, category);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return amapPoiTypeMap;
}
2、数据转换及入库 将 CSV 数据成功的解析转换成 LinkedHashMap 之后,事情还远远没结束,接下来需要将获取的 LinkedHashMap 转换成 ArrayList。为了在查询数据的时候方便,在正式入库之前还需要增加一些额外的辅助信息,信息如下:
private static List<PoiCategory> convertMap2DataList (LinkedHashMap<String,PoiCategory> amapPoiTypeMap) {
List<PoiCategory> categoryData = new ArrayList <PoiCategory>();
Date now = DateUtils.getNowDate();
for (PoiCategory value : amapPoiTypeMap.values()) {
value.setPlatform("tianditu" );
value.setDelFlag(0 );
value.setStatus(0 );
value.setOrderNum(1 );
value.setCreateTime(now);
categoryData.add(value);
}
return categoryData;
}
到这里,我们就已经实现了如何使用 Java 标准库来解析 CSV 文件,并且将数据都读到了 LinkedHashMap 对象中,接下来就调用上述的方法来组装成一个完整的数据导入实例,核心代码如下:
@Test
public void writer2DB () {
LinkedHashMap<String,PoiCategory> map = csv2Map();
List<PoiCategory> categoryData = convertMap2DataList(map);
for (PoiCategory poiCategory : categoryData) {
System.out.println(poiCategory);
}
poiCateGoryService.batchInsertPoiCategory(categoryData);
System.out.println("finished..." );
}
3、入库成果及检索 完成以上的程序编写,并运行操作后就完成了天地图 POI 分类数据的 PostGIS 数据库导入操作,程序执行完成后,可以在控制台看到以下输出:
为了验证是否在数据库中是否也保存了这些数据,可以使用以下 SQL 语句进行查询:
select * from biz_poi_category t where platform = 'tianditu' ;
在客户端软件中执行以上 SQL 后可以看到以下结果:
三、总结 以上就是本文的主要内容,支持对天地图的 POI 分类的 CSV 入库及检索就基本完成,后续我们将深入使用 POI 信息以及如何进行相应数据的采集。需要注意的是,本文读取的文件不是 Excel 而是 CSV 格式,因此也深入讲解了如何使用 Java 标准库来解析天地图的 POI 分类文件,同时深入讲解天地图 POI 分类如何进行数据导入,如何将 CSV 数据转为 LinkedHashMap,再到 ArrayList,最后批量入库。也为各类基于 POI 分类数据的地理信息系统开发、商业智能分析以及城市规划应用等,提供从数据获取到存储利用的高效路径。