欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

本文基于flutter3.27.5开发

在这里插入图片描述

一、geocoding 库概述

地理编码是将地址描述转换为地理坐标(经纬度),或将地理坐标转换为地址描述的过程。在移动应用开发中,地理编码广泛应用于地址搜索、位置标注、导航规划等场景。在 Flutter for OpenHarmony 应用开发中,geocoding 是一个功能完善的地理编码插件,提供了完整的跨平台地理编码能力。

geocoding 库特点

geocoding 库基于 Flutter 平台接口实现,提供了以下核心特性:

正向地理编码:将文本地址转换为经纬度坐标,支持模糊搜索和精确匹配。

逆向地理编码:将经纬度坐标转换为详细的地址信息,包括国家、省份、城市、街道等。

多语言支持:支持设置语言环境,返回指定语言的地址信息。

服务状态检测:提供地理编码服务可用性检测,确保服务可靠性。

批量查询:支持返回多个匹配结果,提高地址解析成功率。

详细地址信息:提供完整的地址标记信息,包括行政区划、邮政编码等。

功能支持对比

功能 Android iOS OpenHarmony
地址转坐标
坐标转地址
多语言支持
服务状态检测
批量结果返回
详细地址信息

使用场景:地址搜索、位置标注、地图导航、外卖配送、打车应用、位置打卡等。


二、安装与配置

2.1 添加依赖

在项目的 pubspec.yaml 文件中添加 geocoding_ohos 依赖:

dependencies:
  geocoding_ohos:
    git:
      url: https://atomgit.com/openharmony-sig/fluttertpc_geocoding.git
      path: geocoding_ohos

然后执行以下命令获取依赖:

flutter pub get

2.2 权限配置

OpenHarmony 需要在配置文件中声明位置权限(地理编码服务需要位置权限)。在 ohos/entry/src/main/module.json5 中添加:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

ohos/entry/src/main/resources/base/element/string.json 中添加权限说明:

{
  "string": [
    {
      "name": "location_reason",
      "value": "应用需要位置权限来提供地理编码服务"
    }
  ]
}

三、核心 API 详解

3.1 locationFromAddress 方法

locationFromAddress 方法用于将地址转换为经纬度坐标。

Future<List<Location>> locationFromAddress(String address)

参数说明

address 参数是文本地址,支持模糊匹配和精确地址。

返回类型List<Location> - 位置信息列表,可能包含多个匹配结果

使用示例

List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街');
if (locations.isNotEmpty) {
  Location location = locations.first;
  print('纬度: ${location.latitude}');
  print('经度: ${location.longitude}');
}

3.2 placemarkFromCoordinates 方法

placemarkFromCoordinates 方法用于将经纬度坐标转换为地址信息。

Future<List<Placemark>> placemarkFromCoordinates(double latitude, double longitude)

参数说明

latitude 参数是纬度坐标(-90 到 90)。

longitude 参数是经度坐标(-180 到 180)。

返回类型List<Placemark> - 地址标记列表,可能包含多个匹配结果

使用示例

List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);
if (placemarks.isNotEmpty) {
  Placemark placemark = placemarks.first;
  print('国家: ${placemark.country}');
  print('城市: ${placemark.locality}');
}

3.3 setLocaleIdentifier 方法

setLocaleIdentifier 方法用于设置语言环境,控制返回地址信息的语言。

Future<void> setLocaleIdentifier(String localeIdentifier)

参数说明

localeIdentifier 参数是语言环境标识符,格式为 languageCode_countryCode

语言环境格式[languageCode]_[countryCode]

常用语言环境:

  • zh_CN:中文(中国)
  • en_US:英文(美国)
  • ja_JP:日文(日本)
  • ko_KR:韩文(韩国)
  • fr_FR:法文(法国)
  • de_DE:德文(德国)

使用示例

await _geocodingOhos.setLocaleIdentifier('zh_CN');  // 设置为中文环境
List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);

3.4 isPresent 方法

isPresent 方法用于检查地理编码服务是否可用。

Future<bool> isPresent()

返回类型bool - 服务是否可用

使用示例

bool available = await _geocodingOhos.isPresent();
if (available) {
  // 地理编码服务可用
  List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区');
} else {
  // 地理编码服务不可用
  print('地理编码服务不可用');
}

