diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index c7fbecb7..4f22ae25 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -87,9 +87,10 @@ class _MapPageEnterpriseState extends State { WorkStatus _workStatus = WorkStatus.idle; bool isStartWork = false; //是否开始作业 - LatLng? _currentLatLng; + LatLng? _currentLatLng; //gc格式 + LatLng? _currentWgsLatLng; + StreamSubscription? _positionSub; - bool _hasMovedOnce = false; bool _isPanelOpen = false; // 控制面板显示/隐藏 bool _directionBoxOpen = false; //航线面板显示/隐藏 bool _saveBoxOpen = false; //保存按钮显示/隐藏 @@ -111,14 +112,18 @@ class _MapPageEnterpriseState extends State { List? gctracePoint = []; // ========== 核心新增状态 ========== - final List _markedPoints = []; // 存储所有打点坐标 + final List _markedPoints = []; // 存储所有s十字准星打点坐标 + List _robotModeWgsPoints = []; // Robot模式下从设备获取的wgs打点 + List _robotModeObsWgsPoints = []; // Robot模式下从设备获取的wgs打点 障碍物 + LatLng _mapCenter = const LatLng(39.9042, 116.4074); double _headingAngle = 0.0; // 当前机器航向角(单位:度) - List? _currentTracePoints; //收到机器的点坐标 List typedPathList = []; // 存储生成路径的坐标列表(已转换为LatLng) // ========== 障碍物模式核心状态 ========== List> _obstacleHoles = []; // 存储多组障碍物打点(嵌套数组:每组是一个空洞) + + List> _obstacleWgsHoles = []; // 存储多组障碍物打点(嵌套数组:每组是一个空洞) List _currentObstaclePoints = []; // 当前正在绘制的障碍物打点 bool _isObstacleEditing = false; // 是否处于障碍物编辑状态 @@ -524,69 +529,62 @@ class _MapPageEnterpriseState extends State { setState(() { // 1. 作业区域模式 if (_currentAreaMode == AreaMode.work) { - if (_markedPoints.isNotEmpty) { + if (_currentRobotMode == RobotMode.robot && _robotModeWgsPoints.isNotEmpty && _markedPoints.isNotEmpty) { + // Robot模式撤回 + _robotModeWgsPoints.removeLast(); _markedPoints.removeLast(); - // 作业点撤回后,若路径已生成,重新生成路径(保持同步) if (_isWorkAreaCompleted && _markedPoints.length >= 3) { _generatePath(showTips: false); } - ToastUtils.showInfo(context, '已撤回作业区域最后一个点,剩余${_markedPoints.length}个点'); - //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回作业区域最后一个点,剩余${_markedPoints.length}个点'))); + ToastUtils.showInfo(context, '已撤回Robot模式最后一个点,剩余${_robotModeWgsPoints.length}个点'); } else { ToastUtils.showInfo(context, '暂无作业区域点可撤回'); - //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可撤回'))); } return; } - // 2. 空洞模式(完成后优先撤回已完成的空洞) + // 2. 空洞模式(逻辑不变,Robot模式下障碍物打点和Point模式一致) if (_currentAreaMode == AreaMode.obstacle) { if (_currentObstaclePoints.isNotEmpty) { _currentObstaclePoints.removeLast(); - // 如果当前空洞的点撤空了,退出编辑状态 + _robotModeObsWgsPoints.removeLast(); if (_currentObstaclePoints.isEmpty) { _isObstacleEditing = false; } ToastUtils.showInfo(context, '已撤回当前空洞最后一个点,剩余${_currentObstaclePoints.length}个点'); - - //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回当前空洞最后1个点,剩余${_currentObstaclePoints.length}个点'))); - return; // 撤完点就结束,不执行后面的逻辑 + return; } - // 第二步:当前空洞无点可撤 → 再撤回「上一个完整空洞」 if (_obstacleHoles.isNotEmpty) { _obstacleHoles.removeLast(); - // 撤完空洞后重新生成路径(排除该空洞) if (_isWorkAreaCompleted) { _generatePath(showTips: false); } ToastUtils.showInfo(context, '已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞'); - - //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞'))); return; } - // 第三步:无任何可撤内容 ToastUtils.showInfo(context, '暂无空洞可撤回'); - //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可撤回'))); } }); } - // 兼容原有调用(可保留,也可直接用上面的_unifiedUndo) - void _undoLastPoint() => _undoAction(); - void _undoObstaclePoint() => _undoAction(); - // ========== 统一删除方法(适配完成后状态) ========== void _deleteAllAction() { setState(() { // 1. 作业区域模式:清空作业点 + 重置路径 if (_currentAreaMode == AreaMode.work) { - if (_markedPoints.isEmpty) { - ToastUtils.showInfo(context, '暂无作业区域点可删除'); - - //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可删除'))); - return; + if (_currentRobotMode == RobotMode.robot) { + _robotModeWgsPoints.clear(); + _markedPoints.clear(); + ToastUtils.showSuccess(context, '已清空所有作业点'); + } else { + // 原有Point模式清空 + if (_markedPoints.isEmpty) { + ToastUtils.showInfo(context, '暂无作业区域点可删除'); + return; + } + _markedPoints.clear(); } _markedPoints.clear(); gcjPathPoints.clear(); @@ -606,6 +604,7 @@ class _MapPageEnterpriseState extends State { } _obstacleHoles.clear(); _currentObstaclePoints.clear(); + _robotModeObsWgsPoints.clear(); _isObstacleEditing = false; // 清空后重新生成路径(无空洞) if (_isWorkAreaCompleted) { @@ -616,10 +615,6 @@ class _MapPageEnterpriseState extends State { }); } - // 兼容原有调用 - void _deleteAllPoints() => _deleteAllAction(); - void _clearAllObstacles() => _deleteAllAction(); - // ========== 核心:清空所有打点和轨迹 ========== //void _deleteAllPoints() { // if (_markedPoints.isNotEmpty) { @@ -670,8 +665,9 @@ class _MapPageEnterpriseState extends State { // 清空打点和轨迹数据 _markedPoints.clear(); gcjPathPoints.clear(); + _robotModeWgsPoints.clear(); + _robotModeObsWgsPoints.clear(); gcjOuterPoints.clear(); - _currentTracePoints = null; _isWorkAreaCompleted = false; _saveBoxOpen = false; @@ -698,7 +694,6 @@ class _MapPageEnterpriseState extends State { _workDistance = 0.0; _angle = 0.0; _mapCenter = const LatLng(39.9042, 116.4074); - _hasMovedOnce = false; _currentLatLng = null; }); @@ -741,29 +736,13 @@ class _MapPageEnterpriseState extends State { } } - /// =============================== - /// 更新位置 - /// =============================== - void _updateLocation(LatLng latLng, {bool moveMap = false}) { - setState(() { - _currentLatLng = latLng; - }); - - if (moveMap) { - //_hasMovedOnce = true; - WidgetsBinding.instance.addPostFrameCallback((_) { - _hasMovedOnce = true; - _mapController.move(latLng, 17); - }); - } - } - /// 手动回到当前位置 void _moveToCurrentLocation() { if (_currentLatLng == null) return; _mapController.move(_currentLatLng!, 17); } + // ========== 障碍物模式:完成当前障碍物绘制 ========== // ========== 障碍物模式:完成当前障碍物绘制 ========== void _completeObstacle() { if (_currentObstaclePoints.length < 3) { @@ -772,13 +751,20 @@ class _MapPageEnterpriseState extends State { } setState(() { - _obstacleHoles.add(List.from(_currentObstaclePoints)); // 保存当前障碍物组 + // 保存当前障碍物组(Robot模式下直接用WGS84坐标) + if (_currentRobotMode == RobotMode.robot && _robotModeObsWgsPoints.isNotEmpty) { + final gcjPoints = _robotModeObsWgsPoints.map((wgs) => wgs84ToGcj02(wgs.latitude, wgs.longitude)).toList(); + _obstacleHoles.add(gcjPoints); + _obstacleWgsHoles.add(List.from(_robotModeObsWgsPoints)); + } else { + _obstacleHoles.add(List.from(_currentObstaclePoints)); + } _currentObstaclePoints.clear(); // 清空当前绘制的障碍物点 + _robotModeObsWgsPoints.clear(); // 清空Robot模式下的障碍物WGS84坐标 _isObstacleEditing = false; // 退出编辑状态 }); debugPrint('完成障碍物绘制,当前障碍物组数:${_obstacleHoles.length}'); - ToastUtils.showSuccess(context, '已添加第${_obstacleHoles.length}个障碍物区域'); } @@ -813,14 +799,34 @@ class _MapPageEnterpriseState extends State { return; } setState(() { - if (_currentAreaMode == AreaMode.work) { - _markedPoints.add(_mapCenter); - debugPrint('新增作业区域打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + if (_currentRobotMode == RobotMode.point) { + if (_currentAreaMode == AreaMode.work) { + _markedPoints.add(_mapCenter); + debugPrint('新增作业区域打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + } else { + // 障碍物模式:添加到当前障碍物打点 + _currentObstaclePoints.add(_mapCenter); + _isObstacleEditing = true; // 标记进入障碍物编辑状态 + debugPrint('新增障碍物打点:第${_currentObstaclePoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + } } else { - // 障碍物模式:添加到当前障碍物打点 - _currentObstaclePoints.add(_mapCenter); - _isObstacleEditing = true; // 标记进入障碍物编辑状态 - debugPrint('新增障碍物打点:第${_currentObstaclePoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + if (_currentLatLng == null || !_isValidLatLng(_currentLatLng!.latitude, _currentLatLng!.longitude)) { + ToastUtils.showError(context, '设备坐标无效,无法添加打点!'); + return; + } + + // 2. Robot模式-作业区域打点 + if (_currentAreaMode == AreaMode.work) { + _markedPoints.add(_currentLatLng!); // 从设备实时坐标取最后一个值 + _robotModeWgsPoints.add(_currentWgsLatLng!); + debugPrint('Robot模式新增作业打点:第${_markedPoints.length}个点,经纬度:${_currentLatLng!.latitude}, ${_currentLatLng!.longitude}'); + } else { + // 3. Robot模式-障碍物打点 + _currentObstaclePoints.add(_currentLatLng!); + _robotModeObsWgsPoints.add(_currentWgsLatLng!); + _isObstacleEditing = true; + debugPrint('Robot模式新增障碍物打点:第${_currentObstaclePoints.length}个点,经纬度:${_currentLatLng!.latitude}, ${_currentLatLng!.longitude}'); + } } }); debugPrint('$_markedPoints,新增打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude.toStringAsFixed(20)}, ${_mapCenter.longitude.toStringAsFixed(20)}'); @@ -899,7 +905,6 @@ class _MapPageEnterpriseState extends State { setState(() { gcjPathPoints = []; gcjOuterPoints = []; - _currentTracePoints = null; }); await context.read().loadSelectedPath(plot.plotName); @@ -1180,6 +1185,7 @@ class _MapPageEnterpriseState extends State { '120.81992468426961', // 经度 '32.04532826114155', ); + _currentWgsLatLng = LatLng(lat, lng); _currentLatLng = gcjPoint; // ✅ 状态变量在setState内更新 return FlutterMap( @@ -1300,18 +1306,6 @@ class _MapPageEnterpriseState extends State { }).toList(), ), //开始作业之后 - if (gctracePoint!.isNotEmpty && isStartWork) - PolylineLayer( - polylines: [ - for (int i = 0; i < gctracePoint!.length - 1; i++) - Polyline( - points: [gctracePoint![i], gctracePoint![i + 1]], - color: Colors.white, - strokeWidth: 1.5, - isDotted: true, // 虚线区分作业区域 - ), - ], - ), // 2. 正在绘制的障碍物打点连线(红色虚线) if (_isObstacleEditing && _currentObstaclePoints.isNotEmpty) @@ -1429,6 +1423,18 @@ class _MapPageEnterpriseState extends State { ); }).toList(), ), + if (gctracePoint!.isNotEmpty && isStartWork) + PolylineLayer( + polylines: [ + for (int i = 0; i < gctracePoint!.length - 1; i++) + Polyline( + points: [gctracePoint![i], gctracePoint![i + 1]], + color: Colors.white, + strokeWidth: 1.5, + isDotted: true, // 虚线区分作业区域 + ), + ], + ), ], ); } @@ -1778,14 +1784,13 @@ class _MapPageEnterpriseState extends State { gcjOuterPoints = List.from(_markedPoints); typedPathList = []; // 路径列表置空 }); - return; } + // 1. 校验必要条件 if (_currentAreaMode == AreaMode.work && _markedPoints.isEmpty) { if (showTips) { ToastUtils.showError(context, '请先添加作业区域打点!'); - //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先添加作业区域打点!'), backgroundColor: Colors.red)); } return; } @@ -1798,54 +1803,97 @@ class _MapPageEnterpriseState extends State { if (_currentWorkMode == null) { if (showTips) { ToastUtils.showError(context, '请先选择作业模式!'); - //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先选择作业模式!'), backgroundColor: Colors.red)); } return; } try { // 2. 转换页面数据为 generatePath 所需的参数格式 - // 2.1 参考点(取作业区域第一个打点) - final firstPoint = _markedPoints.first; - final wgs84FirstPoint = gcj02ToWgs84(firstPoint.latitude, firstPoint.longitude); - final referencePoint = work_area_model.ReferencePoint(lat: wgs84FirstPoint.latitude, lon: wgs84FirstPoint.longitude); + // 2.1 参考点(区分Robot/Point模式) + LatLng firstPointWgs; // 最终传给接口的WGS84格式参考点 + if (_currentRobotMode == RobotMode.robot && _robotModeWgsPoints.isNotEmpty) { + // Robot模式:直接使用原始WGS84坐标(无需转换) + firstPointWgs = _robotModeWgsPoints.first; + } else { + // Point模式:先取十字准星的GCJ02坐标 → 转换为WGS84 + if (_markedPoints.isEmpty) return; + LatLng firstPointGcj = _markedPoints.first; + firstPointWgs = gcj02ToWgs84(firstPointGcj.latitude, firstPointGcj.longitude); + } + // 参考点使用转换后的WGS84坐标 + final referencePoint = work_area_model.ReferencePoint(lat: firstPointWgs.latitude, lon: firstPointWgs.longitude); // 2.2 航向角 final heading = _angle == -1 ? -1 : _angle.toInt(); - // 2.3 外边界(作业区域) - final outerBoundary = work_area_model.OuterBoundary( - position: _markedPoints.map((latLng) { + // 2.3 外边界(作业区域)- 核心区分Robot/Point模式 + List outerPositions = []; + if (_currentRobotMode == RobotMode.robot && _robotModeWgsPoints.isNotEmpty) { + // Robot模式:直接使用原始WGS84坐标,跳过GCJ02→WGS84转换 + outerPositions = _robotModeWgsPoints.map((latLng) { + return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude); + }).toList(); + } else { + // Point模式:十字准星的GCJ02坐标 → 转换为WGS84 + outerPositions = _markedPoints.map((latLng) { final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); - }).toList(), - sideWidth: _workDistance, - ); + }).toList(); + } + final outerBoundary = work_area_model.OuterBoundary(position: outerPositions, sideWidth: _workDistance); + + // 2.4 障碍物处理(同样区分Robot/Point模式) work_area_model.HoleBoundary? holeBoundary; - if (_obstacleHoles.isNotEmpty) { - // 1. 构造二维 Position 数组 List> positionList = []; - for (var holePoints in _obstacleHoles) { - List innerPositionList = holePoints.map((latLng) { - final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); - return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); - }).toList(); + if (_currentRobotMode == RobotMode.robot && _robotModeObsWgsPoints.isNotEmpty) { + for (var holePoints in _obstacleWgsHoles) { + List innerPositionList = []; - positionList.add(innerPositionList); + innerPositionList = holePoints.map((latLng) { + return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude); + }).toList(); + positionList.add(innerPositionList); + } + } else { + for (var holePoints in _obstacleHoles) { + List innerPositionList = []; + + // Point模式:GCJ02→WGS84转换 + innerPositionList = holePoints.map((latLng) { + final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); + return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); + }).toList(); + positionList.add(innerPositionList); + } } - // 2. 创建 HoleBoundary 对象(无外层 Map) + for (var holePoints in _obstacleHoles) { + List innerPositionList = []; + + if (_currentRobotMode == RobotMode.robot && _robotModeObsWgsPoints.isNotEmpty) { + // Robot模式:使用障碍物原始WGS84坐标 + innerPositionList = holePoints.map((latLng) { + return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude); + }).toList(); + } else { + // Point模式:GCJ02→WGS84转换 + innerPositionList = holePoints.map((latLng) { + final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); + return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); + }).toList(); + } + positionList.add(innerPositionList); + } holeBoundary = work_area_model.HoleBoundary(position: positionList, sideWidth: 0.0); } // 🔥 第二步:构造符合方法参数要求的 Map - // 用一个临时 Map 来满足类型要求,后续在 Cubit 中取值即可 Map holes = {}; if (holeBoundary != null) { - holes = {'tempKey': holeBoundary}; // 键名任意,仅为满足类型要求 + holes = {'tempKey': holeBoundary}; } final workType = _currentWorkMode == WorkMode.bow ? 0 : 2; @@ -1854,7 +1902,7 @@ class _MapPageEnterpriseState extends State { reference: referencePoint, heading: heading, outer: outerBoundary, - holes: holes.isNotEmpty ? holes : {}, // 无障碍物时传空对象 + holes: holes.isNotEmpty ? holes : {}, workType: workType, ); @@ -1865,7 +1913,6 @@ class _MapPageEnterpriseState extends State { if (cubitState.errorMessage != null) { if (showTips) { ToastUtils.showError(context, '路径生成失败:${cubitState.errorMessage}'); - //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red)); } } else if (cubitState.generatedPath != null) { if (showTips) { @@ -1880,8 +1927,8 @@ class _MapPageEnterpriseState extends State { final pathList = cubitState.generatedPath as List; typedPathList = pathList.whereType().toList(); debugPrint('✅ 过滤并转换后的路径点列表:typedPathList 长度=${typedPathList.length}'); - //打印 + // 路径点转换为GCJ02(无论哪种模式,最终地图显示都需要GCJ02) gcjPathPoints = typedPathList.map((point) { double wgs84Lat = point.latitude; double wgs84Lon = point.longitude; @@ -1889,8 +1936,8 @@ class _MapPageEnterpriseState extends State { return LatLng(gcjPoint.latitude, gcjPoint.longitude); }).toList(); + // 外边界保持原有逻辑 gcjOuterPoints = List.from(_markedPoints); - //_markedPoints.clear(); }); moveMapToPointsCenter(gcjPathPoints); } @@ -1899,7 +1946,6 @@ class _MapPageEnterpriseState extends State { debugPrint('路径生成异常:$e'); if (showTips) { ToastUtils.showError(context, '路径生成异常:${e.toString().substring(0, 50)}'); - //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成异常:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red)); } } } @@ -1912,13 +1958,12 @@ class _MapPageEnterpriseState extends State { final userState = context.watch().state; final deviceId = context.watch().state.selectedDevice?.deviceName; - if (deviceId != null && userState.user?.token != null) { + if (deviceId != null && userState.user != null && userState.user!.token != null) { _videoStreamUrl = "webrtc://${TCPConsts.TCP_IP}/live/livestream/$deviceId?token=${userState.user!.token}"; } else { - _videoStreamUrl = ''; // 无设备/用户信息时置空 + _videoStreamUrl = ''; print("设备未选中或用户未登录,无法生成视频流地址"); } - return BlocBuilder( builder: (context, state) { // 初始化默认值