HarmonyOS 地理编码转化与逆地理编码转化开发指导

🎯 想入门鸿蒙开发又怕花冤枉钱?别错过!

现在能免费系统学 —— 从 ArkTS 面向对象核心的类和对象、继承多态,到吃透鸿蒙开发关键技能,还能冲刺鸿蒙基础 + 高级开发者证书,更惊喜的是考证成功还送好礼!快加入我的鸿蒙班,一起从入门到精通!

班级链接鸿蒙开发训练营

场景概述

使用坐标描述一个位置,非常准确,但是并不直观,面向用户表达并不友好。系统向开发者提供了以下两种转化能力:

  • 地理编码转化:将地理描述转化为具体坐标
  • 逆地理编码转化能力:将坐标转化为地理描述

其中地理编码包含多个属性来描述位置,包括国家、行政区划、街道、门牌号、地址描述等等,这样的信息更便于用户理解。

应用场景

  • 地图应用:地址搜索、位置标记、路线规划
  • 导航服务:地址输入、目的地设置、位置显示
  • 社交应用:位置分享、签到打卡、附近的人
  • 商业服务:店铺定位、配送地址、服务范围
  • 生活服务:周边搜索、地址管理、位置记录

接口说明

进行坐标和地理编码信息的相互转化,所使用的接口说明如下,详细信息参见:Location Kit。

接口名 功能描述
isGeocoderAvailable(): boolean 判断地理编码与逆地理编码服务是否可用
getAddressesFromLocation(request: ReverseGeoCodeRequest, callback: AsyncCallback<Array>): void 调用逆地理编码服务,将坐标转换为地理描述,使用callback回调异步返回结果
getAddressesFromLocationName(request: GeoCodeRequest, callback: AsyncCallback<Array>): void 调用地理编码服务,将地理描述转换为具体坐标,使用callback回调异步返回结果

开发步骤

1. 网络连接检查

说明:地理编码与逆地理编码功能需要访问后端服务,请确保设备联网,以进行信息获取。

2. 导入模块

导入geoLocationManager模块,所有与地理编码转化&逆地理编码转化能力相关的功能API,都是通过该模块提供的。

import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';

3. 服务可用性检查

调用isGeoServiceAvailable查询地理编码与逆地理编码服务是否可用,如果服务可用再继续进行步骤4。如果服务不可用,说明该设备不具备地理编码与逆地理编码能力,请勿使用相关接口。

import { geoLocationManager } from '@kit.LocationKit';

try {
    let isAvailable = geoLocationManager.isGeocoderAvailable();
    if (!isAvailable) {
        console.warn('Geocoding service is not available');
        return;
    }
    console.info('Geocoding service is available');
} catch (err) {
    console.error("errCode:" + JSON.stringify(err));
}

4. 获取转化结果

逆地理编码:坐标转地址

调用getAddressesFromLocation,把坐标转化为地理位置信息。应用可以获得与此坐标匹配的GeoAddress(地理编码地址信息)列表,应用可以根据实际使用需求,读取相应的参数数据。

let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
    "latitude": 31.12, 
    "longitude": 121.11, 
    "maxItems": 1
};

try {
    geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
        if (err) {
            console.error('getAddressesFromLocation err: ' + JSON.stringify(err));
        } else {
            console.info('getAddressesFromLocation data: ' + JSON.stringify(data));
            this.handleReverseGeocodeResult(data);
        }
    });
} catch (err) {
    console.error("errCode:" + JSON.stringify(err));
}
地理编码:地址转坐标

调用getAddressesFromLocationName把位置描述转化为坐标。

let geocodeRequest: geoLocationManager.GeoCodeRequest = {
    "description": "上海市浦东新区xx路xx号", 
    "maxItems": 1
};

try {
    geoLocationManager.getAddressesFromLocationName(geocodeRequest, (err, data) => {
        if (err) {
            console.error('getAddressesFromLocationName err: ' + JSON.stringify(err));
        } else {
            console.info('getAddressesFromLocationName data: ' + JSON.stringify(data));
            this.handleGeocodeResult(data);
        }
    });
} catch (err) {
    console.error("errCode:" + JSON.stringify(err));
}

应用可以获得与位置描述相匹配的GeoAddress(地理编码地址信息)列表,其中包含对应的坐标数据。

完整实现示例

地理编码服务类

