点击某一项可以在地图绘制轨迹 并且 地图居中
This commit is contained in:
@@ -139,6 +139,46 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 新增:计算坐标列表的边界范围 ==========
|
||||
LatLngBounds? calculateBounds(List<LatLng> points) {
|
||||
if (points.isEmpty) return null;
|
||||
|
||||
// 使用静态方法 fromPoints 创建边界(所有latlong2版本都支持)
|
||||
return LatLngBounds.fromPoints(points);
|
||||
}
|
||||
|
||||
// ========== 新增:移动地图到坐标中心 ==========
|
||||
// ========== 移动地图到坐标中心(无需修改) ==========
|
||||
void moveMapToPointsCenter(List<LatLng> points) {
|
||||
if (points.isEmpty || !mounted) return;
|
||||
|
||||
// 计算边界
|
||||
final bounds = calculateBounds(points);
|
||||
if (bounds == null) return;
|
||||
|
||||
// 移动地图到边界中心(自动适配缩放级别)
|
||||
_mapController.fitBounds(
|
||||
bounds,
|
||||
options: FitBoundsOptions(
|
||||
padding: const EdgeInsets.all(50), // 边缘留白(避免内容贴边)
|
||||
maxZoom: 18, // 最大缩放级别(防止过度放大)
|
||||
),
|
||||
);
|
||||
|
||||
print('地图已移动到绘制内容中心,边界范围:$bounds');
|
||||
}
|
||||
|
||||
// ========== 新增:安全转换任意类型为double ==========
|
||||
double? safeToDouble(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is double) return value;
|
||||
if (value is int) return value.toDouble();
|
||||
if (value is String) {
|
||||
return double.tryParse(value); // 字符串转数字(失败返回null)
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _onDeviceData(RawPacket packet) {
|
||||
try {
|
||||
debugPrint('>>> 收到0x12设备数据: ${packet}');
|
||||
@@ -396,19 +436,105 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
return GestureDetector(
|
||||
// 核心:点击列表项触发选中逻辑
|
||||
onTap: () async {
|
||||
// 1. 清空之前的轨迹数据
|
||||
setState(() {
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
_currentTracePoints = null;
|
||||
});
|
||||
|
||||
await context.read<DevicesCubit>().loadSelectedPath(plot.plotName);
|
||||
final cubitState = context.read<DevicesCubit>().state;
|
||||
final _loadedPlot = cubitState.pathData;
|
||||
print('加载地块「${plot.plotName}」的路径数据: $_loadedPlot');
|
||||
|
||||
setState(() {
|
||||
// 1. 选中当前地块
|
||||
_selectedPlot = plot;
|
||||
// 2. 关闭列表抽屉
|
||||
_isListBoxOpen = false;
|
||||
// 3. 打开底部作业面板
|
||||
_isWorkPanelOpen = true;
|
||||
});
|
||||
// 2. 立即解析并更新状态(在事件回调中,不在 build 中)
|
||||
if (_loadedPlot is List) {
|
||||
final List<dynamic> rawList = _loadedPlot as List<dynamic>;
|
||||
final pathRecords = rawList
|
||||
.where((item) => item is Map<String, dynamic>)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.toList();
|
||||
|
||||
if (pathRecords.isNotEmpty) {
|
||||
final selectedPlotPath = PlotDataPath(jsonData: pathRecords.first);
|
||||
try {
|
||||
final dynamic nestedJsonRaw =
|
||||
selectedPlotPath.jsonData['jsonData'];
|
||||
if (nestedJsonRaw != null && nestedJsonRaw is String) {
|
||||
final String nestedJsonStr = nestedJsonRaw;
|
||||
final dynamic parsedJson = jsonDecode(nestedJsonStr);
|
||||
if (parsedJson is Map<String, dynamic>) {
|
||||
final Map<String, dynamic> nestedJson = parsedJson;
|
||||
final dynamic rawPath = nestedJson['path'];
|
||||
final dynamic rawOuter = nestedJson['outer'];
|
||||
List<dynamic> pathList = rawPath is List ? rawPath : [];
|
||||
List<dynamic> outerList = rawOuter is List ? rawOuter : [];
|
||||
|
||||
List<LatLng> newPathPoints = [];
|
||||
List<LatLng> newOuterPoints = [];
|
||||
|
||||
for (var item in pathList) {
|
||||
double? lat = safeToDouble(item['lat']);
|
||||
double? lon = safeToDouble(item['lon']);
|
||||
if (lat != null && lon != null) {
|
||||
newPathPoints.add(convertWGS84ToGCJ02(lat, lon));
|
||||
}
|
||||
}
|
||||
for (var item in outerList) {
|
||||
double? lng = safeToDouble(item['lng']);
|
||||
double? lat = safeToDouble(item['lat']);
|
||||
if (lng != null && lat != null) {
|
||||
newOuterPoints.add(convertWGS84ToGCJ02(lat, lng));
|
||||
}
|
||||
}
|
||||
|
||||
// 在这里更新状态(安全,因为在事件回调中)
|
||||
setState(() {
|
||||
gcjPathPoints = newPathPoints;
|
||||
gcjOuterPoints = newOuterPoints;
|
||||
_selectedPlot = plot;
|
||||
_isListBoxOpen = false;
|
||||
_isWorkPanelOpen = true;
|
||||
});
|
||||
|
||||
// 移动地图到中心
|
||||
List<LatLng> allPoints = [];
|
||||
allPoints.addAll(gcjPathPoints);
|
||||
allPoints.addAll(gcjOuterPoints);
|
||||
if (allPoints.isNotEmpty) {
|
||||
moveMapToPointsCenter(allPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('解析/转换坐标失败:$e');
|
||||
setState(() {
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
_selectedPlot = plot;
|
||||
_isListBoxOpen = false;
|
||||
_isWorkPanelOpen = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
_selectedPlot = plot;
|
||||
_isListBoxOpen = false;
|
||||
_isWorkPanelOpen = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
_selectedPlot = plot;
|
||||
_isListBoxOpen = false;
|
||||
_isWorkPanelOpen = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
@@ -496,72 +622,19 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
// 解析路径数据(根据你的实际数据结构调整)
|
||||
List<Map<String, dynamic>> pathRecords = [];
|
||||
if (state.pathData is List) {
|
||||
// 第一步:安全转换为List,并过滤掉非Map的元素
|
||||
final List<dynamic> rawList = state.pathData as List<dynamic>;
|
||||
pathRecords = rawList
|
||||
.where((item) => item is Map<String, dynamic>)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.toList();
|
||||
|
||||
if (pathRecords.isNotEmpty) {
|
||||
_selectedPlotPath = PlotDataPath(jsonData: pathRecords.first);
|
||||
// ========== 核心新增:解析并转换坐标 ==========
|
||||
try {
|
||||
// 1. 解析嵌套的jsonData
|
||||
final String nestedJsonStr =
|
||||
_selectedPlotPath!.jsonData['jsonData'] as String;
|
||||
final Map<String, dynamic> nestedJson = jsonDecode(nestedJsonStr);
|
||||
|
||||
// 2. 提取path和outer原始坐标
|
||||
final List<dynamic> rawPath = nestedJson['path'] as List<dynamic>;
|
||||
final List<dynamic> rawOuter =
|
||||
nestedJson['outer'] as List<dynamic>;
|
||||
|
||||
// 3. 转换为GCJ02坐标
|
||||
gcjPathPoints = convertPathToGCJ02(rawPath);
|
||||
gcjOuterPoints = convertOuterToGCJ02(rawOuter);
|
||||
//setState(() {});
|
||||
|
||||
print('转换后的path坐标数量:${gcjPathPoints.length}');
|
||||
print('转换后的outer坐标数量:${gcjOuterPoints.length}');
|
||||
} catch (e) {
|
||||
print('解析/转换坐标失败:$e');
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
}
|
||||
} else {
|
||||
_selectedPlotPath = null; // 空列表时清空,避免残留旧数据
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
String workMode = "未知模式";
|
||||
if (_selectedPlotPath != null) {
|
||||
try {
|
||||
final String nestedJsonStr =
|
||||
_selectedPlotPath!.jsonData['jsonData'] as String;
|
||||
final Map<String, dynamic> nestedJson = jsonDecode(nestedJsonStr);
|
||||
workMode = nestedJson['planModel'] == WorkMode.bow.value
|
||||
? "弓字模式"
|
||||
: "自定义模式";
|
||||
} catch (e) {
|
||||
workMode = "未知模式";
|
||||
}
|
||||
} else {
|
||||
_selectedPlotPath = null; // 非List类型时清空
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
}
|
||||
//print(
|
||||
// _selectedPlotPath == null
|
||||
// ? '无数据'
|
||||
// : 'path坐标: ${jsonDecode(_selectedPlotPath!.jsonData['jsonData'] as String)}',
|
||||
//);
|
||||
String workMode =
|
||||
jsonDecode(
|
||||
_selectedPlotPath!.jsonData['jsonData'] as String,
|
||||
)['planModel'] ==
|
||||
WorkMode.bow.value
|
||||
? "弓字模式"
|
||||
: "自定义模式";
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted && _mapController.mapEventStream != null) {
|
||||
setState(() {
|
||||
// 触发地图重绘,确保坐标生效
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@@ -623,7 +696,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
Text(
|
||||
'作业模式:$workMode',
|
||||
style: const TextStyle(
|
||||
@@ -639,6 +711,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
setState(() {
|
||||
_isWorkPanelOpen = false;
|
||||
_selectedPlot = null;
|
||||
gcjPathPoints = [];
|
||||
gcjOuterPoints = [];
|
||||
});
|
||||
},
|
||||
icon: const Icon(
|
||||
@@ -698,12 +772,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.isLoading || pathRecords.isEmpty
|
||||
onPressed: state.isLoading || gcjPathPoints.isEmpty
|
||||
? null
|
||||
: () {
|
||||
// 作业逻辑:使用加载的pathData
|
||||
debugPrint(
|
||||
'开始作业:${_selectedPlot!.plotName},路径数据:$pathRecords',
|
||||
'开始作业:${_selectedPlot!.plotName},路径数据:$gcjPathPoints',
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -728,7 +802,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
: const Text(
|
||||
'开始作业',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -774,8 +848,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
options: MapOptions(
|
||||
initialCenter:
|
||||
_currentLatLng ?? const LatLng(39.9042, 116.4074),
|
||||
initialZoom: 15,
|
||||
maxZoom: 18,
|
||||
initialZoom: 20,
|
||||
maxZoom: 22,
|
||||
// 禁止地图点击事件(避免和中心标冲突)
|
||||
onTap: (_, __) {}, // 空实现,禁用地图点击响应
|
||||
),
|
||||
@@ -787,7 +861,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
|
||||
'&key=bbb1f0f20eed6bf679eddf2625630aba',
|
||||
),
|
||||
if (gcjPathPoints.isNotEmpty) // 仅当有数据时绘制
|
||||
// 绘制path折线(Line模式)
|
||||
if (gcjPathPoints.isNotEmpty)
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(
|
||||
@@ -801,8 +876,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
],
|
||||
),
|
||||
|
||||
// ========== 新增:绘制outer边框(Polygon模式) ==========
|
||||
if (gcjOuterPoints.isNotEmpty) // 仅当有数据时绘制
|
||||
// 绘制outer边框(Polygon模式)
|
||||
if (gcjOuterPoints.isNotEmpty)
|
||||
PolygonLayer(
|
||||
polygons: [
|
||||
Polygon(
|
||||
@@ -815,7 +890,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
],
|
||||
),
|
||||
|
||||
/// ========== 新增:中心标与历史打点的虚线连线 ==========
|
||||
/// 中心标与历史打点的虚线连线
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
for (int i = 0; i < _markedPoints.length - 1; i++)
|
||||
@@ -868,7 +943,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
],
|
||||
),
|
||||
|
||||
/// ========== 新增:历史打点的绿色标记 ==========
|
||||
/// 历史打点的绿色标记
|
||||
MarkerLayer(
|
||||
markers: _markedPoints.asMap().entries.map((entry) {
|
||||
int index = entry.key + 1; // 打点序号(从1开始)
|
||||
@@ -912,7 +987,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
],
|
||||
),
|
||||
|
||||
// ========== 悬浮返回按钮(核心修改) ==========
|
||||
// 悬浮返回按钮(核心修改)
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
@@ -959,7 +1034,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
),
|
||||
),
|
||||
|
||||
/// ========== 核心:地图正中心固定定位标 ==========
|
||||
/// 核心:地图正中心固定定位标
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@@ -1115,7 +1190,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
),
|
||||
),
|
||||
|
||||
// 🔥 改动6:列表面板 - 使用BlocBuilder监听DevicesCubit状态
|
||||
// 列表面板 - 使用BlocBuilder监听DevicesCubit状态
|
||||
if (_isListBoxOpen)
|
||||
BlocBuilder<DevicesCubit, DevicesState>(
|
||||
builder: (context, state) {
|
||||
@@ -1243,9 +1318,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
.read<DevicesCubit>()
|
||||
.loadWorkRecords(userId);
|
||||
|
||||
setState(() {
|
||||
// 因为Bloc数据更新后会自动重建,这里可省略
|
||||
});
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -1272,24 +1345,6 @@ LatLng convertWGS84ToGCJ02(double lat, double lon) {
|
||||
return wgs84ToGcj02(lat, lon);
|
||||
}
|
||||
|
||||
// 批量转换path坐标列表(path是lat+lon格式)
|
||||
List<LatLng> convertPathToGCJ02(List<dynamic> pathList) {
|
||||
return pathList.map((item) {
|
||||
double lat = item['lat'] as double;
|
||||
double lon = item['lon'] as double;
|
||||
return convertWGS84ToGCJ02(lat, lon);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// 批量转换outer坐标列表(outer是lng+lat格式,注意顺序)
|
||||
List<LatLng> convertOuterToGCJ02(List<dynamic> outerList) {
|
||||
return outerList.map((item) {
|
||||
double lon = item['lng'] as double;
|
||||
double lat = item['lat'] as double;
|
||||
return convertWGS84ToGCJ02(lat, lon); // 注意lat在前,lon=lon
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// =======================================================
|
||||
/// 坐标转换:WGS84 -> GCJ02(国内高德/腾讯通用)
|
||||
/// =======================================================
|
||||
|
||||
@@ -31,7 +31,7 @@ class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
{"icon": Icons.format_list_bulleted, "name": "列表"},
|
||||
{"icon": Icons.train, "name": "场站"},
|
||||
{"icon": Icons.navigation, "name": "定位"},
|
||||
{"icon": Icons.edit_note, "name": "编辑"},
|
||||
{"icon": Icons.edit_note, "name": "刷新"},
|
||||
{"icon": Icons.videocam, "name": "监控"},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user