3.5 Location 类

Location 表示地理位置信息。

class Location {
  final double latitude;      // 纬度
  final double longitude;     // 经度
  final String? locality;     // 位置名称
  final String? country;      // 国家
  final String? administrativeArea;  // 行政区
  final String? subAdministrativeArea; // 子行政区
  final String? thoroughfare; // 街道
  final String? subThoroughfare; // 子街道
  final String? postalCode;   // 邮政编码
  final String? isoCountryCode; // 国家代码
}

3.6 Placemark 类

Placemark 表示地址标记信息。

class Placemark {
  final String? name;                 // 名称
  final String? street;               // 街道
  final String? isoCountryCode;       // 国家代码
  final String? country;              // 国家
  final String? postalCode;           // 邮政编码
  final String? administrativeArea;   // 行政区
  final String? subAdministrativeArea; // 子行政区
  final String? locality;             // 城市
  final String? subLocality;          // 子城市
  final String? thoroughfare;         // 大道
  final String? subThoroughfare;      // 子大道
}

四、OpenHarmony 特有功能

4.1 地理编码服务

OpenHarmony 的地理编码服务支持多种查询方式:

正向地理编码

  • 支持模糊地址搜索
  • 返回多个匹配结果
  • 支持多语言地址

逆向地理编码

  • 支持精确坐标转地址
  • 返回详细地址信息
  • 支持多语言返回

4.2 多语言支持

OpenHarmony 地理编码服务支持多种语言:

// 中文环境
await _geocodingOhos.setLocaleIdentifier('zh_CN');
List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);
// 返回中文地址

// 英文环境
await _geocodingOhos.setLocaleIdentifier('en_US');
List<Placemark> placemarksEn = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);
// 返回英文地址

4.3 地址格式

OpenHarmony 返回的地址信息包含完整的行政区划:

Placemark {
  country: '中国',
  administrativeArea: '北京市',
  subAdministrativeArea: '海淀区',
  locality: '北京市',
  subLocality: '海淀区',
  thoroughfare: '中关村大街',
  subThoroughfare: '1号',
  postalCode: '100080',
}

五、OpenHarmony 平台实现原理

5.1 原生 API 映射

geocoding 在 OpenHarmony 平台上使用 @kit.LocationKit 模块实现:

Flutter API OpenHarmony API
locationFromAddress geoLocationManager.getAddressesFromLocationName
placemarkFromCoordinates geoLocationManager.getAddressesFromLocation
setLocaleIdentifier geoLocationManager.setLocale
isPresent geoLocationManager.isGeocoderAvailable

5.2 正向地理编码实现

OpenHarmony 使用 geoLocationManager.getAddressesFromLocationName 实现地址转坐标:

async function getAddressesFromLocationName(address: string): Promise<Location[]> {
  let request: geoLocationManager.GeoAddressRequest = {
    description: address,
    maxItems: 5,
  };
  
  let geoAddresses = await geoLocationManager.getAddressesFromLocationName(request);
  let locations: Location[] = [];
  
  for (let geoAddress of geoAddresses) {
    locations.push({
      latitude: geoAddress.latitude,
      longitude: geoAddress.longitude,
      locality: geoAddress.locality,
      country: geoAddress.countryName,
      administrativeArea: geoAddress.administrativeArea,
      subAdministrativeArea: geoAddress.subAdministrativeArea,
      thoroughfare: geoAddress.thoroughfare,
      subThoroughfare: geoAddress.subThoroughfare,
      postalCode: geoAddress.postalCode,
      isoCountryCode: geoAddress.countryCode,
    });
  }
  
  return locations;
}

5.3 逆向地理编码实现

OpenHarmony 使用 geoLocationManager.getAddressesFromLocation 实现坐标转地址:

async function getAddressesFromLocation(latitude: number, longitude: number): Promise<Placemark[]> {
  let request: geoLocationManager.ReverseGeoCodeRequest = {
    latitude: latitude,
    longitude: longitude,
    maxItems: 5,
  };
  
  let geoAddresses = await geoLocationManager.getAddressesFromLocation(request);
  let placemarks: Placemark[] = [];
  
  for (let geoAddress of geoAddresses) {
    placemarks.push({
      name: geoAddress.placeName,
      street: geoAddress.thoroughfare,
      isoCountryCode: geoAddress.countryCode,
      country: geoAddress.countryName,
      postalCode: geoAddress.postalCode,
      administrativeArea: geoAddress.administrativeArea,
      subAdministrativeArea: geoAddress.subAdministrativeArea,
      locality: geoAddress.locality,
      subLocality: geoAddress.subLocality,
      thoroughfare: geoAddress.thoroughfare,
      subThoroughfare: geoAddress.subThoroughfare,
    });
  }
  
  return placemarks;
}

5.4 多语言支持实现

OpenHarmony 使用 geoLocationManager.setLocale 设置语言环境:

async function setLocale(locale: string): Promise<void> {
  await geoLocationManager.setLocale(locale);
}

5.5 服务状态检测实现

OpenHarmony 使用 geoLocationManager.isGeocoderAvailable 检查服务状态:

async function isGeocoderAvailable(): Promise<boolean> {
  return await geoLocationManager.isGeocoderAvailable();
}

六、MethodChannel 通信协议

6.1 方法列表

方法 参数 返回值 说明
locationFromAddress address List <Location> 地址转坐标
placemarkFromCoordinates latitude, longitude List <Placemark> 坐标转地址
setLocaleIdentifier localeIdentifier void 设置语言环境
isPresent - bool 检查服务状态

6.2 Location 数据格式

{
  "latitude": 39.9042,
  "longitude": 116.4074,
  "locality": "北京市",
  "country": "中国",
  "administrativeArea": "北京市",
  "subAdministrativeArea": "海淀区",
  "thoroughfare": "中关村大街",
  "subThoroughfare": "1号",
  "postalCode": "100080",
  "isoCountryCode": "CN"
}

6.3 Placemark 数据格式

{
  "name": "中关村大厦",
  "street": "中关村大街1号",
  "isoCountryCode": "CN",
  "country": "中国",
  "postalCode": "100080",
  "administrativeArea": "北京市",
  "subAdministrativeArea": "海淀区",
  "locality": "北京市",
  "subLocality": "海淀区",
  "thoroughfare": "中关村大街",
  "subThoroughfare": "1号"
}

七、实战案例

7.1 基础地理编码功能

// 地址转坐标
List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街');
if (locations.isNotEmpty) {
  Location location = locations.first;
  print('纬度: ${location.latitude}');
  print('经度: ${location.longitude}');
  print('时间戳: ${location.timestamp}');
}

7.2 逆向地理编码

// 坐标转地址
List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);
if (placemarks.isNotEmpty) {
  Placemark placemark = placemarks.first;
  print('国家: ${placemark.country}');
  print('省份: ${placemark.administrativeArea}');
  print('城市: ${placemark.locality}');
  print('街道: ${placemark.thoroughfare}');
}

7.3 多语言支持

// 设置语言环境
await _geocodingOhos.setLocaleIdentifier('zh-CN');

// 中文地址转坐标
List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街');

// 设置英文环境
await _geocodingOhos.setLocaleIdentifier('en-US');

// 英文地址转坐标
List<Location> locationsEn = await _geocodingOhos.locationFromAddress('Zhongguancun Street, Haidian District, Beijing');

7.4 服务状态检测

// 检查地理编码服务是否可用
bool isAvailable = await _geocodingOhos.isPresent();
if (isAvailable) {
  print('地理编码服务可用');
} else {
  print('地理编码服务不可用');
}

八、最佳实践

8.1 服务封装

class GeocodingService {
  final GeocodingOhos _geocodingOhos = GeocodingOhos();

  Future<Location?> getCoordinatesFromAddress(String address) async {
    try {
      List<Location> locations = await _geocodingOhos.locationFromAddress(address);
      return locations.isNotEmpty ? locations.first : null;
    } catch (e) {
      print('地址转坐标失败: $e');
      return null;
    }
  }

  Future<Placemark?> getAddressFromCoordinates(double lat, double lng) async {
    try {
      List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(lat, lng);
      return placemarks.isNotEmpty ? placemarks.first : null;
    } catch (e) {
      print('坐标转地址失败: $e');
      return null;
    }
  }
}

8.2 错误处理

