点击某一项可以在地图绘制轨迹 并且 地图居中

This commit is contained in:
mmc
2026-02-28 14:25:53 +08:00
parent 560a4d6aaf
commit 812db977c0
2 changed files with 163 additions and 108 deletions

View File

@@ -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(国内高德/腾讯通用)
/// =======================================================

View File

@@ -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": "监控"},
];