经纬度逆编码方法与性能优化
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. 方案
的内容)
索引的作用在于尽可能将待判断的行政区数量降为最小,因此索引精度越高,覆盖的行政区数量越少,但是索引数据量越多。
索引精度 | 数量 |
---|---|
2 | 31 |
3 | 31 * 32 = 992 |
4 | 31 * 32^2 = 31744 |
5 | 31 * 32^3 = 10151808 |
6 | 31 * 32^4 = 32505856 |
当精度为6时就有超过32亿条索引记录,因此检索方案上就应该重新考虑。
以上是关于经纬度逆编码方法与性能优化的主要内容,如果未能解决你的问题,请参考以下文章