Future<Location?> safeLocationFromAddress(String address) async {
  final GeocodingOhos geocodingOhos = GeocodingOhos();
  try {
    bool available = await geocodingOhos.isPresent();
    if (!available) {
      print('地理编码服务不可用');
      return null;
    }

    List<Location> locations = await geocodingOhos.locationFromAddress(address);
    return locations.isNotEmpty ? locations.first : null;
  } catch (e) {
    print('地理编码失败: $e');
    return null;
  }
}

8.3 缓存策略

class GeocodingCache {
  static final Map<String, Location> _addressCache = {};
  static final Map<String, Placemark> _coordinateCache = {};
  static final GeocodingOhos _geocodingOhos = GeocodingOhos();

  static Future<Location?> getCachedLocation(String address) async {
    if (_addressCache.containsKey(address)) {
      return _addressCache[address];
    }

    List<Location> locations = await _geocodingOhos.locationFromAddress(address);
    Location? location = locations.isNotEmpty ? locations.first : null;
    if (location != null) {
      _addressCache[address] = location;
    }

    return location;
  }

  static Future<Placemark?> getCachedPlacemark(double lat, double lng) async {
    final key = '$lat,$lng';
    if (_coordinateCache.containsKey(key)) {
      return _coordinateCache[key];
    }

    List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(lat, lng);
    Placemark? placemark = placemarks.isNotEmpty ? placemarks.first : null;
    if (placemark != null) {
      _coordinateCache[key] = placemark;
    }

    return placemark;
  }

  static void clearCache() {
    _addressCache.clear();
    _coordinateCache.clear();
  }
}

8.4 地址格式化

String formatAddress(Placemark placemark) {
  List<String> parts = [];
  
  if (placemark.country != null) parts.add(placemark.country!);
  if (placemark.administrativeArea != null) parts.add(placemark.administrativeArea!);
  if (placemark.locality != null) parts.add(placemark.locality!);
  if (placemark.thoroughfare != null) parts.add(placemark.thoroughfare!);
  if (placemark.subThoroughfare != null) parts.add(placemark.subThoroughfare!);
  
  return parts.join(' ');
}

8.5 批量查询优化

Future<List<Location>> batchGeocodeAddresses(List<String> addresses) async {
  List<Location> results = [];
  
  for (String address in addresses) {
    Location? location = await GeocodingCache.getCachedLocation(address);
    if (location != null) {
      results.add(location);
    }

    // 避免请求过快
    await Future.delayed(const Duration(milliseconds: 100));
  }
  
  return results;
}

九、常见问题

Q1:地理编码失败怎么办?

检查以下几点:

  1. 确保权限已授予

    // 地理编码需要位置权限
    
  2. 检查服务是否可用

    bool available = await _geocodingOhos.isPresent();
    

if (!available) {
print(‘地理编码服务不可用’);
}

3. **确保地址格式正确**

