|
|
@ -84,12 +84,12 @@ |
|
|
/> |
|
|
/> |
|
|
<el-table v-else :data="trajectoryPoints" border size="mini" max-height="420" class="trajectory-table"> |
|
|
<el-table v-else :data="trajectoryPoints" border size="mini" max-height="420" class="trajectory-table"> |
|
|
<el-table-column label="#" type="index" width="60" align="center" /> |
|
|
<el-table-column label="#" type="index" width="60" align="center" /> |
|
|
<el-table-column :label="$t('device.trajectory.table.time')" min-width="180" align="center"> |
|
|
<el-table-column :label="$t('device.trajectory.table.time')" min-width="100" align="center"> |
|
|
<template slot-scope="scope"> |
|
|
<template slot-scope="scope"> |
|
|
{{ formatTrackTime(scope.row.locationTime || scope.row.reportedTime || scope.row.createTime) }} |
|
|
{{ formatTrackTime(scope.row.locationTime || scope.row.reportedTime || scope.row.createTime) }} |
|
|
</template> |
|
|
</template> |
|
|
</el-table-column> |
|
|
</el-table-column> |
|
|
<el-table-column :label="$t('device.trajectory.table.coordinates')" min-width="220" align="center"> |
|
|
<el-table-column :label="$t('device.trajectory.table.coordinates')" min-width="60" align="center"> |
|
|
<template slot-scope="scope"> |
|
|
<template slot-scope="scope"> |
|
|
{{ formatTrackCoordinates(scope.row.lat, scope.row.lng) }} |
|
|
{{ formatTrackCoordinates(scope.row.lat, scope.row.lng) }} |
|
|
</template> |
|
|
</template> |
|
|
@ -116,19 +116,27 @@ import { loadAMap } from "@/utils/loadAMap"; |
|
|
import { loadGoogleMaps } from "@/utils/loadGoogleMaps"; |
|
|
import { loadGoogleMaps } from "@/utils/loadGoogleMaps"; |
|
|
import { loadLeaflet } from "@/utils/loadLeaflet"; |
|
|
import { loadLeaflet } from "@/utils/loadLeaflet"; |
|
|
|
|
|
|
|
|
|
|
|
// 轨迹点未加载前,各地图默认中心点。 |
|
|
const AMAP_DEFAULT_CENTER = [121.4737, 31.2304]; |
|
|
const AMAP_DEFAULT_CENTER = [121.4737, 31.2304]; |
|
|
const GOOGLE_DEFAULT_CENTER = { lat: 31.2304, lng: 121.4737 }; |
|
|
const GOOGLE_DEFAULT_CENTER = { lat: 31.2304, lng: 121.4737 }; |
|
|
const LEAFLET_DEFAULT_CENTER = [31.2304, 121.4737]; |
|
|
const LEAFLET_DEFAULT_CENTER = [31.2304, 121.4737]; |
|
|
const AMAP_FALLBACK_MAX_ZOOM = 20; |
|
|
|
|
|
const GOOGLE_DETAIL_ZOOM = 21; |
|
|
// 各地图默认/详情缩放级别。 |
|
|
const LEAFLET_MAX_ZOOM = 22; |
|
|
const AMAP_FALLBACK_MAX_ZOOM = 17; |
|
|
const LEAFLET_GOOGLE_MAX_ZOOM = 20; |
|
|
const GOOGLE_DETAIL_ZOOM = 16; |
|
|
|
|
|
const LEAFLET_MAX_ZOOM = 16; |
|
|
|
|
|
const LEAFLET_GOOGLE_MAX_ZOOM = 17; |
|
|
|
|
|
|
|
|
|
|
|
// Leaflet + Google 瓦片地址与轨迹线样式。 |
|
|
const LEAFLET_GOOGLE_TILE_URL = "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"; |
|
|
const LEAFLET_GOOGLE_TILE_URL = "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"; |
|
|
const LEAFLET_LINE_ARROW_MAX = 36; |
|
|
const LEAFLET_LINE_ARROW_MAX = 36; |
|
|
const LEAFLET_SHOW_LINE_ARROWS = false; |
|
|
const LEAFLET_SHOW_LINE_ARROWS = false; |
|
|
const LEAFLET_TRAJECTORY_LINE_COLOR = "#1a73e8"; |
|
|
const LEAFLET_TRAJECTORY_LINE_COLOR = "#1a73e8"; |
|
|
const CONVERT_BATCH_SIZE = 40; |
|
|
|
|
|
const MAX_TRAJECTORY_POINTS = 100; |
|
|
// 坐标转换与渲染数量上限。 |
|
|
|
|
|
const CONVERT_BATCH_SIZE = 500; |
|
|
|
|
|
|
|
|
|
|
|
// 轨迹压缩阈值(用于减少长时间停留产生的密集点)。 |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: "DeviceTrajectoryDialog", |
|
|
name: "DeviceTrajectoryDialog", |
|
|
@ -300,22 +308,17 @@ export default { |
|
|
try { |
|
|
try { |
|
|
const response = await getDeviceTrajectory(this.device.id, this.buildTrajectoryQuery()); |
|
|
const response = await getDeviceTrajectory(this.device.id, this.buildTrajectoryQuery()); |
|
|
const data = Array.isArray(response.data) ? response.data : []; |
|
|
const data = Array.isArray(response.data) ? response.data : []; |
|
|
const validPoints = data |
|
|
const normalizedPoints = data.map((item) => { |
|
|
.map((item) => { |
|
|
const latNum = this.normalizeCoordinate(item.lat, "lat"); |
|
|
const latNum = this.normalizeCoordinate(item.lat, "lat"); |
|
|
const lngNum = this.normalizeCoordinate(item.lng, "lng"); |
|
|
const lngNum = this.normalizeCoordinate(item.lng, "lng"); |
|
|
return { |
|
|
return { |
|
|
...item, |
|
|
...item, |
|
|
latNum, |
|
|
latNum, |
|
|
lngNum, |
|
|
lngNum, |
|
|
}; |
|
|
}; |
|
|
|
|
|
}) |
|
|
|
|
|
.filter((item) => item.latNum !== null && item.lngNum !== null); |
|
|
|
|
|
const sortedPoints = validPoints.slice().sort((left, right) => { |
|
|
|
|
|
return this.getTrackTimestamp(left) - this.getTrackTimestamp(right); |
|
|
|
|
|
}); |
|
|
}); |
|
|
this.totalTrajectoryCount = sortedPoints.length; |
|
|
this.totalTrajectoryCount = normalizedPoints.length; |
|
|
this.trajectoryPoints = sortedPoints.slice(-MAX_TRAJECTORY_POINTS); |
|
|
this.trajectoryPoints = normalizedPoints; |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
this.trajectoryPoints = []; |
|
|
this.trajectoryPoints = []; |
|
|
this.totalTrajectoryCount = 0; |
|
|
this.totalTrajectoryCount = 0; |
|
|
@ -802,8 +805,13 @@ export default { |
|
|
this.map.add(this.polyline); |
|
|
this.map.add(this.polyline); |
|
|
this.map.add(this.markers); |
|
|
this.map.add(this.markers); |
|
|
|
|
|
|
|
|
const detailZoom = this.getAmapMaxZoom(); |
|
|
// 多点:自动适配全貌 |
|
|
this.map.setZoomAndCenter(detailZoom, endPoint); |
|
|
if (path.length > 1) { |
|
|
|
|
|
this.map.setFitView([this.polyline].concat(this.markers), false, [60, 60, 60, 60]); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 单点:给一个适中的细节级别 |
|
|
|
|
|
this.map.setZoomAndCenter(16, startPoint); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
renderGoogleTrajectory() { |
|
|
renderGoogleTrajectory() { |
|
|
if (!this.map || !this.mapsApi || !this.trajectoryPoints.length) { |
|
|
if (!this.map || !this.mapsApi || !this.trajectoryPoints.length) { |
|
|
@ -1038,26 +1046,34 @@ export default { |
|
|
if (!Array.isArray(this.trajectoryPoints) || !this.trajectoryPoints.length) { |
|
|
if (!Array.isArray(this.trajectoryPoints) || !this.trajectoryPoints.length) { |
|
|
return []; |
|
|
return []; |
|
|
} |
|
|
} |
|
|
if (this.trajectoryPoints.length < 2) { |
|
|
const mapPoints = this.trajectoryPoints.filter((item) => item.latNum !== null && item.lngNum !== null); |
|
|
return this.trajectoryPoints.slice(); |
|
|
if (mapPoints.length < 2) { |
|
|
|
|
|
return mapPoints; |
|
|
} |
|
|
} |
|
|
const firstTs = this.getTrackTimestamp(this.trajectoryPoints[0]); |
|
|
const firstTime = this.getTrackTimestamp(mapPoints[0]); |
|
|
const lastTs = this.getTrackTimestamp(this.trajectoryPoints[this.trajectoryPoints.length - 1]); |
|
|
const lastTime = this.getTrackTimestamp(mapPoints[mapPoints.length - 1]); |
|
|
if (firstTs > 0 && lastTs > 0 && firstTs > lastTs) { |
|
|
if (firstTime > 0 && lastTime > 0 && firstTime > lastTime) { |
|
|
return this.trajectoryPoints.slice().reverse(); |
|
|
return mapPoints.slice().reverse(); |
|
|
} |
|
|
} |
|
|
return this.trajectoryPoints.slice(); |
|
|
return mapPoints; |
|
|
}, |
|
|
}, |
|
|
getTrackTimestamp(point) { |
|
|
getTrackTimestamp(point) { |
|
|
if (!point) { |
|
|
if (!point) { |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
const value = point.locationTime || point.reportedTime || point.createTime; |
|
|
const rawValue = point.locationTime || point.reportedTime || point.createTime; |
|
|
if (!value) { |
|
|
if (rawValue === undefined || rawValue === null || rawValue === "") { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
if (typeof rawValue === "number") { |
|
|
|
|
|
return Number.isFinite(rawValue) ? rawValue : 0; |
|
|
|
|
|
} |
|
|
|
|
|
const text = String(rawValue).trim(); |
|
|
|
|
|
if (!text) { |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
const timestamp = new Date(value).getTime(); |
|
|
const parsed = new Date(text.replace(/-/g, "/")).getTime(); |
|
|
return Number.isFinite(timestamp) ? timestamp : 0; |
|
|
return Number.isFinite(parsed) ? parsed : 0; |
|
|
}, |
|
|
}, |
|
|
escapeHtml(value) { |
|
|
escapeHtml(value) { |
|
|
return String(value) |
|
|
return String(value) |
|
|
|