Compare commits

...

40 Commits

Author SHA1 Message Date
a170fef04b fix: ntripcaster 2025-07-30 10:06:16 +08:00
fbde58faf6 fix: ntripcaster 2025-07-30 09:44:13 +08:00
b25a26b2c6 feat: update forward server 2025-07-29 15:08:36 +08:00
006979df31 feat: update forward server 2025-07-29 14:56:08 +08:00
bd9a6e575e feat: update forward server 2025-07-29 14:55:58 +08:00
a35cb1a431 feat: update forward server 2025-07-29 10:01:51 +08:00
weidong
e852b7c640 1、gxjs补推送断点 2025-07-27 18:50:47 +08:00
weidong
15b4cbbe3e 1、xyz显示到小数点后3位 2025-07-25 11:14:29 +08:00
weidong
032ab5c46b 1、增加手工添加补传记录 2025-07-23 17:07:53 +08:00
1875b5e848 feat: 新增多设备搜索 2025-07-22 18:56:29 +08:00
897507b97b fix: 调整D300转发间隔时间为 5s 2025-07-21 16:21:22 +08:00
weidong
40cee949a9 1、修改TCP的重连机制,只有在发送数据时才connect 2025-07-20 23:40:56 +08:00
weidong
f68d548a2c 1、修改TCP的重连机制,只有在发送数据时才connect 2025-07-20 22:53:32 +08:00
weidong
2fd12b9361 1、修改TCP的重连机制,只有在发送数据时才connect 2025-07-20 21:57:45 +08:00
weidong
e286c355ff 1、增加log查问题 2025-07-20 21:26:41 +08:00
weidong
cc5f3d0772 1、不在整点发 2025-07-20 20:12:26 +08:00
weidong
1e9e4b8680 1、不在整点发 2025-07-20 19:06:06 +08:00
weidong
6d0aa977fa 1、广西交设暂停推送历史数据 2025-07-20 15:37:25 +08:00
weidong
1045e33d1a 1、广西交设暂停推送历史数据 2025-07-20 15:37:02 +08:00
weidong
6d170eaed0 1、如果一个项目的设备数大于50,则拆包发送
2、广西交设、葛洲坝、广西路建继承同一个新发展类,基类不加Component注解
3、如果是对端强制中断TCP连接,则10秒后重连
2025-07-19 14:23:34 +08:00
weidong
bedb2e3ef5 1、增加TCP连接过程打印,以便查看连接失败原因 2025-07-19 10:46:43 +08:00
weidong
0acd7d31b8 1、增加TCP连接过程打印,以便查看连接失败原因 2025-07-19 10:27:51 +08:00
weidong
063716d539 1、增加TCP连接过程打印,以便查看连接失败原因 2025-07-19 10:01:50 +08:00
weidong
1996ef79d4 1、增加TCP连接过程打印,以便查看连接失败原因 2025-07-19 09:50:34 +08:00
weidong
87d6f6073d 1、增加TCP连接过程打印,以便查看连接失败原因 2025-07-19 09:49:26 +08:00
weidong
cef333b56b 1、取消周跳自动停止推送 2025-07-15 09:44:44 +08:00
weidong
21a6d2a8df 1、有设备长时间无d3f0但是有数据上来,增加device的统计 2025-07-14 09:24:48 +08:00
weidong
db9d27b3a0 1、接续数据增加接其他编号设备数据的功能,输入:id,datetime,如果不输入id,就是接本机数据
2、恢复周跳停止推送功能
2025-07-11 10:08:27 +08:00
weidong
d250bfc671 1、增加接续数据功能 2025-07-09 13:18:21 +08:00
7555c4c72b fix: 天气预测接口更替为开源的 Open-Meteo
- 默认情况下,将结合最适合的天气模型(auto)
- 预报 3天长度
2025-07-08 12:15:54 +08:00
d8456d165b fix: 修正地图中心计算逻辑,修正用户定位 2025-07-07 18:29:04 +08:00
fengyarnom
983efb2d6b feat: 页面优化、手机端适配、新增谷歌地图和测距功能
- 新增姿态平滑功能
- 优化页面显示效果
- 新增测距和比例尺功能
- 新增谷歌地图集成
- 适配手机端显示

# Conflicts:
#	sec-beidou/src/main/resources/templates/page/device_overview.html
2025-07-07 16:32:01 +08:00
1191c17c35 fix: 双通道 2025-07-04 09:41:08 +08:00
weidong
f9b85cd2d5 1、bug fix:进入变周期模式的设备会一直持续到模式结束,不会因为修改禁止变周期而结束 2025-07-02 22:51:49 +08:00
weidong
a6f711f18d 1、取消临时推送历史数据 2025-07-01 13:00:51 +08:00
weidong
e6ad5a0a3d 1、取消临时推送历史数据 2025-07-01 11:42:44 +08:00
weidong
ae28807818 1、取消临时推送历史数据 2025-06-30 23:26:21 +08:00
weidong
29af023a4f 1、临时推送历史数据 2025-06-30 23:07:11 +08:00
weidong
ee5bae1174 Merge remote-tracking branch 'origin/master' 2025-06-30 14:49:18 +08:00
weidong
893fe1ea78 1、把超过滤波窗口2/3的时间稳定改为1/2 2025-06-30 14:48:59 +08:00
34 changed files with 4116 additions and 1099 deletions

View File

