You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

193 lines
7.5 KiB

package com.inspect.nvr.utils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
/**
* 规范参考 DL/T 664——2016 《带电设备红外诊断应用规范》
*/
@Slf4j
public class DLT664Service {
// 各字段字节长度:数值字段直接取对应 Java 类型的字节数,定长字节数组用命名常量
private static final int SIZE_FILE_VERSION = Short.BYTES;
private static final int SIZE_WIDTH = Short.BYTES;
private static final int SIZE_HEIGHT = Short.BYTES;
private static final int SIZE_DATE_TIME = 14;
private static final int SIZE_EMISS = Float.BYTES;
private static final int SIZE_AMBIENT_TEMPERATURE = Float.BYTES;
private static final int SIZE_LEN = Byte.BYTES;
private static final int SIZE_DISTANCE = Integer.BYTES;
private static final int SIZE_RELATIVE_HUMIDITY = Byte.BYTES;
private static final int SIZE_REFLECTED_TEMPERATURE = Float.BYTES;
private static final int SIZE_STRING_FIELD = 32;
private static final int SIZE_LONGITUDE = Double.BYTES;
private static final int SIZE_LATITUDE = Double.BYTES;
private static final int SIZE_ALTITUDE = Integer.BYTES;
private static final int SIZE_DESCRIPTION_LENGTH = Integer.BYTES;
private static final int SIZE_OFFSET = Integer.BYTES;
private static final int SIZE_FILE_END_TYPE = 16;
// 定长 ASCII 字符串字段的补齐字节
private static final byte STRING_PAD = (byte) ' ';
public static byte[] generate(byte[] imageBytes, float[][] temperatureMatrix, String productor) {
Info info = new Info();
info.setWidth((short) temperatureMatrix[0].length);
info.setHeight((short) temperatureMatrix.length);
info.setIrData(temperatureMatrix);
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
info.setDateTime(toAsciiBytes(dateTime));
info.setProductor(toAsciiBytes(productor));
return generate(imageBytes, info);
}
public static byte[] generate(byte[] imageBytes, Info info) {
float[][] irData = info.getIrData();
int width = info.getWidth();
int height = info.getHeight();
int descriptionLength = info.getDescriptionLength();
// 红外数据块大小,表达式顺序与下方写入顺序一一对应
int dataSize = SIZE_FILE_VERSION + SIZE_WIDTH + SIZE_HEIGHT
+ SIZE_DATE_TIME
+ width * height * Float.BYTES
+ SIZE_EMISS + SIZE_AMBIENT_TEMPERATURE + SIZE_LEN + SIZE_DISTANCE
+ SIZE_RELATIVE_HUMIDITY + SIZE_REFLECTED_TEMPERATURE
+ SIZE_STRING_FIELD * 3
+ SIZE_LONGITUDE + SIZE_LATITUDE + SIZE_ALTITUDE
+ SIZE_DESCRIPTION_LENGTH + descriptionLength;
ByteBuffer dataBuf = ByteBuffer.allocate(dataSize).order(ByteOrder.LITTLE_ENDIAN);
// 文件版本、矩阵宽高
dataBuf.putShort(info.getFileVersion());
dataBuf.putShort((short) width);
dataBuf.putShort((short) height);
// 拍摄时间
putFixedBytes(dataBuf, info.getDateTime(), SIZE_DATE_TIME, STRING_PAD);
// 温度矩阵
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
dataBuf.putFloat(irData[row][col]);
}
}
// 环境参数
dataBuf.putFloat(info.getEmiss());
dataBuf.putFloat(info.getAmbientTemperature());
dataBuf.put(info.getLen());
dataBuf.putInt(info.getDistance());
dataBuf.put(info.getRelativeHumidity());
dataBuf.putFloat(info.getReflectedTemperature());
// 定长字符串
putFixedBytes(dataBuf, info.getProductor(), SIZE_STRING_FIELD, STRING_PAD);
putFixedBytes(dataBuf, info.getType(), SIZE_STRING_FIELD, STRING_PAD);
putFixedBytes(dataBuf, info.getSerialNo(), SIZE_STRING_FIELD, STRING_PAD);
// 经纬度、海拔、备注
dataBuf.putDouble(info.getLongitude());
dataBuf.putDouble(info.getLatitude());
dataBuf.putInt(info.getAltitude());
dataBuf.putInt(descriptionLength);
if (descriptionLength > 0) {
putFixedBytes(dataBuf, info.getDescriptionData(), descriptionLength, (byte) 0);
}
if (dataBuf.hasRemaining()) {
throw new IllegalStateException("dataSize 与实际写入字节数不一致,剩余 " + dataBuf.remaining() + " 字节");
}
byte[] dataBlock = dataBuf.array();
// 拼接: 图片 + 红外数据块 + 偏移量(4) + 文件末尾标识(16)
int offset = imageBytes.length;
byte[] result = new byte[offset + dataBlock.length + SIZE_OFFSET + SIZE_FILE_END_TYPE];
System.arraycopy(imageBytes, 0, result, 0, offset);
System.arraycopy(dataBlock, 0, result, offset, dataBlock.length);
ByteBuffer.wrap(result, offset + dataBlock.length, SIZE_OFFSET)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(offset);
System.arraycopy(info.getFileEndType(), 0, result, offset + dataBlock.length + SIZE_OFFSET, SIZE_FILE_END_TYPE);
log.info("DLT664文件组装成功");
return result;
}
/**
* 写入定长字节:不足补 fill,超出截断。
*/
private static void putFixedBytes(ByteBuffer buf, byte[] src, int len, byte fill) {
byte[] bytes = new byte[len];
Arrays.fill(bytes, fill);
if (src != null) {
System.arraycopy(src, 0, bytes, 0, Math.min(src.length, len));
}
buf.put(bytes);
}
private static byte[] toAsciiBytes(String s) {
return s != null ? s.getBytes(StandardCharsets.US_ASCII) : null;
}
@Data
public static class Info {
// 文件版本
private short fileVersion = (short) 0x0100;
// 矩阵宽度
private short width;
// 矩阵高度
private short height;
// 拍摄时间 (14 bytes ASCII)
private byte[] dateTime;
// 整个温度矩阵
private float[][] irData;
// 辐射率
private float emiss;
// 环境温度
private float ambientTemperature;
// 镜头度数
private byte len;
// 拍摄距离 (4 bytes)
private int distance;
// 相对湿度
private byte relativeHumidity;
// 反射温度
private float reflectedTemperature;
// 生产厂家 (32 bytes)
private byte[] productor;
// 产品型号 (32 bytes)
private byte[] type;
// 产品序列号 (32 bytes)
private byte[] serialNo;
// 经度
private double longitude = 0;
// 纬度
private double latitude = 0;
// 海拔 (4 bytes)
private int altitude = 0;
// 备注信息长度, 0 表示没有存储信息
private int descriptionLength = 0;
// 备注信息 (descriptionLength bytes)
private byte[] descriptionData;
// 红外数据的起始偏移地址
private int irDataOffset;
// 文件末尾标识 (16 bytes)
private byte[] fileEndType = {
(byte) 0x37, (byte) 0x66, (byte) 0x07, (byte) 0x1a,
(byte) 0x12, (byte) 0x3a, (byte) 0x4c, (byte) 0x9f,
(byte) 0xa9, (byte) 0x5d, (byte) 0x21, (byte) 0xd2,
(byte) 0xda, (byte) 0x7d, (byte) 0x26, (byte) 0xbc
};
}
}