@@ -70,7 +70,6 @@ class MapPageEnterprise extends StatefulWidget {
class _MapPageEnterpriseState extends State < MapPageEnterprise > {
final _dispatcher = sl < NetMessageDispatcher > ( ) ;
late StreamSubscription < RawPacket > _sub ;
bool _isRefreshing = false ; // 新增:页面刷新状态标志
bool _isVideoDialogOpen = false ; // 控制视频弹窗显示
String _videoStreamUrl = " " ;
@@ -88,9 +87,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
WorkStatus _workStatus = WorkStatus . idle ;
bool isStartWork = false ; //是否开始作业
LatLng ? _currentLatLng ;
StreamSubscription < Position > ? _positionSub ;
bool _hasMovedOnce = false ;
LatLng ? _currentLatLng ; //gc格式
LatLng ? _currentWgsLatLng ;
bool _isPanelOpen = false ; // 控制面板显示/隐藏
bool _directionBoxOpen = false ; //航线面板显示/隐藏
bool _saveBoxOpen = false ; //保存按钮显示/隐藏
@@ -101,7 +100,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 新增:控制底部作业面板显示
bool _isWorkPanelOpen = false ;
bool _isWorkAreaCompleted = false ;
RobotMode _currentRobotMode = RobotMode . poin t; //打点模式
RobotMode _currentRobotMode = RobotMode . robo t; //打点模式
AreaMode _currentAreaMode = AreaMode . work ; //作业区域还是障碍物区域
WorkMode ? _currentWorkMode = WorkMode . bow ; // 当前作业模式
// 新增: 存储转换后的path和outer坐标
@@ -112,14 +111,18 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
List < PlotPoint > ? gctracePoint = [ ] ;
// ========== 核心新增状态 ==========
final List < LatLng > _markedPoints = [ ] ; // 存储所有打点坐标
final List < LatLng > _markedPoints = [ ] ; // 存储所有s十字准星 打点坐标
List < LatLng > _robotModeWgsPoints = [ ] ; // Robot模式下从设备获取的wgs打点
List < LatLng > _robotModeObsWgsPoints = [ ] ; // Robot模式下从设备获取的wgs打点 障碍物
LatLng _mapCenter = const LatLng ( 39.9042 , 116.4074 ) ;
double _headingAngle = 0.0 ; // 当前机器航向角(单位:度)
List < LatLng > ? _currentTracePoints ; //收到机器的点坐标
List typedPathList = < work_area_model . DeviceAddPathPointModel > [ ] ; // 存储生成路径的坐标列表( 已转换为LatLng)
// ========== 障碍物模式核心状态 ==========
List < List < LatLng > > _obstacleHoles = [ ] ; // 存储多组障碍物打点(嵌套数组:每组是一个空洞 )
List < List < LatLng > > _obstacleHoles = [ ] ; // 存储多组障碍物打点(嵌套数组:每组是一个障碍物 )
List < List < LatLng > > _obstacleWgsHoles = [ ] ; // 存储多组障碍物打点(嵌套数组:每组是一个障碍物)
List < LatLng > _currentObstaclePoints = [ ] ; // 当前正在绘制的障碍物打点
bool _isObstacleEditing = false ; // 是否处于障碍物编辑状态
@@ -180,14 +183,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 计算边界
final bounds = calculateBounds ( points ) ;
if ( bounds = = null ) return ;
final currentZoom = _mapController . camera ? . zoom ? ? 18.0 ;
// 移动地图到边界中心(自动适配缩放级别)
_mapController. fitBounds(
bounds,
options: FitBoundsOptions (
padding: const EdgeInsets . all ( 50 ) , // 边缘留白(避免内容贴边)
//maxZoom: 18, // 最大缩放级别(防止过度放大 )
) ,
// _mapController. fitBounds(
// bounds,
// options: FitBoundsOptions(
// padding: const EdgeInsets.all(50), // 边缘留白(避免内容贴边 )
// //maxZoom: 18, // 最大缩放级别(防止过度放大)
// ),
//);
_mapController . move (
bounds . center , // 边界中心点
currentZoom , // 保留当前缩放比例
) ;
print ( ' 地图已移动到绘制内容中心,边界范围: $ bounds ' ) ;
@@ -204,6 +214,17 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
return null ;
}
bool __isValidLatLng ( LatLng ? latLng ) {
if ( latLng = = null ) return false ;
// 排除赤道0,0坐标, 同时校验经纬度范围( 避免非法值)
return latLng . latitude ! = 0.0 & &
latLng . longitude ! = 0.0 & &
latLng . latitude > = - 90 & &
latLng . latitude < = 90 & &
latLng . longitude > = - 180 & &
latLng . longitude < = 180 ;
}
bool _isValidLatLng ( double lat , double lng ) {
return lat ! = 0 & & lng ! = 0 & & lat > = - 90 & & lat < = 90 & & lng > = - 180 & & lng < = 180 ;
}
@@ -507,69 +528,62 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
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 , ' 已撤回作业区域 最后一个点,剩余 ${ _marked Points . length } 个点 ' ) ;
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回作业区域最后一个点,剩余${_markedPoints.length}个点')));
ToastUtils . showInfo ( context , ' 已撤回Robot模式 最后一个点,剩余 ${ _robotModeWgs Points . 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 ; // 撤完点就结束,不执行后面的逻辑
}
// 第二步:当前空洞无点可撤 → 再撤回「上一个完整空洞」
if ( _obstacleHoles . isNotEmpty ) {
_obstacleHoles . removeLast ( ) ;
// 撤完空洞后重新生成路径(排除该空洞)
if ( _isWorkAreaCompleted ) {
_generatePath ( showTips: false ) ;
}
ToastUtils . showInfo ( context , ' 已撤回上一个完整空洞,剩余 ${ _obstacleHoles . length } 个空洞 ' ) ;
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞')));
ToastUtils . showInfo ( context , ' 已撤回当前障碍物 最后一个点,剩余 ${ _currentObstaclePoints . length } 个点 ' ) ;
return ;
}
// 第三步:无任何可撤内容
ToastUtils . showInfo ( context , ' 暂无空洞可撤回 ' ) ;
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可撤回')));
if ( _obstacleHoles . isNotEmpty ) {
_obstacleHoles . removeLast ( ) ;
if ( _isWorkAreaCompleted ) {
_generatePath ( showTips: false ) ;
}
ToastUtils . showInfo ( context , ' 已撤回上一个完整障碍物,剩余 ${ _obstacleHoles . length } 个障碍物 ' ) ;
return ;
}
ToastUtils . showInfo ( context , ' 暂无障碍物可撤回 ' ) ;
}
} ) ;
}
// 兼容原有调用( 可保留, 也可直接用上面的_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 ( ) ;
@@ -581,28 +595,25 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有作业区域点和路径')));
}
// 2. 空洞 模式:清空所有空洞 + 恢复UI + 重新生成路径
// 2. 障碍物 模式:清空所有障碍物 + 恢复UI + 重新生成路径
else if ( _currentAreaMode = = AreaMode . obstacle ) {
if ( _obstacleHoles . isEmpty & & _currentObstaclePoints . isEmpty ) {
ToastUtils . showInfo ( context , ' 暂无空洞 可删除 ' ) ;
ToastUtils . showInfo ( context , ' 暂无障碍物 可删除 ' ) ;
return ;
}
_obstacleHoles . clear ( ) ;
_currentObstaclePoints . clear ( ) ;
_robotModeObsWgsPoints . clear ( ) ;
_isObstacleEditing = false ;
// 清空后重新生成路径(无空洞 )
// 清空后重新生成路径(无障碍物 )
if ( _isWorkAreaCompleted ) {
_generatePath ( showTips: false ) ;
}
ToastUtils . showSuccess ( context , ' 已清空所有空洞 ' ) ;
ToastUtils . showSuccess ( context , ' 已清空所有障碍物 ' ) ;
}
} ) ;
}
// 兼容原有调用
void _deleteAllPoints ( ) = > _deleteAllAction ( ) ;
void _clearAllObstacles ( ) = > _deleteAllAction ( ) ;
// ========== 核心:清空所有打点和轨迹 ==========
//void _deleteAllPoints() {
// if (_markedPoints.isNotEmpty) {
@@ -623,38 +634,31 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
Future < void > _initLocationEnterprise ( ) async {
// 1. 设置轨迹模式为 LOCATION( 核心: 确保模式正确)
_traceManager . setMode ( TPMode . LOCATION ) ;
// 2. 清空旧的定位监听(如果有)
await _positionSub ? . cancel ( ) ;
_positionSub = null ;
// 3. 初始化默认坐标(可选,避免地图空白)
setState ( ( ) {
_currentLatLng = const LatLng ( 39.9042 , 116.4074 ) ; // 北京默认坐标
} ) ;
}
// ========== 新增:页面刷新初始化方法 ==========
Future < void > _handleRefresh ( ) async {
// 防止重复刷新
if ( _isRefreshing ) return ;
// 🔥 保存当前定位(刷新前先记录)
final savedCurrentLatLng = _currentLatLng ;
final savedCurrentWgsLatLng = _currentWgsLatLng ;
final savedMapCenter = _mapCenter ;
setState ( ( ) {
_isRefreshing = true ; // 标记开始刷新
} ) ;
try {
// 1. 取消旧的定位监听
await _positionSub ? . cancel ( ) ;
// 2. 重置所有页面状态(恢复初始值)
setState ( ( ) {
_traceManager . setMode ( TPMode . LOCATION ) ;
// 清空打点和轨迹数据
_markedPoints . clear ( ) ;
gcjPathPoints . clear ( ) ;
_robotModeWgsPoints . clear ( ) ;
_robotModeObsWgsPoints . clear ( ) ;
gcjOuterPoints . clear ( ) ;
_currentTracePoints = null ;
_isWorkAreaCompleted = false ;
_saveBoxOpen = false ;
@@ -680,9 +684,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 重置参数
_workDistance = 0.0 ;
_angle = 0.0 ;
_mapCenter = const LatLng ( 39.9042 , 116.4074 ) ;
_hasMovedOnce = false ;
_currentLatLng = null ;
_currentLatLng = savedCurrentLatLng ;
_currentWgsLatLng = savedCurrentWgsLatLng ;
_mapCenter = savedMapCenter ? ? _currentLatLng ? ? const LatLng ( 39.9042 , 116.4074 ) ;
} ) ;
// 3. 重新初始化定位
@@ -692,13 +696,16 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager . reset ( ) ;
// 5. 重新加载作业记录(保持数据最新)
final userId = context . read < AppUserCubit > ( ) . state . user ? . userId ? ? " " ;
if ( userId. isNotEmpty) {
await context. read < DevicesCubit > ( ) . loadWorkRecords( userId) ;
}
// final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
// if ( userId. isNotEmpty) {
// await context.read<DevicesCubit>(). loadWorkRecords( userId);
// }
// 6. 重置地图视角到初始位置
_mapController . move ( const LatLng ( 39.9042 , 116.4074 ) , 17 ) ;
if ( savedCurrentLatLng ! = null ) {
_mapController . move ( savedCurrentLatLng , _mapController . camera ? . zoom ? ? 17 ) ;
} else {
_mapController . move ( const LatLng ( 39.9042 , 116.4074 ) , 17 ) ;
}
// 刷新成功提示
if ( mounted ) {
@@ -724,29 +731,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
}
/// ===============================
/// 更新位置
/// ===============================
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 ) {
@@ -755,13 +746,20 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
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 } 个障碍物区域 ' ) ;
}
@@ -796,14 +794,34 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
return ;
}
setState ( ( ) {
if ( _currentArea Mode = = Area Mode. work ) {
_markedPoints . add ( _mapCenter ) ;
debugPrint ( ' 新增作业区域打点:第 ${ _markedPoints . length } 个点,经纬度: ${ _mapCenter . latitude } , ${ _mapCenter . longitude } ' ) ;
if ( _currentRobot Mode = = Robot Mode. 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 ) } ' ) ;
@@ -817,10 +835,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
@ override
void dispose ( ) {
_positionSub ? . cancel ( ) ;
_traceManager . reset ( ) ;
_mapController . dispose ( ) ;
_sub . cancel ( ) ;
super . dispose ( ) ;
}
@@ -883,7 +899,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
setState ( ( ) {
gcjPathPoints = [ ] ;
gcjOuterPoints = [ ] ;
_currentTracePoints = null ;
} ) ;
await context . read < DevicesCubit > ( ) . loadSelectedPath ( plot . plotName ) ;
@@ -1160,20 +1175,28 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
final gcjPoint = wgs84ToGcj02 ( lat , lng ) ;
// 关键: 判断Widget是否还挂载, 避免空指针
final initCenter = bd09ToGcj02 (
' 120.81992468426961 ' , // 经度
' 32.04532826114155 ' ,
) ;
_currentWgsLatLng = LatLng ( lat , lng ) ;
_currentLatLng = gcjPoint ; // ✅ 状态变量在setState内更新
return FlutterMap (
mapController: _mapController ,
options: MapOptions (
initialCenter: _currentLatLng ? ? const LatLng ( 39.9042 , 116.4074 ) ,
initialZoom: 20 ,
initialCenter: __isValidLatLng ( _currentLatLng ) ? _currentLatLng ! : initCenter ,
initialZoom: 22 ,
maxZoom: 22 ,
// 禁止地图点击事件(避免和中心标冲突)
minZoom: 3 ,
enableScrollWheel: true ,
onTap: ( _ , __ ) { } , // 空实现,禁用地图点击响应
) ,
children: [
/// 高德瓦片( GCJ-02)
TileLayer (
//https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
// '&key=bbb1f0f20eed6bf679eddf2625630aba
urlTemplate:
' https://webrd02.is.autonavi.com/appmaptile '
' ?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1 '
@@ -1230,7 +1253,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
Polyline ( points: [ _markedPoints [ i ] , _markedPoints [ i + 1 ] ] , color: Colors . orange . withOpacity ( 0.5 ) , strokeWidth: 1.5 ) ,
] ,
) ,
if ( _markedPoints . isNotEmpty & & ! _isWorkAreaCompleted )
if ( _markedPoints . isNotEmpty & & ! _isWorkAreaCompleted & & _currentRobotMode = = RobotMode . point )
PolylineLayer (
polylines: [
Polyline ( points: [ _mapCenter , _markedPoints . last ] , color: Colors . blue . withOpacity ( 0.5 ) , strokeWidth: 1.5 ) ,
@@ -1252,7 +1275,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
) ,
] ,
) ,
if ( _currentWorkMode = = WorkMode . bow & & _markedPoints . isNotEmpty & & ! _isWorkAreaCompleted )
if ( _currentWorkMode = = WorkMode . bow & & _markedPoints . isNotEmpty & & ! _isWorkAreaCompleted & & _currentRobotMode = = RobotMode . point )
PolygonLayer (
polygons: [
Polygon (
@@ -1276,22 +1299,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
) ;
} ) . toList ( ) ,
) ,
if ( gctracePoint ! . isNotEmpty )
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 )
if ( _isObstacleEditing & & _currentObstaclePoints . isNotEmpty & & _currentRobotMode = = RobotMode . point )
PolylineLayer (
polylines: [
// 已绘制的障碍物点连线
@@ -1406,6 +1417,18 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
) ;
} ) . 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 , // 虚线区分作业区域
) ,
] ,
) ,
] ,
) ;
}
@@ -1755,14 +1778,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
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 ;
}
@@ -1775,54 +1797,97 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
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 , first Point. longitude ) ;
final referencePoint = work_area_model . ReferencePoint ( lat: wgs84FirstPoint . latitude , lon: wgs84FirstPoint . longitude ) ;
// 2.1 参考点(区分Robot/Point模式 )
LatLng firstPointWgs ; // 最终传给接口的WGS84格式参考点
if ( _currentRobotMode = = RobotMode . robot & & _robotModeWgs Points . 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 < work_area_model . Position > 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 < List < work_area_model . Position > > positionList = [ ] ;
for ( var holePoints in _obstacleHoles ) {
List < work_area_model . Position > innerPositionList = holePoints . map ( ( latLng ) {
final wgs84Point = gcj02ToWgs84 ( latLng . latitude , latLng . longitude ) ;
return work_area_model . Position ( lat: wgs84Point . latitude , lon: wgs84Point . longitude ) ;
} ) . toList ( ) ;
i f ( _currentRobotMode = = RobotMode . robot & & _r obotModeObsWgsPoints . isNotEmpty ) {
for ( var holePoints in _obstacleWgsHoles ) {
List < work_area_model . Position > innerPositionList = [ ] ;
p ositionList. add ( innerPositionList ) ;
innerP ositionList = 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 < work_area_model . Position > 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 < work_area_model . Position > 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<String, HoleBoundary>
// 用一个临时 Map 来满足类型要求,后续在 Cubit 中取值即可
Map < String , work_area_model . HoleBoundary > holes = { } ;
if ( holeBoundary ! = null ) {
holes = { ' tempKey ' : holeBoundary } ; // 键名任意,仅为满足类型要求
holes = { ' tempKey ' : holeBoundary } ;
}
final workType = _currentWorkMode = = WorkMode . bow ? 0 : 2 ;
@@ -1831,7 +1896,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
reference: referencePoint ,
heading: heading ,
outer: outerBoundary ,
holes: holes . isNotEmpty ? holes : { } , // 无障碍物时传空对象
holes: holes . isNotEmpty ? holes : { } ,
workType: workType ,
) ;
@@ -1842,7 +1907,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
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 ) {
@@ -1857,17 +1921,16 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final pathList = cubitState . generatedPath as List ;
typedPathList = pathList . whereType < work_area_model . DeviceAddPathPointModel > ( ) . toList ( ) ;
debugPrint ( ' ✅ 过滤并转换后的路径点列表: typedPathList 长度= ${ typedPathList . length } ' ) ;
//打印
// 路径点转换为GCJ02( 无论哪种模式, 最终地图显示都需要GCJ02)
gcjPathPoints = typedPathList . map ( ( point ) {
double wgs84Lat = point . latitude ;
double wgs84Lon = point . longitude ;
final gcjPoint = wgs84ToGcj02 ( wgs84Lat , wgs84Lon ) ;
return LatLng ( gcjPoint . latitude , gcjPoint . longitude ) ;
} ) . toList ( ) ;
// 外边界保持原有逻辑
gcjOuterPoints = List . from ( _markedPoints ) ;
//_markedPoints.clear();
} ) ;
moveMapToPointsCenter ( gcjPathPoints ) ;
}
@@ -1876,7 +1939,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
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));
}
}
}
@@ -1889,13 +1951,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final userState = context . watch < AppUserCubit > ( ) . state ;
final deviceId = context . watch < DevicesCubit > ( ) . 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 < DeviceStatusBloc , DeviceStatusState > (
builder: ( context , state ) {
// 初始化默认值
@@ -1986,22 +2047,23 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
) ,
/// 核心:地图正中心固定定位标
Positioned (
left: 0 ,
righ t: 0 ,
top : 0 ,
bot tom : 0 ,
child: Center (
// 自定义十字标(中心精准对齐地图中心)
child: SizedBox (
width: 16 , // 十字整体宽度
height : 16 , // 十字整体高 度
child: CustomPaint (
painter: Crosshair Painter ( ) , // 自定义十字画笔
if ( _currentRobotMode = = RobotMode . point )
Positioned (
lef t: 0 ,
right : 0 ,
top : 0 ,
bottom: 0 ,
child: Center (
// 自定义十字标(中心精准对齐地图中心)
child: SizedBox (
width : 16 , // 十字整体宽 度
height: 16 , // 十字整体高度
child: Custom Paint(
painter: CrosshairPainter ( ) , // 自定义十字画笔
) ,
) ,
) ,
) ,
) ,
//保存按钮
if ( _saveBoxOpen )
@@ -2068,8 +2130,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
right: 0 ,
bottom: 0 ,
child: BottomOperationPanel (
initialRobotMode: RobotMode . point ,
initialAreaMode: AreaMode. work ,
initialRobotMode: _current RobotMode,
initialAreaMode: _current AreaMode,
initialWorkMode: _currentWorkMode ! ,
canUndo: _isWorkAreaCompleted & & _currentAreaMode = = AreaMode . work ? false : true ,
onComplete: ( ) async {
@@ -2302,6 +2364,33 @@ List<LatLng> batchWgs84ToGcj02(List<LatLng> wgs84Points) {
} ) . toList ( ) ;
}
LatLng bd09ToGcj02 ( dynamic bdLng , dynamic bdLat ) {
// 1. 先将字符串转为double( 兼容你的初始值格式)
double lng = _safeToDouble ( bdLng ) ;
double lat = _safeToDouble ( bdLat ) ;
// 2. BD09转GCJ02核心公式
double x = lng - 0.0065 ;
double y = lat - 0.006 ;
double z = math . sqrt ( x * x + y * y ) - 0.00002 * math . sin ( y * _pi ) ;
double theta = math . atan2 ( y , x ) - 0.000003 * math . cos ( x * _pi ) ;
double gcjLng = z * math . cos ( theta ) ;
double gcjLat = z * math . sin ( theta ) ;
return LatLng ( gcjLat , gcjLng ) ;
}
double _safeToDouble ( dynamic value ) {
if ( value = = null ) return 0.0 ;
if ( value is double ) return value ;
if ( value is int ) return value . toDouble ( ) ;
if ( value is String ) {
// 字符串转数字, 失败返回0.0
return double . tryParse ( value ) ? ? 0.0 ;
}
return 0.0 ;
}
LatLng gcj02ToWgs84 ( double lat , double lon ) {
if ( _outOfChina ( lat , lon ) ) {
return LatLng ( lat , lon ) ;