@ -19,7 +19,8 @@ public class OpLogManager {
public final static Short OP_OBJ_GROUP = 6; public final static Short OP_OBJ_GROUP = 6;
public final static Short OP_OBJ_WARNING = 7; public final static Short OP_OBJ_WARNING = 7;
public final static Short OP_OBJ_SYS = 8; public final static Short OP_OBJ_SYS = 8;
public final static Short OP_OBJ_DEVICE_MSG = 9; public final static Short OP_OBJ_FWD_RECORD = 9;
@Autowired @Autowired
OpLogMapper opLogMapper; OpLogMapper opLogMapper;

View File

@ -1,6 +1,5 @@
package com.imdroid.beidou_fwd.service; package com.imdroid.beidou_fwd.service;
import com.imdroid.common.util.ThreadManager;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@ -13,7 +12,6 @@ import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
public class TCPClient { public class TCPClient {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -29,7 +27,7 @@ public class TCPClient {
TCPListener listener; TCPListener listener;
public void start() { public void start() {
new Thread(this::connect, "forwarder tcp-client").start(); //new Thread(this::connect, host+":"+port+" forwarder tcp-client").start();
} }
public void init(String dest_addr, int dest_port, TCPListener listener) { public void init(String dest_addr, int dest_port, TCPListener listener) {
@ -54,7 +52,7 @@ public class TCPClient {
} }
public void connect() { public void connect() {
logger.info("netty client starting"); logger.info("{}:{} tcp connecting...",host,port);
//启动客户端去连接服务器端 //启动客户端去连接服务器端
try { try {
ChannelFuture cf = bootstrap.connect(host, port); ChannelFuture cf = bootstrap.connect(host, port);
@ -62,17 +60,22 @@ public class TCPClient {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) { if (!future.isSuccess()) {
logger.info("{}:{} tcp connect failed. {}",host,port,future.cause().toString());
//重连交给后端线程执行 //重连交给后端线程执行
future.channel().eventLoop().schedule(() -> { /*future.channel().eventLoop().schedule(() -> {
logger.info("tcp client reconnect"); logger.info("{}:{} tcp client reconnect",host,port);
try { try {
connect(); connect();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
}, 5000, TimeUnit.MILLISECONDS); }, 5000, TimeUnit.MILLISECONDS);*/
} else { } else {
logger.info("tcp client start success!"); /*future.channel().config().setWriteBufferWaterMark(new WriteBufferWaterMark(
1024 * 1024, // low
4 *1024*1024 // high
));*/
logger.info("{}:{} tcp client start success!",host,port);
} }
} }
}); });
@ -80,18 +83,35 @@ public class TCPClient {
this.channel = cf.channel(); this.channel = cf.channel();
this.channel.closeFuture().sync(); this.channel.closeFuture().sync();
} catch (Exception e) { } catch (Exception e) {
logger.error("netty client error:", e); logger.error(host+":"+port+" tcp connect error:", e);
} }
} }
boolean tryReconnect() throws Exception{
new Thread(this::connect, host+":"+port+" forwarder tcp-client").start();
for(int i=0; i<20; i++){
Thread.sleep(50);
if(channel!=null && channel.isActive()) return true;
}
return false;
}
public void writeAndFlush(String json) { public void writeAndFlush(String json) {
ByteBuf sendBuffer = Unpooled.buffer(); ByteBuf sendBuffer = Unpooled.buffer();
sendBuffer.writeBytes(json.getBytes(StandardCharsets.UTF_8)); sendBuffer.writeBytes(json.getBytes(StandardCharsets.UTF_8));
//logger.info("send to {}: {}",host,json);
if(channel==null || !channel.isActive()){
try {
if(!tryReconnect()) return;
} catch (Exception e) {
logger.error(e.toString());
}
}
channel.writeAndFlush(sendBuffer).addListener(future -> { channel.writeAndFlush(sendBuffer).addListener(future -> {
if (future.isSuccess()) { if (future.isSuccess()) {
logger.info("send to tcp:"+host+" succeed."); logger.info("send to tcp:"+host+" succeed.");
} else { } else {
logger.info("send to tcp:"+host+" failed."); logger.info("send to tcp: {} failed. {}",host,future.cause().toString());
if(listener!=null){ if(listener!=null){
listener.onMessage("failed"); listener.onMessage("failed");
} }
@ -103,8 +123,8 @@ public class TCPClient {
connectTime = LocalDateTime.now(); connectTime = LocalDateTime.now();
} }
public void onDisconnect(){ public void onDisconnect(boolean isIdle){
if(connectTime.isBefore(LocalDateTime.now().minusMinutes(1))) { /*if(connectTime.isBefore(LocalDateTime.now().minusMinutes(1))) {
connect(); connect();
} }
else{ else{
@ -114,8 +134,8 @@ public class TCPClient {
} catch (Exception e) { } catch (Exception e) {
logger.error(e.toString()); logger.error(e.toString());
} }
},60, TimeUnit.SECONDS); },isIdle?30:10, TimeUnit.SECONDS);
} }*/
} }
public void onMessage(String msg){ public void onMessage(String msg){

View File

@ -40,13 +40,14 @@ public class TcpMessageHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("tcp channel inactive"); logger.info("tcp channel inactive");
tcpClient.onDisconnect(); tcpClient.onDisconnect(true);
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("TcpMessageHandler error: {}", cause.toString()); logger.error("TcpMessageHandler error: {}", cause.toString());
ctx.close(); ctx.close();
tcpClient.onDisconnect(false);
} }
} }

View File

@ -37,17 +37,17 @@ public class Forwarder {
static boolean isFwdTableInit = true;//false; static boolean isFwdTableInit = true;//false;
@Autowired @Autowired
private GnssDeviceMapper deviceMapper; GnssDeviceMapper deviceMapper;
@Autowired @Autowired
private GnssCalcDataMapper gnssDataMapper; GnssCalcDataMapper gnssDataMapper;
@Autowired @Autowired
private FwdRecordMapper fwdRecordsMapper; FwdRecordMapper fwdRecordsMapper;
@Autowired @Autowired
private ResendRecordMapper resendRecordMapper; ResendRecordMapper resendRecordMapper;
@Autowired @Autowired
private GnssStatusMapper gnssStatusMapper; GnssStatusMapper gnssStatusMapper;
@Autowired @Autowired
GnssGroupFwdMapper fwdMapper; GnssGroupFwdMapper fwdMapper;

View File

@ -49,7 +49,7 @@ public class GXJKForwarder extends Forwarder {
/** /**
* 每半小时转发GNSS解算结果 * 每半小时转发GNSS解算结果
*/ */
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次 @Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
private void forwardGnss() { private void forwardGnss() {
logger.debug("gxjk forwardGnss"); logger.debug("gxjk forwardGnss");
if(mqttClient.isConnected()) forwardCurrentGnss(); if(mqttClient.isConnected()) forwardCurrentGnss();

View File

@ -0,0 +1,75 @@
package com.imdroid.beidou_fwd.task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.imdroid.beidou_fwd.service.TCPClient;
import com.imdroid.secapi.dto.ResendRecord;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.List;
@Component
@Configuration
@EnableScheduling
public class GXJSForwarder extends GXXfzForwarder{
private final String FORWARDER_NAME = "广西新发展";
@Value("${xfz.server.host}")
private String host;
@Value("${xfz.server.port}")
private int port;
private boolean enabled=true;
@PostConstruct
void registerMe(){
init(FORWARDER_NAME, "TCP "+host+":"+port,1,FWD_DEVICE_ID,30);
xfzTcpClient = new TCPClient();
xfzTcpClient.init(host, port,listener);
if(!enabled) return;
xfzTcpClient.start();
}
/**
* 每半小时转发GNSS解算结果
*/
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
private void forwardGnss() {
if(!enabled) return;
logger.debug("gxjs forwardGnss");
forwardCurrentGnss();
}
@Override
void forwardHistoryGnss(){
// 1.从转发记录表里检索待补传记录时间表含设备Id时间段
QueryWrapper<ResendRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("fwd_group_id",fwdGroupId);
queryWrapper.eq("state",ResendRecord.STATE_BREAK_POINT);
queryWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
List<ResendRecord> resendRecordsList = resendRecordMapper.selectList(queryWrapper);
if(resendRecordsList!=null && resendRecordsList.size()>0){
//修改状态
UpdateWrapper<ResendRecord> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("fwd_group_id",fwdGroupId);
updateWrapper.eq("state",ResendRecord.STATE_BREAK_POINT);
updateWrapper.ge("createtime", LocalDateTime.now().minusDays(30));
updateWrapper.set("state",ResendRecord.STATE_FWDING);
int updateNum = resendRecordMapper.update(null, updateWrapper);
logger.debug("{} forward history records: {}, update {}",fwdGroupId, resendRecordsList.size(),updateNum);
// 2.检索这个这个时间段的解算结果如果有数据则单个终端转发标志记录为已补传
for(ResendRecord record:resendRecordsList){
if(record.getProjectid()!=null)
logger.debug("{} forward history {}",fwdGroupId, record.getProjectid());
forwardBatchGnssRecords(record);
}
}
}
}

View File

@ -4,6 +4,7 @@ import com.imdroid.beidou_fwd.service.TCPClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -11,20 +12,31 @@ import javax.annotation.PostConstruct;
@Component @Component
@Configuration @Configuration
@EnableScheduling @EnableScheduling
public class GXXfz2Forwarder extends GXXfzForwarder{ public class GXLJForwarder extends GXXfzForwarder{
private String FORWARDER_NAME = "广西路建"; private final String FORWARDER_NAME = "广西路建";
@Value("${gxlj.server.host}") @Value("${gxlj.server.host}")
private String host; private String host;
@Value("${gxlj.server.port}") @Value("${gxlj.server.port}")
private int port; private int port;
private boolean enabled=true;
@PostConstruct @PostConstruct
@Override
void registerMe(){ void registerMe(){
init(FORWARDER_NAME, "TCP "+host+":"+port,6,FWD_DEVICE_ID,30); init(FORWARDER_NAME, "TCP "+host+":"+port,6,FWD_DEVICE_ID,30);
xfzTcpClient = new TCPClient(); xfzTcpClient = new TCPClient();
xfzTcpClient.init(host, port,listener); xfzTcpClient.init(host, port,listener);
if(!enabled) return;
xfzTcpClient.start(); xfzTcpClient.start();
} }
/**
* 每半小时转发GNSS解算结果
*/
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
private void forwardGnss() {
if(!enabled) return;
logger.debug("gxlj forwardGnss");
forwardCurrentGnss();
}
} }

View File

@ -6,31 +6,15 @@ import com.imdroid.beidou_fwd.service.TCPListener;
import com.imdroid.common.util.GsonUtil; import com.imdroid.common.util.GsonUtil;
import com.imdroid.common.util.NumberUtils; import com.imdroid.common.util.NumberUtils;
import com.imdroid.secapi.dto.GnssCalcData; import com.imdroid.secapi.dto.GnssCalcData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Component
@Configuration
@EnableScheduling
public class GXXfzForwarder extends Forwarder{ public class GXXfzForwarder extends Forwarder{
private final String FORWARDER_NAME = "广西新发展";
@Value("${xfz.server.host}")
private String host;
@Value("${xfz.server.port}")
private int port;
TCPClient xfzTcpClient; TCPClient xfzTcpClient;
static class XFZTCPListener implements TCPListener{ class XFZTCPListener implements TCPListener{
public static final int STATE_NO_ACK = 0; public static final int STATE_NO_ACK = 0;
public static final int STATE_OK = 1; public static final int STATE_OK = 1;
public static final int STATE_FAILED = 2; public static final int STATE_FAILED = 2;
@ -51,36 +35,16 @@ public class GXXfzForwarder extends Forwarder{
@Override @Override
public void onMessage(String msg) { public void onMessage(String msg) {
logger.info("{} client rx: {}",fwdGroupId, msg);
if(msg.contains("succeed")) state = STATE_OK; if(msg.contains("succeed")) state = STATE_OK;
else state = STATE_FAILED; else state = STATE_FAILED;
} }
} }
XFZTCPListener listener = new XFZTCPListener(); XFZTCPListener listener = new XFZTCPListener();
@PostConstruct
void registerMe(){
init(FORWARDER_NAME, "TCP "+host+":"+port,1,FWD_DEVICE_ID,30);
xfzTcpClient = new TCPClient();
xfzTcpClient.init(host, port,listener);
xfzTcpClient.start();
}
/**
* 每半小时转发GNSS解算结果
*/
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
private void forwardGnss() {
logger.debug("xfz forwardGnss");
forwardCurrentGnss();
}
/*
@Scheduled(cron = "0 0/10 * * * ?") // 每30分钟执行一次
private void checkDevice() {
//logger.debug("zny checkDevice");
checkOfflineDevice("2345053","2350106","2350124");
}*/
@Override @Override
int send(String projectId, List<GnssCalcData> records, LocalDateTime sentTime){ int send(String projectId, List<GnssCalcData> records, LocalDateTime sentTime){
int batchNum = 0;
int sendNum = 0; int sendNum = 0;
if(records.size() == 0) return 0; if(records.size() == 0) return 0;
@ -104,27 +68,48 @@ public class GXXfzForwarder extends Forwarder{
// 经纬度 // 经纬度
data.setDevLng(locationRecord.getR9250e()); data.setDevLng(locationRecord.getR9250e());
data.setDevLat(locationRecord.getR9250n()); data.setDevLat(locationRecord.getR9250n());
sendNum++; // 发送
} batchNum++;
if(batchNum==20){
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!"; String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
logger.debug("project " + projectId + ": push calculation result to XFZ"); //logger.debug("project {}: forwad {} gnss records to {}",projectId, dataList.size(),fwdGroupId);
//logger.debug(json);
try {
listener.clear();
xfzTcpClient.writeAndFlush(json);
//等待应答
if(checkResult()) sendNum += batchNum;
} catch (Exception e1) {
logger.error(e1.toString());
}
batchNum = 0;
dataList.clear();
}
}
if(batchNum>0){
String json = "#" + GsonUtil.toJson(xfzTcpMessage) + "!";
logger.debug("project {}: forwad {} gnss records to {}",projectId, dataList.size(),fwdGroupId);
logger.debug(json); logger.debug(json);
try { try {
listener.clear(); listener.clear();
xfzTcpClient.writeAndFlush(json); xfzTcpClient.writeAndFlush(json);
//等待应答 //等待应答
if(!checkResult()) sendNum = 0; if(checkResult()) sendNum += batchNum;
} catch (Exception e1) { } catch (Exception e1) {
sendNum = 0; logger.error(e1.toString());
e1.printStackTrace(); }
dataList.clear();
} }
return sendNum; return sendNum;
} }
boolean checkResult() throws InterruptedException { boolean checkResult() throws InterruptedException {
// 等待应答最多等1s // 等待应答最多等500ms
for(int i=0; i<10; i++){ for(int i=0; i<10; i++){
Thread.sleep(100); Thread.sleep(50);
if(listener.state == XFZTCPListener.STATE_OK) return true; if(listener.state == XFZTCPListener.STATE_OK) return true;
else if(listener.state == XFZTCPListener.STATE_FAILED) return false; else if(listener.state == XFZTCPListener.STATE_FAILED) return false;
} }

View File

@ -4,6 +4,7 @@ import com.imdroid.beidou_fwd.service.TCPClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -12,20 +13,30 @@ import javax.annotation.PostConstruct;
@Configuration @Configuration
@EnableScheduling @EnableScheduling
public class GZBForwarder extends GXXfzForwarder{ public class GZBForwarder extends GXXfzForwarder{
private String FORWARDER_NAME = "葛洲坝"; private final String FORWARDER_NAME = "葛洲坝";
@Value("${gzb.server.host}") @Value("${gzb.server.host}")
private String host; private String host;
@Value("${gzb.server.port}") @Value("${gzb.server.port}")
private int port; private int port;
private boolean enabled=true;
@PostConstruct @PostConstruct
@Override
void registerMe(){ void registerMe(){
init(FORWARDER_NAME, "TCP "+host+":"+port,1,FWD_DEVICE_ID,30); init(FORWARDER_NAME, "TCP "+host+":"+port,1,FWD_DEVICE_ID,30);
xfzTcpClient = new TCPClient(); xfzTcpClient = new TCPClient();
xfzTcpClient.init(host, port,listener); xfzTcpClient.init(host, port,listener);
if(!enabled) return;
xfzTcpClient.start(); xfzTcpClient.start();
} }
/**
* 每半小时转发GNSS解算结果
*/
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
private void forwardGnss() {
if(!enabled) return;
logger.debug("gzb forwardGnss");
forwardCurrentGnss();
}
} }

View File

@ -28,17 +28,18 @@ import java.util.List;
@EnableScheduling @EnableScheduling
public class GZYMQTTForwarder extends Forwarder { public class GZYMQTTForwarder extends Forwarder {
static final String FORWARDER_NAME = "贵州交勘院MQTT"; static final String FORWARDER_NAME = "贵州交勘院MQTT";
@Value("${mqtt.server.brokerUrl}") @Value("${gzymqtt.server.brokerUrl}")
private String brokerUrl; private String brokerUrl;
@Value("${mqtt.server.username}") @Value("${gzymqtt.server.username}")
private String username; private String username;
@Value("${mqtt.server.password}") @Value("${gzymqtt.server.password}")
private String password; private String password;
@Value("${mqtt.server.clientid}") @Value("${gzymqtt.server.clientid}")
private String clientid; private String clientid;
@Value("${mqtt.server.topic}") @Value("${gzymqtt.server.topic}")
private String topic; private String topic;
//@Value("${gzymqtt.server.enabled}")
private boolean enabled=true;
@Autowired @Autowired
GnssStatusMsgMapper statusMsgMapper; GnssStatusMsgMapper statusMsgMapper;
MQTTClient mqttClient; MQTTClient mqttClient;
@ -47,6 +48,7 @@ public class GZYMQTTForwarder extends Forwarder {
void registerMe() throws MqttException { void registerMe() throws MqttException {
init(FORWARDER_NAME, "MQTT "+brokerUrl,2,FWD_DEVICE_ALIAS_NAME,30); init(FORWARDER_NAME, "MQTT "+brokerUrl,2,FWD_DEVICE_ALIAS_NAME,30);
mqttClient = new MQTTClient(brokerUrl, username, password,clientid); mqttClient = new MQTTClient(brokerUrl, username, password,clientid);
if(!enabled) return;
try{ try{
mqttClient.connect(); mqttClient.connect();
} }
@ -61,6 +63,7 @@ public class GZYMQTTForwarder extends Forwarder {
@Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次 @Scheduled(cron = "0 0 0/1 * * ?") // 每小时执行一次
//@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次 //@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
private void forwardGnss() { private void forwardGnss() {
if(!enabled) return;
logger.debug("gzy mqtt forwardGnss"); logger.debug("gzy mqtt forwardGnss");
if(mqttClient.isConnected()) { if(mqttClient.isConnected()) {
forwardCurrentGnss(); forwardCurrentGnss();
@ -75,6 +78,11 @@ public class GZYMQTTForwarder extends Forwarder {
} }
} }
} }
/*
@Scheduled(cron = "0 0/5 * * * ?") // 每30分钟执行一次
void forwardHistoryGnss() {
super.forwardHistoryGnss();
}*/
@Override @Override
int send(String projectId, List<GnssCalcData> records, LocalDateTime sentTime) { int send(String projectId, List<GnssCalcData> records, LocalDateTime sentTime) {

View File

@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
/** /**
@ -17,6 +18,7 @@ import org.springframework.context.annotation.ComponentScan;
@ComponentScan({"com.imdroid.*"}) @ComponentScan({"com.imdroid.*"})
@EntityScan({"com.imdroid.*"}) @EntityScan({"com.imdroid.*"})
@EnableFeignClients(basePackages = "com.imdroid.*") @EnableFeignClients(basePackages = "com.imdroid.*")
@EnableScheduling
public class SideSlopeRtcmApp { public class SideSlopeRtcmApp {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -44,11 +44,14 @@ public class GNSSCalcFilterService {
Integer xyfilterCycle = groupCalc.getFilter_hour(); Integer xyfilterCycle = groupCalc.getFilter_hour();
Integer zfilterCycle = groupCalc.getZfilter_hour(); Integer zfilterCycle = groupCalc.getZfilter_hour();
if(null == zfilterCycle) zfilterCycle = xyfilterCycle; if(null == zfilterCycle) zfilterCycle = xyfilterCycle;
if(groupCalc.getAuto_filter()) {
VaryFilterCycle varyCycle = autoCycleDevices.get(deviceId); VaryFilterCycle varyCycle = autoCycleDevices.get(deviceId);
if(varyCycle!=null){ if (varyCycle != null) {
xyfilterCycle = varyCycle.filterCycleHour; xyfilterCycle = varyCycle.filterCycleHour;
zfilterCycle = varyCycle.filterCycleHour; zfilterCycle = varyCycle.filterCycleHour;
} }
}
else autoCycleDevices.remove(deviceId);
// 平滑处理 // 平滑处理
calcFilterLocation(newRecord, referPos, xyfilterCycle, zfilterCycle, groupCalc.getFilter_min_hour(), calcFilterLocation(newRecord, referPos, xyfilterCycle, zfilterCycle, groupCalc.getFilter_min_hour(),
@ -166,7 +169,7 @@ public class GNSSCalcFilterService {
newRecord.setAuxn(NumberUtils.scaleTwo(msumN / minCount)); newRecord.setAuxn(NumberUtils.scaleTwo(msumN / minCount));
newRecord.setAuxd(NumberUtils.scaleTwo(msumD / minCount)); newRecord.setAuxd(NumberUtils.scaleTwo(msumD / minCount));
//滤波窗口里的时间跨度超过滤波时间的2/3才认为稳定 //滤波窗口里的时间跨度超过滤波时间的2/3才认为稳定
newRecord.setStabled(lastRecordTime.isBefore(newRecordTime.minusHours(filterCycleHour * 2 / 3))); newRecord.setStabled(lastRecordTime.isBefore(newRecordTime.minusHours(filterCycleHour * 1 / 2)));
return true; return true;
} }
} }
@ -246,7 +249,7 @@ public class GNSSCalcFilterService {
newRecord.setAuxn(NumberUtils.scaleTwo(msumN / minCount)); newRecord.setAuxn(NumberUtils.scaleTwo(msumN / minCount));
newRecord.setAuxd(NumberUtils.scaleTwo(msumD / minCount)); newRecord.setAuxd(NumberUtils.scaleTwo(msumD / minCount));
//滤波窗口里的时间跨度超过滤波时间的2/3才认为稳定 //滤波窗口里的时间跨度超过滤波时间的2/3才认为稳定
newRecord.setStabled(lastRecordTime.isBefore(newRecordTime.minusHours(filterCycleHour * 2 / 3))); newRecord.setStabled(lastRecordTime.isBefore(newRecordTime.minusHours(filterCycleHour * 1 / 2)));
return true; return true;
} }
} }

View File

@ -0,0 +1,123 @@
package com.imdroid.sideslope.config;
import com.imdroid.sideslope.server.tcp.DeviceTcpPortManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RtcmPortConfigManager {
private static final Logger logger = LoggerFactory.getLogger(RtcmPortConfigManager.class);
@Value("${rtcm.config.file:/root/beidou/config/rtcm_port}")
private String configFilePath;
@Autowired
private DeviceTcpPortManager deviceTcpPortManager;
// 当前配置的设备和端口映射
private final Map<String, Integer> currentConfig = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// 确保配置目录存在
File configFile = new File(configFilePath);
if (!configFile.getParentFile().exists()) {
configFile.getParentFile().mkdirs();
}
// 初始加载配置
loadConfig();
}
@Scheduled(fixedRate = 60000) // 每60秒执行一次
public void scheduledLoadConfig() {
loadConfig();
}
private void loadConfig() {
Map<String, Integer> newConfig = new HashMap<>();
File configFile = new File(configFilePath);
if (!configFile.exists()) {
logger.info("Config file not found: {}, will create when needed", configFilePath);
return;
}
try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue; // 跳过空行和注释
}
String[] parts = line.split("\\s+");
if (parts.length >= 2) {
try {
String deviceId = parts[0];
int port = Integer.parseInt(parts[1]);
newConfig.put(deviceId, port);
} catch (NumberFormatException e) {
logger.warn("Invalid port number in config file: {}", line);
}
}
}
} catch (IOException e) {
logger.error("Error reading config file: {}", configFilePath, e);
return;
}
// 找出需要移除的设备
Set<String> devicesToRemove = new HashSet<>(currentConfig.keySet());
devicesToRemove.removeAll(newConfig.keySet());
// 找出新增或更新的设备
Map<String, Integer> devicesToUpdate = new HashMap<>(newConfig);
// 移除不再需要的设备
for (String deviceId : devicesToRemove) {
deviceTcpPortManager.removeDevice(deviceId);
logger.info("Removed device {} from TCP port manager", deviceId);
}
// 更新或添加新的设备配置
for (Map.Entry<String, Integer> entry : devicesToUpdate.entrySet()) {
String deviceId = entry.getKey();
int port = entry.getValue();
// 如果端口发生变化需要重新创建
Integer currentPort = currentConfig.get(deviceId);
if (currentPort == null || !currentPort.equals(port)) {
if (currentPort != null) {
deviceTcpPortManager.removeDevice(deviceId);
}
deviceTcpPortManager.addDevice(deviceId, port);
logger.info("Updated device {} to use port {}", deviceId, port);
}
}
// 更新当前配置
currentConfig.clear();
currentConfig.putAll(newConfig);
logger.info("Config updated, current devices: {}", currentConfig);
}
public boolean isDeviceEnabled(String deviceId) {
return currentConfig.containsKey(deviceId);
}
public Integer getDevicePort(String deviceId) {
return currentConfig.get(deviceId);
}
}

View File

@ -9,12 +9,14 @@ import com.imdroid.sideslope.bd.Gga;
import com.imdroid.sideslope.bd.Rtcm1005; import com.imdroid.sideslope.bd.Rtcm1005;
import com.imdroid.sideslope.message.D331RtcmMessage; import com.imdroid.sideslope.message.D331RtcmMessage;
import com.imdroid.sideslope.ntrip.UdpNtripServer; import com.imdroid.sideslope.ntrip.UdpNtripServer;
import com.imdroid.sideslope.server.tcp.DeviceTcpPortManager;
import com.imdroid.sideslope.service.Device; import com.imdroid.sideslope.service.Device;
import com.imdroid.sideslope.service.DeviceService; import com.imdroid.sideslope.service.DeviceService;
import com.imdroid.sideslope.server.DeviceChannel; import com.imdroid.sideslope.server.DeviceChannel;
import com.imdroid.sideslope.server.OnlineChannels; import com.imdroid.sideslope.server.OnlineChannels;
import com.imdroid.sideslope.service.DataPersistService; import com.imdroid.sideslope.service.DataPersistService;
import com.imdroid.sideslope.bd.RtcmGgaUtil; import com.imdroid.sideslope.bd.RtcmGgaUtil;
import com.imdroid.sideslope.config.RtcmPortConfigManager;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -45,10 +47,14 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
private DataPersistService dataPersistService; private DataPersistService dataPersistService;
@Autowired @Autowired
UdpNtripServer ntripServer; UdpNtripServer ntripServer;
@Autowired
private DeviceTcpPortManager deviceTcpPortManager;
@Autowired
private RtcmPortConfigManager rtcmPortConfigManager;
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间 // 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>(); private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
private static final long D300_FORWARD_INTERVAL = 10000; // 10秒单位毫秒 private static final long D300_FORWARD_INTERVAL = 5000; // 5单位毫秒
@Override @Override
public Void execute(D331RtcmMessage message) { public Void execute(D331RtcmMessage message) {
@ -275,22 +281,49 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
return null; return null;
} }
private void sendToNtrip(String mountpoint, String hexData) { private void sendToNtrip(String deviceId, String hexData) {
try {
// 将原始字节转换为16进制字符串用于RTCM提取
//String hexData = ByteUtil.bytesToHexString(rawData);
//System.out.println(hexData);
// 提取RTCM数据并发送到NtripServer,使用设备ID作为挂载点
Optional.ofNullable(RtcmGgaUtil.getRtcms(hexData)) Optional.ofNullable(RtcmGgaUtil.getRtcms(hexData))
.ifPresent(rtcm -> { .ifPresent(rtcm -> {
//System.out.println("挂载点: " + mountpoint); //System.out.println("挂载点: " + mountpoint);
//System.out.println("RTCM数据: " + rtcm); //System.out.println("RTCM数据: " + rtcm);
ntripServer.send(mountpoint, rtcm); ntripServer.send(deviceId, rtcm);
}); });
// 检查设备是否启用TCP转发
if (!rtcmPortConfigManager.isDeviceEnabled(deviceId)) {
logger.info("Device {} not enabled for TCP forwarding", deviceId);
return;
}
try {
// 添加原始数据日志
logger.info("Processing hex data for device {}, data length: {}", deviceId, hexData.length());
if (logger.isDebugEnabled()) {
logger.debug("Raw hex data: {}", hexData);
}
// 提取RTCM数据并通过TCP转发
List<String> rtcmList = RtcmGgaUtil.getRtcms(hexData);
logger.info("Extracted {} RTCM messages for device {}", rtcmList != null ? rtcmList.size() : 0, deviceId);
if (rtcmList != null && !rtcmList.isEmpty()) {
for (String rtcm : rtcmList) {
try {
byte[] data = ByteUtil.hexStringTobyte(rtcm);
logger.info("Forwarding RTCM data for device {}, data length: {}", deviceId, data.length);
if (logger.isDebugEnabled()) {
logger.debug("RTCM data: {}", rtcm);
}
deviceTcpPortManager.sendData(deviceId, data);
} catch (Exception e) { } catch (Exception e) {
logger.error("处理NTRIP数据失败, 挂载点: {}, 错误: {}", mountpoint, e.getMessage()); logger.error("Error forwarding RTCM data for device {}: {}", deviceId, e.getMessage(), e);
}
}
} else {
logger.info("No RTCM data found in message for device {}", deviceId);
}
} catch (Exception e) {
logger.error("处理RTCM数据失败, 设备ID: {}, 错误: {}", deviceId, e.getMessage(), e);
} }
} }

View File

@ -0,0 +1,98 @@
package com.imdroid.sideslope.server.tcp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class DeviceTcpPortManager {
private static final Logger logger = LoggerFactory.getLogger(DeviceTcpPortManager.class);
private final Map<String, Integer> devicePortMap = new ConcurrentHashMap<>();
private final Map<Integer, RtcmForwardServer> portServerMap = new ConcurrentHashMap<>();
public synchronized void addDevice(String deviceId, int port) {
if (port <= 0) {
logger.error("Invalid port {} for device {}", port, deviceId);
return;
}
// 检查端口是否已被其他设备使用
if (portServerMap.containsKey(port)) {
logger.error("Port {} is already in use", port);
return;
}
try {
logger.info("Creating new TCP forward server for device {} on port {}", deviceId, port);
RtcmForwardServer server = new RtcmForwardServer(port);
server.start();
portServerMap.put(port, server);
devicePortMap.put(deviceId, port);
logger.info("Successfully created TCP forward server for device {} on port {}", deviceId, port);
} catch (Exception e) {
logger.error("Failed to create TCP server for device {} on port {}: {}", deviceId, port, e.getMessage(), e);
}
}
public void removeDevice(String deviceId) {
Integer port = devicePortMap.remove(deviceId);
if (port != null) {
logger.info("Removing TCP forward server for device {} on port {}", deviceId, port);
RtcmForwardServer server = portServerMap.remove(port);
if (server != null) {
server.stop();
logger.info("Successfully removed TCP forward server for device {} on port {}", deviceId, port);
} else {
logger.warn("Server not found for device {} on port {}", deviceId, port);
}
} else {
logger.info("No port mapping found for device {} to remove", deviceId);
}
}
public void sendData(String deviceId, byte[] data) {
if (data == null || data.length == 0) {
logger.warn("Empty data received for device {}", deviceId);
return;
}
Integer port = devicePortMap.get(deviceId);
if (port != null) {
RtcmForwardServer server = portServerMap.get(port);
if (server != null) {
try {
server.broadcast(data);
logger.info("Successfully forwarded {} bytes of data for device {} to port {}",
data.length, deviceId, port);
} catch (Exception e) {
logger.error("Error broadcasting data for device {} on port {}: {}",
deviceId, port, e.getMessage(), e);
}
} else {
logger.error("Server not found for device {} on port {}", deviceId, port);
// 尝试重新创建服务器
logger.info("Attempting to recreate server for device {} on port {}", deviceId, port);
addDevice(deviceId, port);
}
} else {
logger.error("No port mapping found for device {}", deviceId);
}
}
public Map<String, Integer> getActiveDevicePorts() {
return new HashMap<>(devicePortMap);
}
public boolean hasDevice(String deviceId) {
return devicePortMap.containsKey(deviceId);
}
public Integer getDevicePort(String deviceId) {
return devicePortMap.get(deviceId);
}
}

View File

@ -0,0 +1,171 @@
package com.imdroid.sideslope.server.tcp;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class RtcmForwardServer {
private static final Logger logger = LoggerFactory.getLogger(RtcmForwardServer.class);
private final int port;
private final EventLoopGroup bossGroup;
private final EventLoopGroup workerGroup;
private Channel serverChannel;
private final ConcurrentMap<Channel, Boolean> connectedClients;
private volatile boolean isRunning = false;
public RtcmForwardServer(int port) {
this.port = port;
this.bossGroup = new NioEventLoopGroup(1);
this.workerGroup = new NioEventLoopGroup();
this.connectedClients = new ConcurrentHashMap<>();
}
public synchronized void start() {
if (isRunning) {
logger.warn("Server on port {} is already running", port);
return;
}
try {
logger.info("Starting RTCM forward server on port {}", port);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
logger.info("New client connection on port {}: {}", port, ch.remoteAddress());
ChannelPipeline p = ch.pipeline();
p.addLast(new ByteArrayEncoder());
p.addLast(new SimpleChannelInboundHandler<Object>() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
connectedClients.put(ctx.channel(), true);
logger.info("Client connected to port {}: {}", port, ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
connectedClients.remove(ctx.channel());
logger.info("Client disconnected from port {}: {}", port, ctx.channel().remoteAddress());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
// 我们不需要处理来自客户端的数据
logger.debug("Received data from client on port {}: {}", port, ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("Error on port {} for client {}: {}", port, ctx.channel().remoteAddress(), cause.getMessage(), cause);
ctx.close();
}
});
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
serverChannel = b.bind(port).sync().channel();
isRunning = true;
logger.info("RTCM forward server successfully started on port {}", port);
} catch (Exception e) {
logger.error("Failed to start RTCM forward server on port {}: {}", port, e.getMessage(), e);
throw new RuntimeException("Failed to start server on port " + port, e);
}
}
public void broadcast(byte[] data) {
if (!isRunning) {
logger.error("Cannot broadcast data - server on port {} is not running", port);
return;
}
if (data == null || data.length == 0) {
logger.warn("Attempted to broadcast empty data on port {}", port);
return;
}
int activeClients = 0;
int successfulBroadcasts = 0;
for (Channel channel : connectedClients.keySet()) {
if (channel.isActive()) {
activeClients++;
try {
channel.writeAndFlush(data).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
logger.debug("Successfully sent {} bytes to client {} on port {}",
data.length, future.channel().remoteAddress(), port);
} else {
logger.error("Failed to send data to client {} on port {}: {}",
future.channel().remoteAddress(), port, future.cause().getMessage());
future.channel().close();
}
});
successfulBroadcasts++;
} catch (Exception e) {
logger.error("Error broadcasting to client on port {}: {}", port, e.getMessage(), e);
}
}
}
logger.info("Broadcast {} bytes to {}/{} clients on port {}",
data.length, successfulBroadcasts, activeClients, port);
}
public synchronized void stop() {
if (!isRunning) {
logger.warn("Server on port {} is already stopped", port);
return;
}
logger.info("Stopping RTCM forward server on port {}", port);
try {
if (serverChannel != null) {
serverChannel.close().sync();
}
// 关闭所有客户端连接
int closedConnections = 0;
for (Channel channel : connectedClients.keySet()) {
try {
channel.close().sync();
closedConnections++;
} catch (Exception e) {
logger.warn("Error closing client connection on port {}: {}", port, e.getMessage());
}
}
connectedClients.clear();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
isRunning = false;
logger.info("Successfully stopped RTCM forward server on port {}, closed {} client connections",
port, closedConnections);
} catch (Exception e) {
logger.error("Error stopping RTCM forward server on port {}: {}", port, e.getMessage(), e);
}
}
public boolean isRunning() {
return isRunning;
}
public int getActiveClientCount() {
return (int) connectedClients.keySet().stream()
.filter(Channel::isActive)
.count();
}
}

View File

@ -74,6 +74,7 @@ public class Device {
private BigDecimal used; private BigDecimal used;
LocalDateTime lastRxTime; LocalDateTime lastRxTime;
LocalDateTime lastD3f0f2Time;
LocalDateTime lastD3f2Time; LocalDateTime lastD3f2Time;
short noFixedAndFloatResult=0; short noFixedAndFloatResult=0;
@ -89,13 +90,14 @@ public class Device {
short abnormalD341Num = 0; short abnormalD341Num = 0;
byte cfgChannelType = CHANNEL_TYPE_TCP; // 0:TCP;1:DUP byte cfgChannelType = CHANNEL_TYPE_TCP; // 0:TCP;1:DUP
byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP
int lasQuality = 0;
public void updateRx(int head, int bytes,int count){ public void updateRx(int head, int bytes,int count){
lastRxHead = head; lastRxHead = head;
switch (head){ switch (head){
case 0xd3f0: case 0xd3f0:
//lastD3f0f2Time = LocalDateTime.now(); lastD3f0f2Time = LocalDateTime.now();
//clearStat(); //clearStat();
break; break;
case 0xd3f2: case 0xd3f2:
@ -128,6 +130,7 @@ public class Device {
} }
public void updateCalcQuality(int quality){ public void updateCalcQuality(int quality){
lasQuality = quality;
if(b562AsCalc) { if(b562AsCalc) {
if (quality == UBXUtil.FIX_RESULT) fixedNum++; if (quality == UBXUtil.FIX_RESULT) fixedNum++;
else if (quality == UBXUtil.FLOAT_RESULT) floatNum++; else if (quality == UBXUtil.FLOAT_RESULT) floatNum++;

View File

@ -4,6 +4,7 @@ import com.imdroid.common.util.DataTypeUtil;
import com.imdroid.secapi.client.HttpResp; import com.imdroid.secapi.client.HttpResp;
import com.imdroid.sideslope.calc.MultiLineGNSSCalcService; import com.imdroid.sideslope.calc.MultiLineGNSSCalcService;
import com.imdroid.sideslope.calc.SingleLineGNSSCalcService; import com.imdroid.sideslope.calc.SingleLineGNSSCalcService;
import com.imdroid.sideslope.server.tcp.DeviceTcpPortManager;
import com.imdroid.sideslope.service.Device; import com.imdroid.sideslope.service.Device;
import com.imdroid.sideslope.service.LocalDeviceServiceImpl; import com.imdroid.sideslope.service.LocalDeviceServiceImpl;
import com.imdroid.sideslope.server.DeviceChannel; import com.imdroid.sideslope.server.DeviceChannel;
@ -44,6 +45,8 @@ public class ApiController {
MultiLineGNSSCalcService multiCalcService; MultiLineGNSSCalcService multiCalcService;
@Autowired @Autowired
GroupParaService groupParaService; GroupParaService groupParaService;
@Autowired
private DeviceTcpPortManager deviceTcpPortManager;
@PostMapping(value = "/config") @PostMapping(value = "/config")
public HttpResp config(String deviceId, String configuration) { public HttpResp config(String deviceId, String configuration) {
@ -153,6 +156,13 @@ public class ApiController {
return resp; return resp;
} }
@GetMapping("/tcp/ports")
public Map<String, Object> getTcpPorts() {
Map<String, Object> result = new HashMap<>();
result.put("enabled_devices", deviceTcpPortManager.getActiveDevicePorts());
return result;
}
@PostMapping("/device_param_changed") @PostMapping("/device_param_changed")
public HttpResp deviceParamChanged(String deviceId, String oldParentId) { public HttpResp deviceParamChanged(String deviceId, String oldParentId) {
// 更新设备缓存 // 更新设备缓存

View File

@ -341,6 +341,65 @@ public class GnssDeviceController extends BasicController{
return null; return null;
} }
@PostMapping("/gnss/device/cont_loc")
@ResponseBody
public JSONObject contLoc(String deviceid,String createtime){
JSONObject jsonObject = new JSONObject();
//old data
String[] paras = createtime.split(",");
String oldDeviceId;
if(paras.length==2){
oldDeviceId = paras[0];
createtime = paras[1];
}
else {
oldDeviceId = deviceid;
createtime = paras[0];
}
QueryWrapper<GnssCalcData> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("deviceid",oldDeviceId);
queryWrapper.eq("enabled",1);
queryWrapper.eq("createtime",createtime);
queryWrapper.last("limit 1");
queryWrapper.isNotNull("rpose");
GnssCalcData calcOldData = gnssCalcDataMapper.selectOne(queryWrapper);
if(calcOldData==null) {
jsonObject.put("code",1);
jsonObject.put("msg","该时间无有效解算结果");
return jsonObject;
}
// new data
QueryWrapper<GnssCalcData> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.eq("deviceid",deviceid);
queryWrapper1.eq("enabled",1);
queryWrapper1.orderByDesc("createtime");
queryWrapper1.last("limit 1");
queryWrapper1.isNotNull("rpose");
GnssCalcData calcNewData = gnssCalcDataMapper.selectOne(queryWrapper1);
if(calcNewData==null) {
jsonObject.put("code",1);
jsonObject.put("msg","无有效解算结果");
return jsonObject;
}
// 原来的初始值
QueryWrapper<GnssDevice> deviceQueryWrapper = new QueryWrapper<>();
deviceQueryWrapper.eq("deviceid",oldDeviceId);
deviceQueryWrapper.last("limit 1");
GnssDevice device = gnssDeviceMapper.selectOne(deviceQueryWrapper);
if(device.getIpose()==null) device.setIpose(0.0);
if(device.getIposn()==null) device.setIposn(0.0);
if(device.getIposd()==null) device.setIposd(0.0);
jsonObject.put("code",0);
jsonObject.put("ipose", NumberUtils.scaleTwo(calcNewData.getRpose()-calcOldData.getRpose()+device.getIpose()));
jsonObject.put("iposn",NumberUtils.scaleTwo(calcNewData.getRposn()-calcOldData.getRposn()+device.getIposn()));
jsonObject.put("iposd",NumberUtils.scaleTwo(calcNewData.getRposd()-calcOldData.getRposd()+device.getIposd()));
return jsonObject;
}
void updateBasicGroupAssociatedNum(GnssDevice newCfg, GnssDevice oldCfg){ void updateBasicGroupAssociatedNum(GnssDevice newCfg, GnssDevice oldCfg){
updateBasicGroupAssociatedNum(newCfg.getGroup_id()); updateBasicGroupAssociatedNum(newCfg.getGroup_id());
updateCalcGroupAssociatedNum(newCfg.getCalc_group_id()); updateCalcGroupAssociatedNum(newCfg.getCalc_group_id());

View File

@ -2,18 +2,16 @@ package com.imdroid.beidou.controller;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.imdroid.beidou.common.HttpResult;
import com.imdroid.beidou.service.CommonExcelService; import com.imdroid.beidou.service.CommonExcelService;
import com.imdroid.secapi.dto.GnssGroupFwd; import com.imdroid.secapi.dto.*;
import com.imdroid.secapi.dto.GnssGroupFwdMapper;
import com.imdroid.secapi.dto.ResendRecord;
import com.imdroid.secapi.dto.ResendRecordMapper;
import org.springframework.beans.factory.annotation.Autowired; 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.*;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@Controller @Controller
@ -22,6 +20,10 @@ public class GnssResendController extends BasicController implements CommonExcel
GnssGroupFwdMapper gnssGroupFwdMapper; GnssGroupFwdMapper gnssGroupFwdMapper;
@Autowired @Autowired
ResendRecordMapper resendRecordMapper; ResendRecordMapper resendRecordMapper;
@Autowired
TenantMapper tenantMapper;
@Autowired
OpLogManager opLogManager;
/********* 推送页面 *********/ /********* 推送页面 *********/
@RequestMapping("/page/resend_records") @RequestMapping("/page/resend_records")
@ -33,6 +35,19 @@ public class GnssResendController extends BasicController implements CommonExcel
return "/page/resend_records"; return "/page/resend_records";
} }
@RequestMapping("/page/table/resend_record_add")
public String gnssAddDev(Model m, HttpSession session) {
initModel(m, session);
//以下用于下拉框数据
List<Tenant> tenants = tenantMapper.selectList(null);
List<GnssGroupFwd> gnssGroupFwds = gnssGroupFwdMapper.selectList(null);
m.addAttribute("tenant_list", tenants);
m.addAttribute("gnss_group_fwd_list", gnssGroupFwds);
m.addAttribute("device", new GnssDevice());
return "/page/table/resend_record_add";
}
/********* 推送数据 *********/ /********* 推送数据 *********/
@RequestMapping("/fwd/resend_records") @RequestMapping("/fwd/resend_records")
@ -41,6 +56,40 @@ public class GnssResendController extends BasicController implements CommonExcel
return this.pageList(session, page, limit, searchParams); return this.pageList(session, page, limit, searchParams);
} }
@PostMapping("/fwd/resend_records/add")
@ResponseBody
public String update(HttpSession session, @RequestBody JSONObject object) throws Exception {
// 从请求参数中创建对象
ResendRecord resendRecord = JSONObject.toJavaObject(object,ResendRecord.class);
resendRecord.setCreatetime(LocalDateTime.now());
resendRecord.setState(ResendRecord.STATE_BREAK_POINT);
int num = resendRecordMapper.insert(resendRecord);
if (num == 0) {
return HttpResult.failed();
} else {
opLogManager.addLog(getLoginUser(session),getTenantId(session),
OpLogManager.OP_TYPE_ADD,
OpLogManager.OP_OBJ_FWD_RECORD,
"create new resend record");
return HttpResult.ok();
}
}
@PostMapping("/fwd/resend_records/delete")
@ResponseBody
public String delete(HttpSession session, @RequestParam String del_id) throws Exception {
int num = resendRecordMapper.deleteById(del_id);
opLogManager.addLog(getLoginUser(session),getTenantId(session),
OpLogManager.OP_TYPE_DEL,
OpLogManager.OP_OBJ_FWD_RECORD,
del_id + " deleted");
if (num == 0) {
return HttpResult.failed();
} else{
return HttpResult.ok();
}
}
/** /**
* 获取实体类的class * 获取实体类的class
* *

View File

@ -0,0 +1,79 @@
var CoordinateUtils = (function() {
'use strict';
var pi = 3.14159265358979324;
var a = 6378245.0;
var ee = 0.00669342162296594323;
/**
* 判断是否在国内不在国内则不做偏移
*/
function outOfChina(lon, lat) {
if ((lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)) {
return true;
} else {
return false;
}
}
function transformLat(x, y) {
var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLon(x, y) {
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
return ret;
}
function transform(wgLat, wgLon) {
var mars_point = {lon: 0, lat: 0};
if (outOfChina(wgLon, wgLat)) {
mars_point.lat = wgLat;
mars_point.lon = wgLon;
return mars_point;
}
var dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
var dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
var radLat = wgLat / 180.0 * pi;
var magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
var sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
mars_point.lat = wgLat + dLat;
mars_point.lon = wgLon + dLon;
return mars_point;
}
function getMapCoordinates(lat, lon, mapType) {
var coordinates;
if (mapType === 'amap' || mapType === 'amap_satellite') {
// 高德地图 WGS84 转换为 GCJ-02
var gcjCoord = transform(lat, lon);
coordinates = ol.proj.fromLonLat([gcjCoord.lon, gcjCoord.lat]);
} else if (mapType.startsWith('google_')) {
// Google地图使用WGS84坐标系
coordinates = ol.proj.fromLonLat([lon, lat]);
} else {
// 天地图 CGCS2000与WGS84实质一样
coordinates = ol.proj.fromLonLat([lon, lat]);
}
return coordinates;
}
return {
transform: transform,
getMapCoordinates: getMapCoordinates,
outOfChina: outOfChina
};
})();

View File

@ -0,0 +1,410 @@
var DeviceMarkers = (function() {
'use strict';
var greenFeatures = [];
var orangeFeatures = [];
var redFeatures = [];
var allFeatures = [];
var myLocationFeature = null;
var myLocationInterval = null;
var showDeviceId = true;
var minZoomForLabels = 4;
var map = null;
var vectorSource = null;
var vectorLayer = null;
function init(mapInstance, vectorSourceInstance, vectorLayerInstance) {
map = mapInstance;
vectorSource = vectorSourceInstance;
vectorLayer = vectorLayerInstance;
}
function createDeviceStyle(feature) {
if (feature.get('isMyLocation')) {
return new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: '../images/loc_blue.png',
scale: 0.7
})
});
}
var deviceInfo = feature.get('deviceInfo');
if (!deviceInfo) return null;
var iconSrc;
var color = '#000';
var isHovered = feature.get('hovered') === true;
var scale = isHovered ? 0.85 : 0.7;
// 根据告警级别选择图标
if (deviceInfo.warning == 2) {
iconSrc = '../images/loc1_red.png';
} else if (deviceInfo.warning == 1) {
iconSrc = '../images/loc1_orange.png';
} else {
iconSrc = '../images/loc1_green.png';
}
var style = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: iconSrc,
scale: scale
})
});
// 根据缩放级别和设置决定是否显示设备ID
if (showDeviceId && map && map.getView().getZoom() >= minZoomForLabels) {
style.setText(new ol.style.Text({
text: deviceInfo.deviceid,
offsetY: -30,
fill: new ol.style.Fill({
color: isHovered ? '#1aa094' : color
}),
stroke: new ol.style.Stroke({
color: '#fff',
width: isHovered ? 3 : 2
}),
font: isHovered ? 'bold 12px Arial' : '12px Arial'
}));
}
return style;
}
function addDeviceMarkers(deviceList) {
if (!vectorSource || !deviceList) return;
var savedMyLocationFeature = myLocationFeature;
vectorSource.clear();
greenFeatures = [];
orangeFeatures = [];
redFeatures = [];
allFeatures = [];
for (var i = 0; i < deviceList.length; i++) {
var device = deviceList[i];
var currentMapType = getCurrentMapType();
var mapCoordinates = CoordinateUtils.getMapCoordinates(
device.latitude,
device.longitude,
currentMapType
);
var feature = new ol.Feature({
geometry: new ol.geom.Point(mapCoordinates),
deviceInfo: device
});
// 按告警级别分类
if (device.warning == 2) {
redFeatures.push(feature);
} else if (device.warning == 1) {
orangeFeatures.push(feature);
} else {
greenFeatures.push(feature);
}
allFeatures.push(feature);
vectorSource.addFeature(feature);
}
if (savedMyLocationFeature) {
vectorSource.addFeature(savedMyLocationFeature);
}
// 强制更新样式
if (vectorLayer) {
vectorLayer.changed();
}
}
function getCurrentMapType() {
var mapTypeSelect = document.getElementById('mapTypeSelectNew');
return mapTypeSelect ? mapTypeSelect.value : 'tianditu_satellite';
}
function showAllDevices() {
if (!vectorSource) return;
var savedMyLocationFeature = myLocationFeature;
vectorSource.clear();
if (savedMyLocationFeature) {
vectorSource.addFeature(savedMyLocationFeature);
}
for (var i = 0; i < allFeatures.length; i++) {
vectorSource.addFeature(allFeatures[i]);
}
}
function showWarning1Devices() {
if (!vectorSource) return;
var savedMyLocationFeature = myLocationFeature;
vectorSource.clear();
if (savedMyLocationFeature) {
vectorSource.addFeature(savedMyLocationFeature);
}
for (var i = 0; i < allFeatures.length; i++) {
vectorSource.addFeature(allFeatures[i]);
}
hideGreenFeatures();
hideRedFeatures();
}
function showWarning2Devices() {
if (!vectorSource) return;
var savedMyLocationFeature = myLocationFeature;
vectorSource.clear();
if (savedMyLocationFeature) {
vectorSource.addFeature(savedMyLocationFeature);
}
for (var i = 0; i < allFeatures.length; i++) {
vectorSource.addFeature(allFeatures[i]);
}
hideGreenFeatures();
hideOrangeFeatures();
}
function hideGreenFeatures() {
for (var i = 0; i < greenFeatures.length; i++) {
vectorSource.removeFeature(greenFeatures[i]);
}
}
function hideOrangeFeatures() {
for (var i = 0; i < orangeFeatures.length; i++) {
vectorSource.removeFeature(orangeFeatures[i]);
}
}
function hideRedFeatures() {
for (var i = 0; i < redFeatures.length; i++) {
vectorSource.removeFeature(redFeatures[i]);
}
}
function findDeviceById(deviceId) {
// console.log('搜索设备:', deviceId, '(类型:', typeof deviceId, ')');
// console.log('当前设备总数:', allFeatures.length);
var searchTerm = String(deviceId).trim();
for (var i = 0; i < allFeatures.length; i++) {
var feature = allFeatures[i];
var deviceInfo = feature.get('deviceInfo');
if (deviceInfo) {
var currentDeviceId = String(deviceInfo.deviceid);
// console.log('检查设备:', currentDeviceId, '(类型:', typeof deviceInfo.deviceid, ')');
if (currentDeviceId === searchTerm) {
// console.log('精确匹配找到设备:', currentDeviceId);
return feature;
}
if (currentDeviceId.indexOf(searchTerm) !== -1) {
// console.log('部分匹配找到设备:', currentDeviceId);
return feature;
}
if (currentDeviceId.replace(/\s+/g, '') === searchTerm.replace(/\s+/g, '')) {
// console.log('去除空格后匹配找到设备:', currentDeviceId);
return feature;
}
}
}
console.log('未找到设备:', deviceId);
return null;
}
function locateDevice(deviceId) {
var feature = findDeviceById(deviceId);
if (feature && map) {
var geometry = feature.getGeometry();
var coordinates = geometry.getCoordinates();
map.getView().animate({
center: coordinates,
zoom: Math.max(map.getView().getZoom(), 15),
duration: 1000
});
return true;
}
return false;
}
/**
* 获取我的位置
*/
function getMyLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
var lat = position.coords.latitude;
var lon = position.coords.longitude;
var currentMapType = getCurrentMapType();
var coordinates = CoordinateUtils.getMapCoordinates(lat, lon, currentMapType);
if (myLocationFeature) {
vectorSource.removeFeature(myLocationFeature);
}
myLocationFeature = new ol.Feature({
geometry: new ol.geom.Point(coordinates),
isMyLocation: true
});
vectorSource.addFeature(myLocationFeature);
// map.getView().animate({
// center: coordinates,
// zoom: 15,
// duration: 1000
// });
},
function(error) {
console.error('获取位置失败:', error);
}
);
}
}
function locateMyLocation() {
if (myLocationFeature) {
var geometry = myLocationFeature.getGeometry();
var coordinates = geometry.getCoordinates();
map.getView().animate({
center: coordinates,
zoom: 15,
duration: 1000
});
return true;
} else {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
var lat = position.coords.latitude;
var lon = position.coords.longitude;
var currentMapType = getCurrentMapType();
var coordinates = CoordinateUtils.getMapCoordinates(lat, lon, currentMapType);
if (myLocationFeature) {
vectorSource.removeFeature(myLocationFeature);
}
myLocationFeature = new ol.Feature({
geometry: new ol.geom.Point(coordinates),
isMyLocation: true
});
vectorSource.addFeature(myLocationFeature);
map.getView().animate({
center: coordinates,
zoom: 15,
duration: 1000
});
},
);
}
}
return false;
}
function startLocationUpdates() {
if (navigator.geolocation) {
myLocationInterval = setInterval(function() {
getMyLocation();
}, 30000);
}
}
function stopLocationUpdates() {
if (myLocationInterval) {
clearInterval(myLocationInterval);
myLocationInterval = null;
}
}
function updateMyLocationForMapType(mapType) {
if (myLocationFeature) {
var geometry = myLocationFeature.getGeometry();
var coordinates = geometry.getCoordinates();
var lonLat = ol.proj.toLonLat(coordinates);
var newCoordinates = CoordinateUtils.getMapCoordinates(lonLat[1], lonLat[0], mapType);
myLocationFeature.setGeometry(new ol.geom.Point(newCoordinates));
}
}
function setShowDeviceId(show) {
showDeviceId = show;
if (vectorLayer) {
vectorLayer.changed();
}
}
function getDeviceStats() {
return {
total: allFeatures.length,
green: greenFeatures.length,
orange: orangeFeatures.length,
red: redFeatures.length
};
}
return {
init: init,
createDeviceStyle: createDeviceStyle,
addDeviceMarkers: addDeviceMarkers,
showAllDevices: showAllDevices,
showWarning1Devices: showWarning1Devices,
showWarning2Devices: showWarning2Devices,
hideGreenFeatures: hideGreenFeatures,
hideOrangeFeatures: hideOrangeFeatures,
hideRedFeatures: hideRedFeatures,
findDeviceById: findDeviceById,
locateDevice: locateDevice,
getMyLocation: getMyLocation,
locateMyLocation: locateMyLocation,
startLocationUpdates: startLocationUpdates,
stopLocationUpdates: stopLocationUpdates,
updateMyLocationForMapType: updateMyLocationForMapType,
setShowDeviceId: setShowDeviceId,
getDeviceStats: getDeviceStats,
getAllFeatures: function() { return allFeatures; },
getGreenFeatures: function() { return greenFeatures; },
getOrangeFeatures: function() { return orangeFeatures; },
getRedFeatures: function() { return redFeatures; },
getMyLocationFeature: function() { return myLocationFeature; }
};
})();

View File

@ -0,0 +1,149 @@
var DeviceOverview = (function() {
'use strict';
var deviceList = [];
function init(devices, options) {
deviceList = devices || [];
window.deviceList = deviceList;
window.userRole = options && options.role ? options.role : 'USER';
layui.use(['form'], function(){
var form = layui.form;
form.on('select(mapTypeNew)', function(data){
MapCore.switchMapType(data.value);
});
MapCore.initialize(deviceList);
document.getElementById('warningFilter').value = 'all';
SearchFilter.showAllDevices();
});
}
function onMapTypeChange() {
return MapCore.onMapTypeChange();
}
function searchDeviceNew() {
return MapCore.searchDeviceNew();
}
function onWarningFilterChange() {
return MapCore.onWarningFilterChange();
}
function toggleMapFunctionsMenu() {
return MapCore.toggleMapFunctionsMenu();
}
function toggleDeviceId() {
return MapCore.toggleDeviceId();
}
function toggleCluster() {
return MapCore.toggleCluster();
}
function toggleMeasureDistance() {
return MeasureTools.toggleMeasureDistance();
}
function finishMeasuring() {
return MeasureTools.finishMeasuring();
}
function clearMeasure() {
return MeasureTools.clearMeasure();
}
function toggleWeatherForecast() {
return WeatherForecast.toggleWeatherForecast();
}
function closeWeatherCard() {
return WeatherForecast.closeWeatherCard();
}
function showPrevForecast() {
return WeatherForecast.showPrevForecast();
}
function showNextForecast() {
return WeatherForecast.showNextForecast();
}
function queryDevices(statusType) {
return SearchFilter.queryDevices(statusType);
}
function locateDeviceOnMap(deviceId, latitude, longitude) {
return SearchFilter.locateDeviceOnMap(deviceId, latitude, longitude);
}
function locateDeviceDirectly(deviceId) {
return SearchFilter.locateDeviceDirectly(deviceId);
}
function switchMapType(mapType) {
return MapCore.switchMapType(mapType);
}
function locateMyLocation() {
return DeviceMarkers.locateMyLocation();
}
function clearDeviceFilter() {
return SearchFilter.clearDeviceFilter();
}
return {
init: init,
onMapTypeChange: onMapTypeChange,
switchMapType: switchMapType,
searchDeviceNew: searchDeviceNew,
onWarningFilterChange: onWarningFilterChange,
queryDevices: queryDevices,
locateDeviceOnMap: locateDeviceOnMap,
locateDeviceDirectly: locateDeviceDirectly,
locateMyLocation: locateMyLocation,
clearDeviceFilter: clearDeviceFilter,
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
toggleDeviceId: toggleDeviceId,
toggleCluster: toggleCluster,
toggleMeasureDistance: toggleMeasureDistance,
finishMeasuring: finishMeasuring,
clearMeasure: clearMeasure,
toggleWeatherForecast: toggleWeatherForecast,
closeWeatherCard: closeWeatherCard,
showPrevForecast: showPrevForecast,
showNextForecast: showNextForecast
};
})();
window.DeviceOverview = DeviceOverview;
window.onMapTypeChange = DeviceOverview.onMapTypeChange;
window.searchDeviceNew = DeviceOverview.searchDeviceNew;
window.onWarningFilterChange = DeviceOverview.onWarningFilterChange;
window.toggleMapFunctionsMenu = DeviceOverview.toggleMapFunctionsMenu;
window.toggleDeviceId = DeviceOverview.toggleDeviceId;
window.toggleCluster = DeviceOverview.toggleCluster;
window.toggleMeasureDistance = DeviceOverview.toggleMeasureDistance;
window.finishMeasuring = DeviceOverview.finishMeasuring;
window.clearMeasure = DeviceOverview.clearMeasure;
window.toggleWeatherForecast = DeviceOverview.toggleWeatherForecast;
window.closeWeatherCard = DeviceOverview.closeWeatherCard;
window.showPrevForecast = DeviceOverview.showPrevForecast;
window.showNextForecast = DeviceOverview.showNextForecast;
window.queryDevices = DeviceOverview.queryDevices;
window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap;
window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly;
window.locateMyLocation = DeviceOverview.locateMyLocation;
window.clearDeviceFilter = DeviceOverview.clearDeviceFilter;

View File

@ -0,0 +1,347 @@
var MapCore = (function() {
'use strict';
var map = null;
var vectorSource = null;
var vectorLayer = null;
var clusterSource = null;
var clusterLayer = null;
var currentBaseLayer = null;
var showDeviceId = true;
var showCluster = true;
var minZoomForLabels = 4;
var maxZoomForClustering = 8;
var hoveredFeature = null;
function initialize(deviceList) {
// 创建矢量数据源和图层
vectorSource = new ol.source.Vector();
vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: function(feature) {
return DeviceMarkers.createDeviceStyle(feature);
}
});
// 创建集群数据源和图层
clusterSource = new ol.source.Cluster({
distance: 40,
source: vectorSource
});
clusterLayer = new ol.layer.Vector({
source: clusterSource,
style: function(feature) {
var size = feature.get('features').length;
var style = new ol.style.Style({
image: new ol.style.Circle({
radius: 15,
fill: new ol.style.Fill({
color: '#3399CC'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: '#fff'
})
})
});
return style;
}
});
var initialMapType = document.getElementById('mapTypeSelectNew').value || 'tianditu_satellite';
currentBaseLayer = MapLayers.getLayer(initialMapType);
map = new ol.Map({
target: 'map-container',
layers: [
currentBaseLayer,
clusterLayer,
vectorLayer
],
view: new ol.View({
center: ol.proj.fromLonLat([116.404, 39.915]),
zoom: 7
})
});
DeviceMarkers.init(map, vectorSource, vectorLayer);
MeasureTools.init(map);
WeatherForecast.init();
var scaleLineControl = new ol.control.ScaleLine();
map.addControl(scaleLineControl);
var initialZoom = map.getView().getZoom();
updateLayerVisibility(initialZoom);
map.getView().on('change:resolution', function() {
var zoom = map.getView().getZoom();
updateLayerVisibility(zoom);
vectorLayer.changed();
});
bindMouseEvents();
if (deviceList && deviceList.length > 0) {
setCenterFromDevices(deviceList);
DeviceMarkers.addDeviceMarkers(deviceList);
}
DeviceMarkers.getMyLocation();
DeviceMarkers.startLocationUpdates();
document.getElementById('showDeviceIdSwitch').checked = showDeviceId;
document.getElementById('showClusterSwitch').checked = showCluster;
}
function updateLayerVisibility(zoom) {
if (showCluster) {
if (zoom >= maxZoomForClustering) {
clusterLayer.setVisible(false);
vectorLayer.setVisible(true);
} else {
clusterLayer.setVisible(true);
vectorLayer.setVisible(false);
}
} else {
clusterLayer.setVisible(false);
vectorLayer.setVisible(true);
}
}
function bindMouseEvents() {
map.on('pointermove', function(evt) {
if (evt.dragging) {
return;
}
var pixel = map.getEventPixel(evt.originalEvent);
var hit = map.hasFeatureAtPixel(pixel);
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
return feature;
});
if (hoveredFeature && hoveredFeature !== feature) {
hoveredFeature.set('hovered', false);
}
if (feature) {
// 处理集群
var features = feature.get('features');
if (features && features.length === 1) {
features[0].set('hovered', true);
hoveredFeature = features[0];
} else if (!features && feature.get('deviceInfo')) {
feature.set('hovered', true);
hoveredFeature = feature;
}
}
vectorLayer.changed();
});
map.on('click', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
return feature;
});
if (feature) {
var features = feature.get('features');
if (features && features.length > 1) {
// 集群点击,扩展视图
var extent = vectorSource.getExtent();
map.getView().fit(extent, {
padding: [50, 50, 50, 50],
duration: 1000
});
} else if (features && features.length === 1) {
// 单个设备点击
var deviceInfo = features[0].get('deviceInfo');
if (deviceInfo) {
showDeviceInfo(deviceInfo);
// 如果天气预测开启,显示天气卡片
if (WeatherForecast.isEnabled()) {
WeatherForecast.showWeatherForecast(deviceInfo);
}
}
} else if (feature.get('deviceInfo')) {
var deviceInfo = feature.get('deviceInfo');
showDeviceInfo(deviceInfo);
// 如果天气预测开启,显示天气卡片
if (WeatherForecast.isEnabled()) {
WeatherForecast.showWeatherForecast(deviceInfo);
}
}
}
});
}
function showDeviceInfo(deviceInfo) {
var statusText = '';
if (deviceInfo.warning === 2) {
statusText = '严重告警';
} else if (deviceInfo.warning === 1) {
statusText = '一般告警';
} else {
statusText = '正常';
}
var infoMsg = ' 设备: ' + deviceInfo.deviceid +
' | 状态: ' + statusText +
' | 坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4);
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg(infoMsg, {
time: 3000,
area: ['auto', 'auto'],
offset: 'auto'
});
}
}
function setCenterFromDevices(deviceList) {
if (!deviceList || deviceList.length === 0) return;
var sumLat = 0, sumLon = 0;
for (var i = 0; i < deviceList.length; i++) {
sumLat += deviceList[i].latitude;
sumLon += deviceList[i].longitude;
}
var centerLat = sumLat / deviceList.length;
var centerLon = sumLon / deviceList.length;
map.getView().setCenter(ol.proj.fromLonLat([centerLon, centerLat]));
}
function switchMapType(mapType) {
if (!MapLayers.hasLayer(mapType)) {
console.error('未知的地图类型:', mapType);
return;
}
// 保存当前视图状态
var currentCenter = map.getView().getCenter();
var currentZoom = map.getView().getZoom();
map.removeLayer(currentBaseLayer);
currentBaseLayer = MapLayers.getLayer(mapType);
map.getLayers().insertAt(0, currentBaseLayer);
DeviceMarkers.updateMyLocationForMapType(mapType);
var deviceList = window.deviceList || [];
DeviceMarkers.addDeviceMarkers(deviceList);
// 恢复之前的视图状态
map.getView().setCenter(currentCenter);
map.getView().setZoom(currentZoom);
}
function toggleDeviceId() {
showDeviceId = document.getElementById('showDeviceIdSwitch').checked;
DeviceMarkers.setShowDeviceId(showDeviceId);
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息');
}
}
function toggleCluster() {
showCluster = document.getElementById('showClusterSwitch').checked;
var zoom = map.getView().getZoom();
updateLayerVisibility(zoom);
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示');
}
}
function toggleMapFunctionsMenu() {
var menu = document.getElementById('mapFunctionsMenu');
if (menu) {
menu.classList.toggle('show');
if (menu.classList.contains('show')) {
document.addEventListener('click', closeMapFunctionsMenu);
}
}
}
function closeMapFunctionsMenu(event) {
var dropdown = document.getElementById('mapFunctionsMenu');
var toggleBtn = document.querySelector('.dropdown-toggle');
if (dropdown && !dropdown.contains(event.target) && !toggleBtn.contains(event.target)) {
dropdown.classList.remove('show');
document.removeEventListener('click', closeMapFunctionsMenu);
}
}
function onMapTypeChange() {
var mapType = document.getElementById('mapTypeSelectNew').value;
switchMapType(mapType);
}
function onWarningFilterChange() {
var filterValue = document.getElementById('warningFilter').value;
switch(filterValue) {
case 'all':
SearchFilter.showAllDevices();
break;
case 'warning1':
SearchFilter.showWarning1Devices();
break;
case 'warning2':
SearchFilter.showWarning2Devices();
break;
}
}
function searchDeviceNew() {
var searchInput = document.getElementById('deviceSearchNew');
if (searchInput) {
var deviceId = searchInput.value.trim();
if (deviceId) {
SearchFilter.searchDevice(deviceId);
} else {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('请输入设备编号');
}
}
}
}
function getMap() {
return map;
}
function getVectorSource() {
return vectorSource;
}
function getVectorLayer() {
return vectorLayer;
}
return {
initialize: initialize,
switchMapType: switchMapType,
toggleDeviceId: toggleDeviceId,
toggleCluster: toggleCluster,
toggleMapFunctionsMenu: toggleMapFunctionsMenu,
onMapTypeChange: onMapTypeChange,
onWarningFilterChange: onWarningFilterChange,
searchDeviceNew: searchDeviceNew,
getMap: getMap,
getVectorSource: getVectorSource,
getVectorLayer: getVectorLayer
};
})();

