// lib/utils/location_helper.dart import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// ============ 常量与坐标转换相关(WGS84/GCJ02/BD09) ============ const double _pi = 3.1415926535897932384626; const double _xPi = _pi * 3000.0 / 180.0; const double _a = 6378245.0; const double _ee = 0.006693421622965943; /// 判断是否在中国境内(仅中国境内需要偏移处理) bool _outOfChina(double lat, double lon) { if (lon < 72.004 || lon > 137.8347) return true; if (lat < 0.8293 || lat > 55.8271) return true; return false; } double _transformLat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(x.abs()); ret += (20.0 * sin(6.0 * x * _pi) + 20.0 * sin(2.0 * x * _pi)) * 2.0 / 3.0; ret += (20.0 * sin(y * _pi) + 40.0 * sin(y / 3.0 * _pi)) * 2.0 / 3.0; ret += (160.0 * sin(y / 12.0 * _pi) + 320.0 * sin(y * _pi / 30.0)) * 2.0 / 3.0; return ret; } double _transformLon(double x, double y) { double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x.abs()); ret += (20.0 * sin(6.0 * x * _pi) + 20.0 * sin(2.0 * x * _pi)) * 2.0 / 3.0; ret += (20.0 * sin(x * _pi) + 40.0 * sin(x / 3.0 * _pi)) * 2.0 / 3.0; ret += (150.0 * sin(x / 12.0 * _pi) + 300.0 * sin(x / 30.0 * _pi)) * 2.0 / 3.0; return ret; } /// WGS84 -> GCJ02 List wgs84ToGcj02(double lat, double lon) { if (_outOfChina(lat, lon)) return [lat, lon]; double dLat = _transformLat(lon - 105.0, lat - 35.0); double dLon = _transformLon(lon - 105.0, lat - 35.0); double radLat = lat / 180.0 * _pi; double magic = sin(radLat); magic = 1 - _ee * magic * magic; double sqrtMagic = sqrt(magic); dLat = (dLat * 180.0) / ((_a * (1 - _ee)) / (magic * sqrtMagic) * _pi); dLon = (dLon * 180.0) / ((_a / sqrtMagic) * cos(radLat) * _pi); double mgLat = lat + dLat; double mgLon = lon + dLon; return [mgLat, mgLon]; } /// GCJ02 -> BD09 List gcj02ToBd09(double lat, double lon) { double x = lon; double y = lat; double z = sqrt(x * x + y * y) + 0.00002 * sin(y * _xPi); double theta = atan2(y, x) + 0.000003 * cos(x * _xPi); double bdLon = z * cos(theta) + 0.0065; double bdLat = z * sin(theta) + 0.006; return [bdLat, bdLon]; } /// 直接 WGS84 -> BD09(先 WGS84->GCJ02,再 GCJ02->BD09) List wgs84ToBd09(double lat, double lon) { final gcj = wgs84ToGcj02(lat, lon); return gcj02ToBd09(gcj[0], gcj[1]); } /// ============ 定位相关设置 ============ /// 定位请求超时时间(可根据需要调整) const Duration _locationTimeout = Duration(seconds: 10); /// 获取 BD09 坐标(包含权限检查与多种后备方案) /// 返回 [bdLat, bdLon] Future> getBd09FromGeolocator({ LocationAccuracy accuracy = LocationAccuracy.high, }) async { // 1. 权限检查与请求(先请求权限) LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { // 用户拒绝(不是永久拒绝) throw Exception('定位权限被用户拒绝'); } } if (permission == LocationPermission.deniedForever) { // 永久拒绝(需要用户手动到设置开启) throw Exception('定位权限被永久拒绝'); } // 2. 检查定位服务是否开启(GPS/定位开关) bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { throw Exception('定位服务未开启'); } // 3. 尝试获取当前位置(主方案:getCurrentPosition,带超时) Position? position; try { position = await Geolocator.getCurrentPosition(desiredAccuracy: accuracy) .timeout(_locationTimeout); } on TimeoutException catch (_) { position = null; } catch (_) { position = null; } // 4. 后备:尝试 getLastKnownPosition(可能是旧位置) if (position == null) { try { position = await Geolocator.getLastKnownPosition(); } catch (_) { position = null; } } // 5. 后备:在 Android 上尝试 forceAndroidLocationManager(某些设备/厂商兼容性问题) if (position == null) { try { position = await Geolocator.getCurrentPosition( desiredAccuracy: accuracy, forceAndroidLocationManager: true, ).timeout(_locationTimeout); } catch (_) { position = null; } } // 6. 最终仍无位置 -> 抛异常 if (position == null) { throw Exception('无法获取位置信息,请检查设备定位设置或权限'); } // 7. 转换 WGS84 -> BD09 并返回 [bdLat, bdLon] final bd = wgs84ToBd09(position.latitude, position.longitude); return bd; } /// ============ 错误提示(中文映射) ============ String _mapExceptionToChineseMessage(Object e) { final msg = e?.toString() ?? ''; if (msg.contains('定位权限被用户拒绝') || msg.contains('Location permissions are denied') || msg.contains('denied')) { return '定位权限被拒绝,请允许应用获取定位权限。'; } if (msg.contains('定位权限被永久拒绝') || msg.contains('deniedForever') || msg.contains('permanently denied')) { return '定位权限被永久拒绝,请到系统设置手动开启定位权限。'; } if (msg.contains('定位服务未开启') || msg.contains('Location services are disabled')) { return '设备定位功能未开启,请打开系统定位后重试。'; } if (msg.contains('无法获取位置信息') || msg.contains('无法获取位置信息')) { return '无法获取有效定位,请检查网络/GPS并重试(可尝试切换到高精度模式)。'; } // 默认返回空字符串,调用方根据空串决定是否显示 return '定位失败:${msg.replaceAll('Exception: ', '')}'; } /// ============ 主业务方法:获取并保存 BD09(同时处理 UI 提示/引导) ============ /// 调用示例: await fetchAndSaveBd09(context); Future fetchAndSaveBd09(BuildContext context) async { // 显示 loading(若你项目有 LoadingDialogHelper 也可替换为它) // 注意:若外层已显示 loading,请不要重复显示 try { // 获取 BD09 坐标(包含权限请求、可能弹系统权限对话) final List bd = await getBd09FromGeolocator(); final bdLat = bd[0]; final bdLon = bd[1]; // 保存到 SharedPreferences(以字符串保存,便于后续读取) final prefs = await SharedPreferences.getInstance(); await prefs.setString('bd_lat', bdLat.toString()); await prefs.setString('bd_lon', bdLon.toString()); // 成功提示 // ToastUtil.showNormal(context, '定位成功:$bdLat, $bdLon'); } on Exception catch (e) { final msg = e.toString(); // 定位权限被永久拒绝 -> 引导用户打开应用设置 if (msg.contains('定位权限被永久拒绝') || msg.contains('deniedForever') || msg.contains('permanently denied')) { final open = await CustomAlertDialog.showConfirm( context, title: '定位权限', content: '定位权限被永久拒绝,需要手动到应用设置开启定位权限,是否现在打开设置?', cancelText: '取消', confirmText: '去设置', ); if (open == true) { await Geolocator.openAppSettings(); } } // 定位服务未开启 -> 引导用户打开系统定位设置 else if (msg.contains('定位服务未开启') || msg.contains('Location services are disabled')) { final open = await CustomAlertDialog.showConfirm( context, title: '打开定位', content: '检测到设备定位服务未开启,是否打开系统定位设置?', cancelText: '取消', confirmText: '去打开', ); if (open == true) { await Geolocator.openLocationSettings(); } } // 其它错误 -> 以 toast 显示中文提示(如果映射为空则显示原错误) else { final userMsg = _mapExceptionToChineseMessage(e); if (userMsg.isNotEmpty) { // ToastUtil.showError(context, userMsg); } else { // ToastUtil.showError(context, '定位失败:${e.toString()}'); } } } catch (e) { // 捕获任何未预期异常 // ToastUtil.showError(context, '发生未知错误:${e.toString()}'); } finally { } }