diff --git a/lib/features/devices/data/repositories/generate_path_repository_Impl.dart b/lib/features/devices/data/repositories/generate_path_repository_Impl.dart index acc26bf2..c8b156ba 100644 --- a/lib/features/devices/data/repositories/generate_path_repository_Impl.dart +++ b/lib/features/devices/data/repositories/generate_path_repository_Impl.dart @@ -28,6 +28,7 @@ class PathRepositoryImpl implements PathRepository { 'holes': holes.map((hole) => hole.toJson()).toList(), 'workType': workType, }); + print('请求体: $body'); // 调试输出请求体 try { final response = await http.post(url, headers: headers, body: body); @@ -134,14 +135,12 @@ class PathRepositoryImpl implements PathRepository { required String workName, }) async { final timestamp = DateTime.now().millisecondsSinceEpoch; - final url = Uri.parse( - 'https://serviceri.satabot.com/iot/workRecord/selectByWorkName', - ).replace( - queryParameters: { - 'workName': workName, - '_t': timestamp.toString(), - }, - ); + final url = + Uri.parse( + 'https://serviceri.satabot.com/iot/workRecord/selectByWorkName', + ).replace( + queryParameters: {'workName': workName, '_t': timestamp.toString()}, + ); print('请求URL: $url'); try { @@ -167,20 +166,22 @@ class PathRepositoryImpl implements PathRepository { } else if (rawData is Map) { records = [Map.from(rawData)]; } else { - throw Exception('Unexpected data type for "data": ${rawData.runtimeType}'); + throw Exception( + 'Unexpected data type for "data": ${rawData.runtimeType}', + ); } - return records; } else { throw Exception('API error: ${data['msg'] ?? 'Unknown'}'); } } else { - throw Exception('HTTP ${response.statusCode}: ${response.reasonPhrase}'); + throw Exception( + 'HTTP ${response.statusCode}: ${response.reasonPhrase}', + ); } } catch (e) { throw Exception('Network error in selectWorkRecordByName: $e'); } } - } diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 705bdfac..c938b197 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -11,6 +11,8 @@ import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart'; import 'package:maibu_satabot_v2/core/di/injection.dart'; import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart'; import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart'; +import 'package:maibu_satabot_v2/features/devices/data/models/device_work_area_param_model.dart' + as work_area_model; import 'package:maibu_satabot_v2/features/devices/data/repositories/generate_path_repository_Impl.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/get_work_record_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_record_usecase.dart'; @@ -1235,10 +1237,151 @@ class _MapPageEnterpriseState extends State { child: BottomOperationPanel( initialRobotMode: RobotMode.point, initialAreaMode: AreaMode.work, - onComplete: () { + onComplete: () async { debugPrint( '操作完成回调:当前机器人模式=$_currentRobotMode,当前区域模式=$_currentAreaMode,当前作业模式:$_currentWorkMode,作业区域点:$_markedPoints,作业行距=$_workDistance,航线方向角=$_angle', ); + + // 1. 校验必要条件(至少需要3个打点才能形成闭合区域) + if (_markedPoints.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('请先添加作业区域打点!'), + backgroundColor: Colors.red, + ), + ); + return; + } + if (_markedPoints.length < 3) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('作业区域至少需要3个打点才能生成路径!'), + backgroundColor: Colors.red, + ), + ); + return; + } + if (_currentWorkMode == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('请先选择作业模式!'), + backgroundColor: Colors.red, + ), + ); + return; + } + + try { + // 2. 转换页面数据为 generatePath 所需的参数格式 + // 2.1 参考点(取第一个打点,转换为 WGS84) + final firstPoint = _markedPoints.first; + final wgs84FirstPoint = gcj02ToWgs84( + firstPoint.latitude, + firstPoint.longitude, + ); + final referencePoint = work_area_model.ReferencePoint( + lat: wgs84FirstPoint.latitude, + lon: wgs84FirstPoint.longitude, + ); + + // 2.2 航向角(转换为整型,-1表示最优航向则取-1) + final heading = _angle == -1 ? -1 : _angle.toInt(); + + // 2.3 外边界(转换为 OuterBoundary 格式,所有点转 WGS84) + final outerBoundary = work_area_model.OuterBoundary( + // 将 LatLng 打点转换为 Position 列表(先转 WGS84) + position: _markedPoints.map((latLng) { + // 关键:每个点都转换为 WGS84 坐标系 + final wgs84Point = gcj02ToWgs84( + latLng.latitude, + latLng.longitude, + ); + return work_area_model.Position( + lat: wgs84Point.latitude, + lon: wgs84Point.longitude, + ); + }).toList(), + sideWidth: _workDistance, + ); + + final holes = []; + + final workType = _currentWorkMode == WorkMode.bow + ? 0 + : 2; // 注意:这里弓字模式是0,自定义是2(确认和后端一致) + + // 3. 调用 Cubit 的 generatePath 方法生成路径 + await context.read().generatePath( + reference: referencePoint, + heading: heading, + outer: outerBoundary, + holes: holes, + workType: workType, + ); + + // 4. 获取生成结果并处理 + final cubitState = context.read().state; + if (cubitState.errorMessage != null) { + // 生成失败 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('路径生成失败:${cubitState.errorMessage}'), + backgroundColor: Colors.red, + ), + ); + } else if (cubitState.generatedPath != null) { + // 生成成功 + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('路径生成成功!'), + backgroundColor: Colors.green, + ), + ); + debugPrint('生成的路径数据:${cubitState.generatedPath}'); + + // 可选:将生成的路径转换为地图可显示的坐标(WGS84 → GCJ02) + if (cubitState.generatedPath is List) { + setState(() { + // 关键:生成的路径是 WGS84,需转回 GCJ02 才能在高德地图正确显示 + gcjPathPoints = (cubitState.generatedPath as List) + .map((point) { + // 先取 WGS84 坐标 + double wgs84Lat = + safeToDouble(point['lat']) ?? 0.0; + double wgs84Lon = + safeToDouble(point['lon']) ?? 0.0; + // 转换为 GCJ02 + final gcjPoint = wgs84ToGcj02( + wgs84Lat, + wgs84Lon, + ); + return LatLng( + gcjPoint.latitude, + gcjPoint.longitude, + ); + }) + .toList(); + // 外边界也转回 GCJ02 显示(可选,保持和地图坐标系一致) + gcjOuterPoints = _markedPoints; + }); + // 移动地图到生成的路径中心 + moveMapToPointsCenter(gcjPathPoints); + } + } + } catch (e) { + // 异常处理 + debugPrint('路径生成异常:$e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '路径生成异常:${e.toString().substring(0, 50)}', + ), + backgroundColor: Colors.red, + ), + ); + } + + // 5. 关闭操作面板 setState(() => _isPanelOpen = false); }, onUndoTap: _undoLastPoint, @@ -1480,6 +1623,24 @@ LatLng wgs84ToGcj02(double lat, double lon) { return LatLng(mgLat, mgLon); } +LatLng gcj02ToWgs84(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; }