View File

@ -0,0 +1,176 @@
var MapLayers = (function() {
'use strict';
// 天地图 API 密钥 fengyarnom@gmail.com
// 天地图的英文就是 TIANDITU 所以这里用拼音指代
var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac';
function createTiandituTileLoadFunction() {
return function(imageTile, src) {
imageTile.getImage().src = src;
imageTile.getImage().onerror = function() {
// 天地图加载失败时切换到高德地图
var mapTypeSelect = document.getElementById('mapTypeSelectNew');
if(mapTypeSelect && mapTypeSelect.value.startsWith('tianditu_')) {
mapTypeSelect.value = 'amap';
if (window.DeviceOverview && typeof window.DeviceOverview.switchMapType === 'function') {
window.DeviceOverview.switchMapType('amap');
}
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('天地图加载失败,已自动切换到高德地图');
}
if (window.layui && window.layui.form) {
window.layui.form.render('select');
}
}
};
};
}
var mapLayers = {
// 高德地图
amap: new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
})
}),
// 高德卫星图
amap_satellite: new ol.layer.Group({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
})
}),
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}'
})
})
]
}),
// 谷歌卫星图
google_satellite: new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://mt{0-3}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous'
})
}),
// 谷歌地形图
google_terrain: new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://mt{0-3}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous'
})
}),
// 谷歌道路图
google_roadmap: new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous'
})
}),
// 谷歌混合图
google_hybrid: new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://mt{0-3}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}',
crossOrigin: 'anonymous'
})
}),
// 天地图卫星影像
tianditu_satellite: new ol.layer.Group({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
}),
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
})
]
}),
// 天地图矢量图
tianditu_normal: new ol.layer.Group({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
}),
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
})
]
}),
// 天地图地形图
tianditu_terrain: new ol.layer.Group({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
})
]
}),
// 天地图地形混合图
tianditu_terrain_hybrid: new ol.layer.Group({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
}),
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://t{0-7}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY,
tileLoadFunction: createTiandituTileLoadFunction()
})
})
]
})
};
function getLayer(mapType) {
return mapLayers[mapType];
}
function getAllLayers() {
return mapLayers;
}
function hasLayer(mapType) {
return mapLayers.hasOwnProperty(mapType);
}
return {
getLayer: getLayer,
getAllLayers: getAllLayers,
hasLayer: hasLayer
};
})();

