反向地理编码(Reverse Geocoding)是将经纬度等坐标信息转换为人类可读的地址(如城市名、街道名等)的过程。实现反向地理编码有多种办法,可以调用百度、高德、谷歌等服务商提供的第三方API,也可自行去解析地图离线数据实现。如果需求不是要得到精确地址(例如只要得到省、市名),那么采用后者无疑是最优的。
下载省、市边界数据
点击打开:https://datav.aliyun.com/portal/school/atlas/area_selector,在页面右侧,就可以下载省或市的边界数据了:
或者终端里边运行:
# 市级粒度数据 wget -O china_full_city.geo.json https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json
最后下载来的 china_full_city.geo.json
数据结构大概长这样:
interface FeatureCollection { features: { // 各个市级行政区域的轮廓数据 geometry: { coordinates: [float, float][][]; type: 'MultiPolygon'; }; properties: { name: string; parent: { areacode: int; }; // …此处省略其他的一些属性 }; type: 'Feature'; }[]; type: 'FeatureCollection'; }
它是遵循 GeoJSON
的。所谓“GeoJSON”,就是一种用于编码地理数据的开源格式,专门用于描述几何对象(点、线、多边形等)这类数据的一种格式规范,被广泛应用于地理信息系统(GIS)、地图服务和Web应用。
有了全国所有市的轮廓数据,我们就知道全国每个市在地图上的板块范围。这样,通过遍历每个市的多边形区域,使用turf.js中几何的算法,判断待查询的GPS坐标是否在多边形的范围内,即可得出所在城市。
使用 Turf.js 来检索GeoJSON文件
在 china_full_city.geo.json
同级目录下新建 getCityName.ts
文件,编写TypeScript代码:
import { FeatureCollection, Feature, MultiPolygon } from 'https://esm.sh/v135/@types/geojson@7946.0.14/index.d.ts'; // # 首先是根据这个json文件的结构,可以写出它的类型`XFeatureCollection`: // properties的属性 type XGeoJsonProperties = { acroutes: number[], adcode: number, center: number[], centroid: number[], childrenNum: 0, level: string, name: string, // 市级行政区名称 parent: {adcode: number}, // 市级行政区所在的省级行政区编码 subFeatureIndex: number, } type XFeatureCollection = FeatureCollection<MultiPolygon, XGeoJsonProperties>; // # 然后遍历features列表的各个城市,使用turf.js封装的`booleanPointInPolygon`方法, // 判断待查询经纬度的点是不是在市级行政区图块多边形之中,是:则表示坐标在该市范围之内 import chinaProvincesGeoJSON from "./china_full_city.geo.json" with {type: 'json'}; import { booleanPointInPolygon } from "https://esm.sh/@turf/boolean-point-in-polygon@7.1.0"; import * as turf from "https://esm.sh/@turf/turf@7.1.0"; // 定义一个待查询的国内经纬度坐标,形式是“度分秒” const [[d0, m0, s0], [d1, m1, s1]] = [[112, 34, 56], [34, 56, 12]]; // 转化为“度”的坐标形式 const [lng, lat] = [d0+m0/60+s0/3600, d1+m1/60+s1/3600]; // 定义为Point坐标类型 const pt = turf.point([lng, lat]); // 定义类型 const collections = chinaProvincesGeoJSON as XFeatureCollection; let targetFeature!: Feature<MultiPolygon, XGeoJsonProperties>; for (const f of collections.features) { // 使用turf的这个函数判断这个点是不是在(市级行政区)多边形区域内 const ret = booleanPointInPolygon(pt, turf.multiPolygon(f.geometry.coordinates)); if (ret) { targetFeature = f; break; } } console.log(targetFeature ? targetFeature.properties.name : '(超出查询范围)');
然后终端运行:
# 返回:焦作市 deno run -A getCityName.ts
如果想要知道城市所在的省级行政区名,可以通过查询父级行政区域码 targetFeature.properties.parent.adcode
得知:
const provinceAdcodeNameMapping: {[k: number|string]: string} = { "110000":"北京市","120000":"天津市","130000":"河北省","140000":"山西省","150000":"内蒙古自治区", "210000":"辽宁省","220000":"吉林省","230000":"黑龙江省", "310000":"上海市","320000":"江苏省","330000":"浙江省","340000":"安徽省","350000":"福建省","360000":"江西省","370000":"山东省", "410000":"河南省","420000":"湖北省","430000":"湖南省","440000":"广东省","450000":"广西壮族自治区","460000":"海南省", "500000":"重庆市","510000":"四川省","520000":"贵州省","530000":"云南省","540000":"西藏自治区", "610000":"陕西省","620000":"甘肃省","630000":"青海省","640000":"宁夏回族自治区","650000":"新疆维吾尔自治区", "710000":"台湾省", "810000":"香港特别行政区","820000":"澳门特别行政区", "100000_JD":"" }; // 返回:河南省 console.log(targetFeature ? provinceAdcodeNameMapping[targetFeature.properties.parent.adcode] : '');
最后
这种办法适合对解析精度要求不太高的场景,如果想要解析到区和街道或者更高级别,考虑到未来数据更新和维护的复杂度,建议还是使用各大第三方平台的API比较好。