尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
HarmonyOS7 逆地理编码返回空数组?先别查权限,可能是坐标系没对上
前言这篇文章主要是为了解决社区里的一个具体问题用户在地图上长按拿到经纬度后调用getAddressesFromLocation做逆地理编码结果data一直是空数组err还是undefined。问题地址在这里https://developer.huawei.com/consumer/cn/forum/topic/0208222825485900638?fid0109140870620153026。这种问题最烦的地方在于它不报错。回调正常进代码逻辑看着也没毛病但就是查不到省市区。我排查下来核心原因不是权限也不是getAddressesFromLocation不能用而是坐标系没对上。Map Kit 在中国大陆场景下拿到的通常是 GCJ02 坐标而geoLocationManager.getAddressesFromLocation需要的是 WGS84。两套坐标差个几百米服务端自然可能查不到地址。先说结论如果你也遇到getAddressesFromLocation返回空数组可以按这个顺序查确认位置权限已经声明并动态申请确认geoLocationManager.isGeocoderAvailable()返回true确认设备联网、位置开关打开尽量用真机测试如果坐标来自 Map Kit先把 GCJ02 转成 WGS84再传给getAddressesFromLocation国内展示建议加上locale: zh这几个点里最容易被忽略的就是坐标系。效果预览注意的是我写的案例中 用的是mock 数据你可以换成你自己的真实数据跑一下完整代码在文末同时我也添加了相关函数的封装两套坐标系各管各的HarmonyOS 上涉及坐标的 SDK 有两套体系来源坐标系说明Map Kit 回调地图点击、标注GCJ02国测局加密坐标中国大陆强制使用geoLocationManager逆地理编码 APIWGS84GPS 原始坐标国际通用GCJ02 是在 WGS84 基础上做了非线性加密偏移平均偏移 300-500 米。你拿 GCJ02 的坐标去调只认 WGS84 的接口等于拿着北京的地址去上海的地图上找找不到才是正常的。这就是为什么回调进了、没报错、但data是空数组——请求本身合法只是位置偏了服务端查无结果。别一上来就怀疑权限很多人看到定位相关接口没结果第一反应就是权限没给。这个方向没错但在这个问题里权限不是唯一变量。逆地理编码正常工作前至少要先过三关权限声明、运行时授权、服务可用性检查。module.json5里声明权限{module:{requestPermissions:[{name:ohos.permission.APPROXIMATELY_LOCATION,reason:$string:reason_for_location,usedScene:{abilities:[EntryAbility],when:inuse}},{name:ohos.permission.LOCATION,reason:$string:reason_for_location,usedScene:{abilities:[EntryAbility],when:inuse}}]}}页面里动态申请import{abilityAccessCtrl,common,Permissions}fromkit.AbilityKit;privateasyncrequestPermission():Promisevoid{letperms:ArrayPermissions[ohos.permission.APPROXIMATELY_LOCATION,ohos.permission.LOCATION];letcontextgetContext(this)ascommon.UIAbilityContext;letatManagerabilityAccessCtrl.createAtManager();awaitatManager.requestPermissionsFromUser(context,perms);}调逆地理编码前先检查服务是否可用if(!geoLocationManager.isGeocoderAvailable()){// 服务不可用可能是没网络、模拟器不支持、位置开关没开return;}isGeocoderAvailable()返回false的话后面调了也是白调。模拟器上这个值大概率是false逆地理编码需要联网访问后端服务真机 网络 位置开关打开才行。如果这些都没问题但data还是空数组就该把注意力放到坐标本身了。尤其是坐标来自地图点击、长按、Marker 回调这类 Map Kit 场景时坐标系问题非常常见。核心GCJ02 转 WGS84这是解决问题的关键。地图回调给的 GCJ02 坐标必须先转成 WGS84再传给getAddressesFromLocation。这里有个判断思路如果接口能正常返回 Promise、没有抛BusinessError但结果数组为空说明请求大概率发出去了只是服务端按你给的经纬度没有查到合适地址。这个时候继续改权限意义不大应该先校准坐标。转换算法functiongcj02ToWgs84(lat:number,lon:number):number[]{leta6378245.0;letee0.00669342162296594323;// 海外坐标不需要转换if(lon72.004||lon137.8347||lat0.8293||lat55.8271){return[lat,lon];}letdLattransformLat(lon-105.0,lat-35.0);letdLontransformLon(lon-105.0,lat-35.0);letradLatlat/180.0*Math.PI;letmagicMath.sin(radLat);magic1-ee*magic*magic;letsqrtMagicMath.sqrt(magic);dLat(dLat*180.0)/((a*(1-ee))/(magic*sqrtMagic)*Math.PI);dLon(dLon*180.0)/(a/sqrtMagic*Math.cos(radLat)*Math.PI);// 用原坐标减去偏移量得到 WGS84return[lat-dLat,lon-dLon];}functiontransformLat(x:number,y:number):number{letret-100.02.0*x3.0*y0.2*y*y0.1*x*y0.2*Math.sqrt(Math.abs(x));ret(20.0*Math.sin(6.0*x*Math.PI)20.0*Math.sin(2.0*x*Math.PI))*2.0/3.0;ret(20.0*Math.sin(y*Math.PI)40.0*Math.sin(y/3.0*Math.PI))*2.0/3.0;ret(160.0*Math.sin(y/12.0*Math.PI)320*Math.sin(y*Math.PI/30.0))*2.0/3.0;returnret;}functiontransformLon(x:number,y:number):number{letret300.0x2.0*y0.1*x*x0.1*x*y0.1*Math.sqrt(Math.abs(x));ret(20.0*Math.sin(6.0*x*Math.PI)20.0*Math.sin(2.0*x*Math.PI))*2.0/3.0;ret(20.0*Math.sin(x*Math.PI)40.0*Math.sin(x/3.0*Math.PI))*2.0/3.0;ret(150.0*Math.sin(x/12.0*Math.PI)300.0*Math.sin(x/30.0*Math.PI))*2.0/3.0;returnret;}这段算法做了两件事先算出 GCJ02 相对 WGS84 的偏移量dLat和dLon然后用原坐标减去偏移量还原出 WGS84 坐标。transformLat和transformLon是偏移量计算的核心用一系列三角函数模拟国测局的非线性加密。海外坐标直接跳过转换因为 GCJ02 加密只在中国大陆生效。逆地理编码调用坐标转换完调接口就简单了privatereverseGeocode(lat:number,lon:number):void{// 先转 WGS84letwgs84:number[]gcj02ToWgs84(lat,lon);letrequest:geoLocationManager.ReverseGeoCodeRequest{latitude:wgs84[0],longitude:wgs84[1],maxItems:1,locale:zh// 关键指定中文返回};geoLocationManager.getAddressesFromLocation(request).then((data:ArraygeoLocationManager.GeoAddress){if(data.length0){// 还是空检查网络和位置开关return;}letaddr:geoLocationManager.GeoAddressdata[0];letprovince:stringaddr.administrativeArea??;letcity:stringaddr.locality??;letdistrict:stringaddr.subLocality??;this.bubbleText${province}${city}${district};}).catch((error:BusinessError){// 处理错误});}有两个参数容易漏locale: zh— 不设的话某些设备默认语言可能不是中文返回的地址描述可能是英文或者直接查不到。做国内应用这个值建议写死。maxItems— 控制返回条数气泡展示只需要 1 条就够了。GeoAddress里省市区对应的字段是administrativeArea省、locality市、subLocality区。三个拼起来就是气泡里要显示的文字。Mock 数据模拟地图长按实际场景里经纬度来自地图长按回调这里用 mock 数据模拟方便在没有地图的环境里验证interfaceMockLocation{label:string;lat:number;lon:number;}constMOCK_LOCATIONS:MockLocation[][{label:北京天安门,lat:39.9098,lon:116.4044},{label:上海东方明珠,lat:31.2413,lon:121.5064},{label:广州塔,lat:23.1080,lon:113.3311},{label:成都天府广场,lat:30.6598,lon:104.0633},{label:杭州西湖,lat:30.2420,lon:120.1485},];这些坐标都是 GCJ02 的从地图 SDK 拿到的就是 GCJ02点一下就触发逆地理编码。完整源码import { geoLocationManager } from kit.LocationKit; import { BusinessError } from kit.BasicServicesKit; import { abilityAccessCtrl, common, Permissions } from kit.AbilityKit; function gcj02ToWgs84(lat: number, lon: number): number[] { let a 6378245.0; let ee 0.00669342162296594323; if (lon 72.004 || lon 137.8347 || lat 0.8293 || lat 55.8271) { return [lat, lon]; } let dLat transformLat(lon - 105.0, lat - 35.0); let dLon transformLon(lon - 105.0, lat - 35.0); let radLat lat / 180.0 * Math.PI; let magic Math.sin(radLat); magic 1 - ee * magic * magic; let sqrtMagic Math.sqrt(magic); dLat (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI); dLon (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI); return [lat - dLat, lon - dLon]; } function transformLat(x: number, y: number): number { let ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y 0.2 * Math.sqrt(Math.abs(x)); ret (20.0 * Math.sin(6.0 * x * Math.PI) 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret (20.0 * Math.sin(y * Math.PI) 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0; ret (160.0 * Math.sin(y / 12.0 * Math.PI) 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0; return ret; } function transformLon(x: number, y: number): number { let ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y 0.1 * Math.sqrt(Math.abs(x)); ret (20.0 * Math.sin(6.0 * x * Math.PI) 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret (20.0 * Math.sin(x * Math.PI) 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0; ret (150.0 * Math.sin(x / 12.0 * Math.PI) 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0; return ret; } interface MockLocation { label: string; lat: number; lon: number; } const MOCK_LOCATIONS: MockLocation[] [ { label: 北京天安门, lat: 39.9098, lon: 116.4044 }, { label: 上海东方明珠, lat: 31.2413, lon: 121.5064 }, { label: 广州塔, lat: 23.1080, lon: 113.3311 }, { label: 成都天府广场, lat: 30.6598, lon: 104.0633 }, { label: 杭州西湖, lat: 30.2420, lon: 120.1485 }, ]; Entry Component struct Qa10 { State bubbleText: string ; State selectedIndex: number 0; private async requestPermission(): Promisevoid { let perms: ArrayPermissions [ohos.permission.APPROXIMATELY_LOCATION, ohos.permission.LOCATION]; try { let context getContext(this) as common.UIAbilityContext; let atManager abilityAccessCtrl.createAtManager(); await atManager.requestPermissionsFromUser(context, perms); } catch (_e) { } } private reverseGeocode(lat: number, lon: number): void { this.bubbleText 查询中...; if (!geoLocationManager.isGeocoderAvailable()) { this.bubbleText 服务不可用; return; } let wgs84: number[] gcj02ToWgs84(lat, lon); let request: geoLocationManager.ReverseGeoCodeRequest { latitude: wgs84[0], longitude: wgs84[1], maxItems: 1, locale: zh }; try { geoLocationManager.getAddressesFromLocation(request).then((data: ArraygeoLocationManager.GeoAddress) { if (data.length 0) { this.bubbleText 未查到地址; return; } let addr: geoLocationManager.GeoAddress data[0]; let province: string addr.administrativeArea ?? ; let city: string addr.locality ?? ; let district: string addr.subLocality ?? ; this.bubbleText ${province} ${city} ${district}; }).catch((error: BusinessError) { this.bubbleText 错误: ${error.code}; }); } catch (err) { let e: BusinessError err as BusinessError; this.bubbleText 异常: ${e.code}; } } build() { Column() { Column() { ForEach(MOCK_LOCATIONS, (loc: MockLocation, index: number) { Row() { Text(loc.label) .fontSize(14) .fontColor(#CCCCCC) .layoutWeight(1) Text(${loc.lat}, ${loc.lon}) .fontSize(11) .fontColor(#888888) } .width(100%) .padding(12) .backgroundColor(this.selectedIndex index ? #2A2A4A : #1A1A2E) .borderRadius(8) .margin({ bottom: 6 }) .onClick(() { this.selectedIndex index; this.reverseGeocode(loc.lat, loc.lon); }) }, (loc: MockLocation, index: number) ${index}) } .width(100%) .margin({ bottom: 20 }) if (this.bubbleText ! ) { Column() { Text(this.bubbleText) .fontSize(16) .fontColor(#FFFFFF) .textAlign(TextAlign.Center) } .padding({ left: 20, right: 20, top: 10, bottom: 10 }) .backgroundColor(#4A90D9) .borderRadius(20) .margin({ bottom: 12 }) } Text(点击上方位置模拟地图长按选点GCJ02坐标自动转WGS84后逆地理编码) .fontSize(11) .fontColor(#666666) } .width(100%) .height(100%) .padding(16) .backgroundColor(#0D0D1A) .onAppear(() { this.requestPermission(); }) } }写在最后getAddressesFromLocation返回空数组但不报错最容易把人带偏。因为它不像权限异常那样直接抛错也不像服务不可用那样有明显信号它只是安静地给你一个空结果。这次社区问题的关键就在这里坐标来自 Map Kit逆地理编码走的是geoLocationManager两边默认使用的坐标系不一样。Map Kit 给的是 GCJ02getAddressesFromLocation要的是 WGS84中间差了几百米地址自然查不到。我建议按这个顺序处理先检查isGeocoderAvailable()再确认权限和真机网络环境最后重点看坐标系。如果坐标来自 Map Kit就先转 WGS84如果你直接用 Map Kit 自带的site.reverseGeocode它内部已经处理了坐标系转换就不用再手动转一次。这个坑不难但很隐蔽。下次看到data是空数组、err是undefined先别急着怀疑接口坏了看看经纬度是不是拿错了坐标系。
RELATED

