Java项目实战记录:雷达数据解析

Java项目实战记录:雷达数据解析

背景介绍

最近公司接了一个雷达相关的系统开发项目,雷达数据会由C++程序进行二次处理存放到指定文件夹中,我这边使用Java程序去文件夹下读取这些雷达产品,进行解析并将数据传递到前台,由前台使用OL在地图上进行渲染展示。下面就介绍我是如何解析雷达文件的(别的雷达数据结构可能不一样哈,只供参考)。

数据分析

首先我们看看雷达数据的结构,我这边处理的雷达数据是由C++以二进制格式写入并进行GZIP压缩产生的,文件后缀为dat,如图所示:

image-20240314084508842

如果直接用编辑器打开文件那就是一堆乱码。

想要解析代码需要进行以下操作:

  1. 读取文件时进行gzip解压
  2. 知道文件中数据的定义,然后读取字节进行字节码的转换
  3. 数据进行保存或进行其它分析操作

代码实现

C++雷达数据定义

跟处理雷达数据的开发者沟通后,得到了雷达数据的数据定义,如下所示:

1.雷达头为一个PolarDataBuffer
2.数据部分为LonLatData,对应数量根据文件大小来计算(剔除了无效值)

#ifndef PRODUCTDEF_H_
#define PRODUCTDEF_H_


#pragma pack(1)
//POLAR PRODUCT
typedef	struct
{
    char Country[30];                //国家名,文本格式输入
    char Province[20];               //省名,文本格式输入
    char Station[40];                //站名,文本格式输入
    char StationNumber[10];          //区站号,文本格式输入
    char RadarType[20];              //雷达型号,文本格式输入
    char Longitude[16];              //天线所在经纬度,文本格式输入
    char Latitude[16];               //天线所在纬度,文本格式输入
    long LongitudeValue;             //天线所在经度的数值,以1/1000度为计数单位
    //东经(E)为正,西经(W)为负
    long LatitudeValue;              //天线所在纬度的数值,以1/1000度为计数单位
    //北纬(N)为正,南纬(S)为负
    long Height;                     //天线海拔高度,以毫米为计数单位
    short MaxAngle;                  //测站周围地物最大遮挡仰角,以1/100度为计数单位
    short OptiAngle;                 //测站的最佳观测仰角(地物回波强度<10dBZ),
    //以1/100度为计数单位
}RADARSITE;

typedef struct
{
    unsigned char NStep;             //体扫层次
    unsigned char LayerNumber;       //体扫描层数
    unsigned short SYear;            //观测记录开始时间的年(2000-)
    unsigned char SMonth;            //观测记录开始时间的月(1-12)
    unsigned char SDay;              //观测记录开始时间的日(1-31)
    unsigned char SHour;             //观测记录开始时间的时(00-23)
    unsigned char SMinute;           //观测记录开始时间的分(00-59)
    unsigned char SSecond;           //观测记录开始时间的秒(00-59)
    unsigned long SMillisecond;      //秒的小数位(计数单位微秒)
    unsigned short MaxV;             //本层的最大可测速度,计数单位为厘米/秒
    unsigned short MaxL;             //本层的最大可测距离,以10米为计数单位
    unsigned short ZBinWidth;        //本层强度数据的库长,以1/10米为计数单位
    unsigned short VBinWidth;        //本层速度数据的库长,以1/10米为计数单位
    unsigned short WBinWidth;        //本层谱宽数据的库长,以1/10米为计数单位
    unsigned short ZBinNumber;       //本层扫描强度径向的库数
    unsigned short VBinNumber;       //本层扫描速度径向的库数
    unsigned short WBinNumber;       //本层扫描谱宽径向的库数
    unsigned short RecordNumber;     //本层扫描径向个数
    short SwpAngles;                 //本层的仰角,计数单位为1/100度,当扫描方式为RHI。不填此数组,当扫描方式为PPI时,
    //第一个元素为做PPI时的仰角,计数单位为1/100度,其它元素填写-32768
    unsigned short EYear;            //观测记录结束时间的年(2000-)
    unsigned char EMonth;            //观测记录结束时间的月(1-12)
    unsigned char EDay;              //观测记录结束时间的日(1-31)
    unsigned char EHour;             //观测记录结束时间的时(00-23)
    unsigned char EMinute;           //观测记录结束时间的分(00-59)
    unsigned char ESecond;           //观测记录结束时间的秒(00-59)
    unsigned char ETenth;            //观测记录结束时间的1/100秒(00-99)
    unsigned short   PRF1;           //本层第一重复频率 ,计数单位:1/10 Hz
    unsigned short   PRF2;           //本层第二重复频率,计数单位:1/10 Hz
    char Spare[255];                 //备用字节255个
}RADAROBSERVATIONPARAMPOLAR;