View File

@ -0,0 +1,299 @@
var MeasureTools = (function() {
'use strict';
var measureActive = false;
var measureDraw = null;
var measureSource = null;
var measureLayer = null;
var measureTooltips = [];
var measureFeatures = [];
var currentMeasureTooltips = [];
var currentSketch = null;
var currentListener = null;
var segmentTooltips = [];
var map = null;
function init(mapInstance) {
map = mapInstance;
// 测距专用图层
measureSource = new ol.source.Vector();
measureLayer = new ol.layer.Vector({
source: measureSource,
style: new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
image: new ol.style.RegularShape({
points: 4,
radius: 8,
radius2: 0,
angle: Math.PI / 4,
stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
})
})
});
if (map) {
map.addLayer(measureLayer);
}
}
function toggleMeasureDistance() {
if (measureActive) {
deactivateMeasure();
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('测距功能已关闭');
}
} else {
activateMeasure();
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('点击左键添加距离节点,点击右键结束测量 :)');
}
}
var menu = document.getElementById('mapFunctionsMenu');
if (menu) {
menu.classList.remove('show');
}
}
function finishMeasuring() {
if (measureActive && currentSketch && measureDraw) {
measureDraw.finishDrawing();
}
}
function activateMeasure() {
if (!map || !measureSource) return;
measureActive = true;
// 显示测量状态指示器
var measureStatus = document.getElementById('measureStatus');
if (measureStatus) {
measureStatus.style.display = 'flex';
}
measureDraw = new ol.interaction.Draw({
source: measureSource,
type: 'LineString',
style: new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }),
stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }),
image: new ol.style.RegularShape({
points: 4,
radius: 8,
radius2: 0,
angle: Math.PI / 4,
stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 })
})
})
});
map.addInteraction(measureDraw);
map.getViewport().addEventListener('contextmenu', function(e) {
if (measureActive && currentSketch) {
e.preventDefault();
measureDraw.finishDrawing();
}
});
measureDraw.on('drawstart', function(evt) {
currentSketch = evt.feature;
currentMeasureTooltips = [];
segmentTooltips = [];
currentListener = currentSketch.getGeometry().on('change', function(e) {
var geom = e.target;
var coords = geom.getCoordinates();
clearTemporaryTooltips();
clearSegmentTooltips();
// 计算并显示每个节点的距离
var total = 0;
for (var i = 0; i < coords.length; i++) {
if (i === 0) {
// 起点
var startTooltip = createMeasureTooltip(coords[0], '起点');
currentMeasureTooltips.push(startTooltip);
} else {
// 计算段距离
var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
var segmentLength = ol.sphere.getLength(seg);
total += segmentLength;
// 显示每个节点的累计距离
var output = formatLength(total);
var tooltip = createMeasureTooltip(coords[i], output);
segmentTooltips.push(tooltip);
if (i === coords.length - 1) {
currentMeasureTooltips.push(tooltip);
}
}
}
});
});
// 绘制结束
measureDraw.on('drawend', function(evt) {
var coords = evt.feature.getGeometry().getCoordinates();
clearTemporaryTooltips();
clearSegmentTooltips();
var total = 0;
for (var i = 0; i < coords.length; i++) {
if (i === 0) {
var startTooltip = createMeasureTooltip(coords[0], '起点', true);
measureTooltips.push(startTooltip);
} else {
var seg = new ol.geom.LineString([coords[i-1], coords[i]]);
total += ol.sphere.getLength(seg);
var output = formatLength(total);
var tooltip = createMeasureTooltip(coords[i], output, true);
measureTooltips.push(tooltip);
}
}
measureFeatures.push(evt.feature);
if (currentListener) {
ol.Observable.unByKey(currentListener);
}
currentSketch = null;
currentListener = null;
var measureStatus = document.getElementById('measureStatus');
if (measureStatus) {
measureStatus.style.display = 'none';
}
map.removeInteraction(measureDraw);
measureActive = false;
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('测量完成');
}
});
}
function deactivateMeasure() {
measureActive = false;
var measureStatus = document.getElementById('measureStatus');
if (measureStatus) {
measureStatus.style.display = 'none';
}
if (measureDraw && map) {
map.removeInteraction(measureDraw);
measureDraw = null;
}
if (currentListener) {
ol.Observable.unByKey(currentListener);
currentListener = null;
}
currentSketch = null;
clearTemporaryTooltips();
clearSegmentTooltips();
}
function createMeasureTooltip(coord, text, isStatic) {
var elem = document.createElement('div');
elem.className = isStatic ? 'ol-tooltip ol-tooltip-static' : 'ol-tooltip ol-tooltip-measure';
elem.innerHTML = text;
var overlay = new ol.Overlay({
element: elem,
offset: [0, -15],
positioning: 'bottom-center'
});
overlay.setPosition(coord);
if (map) {
map.addOverlay(overlay);
}
return overlay;
}
function formatLength(length) {
if (length > 1000) {
return (Math.round(length / 100) / 10) + ' km';
} else {
return (Math.round(length * 10) / 10) + ' m';
}
}
function clearAllMeasureTooltips() {
if (!map) return;
for (var i = 0; i < measureTooltips.length; i++) {
map.removeOverlay(measureTooltips[i]);
}
measureTooltips = [];
clearTemporaryTooltips();
}
function clearTemporaryTooltips() {
if (!map) return;
for (var i = 0; i < currentMeasureTooltips.length; i++) {
map.removeOverlay(currentMeasureTooltips[i]);
}
currentMeasureTooltips = [];
}
function clearSegmentTooltips() {
if (!map) return;
for (var i = 0; i < segmentTooltips.length; i++) {
map.removeOverlay(segmentTooltips[i]);
}
segmentTooltips = [];
}
function clearMeasure() {
if (measureSource) {
measureSource.clear();
}
clearAllMeasureTooltips();
measureFeatures = [];
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('测距标记已清除');
}
// 关闭地图功能菜单
var menu = document.getElementById('mapFunctionsMenu');
if (menu) {
menu.classList.remove('show');
}
}
function isActive() {
return measureActive;
}
function getMeasureCount() {
return measureFeatures.length;
}
return {
init: init,
toggleMeasureDistance: toggleMeasureDistance,
finishMeasuring: finishMeasuring,
clearMeasure: clearMeasure,
isActive: isActive,
getMeasureCount: getMeasureCount
};
})();