相关推荐

开关电源PCB设计实战:从EMI根源到布局布线,打造高可靠电源板

开关电源PCB设计实战:从EMI根源到布局布线,打造高可靠电源板

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/15 13:20:29
NOXCAT 品牌全面升级,钱包正式迈向下一阶段

NOXCAT 品牌全面升级,钱包正式迈向下一阶段

从生态代币 $NOXO 到独立品牌 IP,NOXCAT 打造连接 Web3 与全球金融市场的一站式入口随着区块链生态持续发展,Web3 正在为用户带来更加丰富的数字资产、去中心化应用与链上机会。但对于许多用户而言,真正开始使用 Web3,仍然需要面对…

📅 2026/10/7 22:53:57
AI辅助App逆向:从静态分析到协议还原的工程方法论

AI辅助App逆向:从静态分析到协议还原的工程方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/5 17:01:14
MORE NEWS

更多资讯

📰

HJ212协议解析在Java中的工程实践与避坑指南

简介:这份资源面向从事环保监测系统开发的Java工程师,提供国标HJ212协议(污染源在线自动监控系统数据传输标准)的解析实现参考,帮助解决监测数据采集、编码解码与网络交互中的落地问题。压缩包共308个文件,…

📰

Redis内存管理:键过期与淘汰机制详解

