更新事件
parent
9cbc42d90a
commit
ee09c901c2
|
@ -1,4 +1,4 @@
|
|||
package com.god.data.server;
|
||||
package com.god.data;
|
||||
|
||||
import com.god.common.security.annotation.EnableCustomConfig;
|
||||
import com.god.common.security.annotation.EnableMyFeignClients;
|
||||
|
@ -17,6 +17,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||
@SpringBootApplication
|
||||
public class GodCarDataApplication {
|
||||
public static void main (String[] args) {
|
||||
SpringApplication.run(com.god.data.server.GodCarDataApplication.class, args);
|
||||
SpringApplication.run(GodCarDataApplication.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package com.god.data.common.domain;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 电子围栏对象
|
||||
* @Author fst
|
||||
* @date 2023/11/28 21:47
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ElePoint {
|
||||
|
||||
/**
|
||||
* 电子围栏id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 电子围栏名称
|
||||
*/
|
||||
private String fenceName;
|
||||
|
||||
/**
|
||||
* 点赞围栏坐标字符串 格式:经度,维度;经度,维度
|
||||
*/
|
||||
private String fenceLocation;
|
||||
|
||||
/**
|
||||
* 点赞围栏状态 0 未激活 1 已激活
|
||||
*/
|
||||
private Integer fenceStatus;
|
||||
|
||||
/**
|
||||
* 电子围栏判断状态 1驶出 2驶离
|
||||
*/
|
||||
private Integer driveStatus;
|
||||
|
||||
/**
|
||||
* 车辆vin
|
||||
*/
|
||||
private Long carVinId;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.god.data.common.domain;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 电子围栏算法对象
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
public class Point {
|
||||
/**
|
||||
* 经度(-180~180,东经正数,西经负数)
|
||||
*/
|
||||
private double lng;
|
||||
/**
|
||||
* 维度(-90~90,北纬正数,南纬负数)
|
||||
*/
|
||||
private double lat;
|
||||
|
||||
public Point(double lng, double lat) {
|
||||
this.lng = lng;
|
||||
this.lat = lat;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.springframework.kafka.support.serializer.JsonDeserializer;
|
|||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.god.data.contents.KafkaContent.KAFKA_CON;
|
||||
import static com.god.data.contents.KafkaContent.TOPIC;
|
||||
|
||||
/**
|
||||
|
@ -24,66 +25,30 @@ import static com.god.data.contents.KafkaContent.TOPIC;
|
|||
@Data
|
||||
@Builder
|
||||
@Configuration
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ConfigurationProperties(prefix = "spring.kafka.consumer")
|
||||
public class KafkaConsumerConfig {
|
||||
// kafka消费者服务器
|
||||
private String bootstrapServers;
|
||||
// 分组id
|
||||
private String groupId;
|
||||
// 是否自动提交偏移量,默认为true,为了避免出现重复数据和数据丢失,设置为false,手动提交
|
||||
private boolean enableAutoCommit;
|
||||
|
||||
private Integer autoCommitInterval;
|
||||
|
||||
private String sessionTimeout;
|
||||
|
||||
private String maxPollIntervalTime;
|
||||
|
||||
private String maxPollRecords;
|
||||
|
||||
private String autoOffsetReset;
|
||||
|
||||
private String keyDeserializer;
|
||||
|
||||
private String valueDeserializer;
|
||||
|
||||
@Bean
|
||||
public KafkaConsumer<String, String> consumerConfigs() {
|
||||
Properties properties = new Properties();
|
||||
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
|
||||
//是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
|
||||
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
|
||||
//自动提交的时间间隔,自动提交开启时生效
|
||||
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
|
||||
//该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
|
||||
//earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费分区的记录
|
||||
//latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据(在消费者启动之后生成的记录)
|
||||
//none:当各分区都存在已提交的offset时,从提交的offset开始消费;只要有一个分区不存在已提交的offset,则抛出异常
|
||||
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
|
||||
//两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance
|
||||
properties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalTime);
|
||||
//这个参数定义了poll方法最多可以拉取多少条消息,默认值为500。如果在拉取消息的时候新消息不足500条,那有多少返回多少;如果超过500条,每次只返回500。
|
||||
//这个默认值在有些场景下太大,有些场景很难保证能够在5min内处理完500条消息,
|
||||
//如果消费者无法在5分钟内处理完500条消息的话就会触发reBalance,
|
||||
//然后这批消息会被分配到另一个消费者中,还是会处理不完,这样这批消息就永远也处理不完。
|
||||
//要避免出现上述问题,提前评估好处理一条消息最长需要多少时间,然后覆盖默认的max.poll.records参数
|
||||
//注:需要开启BatchListener批量监听才会生效,如果不开启BatchListener则不会出现reBalance情况
|
||||
properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
|
||||
//当broker多久没有收到consumer的心跳请求后就触发reBalance,默认值是10s
|
||||
properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
|
||||
//序列化(建议使用Json,这种序列化方式可以无需额外配置传输实体类)
|
||||
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
|
||||
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
|
||||
//设置分区器
|
||||
properties.put("partitioner.class", "com.god.kafka.partitioner.MyPartitioner");
|
||||
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
|
||||
consumer.subscribe(List.of(TOPIC));
|
||||
return consumer;
|
||||
public KafkaConsumer<String, String> consumerInit(){
|
||||
Properties props = new Properties();
|
||||
//设置kafka服务器地址
|
||||
props.put("bootstrap.servers", KAFKA_CON);
|
||||
//每个消费者分配独立的组号
|
||||
props.put("group.id", "g2");
|
||||
//如果value合法,则自动提交偏移量
|
||||
props.put("enable.auto.commit", "true");
|
||||
//设置多久一次更新被消费消息的偏移量
|
||||
props.put("auto.commit.interval.ms", "1000");
|
||||
//设置会话响应的时间,超过这个时间kafka可以选择放弃消费或者消费下一条消息
|
||||
props.put("session.timeout.ms", "30000");
|
||||
//自动重置offset
|
||||
//earliest 在偏移量无效的情况下 消费者将从起始位置读取分区的记录
|
||||
//latest 在偏移量无效的情况下 消费者将从最新位置读取分区的记录
|
||||
props.put("auto.offset.reset","earliest");
|
||||
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
|
||||
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
|
||||
|
||||
return new KafkaConsumer<>(props);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package com.god.data.contents;
|
|||
*/
|
||||
public class KafkaContent {
|
||||
|
||||
public static final String TOPIC = "top";
|
||||
public static final String TOPIC = "test";
|
||||
|
||||
public static final String KAFKA_CON = "39.100.65.135:39092,39.100.65.135:29092,39.100.65.135:19092";
|
||||
public static final String KAFKA_CON = "10.100.1.8:9092";
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package com.god.data.partitioner;
|
||||
|
||||
import org.apache.kafka.clients.producer.Partitioner;
|
||||
import org.apache.kafka.common.Cluster;
|
||||
import org.apache.kafka.common.PartitionInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author:fst
|
||||
* @date:2023/11/25
|
||||
* @aim:自定义分区器
|
||||
*/
|
||||
@Component
|
||||
public class MyPartitioner implements Partitioner {
|
||||
/**
|
||||
* 自定义kafka分区主要解决用户分区数据倾斜问题 提高并发效率(假设 3 分区)
|
||||
* @param topic 消息队列名
|
||||
* @param key 用户传入key
|
||||
* @param keyBytes key字节数组
|
||||
* @param value 用户传入value
|
||||
* @param valueBytes value字节数组
|
||||
* @param cluster 当前kafka节点数
|
||||
* @return 如果3个分区,返回 0 1 2
|
||||
*/
|
||||
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
|
||||
//获取topic的partitions信息
|
||||
List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
|
||||
int partitionsNum = partitionInfos.size();
|
||||
// 这里以 key 的哈希值作为分区选择依据
|
||||
System.out.println("================================");
|
||||
System.out.println(Math.abs(key.hashCode()) % partitionsNum);
|
||||
return Math.abs(key.hashCode()) % partitionsNum;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public void configure(Map<String, ?> map) {
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package com.god.data.rabbitmq.producer;
|
||||
|
||||
import com.god.data.common.domain.LogMessage;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 消息发送者 - Producer。
|
||||
* @Component Producer类型的对象,必须交由Spring容器管理。
|
||||
* 使用SpringBoot提供的AMQP启动器,来访问rabbitmq的时候,都是通过AmqpTemplate来实现的。
|
||||
* 如果全局配置文件中,配置了rabbitmq相关内容,且工程依赖了starter-amqp,则spring容器自动创建AmqpTemplate对象。
|
||||
*/
|
||||
@Component
|
||||
public class Sender {
|
||||
|
||||
@Autowired
|
||||
private AmqpTemplate rabbitAmqpTemplate;
|
||||
|
||||
/*
|
||||
* 发送消息的方法
|
||||
*/
|
||||
public void send(LogMessage msg){
|
||||
/*
|
||||
|
||||
convertAndSend - 转换并发送消息的template方法。
|
||||
是将传入的普通java对象,转换为rabbitmq中需要的message类型对象,并发送消息到rabbitmq中。
|
||||
参数一:交换器名称。 类型是String
|
||||
参数二:路由键。 类型是String
|
||||
参数三:消息,是要发送的消息内容对象。类型是Object
|
||||
*/
|
||||
this.rabbitAmqpTemplate.convertAndSend(msg.getExchange(), UUID.randomUUID().toString(), msg);
|
||||
}
|
||||
}
|
|
@ -3,18 +3,23 @@ package com.god.data.service;
|
|||
import com.god.common.core.utils.SpringUtils;
|
||||
import com.god.common.redis.service.RedisService;
|
||||
import com.god.data.common.domain.CarMessage;
|
||||
import com.god.data.service.impl.ElectronicFenceEvent;
|
||||
import com.god.data.utils.AnalyzeUtils;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 获取kafka数据进行解析,并执行事件
|
||||
* @author fst
|
||||
|
@ -26,22 +31,22 @@ public class ParseDataService {
|
|||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
private final KafkaConsumer<String, String> consumer;
|
||||
|
||||
@Autowired
|
||||
public ParseDataService(KafkaConsumer<String, String> consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
private KafkaConsumer<String, String> consumer;
|
||||
|
||||
@PostConstruct
|
||||
public void start(){
|
||||
|
||||
new Thread(() -> {
|
||||
consumer.subscribe(Arrays.asList("test"));
|
||||
log.info("kafka数据解析服务启动");
|
||||
while (true){
|
||||
ConsumerRecords<String, String> records = null;
|
||||
try {
|
||||
//死循环拉取kafka数据
|
||||
records = consumer.poll(Duration.ofMillis(100));
|
||||
records = consumer.poll(Duration.ofMillis(1000));
|
||||
for (ConsumerRecord<String, String> record : records) {
|
||||
//把报文解析成对象
|
||||
CarMessage carMessage = AnalyzeUtils.parseVehicleData(record.value());
|
||||
//根据对象车辆vin获取事件集合,从redis中获取
|
||||
List<String> eventList = redisService.getCacheList("event" + carMessage.getVin());
|
||||
|
@ -52,6 +57,7 @@ public class ParseDataService {
|
|||
eventService.execute(carMessage);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(record.offset() +" - "+ record.key() +" - "+ record.value());
|
||||
}
|
||||
}catch (Exception e){
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
package com.god.data.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.god.common.redis.service.RedisService;
|
||||
import com.god.data.common.domain.CarMessage;
|
||||
import com.god.data.common.domain.ElePoint;
|
||||
import com.god.data.common.domain.Point;
|
||||
import com.god.data.service.EventService;
|
||||
import com.god.data.utils.GeofencingUtils;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 电子围栏数据解析事件
|
||||
|
@ -11,14 +22,61 @@ import org.springframework.stereotype.Service;
|
|||
*/
|
||||
@Service(value = "Fence")
|
||||
public class ElectronicFenceEvent implements EventService {
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Override
|
||||
public void insert() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CarMessage carMessage) {
|
||||
|
||||
//从redis中获取当前车辆的电子围栏数据
|
||||
List<ElePoint> list = redisService.getCacheList("Fence" + carMessage.getVin());
|
||||
//根据当前经纬度和电子围栏数据进行判断,调用算法工具类
|
||||
for (ElePoint elePoint : list) {
|
||||
//获取电子围栏数据
|
||||
String s = elePoint.getFenceLocation();
|
||||
String[] split = s.split(";");
|
||||
//转换为字符串集合,集合中字符串是 经度,纬度
|
||||
List<String> strList = Arrays.asList(split);
|
||||
//把字符串集合转换为Point集合,point存放经纬度
|
||||
List<Point> points = strList.stream()
|
||||
.map(str -> {
|
||||
String[] split1 = str.split(",");
|
||||
return new Point(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
|
||||
}).collect(Collectors.toList());
|
||||
//获取经纬度
|
||||
Point point = new Point();
|
||||
//把当前车辆的经纬度赋值给point对象
|
||||
point.setLng(carMessage.getLongitude().doubleValue());
|
||||
point.setLat(carMessage.getLatitude().doubleValue());
|
||||
//判断车辆当前是否在点赞围栏中
|
||||
boolean inPolygon = GeofencingUtils.isInPolygon(point, points);
|
||||
//判断当前车辆是驶入/驶出围栏
|
||||
if (elePoint.getDriveStatus().equals(1)){
|
||||
//如果不在围栏范围内则,触发驶出电子围栏事件 ,存入rabbitmq中
|
||||
if (!inPolygon){
|
||||
rabbitTemplate.convertAndSend("OUT_FENCE", JSON.toJSON(elePoint), message -> {
|
||||
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
|
||||
return message;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (elePoint.getDriveStatus().equals(2)){
|
||||
//如果不在围栏范围内则,触发驶入电子围栏事件 ,存入rabbitmq中
|
||||
if (!inPolygon){
|
||||
rabbitTemplate.convertAndSend("IN_FENCE", JSON.toJSON(elePoint), message -> {
|
||||
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
|
||||
return message;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.god.data.service.impl;
|
|||
import com.god.common.redis.service.RedisService;
|
||||
import com.god.data.common.domain.CarMessage;
|
||||
import com.god.data.service.EventService;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -18,6 +19,8 @@ public class FaultAlarmEvent implements EventService {
|
|||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -33,7 +36,10 @@ public class FaultAlarmEvent implements EventService {
|
|||
if (carMessage.getBatteryStatus()==1){
|
||||
strings.add("111");
|
||||
}
|
||||
//把对应的故障码存入rabbitmq
|
||||
//把对应的故障码集合存入rabbitmq
|
||||
if (strings.size()>0){
|
||||
rabbitTemplate.convertAndSend("god.car.fault.alarm",strings);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package com.god.data.service.impl;
|
||||
|
||||
import com.god.common.redis.service.RedisService;
|
||||
import com.god.data.common.domain.CarMessage;
|
||||
import com.god.data.service.EventService;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
|
@ -11,6 +15,16 @@ import org.springframework.stereotype.Service;
|
|||
*/
|
||||
@Service(value = "RealTimeTrajectory")
|
||||
public class RealTimeTrajectoryEvent implements EventService {
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
// @Autowired
|
||||
// private RedisTemplate<String,Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Override
|
||||
public void insert() {
|
||||
|
||||
|
@ -18,7 +32,8 @@ public class RealTimeTrajectoryEvent implements EventService {
|
|||
|
||||
@Override
|
||||
public void execute(CarMessage carMessage) {
|
||||
|
||||
//实时轨迹只需要把当前车辆的经纬度发送到redis集合即可
|
||||
// redisTemplate.opsForList().set("Trajectory",1,carMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,76 +15,70 @@ public class AnalyzeUtils {
|
|||
/**
|
||||
* 解析报文获取报文对象
|
||||
*/
|
||||
public static CarMessage parseVehicleData(String hexInput) {
|
||||
//先去除空格
|
||||
String inputString = hexInput.replaceAll(" ", "");
|
||||
//调用工具类解析报文
|
||||
String input = hexToString(inputString);
|
||||
public static CarMessage parseVehicleData(String input) {
|
||||
//把数据处理成java对象
|
||||
CarMessage carMessage = new CarMessage();
|
||||
CarMessage.builder()
|
||||
.vin(input.substring(1, 17))
|
||||
.timeString(Long.valueOf(input.substring(18, 30)))
|
||||
.longitude(BigDecimal.valueOf(Double.parseDouble(input.substring(31, 41))))
|
||||
.latitude(BigDecimal.valueOf(Double.parseDouble(input.substring(42, 51))))
|
||||
.speed(BigDecimal.valueOf(Double.parseDouble(input.substring(52, 57))))
|
||||
.sumMileage(BigDecimal.valueOf(Double.parseDouble(input.substring(58, 68))))
|
||||
.sumVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(69, 74))))
|
||||
.sumElectricity(BigDecimal.valueOf(Double.parseDouble(input.substring(75, 79))))
|
||||
.sumResistance(BigDecimal.valueOf(Double.parseDouble(input.substring(80, 88))))
|
||||
.gear(input.substring(89, 89))
|
||||
.acceleratorPedal(BigDecimal.valueOf(Double.parseDouble(input.substring(90, 91))))
|
||||
.brakePedal(BigDecimal.valueOf(Double.parseDouble(input.substring(92, 93))))
|
||||
.specificFuelConsumption(BigDecimal.valueOf(Double.parseDouble(input.substring(94, 98))))
|
||||
.motorControllerTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(99, 104))))
|
||||
.motorSpeed(Long.valueOf(input.substring(105, 109)))
|
||||
.motorTorque(Long.valueOf(input.substring(110, 113)))
|
||||
.motorTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(114, 119))))
|
||||
.motorTage(BigDecimal.valueOf(Double.parseDouble(input.substring(120, 124))))
|
||||
.motorCurrent(BigDecimal.valueOf(Double.parseDouble(input.substring(125, 132))))
|
||||
.remainingBattery(BigDecimal.valueOf(Double.parseDouble(input.substring(133, 138))))
|
||||
.maximumFeedbackPower(BigDecimal.valueOf(Double.parseDouble(input.substring(139, 144))))
|
||||
.maximumDischargePower(BigDecimal.valueOf(Double.parseDouble(input.substring(145, 150))))
|
||||
.selfCheckCounter(Integer.valueOf(input.substring(151, 152)))
|
||||
.totalBatteryCurrent(BigDecimal.valueOf(Double.parseDouble(input.substring(153, 157))))
|
||||
.totalBatteryVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(158, 163))))
|
||||
.singleBatteryMaxVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(164, 167))))
|
||||
.singleBatteryMinVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(168, 171))))
|
||||
.singleBatteryMaxTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(172, 177))))
|
||||
.singleBatteryMinTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(178, 183))))
|
||||
.availableBatteryCapacity(BigDecimal.valueOf(Double.parseDouble(input.substring(184, 189))))
|
||||
.vehicleStatus(Integer.valueOf(input.substring(190, 190)))
|
||||
.chargingStatus(Integer.valueOf(input.substring(191, 191)))
|
||||
.operatingStatus(Integer.valueOf(input.substring(192, 192)))
|
||||
.socStatus(Integer.valueOf(input.substring(193, 193)))
|
||||
.chargingEnergyStorageStatus(Integer.valueOf(input.substring(194, 194)))
|
||||
.driveMotorStatus(Integer.valueOf(input.substring(195, 195)))
|
||||
.positionStatus(Integer.valueOf(input.substring(196, 196)))
|
||||
.easStatus(Integer.valueOf(input.substring(197, 197)))
|
||||
.ptcStatus(Integer.valueOf(input.substring(198, 198)))
|
||||
.epsStatus(Integer.valueOf(input.substring(199, 199)))
|
||||
.absStatus(Integer.valueOf(input.substring(200, 200)))
|
||||
.mcuStatus(Integer.valueOf(input.substring(201, 201)))
|
||||
.heatingStatus(Integer.valueOf(input.substring(202, 202)))
|
||||
.batteryStatus(Integer.valueOf(input.substring(203, 203)))
|
||||
.batteryInsulationStatus(Integer.valueOf(input.substring(204, 204)))
|
||||
.dcdcStatus(Integer.valueOf(input.substring(205, 205)))
|
||||
.chgStatus(Integer.valueOf(input.substring(206, 206)));
|
||||
.timeString(Long.valueOf(input.substring(17, 30)))
|
||||
.longitude(BigDecimal.valueOf(Double.parseDouble(input.substring(30, 41))))
|
||||
.latitude(BigDecimal.valueOf(Double.parseDouble(input.substring(41, 51))))
|
||||
.speed(BigDecimal.valueOf(Double.parseDouble(input.substring(51, 57))))
|
||||
.sumMileage(BigDecimal.valueOf(Double.parseDouble(input.substring(57, 68))))
|
||||
.sumVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(68, 74))))
|
||||
.sumElectricity(BigDecimal.valueOf(Double.parseDouble(input.substring(74, 79))))
|
||||
.sumResistance(BigDecimal.valueOf(Double.parseDouble(input.substring(79, 88))))
|
||||
.gear(input.substring(88, 89))
|
||||
.acceleratorPedal(BigDecimal.valueOf(Double.parseDouble(input.substring(89, 91))))
|
||||
.brakePedal(BigDecimal.valueOf(Double.parseDouble(input.substring(91, 93))))
|
||||
.specificFuelConsumption(BigDecimal.valueOf(Double.parseDouble(input.substring(93, 98))))
|
||||
.motorControllerTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(98, 104))))
|
||||
.motorSpeed(Long.valueOf(input.substring(104, 109)))
|
||||
.motorTorque(Long.valueOf(input.substring(109, 113)))
|
||||
.motorTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(113, 119))))
|
||||
.motorTage(BigDecimal.valueOf(Double.parseDouble(input.substring(119, 124))))
|
||||
.motorCurrent(BigDecimal.valueOf(Double.parseDouble(input.substring(124, 132))))
|
||||
.remainingBattery(BigDecimal.valueOf(Double.parseDouble(input.substring(132, 138))))
|
||||
.maximumFeedbackPower(BigDecimal.valueOf(Double.parseDouble(input.substring(138, 144))))
|
||||
.maximumDischargePower(BigDecimal.valueOf(Double.parseDouble(input.substring(144, 150))))
|
||||
.selfCheckCounter(Integer.valueOf(input.substring(150, 152)))
|
||||
.totalBatteryCurrent(BigDecimal.valueOf(Double.parseDouble(input.substring(152, 157))))
|
||||
.totalBatteryVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(157, 163))))
|
||||
.singleBatteryMaxVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(163, 167))))
|
||||
.singleBatteryMinVoltage(BigDecimal.valueOf(Double.parseDouble(input.substring(167, 171))))
|
||||
.singleBatteryMaxTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(171, 177))))
|
||||
.singleBatteryMinTemperature(BigDecimal.valueOf(Double.parseDouble(input.substring(177, 183))))
|
||||
.availableBatteryCapacity(BigDecimal.valueOf(Double.parseDouble(input.substring(183, 189))))
|
||||
.vehicleStatus(Integer.valueOf(input.substring(189, 190)))
|
||||
.chargingStatus(Integer.valueOf(input.substring(190, 191)))
|
||||
.operatingStatus(Integer.valueOf(input.substring(191, 192)))
|
||||
.socStatus(Integer.valueOf(input.substring(192, 193)))
|
||||
.chargingEnergyStorageStatus(Integer.valueOf(input.substring(193, 194)))
|
||||
.driveMotorStatus(Integer.valueOf(input.substring(194, 195)))
|
||||
.positionStatus(Integer.valueOf(input.substring(195, 196)))
|
||||
.easStatus(Integer.valueOf(input.substring(196, 197)))
|
||||
.ptcStatus(Integer.valueOf(input.substring(197, 198)))
|
||||
.epsStatus(Integer.valueOf(input.substring(198, 199)))
|
||||
.absStatus(Integer.valueOf(input.substring(199, 200)))
|
||||
.mcuStatus(Integer.valueOf(input.substring(200, 201)))
|
||||
.heatingStatus(Integer.valueOf(input.substring(201, 202)))
|
||||
.batteryStatus(Integer.valueOf(input.substring(202, 203)))
|
||||
.batteryInsulationStatus(Integer.valueOf(input.substring(203, 204)))
|
||||
.dcdcStatus(Integer.valueOf(input.substring(204, 205)))
|
||||
.chgStatus(Integer.valueOf(input.substring(205, 206)));
|
||||
//返回解析完的对象
|
||||
return carMessage;
|
||||
}
|
||||
|
||||
|
||||
private static String hexToString(String hex) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
for (int i = 0; i < hex.length(); i += 2) {
|
||||
String str = hex.substring(i, i + 2);
|
||||
output.append((char) Integer.parseInt(str, 16));
|
||||
}
|
||||
return output.toString();
|
||||
public static void main(String[] args) {
|
||||
CarMessage carMessage = parseVehicleData("~VIN12345678912345170124349622700000000000000000000000000012.5000000000000000000000000000P000000000000000000000000000000000000000000045000000000000000000000000000000000000000000000000000000011111111111111111©~");
|
||||
System.out.println(carMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
package com.god.data.utils;
|
||||
|
||||
|
||||
import com.god.data.common.domain.Point;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 电子围栏计算
|
||||
* @author : fst
|
||||
* @version : 1.0.0
|
||||
* @date: 2023-11-28 21:02:54
|
||||
*/
|
||||
public class GeofencingUtils {
|
||||
|
||||
/**
|
||||
* 地球半径(米)
|
||||
*/
|
||||
private static final double EARTH_RADIUS = 6378137.0;
|
||||
|
||||
|
||||
private static double rad(double d) {
|
||||
return d * Math.PI / 180.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算是否在圆内
|
||||
* @param radius 半径(单位/米)
|
||||
* @param p1 圆心坐标
|
||||
* @param p2 判断点坐标
|
||||
* @return: boolean true:在圆内,false:在圆外
|
||||
* @date: 2021-11-08 09:44:54
|
||||
*/
|
||||
public static boolean isInCircle(double radius, Point p1, Point p2) {
|
||||
double radLat1 = rad(p1.getLat());
|
||||
double radLat2 = rad(p2.getLat());
|
||||
double a = radLat1 - radLat2;
|
||||
double b = rad(p1.getLng()) - rad(p2.getLng());
|
||||
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
|
||||
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
|
||||
s = s * EARTH_RADIUS;
|
||||
s = Math.round(s * 10000) / 10000;
|
||||
return !(s > radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在矩形区域内
|
||||
* @param lng 测试点经度
|
||||
* @param lat 测试点纬度
|
||||
* @param minLng 矩形四个点中最小经度
|
||||
* @param maxLng 矩形四个点中最大经度
|
||||
* @param minLat 矩形四个点中最小纬度
|
||||
* @param maxLat 矩形四个点中最大纬度
|
||||
* @return boolean true:在矩形内, false:在矩形外
|
||||
* @Title: isInArea
|
||||
*/
|
||||
public static boolean isInRectangleArea(double lng, double lat, double minLng, double maxLng,
|
||||
double minLat, double maxLat) {
|
||||
if (isInRange(lat, minLat, maxLat)) {//如果在纬度的范围内
|
||||
if (minLng * maxLng > 0) {
|
||||
return isInRange(lng, minLng, maxLng);
|
||||
} else {
|
||||
if (Math.abs(minLng) + Math.abs(maxLng) < 180) {
|
||||
return isInRange(lng, minLng, maxLng);
|
||||
} else {
|
||||
double left = Math.max(minLng, maxLng);
|
||||
double right = Math.min(minLng, maxLng);
|
||||
return isInRange(lng, left, 180) || isInRange(lng, right, -180);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在矩形区域内
|
||||
* @param point 测试点
|
||||
* @param gpsPoints 矩形GPS四个坐标点
|
||||
* @return boolean true:在矩形内, false:在矩形外
|
||||
* @Title: isInArea
|
||||
*/
|
||||
public static boolean isInRectangleArea(Point point, Point[] gpsPoints) {
|
||||
if (gpsPoints.length != 4) {
|
||||
return false;
|
||||
}
|
||||
double[] lats = new double[4];
|
||||
double[] lngs = new double[4];
|
||||
for (int i = 0; i < gpsPoints.length; i++) {
|
||||
lats[i] = gpsPoints[i].getLat();
|
||||
lngs[i] = gpsPoints[i].getLng();
|
||||
}
|
||||
Arrays.sort(lats);
|
||||
Arrays.sort(lngs);
|
||||
return isInRectangleArea(point.getLat(), point.getLng(), lats[0], lats[3], lngs[0], lngs[3]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否在经纬度范围内
|
||||
* @param point
|
||||
* @param left
|
||||
* @param right
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isInRange(double point, double left, double right) {
|
||||
return point >= Math.min(left, right) && point <= Math.max(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断点是否在多边形内
|
||||
* @param point 测试点
|
||||
* @param pts 多边形的点
|
||||
* @return boolean true:在多边形内, false:在多边形外
|
||||
* @throws
|
||||
* @Title: IsPointInPoly
|
||||
*/
|
||||
public static boolean isInPolygon(Point point, List<Point> pts) {
|
||||
|
||||
int N = pts.size();
|
||||
boolean boundOrVertex = true;
|
||||
int intersectCount = 0;//交叉点数量
|
||||
double precision = 2e-10; //浮点类型计算时候与0比较时候的容差
|
||||
Point p1, p2;//临近顶点
|
||||
Point p = point; //当前点
|
||||
|
||||
p1 = pts.get(0);
|
||||
for (int i = 1; i <= N; ++i) {
|
||||
if (p.equals(p1)) {
|
||||
return boundOrVertex;
|
||||
}
|
||||
|
||||
p2 = pts.get(i % N);
|
||||
if (p.getLng() < Math.min(p1.getLng(), p2.getLng()) || p.getLng() > Math.max(p1.getLng(), p2.getLng())) {
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
|
||||
//射线穿过算法
|
||||
if (p.getLng() > Math.min(p1.getLng(), p2.getLng()) && p.getLng() < Math.max(p1.getLng(), p2.getLng())) {
|
||||
if (p.getLat() <= Math.max(p1.getLat(), p2.getLat())) {
|
||||
if (p1.getLng() == p2.getLng() && p.getLat() >= Math.min(p1.getLat(), p2.getLat())) {
|
||||
return boundOrVertex;
|
||||
}
|
||||
|
||||
if (p1.getLat() == p2.getLat()) {
|
||||
if (p1.getLat() == p.getLat()) {
|
||||
return boundOrVertex;
|
||||
} else {
|
||||
++intersectCount;
|
||||
}
|
||||
} else {
|
||||
double xinters = (p.getLng() - p1.getLng()) * (p2.getLat() - p1.getLat()) / (p2.getLng() - p1.getLng()) + p1.getLat();
|
||||
if (Math.abs(p.getLat() - xinters) < precision) {
|
||||
return boundOrVertex;
|
||||
}
|
||||
|
||||
if (p.getLat() < xinters) {
|
||||
++intersectCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (p.getLng() == p2.getLng() && p.getLat() <= p2.getLat()) {
|
||||
Point p3 = pts.get((i + 1) % N);
|
||||
if (p.getLng() >= Math.min(p1.getLng(), p3.getLng()) && p.getLng() <= Math.max(p1.getLng(), p3.getLng())) {
|
||||
++intersectCount;
|
||||
} else {
|
||||
intersectCount += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
p1 = p2;
|
||||
}
|
||||
return intersectCount % 2 != 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ spring:
|
|||
# kafka消费者服务器
|
||||
bootstrap-servers: 10.100.1.8:9092
|
||||
# 分组id
|
||||
group-id: firstGroup
|
||||
group-id: fst001
|
||||
# 指定了消费者读取了一个没有偏移量的分区获取偏移量无效的情况下该如何处理
|
||||
# earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费分区的记录
|
||||
# latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据(在消费者启动之后生成的记录)
|
||||
|
|
Loading…
Reference in New Issue