Java项目实战记录:雷达数据解析
背景介绍
最近公司接了一个雷达相关的系统开发项目,雷达数据会由C++程序进行二次处理存放到指定文件夹中,我这边使用Java程序去文件夹下读取这些雷达产品,进行解析并将数据传递到前台,由前台使用OL在地图上进行渲染展示。下面就介绍我是如何解析雷达文件的(别的雷达数据结构可能不一样哈,只供参考)。
数据分析
首先我们看看雷达数据的结构,我这边处理的雷达数据是由C++以二进制格式写入并进行GZIP压缩产生的,文件后缀为dat,如图所示:
如果直接用编辑器打开文件那就是一堆乱码。
想要解析代码需要进行以下操作:
- 读取文件时进行gzip解压
- 知道文件中数据的定义,然后读取字节进行字节码的转换
- 数据进行保存或进行其它分析操作
代码实现
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的字节序是可配置的。
大端小端概念
大端小端其实是我们通俗意义上的叫法,实际上指的是计算机存储字节的顺序模式,根据数据在内存中的存储方式分为两种大端字节序模式和小端字节序模式。
大端字节序模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。符合我们的阅读习惯。
小端字节序模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。这个比较符合逻辑思维习惯,高对高,低对低。
那么,到底什么是大端,什么是小端? 如下图(图片来源于网络):
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 方式来存储数据。
解决问题
- 针对第一个问题:根据和雷达数据的开发者的沟通,确定了他使用的32位编辑器,因此基本类型字节不一样长的问题就好解决了,可以用相同长度的基本类型进行替换,如:
将C++中定义为char类型的数据,在Java中使用byte存储,这样位长就一样了,上面给出的实体类已经替换了,可以参考上面的内容。
- 针对第二个问题:多字节的基本数据对象需要将小端序转大端序,再进行解析。下面我给出几个例子(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进行解压,解析后的内容如下所示:
完整的解析代码
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;
}
}