View File

@ -0,0 +1,327 @@
var SearchFilter = (function() {
'use strict';
var currentSearchedDevice = null;
var markerState = 3; // 1:all; 2:orange; 3:red
var filteredDeviceIds = [];
var isFilterActive = false;
function searchDevice(deviceId) {
if (!deviceId || !deviceId.trim()) {
clearSearch();
return false;
}
deviceId = deviceId.trim();
// 多设备
if (deviceId.indexOf(',') !== -1) {
return handleMultiDeviceSearch(deviceId);
}
// 单设备
currentSearchedDevice = deviceId;
var success = DeviceMarkers.locateDevice(deviceId);
if (success) {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('已定位到设备: ' + deviceId);
}
// 获取设备信息并显示天气预测(如果启用)
var targetFeature = DeviceMarkers.findDeviceById(deviceId);
if (targetFeature && window.WeatherForecast && window.WeatherForecast.isEnabled()) {
var deviceInfo = targetFeature.get('deviceInfo');
if (deviceInfo) {
window.WeatherForecast.showWeatherForecast(deviceInfo);
}
}
} else {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('未找到设备: ' + deviceId);
}
}
return success;
}
function handleMultiDeviceSearch(deviceIdsString) {
var deviceIds = deviceIdsString.split(',').map(function(id) {
return id.trim();
}).filter(function(id) {
return id !== '';
});
if (deviceIds.length === 0) {
clearSearch();
return false;
}
filteredDeviceIds = deviceIds;
var foundDevices = filterDevicesByIds(deviceIds);
if (foundDevices.length > 0) {
showFilterStatus(deviceIds.length);
isFilterActive = true;
var firstDevice = foundDevices[0];
var geometry = firstDevice.getGeometry();
var coordinates = geometry.getCoordinates();
if (MapCore.getMap()) {
MapCore.getMap().getView().animate({
center: coordinates,
zoom: 12,
duration: 1000
});
}
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('已过滤显示 ' + foundDevices.length + ' 个设备');
}
return true;
} else {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('未找到任何匹配的设备');
}
return false;
}
}
function filterDevicesByIds(deviceIds) {
if (!DeviceMarkers.getAllFeatures) return [];
var allFeatures = DeviceMarkers.getAllFeatures();
var foundDevices = [];
var vectorSource = MapCore.getVectorSource();
if (!vectorSource) return [];
var myLocationFeature = DeviceMarkers.getMyLocationFeature();
vectorSource.clear();
if (myLocationFeature) {
vectorSource.addFeature(myLocationFeature);
}
for (var i = 0; i < allFeatures.length; i++) {
var feature = allFeatures[i];
var deviceInfo = feature.get('deviceInfo');
if (deviceInfo) {
var deviceId = String(deviceInfo.deviceid).trim();
for (var j = 0; j < deviceIds.length; j++) {
var searchId = String(deviceIds[j]).trim();
if (deviceId === searchId ||
deviceId.indexOf(searchId) !== -1 ||
deviceId.replace(/\s+/g, '') === searchId.replace(/\s+/g, '')) {
vectorSource.addFeature(feature);
foundDevices.push(feature);
break;
}
}
}
}
return foundDevices;
}
function showFilterStatus(count) {
var filterStatus = document.getElementById('filterStatus');
if (filterStatus) {
filterStatus.innerHTML =
'<span style="color: #1aa094; font-weight: bold; font-size: 12px;">定位中</span>' +
'<button class="toolbar-btn" onclick="clearDeviceFilter()" style="background: #f56565; margin-left: 6px;">' +
'<i class="layui-icon layui-icon-close" style="font-size: 12px;"></i>' +
'</button>';
filterStatus.style.display = 'flex';
}
}
function hideFilterStatus() {
var filterStatus = document.getElementById('filterStatus');
if (filterStatus) {
filterStatus.style.display = 'none';
}
}
function clearDeviceFilter() {
isFilterActive = false;
filteredDeviceIds = [];
hideFilterStatus();
applyCurrentFilter();
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('已清除设备过滤');
}
}
function clearSearch() {
currentSearchedDevice = null;
var searchInput = document.getElementById('deviceSearchNew');
if (searchInput) {
searchInput.value = '';
}
if (isFilterActive) {
clearDeviceFilter();
} else {
// 恢复到当前的过滤状态
applyCurrentFilter();
}
}
function applyCurrentFilter() {
switch(markerState) {
case 1:
showAllDevices();
break;
case 2:
showWarning1Devices();
break;
case 3:
showWarning2Devices();
break;
}
}
function showAllDevices() {
currentSearchedDevice = null;
clearSearchInput();
DeviceMarkers.showAllDevices();
markerState = 1;
updateFilterSelect('all');
}
function showWarning1Devices() {
currentSearchedDevice = null;
clearSearchInput();
DeviceMarkers.showWarning1Devices();
markerState = 2;
updateFilterSelect('warning1');
}
function showWarning2Devices() {
currentSearchedDevice = null;
clearSearchInput();
DeviceMarkers.showWarning2Devices();
markerState = 3;
updateFilterSelect('warning2');
}
function clearSearchInput() {
var searchInput = document.getElementById('deviceSearchNew');
if (searchInput) {
searchInput.value = '';
}
}
function updateFilterSelect(filterValue) {
var filterSelect = document.getElementById('warningFilter');
if (filterSelect) {
filterSelect.value = filterValue;
}
}
function filterDevicesByStatus(statusType) {
switch(statusType) {
case 'warning1':
showWarning1Devices();
break;
case 'warning2':
showWarning2Devices();
break;
case 'offline':
case 'no_fwd':
case 'nofixed':
case 'nogga':
// 对于这些状态,显示所有设备,让用户在弹窗中选择
showAllDevices();
break;
default:
showAllDevices();
break;
}
}
function queryDevices(statusType) {
filterDevicesByStatus(statusType);
// 打开设备列表弹窗
if (window.layer && typeof window.layer.open === 'function') {
var index = window.layer.open({
title: '设备列表',
type: 2,
shade: 0.2,
maxmin: true,
shadeClose: true,
anim: 2,
offset: 'rb',
area: ['100%', '50%'],
content: '../page/gnss_q_status?query=' + statusType,
});
}
}
function locateDeviceOnMap(deviceId, latitude, longitude) {
currentSearchedDevice = deviceId;
var success = DeviceMarkers.locateDevice(deviceId);
if (success && window.layer && typeof window.layer.closeAll === 'function') {
window.layer.closeAll();
}
return success;
}
function locateDeviceDirectly(deviceId) {
currentSearchedDevice = deviceId;
return DeviceMarkers.locateDevice(deviceId);
}
function getCurrentSearchedDevice() {
return currentSearchedDevice;
}
function getMarkerState() {
return markerState;
}
function setMarkerState(state) {
markerState = state;
}
function isFilterModeActive() {
return isFilterActive;
}
function getFilteredDeviceIds() {
return filteredDeviceIds;
}
return {
searchDevice: searchDevice,
clearSearch: clearSearch,
showAllDevices: showAllDevices,
showWarning1Devices: showWarning1Devices,
showWarning2Devices: showWarning2Devices,
filterDevicesByStatus: filterDevicesByStatus,
queryDevices: queryDevices,
locateDeviceOnMap: locateDeviceOnMap,
locateDeviceDirectly: locateDeviceDirectly,
getCurrentSearchedDevice: getCurrentSearchedDevice,
getMarkerState: getMarkerState,
setMarkerState: setMarkerState,
clearDeviceFilter: clearDeviceFilter,
isFilterModeActive: isFilterModeActive,
getFilteredDeviceIds: getFilteredDeviceIds
};
})();

