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