import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class GeocodingService {
    private isServiceAvailable: boolean = false;

    constructor() {
        this.checkServiceAvailability();
    }

    private checkServiceAvailability(): void {
        try {
            this.isServiceAvailable = geoLocationManager.isGeocoderAvailable();
            console.info('Geocoding service availability:', this.isServiceAvailable);
        } catch (err) {
            console.error('Failed to check geocoding service availability:', err);
            this.isServiceAvailable = false;
        }
    }

    // 逆地理编码:坐标转地址
    async reverseGeocode(latitude: number, longitude: number, maxItems: number = 1): Promise<geoLocationManager.GeoAddress[]> {
        if (!this.isServiceAvailable) {
            throw new Error('Geocoding service is not available');
        }

        return new Promise((resolve, reject) => {
            const request: geoLocationManager.ReverseGeoCodeRequest = {
                latitude: latitude,
                longitude: longitude,
                maxItems: maxItems
            };

            try {
                geoLocationManager.getAddressesFromLocation(request, (err, data) => {
                    if (err) {
                        console.error('Reverse geocoding error:', err);
                        reject(err);
                    } else {
                        console.info('Reverse geocoding result:', data);
                        resolve(data);
                    }
                });
            } catch (error) {
                console.error('Reverse geocoding exception:', error);
                reject(error);
            }
        });
    }

    // 地理编码:地址转坐标
    async geocode(description: string, maxItems: number = 1): Promise<geoLocationManager.GeoAddress[]> {
        if (!this.isServiceAvailable) {
            throw new Error('Geocoding service is not available');
        }

        return new Promise((resolve, reject) => {
            const request: geoLocationManager.GeoCodeRequest = {
                description: description,
                maxItems: maxItems
            };

            try {
                geoLocationManager.getAddressesFromLocationName(request, (err, data) => {
                    if (err) {
                        console.error('Geocoding error:', err);
                        reject(err);
                    } else {
                        console.info('Geocoding result:', data);
                        resolve(data);
                    }
                });
            } catch (error) {
                console.error('Geocoding exception:', error);
                reject(error);
            }
        });
    }

    // 带范围的地理编码
    async geocodeWithBounds(description: string, bounds: {
        minLatitude: number,
        maxLatitude: number,
        minLongitude: number,
        maxLongitude: number
    }, maxItems: number = 1): Promise<geoLocationManager.GeoAddress[]> {
        if (!this.isServiceAvailable) {
            throw new Error('Geocoding service is not available');
        }

        return new Promise((resolve, reject) => {
            const request: geoLocationManager.GeoCodeRequest = {
                description: description,
                maxItems: maxItems,
                minLatitude: bounds.minLatitude,
                maxLatitude: bounds.maxLatitude,
                minLongitude: bounds.minLongitude,
                maxLongitude: bounds.maxLongitude
            };

            try {
                geoLocationManager.getAddressesFromLocationName(request, (err, data) => {
                    if (err) {
                        console.error('Geocoding with bounds error:', err);
                        reject(err);
                    } else {
                        console.info('Geocoding with bounds result:', data);
                        resolve(data);
                    }
                });
            } catch (error) {
                console.error('Geocoding with bounds exception:', error);
                reject(error);
            }
        });
    }
}

地址信息处理类

export class AddressProcessor {
    // 格式化地址信息
    static formatAddress(address: geoLocationManager.GeoAddress): string {
        const parts: string[] = [];
        
        if (address.countryName) {
            parts.push(address.countryName);
        }
        if (address.administrativeArea) {
            parts.push(address.administrativeArea);
        }
        if (address.subAdministrativeArea) {
            parts.push(address.subAdministrativeArea);
        }
        if (address.locality) {
            parts.push(address.locality);
        }
        if (address.subLocality) {
            parts.push(address.subLocality);
        }
        if (address.thoroughfare) {
            parts.push(address.thoroughfare);
        }
        if (address.subThoroughfare) {
            parts.push(address.subThoroughfare);
        }
        if (address.postalCode) {
            parts.push(address.postalCode);
        }

        return parts.join(', ');
    }

    // 获取详细地址信息
    static getDetailedAddress(address: geoLocationManager.GeoAddress): {
        country: string;
        province: string;
        city: string;
        district: string;
        street: string;
        fullAddress: string;
    } {
        return {
            country: address.countryName || '',
            province: address.administrativeArea || '',
            city: address.locality || '',
            district: address.subLocality || '',
            street: address.thoroughfare || '',
            fullAddress: this.formatAddress(address)
        };
    }

    // 检查地址完整性
    static isAddressComplete(address: geoLocationManager.GeoAddress): boolean {
        return !!(address.countryName && address.administrativeArea && address.locality);
    }
}

使用示例

export class GeocodingExample {
    private geocodingService: GeocodingService;

    constructor() {
        this.geocodingService = new GeocodingService();
    }

    // 示例1:坐标转地址
    async getAddressFromCoordinates(): Promise<void> {
        try {
            const addresses = await this.geocodingService.reverseGeocode(31.12, 121.11);
            
            if (addresses.length > 0) {
                const address = addresses[0];
                const formattedAddress = AddressProcessor.formatAddress(address);
                console.info('Formatted address:', formattedAddress);
                
                const detailedAddress = AddressProcessor.getDetailedAddress(address);
                console.info('Detailed address:', detailedAddress);
            }
        } catch (error) {
            console.error('Failed to get address from coordinates:', error);
        }
    }

    // 示例2:地址转坐标
    async getCoordinatesFromAddress(): Promise<void> {
        try {
            const addresses = await this.geocodingService.geocode('上海市浦东新区陆家嘴');
            
            if (addresses.length > 0) {
                const address = addresses[0];
                console.info('Coordinates:', {
                    latitude: address.latitude,
                    longitude: address.longitude
                });
            }
        } catch (error) {
            console.error('Failed to get coordinates from address:', error);
        }
    }

