229 lines
6.2 KiB
Dart
229 lines
6.2 KiB
Dart
|
|
import 'dart:async';
|
|||
|
|
import 'dart:math';
|
|||
|
|
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:flutter_map/flutter_map.dart';
|
|||
|
|
import 'package:geolocator/geolocator.dart';
|
|||
|
|
import 'package:latlong2/latlong.dart';
|
|||
|
|
|
|||
|
|
import '../path_list_pages.dart';
|
|||
|
|
|
|||
|
|
class MapPageEnterprise extends StatefulWidget {
|
|||
|
|
const MapPageEnterprise({Key? key}) : super(key: key);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
State<MapPageEnterprise> createState() => _MapPageEnterpriseState();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
|||
|
|
final MapController _mapController = MapController();
|
|||
|
|
|
|||
|
|
LatLng? _currentLatLng;
|
|||
|
|
StreamSubscription<Position>? _positionSub;
|
|||
|
|
bool _hasMovedOnce = false;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_initLocationEnterprise();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// ===============================
|
|||
|
|
/// 企业级定位初始化(已修复坐标系)
|
|||
|
|
/// ===============================
|
|||
|
|
Future<void> _initLocationEnterprise() async {
|
|||
|
|
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|||
|
|
if (!serviceEnabled) return;
|
|||
|
|
|
|||
|
|
LocationPermission permission = await Geolocator.checkPermission();
|
|||
|
|
if (permission == LocationPermission.denied) {
|
|||
|
|
permission = await Geolocator.requestPermission();
|
|||
|
|
}
|
|||
|
|
if (permission != LocationPermission.whileInUse &&
|
|||
|
|
permission != LocationPermission.always) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 1️⃣ 首次强制获取定位
|
|||
|
|
final position = await Geolocator.getCurrentPosition(
|
|||
|
|
desiredAccuracy: LocationAccuracy.bestForNavigation,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
final gcj = wgs84ToGcj02(position.latitude, position.longitude);
|
|||
|
|
_updateLocation(gcj, moveMap: true);
|
|||
|
|
|
|||
|
|
// 2️⃣ 实时监听(不再强制移动地图)
|
|||
|
|
_positionSub = Geolocator.getPositionStream(
|
|||
|
|
locationSettings: const LocationSettings(
|
|||
|
|
accuracy: LocationAccuracy.bestForNavigation,
|
|||
|
|
distanceFilter: 2,
|
|||
|
|
),
|
|||
|
|
).listen((pos) {
|
|||
|
|
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
|
|||
|
|
_updateLocation(gcj, moveMap: false);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// ===============================
|
|||
|
|
/// 更新位置
|
|||
|
|
/// ===============================
|
|||
|
|
void _updateLocation(LatLng latLng, {bool moveMap = false}) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentLatLng = latLng;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (moveMap && !_hasMovedOnce) {
|
|||
|
|
_hasMovedOnce = true;
|
|||
|
|
_mapController.move(latLng, 17);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 手动回到当前位置
|
|||
|
|
void _moveToCurrentLocation() {
|
|||
|
|
if (_currentLatLng == null) return;
|
|||
|
|
_mapController.move(_currentLatLng!, 17);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_positionSub?.cancel();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
final screenHeight = MediaQuery.of(context).size.height;
|
|||
|
|
final menuHeight = 16 * 6; // 假设 VerticalFloatMenu 有 6 个选项,每个高度为 56
|
|||
|
|
final maxTop = screenHeight - menuHeight;
|
|||
|
|
return Scaffold(
|
|||
|
|
body:
|
|||
|
|
SafeArea(
|
|||
|
|
child: Stack(
|
|||
|
|
children: [
|
|||
|
|
FlutterMap(
|
|||
|
|
mapController: _mapController,
|
|||
|
|
options: MapOptions(
|
|||
|
|
initialCenter:
|
|||
|
|
_currentLatLng ?? const LatLng(39.9042, 116.4074),
|
|||
|
|
initialZoom: 15,
|
|||
|
|
maxZoom: 18,
|
|||
|
|
),
|
|||
|
|
children: [
|
|||
|
|
/// 高德瓦片(GCJ-02)
|
|||
|
|
TileLayer(
|
|||
|
|
urlTemplate:
|
|||
|
|
'https://webrd02.is.autonavi.com/appmaptile'
|
|||
|
|
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
|
|||
|
|
'&key=bbb1f0f20eed6bf679eddf2625630aba',
|
|||
|
|
),
|
|||
|
|
|
|||
|
|
/// 当前定位 Marker
|
|||
|
|
if (_currentLatLng != null)
|
|||
|
|
MarkerLayer(
|
|||
|
|
markers: [
|
|||
|
|
Marker(
|
|||
|
|
point: _currentLatLng!,
|
|||
|
|
width: 40,
|
|||
|
|
height: 40,
|
|||
|
|
child: const Icon(
|
|||
|
|
Icons.my_location,
|
|||
|
|
color: Colors.blue,
|
|||
|
|
size: 30,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
|
|||
|
|
Positioned(
|
|||
|
|
height: 336,
|
|||
|
|
right: 16,
|
|||
|
|
top: maxTop > 10 ? 11 : maxTop, // 确保不超出屏幕范围
|
|||
|
|
child: FloatingActionButton(
|
|||
|
|
onPressed: _moveToCurrentLocation,
|
|||
|
|
child: new VerticalFloatMenu(),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// =======================================================
|
|||
|
|
/// 坐标转换:WGS84 -> GCJ02(国内高德/腾讯通用)
|
|||
|
|
/// =======================================================
|
|||
|
|
|
|||
|
|
const double _pi = 3.14159265358979324;
|
|||
|
|
const double _a = 6378245.0;
|
|||
|
|
const double _ee = 0.00669342162296594323;
|
|||
|
|
|
|||
|
|
LatLng wgs84ToGcj02(double lat, double lon) {
|
|||
|
|
if (_outOfChina(lat, lon)) {
|
|||
|
|
return LatLng(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 LatLng(mgLat, mgLon);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool _outOfChina(double lat, double lon) {
|
|||
|
|
return lon < 72.004 ||
|
|||
|
|
lon > 137.8347 ||
|
|||
|
|
lat < 0.8293 ||
|
|||
|
|
lat > 55.8271;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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 * 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;
|
|||
|
|
}
|