如何使用经纬度反查地理位置?

有时业务需要我们根据经纬度去查找对应的城市或者省份名称,更甚需要精确到区或村庄。通常情况下最先想到的便是各个地图服务提供商所提供的开放坐标拾取 API 了,但是使用 API 也存在问题,比如对于计算速度或延时要求较高的业务,使用 API 的毫秒级响应明显不能满足需要,另外大量使用 API 的请求也许会被做 QPS 限制。

本文主要介绍的是一种通过本地存储地图数据来根据经纬度查找地理位置信息方法。其主要是依赖 GeoJSON 格式的地图数据来实现的。

准备地图数据

想要通过经纬度查找地理位置信息,首先我们需要的就是一份地图,这里我们选择使用 GeoJSON 格式的地图文件,对于 GeoJSON 的详细介绍可以参考 Wiki

这里我们主要介绍如何获取 GeoJSON 文件

如果是比较发达或者热度比较高的地区,大概率会有专门的组织或机构能及时更新维护相关地区的 GeoJSON 数据。比如拿中国来说,你可以在 阿里云 的工具站上找到中国的 GeoJSON 文件,并且可以具体到某个省的某个市,非常方便

如果不是特别热门的地区,可能需要我们自己来生成 GeoJSON 文件,好处是大部分地区都会有机构维护 Shapefile。如果是想用国外的地图数据,可以到下面几个网站去尝试先找到 Shapefile 文件,当然如果能找到其他格式的文件比如 TopoJSON 等也是可以的。

http://www.gadm.org/country

http://www.naturalearthdata.com/downloads/

接下来我们需要根据获取到的 Shapefile 文件生成 GeoJSON 文件,在 这里 我们可以导入地图文件,便可以看到地图样式了,这里我以导入印尼的地图压缩数据为例

19fa611fa2210f75c66fe479b103647c.jpeg

3a7fdffe8e4c0f2b940e8a3f50fe3383.jpeg

注意到在导入时,压缩包内存在很多文件,实际上这些文件对应了不同的行政区划级别,即所谓的国家、省、市、村庄等等,在上面的选单中我们可以选择显示哪一级别的地图数据

80a17fd62af5160bf3e756ea11a57533.jpeg

在选择城市级别后,地图便有了变化

ddcd52db96c19754118c166e9e628d75.jpeg

整个地图的数据是很庞大的,后面我们也会讲到 GeoJSON 的查询方式,像是印尼这种千岛之国,精密的地图数据会很分散且庞大。因此在必要时我们可以对地图做简化,在右上角我们可以看到 Simplify 操作

b58e5ef94fe657c76c013dd3fcceb13f.jpeg

这里的简化提供了三种算法,上面的横轴可以设定简化的力度,我们可以根据需要手动调整简化力度,在调整时能够明显看到地图会发生简化。实测导出后地图数据大小和简化力度基本是一致的

最后导出时我们也能看到导出项是可以选择的,我们可以只选择我们需要行政级别的数据进行导出就可以了

ac070055c4d4099d4642724b0d65b1eb.jpeg

加载并使用 GeoJSON

拥有 GeoJSON 文件后我们要做的便是加载数据进行转换计算,这里简单说明一下计算方式:实际上这里的 GeoJSON 是维护了许多的多边形,大量的多边形数据拼凑成完整的地图。

而经纬度实际上代表的便是一个点,要查找经纬度所代表的的地理位置,便需要遍历所有的多边形,并判断这个点是否在多边形内,从而找到经纬度所归属的位置。这也是为什么我们之前提到要对地图数据进行简化的原因,否则大量的数据在进行遍历时是非常慢的。

判断点是否在多边形内的算法非常多,常用的是 W. Randolph Franklin 提出的 PNPoly 算法,具体算法我们不做深究,有兴趣的同学可以自行研究。

对于 Golang 来说,已经有相关的第三方库帮我们实现了这一功能,我们着重用到两个库:geojson 和 geo,分别用来读取 GeoJSON 文件和判断点是否在多边形内。由于第三方库众多,我也不再具体罗列,大家可以选择自己喜欢的,能够实现功能即可。

当然对于不想求甚解的同学,我也整合了一份工具库,可以方便的实现根据经纬度获取 GeoJSON 中所表示的地理位置信息,同时支持对多个不同的 GeoJSON 横向对比查询效率、查询精准度,具体代码放在了 Github,具体使用方法查看 test 文件即可理解。

使用 GeoHash 缓存优化查找

采用不同精度的 GeoJSON 文件,其查询效率可能在微秒级到毫秒级不等,但有的同学会说如果微秒级的查询仍不能满足要求该怎么办?

这个时候我们便需要进一步的牺牲精度,对于某些业务来讲,效率的优先级有时候比精度要高,而要实现这一方案,最优先想到的便是 GeoHash

什么是 GeoHash 呢?简单来讲,hash 我们都知道,GeoHash 实际上就是对经纬度按照一定规则进行哈希映射,从而生成具有一定精度的地理编码。在固定精度下,一个经纬度在固定长度半径的周边区域所产生的 GeoHash 编码也是相邻的。这样一来我们可以认为某一个固定大小的区域内的所有经纬度都映射到了一个 GeoHash 编码内。

了解到这里相信你也明白该怎么做了,我们只需要找到一个符合我们业务需求的精度,将所有查找过的经纬度转换成 GeoHash 编码做存储即可,后面的经纬度会先去查找已经缓存的 GeoHash 列表,如果未命中才会再去查找 GeoJSON 文件。

至于 GeoHash 的实现在网上已经有非常多的例子,开源库也是非常多,使用起来很简单,就不再做具体阐述了

实际上 GeoHash 在我们的生活中已经有了非常多的应用,如果有感兴趣的同学想更深层的了解,这里推荐两篇文章,仅供参考

https://halfrost.com/go_spatial_search/

https://www.cnblogs.com/woshimrf/p/geohash-visualization.html


1785 Words

2020-07-24 08:00 +0800