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
|
|
};
|
|
}
|
|
}
|