feat 对齐数据
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
/** 热力图图层与色阶工具 */
|
/** 热力图图层与色阶工具(字段与 RtkTrack / MockRtkPoint 对齐) */
|
||||||
|
|
||||||
export type HeatmapLayerKey = 'compaction' | 'speed' | 'temperature' | 'passCount'
|
import type { MockRtkPoint } from '@/mock/rtkTrack'
|
||||||
|
|
||||||
|
export type HeatmapLayerKey = 'compaction_value' | 'speed' | 'steel_wheel_temp' | 'pass_count'
|
||||||
|
|
||||||
export const HEATMAP_LAYERS: { key: HeatmapLayerKey, label: string }[] = [
|
export const HEATMAP_LAYERS: { key: HeatmapLayerKey, label: string }[] = [
|
||||||
{ key: 'compaction', label: '压实度' },
|
{ key: 'compaction_value', label: '压实度' },
|
||||||
{ key: 'speed', label: '压实速度' },
|
{ key: 'speed', label: '压实速度' },
|
||||||
{ key: 'temperature', label: '温度' },
|
{ key: 'steel_wheel_temp', label: '温度' },
|
||||||
{ key: 'passCount', label: '碾压遍数' },
|
{ key: 'pass_count', label: '碾压遍数' },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** 色阶阈值:0–90 绿,90–95 黄,95–100 红 */
|
/** 色阶阈值:0–90 绿,90–95 黄,95–100 红 */
|
||||||
@@ -22,46 +24,37 @@ export function colorByScore(score: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 各图层原始值映射到 0–100 分,统一用 90/95 分色 */
|
/** 各图层原始值映射到 0–100 分,统一用 90/95 分色 */
|
||||||
export function layerScore(
|
export function layerScore(layer: HeatmapLayerKey, point: MockRtkPoint): number {
|
||||||
layer: HeatmapLayerKey,
|
|
||||||
point: { compaction: number, speed: number, temperature: number, passCount: number },
|
|
||||||
): number {
|
|
||||||
switch (layer) {
|
switch (layer) {
|
||||||
case 'compaction':
|
case 'compaction_value':
|
||||||
return point.compaction
|
return point.compaction_value
|
||||||
case 'speed': {
|
case 'speed': {
|
||||||
// 目标约 4~5 km/h 偏优,偏快偏慢降分
|
|
||||||
const ideal = 4.2
|
const ideal = 4.2
|
||||||
const diff = Math.abs(point.speed - ideal)
|
const diff = Math.abs(point.speed - ideal)
|
||||||
return Math.max(0, Math.min(100, 100 - diff * 18))
|
return Math.max(0, Math.min(100, 100 - diff * 18))
|
||||||
}
|
}
|
||||||
case 'temperature': {
|
case 'steel_wheel_temp': {
|
||||||
// 目标约 150℃
|
|
||||||
const ideal = 150
|
const ideal = 150
|
||||||
const diff = Math.abs(point.temperature - ideal)
|
const diff = Math.abs(point.steel_wheel_temp - ideal)
|
||||||
return Math.max(0, Math.min(100, 100 - diff * 2.5))
|
return Math.max(0, Math.min(100, 100 - diff * 2.5))
|
||||||
}
|
}
|
||||||
case 'passCount':
|
case 'pass_count':
|
||||||
// 目标遍数 6
|
return Math.max(0, Math.min(100, (point.pass_count / 6) * 100))
|
||||||
return Math.max(0, Math.min(100, (point.passCount / 6) * 100))
|
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function layerRawText(
|
export function layerRawText(layer: HeatmapLayerKey, point: MockRtkPoint): string {
|
||||||
layer: HeatmapLayerKey,
|
|
||||||
point: { compaction: number, speed: number, temperature: number, passCount: number },
|
|
||||||
): string {
|
|
||||||
switch (layer) {
|
switch (layer) {
|
||||||
case 'compaction':
|
case 'compaction_value':
|
||||||
return `${point.compaction}%`
|
return `${point.compaction_value}%`
|
||||||
case 'speed':
|
case 'speed':
|
||||||
return `${point.speed}km/h`
|
return `${point.speed}km/h`
|
||||||
case 'temperature':
|
case 'steel_wheel_temp':
|
||||||
return `${point.temperature}℃`
|
return `${point.steel_wheel_temp}℃`
|
||||||
case 'passCount':
|
case 'pass_count':
|
||||||
return `${point.passCount}遍`
|
return `${point.pass_count}遍`
|
||||||
default:
|
default:
|
||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const emit = defineEmits<{
|
|||||||
压实度
|
压实度
|
||||||
</text>
|
</text>
|
||||||
<text class="value">
|
<text class="value">
|
||||||
{{ point.compaction }}%
|
{{ point.compaction_value }}%
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="row">
|
<view class="row">
|
||||||
@@ -64,7 +64,7 @@ const emit = defineEmits<{
|
|||||||
温度
|
温度
|
||||||
</text>
|
</text>
|
||||||
<text class="value">
|
<text class="value">
|
||||||
{{ point.temperature }}℃
|
{{ point.steel_wheel_temp }}℃
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="row">
|
<view class="row">
|
||||||
@@ -72,7 +72,15 @@ const emit = defineEmits<{
|
|||||||
碾压遍数
|
碾压遍数
|
||||||
</text>
|
</text>
|
||||||
<text class="value">
|
<text class="value">
|
||||||
{{ point.passCount }}遍
|
{{ point.pass_count }}遍
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
合格率
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ point.qualified_rate }}%
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ function draw() {
|
|||||||
ctx.setLineWidth(ROLLER_WIDTH)
|
ctx.setLineWidth(ROLLER_WIDTH)
|
||||||
for (let i = 1; i < coords.length; i++) {
|
for (let i = 1; i < coords.length; i++) {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.setStrokeStyle(compactionColor(path[i].compaction))
|
ctx.setStrokeStyle(compactionColor(path[i].compaction_value))
|
||||||
ctx.moveTo(coords[i - 1].x, coords[i - 1].y)
|
ctx.moveTo(coords[i - 1].x, coords[i - 1].y)
|
||||||
ctx.lineTo(coords[i].x, coords[i].y)
|
ctx.lineTo(coords[i].x, coords[i].y)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* RTK 轨迹假数据:蛇形碾压路径 + 持续推送
|
* RTK 轨迹假数据:蛇形碾压路径 + 持续推送
|
||||||
* 字段:经纬度、航向、压实度、速度、温度、碾压遍数
|
* 字段与 `@/service/rtkTrack` 的 RtkTrack 对齐
|
||||||
*/
|
*/
|
||||||
|
import type { RtkTrack } from '@/service/rtkTrack'
|
||||||
|
|
||||||
export interface MockRtkPoint {
|
/** 假数据点:与 rtk_track 表结构一致(坐标类字段必填便于绘制) */
|
||||||
latitude: number // 纬度
|
export type MockRtkPoint = Required<
|
||||||
longitude: number // 经度
|
Pick<
|
||||||
heading: number // 航向角(度,0=北,顺时针)
|
RtkTrack,
|
||||||
compaction: number // 压实度 %
|
| 'latitude'
|
||||||
speed: number // 压实速度 km/h
|
| 'longitude'
|
||||||
temperature: number // 温度 ℃
|
| 'speed'
|
||||||
passCount: number // 碾压遍数
|
| 'heading'
|
||||||
|
| 'steel_wheel_temp'
|
||||||
|
| 'compaction_value'
|
||||||
|
| 'pass_count'
|
||||||
|
| 'qualified_rate'
|
||||||
|
>
|
||||||
|
> & {
|
||||||
|
work_record_id?: number
|
||||||
|
created_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MockRtkStreamOptions {
|
export interface MockRtkStreamOptions {
|
||||||
@@ -18,14 +27,15 @@ export interface MockRtkStreamOptions {
|
|||||||
intervalMs?: number
|
intervalMs?: number
|
||||||
/** 结束后是否循环,默认 true */
|
/** 结束后是否循环,默认 true */
|
||||||
loop?: boolean
|
loop?: boolean
|
||||||
/** 横向行数,默认 80(约 10 倍于早期 8 行) */
|
/** 横向行数,默认 80 */
|
||||||
rows?: number
|
rows?: number
|
||||||
/** 每行采样点数,默认 40 */
|
/** 每行采样点数,默认 40 */
|
||||||
pointsPerRow?: number
|
pointsPerRow?: number
|
||||||
|
/** 关联作业记录 id,默认 1 */
|
||||||
|
workRecordId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MockRtkStream {
|
export interface MockRtkStream {
|
||||||
/** 完整路径(用于画布定界) */
|
|
||||||
readonly points: MockRtkPoint[]
|
readonly points: MockRtkPoint[]
|
||||||
start: (onPoint: (point: MockRtkPoint, index: number) => void) => void
|
start: (onPoint: (point: MockRtkPoint, index: number) => void) => void
|
||||||
stop: () => void
|
stop: () => void
|
||||||
@@ -33,12 +43,11 @@ export interface MockRtkStream {
|
|||||||
getIndex: () => number
|
getIndex: () => number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 航向归一化到 [0, 360) */
|
|
||||||
function normalizeHeading(deg: number) {
|
function normalizeHeading(deg: number) {
|
||||||
return ((deg % 360) + 360) % 360
|
return ((deg % 360) + 360) % 360
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCompaction(seed: number) {
|
function makeCompactionValue(seed: number) {
|
||||||
const wave = Math.sin(seed / 4) * 4
|
const wave = Math.sin(seed / 4) * 4
|
||||||
return Number(Math.min(98, Math.max(78, 88 + wave + (Math.random() - 0.5) * 3)).toFixed(1))
|
return Number(Math.min(98, Math.max(78, 88 + wave + (Math.random() - 0.5) * 3)).toFixed(1))
|
||||||
}
|
}
|
||||||
@@ -47,7 +56,7 @@ function makeSpeed(seed: number) {
|
|||||||
return Number((3.2 + Math.sin(seed / 5) * 1.2 + Math.random() * 0.8).toFixed(1))
|
return Number((3.2 + Math.sin(seed / 5) * 1.2 + Math.random() * 0.8).toFixed(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTemperature(seed: number) {
|
function makeSteelWheelTemp(seed: number) {
|
||||||
return Number((148 + Math.sin(seed / 7) * 8 + (Math.random() - 0.5) * 6).toFixed(0))
|
return Number((148 + Math.sin(seed / 7) * 8 + (Math.random() - 0.5) * 6).toFixed(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,27 +64,36 @@ function makePassCount(seed: number) {
|
|||||||
return Math.max(1, Math.min(8, Math.round(2 + (seed % 9) / 2 + Math.random())))
|
return Math.max(1, Math.min(8, Math.round(2 + (seed % 9) / 2 + Math.random())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeQualifiedRate(compaction: number) {
|
||||||
|
return Number(Math.min(100, Math.max(70, compaction - 2 + (Math.random() - 0.5) * 4)).toFixed(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
function nowDateTime() {
|
||||||
|
return new Date().toISOString().replace('T', ' ').slice(0, 19)
|
||||||
|
}
|
||||||
|
|
||||||
function buildPoint(
|
function buildPoint(
|
||||||
lat: number,
|
lat: number,
|
||||||
lon: number,
|
lon: number,
|
||||||
heading: number,
|
heading: number,
|
||||||
seed: number,
|
seed: number,
|
||||||
|
workRecordId: number,
|
||||||
): MockRtkPoint {
|
): MockRtkPoint {
|
||||||
|
const compaction_value = makeCompactionValue(seed)
|
||||||
return {
|
return {
|
||||||
|
work_record_id: workRecordId,
|
||||||
latitude: Number(lat.toFixed(7)),
|
latitude: Number(lat.toFixed(7)),
|
||||||
longitude: Number(lon.toFixed(7)),
|
longitude: Number(lon.toFixed(7)),
|
||||||
heading,
|
|
||||||
compaction: makeCompaction(seed),
|
|
||||||
speed: makeSpeed(seed),
|
speed: makeSpeed(seed),
|
||||||
temperature: makeTemperature(seed),
|
heading,
|
||||||
passCount: makePassCount(seed),
|
steel_wheel_temp: makeSteelWheelTemp(seed),
|
||||||
|
compaction_value,
|
||||||
|
pass_count: makePassCount(seed),
|
||||||
|
qualified_rate: makeQualifiedRate(compaction_value),
|
||||||
|
created_at: nowDateTime(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 行末三角掉头:行末 → 外侧顶点 → 下行起点
|
|
||||||
* 航向先转 60°(到顶点),再转 120°(合计 180° 衔接下行)
|
|
||||||
*/
|
|
||||||
function pushUTurnTriangle(
|
function pushUTurnTriangle(
|
||||||
points: MockRtkPoint[],
|
points: MockRtkPoint[],
|
||||||
startLat: number,
|
startLat: number,
|
||||||
@@ -84,6 +102,7 @@ function pushUTurnTriangle(
|
|||||||
latStep: number,
|
latStep: number,
|
||||||
eastward: boolean,
|
eastward: boolean,
|
||||||
seedRef: { value: number },
|
seedRef: { value: number },
|
||||||
|
workRecordId: number,
|
||||||
) {
|
) {
|
||||||
const nextLat = startLat + latStep
|
const nextLat = startLat + latStep
|
||||||
const midLat = startLat + latStep / 2
|
const midLat = startLat + latStep / 2
|
||||||
@@ -99,19 +118,22 @@ function pushUTurnTriangle(
|
|||||||
tipLon,
|
tipLon,
|
||||||
normalizeHeading(fromHeading + 60 * turnSign),
|
normalizeHeading(fromHeading + 60 * turnSign),
|
||||||
seedRef.value++,
|
seedRef.value++,
|
||||||
|
workRecordId,
|
||||||
))
|
))
|
||||||
points.push(buildPoint(
|
points.push(buildPoint(
|
||||||
nextLat,
|
nextLat,
|
||||||
startLon,
|
startLon,
|
||||||
normalizeHeading(fromHeading + 180 * turnSign),
|
normalizeHeading(fromHeading + 180 * turnSign),
|
||||||
seedRef.value++,
|
seedRef.value++,
|
||||||
|
workRecordId,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生成类似示意图的左右蛇形碾压路径 */
|
/** 生成蛇形碾压路径假数据 */
|
||||||
export function generateSerpentineTrack(options: MockRtkStreamOptions = {}): MockRtkPoint[] {
|
export function generateSerpentineTrack(options: MockRtkStreamOptions = {}): MockRtkPoint[] {
|
||||||
const rows = options.rows ?? 80
|
const rows = options.rows ?? 80
|
||||||
const pointsPerRow = options.pointsPerRow ?? 40
|
const pointsPerRow = options.pointsPerRow ?? 40
|
||||||
|
const workRecordId = options.workRecordId ?? 1
|
||||||
|
|
||||||
const originLat = 39.0425
|
const originLat = 39.0425
|
||||||
const originLon = 115.8620
|
const originLon = 115.8620
|
||||||
@@ -129,13 +151,13 @@ export function generateSerpentineTrack(options: MockRtkStreamOptions = {}): Moc
|
|||||||
for (let col = 0; col < pointsPerRow; col++) {
|
for (let col = 0; col < pointsPerRow; col++) {
|
||||||
const t = eastward ? col : (pointsPerRow - 1 - col)
|
const t = eastward ? col : (pointsPerRow - 1 - col)
|
||||||
const lon = originLon + t * lonStep
|
const lon = originLon + t * lonStep
|
||||||
points.push(buildPoint(lat, lon, heading, seedRef.value++))
|
points.push(buildPoint(lat, lon, heading, seedRef.value++, workRecordId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row < rows - 1) {
|
if (row < rows - 1) {
|
||||||
const endT = eastward ? (pointsPerRow - 1) : 0
|
const endT = eastward ? (pointsPerRow - 1) : 0
|
||||||
const endLon = originLon + endT * lonStep
|
const endLon = originLon + endT * lonStep
|
||||||
pushUTurnTriangle(points, lat, endLon, heading, latStep, eastward, seedRef)
|
pushUTurnTriangle(points, lat, endLon, heading, latStep, eastward, seedRef, workRecordId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +197,7 @@ export function createMockRtkStream(options: MockRtkStreamOptions = {}): MockRtk
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const point = points[index]
|
onPoint(points[index], index)
|
||||||
onPoint(point, index)
|
|
||||||
index += 1
|
index += 1
|
||||||
}, intervalMs)
|
}, intervalMs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ definePage({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer = ref<HeatmapLayerKey>('compaction')
|
const layer = ref<HeatmapLayerKey>('compaction_value')
|
||||||
const layerVisible = ref(false)
|
const layerVisible = ref(false)
|
||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const selectedPoint = ref<MockRtkPoint | null>(null)
|
const selectedPoint = ref<MockRtkPoint | null>(null)
|
||||||
|
|||||||
Reference in New Issue
Block a user