View File

@ -0,0 +1,343 @@
var WeatherForecast = (function() {
'use strict';
var weatherEnabled = false;
var weatherData = null;
var currentForecastIndex = 0;
var currentWeatherDevice = null;
function init() {
// 天气预报模块初始化完成
}
function toggleWeatherForecast() {
var role = window.userRole || 'USER';
if (role !== 'SUPER_ADMIN') {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('您没有权限使用此功能');
}
return;
}
var enableSwitch = document.getElementById('enableWeatherSwitch');
weatherEnabled = enableSwitch ? enableSwitch.checked : false;
if (weatherEnabled) {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg(
'搜索设备或点击地图设备图标即可自动查询天气预测',
{time: 3000, area: ['300px', '80px']}
);
}
} else {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('天气预测功能已关闭');
}
closeWeatherCard();
}
var menu = document.getElementById('mapFunctionsMenu');
if (menu) {
menu.classList.remove('show');
}
}
function showWeatherForecast(deviceInfo) {
currentWeatherDevice = deviceInfo;
weatherData = null;
var role = window.userRole || 'USER';
if (role !== 'SUPER_ADMIN') {
return;
}
if (!weatherEnabled) {
if (window.layer && typeof window.layer.msg === 'function') {
window.layer.msg('天气预测功能未启用', {time: 2000});
}
return;
}
var deviceIdElement = document.getElementById('weatherDeviceId');
var deviceCoordsElement = document.getElementById('weatherDeviceCoords');
if (deviceIdElement) {
deviceIdElement.textContent = '设备: ' + deviceInfo.deviceid;
}
if (deviceCoordsElement) {
deviceCoordsElement.textContent =
'坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4);
}
var weatherCard = document.getElementById('weatherForecastCard');
if (weatherCard) {
weatherCard.style.display = 'block';
}
var contentElement = document.getElementById('weatherForecastContent');
if (contentElement) {
contentElement.innerHTML =
'<div class="weather-loading">' +
'<i class="layui-icon layui-icon-loading layui-icon-anim-rotate"></i>' +
'<p>正在获取天气预测数据...</p>' +
'</div>';
}
var prevBtn = document.getElementById('prevForecast');
var nextBtn = document.getElementById('nextForecast');
var timeDisplay = document.getElementById('forecastTimeDisplay');
if (prevBtn) prevBtn.disabled = true;
if (nextBtn) nextBtn.disabled = true;
if (timeDisplay) timeDisplay.textContent = '--:--';
fetchWeatherData(deviceInfo.latitude, deviceInfo.longitude);
}
function closeWeatherCard() {
var weatherCard = document.getElementById('weatherForecastCard');
if (weatherCard) {
weatherCard.style.display = 'none';
}
currentWeatherDevice = null;
weatherData = null;
}
function showPrevForecast() {
if (!weatherData || currentForecastIndex <= 0) return;
currentForecastIndex--;
displayCurrentForecast();
updateForecastNavigation();
}
function showNextForecast() {
if (!weatherData || !weatherData.ts || currentForecastIndex >= weatherData.ts.length - 1) return;
currentForecastIndex++;
displayCurrentForecast();
updateForecastNavigation();
}
function updateForecastNavigation() {
if (!weatherData || !weatherData.ts) return;
var prevBtn = document.getElementById('prevForecast');
var nextBtn = document.getElementById('nextForecast');
var timeDisplay = document.getElementById('forecastTimeDisplay');
if (prevBtn) prevBtn.disabled = (currentForecastIndex <= 0);
if (nextBtn) nextBtn.disabled = (currentForecastIndex >= weatherData.ts.length - 1);
if (timeDisplay) {
var timestamp = weatherData.ts[currentForecastIndex];
var date = new Date(timestamp);
timeDisplay.textContent = formatDateTime(date);
}
}
function fetchWeatherData(lat, lon) {
var url = 'https://api.open-meteo.com/v1/forecast?' +
'latitude=' + lat.toFixed(4) +
'&longitude=' + lon.toFixed(4) +
'&current=temperature_2m,wind_speed_10m,wind_direction_10m,relative_humidity_2m,surface_pressure' +
'&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m,wind_direction_10m,precipitation,surface_pressure,wind_gusts_10m' +
'&forecast_days=3' +
'&timezone=auto';
fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
})
.then(function(response) {
if (!response.ok) {
throw new Error('网络响应状态: ' + response.status);
}
return response.json();
})
.then(function(data) {
weatherData = transformOpenMeteoData(data);
var currentTime = new Date().getTime();
var closestIndex = 0;
var futureIndex = -1;
if (weatherData.ts && weatherData.ts.length > 0) {
for (var i = 0; i < weatherData.ts.length; i++) {
if (weatherData.ts[i] > currentTime) {
futureIndex = i;
break;
}
}
if (futureIndex >= 0) {
closestIndex = futureIndex;
} else {
var smallestDiff = Number.MAX_VALUE;
for (var i = 0; i < weatherData.ts.length; i++) {
var diff = Math.abs(weatherData.ts[i] - currentTime);
if (diff < smallestDiff) {
smallestDiff = diff;
closestIndex = i;
}
}
}
}
currentForecastIndex = closestIndex;
displayCurrentForecast();
updateForecastNavigation();
})
.catch(function(error) {
console.error('天气数据获取失败:', error);
displayWeatherError('获取天气数据失败: ' + error.message);
});
}
function transformOpenMeteoData(data) {
var transformed = {
ts: [],
'temp-surface': [],
'wind-speed': [],
'wind-direction': [],
'precipitation': [],
'rh-surface': [],
'pressure-surface': [],
'gust-surface': []
};
if (!data.hourly || !data.hourly.time) {
return transformed;
}
for (var i = 0; i < data.hourly.time.length; i++) {
transformed.ts.push(new Date(data.hourly.time[i]).getTime());
transformed['temp-surface'].push(data.hourly.temperature_2m[i]);
var windSpeedMs = data.hourly.wind_speed_10m[i] ? data.hourly.wind_speed_10m[i] / 3.6 : null;
transformed['wind-speed'].push(windSpeedMs);
transformed['wind-direction'].push(data.hourly.wind_direction_10m[i]);
transformed['precipitation'].push(data.hourly.precipitation[i]);
transformed['rh-surface'].push(data.hourly.relative_humidity_2m[i]);
transformed['pressure-surface'].push(data.hourly.surface_pressure[i]);
var gustMs = data.hourly.wind_gusts_10m[i] ? data.hourly.wind_gusts_10m[i] / 3.6 : null;
transformed['gust-surface'].push(gustMs);
}
return transformed;
}
function displayCurrentForecast() {
if (!weatherData || !weatherData.ts || weatherData.ts.length === 0) {
displayWeatherError('无可用的天气预测数据');
return;
}
var i = currentForecastIndex;
var forecastHtml = '<div class="weather-forecast-item"><div class="weather-param-grid">';
if (weatherData['temp-surface'] && weatherData['temp-surface'][i] !== null) {
var temp = weatherData['temp-surface'][i].toFixed(1);
forecastHtml += createWeatherParam('温度', temp + '°C');
}
if (weatherData['wind-speed'] && weatherData['wind-speed'][i] !== null) {
var windSpeed = weatherData['wind-speed'][i].toFixed(1);
forecastHtml += createWeatherParam('风速', windSpeed + ' m/s');
}
if (weatherData['wind-direction'] && weatherData['wind-direction'][i] !== null) {
var windDir = getWindDirectionFromDegrees(weatherData['wind-direction'][i]);
forecastHtml += createWeatherParam('风向', windDir);
}
if (weatherData['precipitation'] && weatherData['precipitation'][i] !== null) {
var precip = weatherData['precipitation'][i].toFixed(1);
forecastHtml += createWeatherParam('降水', precip + ' mm');
}
if (weatherData['rh-surface'] && weatherData['rh-surface'][i] !== null) {
var humidity = weatherData['rh-surface'][i].toFixed(0);
forecastHtml += createWeatherParam('湿度', humidity + '%');
}
if (weatherData['pressure-surface'] && weatherData['pressure-surface'][i] !== null) {
var pressure = weatherData['pressure-surface'][i].toFixed(0);
forecastHtml += createWeatherParam('气压', pressure + ' hPa');
}
if (weatherData['gust-surface'] && weatherData['gust-surface'][i] !== null) {
var gust = weatherData['gust-surface'][i].toFixed(1);
forecastHtml += createWeatherParam('阵风', gust + ' m/s');
}
forecastHtml += '</div></div>';
var contentElement = document.getElementById('weatherForecastContent');
if (contentElement) {
contentElement.innerHTML = forecastHtml;
}
}
function displayWeatherError(message) {
var contentElement = document.getElementById('weatherForecastContent');
if (contentElement) {
contentElement.innerHTML =
'<div class="weather-error">' +
'<i class="layui-icon layui-icon-close"></i>' +
'<p>' + message + '</p>' +
'</div>';
}
}
function createWeatherParam(label, value) {
return '<div class="weather-param">' +
'<span class="weather-param-label">' + label + '</span>' +
'<span class="weather-param-value">' + value + '</span>' +
'</div>';
}
function formatDateTime(date) {
var month = (date.getMonth() + 1).toString().padStart(2, '0');
var day = date.getDate().toString().padStart(2, '0');
var hours = date.getHours().toString().padStart(2, '0');
var minutes = date.getMinutes().toString().padStart(2, '0');
return month + '-' + day + ' ' + hours + ':' + minutes;
}
function getWindDirection(u, v) {
var angle = Math.atan2(-u, -v) * 180 / Math.PI;
angle = (angle + 360) % 360;
var directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
var index = Math.round(angle / 45) % 8;
return directions[index];
}
function getWindDirectionFromDegrees(degrees) {
if (degrees === null || degrees === undefined) return '无风';
var directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
var index = Math.round(degrees / 45) % 8;
return directions[index];
}
function isEnabled() {
return weatherEnabled;
}
return {
init: init,
toggleWeatherForecast: toggleWeatherForecast,
showWeatherForecast: showWeatherForecast,
closeWeatherCard: closeWeatherCard,
showPrevForecast: showPrevForecast,
showNextForecast: showNextForecast,
isEnabled: isEnabled
};
})();

