经纬度逆编码方法与性能优化

Posted 嗡汤圆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经纬度逆编码方法与性能优化相关的知识,希望对你有一定的参考价值。

1.需求与既有方案

本文需求来源于基础地理信息服务,需要将用户设备采集的经纬度信息转换为所处的行政区域。以往的方式是有以下几种

  • 1)直接使用高德web api 中的逆编码方式获取
    此方法问题在于高度依赖网络调用,难以实现高并发、快速查询。且高德web api有调用次数限制,无法做到无限使用。

  • 2)从网络上搜集行政区域数据,并且查询其中心坐标,计算用户传入坐标与中心坐标最近的区域作为逆编码结果
    此方法的问题在于,数据陈旧,无法有效更新,其次,以中心点坐标距离作为判断依据,对于行政区不规则边界而言,精度不高,极易出现误判情况。

  • 3)从高德获取行政区域 & 区域边界数据,通过算法计算逐一计算坐标点位于哪个行政区域的边界内
    此方法的好处在于结果准确。问题在于计算性能受边界坐标点数量影响。高德共有3200+行政区域,所有边界点数量约百万(约640万)级别,若每次用户请求均实时比较计算,明显无法满足性能需求。

2. 方案

该方法依赖于高德行政区域完整数据源,可以通过web api统一一次性拉取后,定期更新基础数据即可。

2.1 拉取基本信息

数据来源接口: 高德行政区域查询

STEP 1: 行政区信息

在这里传入keywords=&subdistrict=3&extensions=base 即可获取国家 -> 省 -> 市 -> 区 四级基本行政区的树结构。

STEP 2: 边界信息

遍历行政区树节点,在这里传入keywords=${行政区adcode}&subdistrict=0&extensions=all 即可获取该节点的边界坐标列表(即 polyline )。
⚠️有的行政区域由多个独立的地块组成,polyline之间用分割。坐标和坐标间用; 分割。

2.2 逆编码实现

借助java原生工具包 java.awt.geom 即可。
该实现方案即为上节内容的3)方案

/* Point2D.Double 是java.awt.geom.Point2D.Double , 记录x,y 二维坐标,此处直接填入经纬度 */

public boolean pointInPolygon(double latitude, double longitude, List<Point2D.Double> polyline) {
	// 生成 generalPath
	java.awt.geom.GeneralPath generalPath = new java.awt.geom.GeneralPath();
	Point2D.Double firstPoint = polyline.get(0);
	generalPath.moveTo(first.x, first.y);
	polyline.remove(0);
	for(Point2D.Double p: polyline) {
    	generalPath.lineTo(p.x, p.y);
    }
    // 闭合
    generalPath.lineTo(first.x, first.y);

	return generalPath.contains(new Point2D.Double(longitude, latitude));
}

3. 方案优化

参考博客: 利用GeoHash实现逆地理编码(经纬度坐标转换行政区划)

在即有数据(即:行政区基础数据、行政区边界数据)的基础上,添加geohash索引数据。方法如下:

3.1 列举全国范围内精度为N的geohash列表

如上先手动粗筛精度为2的geohash列表。大约30个左右。

每个geohash再细分精度为3的子geohash(32个子单元格),细分规则在参考博文中有。

每个精度3的geohash还能细分精度为4的子geohash,以此类推。

3.2 对于每个geohash计算其是否和行政区相关

对于每个geohash可以获取其左上角经纬度、经纬度差。以此可以计算geohash区域和行政区域是否发生交叉或覆盖。
// 依赖 ch.hsr.geohash

// geohash区域与行政区覆盖、相交均为相关
// 一个行政区可能有多个地块(即generalPath)
public boolean geoHashIntersectOrContainsPolygon(String geoHashString, java.awt.geom.GeneralPath generalPath) {
	GeoHash geoHash = GeoHash.fromGeoHashString(geoHashString);
	BoundingBox bb = geoHash.getBoundingBox();
	// 左上角点由最小经度和最大纬度组成
	double maxLat = bb.getMaxLat();
	double minLon = bb.getMinLon();
	
	double latDiff = bb.getMaxLat() - bb.getMinLat();
	double lonDiff = bb.getMaxLon() - bb.getMinLon();
	return generalPath.contains(minLon, maxLat, lonDiff, latDiff) || generalPath.intersects(minLon, maxLat, lonDiff, latDiff);
}

3.3 建立索引

根据3.1方法,生成精度为2,3,4,5… 的geohash列表,再根据3.2方法逐一搜索相关行政区编码并作为索引数据存储即可。

3.4 索引使用

一个geohash就是一个索引,它可以直接定位到相关行政区域,从而避免全局搜索。

假设用户输入经纬度 121.23123123, 23.232123, 该经纬度可以生成一个指定精度的geohash,如精度为5时:tzmtu。
搜索索引表中精度为5,索引值为tzmtu的相关行政区列表
1)若仅存1个,则该行政区就是经纬度的逆编码结果。
2)若多于1个,则逐一根据行政区边界数据判断输入的经纬度属于哪个行政区(参考2. 方案 的内容)

索引的作用在于尽可能将待判断的行政区数量降为最小,因此索引精度越高,覆盖的行政区数量越少,但是索引数据量越多。

索引精度数量
231
331 * 32 = 992
431 * 32^2 = 31744
531 * 32^3 = 10151808
631 * 32^4 = 32505856

当精度为6时就有超过32亿条索引记录,因此检索方案上就应该重新考虑。

以上是关于经纬度逆编码方法与性能优化的主要内容,如果未能解决你的问题,请参考以下文章

经纬度逆编码方法与性能优化

经纬度逆编码方法与性能优化

逆地理编码-离线版-part1

使用定位,逆地理编码,经纬度《=转=》地址信息逆地理编码,地址《=转=》经纬度,贼方便!!!!

基于矢量数据的逆地理编码功能实现

iOS定位的使用:地理/逆地理编码/判断目标经纬度是否在大陆