feat: 新增姿态平滑

This commit is contained in:
fengyarnom 2025-06-30 16:02:33 +08:00
parent cbb1a22161
commit 3a503701af
2 changed files with 312 additions and 13 deletions

View File

@ -1,6 +1,7 @@
package com.imdroid.beidou.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.imdroid.beidou.service.CommonExcelService;
import com.imdroid.secapi.dto.GnssStatusMsg;
@ -11,12 +12,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.*;
/**
* 状态消息 控制器
@ -32,6 +34,10 @@ public class GnssMsgStatusController extends BasicController implements CommonEx
GnssStatusMsgMapper statusMsgMapper;
@Autowired
OpLogManager opLogManager;
// 滑动窗口大小
private static final int WINDOW_SIZE = 5;
@RequestMapping("/page/gnss_msg_status")
public String gnssStatusMsg(Model m, HttpSession session) {
initModel(m, session);
@ -53,6 +59,118 @@ public class GnssMsgStatusController extends BasicController implements CommonEx
}
return obj;
}
/**
* 获取特定设备的数据并应用滑动平均
*
* @param deviceId 设备ID
* @param limit 获取条数默认150条
* @return 处理后的数据
*/
@RequestMapping("/gnss/msg/status/device_data")
@ResponseBody
public JSONObject getDeviceData(@RequestParam String deviceId,
@RequestParam(required = false, defaultValue = "150") Integer limit) {
// 查询指定设备的最近数据
QueryWrapper<GnssStatusMsg> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("deviceid", deviceId)
.orderByDesc("createtime")
.last("limit " + limit);
List<GnssStatusMsg> dataList = statusMsgMapper.selectList(queryWrapper);
// 反转列表使其按时间升序排列
Collections.reverse(dataList);
// 应用滑动平均
List<GnssStatusMsg> smoothedData = applyMovingAverage(dataList, WINDOW_SIZE);
JSONObject result = new JSONObject();
result.put("code", 0);
result.put("msg", "");
result.put("count", smoothedData.size());
result.put("data", smoothedData);
result.put("originalData", dataList);
return result;
}
/**
* 应用滑动平均算法
*
* @param dataList 原始数据列表
* @param windowSize 窗口大小
* @return 处理后的数据列表
*/
private List<GnssStatusMsg> applyMovingAverage(List<GnssStatusMsg> dataList, int windowSize) {
if (dataList.size() <= 1) {
return new ArrayList<>(dataList);
}
List<GnssStatusMsg> result = new ArrayList<>(dataList.size());
// 初始窗口
Deque<Float> rollWindow = new LinkedList<>();
Deque<Float> pitchWindow = new LinkedList<>();
Deque<Float> yawWindow = new LinkedList<>();
for (int i = 0; i < dataList.size(); i++) {
GnssStatusMsg currentMsg = dataList.get(i);
GnssStatusMsg smoothedMsg = new GnssStatusMsg();
// 复制基本属性
smoothedMsg.setId(currentMsg.getId());
smoothedMsg.setDeviceid(currentMsg.getDeviceid());
smoothedMsg.setCreatetime(currentMsg.getCreatetime());
smoothedMsg.setDevicetime(currentMsg.getDevicetime());
smoothedMsg.setTenantid(currentMsg.getTenantid());
smoothedMsg.setDtustate(currentMsg.getDtustate());
smoothedMsg.setRssi(currentMsg.getRssi());
smoothedMsg.setVoltage(currentMsg.getVoltage());
smoothedMsg.setSolarvoltage(currentMsg.getSolarvoltage());
smoothedMsg.setChargecurrency(currentMsg.getChargecurrency());
smoothedMsg.setChargewatt(currentMsg.getChargewatt());
smoothedMsg.setTemperature(currentMsg.getTemperature());
smoothedMsg.setHumidity(currentMsg.getHumidity());
// 添加当前值到窗口
if (currentMsg.getRoll() != null) rollWindow.addLast(currentMsg.getRoll());
if (currentMsg.getPitch() != null) pitchWindow.addLast(currentMsg.getPitch());
if (currentMsg.getYaw() != null) yawWindow.addLast(currentMsg.getYaw());
// 保持窗口大小
if (rollWindow.size() > windowSize) rollWindow.removeFirst();
if (pitchWindow.size() > windowSize) pitchWindow.removeFirst();
if (yawWindow.size() > windowSize) yawWindow.removeFirst();
// 计算平均值
if (!rollWindow.isEmpty()) {
float rollSum = 0;
for (Float val : rollWindow) rollSum += val;
smoothedMsg.setRoll(rollSum / rollWindow.size());
} else {
smoothedMsg.setRoll(currentMsg.getRoll());
}
if (!pitchWindow.isEmpty()) {
float pitchSum = 0;
for (Float val : pitchWindow) pitchSum += val;
smoothedMsg.setPitch(pitchSum / pitchWindow.size());
} else {
smoothedMsg.setPitch(currentMsg.getPitch());
}
if (!yawWindow.isEmpty()) {
float yawSum = 0;
for (Float val : yawWindow) yawSum += val;
smoothedMsg.setYaw(yawSum / yawWindow.size());
} else {
smoothedMsg.setYaw(currentMsg.getYaw());
}
result.add(smoothedMsg);
}
return result;
}
/**
* 导出excel

View File

@ -21,7 +21,7 @@
<div class="layui-inline">
<label class="layui-form-label">设备号</label>
<div class="layui-input-inline">
<input type="text" name="s_deviceid" autocomplete="off" class="layui-input">
<input type="text" name="s_deviceid" id="deviceid" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
@ -44,7 +44,7 @@
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon">&#xe67d;</i>导出</button>
</div>
</div>
@ -52,17 +52,36 @@
</div>
</fieldset>
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
<div class="layui-tab layui-tab-card" lay-filter="data-tab">
<ul class="layui-tab-title">
<li class="layui-this">数据表格</li>
<li>姿态曲线</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
</div>
<div class="layui-tab-item">
<div id="echarts-attitude" style="min-height:500px;padding: 10px"></div>
</div>
</div>
</div>
</div>
</div>
<script src="../js/lay-module/echarts/echartsTheme.js" charset="utf-8"></script>
<script src="../js/lay-module/echarts/echarts.js" charset="utf-8"></script>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script th:inline="javascript">
layui.use(['form', 'table', 'laydate'], function () {
layui.use(['form', 'table', 'laydate', 'element'], function () {
var $ = layui.$;
var form = layui.form,
table = layui.table,
laydate = layui.laydate;
laydate = layui.laydate,
element = layui.element;
var echartsAttitude = echarts.init(document.getElementById('echarts-attitude'), 'walden');
var chartDataLoaded = false;
var cfg_cols = [
{field: 'deviceid', title: '设备号', width: 100},
@ -78,9 +97,7 @@
{field: 'temperature', title: '温度(°C)'},
{field: 'humidity', title: '湿度(%)'}
];
/**
* 初始化表单,要加上,不然刷新部分组件可能会不加载
*/
form.render();
laydate.render({
@ -95,7 +112,7 @@
table.render({
elem: '#currentTableId',
url: '/gnss/msg/status/list',
toolbar: '#toolbarDemo',//开启头部工具栏
toolbar: '#toolbarDemo',
defaultToolbar: ['filter'],
cols: [
cfg_cols
@ -106,11 +123,9 @@
skin: 'line'
});
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
var result = JSON.stringify(data.field);
//执行搜索重载
table.reload('currentTableId', {
page: {
curr: 1
@ -123,13 +138,179 @@
return false;
});
// 监听导出操作
form.on('submit(data-export-btn)', function (data) {
var result = $('#searchFrm').serialize();
var u = "/gnss/msg/status/export?" + result;
window.open(u, "_blank");
return false;
});
element.on('tab(data-tab)', function(data){
if (data.index == 1) {
echartsAttitude.resize();
if (!chartDataLoaded) {
var deviceId = $('#deviceid').val();
if (deviceId) {
loadChartData(deviceId);
} else {
layer.msg('请先在搜索框中输入设备号');
}
}
}
});
function loadChartData(deviceId) {
$.ajax({
url: '/gnss/msg/status/device_data',
type: 'GET',
data: {
deviceId: deviceId
},
success: function(res) {
if (res.code === 0 && res.data) {
showChart(res.data, res.originalData);
chartDataLoaded = true;
} else {
layer.msg('获取数据失败');
}
},
error: function() {
layer.msg('获取数据失败');
}
});
}
function showChart(smoothedData, originalData) {
var t = [];
var roll = [];
var pitch = [];
var yaw = [];
var smoothRoll = [];
var smoothPitch = [];
var smoothYaw = [];
for (var i = 0; i < originalData.length; i++) {
t.push(originalData[i].createtime);
roll.push(originalData[i].roll);
pitch.push(originalData[i].pitch);
yaw.push(originalData[i].yaw);
}
for (var i = 0; i < smoothedData.length; i++) {
smoothRoll.push(smoothedData[i].roll);
smoothPitch.push(smoothedData[i].pitch);
smoothYaw.push(smoothedData[i].yaw);
}
var option = {
title: {
text: '姿态数据曲线'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['Roll', 'Pitch', 'Yaw', '平滑Roll', '平滑Pitch', '平滑Yaw']
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: t,
axisLabel: {
formatter: function(value) {
var date = new Date(value);
return date.getFullYear() + '-' +
(date.getMonth() + 1).toString().padStart(2, '0') + '-' +
date.getDate().toString().padStart(2, '0') + ' ' +
date.getHours().toString().padStart(2, '0') + ':' +
date.getMinutes().toString().padStart(2, '0') + ':' +
date.getSeconds().toString().padStart(2, '0');
}
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: 'Roll',
type: 'line',
data: roll,
lineStyle: {
width: 1
}
},
{
name: 'Pitch',
type: 'line',
data: pitch,
lineStyle: {
width: 1
}
},
{
name: 'Yaw',
type: 'line',
data: yaw,
lineStyle: {
width: 1
}
},
{
name: '平滑Roll',
type: 'line',
data: smoothRoll,
lineStyle: {
width: 2
}
},
{
name: '平滑Pitch',
type: 'line',
data: smoothPitch,
lineStyle: {
width: 2
}
},
{
name: '平滑Yaw',
type: 'line',
data: smoothYaw,
lineStyle: {
width: 2
}
}
]
};
echartsAttitude.setOption(option);
window.onresize = function() {
echartsAttitude.resize();
}
}
});
</script>