View File

@ -67,9 +67,9 @@
var cfg_cols = [ var cfg_cols = [
{field: 'deviceid', title: '设备号', width: 100}, {field: 'deviceid', title: '设备号', width: 100},
{field: 'createtime', title: '上报时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"}, {field: 'createtime', title: '上报时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'roll', title: 'roll', templet: "<div>{{d.roll.toFixed(2)}}</div>"}, {field: 'roll', title: 'roll', templet: "<div>{{d.roll.toFixed(3)}}</div>"},
{field: 'pitch', title: 'pitch', templet: "<div>{{d.pitch.toFixed(2)}}</div>"}, {field: 'pitch', title: 'pitch', templet: "<div>{{d.pitch.toFixed(3)}}</div>"},
{field: 'yaw', title: 'yaw', templet: "<div>{{d.yaw.toFixed(2)}}</div>"}, {field: 'yaw', title: 'yaw', templet: "<div>{{d.yaw.toFixed(3)}}</div>"},
{field: 'rssi', title: '信号强度'}, {field: 'rssi', title: '信号强度'},
{field: 'voltage', title: '电压(mV)'}, {field: 'voltage', title: '电压(mV)'},
{field: 'solarvoltage', title: '光伏电压(mV)'}, {field: 'solarvoltage', title: '光伏电压(mV)'},

View File

@ -53,16 +53,38 @@
</fieldset> </fieldset>
<table class="layui-hide" id="forwardParaTableId" lay-filter="forwardParaTableFilter"></table> <table class="layui-hide" id="forwardParaTableId" lay-filter="forwardParaTableFilter"></table>
<script type="text/html" id="currentTableBar">
<a class="layui-btn layui-btn-xs layui-btn-danger data-count-delete" lay-event="delete">删除</a>
</script>
<script type="text/html" id="toolbarTop">
<div class="layui-btn-container" th:if="${role=='SUPER_ADMIN'}">
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add">添加</button>
</div>
</script>
</div> </div>
</div> </div>
<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="none"> <script th:inline="javascript">
layui.use(['form', 'table'], function () { layui.use(['form', 'table'], function () {
var $ = layui.$, var $ = layui.$,
form = layui.form, form = layui.form,
table = layui.table, table = layui.table,
laydate = layui.laydate; laydate = layui.laydate;
var cfg_cols = [
{field: 'projectid', title: '项目号', sort: true},
{field: 'deviceid', title: '设备号'},
{field: 'createtime', title: '推送时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'starttime', title: '数据开始时间', templet: "<div>{{layui.util.toDateString(d.starttime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'endtime', title: '数据结束时间', templet: "<div>{{layui.util.toDateString(d.endtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'fwd_group_id', title: '推送组'},
{field: 'state', title: '状态',templet: '#stateTrans'},
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
];
if([[${role}]] != "SUPER_ADMIN") {
cfg_cols[7].hide = true;
}
laydate.render({ laydate.render({
elem: '#ID-laydate-start-date1', elem: '#ID-laydate-start-date1',
type: 'datetime' type: 'datetime'
@ -85,17 +107,11 @@
table.render({ table.render({
elem: '#forwardParaTableId', elem: '#forwardParaTableId',
url: '/fwd/resend_records', url: '/fwd/resend_records',
toolbar: '#toolbarTable', toolbar: '#toolbarTop',
defaultToolbar: ['filter'], defaultToolbar: ['filter'],
cols: [[ cols: [
{field: 'projectid', title: '项目号', sort: true}, cfg_cols
{field: 'deviceid', title: '设备号'}, ],
{field: 'createtime', title: '推送时间', templet: "<div>{{layui.util.toDateString(d.createtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'starttime', title: '数据开始时间', templet: "<div>{{layui.util.toDateString(d.starttime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'endtime', title: '数据结束时间', templet: "<div>{{layui.util.toDateString(d.endtime, 'yyyy-MM-dd HH:mm:ss')}}</div>"},
{field: 'fwd_group_id', title: '推送组'},
{field: 'state', title: '状态',templet: '#stateTrans'}
]],
limits: [10, 20, 50, 100, 150], limits: [10, 20, 50, 100, 150],
limit: 10, limit: 10,
page: true, page: true,
@ -119,8 +135,50 @@
return false; return false;
}); });
table.on('toolbar(forwardParaTableFilter)', function (obj) {
if (obj.event === 'add') { // 监听添加操作
var index = layer.open({
title: '添加补传记录',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
area: ['100%', '100%'],
content: '../page/table/resend_record_add'
});
$(window).on("resize", function () {
layer.full(index);
});
}
}); });
table.on('tool(forwardParaTableFilter)', function (obj) {
var data = obj.data;
if (obj.event === 'delete') {
layer.confirm('确定删除?', function(index){
$.ajax({
type:"POST",
url:"/fwd/resend_records/delete",
data:{
'del_id':data.id
},
success: function (data) {
table.reload('forwardParaTableId');
},
error: function () {
console.log("ajax error");
}
});
layer.close(index);
});
}
});
});
function onUpdated(){
layui.table.reload('forwardParaTableId');
}
</script> </script>
<script type="text/html" id="stateTrans"> <script type="text/html" id="stateTrans">
@ -129,9 +187,9 @@
{{# } else if(d.state == 1){ }} {{# } else if(d.state == 1){ }}
<span class="layui-badge layui-bg-red">推送失败</span> <span class="layui-badge layui-bg-red">推送失败</span>
{{# } else if(d.state == 2){ }} {{# } else if(d.state == 2){ }}
<span class="layui-badge layui-bg-red">断点补传</span> <span class="layui-badge layui-bg-orange">断点补传</span>
{{# } else { }} {{# } else { }}
<span class="layui-badge layui-bg-orange">推送中</span> <span class="layui-badge layui-bg-blue">推送中</span>
{{# } }} {{# } }}
</script> </script>

View File

@ -230,8 +230,9 @@
<hr th:if="${role=='SUPER_ADMIN'}"> <hr th:if="${role=='SUPER_ADMIN'}">
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block" style="float:right" > <div class="layui-input-block" style="float:right" >
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button> <button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">保存</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="initLocBtn">取初值</button> <button class="layui-btn layui-btn-normal" lay-submit lay-filter="initLocBtn">取初值</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="contBtn">续数据</button>
</div> </div>
</div> </div>
</div> </div>
@ -286,6 +287,47 @@
return false; return false;
}); });
form.on('submit(contBtn)', function (data) {
// 弹出输入框
layer.prompt({
title: '请输入接续的设备编号和日期时间,用逗号隔开', // 弹窗标题
formType: 0, // 0-文本输入框
maxlength: 50, // 最大输入长度
value: '', // 初始值
btn: ['确定', '取消'] // 按钮组
}, function(value, index) { // 确定按钮回调
// 处理输入结果
if(value) {
$.ajax({
type:"POST",
url:"/gnss/device/cont_loc",
data:{
'deviceid':$('#deviceid').val(),
'createtime':value
},
success: function (result) {
if(result.code == 0) {
$('#ipose').val(result.ipose);
$('#iposn').val(result.iposn);
$('#iposd').val(result.iposd);
}
else{
layer.alert(result.msg);
}
},
error: function () {
console.log("ajax error");
}
});
} else {
layer.msg('输入内容不能为空!', {icon: 2});
}
// 关闭当前弹窗
layer.close(index);
});
return false;
});
form.on('select(device_type)', function (data) { form.on('select(device_type)', function (data) {
setEcefEditor(); setEcefEditor();
}); });

View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>补传数据</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">
<style>
body {
background-color: #ffffff;
}
</style>
</head>
<body>
<div class="layui-form layuimini-form">
<input type="hidden" name="id" id="id">
<div class="layui-form-item">
<label class="layui-form-label">所属部门</label>
<div class="layui-input-inline">
<select name="tenantid" id="tenantid" lay-search="" lay-filter="tenant">
<option value="">全部</option>
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.id}"></option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">所属项目</label>
<div class="layui-input-inline">
<select name="project_id" id="projectid" lay-search="" lay-filter="project">
<option value="">全部</option>
<option th:each="item : ${project_list}" th:text="${item.project_id}" th:value="${item.project_id}"></option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">设备编号</label>
<div class="layui-input-inline">
<input type="text" name="deviceid" id="deviceid" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">推送组</label>
<div class="layui-input-inline">
<select name="fwd_group_id" id="fwd_group_id" lay-verify="required" lay-reqtext="不能为空" lay-search="">
<option th:each="item : ${gnss_group_fwd_list}" th:text="${item.name}" th:value="${item.name}"></option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">时间段</label>
<div class="layui-input-inline">
<input type="text" name="starttime" autocomplete="off" lay-verify="required" lay-reqtext="不能为空" id="ID-laydate-start-date" class="layui-input" placeholder="开始日期">
</div>
<div class="layui-input-inline">
<input type="text" name="endtime" autocomplete="off" lay-verify="required" lay-reqtext="不能为空" id="ID-laydate-end-date" class="layui-input" placeholder="结束日期">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
</div>
</div>
</div>
<script src="../../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
layui.use(['form'], function () {
var form = layui.form,
$ = layui.$,
laydate = layui.laydate;
var iframeIndex = parent.layer.getFrameIndex(window.name);
laydate.render({
elem: '#ID-laydate-start-date',
type: 'datetime'
});
laydate.render({
elem: '#ID-laydate-end-date',
type: 'datetime'
});
// 所属部门下拉框改变,修改项目下拉框
form.on('select(tenant)', function (data) {
console.log(data.value);
$.ajax({
type:"GET",
url:"/gnss/device/q_project",
data:{
'tenantid':data.value
},
success: function(result) {
$('#projectid').empty();
$('#projectid').append(new Option("全部", ""));
$('#deviceid').empty();
//console.log(result);
$.each(result.data, function (index, item) {
$('#projectid')
.append(new Option(item.project_id, item.project_id));
});
layui.form.render("select");
},
error: function () {
console.log("ajax error");
}
});
});
//监听提交
form.on('submit(saveBtn)', function (data) {
$.ajax({
type:"POST",
url:"/fwd/resend_records/add",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(data.field),
success: function (result) {
parent.onUpdated();
parent.layer.close(iframeIndex);
},
error: function () {
console.log("ajax error");
parent.layer.close(iframeIndex);
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -3,7 +3,9 @@ package com.imdroid.ntripproxy.service;
public class Ntrip2Channels { public class Ntrip2Channels {
final private String localHost="127.0.0.1"; final private String localHost="127.0.0.1";
final private int localPort=9903; final private int localPort=9903;
final private String remoteHost="47.107.50.52"; final private String remoteHost="8.134.185.53";
//final private String remoteHost="100.91.37.6";
//final private String remoteHost="47.107.50.52";
final private int remotePort=9903; final private int remotePort=9903;
public static final Ntrip2Channels INSTANCE = new Ntrip2Channels(); public static final Ntrip2Channels INSTANCE = new Ntrip2Channels();