高德区划数据入库面积出现负值?一文搞定修复
目录
前言
在地理信息系统(GIS)的数据处理工作中,数据的准确性是至关重要的基石。当我们将高德地图的行政区划数据导入数据库,却发现部分区域的面积计算结果竟然是负值时,这无疑是一个令人困惑且亟待解决的问题。这不仅影响了数据的可用性,更可能对后续的分析和决策产生误导。本文将深入探讨这一现象的可能成因,并提供一套系统且有效的修复方法,帮助你快速定位问题并恢复数据的准确性,确保你的地理信息系统能够稳定可靠地运行。

面对高德区划数据入库后出现的负面积问题,我们首先要冷静分析可能的原因。在地理数据的处理过程中,坐标系的不匹配、几何形状的拓扑错误、数据精度不足以及导入过程中的格式转换错误等,都可能是导致这一异常现象的罪魁祸首。例如,当数据从一种坐标系转换到另一种坐标系时,如果没有正确地进行坐标转换,就可能导致几何形状的扭曲,进而使得面积计算出现负值。又或者,在数据导入过程中,某些边界点的丢失或错误排序,也可能引发拓扑结构的混乱,最终导致面积计算的错误。因此,我们需要对这些潜在的原因进行逐一排查,从数据源的检查到导入过程的复核,每一个环节都不能放过。只有准确地找到问题的根源,才能为后续的修复工作奠定坚实的基础。
在明确了可能的原因之后,本文将为你提供一套完整的修复策略。我们将从数据的预处理开始,详细讲解如何检查和纠正坐标系的错误,确保数据在正确的地理空间框架内。接着,我们会深入探讨几何形状的修复方法,包括如何检测和修复拓扑错误,如何处理边界点的丢失或错误排序等问题。此外,我们还会分享一些实用的工具和脚本,帮助你高效地完成数据的修复工作。最后,我们将通过实际案例的演示,让你直观地看到修复过程和效果,确保你能够将这些方法应用到自己的数据处理工作中。无论你是GIS领域的资深专家,还是刚刚踏入这个领域的初学者,本文都将为你提供清晰易懂的指导,帮助你轻松解决高德区划数据入库面积为负的难题。
一、问题重现
本节将在之前的例子上重点介绍出现对行政区划的面积求解时遇到面积为负数的问题。不仅详细介绍st_area这个面积函数,也介绍在数据库中的面数据。
1、面积求解
问题重现,在之前的博文中我介绍了如何将在线地图转换为空间数据,原文地址:SpringBoot联合PostGIS实现在线地图行政区划离线存储的实践探索。按照教程执行代码,虽然不会报什么错,空间数据也能正常插入到数据库中。但是在后来的应用需求中,我们需要对这个空间面数据进行面积求解,面积求解查询SQL如下:
SELECT T.*, st_area ( T.polyline_geom ) FROM biz_region_info_bak T WHERE T.LEVEL <> 'street';
这里使用了备份表来进行查询,在Navicat中执行上述语句后查询结果如下:

从结果可以看到深圳市、宝安区、南山区都是出现了负数的情况。
2、坐标参考查询
首先我们来查一下,导入的空间数据的空间坐标参考是什么,使用以下空间函数进行查询:
SELECT T.*, st_area ( T.polyline_geom ) , st_srid(T.polyline_geom) FROM biz_region_info_bak T WHERE T.LEVEL <> 'street';
执行以后发现,这些数据的空间参考都是4326,结果如图所示:

3、WKT数据查询
看完空间参考后,大概可以猜出来,出现面积为负的情况跟空间参考应该关联不大。因此我们来看下不同的空间数据类型,比如是单个Polygon还是什么?查询WKT的函数方法如下:
SELECT T.*, st_area ( T.polyline_geom ) , st_srid(T.polyline_geom), st_asewkt(T.polyline_geom) FROM biz_region_info_bak T WHERE T.LEVEL <> 'street';
执行以上SQL以后,在输出控制台中可以看到以下结果:

貌似看起来没什么问题,似乎找不到问题了。相信看到这里,有空间数据处理经验的朋友已经有了答案。这里暂且不提前揭晓。
二、WKT数据面积为负求解
本节将具体讲解如何解决WKT数据入库后,使用PostGIS空间函数求解时出现面积为负的情况。通过实际的代码来解决这个问题,让大家掌握具体的解决办法。
1、问题定位
在说明答案之前,首先我们先去高德地图中以龙岗区为例,看看龙岗的行政区划范围,在官网网站中输入龙岗区之后,可以看到以下结果:

这张图已经很明显的看到,龙岗区是两个面。相信到这里,大家可以猜到问题的所在了。就是我们对空间数据处理的问题。在进行数据入库的时候,针对与多个面数据,没有正确的进行处理。来看下官方行政区划地图的API返回参数说明:
|
polyline |
行政区边界坐标点 |
当一个行政区范围,由完全分隔两块或者多块的地块组 成,每块地的 polyline 坐标串以 | 分隔 。 如北京 的 朝阳区 |
2、问题解决
在明确了问题之后,就可以对症下药了。接下来的问题就回归到如何针对性的解决行政区划的多面兼容性问题。原来给出的WKT生成方法是有缺陷的。在MultiPolygon的模式下,是存在bug的。因此需要我们能处理成多个面。
因此,我们可以这样处理:
用
|分隔字符串,得到多个地块的坐标串。每个地块的坐标串是简单多边形的环(闭合或不闭合)。
每个地块构建一个POLYGON,如果有多个地块,则构建MULTIPOLYGON。
我们来构建一个生成WKT方法的实例,代码如下:
/**
* -构建Polygon WKT字符串
*
* @param coordinateStr 坐标字符串,格式: -多个地块(多个polygon):地块1坐标|地块2坐标|地块3坐标
* -每个地块格式:外壳坐标 -如果有孔洞,孔洞环直接在外壳环后面,用;分隔继续
* -坐标格式:经度,纬度;经度,纬度;...
* @return WKT字符串
*/
public static String buildWktFromCoordinates(String coordinateStr) {
if (coordinateStr == null || coordinateStr.trim().isEmpty()) {
throw new IllegalArgumentException("坐标字符串不能为空");
}
// 去除首尾空格
coordinateStr = coordinateStr.trim();
// 如果字符串以|结尾,去掉最后一个|
if (coordinateStr.endsWith("|")) {
coordinateStr = coordinateStr.substring(0, coordinateStr.length() - 1);
}
// 分割多个地块(用|分隔)
String[] plotStrs = coordinateStr.split("\\|");
if (plotStrs.length == 0) {
throw new IllegalArgumentException("坐标字符串不能为空");
}
List<String> validPlots = new ArrayList<>();
for (String plotStr : plotStrs) {
if (plotStr != null && !plotStr.trim().isEmpty()) {
validPlots.add(plotStr.trim());
}
}
if (validPlots.isEmpty()) {
throw new IllegalArgumentException("坐标字符串不能为空");
}
// 如果只有一个地块,返回POLYGON
if (validPlots.size() == 1) {
return buildSinglePolygonWkt(validPlots.get(0));
}
// 如果有多个地块,返回MULTIPOLYGON
return buildMultiPolygonWkt(validPlots);
}
篇幅有限,这里我们不列出更详细的代码,更多的实例代码可以从以下链接获取:适用高德地图行政区划字符串转Polygon的WKT格式的源码。为了验证数据的正确性,这里我们提供一个空间数据的验证方法,代码如下:
/**
* -验证坐标字符串格式
*/
public static boolean validateCoordinateString(String coordinateStr) {
if (coordinateStr == null || coordinateStr.trim().isEmpty()) {
return false;
}
try {
String str = coordinateStr.trim();
// 如果字符串以|结尾,去掉最后一个|
if (str.endsWith("|")) {
str = str.substring(0, str.length() - 1);
}
// 分割地块(用|分隔)
String[] plotStrs = str.split("\\|");
for (String plotStr : plotStrs) {
if (plotStr == null || plotStr.trim().isEmpty()) {
return false;
}
// 分割每个地块的环(用#分隔,向后兼容)
String[] ringStrs;
if (plotStr.contains("#")) {
ringStrs = plotStr.split("#");
} else {
ringStrs = new String[] { plotStr };
}
for (String ringStr : ringStrs) {
if (ringStr == null || ringStr.trim().isEmpty()) {
continue;
}
String[] pointStrs = ringStr.split(";");
if (pointStrs.length < 3) {
return false;
}
int validPoints = 0;
for (String pointStr : pointStrs) {
if (pointStr == null || pointStr.trim().isEmpty()) {
continue;
}
String[] xy = pointStr.split(",");
if (xy.length != 2) {
return false;
}
try {
Double.parseDouble(xy[0].trim());
Double.parseDouble(xy[1].trim());
validPoints++;
} catch (NumberFormatException e) {
return false;
}
}
if (validPoints < 3) {
return false;
}
}
}
return true;
} catch (Exception e) {
return false;
}
}
测试代码如下:
/**
* - 使用示例
*/
public static void main(String[] args) {
System.out.println("=== 行政区多边形WKT构建器(只有|分隔符)===");
System.out.println();
// 示例1:单个地块(单个polygon),无孔洞
String plot1 = "1.0,1.0;5.0,1.0;5.0,5.0;1.0,5.0";
System.out.println("示例1 - 单个地块(无孔洞):");
System.out.println("验证结果: " + validateCoordinateString(plot1));
System.out.println("WKT结果: " + buildWktFromCoordinates(plot1));
System.out.println();
// 示例2:两个完全分隔的地块(两个polygon),无孔洞
String plots2 = "1.0,1.0;5.0,1.0;5.0,5.0;1.0,5.0|10.0,10.0;15.0,10.0;15.0,15.0;10.0,15.0";
System.out.println("示例2 - 两个分隔的地块(无孔洞):");
System.out.println("验证结果: " + validateCoordinateString(plots2));
System.out.println("WKT结果: " + buildWktFromCoordinates(plots2));
System.out.println();
// 示例3:三个完全分隔的地块(三个polygon)
String plots3 = "0.0,0.0;20.0,0.0;20.0,20.0;0.0,20.0|" + "30.0,30.0;40.0,30.0;40.0,40.0;30.0,40.0|"
+ "50.0,50.0;60.0,50.0;60.0,60.0;50.0,60.0";
System.out.println("示例3 - 三个分隔的地块:");
System.out.println("验证结果: " + validateCoordinateString(plots3));
System.out.println("WKT结果: " + buildWktFromCoordinates(plots3));
System.out.println();
// 示例4:你提供的坐标字符串(单个地块)
String plot4 = "120.903347,38.381733;120.900577,38.382;120.899033,38.383545;120.898553,38.386476;120.899193,38.389566;120.900578,38.39127;120.902656,38.392495;120.905799,38.392974;120.909102,38.393667;120.910328,38.394733;120.911021,38.396491;120.911554,38.398302;120.913793,38.400594;120.915712,38.401926;120.918058,38.402247;120.920777,38.40145;120.92435,38.398842;120.927977,38.395382;120.932031,38.391231;120.933472,38.38745;120.933685,38.384839;120.933044,38.383187;120.931924,38.38228;120.929736,38.382438;120.924722,38.384937;120.922162,38.385254;120.920082,38.384241;120.917949,38.382588;120.916296,38.382321;120.912778,38.383811;120.910434,38.383864;120.903347,38.381733";
System.out.println("示例4 - 你提供的坐标字符串(单个地块):");
System.out.println("验证结果: " + validateCoordinateString(plot4));
System.out.println("WKT结果: " + buildWktFromCoordinates(plot4));
System.out.println();
// 示例5:多个地块,每个地块都是你提供的格式
String plots5 = plot4 + "|" + plot4 + "|" + plot4;
System.out.println("示例5 - 三个地块(每个都是你提供的格式):");
System.out.println("验证结果: " + validateCoordinateString(plots5));
System.out.println("WKT结果: " + buildWktFromCoordinates(plots5));
System.out.println();
// 示例6:向后兼容,支持#分隔外壳和孔洞
String plot6 = "0.0,0.0;10.0,0.0;10.0,10.0;0.0,10.0#2.0,2.0;8.0,2.0;8.0,8.0;2.0,8.0";
System.out.println("示例6 - 单个地块带孔洞(向后兼容#分隔符):");
System.out.println("验证结果: " + validateCoordinateString(plot6));
System.out.println("WKT结果: " + buildWktFromCoordinates(plot6));
System.out.println();
// 示例7:多个地块,有的有孔洞(向后兼容)
String plots7 = "0.0,0.0;10.0,0.0;10.0,10.0;0.0,10.0#2.0,2.0;8.0,2.0;8.0,8.0;2.0,8.0|"
+ "20.0,20.0;30.0,20.0;30.0,30.0;20.0,30.0";
System.out.println("示例7 - 两个地块,第一个有孔洞(向后兼容#分隔符):");
System.out.println("验证结果: " + validateCoordinateString(plots7));
System.out.println("WKT结果: " + buildWktFromCoordinates(plots7));
System.out.println();
// 测试自动闭合
String notClosed = "1.0,1.0;5.0,1.0;5.0,5.0"; // 只有3个点,未闭合
System.out.println("自动闭合测试(单个地块未闭合):");
System.out.println(buildWktFromCoordinates(notClosed));
System.out.println();
// 测试错误格式
try {
String invalid = "1.0,1.0;5.0,1.0|"; // 空的地块
System.out.println("错误格式测试:");
System.out.println(buildWktFromCoordinates(invalid));
} catch (Exception e) {
System.out.println("正确捕获错误: " + e.getMessage());
}
}
完成后看到以下输出,表示运行成功:

3、成果验证
为了验证是否将行政区划的数据正常的导入,我们将原来的数据删除后,重新导入新的行政区划数据,数据插入方法示例代码如下:
String polylineGeomWkt = PolygonWktBuilder.buildWktFromCoordinates(amapPolyline);
完整的调整代码如下:

最后我们在PostGIS数据库中进行查询验证:
SELECT T.*, st_area ( T.polyline_geom ) , st_area(T.polyline_geom :: geography) area2, st_area(T.polyline_geom :: geography) / 1000000.0 area3, st_asgeojson(t.polyline_geom) FROM biz_region_info T WHERE T.LEVEL <> 'street';

可以看到面积为负的问题解决了,面积求解已经为正数。同时可以看到,比如深圳市、宝安区、南山区的空间类型都变成了:MultiPolygon,一切正常。到此问题解决。
三、总结
以上就是本文的主要内容,针对使用Java对高德地图等在线地图进行行政区划范围获取面积时出现负数的问题,本文将为你提供一套完整的修复策略。我们将从数据的预处理开始,详细讲解如何检查和纠正坐标系的错误,确保数据在正确的地理空间框架内。接着,我们会深入探讨几何形状的修复方法,包括如何检测和修复拓扑错误,如何处理边界点的丢失或错误排序等问题。最后,我们将通过实际案例的演示,让你直观地看到修复过程和效果,确保你能够将这些方法应用到自己的数据处理工作中。
- 点赞
- 收藏
- 关注作者
评论(0)