1. Redis键过期与内存淘汰机制全景解读 Redis作为高性能键值数据库,其内存管理机制直接影响系统稳定性和性能表现。我在处理多个千万级QPS的Redis集群时发现,90%的线上事故都与键过期和内存淘汰配置不当有关。本文将结合生产案例,拆解Redis 4…

📰

Photoshop切图与PSD图层批量导出:多倍图命名规范与工程实践

切图的活儿看着碎,但真上手就知道它考验的是对 PSD 结构的判断力。我见过太多人接手一份设计稿,直接打开导出面板一个一个点“快速导出为 PNG”,两百多个图标点到手酸,最后还因为命名混乱、尺寸不统一被返工。用 Photoshop 做切图…

📰

肌电前端设计:高输入阻抗、右腿驱动与陷波滤波的工程实践

简介:本资源是一份面向生物医学工程、电子电路设计及嵌入式信号采集方向初学者与课程实验者的肌电放大器专项学习材料,聚焦微弱生理信号检测中的核心电路设计与仿真验证问题。内容涵盖肌电信号特性分析(50μV~2mV幅值、10&#xf…

📰

需求中止评估框架与数据驱动决策方法论

1. 需求中止的底层逻辑与价值评估 作为一名经历过上百次需求评审的产品人,我深刻理解"叫停一个需求比启动它更需要勇气"。需求中止不是失败,而是对团队资源的最大尊重。判断需求是否该中止的核心,在于建立系统化的评估框架。 1.1…

📰

水利信息化转型:智慧水务关键技术解析与应用

1. 专题背景与核心价值水利信息化建设正在经历从传统人工管理向智慧化决策的转型关键期。这个专题课程聚焦水资源管理、河湖治理、供水保障和防洪抗旱四大核心场景,正是为了解决水利行业数字化转型中的痛点问题。我在参与某流域智慧水务平台建设时深有体会&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