feat: 新增姿态平滑
This commit is contained in:
parent
cbb1a22161
commit
3a503701af
@ -1,6 +1,7 @@
|
|||||||
package com.imdroid.beidou.controller;
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.imdroid.beidou.service.CommonExcelService;
|
import com.imdroid.beidou.service.CommonExcelService;
|
||||||
import com.imdroid.secapi.dto.GnssStatusMsg;
|
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.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
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;
|
GnssStatusMsgMapper statusMsgMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
OpLogManager opLogManager;
|
OpLogManager opLogManager;
|
||||||
|
|
||||||
|
// 滑动窗口大小
|
||||||
|
private static final int WINDOW_SIZE = 5;
|
||||||
|
|
||||||
@RequestMapping("/page/gnss_msg_status")
|
@RequestMapping("/page/gnss_msg_status")
|
||||||
public String gnssStatusMsg(Model m, HttpSession session) {
|
public String gnssStatusMsg(Model m, HttpSession session) {
|
||||||
initModel(m, session);
|
initModel(m, session);
|
||||||
@ -54,6 +60,118 @@ public class GnssMsgStatusController extends BasicController implements CommonEx
|
|||||||
return obj;
|
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
|
* 导出excel
|
||||||
*
|
*
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">设备号</label>
|
<label class="layui-form-label">设备号</label>
|
||||||
<div class="layui-input-inline">
|
<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>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<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"></i>导出</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-export-btn"><i class="layui-icon"></i>导出</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,17 +52,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</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>
|
||||||
</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 src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
layui.use(['form', 'table', 'laydate'], function () {
|
layui.use(['form', 'table', 'laydate', 'element'], function () {
|
||||||
var $ = layui.$;
|
var $ = layui.$;
|
||||||
var form = layui.form,
|
var form = layui.form,
|
||||||
table = layui.table,
|
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 = [
|
var cfg_cols = [
|
||||||
{field: 'deviceid', title: '设备号', width: 100},
|
{field: 'deviceid', title: '设备号', width: 100},
|
||||||
@ -78,9 +97,7 @@
|
|||||||
{field: 'temperature', title: '温度(°C)'},
|
{field: 'temperature', title: '温度(°C)'},
|
||||||
{field: 'humidity', title: '湿度(%)'}
|
{field: 'humidity', title: '湿度(%)'}
|
||||||
];
|
];
|
||||||
/**
|
|
||||||
* 初始化表单,要加上,不然刷新部分组件可能会不加载
|
|
||||||
*/
|
|
||||||
form.render();
|
form.render();
|
||||||
|
|
||||||
laydate.render({
|
laydate.render({
|
||||||
@ -95,7 +112,7 @@
|
|||||||
table.render({
|
table.render({
|
||||||
elem: '#currentTableId',
|
elem: '#currentTableId',
|
||||||
url: '/gnss/msg/status/list',
|
url: '/gnss/msg/status/list',
|
||||||
toolbar: '#toolbarDemo',//开启头部工具栏
|
toolbar: '#toolbarDemo',
|
||||||
defaultToolbar: ['filter'],
|
defaultToolbar: ['filter'],
|
||||||
cols: [
|
cols: [
|
||||||
cfg_cols
|
cfg_cols
|
||||||
@ -106,11 +123,9 @@
|
|||||||
skin: 'line'
|
skin: 'line'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听搜索操作
|
|
||||||
form.on('submit(data-search-btn)', function (data) {
|
form.on('submit(data-search-btn)', function (data) {
|
||||||
var result = JSON.stringify(data.field);
|
var result = JSON.stringify(data.field);
|
||||||
|
|
||||||
//执行搜索重载
|
|
||||||
table.reload('currentTableId', {
|
table.reload('currentTableId', {
|
||||||
page: {
|
page: {
|
||||||
curr: 1
|
curr: 1
|
||||||
@ -123,7 +138,6 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听导出操作
|
|
||||||
form.on('submit(data-export-btn)', function (data) {
|
form.on('submit(data-export-btn)', function (data) {
|
||||||
var result = $('#searchFrm').serialize();
|
var result = $('#searchFrm').serialize();
|
||||||
var u = "/gnss/msg/status/export?" + result;
|
var u = "/gnss/msg/status/export?" + result;
|
||||||
@ -131,6 +145,173 @@
|
|||||||
return false;
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user