```dart
// 使用标准地址格式
List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街1号');

Q2:地址解析不准确怎么办?

提高地址精度:

// 使用更详细的地址
List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街1号中关村大厦');

// 从多个结果中选择最匹配的
if (locations.length > 1) {
  // 根据业务逻辑选择最合适的结果
  Location bestMatch = locations.first;
}

Q3:如何处理多语言地址?

设置对应的语言环境:

// 处理中文地址
await _geocodingOhos.setLocaleIdentifier('zh_CN');
List<Location> locations = await _geocodingOhos.locationFromAddress('北京市海淀区中关村大街');

// 处理英文地址
await _geocodingOhos.setLocaleIdentifier('en_US');
List<Location> locationsEn = await _geocodingOhos.locationFromAddress('Zhongguancun Street, Beijing');

Q4:如何提高查询性能?

使用缓存策略:

// 使用缓存避免重复查询
Location? location = await GeocodingCache.getCachedLocation('北京市海淀区中关村大街');

Q5:如何处理无效坐标?

验证坐标范围:

Future<Placemark?> safePlacemarkFromCoordinates(double lat, double lng) async {
  // 验证坐标范围
  if (lat < -90 || lat > 90 || lng < -180 || lng > 180) {
    print('无效的坐标');
    return null;
  }
  
  return await GeocodingService().getAddressFromCoordinates(lat, lng);
}

Q6:如何获取更详细的地址信息?

使用完整的 Placemark 对象:

List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(39.9042, 116.4074);
if (placemarks.isNotEmpty) {
  Placemark placemark = placemarks.first;
  print('国家: ${placemark.country}');
  print('省份: ${placemark.administrativeArea}');
  print('城市: ${placemark.locality}');
  print('区县: ${placemark.subAdministrativeArea}');
  print('街道: ${placemark.thoroughfare}');
  print('门牌号: ${placemark.subThoroughfare}');
  print('邮编: ${placemark.postalCode}');
}

十、总结

geocoding 库为 Flutter for OpenHarmony 开发提供了完整的地理编码能力。通过丰富的 API,开发者可以实现地址转坐标、坐标转地址等功能。该库在鸿蒙平台上已经完成了完整的适配,支持所有核心功能,包括多语言支持和详细地址信息,开发者可以放心使用。

核心特性

  1. 完整功能:支持正向地理编码、逆向地理编码等完整的地理编码功能
  2. 跨平台一致:API 设计与 Android、iOS 平台保持一致,降低学习成本
  3. 多语言支持:支持设置不同语言环境,返回对应语言的地址信息
  4. 易于集成:提供清晰的 API 和丰富的示例代码

使用建议

  1. 在使用前先检查服务是否可用
  2. 使用缓存策略提高性能
  3. 处理好错误和异常情况
  4. 根据业务需求选择合适的语言环境

结合 location 库使用
geocoding 库可以与 location 库配合使用,实现更完整的位置服务功能,如地址搜索、位置标注、导航规划等,为用户提供全方位的位置服务体验。


十一、完整代码示例

以下是一个完整的可运行示例,展示了 geocoding 库的核心功能:

main.dart

import 'package:flutter/material.dart';
import 'package:geocoding_ohos/geocoding_ohos.dart';
import 'package:geocoding_platform_interface/geocoding_platform_interface.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Geocoding Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final TextEditingController _addressController = TextEditingController();
  final TextEditingController _latController = TextEditingController();
  final TextEditingController _lngController = TextEditingController();
  final GeocodingOhos _geocodingOhos = GeocodingOhos();
  
  List<Location> _locations = [];
  List<Placemark> _placemarks = [];
  String? _errorMessage;
  bool _isLoading = false;
  String _currentLocale = 'zh_CN';

  
  void initState() {
    super.initState();
    _checkService();
  }

  Future<void> _checkService() async {
    bool available = await _geocodingOhos.isPresent();
    if (!available) {
      setState(() {
        _errorMessage = '地理编码服务不可用';
      });
    }
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  Future<void> _geocodeAddress() async {
    final address = _addressController.text.trim();
    if (address.isEmpty) {
      setState(() {
        _errorMessage = '请输入地址';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
      _locations.clear();
    });

    try {
      // 检查服务是否可用
      bool available = await _geocodingOhos.isPresent();
      if (!available) {
        setState(() {
          _errorMessage = '地理编码服务不可用';
          _isLoading = false;
        });
        return;
      }

      // 设置语言环境
      await _geocodingOhos.setLocaleIdentifier(_currentLocale);

      // 搜索地址
      List<Location> locations = await _geocodingOhos.locationFromAddress(address);
    
      setState(() {
        _locations = locations;
        _isLoading = false;
      });
    
      _showMessage('找到 ${locations.length} 个结果');
    } catch (e) {
      setState(() {
        _errorMessage = '搜索失败: $e';
        _isLoading = false;
      });
      _showMessage('搜索失败');
    }
  }

  Future<void> _reverseGeocode() async {
    final lat = double.tryParse(_latController.text);
    final lng = double.tryParse(_lngController.text);
  
    if (lat == null || lng == null) {
      setState(() {
        _errorMessage = '请输入有效的经纬度';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
      _placemarks.clear();
    });

    try {
      // 检查服务是否可用
      bool available = await _geocodingOhos.isPresent();
      if (!available) {
        setState(() {
          _errorMessage = '地理编码服务不可用';
          _isLoading = false;
        });
        return;
      }

      // 设置语言环境
      await _geocodingOhos.setLocaleIdentifier(_currentLocale);

      // 坐标转地址
      List<Placemark> placemarks = await _geocodingOhos.placemarkFromCoordinates(lat, lng);
    
      setState(() {
        _placemarks = placemarks;
        _isLoading = false;
      });
    
      _showMessage('找到 ${placemarks.length} 个结果');
    } catch (e) {
      setState(() {
        _errorMessage = '逆地理编码失败: $e';
        _isLoading = false;
      });
      _showMessage('逆地理编码失败');
    }
  }

  void _changeLocale(String locale) {
    setState(() {
      _currentLocale = locale;
    });
    _showMessage('语言环境已切换');
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('地理编码示例'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          PopupMenuButton<String>(
            onSelected: _changeLocale,
            itemBuilder: (context) => [
              const PopupMenuItem(
                value: 'zh_CN',
                child: Text('中文'),
              ),
              const PopupMenuItem(
                value: 'en_US',
                child: Text('英文'),
              ),
              const PopupMenuItem(
                value: 'ja_JP',
                child: Text('日文'),
              ),
            ],
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // 正向地理编码部分
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('正向地理编码', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),
                    TextField(
                      controller: _addressController,
                      decoration: const InputDecoration(
                        labelText: '输入地址',
                        hintText: '例如:北京市海淀区中关村大街1号',
                        border: OutlineInputBorder(),
                      ),
                      onSubmitted: (_) => _geocodeAddress(),
                    ),
                    const SizedBox(height: 16),
                    ElevatedButton.icon(
                      onPressed: _isLoading ? null : _geocodeAddress,
                      icon: const Icon(Icons.search),
                      label: const Text('地址转坐标'),
                    ),
                    if (_locations.isNotEmpty) ...[
                      const SizedBox(height: 16),
                      const Text('结果:', style: TextStyle(fontWeight: FontWeight.bold)),
                      ..._locations.map((location) => ListTile(
                        leading: const CircleAvatar(child: Icon(Icons.location_on)),
                        title: Text('纬度: ${location.latitude?.toStringAsFixed(6)}, 经度: ${location.longitude?.toStringAsFixed(6)}'),
                      )),
                    ],
                  ],
                ),
              ),
            ),
          
            const SizedBox(height: 16),
          
            // 逆向地理编码部分
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('逆向地理编码', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    const SizedBox(height: 16),
                    Row(
                      children: [
                        Expanded(
                          child: TextField(
                            controller: _latController,
                            decoration: const InputDecoration(
                              labelText: '纬度',
                              hintText: '例如:39.9042',
                              border: OutlineInputBorder(),
                            ),
                            keyboardType: TextInputType.number,
                          ),
                        ),
                        const SizedBox(width: 16),
                        Expanded(
                          child: TextField(
                            controller: _lngController,
                            decoration: const InputDecoration(
                              labelText: '经度',
                              hintText: '例如:116.4074',
                              border: OutlineInputBorder(),
                            ),
                            keyboardType: TextInputType.number,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 16),
                    ElevatedButton.icon(
                      onPressed: _isLoading ? null : _reverseGeocode,
                      icon: const Icon(Icons.my_location),
                      label: const Text('坐标转地址'),
                    ),
                    if (_placemarks.isNotEmpty) ...[
                      const SizedBox(height: 16),
                      const Text('结果:', style: TextStyle(fontWeight: FontWeight.bold)),
                      ..._placemarks.map((placemark) => ListTile(
                        leading: const CircleAvatar(child: Icon(Icons.place)),
                        title: Text('${placemark.country ?? ""} ${placemark.administrativeArea ?? ""}'),
                        subtitle: Text('${placemark.locality ?? ""} ${placemark.thoroughfare ?? ""}'),
                      )),
                    ],
                  ],
                ),
              ),
            ),
          
            if (_errorMessage != null) ...[
              const SizedBox(height: 16),
              Card(
                color: Colors.red.shade50,
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Row(
                    children: [
                      const Icon(Icons.error, color: Colors.red),
                      const SizedBox(width: 8),
                      Expanded(child: Text(_errorMessage!, style: const TextStyle(color: Colors.red))),
                    ],
                  ),
                ),
              ),
            ],
          
            if (_isLoading) ...[
              const SizedBox(height: 16),
              const Center(child: CircularProgressIndicator()),
            ],
          ],
        ),
      ),
    );
  }
}
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