feat: 新增事务回填

This commit is contained in:
yarnom 2025-11-13 01:46:45 +08:00
parent 27535fc82e
commit 87135fdaf0
8 changed files with 235 additions and 9 deletions

View File

@ -0,0 +1,23 @@
package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName(value = "rtktransaction")
public class RtkTransaction {
@TableId(value = "id", type = IdType.AUTO)
Integer id;
String device_id;
Double latitude;
Double longitude;
Double altitude;
Double ecef_x;
Double ecef_y;
Double ecef_z;
Short status;
Short checked;
}

View File

@ -0,0 +1,7 @@
package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface RtkTransactionMapper extends BaseMapper<RtkTransaction> {
}

View File

@ -412,6 +412,7 @@ public class APIController extends BasicController {
} }
if(q >= 0){ if(q >= 0){
rtkMonitorService.onSolution(deviceId, q); rtkMonitorService.onSolution(deviceId, q);
try{ rtkMonitorService.onSolutionLLH(deviceId, q, lat, lon, h);}catch(Exception ignore){}
} }
} catch (Exception e){ } catch (Exception e){
String msg = "RTK " + deviceId + " " + line; String msg = "RTK " + deviceId + " " + line;

View File

@ -0,0 +1,43 @@
package com.imdroid.beidou.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imdroid.secapi.dto.RtkTransaction;
import com.imdroid.secapi.dto.RtkTransactionMapper;
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.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class RtkTransactionController extends BasicController {
@Autowired
RtkTransactionMapper mapper;
@RequestMapping("/page/rtk_transactions")
public String page(Model m, HttpSession session){
initModel(m, session);
return "/page/rtk_transactions";
}
@RequestMapping("/rtk/transaction/list")
@ResponseBody
public JSONObject list(int page, int limit, String device_id){
Page<RtkTransaction> pageable = new Page<>(page, limit);
QueryWrapper<RtkTransaction> qw = new QueryWrapper<>();
if(device_id!=null && !device_id.isEmpty()) qw.eq("device_id", device_id);
IPage<RtkTransaction> cs = mapper.selectPage(pageable, qw);
JSONObject json = new JSONObject();
json.put("code",0);
json.put("msg","");
json.put("count", cs.getTotal());
json.put("data", cs.getRecords());
return json;
}
}

View File

@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class RtkMonitorService { public class RtkMonitorService {
private final Map<String, Integer> consecFix = new ConcurrentHashMap<>(); private final Map<String, Integer> consecFix = new ConcurrentHashMap<>();
private final Map<String, Boolean> running = new ConcurrentHashMap<>(); private final Map<String, Boolean> running = new ConcurrentHashMap<>();
private final Map<String, java.util.LinkedList<double[]>> fixBuffer = new ConcurrentHashMap<>();
@Autowired @Autowired
RtkrcvClient rtkrcvClient; RtkrcvClient rtkrcvClient;
@ -21,10 +22,12 @@ public class RtkMonitorService {
public void onStart(String deviceId){ public void onStart(String deviceId){
running.put(deviceId, true); running.put(deviceId, true);
consecFix.put(deviceId, 0); consecFix.put(deviceId, 0);
fixBuffer.remove(deviceId);
} }
public void onStop(String deviceId){ public void onStop(String deviceId){
running.remove(deviceId); running.remove(deviceId);
consecFix.remove(deviceId); consecFix.remove(deviceId);
fixBuffer.remove(deviceId);
} }
public void onSolution(String deviceId, int q){ public void onSolution(String deviceId, int q){
if(Boolean.TRUE.equals(running.get(deviceId))){ if(Boolean.TRUE.equals(running.get(deviceId))){
@ -32,9 +35,17 @@ public class RtkMonitorService {
consecFix.merge(deviceId, 1, Integer::sum); consecFix.merge(deviceId, 1, Integer::sum);
} else { } else {
consecFix.put(deviceId, 0); consecFix.put(deviceId, 0);
fixBuffer.remove(deviceId);
} }
} }
} }
public void onSolutionLLH(String deviceId, int q, double lat, double lon, double h){
if(Boolean.TRUE.equals(running.get(deviceId)) && q==1){
java.util.LinkedList<double[]> buf = fixBuffer.computeIfAbsent(deviceId,k->new java.util.LinkedList<>());
buf.add(new double[]{lat,lon,h});
if(buf.size()>100) buf.removeFirst();
}
}
@Scheduled(fixedRate = 30000) @Scheduled(fixedRate = 30000)
public void check(){ public void check(){
@ -44,6 +55,7 @@ public class RtkMonitorService {
if(Boolean.TRUE.equals(running.get(dev)) && cnt != null && cnt >= 40){ if(Boolean.TRUE.equals(running.get(dev)) && cnt != null && cnt >= 40){
try { rtkrcvClient.stop(dev); } catch (Exception ignore) {} try { rtkrcvClient.stop(dev); } catch (Exception ignore) {}
running.remove(dev); running.remove(dev);
try { writeTransaction(dev); } catch (Exception ignore) {}
try { try {
com.alibaba.fastjson.JSONObject ctrl = new com.alibaba.fastjson.JSONObject(); com.alibaba.fastjson.JSONObject ctrl = new com.alibaba.fastjson.JSONObject();
ctrl.put("type","rtk_ctrl"); ctrl.put("type","rtk_ctrl");
@ -55,4 +67,41 @@ public class RtkMonitorService {
} }
} }
} }
@org.springframework.beans.factory.annotation.Autowired
com.imdroid.secapi.dto.RtkTransactionMapper rtkTransactionMapper;
private void writeTransaction(String deviceId){
java.util.LinkedList<double[]> buf = fixBuffer.get(deviceId);
if(buf==null || buf.size()<40) return;
java.util.List<double[]> last = buf.subList(Math.max(buf.size()-40,0), buf.size());
double[] latArr = new double[last.size()];
double[] lonArr = new double[last.size()];
double[] hArr = new double[last.size()];
for(int i=0;i<last.size();i++){ latArr[i]=last.get(i)[0]; lonArr[i]=last.get(i)[1]; hArr[i]=last.get(i)[2]; }
double lat = trimmedMean(latArr);
double lon = trimmedMean(lonArr);
double alt = trimmedMean(hArr);
com.imdroid.beidou.util.GeoCoordConverterUtil.LLA_Coordinate L = new com.imdroid.beidou.util.GeoCoordConverterUtil.LLA_Coordinate(lat,lon,alt);
com.imdroid.beidou.util.GeoCoordConverterUtil.ECEF_Coordinate E = com.imdroid.beidou.util.GeoCoordConverterUtil.LLA2ECEF(L);
com.imdroid.secapi.dto.RtkTransaction t = new com.imdroid.secapi.dto.RtkTransaction();
t.setDevice_id(deviceId);
t.setLatitude(lat);
t.setLongitude(lon);
t.setAltitude(alt);
t.setEcef_x(E.ECEF_X);
t.setEcef_y(E.ECEF_Y);
t.setEcef_z(E.ECEF_Z);
t.setStatus((short)1);
t.setChecked((short)0);
rtkTransactionMapper.insert(t);
fixBuffer.remove(deviceId);
}
private double trimmedMean(double[] arr){
java.util.Arrays.sort(arr);
if(arr.length<=2){ return arr.length==0?0: (arr.length==1?arr[0]:(arr[0]+arr[1])/2.0); }
double sum=0; for(int i=1;i<arr.length-1;i++) sum+=arr[i];
return sum/(arr.length-2);
}
} }

View File

@ -0,0 +1,33 @@
package com.imdroid.beidou.util;
public class GeoCoordConverterUtil {
public static class LLA_Coordinate {
public double Latitude;
public double Longitude;
public double Altitude;
public LLA_Coordinate(double lat,double lon,double alt){ this.Latitude=lat; this.Longitude=lon; this.Altitude=alt; }
}
public static class ECEF_Coordinate {
public double ECEF_X;
public double ECEF_Y;
public double ECEF_Z;
public ECEF_Coordinate(double x,double y,double z){ this.ECEF_X=x; this.ECEF_Y=y; this.ECEF_Z=z; }
}
public static ECEF_Coordinate LLA2ECEF(LLA_Coordinate L){
double a = 6378137.0;
double e2 = 6.69437999014e-3;
double lat = Math.toRadians(L.Latitude);
double lon = Math.toRadians(L.Longitude);
double h = L.Altitude;
double sinLat = Math.sin(lat);
double cosLat = Math.cos(lat);
double cosLon = Math.cos(lon);
double sinLon = Math.sin(lon);
double N = a / Math.sqrt(1 - e2 * sinLat * sinLat);
double x = (N + h) * cosLat * cosLon;
double y = (N + h) * cosLat * sinLon;
double z = (N * (1 - e2) + h) * sinLat;
return new ECEF_Coordinate(x,y,z);
}
}

View File

@ -54,10 +54,24 @@
] ]
}, },
{ {
"title": "日志", "title": "定位管理",
"href": "page/gnss_single_data", "href": "",
"icon": "fa fa-clipboard", "icon": "fa fa-clipboard",
"target": "_self" "target": "_self",
"child": [
{
"title": "通用",
"href": "page/rtkrcv",
"icon": "fa fa-minus",
"target": "_self"
},
{
"title": "事务管理",
"href": "page/rtk_transactions",
"icon": "fa fa-minus",
"target": "_self"
}
]
}, },
{ {
"title": "配置管理", "title": "配置管理",
@ -82,12 +96,6 @@
"href": "page/cmd_line", "href": "page/cmd_line",
"icon": "fa fa-minus", "icon": "fa fa-minus",
"target": "_self" "target": "_self"
},
{
"title": "定位管理",
"href": "page/rtkrcv",
"icon": "fa fa-minus",
"target": "_self"
} }
] ]
}, },

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>RTK事务</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../css/public.css" media="all">
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<fieldset class="table-search-fieldset">
<legend>查询</legend>
<div style="margin:10px">
<form class="layui-form layui-form-pane" action="" id="txSearch">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">设备号</label>
<div class="layui-input-inline">
<input type="text" name="device_id" id="tx-device" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="tx-search-btn"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</div>
</form>
</div>
</fieldset>
<table class="layui-hide" id="txTable" lay-filter="txTableFilter"></table>
</div>
</div>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
layui.use(['form','table'], function(){
var $=layui.$, form=layui.form, table=layui.table;
function render(params){
table.render({
elem:'#txTable', url:'/rtk/transaction/list', where: params||{},
cols:[ [
{field:'device_id', title:'设备号', width:120},
{field:'latitude', title:'纬度', width:140},
{field:'longitude', title:'经度', width:140},
{field:'altitude', title:'高程', width:120},
{field:'ecef_x', title:'ECEF_X'},
{field:'ecef_y', title:'ECEF_Y'},
{field:'ecef_z', title:'ECEF_Z'},
{field:'status', title:'状态', width:80, templet:function(d){ return d.status==1?'<span class="layui-badge layui-bg-green">固定</span>':'<span class="layui-badge layui-bg-orange">浮点</span>'; }},
{field:'checked', title:'已检查', width:80, templet:function(d){ return d.checked?'<span class="layui-badge layui-bg-blue"></span>':'<span class="layui-badge"></span>'; }}
] ], page:true, limits:[10,20,50,100], limit:10
});
}
render({});
form.on('submit(tx-search-btn)', function(data){ render(data.field); return false; });
});
</script>
</body>
</html>