HarmonyOS 7 Reverse Geocoding Empty Result? Fix GCJ02 to WGS84 Conversion
This article solves a HarmonyOS 7 issue where getAddressesFromLocation returns an empty array without errors, revealing the root cause is a coordinate system mismatch: Map Kit provides GCJ02 coordinates while the geocoding API requires WGS84, and provides a conversion algorithm and complete implementation.
Problem: Reverse Geocoding Returns Empty Array Without Errors
Developers using HarmonyOS 7 encounter a silent failure: calling geoLocationManager.getAddressesFromLocation after a map long-press returns an empty data array and err is undefined. The callback executes normally, permissions appear correct, yet no address is resolved.
Troubleshooting Checklist
Confirm location permissions are declared in module.json5 and requested at runtime ( ohos.permission.APPROXIMATELY_LOCATION and ohos.permission.LOCATION).
Verify geoLocationManager.isGeocoderAvailable() returns true.
Ensure device is online, location switch is on, and test on a real device (emulator often returns false).
Critical: If coordinates come from Map Kit, convert GCJ02 to WGS84 before passing to getAddressesFromLocation.
For domestic apps, set locale: 'zh' in the request.
Two Coordinate Systems on HarmonyOS
Map Kit callbacks (map click, marker) — GCJ02 — Chinese encrypted coordinate system, mandatory in mainland China.
geoLocationManager reverse geocoding API — WGS84 — GPS raw coordinates, international standard.
GCJ02 applies a non-linear offset of 300–500 meters over WGS84. Feeding GCJ02 coordinates into a WGS84-only service shifts the query location by hundreds of meters, causing the backend to find no matching address — hence the empty result despite a valid request.
Permission Setup
Declare in 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" }
}
]
}
}Request at runtime:
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
private async requestPermission(): Promise<void> {
let perms: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'];
let context = getContext(this) as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtManager();
await atManager.requestPermissionsFromUser(context, perms);
}Service Availability Check
if (!geoLocationManager.isGeocoderAvailable()) {
// Service unavailable: no network, emulator unsupported, location switch off
return;
}On emulators isGeocoderAvailable() typically returns false; real device with network and location enabled is required.
Core Solution: GCJ02 to WGS84 Conversion
The conversion algorithm computes the offset ( dLat, dLon) using the Chinese encryption parameters and subtracts it from the original coordinates. Coordinates outside mainland China (longitude < 72.004 or > 137.8347, latitude < 0.8293 or > 55.8271) are returned unchanged.
function gcj02ToWgs84(lat: number, lon: number): number[] {
let a = 6378245.0;
let ee = 0.00669342162296594323;
// Overseas coordinates need no conversion
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);
// Subtract offset to obtain WGS84
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;
} transformLatand transformLon implement the non-linear encryption simulation using trigonometric series.
Reverse Geocoding Call
private reverseGeocode(lat: number, lon: number): void {
// Convert to WGS84 first
let wgs84: number[] = gcj02ToWgs84(lat, lon);
let request: geoLocationManager.ReverseGeoCodeRequest = {
latitude: wgs84[0],
longitude: wgs84[1],
maxItems: 1,
locale: 'zh' // Essential for Chinese address results
};
geoLocationManager.getAddressesFromLocation(request).then((data: Array<geoLocationManager.GeoAddress>) => {
if (data.length === 0) {
// Still empty? Check network and location switch
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) => {
// Handle error
});
}Key parameters: locale: 'zh' — forces Chinese address output; omitting it may yield English or no result. maxItems: 1 — limits results to one, sufficient for a map bubble. GeoAddress fields: administrativeArea (province), locality (city), subLocality (district).
Mock Data for Testing
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 },
];These coordinates are GCJ02 (as returned by Map Kit). Clicking a list item triggers the conversion and reverse geocoding flow.
Complete Source Code
The full component includes imports, conversion functions, mock data, permission request, reverse geocoding method, and a UI with a selectable list and result bubble. The component uses @Entry, @Component, @State, ForEach, Row, Column, Text, and event handlers. (See the article for the complete listing.)
Conclusion
The silent empty-array symptom misleads developers into checking permissions first. The real culprit is the coordinate system mismatch: Map Kit delivers GCJ02, while getAddressesFromLocation expects WGS84. Always verify isGeocoderAvailable(), permissions, and network, then convert coordinates if they originate from Map Kit. Alternatively, use Map Kit's built-in site.reverseGeocode which handles the conversion internally.
}
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