typedef struct
{
    unsigned short ProductionID;       //产品名称  Luo
    //1=CAPPI
    //.......
    char ProductName[100];			//产品名称
    char ProductUnit[20];			//数值单位
    char ColorName[20];			//色标名称
    short Vector;
    short Line;
    short Circle;
    short Text;
    short Triangle;
    short DoubleArc;
    short bAdjustColorTable;		//0不需要调整 1需要调整
    short bUnChar; 					//0是char;1是Unsigned Char;2是Short
    short BlankZero;  				//产品中空值的表示
    float addV;    	  		//产品中保存的为归一化值,变为真值后应该时=(V+addV)*mulV
    float mulV;       			//产品中保存的为归一化值,变为真值后应该时=(V+addV)*mulV
    unsigned short BinNumber;	    //产品的库数
    unsigned short BinWidth;		//产品的库长
    float MaxValue;					//实际测到正的最大数值;
    float MinValue;					//实际测到负的最小数值;
    float Height;					//当Height==-1时则表示产品信息中不显示仰角和高度
    //当Height<360时则表示产品信息中显示仰角, 单位为度
    //当Height>360时则表示产品信息中显示高度,单位为Height-360 米;
    char WaringType[20];            //警报类型
    //HI TVS STORM RAINSTORM GUSTFRONT DOWNBURST
    char Spare[360];                //备用字节380个
RADAROTHERINFORMATIONPOLAR;

//PRODUCT
typedef struct{
    char FileID[4];                 //雷达数据标识(原始数据标识符,‘RD’为雷达原
    //始数据,‘GD‘为雷达衍生数据等等)
    float VersionNo;                //表示数据格式的版本号
    long FileHeaderLength;          //表示文件头的长度
    RADARSITE SiteInfo;             //站址基本情况
    RADAROBSERVATIONPARAMPOLAR ObservationInfo; //观测参数
    RADAROTHERINFORMATIONPOLAR OtherInfo;       //其他信息参数
}PolarProduct;

    
typedef struct
{
    float Logitude;		// 经度
    float Latitude;		// 纬度
    short Value;		// 雷达回波强度=Value/100;
}LonLatData;

#endif /*PRODUCTDEF_H_*/

可以看到雷达数据的头部结构如下:

typedef struct{
    char FileID[4];                 //雷达数据标识(原始数据标识符,‘RD’为雷达原
    //始数据,‘GD‘为雷达衍生数据等等)
    float VersionNo;                //表示数据格式的版本号
    long FileHeaderLength;          //表示文件头的长度
    RADARSITE SiteInfo;             //站址基本情况
    RADAROBSERVATIONPARAMPOLAR ObservationInfo; //观测参数
    RADAROTHERINFORMATIONPOLAR OtherInfo;       //其他信息参数
}PolarProduct;

雷达数据部分结构如下:

typedef struct
{
    float Logitude;		// 经度
    float Latitude;		// 纬度
    short Value;		// 雷达回波强度=Value/100;
}LonLatData;

Java雷达数据定义

根据上面的C++类定义,实现对应的Java实体类定义,以下数据使用了lombok简化了代码量

雷达极面产品信息
import lombok.Data;

/**
 * 雷达极面产品信息
 */
@Data
public class PolarProduct {
    public String fileID; // 雷达数据标识(原始数据标识符,‘RD’为雷达原//始数据,‘GD‘为雷达衍生数据等等)
    public float versionNo; // 数据格式的版本号
    public long fileHeaderLength; // 文件头的长度
    public RADARSITE siteInfo; // 站址基本情况
    public RADAROBSERVATIONPARAMPOLAR observationInfo; // 观测参数
    public RADAROTHERINFORMATIONPOLAR otherInfo; // 其他信息参数
}
雷达站信息
import lombok.Data;

/**
 * 雷达站信息
 */
@Data
public class RADARSITE {
    public String Country; // 国家名
    public String Province; // 省名
    public String Station; // 站名
    public String StationNumber; // 区站号
    public String RadarType; // 雷达型号
    public String Longitude; // 经度
    public String Latitude; // 纬度
    public int LongitudeValue; // 经度数值
    public int LatitudeValue; // 纬度数值
    public int Height; // 高度
    public short MaxAngle; // 最大角度
    public short OptiAngle; // 最优角度
}
雷达监测参数
import lombok.Data;

/**
 * 雷达监测信息
 */
@Data
public class RADAROBSERVATIONPARAMPOLAR {
    private byte NStep;             // 体扫层次
    private byte LayerNumber;       // 体扫描层数
    private short SYear;            // 观测记录开始时间的年(2000-)
    private byte SMonth;            // 观测记录开始时间的月(1-12)
    private byte SDay;              // 观测记录开始时间的日(1-31)
    private byte SHour;             // 观测记录开始时间的时(00-23)
    private byte SMinute;           // 观测记录开始时间的分(00-59)
    private byte SSecond;           // 观测记录开始时间的秒(00-59)
    private int SMillisecond;       // 秒的小数位(计数单位微秒)
    private short MaxV;                // 本层的最大可测速度,计数单位为厘米/秒
    private short MaxL;                // 本层的最大可测距离,以10米为计数单位
    private short ZBinWidth;           // 本层强度数据的库长,以1/10米为计数单位
    private short VBinWidth;           // 本层速度数据的库长,以1/10米为计数单位
    private short WBinWidth;           // 本层谱宽数据的库长,以1/10米为计数单位
    private short ZBinNumber;          // 本层扫描强度径向的库数
    private short VBinNumber;          // 本层扫描速度径向的库数
    private short WBinNumber;          // 本层扫描谱宽径向的库数
    private short RecordNumber;        // 本层扫描径向个数
    private short SwpAngles;         // 本层的仰角,计数单位为1/100度,当扫描方式为RHI。不填此数组,当扫描方式为PPI时,第一个元素为做PPI时的仰角,计数单位为1/100度,其它元素填写-32768
    private short EYear;               // 观测记录结束时间的年(2000-)
    private byte EMonth;            // 观测记录结束时间的月(1-12)
    private byte EDay;              // 观测记录结束时间的日(1-31)
    private byte EHour;             // 观测记录结束时间的时(00-23)
    private byte EMinute;           // 观测记录结束时间的分(00-59)
    private byte ESecond;           // 观测记录结束时间的秒(00-59)
    private byte ETenth;            // 观测记录结束时间的1/100秒(00-99)
    private short PRF1;                // 本层第一重复频率 ,计数单位:1/10 Hz
    private short PRF2;                // 本层第二重复频率,计数单位:1/10 Hz
    private String Spare;            // 备用字节255个

}
雷达其他信息
import lombok.Data;

/**
 * 雷达信息极
 */
@Data
public class RADAROTHERINFORMATIONPOLAR {

    private short productionID;       // 产品名称
    private String productName;     // 产品名称
    private String productUnit;     // 数值单位
    private String colorName;       // 色标名称
    private short vector;
    private short line;
    private short circle;
    private short text;
    private short triangle;
    private short doubleArc;
    private short bAdjustColorTable; // 0不需要调整 1需要调整
    private short bUnChar;           // 0是char;1是Unsigned Char;2是Short
    private short blankZero;         // 产品中空值的表示
    private float addV;              //产品中保存的为归一化值,变为真值后应该时=(V+addV)*mulV
    private float mulV;              // 产品中保存的为归一化值,变为真值后应该时=(V+addV)*mulV
    private short binNumber;           // 产品的库数
    private short binWidth;            // 产品的库长
    private float maxValue;          // 实际测到正的最大数值
    private float minValue;          // 实际测到负的最小数值
    private float height;            // 当Height==-1时则表示产品信息中不显示仰角和高度;当Height<360时则表示产品信息中显示仰角, 单位为度;当Height>360时则表示产品信息中显示高度,单位为Height-360 米;
    private String warningType;      // 警报类型:HI TVS STORM RAINSTORM GUSTFRONT DOWNBURST
    private String spare;            // 备用字节380个
}

以上都是雷达的头部信息

雷达点数据定义
import lombok.Data;

/**
 * 雷达点数据信息
 */
@Data
public class LonLatData {
    private float lon; // 经度
    private float lat;  // 纬度
    private double val;     // 值(等于value/100)
}

出现的问题

基本类型分配的字节不一样

Java和C++基本数据类型变量分配的字节是不一样的,并且不同的操作系统平台,给 C/C++ 基本数据类型变量分配的字节是不一样的。

32位编译器:

char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   4个字节
long long:  8个字节
unsigned long:  4个字节

64位编译器:

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   8个字节
long long:  8个字节
unsigned long:  8个字节

由于 Java 是跨平台语言,所以 JVM 表现下的基础数据字节长度其实都是一致的。

int:4 个字节。
short:2 个字节。
long:8 个字节。
byte:1 个字节。
float:4 个字节。
double:8 个字节。
char:2 个字节。
boolean:boolean属于布尔类型,在存储的时候不使用字节,仅仅使用 1 位来存储,范围仅仅为0和1,其字面量为true和false。
字节序大小端问题
字节

字节(Byte)是存储数据的基本单位,并且是硬件所能访问的最小单位。

常见的存储单位主要有bit(位)、B(字节)、KB(千字节)、MB(兆字节)、GB(千兆字节)。它们之间主要有如下换算关系:

1B=8bit
1KB=1024B
1MB=1024KB
1GB=1024MB

其中 B 是 Byte 的缩写。

字节序

字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。

在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如:如果C/C++中的一个int型变量 a 的起始地址是&a = 0x100,那么 a 的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。

根据整数 a 在连续的 4 byte 内存中的存储顺序,字节序被分为大端序(Big Endian) 与 小端序(Little Endian)两类。 然后就牵涉出两大CPU派系:

Motorola 6800,PowerPC 970,SPARC(除V9外)等处理器采用 Big Endian方式存储数据;
x86系列,VAX,PDP-11等处理器采用Little Endian方式存储数据。
ARM, PowerPC (除PowerPC 970外), DEC Alpha, SPARC V9, MIPS, PA-RISC and IA64的字节序是可配置的。

大端小端概念

大端小端其实是我们通俗意义上的叫法,实际上指的是计算机存储字节的顺序模式,根据数据在内存中的存储方式分为两种大端字节序模式和小端字节序模式。

大端字节序模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。符合我们的阅读习惯。

小端字节序模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。这个比较符合逻辑思维习惯,高对高,低对低。

那么,到底什么是大端,什么是小端? 如下图(图片来源于网络):

img

Big Endian 是指低地址端 存放 高位字节。
Little Endian 是指低地址端 存放 低位字节。

为什么要注意字节序

当一个 C/C++ 的程序要与一个 Java 程序交互时:

C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而现在比较普遍的 x86 处理器是 Little Endian
Java编写的程序则唯一采用 Big Endian 方式来存储数据,因此我是无法使用Java直接解析C++生成的雷达数据的。

总结:

C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而现在比较普遍的 x86 处理器是 Little Endian。不同处理器之间采用的字节序可能不同。
网络序一般统一为大端序。
多字节数据对象才需要转字节序,例如int,short等,而char不需要。
Java编写的程序则唯一采用 Big Endian 方式来存储数据。

解决问题

  1. 针对第一个问题:根据和雷达数据的开发者的沟通,确定了他使用的32位编辑器,因此基本类型字节不一样长的问题就好解决了,可以用相同长度的基本类型进行替换,如:

将C++中定义为char类型的数据,在Java中使用byte存储,这样位长就一样了,上面给出的实体类已经替换了,可以参考上面的内容。

  1. 针对第二个问题:多字节的基本数据对象需要将小端序转大端序,再进行解析。下面我给出几个例子(short、int、float):
    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个short类型的数据。
     *
     * @param dis DataInputStream,用于读取short类型数据的输入流
     * @return 读取的short类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static short readShortLE(DataInputStream dis) throws IOException {
        // 创建一个长度为2的字节数组用于存储读取的数据
        byte[] b = new byte[2];
        // 从输入流中读取两个字节的数据并存入字节数组中
        dis.readFully(b);
        // 将两个字节按小端顺序合并成一个short类型的数据,并返回结果
        return (short) ((b[1] << 8) | (b[0] & 0xFF));
    }

    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个int类型的数据。
     *
     * @param dis DataInputStream,用于读取int类型数据的输入流
     * @return 读取的int类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static int readIntLE(DataInputStream dis) throws IOException {
        // 创建一个长度为4的字节数组用于存储读取的数据
        byte[] b = new byte[4];
        // 从输入流中读取四个字节的数据并存入字节数组中
        dis.readFully(b);
        // 将四个字节按小端顺序合并成一个int类型的数据,并返回结果
        return ((b[3] & 0xFF) << 24) | ((b[2] & 0xFF) << 16) |
                ((b[1] & 0xFF) << 8) | (b[0] & 0xFF);
    }

    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个float类型的数据。
     *
     * @param dis DataInputStream,用于读取float类型数据的输入流
     * @return 读取的float类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static float readFloatLE(DataInputStream dis) throws IOException {
        return Float.intBitsToFloat(readIntLE(dis));
    }

解析后的数据

注意:解析的时候还需要使用gzip进行解压,解析后的内容如下所示:

image-20240314111514001

完整的解析代码

import com.google.gson.Gson;
import com.zykj.radar_server.datahandle.pojo.*;
import com.zykj.radar_server.entity.pojo.RadarDataPojo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.zip.GZIPInputStream;

/**
 * 雷达数据处理方法
 */
public class RadarDataParser {
    private static final Logger logger = LoggerFactory.getLogger(RadarDataParser.class);

    public static ArrayList<LonLatData> handlePolarProduct(String filePath) {
        // 存储解析后的数据
        ArrayList<LonLatData> res = new ArrayList<>();
        // 解析文件内容
        try (
                FileInputStream fis = new FileInputStream(filePath);
                BufferedInputStream bis = new BufferedInputStream(fis);
                GZIPInputStream gis = new GZIPInputStream(bis);
                DataInputStream dis = new DataInputStream(gis);
        ) {
            PolarProduct polarProduct = readPolarProduct(dis);
            logger.info("雷达数据标识:" + polarProduct.getFileID());
            logger.info("雷达数据格式版本号:" + polarProduct.getVersionNo());
            logger.info("文件头部数据长度:" + polarProduct.getFileHeaderLength());
            logger.info("雷达站信息:" + polarProduct.getSiteInfo());
            logger.info("雷达监测信息:" + polarProduct.getObservationInfo());
            logger.info("雷达监测其他信息:" + polarProduct.getOtherInfo());
            logger.info("本层扫描径向个数:" + polarProduct.getObservationInfo().getRecordNumber());
            logger.info("产品的库数:" + polarProduct.getOtherInfo().getBinNumber());

            // 经纬度点数据个数(经纬度点数据固定长度为10)


            try {
                while (true) { // 无限循环,依赖异常来终止
                    LonLatData data = readLonLatData(dis);
                    res.add(data);
                }
            } catch (EOFException e) {
                // 到达文件末尾
                logger.warn("文件到达底部");
            } catch (IOException e) {
                e.printStackTrace();
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            logger.info("文件包含的经纬度点信息总数:" + res.size());
            logger.info("解析的第一个雷达点数据:" + res.get(0));
            logger.info("解析的第二个雷达点数据:" + res.get(1));
        }
        return res;
    }

    /**
     * 读取雷达极面产品信息
     *
     * @param dis 文件数据流
     * @return PolarProduct
     * @throws IOException 文件IO异常
     */
    public static PolarProduct readPolarProduct(DataInputStream dis) throws IOException {
        PolarProduct product = new PolarProduct();
        byte[] fileIDBytes = new byte[4];
        dis.readFully(fileIDBytes);
        product.setFileID(new String(fileIDBytes));
        product.setVersionNo(readFloatLE(dis));
        product.setFileHeaderLength(readIntLE(dis));
        product.setSiteInfo(readRADARSITE(dis));
        product.observationInfo = readRADAROBSERVATIONPARAMPOLAR(dis);
        product.otherInfo = readRADAROTHERINFORMATIONPOLAR(dis);
        return product;
    }

    /**
     * 解析雷达站信息
     *
     * @param dis 文件数据流
     * @return RADARSITE
     * @throws IOException 文件IO异常
     */
    private static RADARSITE readRADARSITE(DataInputStream dis) throws IOException {
        RADARSITE site = new RADARSITE();
        site.setCountry(readFixedLengthString(dis, 30));
        site.setProvince(readFixedLengthString(dis, 20));
        site.setStation(readFixedLengthString(dis, 40));
        site.setStationNumber(readFixedLengthString(dis, 10));
        site.setRadarType(readFixedLengthString(dis, 20));
        site.setLongitude(readFixedLengthString(dis, 16));
        site.setLatitude(readFixedLengthString(dis, 16));
        site.setLongitudeValue(readIntLE(dis));
        site.setLatitudeValue(readIntLE(dis));
        site.setHeight(readIntLE(dis));
        site.setMaxAngle(readShortLE(dis));
        site.setOptiAngle(readShortLE(dis));
        return site;
    }

    /**
     * 解析雷达监测信息
     *
     * @param dis 文件数据流
     * @return RADAROBSERVATIONPARAMPOLAR
     * @throws IOException 文件IO异常
     */
    public static RADAROBSERVATIONPARAMPOLAR readRADAROBSERVATIONPARAMPOLAR(DataInputStream dis) throws IOException {
        RADAROBSERVATIONPARAMPOLAR params = new RADAROBSERVATIONPARAMPOLAR();
        params.setNStep(dis.readByte());
        params.setLayerNumber(dis.readByte());
        params.setSYear(readShortLE(dis));
        params.setSMonth(dis.readByte());
        params.setSDay(dis.readByte());
        params.setSHour(dis.readByte());
        params.setSMinute(dis.readByte());
        params.setSSecond(dis.readByte());
        params.setSMillisecond(readIntLE(dis));
        params.setMaxV(readShortLE(dis));
        params.setMaxL(readShortLE(dis));
        params.setZBinWidth(readShortLE(dis));
        params.setVBinWidth(readShortLE(dis));
        params.setWBinWidth(readShortLE(dis));
        params.setZBinNumber(readShortLE(dis));
        params.setVBinNumber(readShortLE(dis));
        params.setWBinNumber(readShortLE(dis));
        params.setRecordNumber(readShortLE(dis));
        params.setSwpAngles(readShortLE(dis));
        params.setEYear(readShortLE(dis));
        params.setEMonth(dis.readByte());
        params.setEDay(dis.readByte());
        params.setEHour(dis.readByte());
        params.setEMinute(dis.readByte());
        params.setESecond(dis.readByte());
        params.setETenth(dis.readByte());
        params.setPRF1(readShortLE(dis));
        params.setPRF2(readShortLE(dis));
        params.setSpare(readFixedLengthString(dis, 255));
        return params;
    }


    /**
     * 解析雷达信息极
     *
     * @param dis 文件数据流
     * @return RADAROTHERINFORMATIONPOLAR
     * @throws IOException 文件IO异常
     */
    public static RADAROTHERINFORMATIONPOLAR readRADAROTHERINFORMATIONPOLAR(DataInputStream dis) throws IOException {
        RADAROTHERINFORMATIONPOLAR info = new RADAROTHERINFORMATIONPOLAR();
        info.setProductionID(readShortLE(dis));
        info.setProductName(readFixedLengthStringForCharset(dis, 100));
        info.setProductUnit(readFixedLengthString(dis, 20));
        info.setColorName(readFixedLengthString(dis, 20));
        info.setVector(dis.readShort());
        info.setLine(dis.readShort());
        info.setCircle(dis.readShort());
        info.setText(dis.readShort());
        info.setTriangle(dis.readShort());
        info.setDoubleArc(dis.readShort());
        info.setBAdjustColorTable(dis.readShort());
        info.setBUnChar(dis.readShort());
        info.setBlankZero(dis.readShort());
        info.setAddV(dis.readFloat());
        info.setMulV(dis.readFloat());
        info.setBinNumber(readShortLE(dis));
        info.setBinWidth(readShortLE(dis));
        info.setMaxValue(dis.readFloat());
        info.setMinValue(dis.readFloat());
        info.setHeight(dis.readFloat());
        info.setWarningType(readFixedLengthString(dis, 20));
        info.setSpare(readFixedLengthString(dis, 360));
        return info;
    }

    /**
     * 从DataInputStream中读取固定长度的字节数组,并将其转换为字符串。
     *
     * @param dis    DataInputStream,用于读取字节数组的输入流
     * @param length 要读取的字节数组的长度
     * @return 读取的字节数组转换后的字符串
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static String readFixedLengthString(DataInputStream dis, int length) throws IOException {
        // 创建一个指定长度的字节数组用于存储读取的数据
        byte[] bytes = new byte[length];
        // 从输入流中读取指定长度的字节数组,直到读取满为止
        dis.readFully(bytes);
        // 使用字节数组构造一个新的字符串对象,并返回结果
        return new String(bytes);
    }

    private static String readFixedLengthStringForCharset(DataInputStream dis, int length) throws IOException {
        // 创建一个指定长度的字节数组用于存储读取的数据
        byte[] bytes = new byte[length];
        // 从输入流中读取指定长度的字节数组,直到读取满为止
        dis.readFully(bytes);
        // 使用字节数组构造一个新的字符串对象,并返回结果
        return new String(bytes, "GBK");
    }

    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个short类型的数据。
     *
     * @param dis DataInputStream,用于读取short类型数据的输入流
     * @return 读取的short类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static short readShortLE(DataInputStream dis) throws IOException {
        // 创建一个长度为2的字节数组用于存储读取的数据
        byte[] b = new byte[2];
        // 从输入流中读取两个字节的数据并存入字节数组中
        dis.readFully(b);
        // 将两个字节按小端顺序合并成一个short类型的数据,并返回结果
        return (short) ((b[1] << 8) | (b[0] & 0xFF));
    }

    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个int类型的数据。
     *
     * @param dis DataInputStream,用于读取int类型数据的输入流
     * @return 读取的int类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static int readIntLE(DataInputStream dis) throws IOException {
        // 创建一个长度为4的字节数组用于存储读取的数据
        byte[] b = new byte[4];
        // 从输入流中读取四个字节的数据并存入字节数组中
        dis.readFully(b);
        // 将四个字节按小端顺序合并成一个int类型的数据,并返回结果
        return ((b[3] & 0xFF) << 24) | ((b[2] & 0xFF) << 16) |
                ((b[1] & 0xFF) << 8) | (b[0] & 0xFF);
    }

    /**
     * 从DataInputStream中按小端(Little Endian)顺序读取一个float类型的数据。
     *
     * @param dis DataInputStream,用于读取float类型数据的输入流
     * @return 读取的float类型数据
     * @throws IOException 如果读取过程中发生I/O错误
     */
    private static float readFloatLE(DataInputStream dis) throws IOException {
        return Float.intBitsToFloat(readIntLE(dis));
    }

    /**
     * 读取经纬度点数据
     *
     * @param dis 数据流
     * @return LonLatData
     * @throws IOException 文件IO异常
     */
    public static LonLatData readLonLatData(DataInputStream dis) throws IOException {
        LonLatData data = new LonLatData();
        data.setLon(readFloatLE(dis)); // 读取经度
        data.setLat(readFloatLE(dis));  // 读取纬度
        data.setVal((readShortLE(dis) / 100.0));     // 读取值
        return data;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/458101.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【吊打面试官系列】Java虚拟机JVM篇 - 关于JVM 新生代、老年代、永久代的区别

大家好&#xff0c;我是锋哥。今天分享关于JVM新生代、老年代、永久代的区别的JVM面试题&#xff0c;希望对大家有帮助&#xff1b; JVM 新生代、老年代、永久代的区别? 在 Java 中&#xff0c;堆被划分成两个不同的区域&#xff1a;新生代 ( Young ) 、老年代 ( Old ) 。而新…

【学习记录】调试千寻服务+DTU+导远RTK过程的记录

最近调试车载定位的时候&#xff0c;遇到了一些问题&#xff0c;千寻服务已经正确配置到RTK里面了&#xff0c;但是导远的定位设备一直显示RTK浮动解&#xff0c;通过千寻服务后台查看状态&#xff0c;长时间显示不合法的GGA值。 首先&#xff0c;通过四处查资料&#xff0c;千…

ThingsBoard 开源物联网平台

文章目录 1.ThingsBoard 介绍2.ThingsBoard 架构2.1.单体架构2.2.微服务架构 3.物联网网关4.边缘计算 ThingsBoard # ThingsBoardhttps://iothub.org.cn/docs/iot/ https://iothub.org.cn/docs/iot/thingsboard-ce/1.ThingsBoard 介绍 ThingsBoard 是一个开源物联网平台&…

【Claude 3】关于注册Claude 3模型的操作演示

文章目录 1. 登录Claude URL2. 海外手机号码验证3. 获取手机验证码4. 输入Claude用户名称5. 同意确认使用协议6. 点击去开始体验7. 注册登录成功8. 重新登录进入Claude9. 参考链接PS&#xff1a;所遇问题&#xff1a;⚠️注册即封号&#xff01;&#xff01;&#xff01; 1. 登…

代码随想录刷题笔记 Day 51 | 单词拆分 No.139 | 多重背包理论基础

文章目录 Day 5101. 单词拆分&#xff08;No. 139&#xff09;<1> 题目<2> 笔记<3> 代码 02. 多重背包理论基础2.1 解题思路2.2 携带矿石资源&#xff08;卡码网No.56&#xff09;<1> 题目<2> 笔记<3> 代码 Day 51 01. 单词拆分&#xff…

Python·算法·每日一题(3月15日)合并两个有序链表

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&am…

如何正确地设置Outlook SMTP发送电子邮件?

Outlook SMTP发送邮件配置方法&#xff1f;Outlook怎么开启SMTP&#xff1f; 在使用Outlook发送邮件时&#xff0c;正确设置SMTP服务器是确保邮件能够顺利发送的关键步骤。接下来&#xff0c;就让AokSend一起探讨如何正确地设置Outlook SMTP发送电子邮件吧&#xff01; Outlo…

【Redis】Redis常用命令之Hash

1.hset&#xff1a;设置hash中指定的字段&#xff08;field&#xff09;的值&#xff08;value&#xff09;。 HSET key field value [field value ...]时间复杂度&#xff1a;插⼊⼀组field为O(1),插⼊N组field为O(N)。 返回值&#xff1a;添加的字段的个数。 2.hget&#xf…

vscode 导入前端项目

vscode 导入前端项目 导入安装依赖 运行 参考vscode 下载 导入 安装依赖 运行 在前端项目的终端中输入npm run serve

【洛谷 P8637】[蓝桥杯 2016 省 B] 交换瓶子 题解(贪心算法)

[蓝桥杯 2016 省 B] 交换瓶子 题目描述 有 N N N 个瓶子&#xff0c;编号 1 ∼ N 1 \sim N 1∼N&#xff0c;放在架子上。 比如有 5 5 5 个瓶子&#xff1a; 2 , 1 , 3 , 5 , 4 2,1,3,5,4 2,1,3,5,4 要求每次拿起 2 2 2 个瓶子&#xff0c;交换它们的位置。 经过若干次…

Springboot的配置文件及其优先级

配置文件 内置配置文件 配置文件的作用&#xff1a;修改SpringBoot自动配置的默认值&#xff1b;SpringBoot在底层都给我们自动配置好&#xff1b;SpringBoot使用一个全局的配置文件&#xff0c;配置文件名是固定的&#xff1a; application.propertiesapplication.yml 以上…

【无标题】vmprotect net 混淆效果挺不错

vmprotect net 混淆效果挺不错,测试了一个&#xff0c;以前的写程序。用dnspy测试一下&#xff0c;效果非常好。 sunnf0451qq.com

string接口[小白理解篇]

作文目的 本文是为了加深对string底层函数的一点理解(请勿与底层源码混为一谈)&#xff0c;下面从模拟与注意项出发。 一.string 功能化模拟 1.迭代器模拟 迭代器&#xff0c;为实现简单便理解故使用指针的方式(非说明迭代器使用该方法实现)。其中的begin、end都是为了给迭代…

【论文笔记合集】ARIMA 非平稳过程通过差分转化为平稳过程

本文作者&#xff1a; slience_me 文章目录 ARIMA 非平稳过程通过差分转化为平稳过程文章原文具体解释详解参照 ARIMA 非平稳过程通过差分转化为平稳过程 文章原文 Many time series forecasting methods start from the classic tools [38, 10]. ARIMA [7, 6] tackles the fo…

爬虫入门到精通_框架篇16(Scrapy框架基本使用)_名人名言的抓取

1 目标站点分析 抓取网站&#xff1a;http://quotes.toscrape.com/ 主要显示了一些名人名言&#xff0c;以及作者、标签等等信息&#xff1a; 点击next&#xff0c;page变为2&#xff1a; 2 流程框架 抓取第一页&#xff1a;请求第一页的URL并得到源代码&#xff0c;进行下…

避免阻塞主线程 —— Web Worker 示例项目

前期回顾 迄今为止易用 —— 的 “盲水印“ 实现方案-CSDN博客https://blog.csdn.net/m0_57904695/article/details/136720192?spm1001.2014.3001.5501 目录 CSDN 彩色之外 &#x1f4dd; 前言 &#x1f6a9; 技术栈 &#x1f6e0;️ 功能 &#x1f916; 如何运行 ♻️ …

Linux 部署 Samba 服务

一、Ubuntu 部署 Samba 1、安装 Samba # 更新本地软件包列表 sudo apt update# 安装Samba sudo apt install samba# 查看版本 smbd --version2、创建共享文件夹&#xff0c;并配置 Samba 创建需要共享的文件夹&#xff0c;并赋予权限&#xff1a; sudo mkdir /home/test sud…

深度学习PyTorch 之 LSTM-中文多分类

LSTM 代码流程与RNN代码基本一致&#xff0c;只是这里做了几点优化 1、数据准备 数据从导入到分词&#xff0c;流程是一致的 # 加载数据 file_path ./data/news.csv data pd.read_csv(file_path)# 显示数据的前几行 data.head()# 划分数据集 X_train, X_test, y_train, y_…

【UE5】非持枪趴姿移动混合空间

项目资源文末百度网盘自取 创建角色在非持枪状态趴姿移动的动画混合空间 在BlendSpace文件夹中单击右键选择 动画(Animation) 中的混合空间(Blend Space) 选择SK_Female_Skeleton 命名为BS_NormaProne 打开BS_NormaProne 水平轴表示角色的方向&#xff0c;命名为Directi…

Vue2 父子组件某一属性的双向绑定

原本&#xff1a;父组件使用props传值给孩子组件初始化&#xff0c;触发事件子组件使用$emit传值给父组件&#xff0c;很麻烦后来&#xff1a;使用computed和$event例子代码&#xff1a; <template><div class"box">grandpa <el-input v-model"…