尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

uni-app 定位从高德地图改天地图方案

uni-app 定位从高德地图改天地图方案 uni-app 定位从高德切回天地图geocoder.js 完整封装与参数坑记录摘要定位方案从高德切回了天地图顺手把天地图 Web API 的地理编码封装整理了一遍。这篇记录geocoder.js的完整实现——正向/反向地理编码、请求封装、key 配置以及调试时踩过的几个参数坑ds/postStr的格式、autoConvert二次转换、needPoi生效条件。代码可直接抄替换成自己的tk就能用。一、前言为什么切回天地图之前定位链路走的是高德方案跑了一阵子之后我们决定切回天地图。原因不复杂国内业务场景下天地图对 GCJ02 坐标系原生支持客户端不用再做 WGS84→GCJ02 转换免费额度 7000 次/天对我们这种巡检类 App 的用量来说足够了认证只需要一个tk参数不用像之前那样维护多个 key 还要轮换。切回来后重新封装了geocoder.js把正向、反向地理编码和请求层都归拢到一个文件里。代码本身不复杂麻烦的是参数格式——有几个地方官方文档写得比较含糊当初对着文档调了挺久这里一并记下来下次谁接手不用再踩一遍。二、完整封装代码/** * 天地图 Web API 地理编码工具 * * API 地址https://api.tianditu.gov.cn * 免费额度7000 次/天个人开发者 * * 坐标系说明 * - 天地图 API 默认接受 GCJ02 坐标通过 autoConvert 参数可自动将 WGS84 转为 GCJ02 * - reverseGeocode 默认 autoConverttrue自动 WGS84→GCJ02 * - 直接传入 GCJ02 时设置 autoConvertfalse 避免二次转换 */// 换成你自己在天地图控制台申请的 keyconstTIANDITU_KEYYOUR_TIANDITU_KEY;constTIANDITU_BASE_URLhttps://api.tianditu.gov.cn;/** * 请求封装 */functiontiandituRequest(endpoint,params){returnnewPromise((resolve,reject){constqueryObject.keys(params).map((k)${encodeURIComponent(k)}${encodeURIComponent(params[k])}).join();consturl${TIANDITU_BASE_URL}${endpoint}?${query};uni.request({url,timeout:10000,success:(res){if(res.statusCode200res.data){resolve(res.data);}else{reject(newError(天地图 API 请求失败: statusCode${res.statusCode}));}},fail:(err){reject(newError(天地图 API 网络请求失败:${err.errMsg}));},});});}/** * 正向地理编码地址 → 坐标GCJ02 * param {string} address - 中文地址字符串 * returns {Promise{lng: number, lat: number, address: string, province: string, city: string, district: string}} */exportfunctiongeocode(address){if(!address||typeofaddress!string||address.trim().length0){returnPromise.reject(newError(地址参数不能为空));}if(address.length200){returnPromise.reject(newError(地址长度不能超过 200 个字符));}returntiandituRequest(/geocoder,{// 注意ds 必须是 JSON 字符串不是对象ds:JSON.stringify({keyWord:address.trim()}),tk:TIANDITU_KEY,}).then((data){if(data.status0data.location){const{lon,lat}data.location;return{lng:parseFloat(lon),lat:parseFloat(lat),address:data.formatted_address||address,province:data.addressComponent?.province||,city:data.addressComponent?.city||,district:data.addressComponent?.district||,};}thrownewError(天地图正向地理编码失败:${data.msg||未知错误});});}/** * 反向地理编码坐标 → 详细地址 * param {number} lng - 经度GCJ02或配合 autoConverttrue 传 WGS84 * param {number} lat - 纬度 * param {Object} options * param {boolean} [options.autoConverttrue] - true: 自动将 WGS84 转为 GCJ02 * param {boolean} [options.needPOIfalse] - true: 返回周边 POI 信息 * returns {Promise{formattedAddress: string, address: string, province: string, city: string, district: string, township: string, street: string, streetNumber: string, poiName: string}} */exportfunctionreverseGeocode(lng,lat,options{}){const{autoConverttrue,needPOIfalse}options;if(typeoflng!number||typeoflat!number){returnPromise.reject(newError(经纬度参数必须为数字));}if(lng-180||lng180||lat-90||lat90){returnPromise.reject(newError(经纬度超出有效范围));}constparams{// postStr 固定为 {lon, lat, ver: 1} 结构postStr:JSON.stringify({lon:lng,lat:lat,ver:1}),type:geocode,tk:TIANDITU_KEY,};// 坑needPoi 只在 autoConvertfalse 时才生效if(!autoConvertneedPOI){params.needPoi1;}returntiandituRequest(/geocoder,params).then((data){if(data.status0data.result){constresultdata.result;constaddressresult.formatted_address||;// 地址提取优先级formatted_address 省市区县乡镇道路详细地址letformattedAddressaddress;if(!formattedAddressresult.addressComponent){constacresult.addressComponent;formattedAddress[ac.province,ac.city,ac.district,ac.township,ac.street,ac.streetNumber,].filter((p)p).join();}constcomponentresult.addressComponent||{};return{formattedAddress:formattedAddress||自动定位,address:formattedAddress||自动定位,province:component.province||,city:component.city||,district:component.district||,township:component.township||,street:component.street||,streetNumber:component.streetNumber||,poiName:result.poisresult.pois.length0?result.pois[0].name:,};}thrownewError(天地图反向地理编码失败:${data.msg||未知错误});});}三、几个值得展开的点3.1 请求封装为什么单独拆一层tiandituRequest做的事不多拼 query、发请求、分类错误。单独拆出来的好处是正向和反向两个函数只管业务参数网络异常、HTTP 状态码这些杂事都在这层统一处理。后面如果换别的 API也只需要改这一层。超时设了 10 秒。定位场景对响应时间敏感10 秒没回来基本可以断定有问题了直接走 fail 分支让上层降级处理。3.2 正向地理编码ds参数必须传 JSON 字符串正向地理编码走的是/geocoder接口关键词放在ds参数里。这里有个坑ds的值必须是 JSON 字符串直接传对象是不行的// 正确JSON 字符串ds:JSON.stringify({keyWord:address.trim()})// 错误直接传对象接口不认ds:{keyWord:address.trim()}另外status 0才是成功——天地图这里和很多别的服务商相反别顺手写成1。参数校验也别忘了地址为空、超 200 字符直接reject别让明显非法的请求打到服务端浪费配额。3.3 反向地理编码autoConvert是最大的坑反向地理编码的关键参数是autoConvert默认true意思是把 WGS84 坐标自动转成 GCJ02 再解析。问题来了如果你的uni.getLocation用的是type: gcj02国内 App 基本都是拿到的坐标已经是 GCJ02再开autoConvert: true就会二次转换坐标直接偏出几条街。所以传 WGS84 坐标 →autoConvert: true默认不用传传 GCJ02 坐标 → 必须显式传autoConvert: false// uni.getLocation 用 gcj02 拿到坐标后一定要关掉 autoConvertreverseGeocode(lng,lat,{autoConvert:false})3.4needPoi的生效条件想拿周边 POI 的话注意needPoi只在autoConvertfalse时才会被带上。这是调试时发现的行为官方文档没有明说。所以上面代码里写成了if(!autoConvertneedPOI){params.needPoi1;}传 GCJ02 坐标 要 POI 的场景两个参数一起传{ autoConvert: false, needPOI: true }就行。3.5 地址提取优先级反向解析返回的result里优先用formatted_address现成字段没有的话再拿addressComponent按省-市-区县-乡镇-街道-门牌号顺序拼。全空就兜底自动定位保证上层拿到的永远是字符串不会出现undefined再二次判断。四、坑位速查坑说明ds参数格式正向 geocode 必须传 JSON 字符串不能传对象postStr参数格式反向 geocode 固定{lon, lat, ver: 1}结构status 0天地图的成功状态码是0别写成1autoConvert二次转换传 GCJ02 坐标时记得关掉否则坐标会偏needPoi生效条件只在autoConvertfalse时生效7000 次/天配额免费额度有限上线前算一下调用量参数校验放前面能省不少无效请求结语切回天地图之后这套封装在项目里跑到现在没出过问题。如果你也在 uni-app 里用天地图 Web API 做地理编码直接把上面的代码拿走换一下TIANDITU_KEY就能用。有别的坑或者更好的写法欢迎评论区交流。说明文中天地图 Key 已替换为YOUR_TIANDITU_KEY占位符实际使用时请替换为自己在天地图控制台申请的key值。
返回列表