flutter_integrated_whb/lib/tools/coord_convert.dart

230 lines
8.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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 {
}
}