flutter_integrated_whb/lib/tools/coord_convert.dart

230 lines
8.5 KiB
Dart
Raw Normal View History

2025-09-02 16:22:17 +08:00
// 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<double> 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<double> 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<double> 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<List<double>> 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<void> fetchAndSaveBd09(BuildContext context) async {
// 显示 loading若你项目有 LoadingDialogHelper 也可替换为它)
// 注意:若外层已显示 loading请不要重复显示
try {
// 获取 BD09 坐标(包含权限请求、可能弹系统权限对话)
final List<double> 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 {
}
}