    // 示例3:带范围的地理编码
    async getCoordinatesWithBounds(): Promise<void> {
        try {
            // 在上海范围内搜索
            const addresses = await this.geocodingService.geocodeWithBounds(
                '南京路',
                {
                    minLatitude: 30.0,
                    maxLatitude: 32.0,
                    minLongitude: 120.0,
                    maxLongitude: 122.0
                }
            );
            
            console.info('Found addresses:', addresses.length);
            addresses.forEach((address, index) => {
                console.info(`Address ${index + 1}:`, AddressProcessor.formatAddress(address));
            });
        } catch (error) {
            console.error('Failed to get coordinates with bounds:', error);
        }
    }

    // 示例4:批量地址处理
    async batchGeocode(addresses: string[]): Promise<geoLocationManager.GeoAddress[][]> {
        const results: geoLocationManager.GeoAddress[][] = [];
        
        for (const address of addresses) {
            try {
                const result = await this.geocodingService.geocode(address);
                results.push(result);
            } catch (error) {
                console.error(`Failed to geocode address: ${address}`, error);
                results.push([]);
            }
        }
        
        return results;
    }
}

错误处理

常见错误类型

export class GeocodingErrorHandler {
    static handleGeocodingError(error: BusinessError): void {
        switch (error.code) {
            case 201:
                console.error('Geocoding service unavailable');
                this.handleServiceUnavailable();
                break;
            case 202:
                console.error('Network error');
                this.handleNetworkError();
                break;
            case 203:
                console.error('Invalid parameters');
                this.handleInvalidParameters();
                break;
            case 204:
                console.error('No results found');
                this.handleNoResults();
                break;
            default:
                console.error('Unknown geocoding error:', error);
                this.handleUnknownError(error);
        }
    }

    private static handleServiceUnavailable(): void {
        console.warn('Geocoding service is not available on this device');
    }

    private static handleNetworkError(): void {
        console.warn('Network connection required for geocoding');
    }

    private static handleInvalidParameters(): void {
        console.warn('Invalid geocoding parameters provided');
    }

    private static handleNoResults(): void {
        console.warn('No geocoding results found for the given input');
    }

    private static handleUnknownError(error: BusinessError): void {
        console.error('Unknown geocoding error occurred:', error);
    }
}

最佳实践

1. 服务可用性检查

export class GeocodingBestPractices {
    static async ensureServiceAvailable(): Promise<boolean> {
        try {
            const isAvailable = geoLocationManager.isGeocoderAvailable();
            if (!isAvailable) {
                console.warn('Geocoding service is not available');
                return false;
            }
            return true;
        } catch (error) {
            console.error('Failed to check geocoding service:', error);
            return false;
        }
    }

    static async withServiceCheck<T>(operation: () => Promise<T>): Promise<T | null> {
        if (!await this.ensureServiceAvailable()) {
            return null;
        }
        
        try {
            return await operation();
        } catch (error) {
            console.error('Geocoding operation failed:', error);
            return null;
        }
    }
}

2. 缓存机制

export class GeocodingCache {
    private static cache = new Map<string, geoLocationManager.GeoAddress[]>();
    private static readonly CACHE_DURATION = 24 * 60 * 60 * 1000; // 24小时

    static set(key: string, value: geoLocationManager.GeoAddress[]): void {
        this.cache.set(key, value);
    }

    static get(key: string): geoLocationManager.GeoAddress[] | undefined {
        return this.cache.get(key);
    }

    static clear(): void {
        this.cache.clear();
    }

    static generateKey(type: 'geocode' | 'reverse', input: string): string {
        return `${type}:${input}`;
    }
}

3. 重试机制

export class GeocodingRetry {
    static async withRetry<T>(
        operation: () => Promise<T>,
        maxRetries: number = 3,
        delay: number = 1000
    ): Promise<T> {
        let lastError: Error;
        
        for (let i = 0; i < maxRetries; i++) {
            try {
                return await operation();
            } catch (error) {
                lastError = error as Error;
                console.warn(`Geocoding attempt ${i + 1} failed:`, error);
                
                if (i < maxRetries - 1) {
                    await this.sleep(delay * Math.pow(2, i)); // 指数退避
                }
            }
        }
        
        throw lastError!;
    }

    private static sleep(ms: number): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

注意事项

  1. 网络连接:地理编码服务需要网络连接,确保设备联网
  2. 服务可用性:使用前检查服务是否可用
  3. 参数验证:确保输入的坐标和地址参数有效
  4. 结果处理:处理空结果和错误情况
  5. 性能优化:合理使用缓存和重试机制
  6. 用户隐私:注意保护用户位置隐私信息
  7. 错误处理:妥善处理各种异常情况
  8. 范围限制:对于多地重名的情况,使用范围限制提高准确性

🎯 想入门鸿蒙开发又怕花冤枉钱?别错过!

现在能免费系统学 —— 从 ArkTS 面向对象核心的类和对象、继承多态,到吃透鸿蒙开发关键技能,还能冲刺鸿蒙基础 + 高级开发者证书,更惊喜的是考证成功还送好礼!快加入我的鸿蒙班,一起从入门到精通!

班级链接鸿蒙开发训练营

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