From da789f25beac7eb93c78577b1f2b697085acfd57 Mon Sep 17 00:00:00 2001 From: wyq Date: Sat, 1 Jun 2024 13:30:43 +0800 Subject: [PATCH] support SA/SB, SC, PA, CC radar data files --- .../chart/graphic/GraphicFactory.java | 20 +- .../org/meteoinfo/common/util/GlobalUtil.java | 2 +- .../data/meteodata/MeteoDataInfo.java | 3 - .../meteodata/radar/BaseRadarDataInfo.java | 29 +- .../data/meteodata/radar/CCRadarDataInfo.java | 521 ++++++++++----- .../meteodata/radar/CMARadarBaseDataInfo.java | 496 -------------- .../data/meteodata/radar/GenericHeader.java | 17 + .../data/meteodata/radar/PARadarDataInfo.java | 612 ++++++++++++++++++ .../data/meteodata/radar/RadarDataUtil.java | 76 ++- .../data/meteodata/radar/RadialRecord.java | 38 +- .../meteodata/radar/SABRadarDataInfo.java | 39 +- .../data/meteodata/radar/SCRadarDataInfo.java | 22 +- .../radar/StandardRadarDataInfo.java | 256 ++++++++ meteoinfo-lab/milconfig.xml | 16 +- pom.xml | 2 +- 15 files changed, 1388 insertions(+), 761 deletions(-) delete mode 100644 meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CMARadarBaseDataInfo.java create mode 100644 meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/PARadarDataInfo.java create mode 100644 meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/StandardRadarDataInfo.java diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java index 3236b187..9517cb0c 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java @@ -221,10 +221,10 @@ public class GraphicFactory { PolylineShape pls; List points; double x, y; - IndexIterator xIter = xdata.getIndexIterator(); - IndexIterator yIter = ydata.getIndexIterator(); ColorBreak cb; if (xdata.getRank() == 1) { + IndexIterator xIter = xdata.getIndexIterator(); + IndexIterator yIter = ydata.getIndexIterator(); points = new ArrayList<>(); int i = 0; while (xIter.hasNext()) { @@ -265,14 +265,16 @@ public class GraphicFactory { int[] shape = xdata.getShape(); int yn = shape[0]; int xn = shape[1]; - for (int j = 0; j < yn; j++) { + Index2D xIndex = (Index2D) xdata.getIndex(); + Index2D yIndex = (Index2D) ydata.getIndex(); + for (int i = 0; i < xn; i++) { points = new ArrayList<>(); - cb = cbs.get(j); - for (int i = 0; i < xn; i++) { - x = xIter.getDoubleNext(); - y = yIter.getDoubleNext(); - //x = xdata.getDouble(j * xn + i); - //y = ydata.getDouble(j * xn + i); + cb = cbs.get(i); + for (int j = 0; j < yn; j++) { + xIndex.set(j, i); + yIndex.set(j, i); + x = xdata.getDouble(xIndex); + y = ydata.getDouble(yIndex); if (Double.isNaN(y) || Double.isNaN(x)) { if (points.isEmpty()) { continue; diff --git a/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java b/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java index d67b1323..fd2cb449 100644 --- a/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java +++ b/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java @@ -67,7 +67,7 @@ import java.util.zip.ZipInputStream; public static String getVersion(){ String version = GlobalUtil.class.getPackage().getImplementationVersion(); if (version == null || version.equals("")) { - version = "3.8.9"; + version = "3.8.10"; } return version; } diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/MeteoDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/MeteoDataInfo.java index 05bad8b6..b31db319 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/MeteoDataInfo.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/MeteoDataInfo.java @@ -39,9 +39,6 @@ import java.time.LocalDateTime; import java.time.Duration; import java.util.ArrayList; -import org.meteoinfo.data.meteodata.numpy.NumpyDataInfo; -import org.meteoinfo.data.meteodata.radar.CMARadarBaseDataInfo; -import org.meteoinfo.data.meteodata.radar.RadarDataType; import org.meteoinfo.data.meteodata.radar.RadarDataUtil; import org.meteoinfo.ndarray.math.ArrayMath; import org.meteoinfo.projection.ProjectionInfo; diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/BaseRadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/BaseRadarDataInfo.java index 39e80485..1f49e1f8 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/BaseRadarDataInfo.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/BaseRadarDataInfo.java @@ -8,6 +8,8 @@ import org.meteoinfo.ndarray.*; import org.meteoinfo.ndarray.math.ArrayMath; import org.meteoinfo.ndarray.math.ArrayUtil; +import java.io.IOException; +import java.io.InputStream; import java.io.RandomAccessFile; import java.util.*; import java.util.stream.Collectors; @@ -27,8 +29,8 @@ public abstract class BaseRadarDataInfo extends DataInfo { protected Dimension radialDim, scanDim, gateRDim, gateVDim; protected float antennaHeight = 0; protected float beamWidthVert = 1.f; - protected int logResolution = 1000; - protected int dopplerResolution = 1000; + protected float logResolution = 1000; + protected float dopplerResolution = 1000; /** * Get record map @@ -56,6 +58,23 @@ public abstract class BaseRadarDataInfo extends DataInfo { return velocityGroup.contains(product); } + @Override + public void readDataInfo(String fileName) { + this.fileName = fileName; + try { + InputStream inputStream = RadarDataUtil.getInputStream(fileName); + readDataInfo(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Read data info + * @param is The InputStream + */ + abstract void readDataInfo(InputStream is); + protected void makeRefVariables(RadialRecord refRadialRecord) { Dimension[] dimensions = new Dimension[]{scanDim, radialDim, gateRDim}; for (RadialRecord radialRecord : this.recordMap.values()) { @@ -293,6 +312,12 @@ public abstract class BaseRadarDataInfo extends DataInfo { } } + Attribute mvAttr = variable.findAttribute("missing_value"); + if (mvAttr != null) { + Number missingValue = mvAttr.getNumericValue(); + ArrayMath.missingToNaN(dataArray, missingValue); + } + Attribute aoAttr = variable.findAttribute("add_offset"); Attribute sfAttr = variable.findAttribute("scale_factor"); if (aoAttr != null || sfAttr != null) { diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CCRadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CCRadarDataInfo.java index cf051104..db175544 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CCRadarDataInfo.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CCRadarDataInfo.java @@ -13,123 +13,118 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class CCRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo { + private List cutConfigs; + private int radarHeaderSize = 1024; + private int perRadialSize = 3000; + private int messageHeaderSize = 28; + @Override public boolean isValidFile(java.io.RandomAccessFile raf) { return false; } - void setScaleOffset(RadialRecord record, int vResolution) { + void setScaleOffset(RadialRecord record) { switch (record.product) { case "dBZ": - record.scale = 0.5f; - record.offset = -33.f; - break; case "V": - if (vResolution == 2) { - record.scale = 0.5f; - record.offset = -64.5f; - } else { - record.scale = 1.f; - record.offset = -129.f; - } - break; case "W": - record.scale = 0.5f; - record.offset = -64.5f; + record.scale = 0.1f; + record.offset = 0.f; break; } } @Override - public void readDataInfo(String fileName) { - this.fileName = fileName; - if (fileName.endsWith(".bz2")) { - try { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - try { - BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - void readDataInfo(InputStream is) { try { - int index = 0; - byte[] rhBytes = new byte[SABRadarDataInfo.RadialHeader.length]; - while (is.read(rhBytes) != -1) { - SABRadarDataInfo.RadialHeader radialHeader = new SABRadarDataInfo.RadialHeader(rhBytes); - if (index == 0) { - this.logResolution = radialHeader.gateSizeOfReflectivity; - this.dopplerResolution = radialHeader.gateSizeOfDoppler; + byte[] headerBytes = new byte[this.radarHeaderSize]; + is.read(headerBytes); + byte[] bytes = Arrays.copyOf(headerBytes, RadarHeader.length); + RadarHeader radarHeader = new RadarHeader(bytes); + + int sweepN = radarHeader.getSweepNumber(); + cutConfigs = new ArrayList<>(); + int idx = RadarHeader.length; + for (int i = 0; i < sweepN; i++) { + bytes = Arrays.copyOfRange(headerBytes, idx, idx + CutConfig.length); + idx += CutConfig.length; + CutConfig cutConfig = new CutConfig(bytes); + cutConfigs.add(cutConfig); + if (i == 0) { + this.logResolution = cutConfig.usBindWidth; + this.dopplerResolution = cutConfig.usBindWidth; } - if (!radialHeader.hasReflectivityData()) { - is.read(new byte[460]); - } - for (String product : radialHeader.getProducts()) { - RadialRecord record; - if (this.recordMap.containsKey(product)) { - record = this.recordMap.get(product); - } else { - record = new RadialRecord(product); - record.setBinLength(1); - setScaleOffset(record, radialHeader.resolutionOfVelocity); - this.recordMap.put(product, record); - } - if (radialHeader.radialNumber == 1) { - record.fixedElevation.add(radialHeader.getElevation()); - record.elevation.add(new ArrayList<>()); - record.azimuth.add(new ArrayList<>()); - record.azimuthMinIndex.add(0); - if (isVelocityGroup(record)) { - record.disResolution.add(radialHeader.gateSizeOfDoppler); - record.distance.add(ArrayUtil.arrayRange1(radialHeader.rangeToFirstGateOfDop, - radialHeader.gatesNumberOfDoppler, radialHeader.gateSizeOfDoppler)); - } else { - record.disResolution.add(radialHeader.gateSizeOfReflectivity); - record.distance.add(ArrayUtil.arrayRange1(radialHeader.rangeToFirstGateOfRef, - radialHeader.gatesNumberOfReflectivity, radialHeader.gateSizeOfReflectivity)); - } - record.newScanData(); - } - record.elevation.get(record.elevation.size() - 1).add(radialHeader.getElevation()); - record.addAzimuth(radialHeader.getAzimuth()); - int dataLength = isVelocityGroup(record) ? radialHeader.gatesNumberOfDoppler : radialHeader.gatesNumberOfReflectivity; - byte[] bytes = new byte[dataLength]; + } + + idx = 878; + bytes = Arrays.copyOfRange(headerBytes, idx, idx + RadarHeader2.length); + RadarHeader2 radarHeader2 = new RadarHeader2(bytes); + + List products = new ArrayList<>(Arrays.asList("dBZ", "V", "W")); + for (String product : products) { + RadialRecord record = new RadialRecord(product); + record.setRadarDataType(RadarDataType.CC); + record.setBinLength(2); + record.setDataType(DataType.SHORT); + setScaleOffset(record); + this.recordMap.put(product, record); + } + + for (int i = 0; i < sweepN; i++) { + CutConfig cutConfig = cutConfigs.get(i); + int radialN = cutConfig.usBinNumber; + float azimuth = 0; + float aDelta = 360.f / cutConfig.usRecordNumber; + for (int j = 0; j < cutConfig.usRecordNumber; j++) { + bytes = new byte[this.perRadialSize]; is.read(bytes); - record.addDataBytes(bytes); - if (isVelocityGroup(record)) { - if (dataLength < 920) { - is.read(new byte[920 - dataLength]); + idx = 0; + for (String product : products) { + RadialRecord record = this.recordMap.get(product); + if (j == 0) { + record.fixedElevation.add(cutConfig.getAngle()); + record.elevation.add(new ArrayList<>()); + record.azimuth.add(new ArrayList<>()); + record.azimuthMinIndex.add(0); + record.disResolution.add((float) cutConfig.usBindWidth); + record.distance.add(ArrayUtil.arrayRange1(300, + radialN, cutConfig.usBindWidth)); + record.newScanData(); } - } else { - if (dataLength < 460) { - is.read(new byte[460 - dataLength]); + record.elevation.get(record.elevation.size() - 1).add(cutConfig.getAngle()); + record.addAzimuth(azimuth); + byte[] data = Arrays.copyOfRange(bytes, idx, idx + 2 * radialN); + idx += 2 * radialN; + Array dataArray = Array.factory(record.getDataType(), new int[]{radialN}); + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + for (int k = 0; k < radialN; k++) { + dataArray.setShort(k, byteBuffer.getShort()); } + record.addDataArray(dataArray); } + azimuth += aDelta; } - if (!radialHeader.hasDopplerData()) { - is.read(new byte[920 + 920]); - } - is.read(new byte[4]); - index += 1; } is.close(); + this.addAttribute(new Attribute("Country", radarHeader.cCountry)); + this.addAttribute(new Attribute("Province", radarHeader.cProvince)); + this.addAttribute(new Attribute("StationName", radarHeader.cStation)); + this.addAttribute(new Attribute("StationCode", radarHeader.cStationNumber)); + this.addAttribute(new Attribute("StationLongitude", radarHeader.getLongitude())); + this.addAttribute(new Attribute("StationLatitude", radarHeader.getLatitude())); + this.addAttribute(new Attribute("AntennaHeight", radarHeader.getHeight())); this.addAttribute(new Attribute("featureType", "RADIAL")); this.addAttribute(new Attribute("DataType", "Radial")); + this.addAttribute(new Attribute("RadarDataType", "CC")); //Add dimensions and variables RadialRecord refRadialRecord = this.recordMap.get("dBZ"); @@ -165,111 +160,287 @@ public class CCRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo return RadarDataType.CC; } - static class RadialHeader { - public static int length = 128; - public int mSecond; // collection time for this radial, msecs since midnight - public short julianDate; // prob "collection time" - public short uRange; // unambiguous range - public int azimuth; // azimuth angle - public short radialNumber; // radial number within the elevation - public short radialStatus; - public short elevation; - public short elNumber; // elevation number - public int rangeToFirstGateOfRef; // range to first gate of reflectivity (m) may be negative - public int rangeToFirstGateOfDop; // range to first gate of doppler (m) may be negative - public int gateSizeOfReflectivity; // reflectivity data gate size (m) - public int gateSizeOfDoppler; // doppler data gate size (m) - public int gatesNumberOfReflectivity; // number of reflectivity gates - public int gatesNumberOfDoppler; // number of velocity or spectrum width gates - public short cutSectorNumber; - public int calibrationConst; - public short ptrOfReflectivity; - public short ptrOfVelocity; - public short ptrOfSpectrumWidth; - public int resolutionOfVelocity; - public short vcpNumber; - public short nyquist; + /** + * Radar header inner class + */ + static class RadarHeader { + public static int length = 218; + public String cFileType; //16 bytes CINRADC + public String cCountry; //30 bytes, country name + public String cProvince; //20 bytes, province name + public String cStation; //40 bytes, station name + public String cStationNumber; //10 bytes, station ID + public String cRadarType; //20 bytes, radar type + public String cLongitude; //16 bytes, longitude string + public String cLatitude; //16 bytes, latitude string + public int lLongitudeValue; //longitude + public int lLatitudeValue; //latitude + public int lHeight; //height + public short sMaxAngle; + public short sOptAngle; + public short ucSYear1; + public short ucSYear2; + public short ucSMonth; + public short ucSDay; + public short ucSHour; + public short ucSMinute; + public short ucSSecond; + public short ucTimeFrom; + public short ucEYear1; + public short ucEYear2; + public short ucEMonth; + public short ucEDay; + public short ucEHour; + public short ucEMinute; + public short ucESecond; + public short ucScanMode; + public int ulSmilliSecond; + public short usRHIA; + public short sRHIL; + public short sRHIH; + public int usEchoType; + public int usProdCode; + public short ucCalibration; + public byte[] remain1; //3 bytes /** * Constructor - * @param is InputStream + * @param bytes The byte array + * @throws IOException */ - public RadialHeader(byte[] inBytes) throws IOException { - ByteBuffer byteBuffer = ByteBuffer.wrap(inBytes); + public RadarHeader(byte[] bytes) throws IOException { + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.position(28); - mSecond = byteBuffer.getInt(); - julianDate = byteBuffer.getShort(); - uRange = byteBuffer.getShort(); - azimuth = DataType.unsignedShortToInt(byteBuffer.getShort()); - radialNumber = byteBuffer.getShort(); - radialStatus = byteBuffer.getShort(); - elevation = byteBuffer.getShort(); - elNumber = byteBuffer.getShort(); - rangeToFirstGateOfRef = byteBuffer.getShort(); - rangeToFirstGateOfDop = byteBuffer.getShort(); - gateSizeOfReflectivity = byteBuffer.getShort(); - gateSizeOfDoppler = byteBuffer.getShort(); - gatesNumberOfReflectivity = byteBuffer.getShort(); - gatesNumberOfDoppler = byteBuffer.getShort(); - cutSectorNumber = byteBuffer.getShort(); - calibrationConst = byteBuffer.getShort(); - ptrOfReflectivity = byteBuffer.getShort(); - ptrOfVelocity = byteBuffer.getShort(); - ptrOfSpectrumWidth = byteBuffer.getShort(); - resolutionOfVelocity = byteBuffer.getShort(); - vcpNumber = byteBuffer.getShort(); - byteBuffer.position(byteBuffer.position() + 14); - nyquist = byteBuffer.getShort(); - } - - /** - * Has reflectivity data or not - * @return Has reflectivity data - */ - public boolean hasReflectivityData() { - return gatesNumberOfReflectivity > 0; - } - - /** - * Has doppler data or not - * @return Has doppler data - */ - public boolean hasDopplerData() { - return gatesNumberOfDoppler > 0; - } - - /** - * Get product names - * @return Product names - */ - public List getProducts() { - List products = new ArrayList<>(); - if (hasReflectivityData()) { - products.add("dBZ"); - } - if (hasDopplerData()) { - products.add("V"); - products.add("W"); + bytes = new byte[16]; + byteBuffer.get(bytes); + cFileType = new String(bytes); + bytes = new byte[30]; + byteBuffer.get(bytes); + cCountry = new String(bytes, "GB2312"); + bytes = new byte[20]; + byteBuffer.get(bytes); + cProvince = new String(bytes, "GB2312"); + bytes = new byte[40]; + byteBuffer.get(bytes); + cStation = new String(bytes, "GB2312"); + bytes = new byte[10]; + byteBuffer.get(bytes); + cStationNumber = new String(bytes, "GB2312"); + bytes = new byte[20]; + byteBuffer.get(bytes); + cRadarType = new String(bytes, "GB2312"); + bytes = new byte[16]; + byteBuffer.get(bytes); + cLongitude = new String(bytes, "GB2312"); + bytes = new byte[16]; + byteBuffer.get(bytes); + cLatitude = new String(bytes, "GB2312"); + lLongitudeValue = byteBuffer.getInt(); + lLatitudeValue = byteBuffer.getInt(); + lHeight = byteBuffer.getInt(); + sMaxAngle = byteBuffer.getShort(); + sOptAngle = byteBuffer.getShort(); + ucSYear1 = DataType.unsignedByteToShort(byteBuffer.get()); + ucSYear2 = DataType.unsignedByteToShort(byteBuffer.get()); + ucSMonth = DataType.unsignedByteToShort(byteBuffer.get()); + ucSDay = DataType.unsignedByteToShort(byteBuffer.get()); + ucSHour = DataType.unsignedByteToShort(byteBuffer.get()); + ucSMinute = DataType.unsignedByteToShort(byteBuffer.get()); + ucSSecond = DataType.unsignedByteToShort(byteBuffer.get()); + ucTimeFrom = DataType.unsignedByteToShort(byteBuffer.get()); + ucEYear1 = DataType.unsignedByteToShort(byteBuffer.get()); + ucEYear2 = DataType.unsignedByteToShort(byteBuffer.get()); + ucEMonth = DataType.unsignedByteToShort(byteBuffer.get()); + ucEDay = DataType.unsignedByteToShort(byteBuffer.get()); + ucEHour = DataType.unsignedByteToShort(byteBuffer.get()); + ucEMinute = DataType.unsignedByteToShort(byteBuffer.get()); + ucESecond = DataType.unsignedByteToShort(byteBuffer.get()); + ucScanMode = DataType.unsignedByteToShort(byteBuffer.get()); + if (ucScanMode < 100 && ucScanMode != 10) { + throw new IOException("Error reading CINRAD CC data: Unsupported product: RHI/FFT"); } - return products; + ulSmilliSecond = byteBuffer.getInt(); + usRHIA = byteBuffer.getShort(); + sRHIL = byteBuffer.getShort(); + sRHIH = byteBuffer.getShort(); + usEchoType = DataType.unsignedShortToInt(byteBuffer.getShort()); + if (usEchoType != 0x408a) // only support vppi at this moment + throw new IOException("Error reading CINRAD CC data: Unsupported level 2 data"); + + usProdCode = DataType.unsignedShortToInt(byteBuffer.getShort()); + if (usProdCode != 0x8003) // only support vppi at this moment + throw new IOException("Error reading CINRAD CC data: Unsupported product: RHI/FFT"); + + ucCalibration = DataType.unsignedByteToShort(byteBuffer.get()); } /** - * Get azimuth - * @return Azimuth + * Get longitude + * @return Longitude */ - public float getAzimuth() { - return azimuth / 8.f * 180.f / 4096.f; + public float getLongitude() { + return lLongitudeValue / 3600000.f; } /** - * Get elevation - * @return Elevation + * Get latitude + * @return Latitude */ - public float getElevation() { - return elevation / 8.f * 180.f / 4096.f; + public float getLatitude() { + return lLatitudeValue / 3600000.f; + } + + /** + * Get height + * @return Height + */ + public float getHeight() { + return lHeight / 1000.f; + } + + /** + * Get start time + * @return Start time + */ + public LocalDateTime getStartTime() { + int sYear = ucSYear1 * 100 + ucSYear2; + return LocalDateTime.of(sYear, ucSMonth, ucSDay, ucSHour, ucSMinute, ucSSecond); + } + + /** + * Get end time + * @return end time + */ + public LocalDateTime getEndTime() { + int eYear = ucEYear1 * 100 + ucEYear2; + return LocalDateTime.of(eYear, ucEMonth, ucEDay, ucEHour, ucEMinute, ucESecond); + } + + /** + * Get sweep number + * @return Sweep number + */ + public int getSweepNumber() { + int sweepN = 0; + if (ucScanMode == 10) { + sweepN = 1; + } else if (ucScanMode >= 100) { + sweepN = ucScanMode - 100; + } + + return sweepN; + } + } + + /** + * Cut configure inner class + */ + static class CutConfig { + public static int length = 22; + public int usMaxV; + public int usMaxL; + public int usBindWidth; + public int usBinNumber; + public int usRecordNumber; + public int usArotate; + public int usPrf1; + public int usPrf2; + public int usSpulseW; + public short usAngle; + public short cSweepStatus; + public short cAmbiguousp; + + /** + * Constructor + * @param bytes The byte array + */ + public CutConfig(byte[] bytes) throws IOException { + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + usMaxV = DataType.unsignedShortToInt(byteBuffer.getShort()); + usMaxL = DataType.unsignedShortToInt(byteBuffer.getShort()); + usBindWidth = DataType.unsignedShortToInt(byteBuffer.getShort()); + usBinNumber = DataType.unsignedShortToInt(byteBuffer.getShort()); + usRecordNumber = DataType.unsignedShortToInt(byteBuffer.getShort()); + usArotate = DataType.unsignedShortToInt(byteBuffer.getShort()); + usPrf1 = DataType.unsignedShortToInt(byteBuffer.getShort()); + usPrf2 = DataType.unsignedShortToInt(byteBuffer.getShort()); + usSpulseW = DataType.unsignedShortToInt(byteBuffer.getShort()); + usAngle = byteBuffer.getShort(); + cSweepStatus = DataType.unsignedByteToShort(byteBuffer.get()); + cAmbiguousp = DataType.unsignedByteToShort(byteBuffer.get()); + } + + /** + * Get angle + * @return Angle + */ + public float getAngle() { + return usAngle / 100.f; + } + } + + /** + * Radar header 2 inner class + */ + static class RadarHeader2 { + public static int length = 146; + public byte[] remain2; //2 bytes + public int lAntennaG; + public int lPower; + public int lWavelength; + + public int usBeamH; + public int usBeamL; + public int usPolarization; + public int usLogA; + public int usLineA; + public int usAGCP; + public int usFreqMode; + public int usFreqRepeat; + public int usPPPPulse; + public int usFFTPoint; + public int usProcessType; + + public short ucClutterT; + public short cSidelobe; + public short ucVelocityT; + public short ucFilderP; + public short ucNoiseT; + public short ucSQIT; + public short ucIntensityC; + public short ucIntensityR; + public short ucCalNoise; + public short ucCalPower; + public short ucCalPulseWidth; + public short ucCalWorkFreq; + public short ucCalLog; + + public byte[] remain3; //92 bytes + public int liDataOffset; + public byte[] remain4; //1 byte + + /** + * Constructor + * @param bytes The byte array + */ + public RadarHeader2(byte[] bytes) throws IOException { + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.position(2); + lAntennaG = byteBuffer.getInt(); + lPower = byteBuffer.getInt(); + lWavelength = byteBuffer.getInt(); + + usBeamH = DataType.unsignedShortToInt(byteBuffer.getShort()); + usBeamL = DataType.unsignedShortToInt(byteBuffer.getShort()); + usPolarization = DataType.unsignedShortToInt(byteBuffer.getShort()); + usLogA = DataType.unsignedShortToInt(byteBuffer.getShort()); + usLineA = DataType.unsignedShortToInt(byteBuffer.getShort()); + usAGCP = DataType.unsignedShortToInt(byteBuffer.getShort()); + usFreqMode = DataType.unsignedShortToInt(byteBuffer.getShort()); + usFreqRepeat = DataType.unsignedShortToInt(byteBuffer.getShort()); } } } diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CMARadarBaseDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CMARadarBaseDataInfo.java deleted file mode 100644 index cd70294f..00000000 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/CMARadarBaseDataInfo.java +++ /dev/null @@ -1,496 +0,0 @@ -package org.meteoinfo.data.meteodata.radar; - -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.meteoinfo.common.DataConvert; -import org.meteoinfo.data.GridArray; -import org.meteoinfo.data.GridData; -import org.meteoinfo.data.dimarray.Dimension; -import org.meteoinfo.data.dimarray.DimensionType; -import org.meteoinfo.data.meteodata.*; -import org.meteoinfo.ndarray.*; -import org.meteoinfo.ndarray.math.ArrayMath; -import org.meteoinfo.ndarray.math.ArrayUtil; - -import java.awt.image.ImagingOpException; -import java.io.*; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class CMARadarBaseDataInfo extends BaseRadarDataInfo implements IRadarDataInfo { - - private GenericHeader genericHeader; - private SiteConfig siteConfig; - private TaskConfig taskConfig; - private List cutConfigs; - private List radialHeaders; - - /** - * Constructor - */ - public CMARadarBaseDataInfo() { - this.meteoDataType = MeteoDataType.RADAR; - } - - /** - * Get radar data type - * @return Radar data type - */ - @Override - public RadarDataType getRadarDataType() { - return RadarDataType.STANDARD; - } - - /** - * Get generic header - * @return Generic header - */ - public GenericHeader getGenericHeader() { - return this.genericHeader; - } - - /** - * Get site config - * @return Site config - */ - public SiteConfig getSiteConfig() { - return this.siteConfig; - } - - /** - * Get task config - * @return Task config - */ - public TaskConfig getTaskConfig() { - return this.taskConfig; - } - - /** - * Get cut config list - * @return Cut config list - */ - public List getCutConfigs() { - return this.cutConfigs; - } - - /** - * Get radial header list - * @return Radial header list - */ - public List getRadialHeaders() { - return this.radialHeaders; - } - - @Override - public boolean isValidFile(RandomAccessFile raf) { - try { - raf.seek(0); - byte[] bytes = new byte[4]; - raf.read(bytes); - int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); - if (magic == 1297371986) { - return true; - } else { - return false; - } - } catch (IOException e) { - return false; - } - } - - /** - * Check the data file format - * @param fileName Data file name - * @return Boolean - */ - public static boolean canOpen(String fileName) { - try { - byte[] bytes = new byte[4]; - if (fileName.endsWith("bz2")) { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - inputStream.read(bytes); - inputStream.close(); - } else { - RandomAccessFile raf = new RandomAccessFile(fileName, "r"); - raf.seek(0); - raf.read(bytes); - raf.close(); - } - int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); - if (magic == 1297371986) { - return true; - } else { - return false; - } - } catch (IOException e) { - return false; - } - } - - @Override - public void readDataInfo(String fileName) { - this.fileName = fileName; - if (fileName.endsWith(".bz2")) { - try { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - try { - //RandomAccessFile raf = new RandomAccessFile(fileName, "r"); - //readDataInfo(raf); - BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - void readDataInfo(InputStream raf) { - try { - genericHeader = new GenericHeader(raf); - siteConfig = new SiteConfig(raf); - this.antennaHeight = siteConfig.antennaHeight; - this.beamWidthVert = siteConfig.beamWidthVert; - - //Add global attributes - this.addAttribute(new Attribute("StationCode", siteConfig.siteCode)); - this.addAttribute(new Attribute("StationName", siteConfig.siteName)); - this.addAttribute(new Attribute("StationLatitude", siteConfig.latitude)); - this.addAttribute(new Attribute("StationLongitude", siteConfig.longitude)); - this.addAttribute(new Attribute("AntennaHeight", siteConfig.antennaHeight)); - this.addAttribute(new Attribute("GroundHeight", siteConfig.groundHeight)); - this.addAttribute(new Attribute("RadarType", siteConfig.getRadarType())); - this.addAttribute(new Attribute("featureType", "RADIAL")); - this.addAttribute(new Attribute("DataType", "Radial")); - - //Read task configuration - taskConfig = new TaskConfig(raf); - this.addAttribute(new Attribute("TaskName", taskConfig.taskName)); - this.addAttribute(new Attribute("TaskDescription", taskConfig.taskDescription)); - - //Read radial data - cutConfigs = new ArrayList<>(); - CutConfig cutConfig; - for (int i = 0; i < taskConfig.cutNumber; i++) { - cutConfig = new CutConfig(raf); - cutConfigs.add(cutConfig); - if (i == 0) { - this.logResolution = cutConfig.logResolution; - this.dopplerResolution = cutConfig.dopplerResolution; - } - } - radialHeaders = new ArrayList<>(); - byte[] rhBytes = new byte[RadialHeader.length]; - while (raf.read(rhBytes) != -1) { - RadialHeader radialHeader = new RadialHeader(rhBytes); - for (int i = 0; i < radialHeader.momentNumber; i++) { - MomentHeader momentHeader = new MomentHeader(raf); - String product = this.productMap.get(momentHeader.dataType); - RadialRecord record; - if (this.recordMap.containsKey(product)) { - record = this.recordMap.get(product); - } else { - record = new RadialRecord(product); - record.setBinLength(momentHeader.binLength); - record.scale = 1.f / momentHeader.scale; - record.offset = -momentHeader.offset / (float) momentHeader.scale; - this.recordMap.put(product, record); - } - if (radialHeader.radialNumber == 1) { - record.fixedElevation.add(cutConfigs.get(radialHeader.elevationNumber - 1).elevation); - record.elevation.add(new ArrayList<>()); - record.azimuth.add(new ArrayList<>()); - record.azimuthMinIndex.add(0); - if (isVelocityGroup(record)) { - record.disResolution.add(cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution); - record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, - cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution)); - } else { - record.disResolution.add(cutConfigs.get(radialHeader.elevationNumber - 1).logResolution); - record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, - cutConfigs.get(radialHeader.elevationNumber - 1).logResolution)); - } - record.newScanData(); - } - record.elevation.get(record.elevation.size() - 1).add(radialHeader.elevation); - record.addAzimuth(radialHeader.azimuth); - byte[] bytes = new byte[momentHeader.dataLength]; - raf.read(bytes); - record.addDataBytes(bytes); - } - radialHeaders.add(radialHeader); - } - raf.close(); - - //Add dimensions and variables - RadialRecord refRadialRecord = this.recordMap.get("dBZ"); - radialDim = new Dimension(); - radialDim.setName("radial"); - radialDim.setLength(refRadialRecord.getMaxRadials()); - this.addDimension(radialDim); - scanDim = new Dimension(); - scanDim.setName("scan"); - scanDim.setLength(refRadialRecord.getScanNumber()); - this.addDimension(scanDim); - gateRDim = new Dimension(); - gateRDim.setName("gateR"); - gateRDim.setLength(refRadialRecord.getGateNumber(0)); - this.addDimension(gateRDim); - makeRefVariables(refRadialRecord); - - RadialRecord velRadialRecord = this.recordMap.get("V"); - gateVDim = new Dimension(); - gateVDim.setName("gateV"); - gateVDim.setLength(velRadialRecord.getGateNumber(0)); - this.addDimension(gateVDim); - makeVelVariables(velRadialRecord); - - /*Dimension xyzDim = new Dimension(DimensionType.OTHER); - xyzDim.setShortName("xyz"); - xyzDim.setDimValue(Array.factory(DataType.INT, new int[]{3}, new int[]{1,2,3})); - this.addDimension(xyzDim); - for (String product : this.recordMap.keySet()) { - this.recordMap.get(product).makeVariables(this, xyzDim); - }*/ - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Get scan elevations - * @return Scan elevations - */ - public List getElevations() { - List elevations = new ArrayList<>(); - for (CutConfig cutConfig : this.cutConfigs) { - if (!elevations.contains(cutConfig.elevation)) - elevations.add(cutConfig.elevation); - } - - return elevations; - } - - @Override - public List getGlobalAttributes() { - return this.attributes; - } - - /** - * Read grid ppi data - * @param varName Variable name - * @param scanIdx Scan index - * @param xa X coordinates array - * @param ya Y coordinates array - * @param h Radar height - * @return Grid ppi data - */ - public Array readGridData(String varName, int scanIdx, Array xa, Array ya, Float h) { - RadialRecord record = this.recordMap.get(varName); - if (h == null) { - h = (float) siteConfig.antennaHeight; - } - Array[] rr = Transform.cartesianToAntennaElevation(xa, ya, record.fixedElevation.get(scanIdx), h); - Array azimuth = rr[0]; - Array ranges = rr[1]; - Array data = Array.factory(DataType.FLOAT, xa.getShape()); - IndexIterator iterA = azimuth.getIndexIterator(); - IndexIterator iterR = ranges.getIndexIterator(); - IndexIterator iterData = data.getIndexIterator(); - float v; - while (iterData.hasNext()) { - v = record.interpolateValue(scanIdx, iterA.getFloatNext(), iterR.getFloatNext()); - iterData.setFloatNext(v); - } - - return data; - } - - /** - * Read CR data - * @param varName Variable name - * @param xa X coordinates array - 2D - * @param ya Y coordinates array - 2D - * @param h Radar height - * @return CR data - */ - public Array getCRData(String varName, Array xa, Array ya, Float h) { - RadialRecord record = this.recordMap.get(varName); - int nScan = record.getScanNumber(); - if (h == null) { - h = (float) siteConfig.antennaHeight; - } - - int[] shape = xa.getShape(); - int ny = shape[0]; - int nx = shape[1]; - Array data = Array.factory(DataType.FLOAT, shape); - Index2D index2D = (Index2D) data.getIndex(); - float v; - for (int s = 0; s < nScan; s++) { - Array[] rr = Transform.cartesianToAntennaElevation(xa, ya, record.fixedElevation.get(s), h); - Array azimuth = rr[0]; - Array ranges = rr[1]; - IndexIterator iterA = azimuth.getIndexIterator(); - IndexIterator iterR = ranges.getIndexIterator(); - if (s == 0) { - for (int i = 0; i < ny; i++) { - for (int j = 0; j < nx; j++) { - v = record.interpolateValue(s, iterA.getFloatNext(), iterR.getFloatNext()); - data.setFloat(index2D.set(i, j), v); - } - } - } else { - float v1; - for (int i = 0; i < ny; i++) { - for (int j = 0; j < nx; j++) { - v = record.interpolateValue(s, iterA.getFloatNext(), iterR.getFloatNext()); - index2D.set(i, j); - v1 = data.getFloat(index2D); - if (Float.isNaN(v1) || (v > v1)) - data.setFloat(index2D, v); - } - } - } - } - - return data; - } - - /** - * Read CAPPI data - * @param varName Variable name - * @param xa X coordinates array - * @param ya Y coordinates array - * @param z Z coordinates value - * @param h Radar height - * @return Grid ppi data - */ - public Array getCAPPIData(String varName, Array xa, Array ya, float z, Float h) { - RadialRecord record = this.recordMap.get(varName); - if (h == null) { - h = (float) siteConfig.antennaHeight; - } - Array[] rr = Transform.cartesianToAntenna(xa, ya, z, h); - Array azimuth = rr[0]; - Array ranges = rr[1]; - Array elevation = rr[2]; - Array data = Array.factory(DataType.FLOAT, xa.getShape()); - IndexIterator iterA = azimuth.getIndexIterator(); - IndexIterator iterR = ranges.getIndexIterator(); - IndexIterator iterE = elevation.getIndexIterator(); - IndexIterator iterData = data.getIndexIterator(); - float v; - float halfBeamWidth = this.siteConfig.beamWidthVert / 2; - while (iterData.hasNext()) { - v = record.interpolateValue(iterE.getFloatNext(), iterA.getFloatNext(), - iterR.getFloatNext(), halfBeamWidth); - iterData.setFloatNext(v); - } - - return data; - } - - /** - * Read grid 3d data - * @param varName Variable name - * @param xa X coordinates array - * @param ya Y coordinates array - * @param z Z coordinates array - * @param h Radar height - * @return Grid ppi data - */ - public Array getGrid3DData(String varName, Array xa, Array ya, Array za, Float h) { - RadialRecord record = this.recordMap.get(varName); - if (h == null) { - h = (float) siteConfig.antennaHeight; - } - - int nz = (int) za.getSize(); - int[] shape2D = xa.getShape(); - int[] shape3D = new int[]{nz, shape2D[0], shape2D[1]}; - Array data = Array.factory(DataType.FLOAT, shape3D); - IndexIterator iterData = data.getIndexIterator(); - IndexIterator iterZ = za.getIndexIterator(); - float halfBeamWidth = this.siteConfig.beamWidthVert / 2; - while(iterZ.hasNext()) { - float z = iterZ.getFloatNext(); - Array[] rr = Transform.cartesianToAntenna(xa, ya, z, h); - Array azimuth = rr[0]; - Array ranges = rr[1]; - Array elevation = rr[2]; - IndexIterator iterA = azimuth.getIndexIterator(); - IndexIterator iterR = ranges.getIndexIterator(); - IndexIterator iterE = elevation.getIndexIterator(); - float v; - while (iterA.hasNext()) { - v = record.interpolateValue(iterE.getFloatNext(), iterA.getFloatNext(), - iterR.getFloatNext(), halfBeamWidth); - iterData.setFloatNext(v); - } - } - - return data; - } - - /** - * Get VCS data - * @param varName Variable name - * @param startX Start x, km - * @param startY Start y, km - * @param endX End x, km - * @param endY End y, km - * @return VCS data - */ - public Array[] getVCSData(String varName, float startX, float startY, float endX, float endY) { - RadialRecord record = this.recordMap.get(varName); - int nScan = record.getScanNumber(); - float halfBeamWidth = this.siteConfig.beamWidthVert / 2; - float binRes = this.cutConfigs.get(0).logResolution; - float height = this.siteConfig.antennaHeight; - float startEndDistance = (float) Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); - int nPoints = (int) (startEndDistance * 1000 / binRes + 1); - Array xa = ArrayUtil.lineSpace(startX, endX, nPoints); - Array ya = ArrayUtil.lineSpace(startY, endY, nPoints); - Array aa = Transform.xyToAzimuth(xa, ya); - int[] shape = new int[]{nScan, 2, nPoints}; - Array data = Array.factory(DataType.FLOAT, shape); - Array meshXY = Array.factory(DataType.FLOAT, shape); - Array meshZ = Array.factory(DataType.FLOAT, shape); - Index dataIndex = data.getIndex(); - Index meshXYIndex = meshXY.getIndex(); - Index meshZIndex = meshZ.getIndex(); - float x, y, z1, z2, dis, azi, v, ele; - for (int i = 0; i < nScan; i++) { - ele = record.fixedElevation.get(i); - for (int j = 0; j < nPoints; j++) { - x = xa.getFloat(j); - y = ya.getFloat(j); - dis = (float) Math.sqrt(x * x + y * y); - azi = aa.getFloat(j); - v = record.getValue(i, azi, dis * 1000); - z1 = Transform.toCartesianZ(dis * 1000, (float) Math.toRadians(ele - - halfBeamWidth), height) / 1000.f; - z2 = Transform.toCartesianZ(dis * 1000, (float) Math.toRadians(ele + - halfBeamWidth), height) / 1000.f; - for (int k = 0; k < 2; k++) { - data.setFloat(dataIndex.set(i, k, j), v); - meshXY.setFloat(meshXYIndex.set(i, k, j), dis); - } - meshZ.setFloat(meshZIndex.set(i, 0, j), z1); - meshZ.setFloat(meshZIndex.set(i, 1, j), z2); - } - } - - return new Array[]{data, meshXY, meshZ}; - } -} diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/GenericHeader.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/GenericHeader.java index 51710c0a..e1e3ed82 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/GenericHeader.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/GenericHeader.java @@ -1,11 +1,14 @@ package org.meteoinfo.data.meteodata.radar; import org.meteoinfo.common.DataConvert; +import org.meteoinfo.ndarray.DataType; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; public class GenericHeader { public static int length = 32; @@ -59,4 +62,18 @@ public class GenericHeader { reserved = new byte[16]; in.read(reserved); } + + /** + * Constructor + * @param inBytes The input bytes + */ + public GenericHeader(byte[] inBytes) throws IOException { + ByteBuffer byteBuffer = ByteBuffer.wrap(inBytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + magicNumber = byteBuffer.getInt(); + majorVersion = DataType.unsignedShortToInt(byteBuffer.getShort()); + minorVersion = DataType.unsignedShortToInt(byteBuffer.getShort()); + genericType = byteBuffer.getInt(); + productType = byteBuffer.getInt(); + } } diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/PARadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/PARadarDataInfo.java new file mode 100644 index 00000000..30012dbf --- /dev/null +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/PARadarDataInfo.java @@ -0,0 +1,612 @@ +package org.meteoinfo.data.meteodata.radar; + +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.meteoinfo.common.DataConvert; +import org.meteoinfo.data.dimarray.Dimension; +import org.meteoinfo.data.meteodata.Attribute; +import org.meteoinfo.data.meteodata.MeteoDataType; +import org.meteoinfo.ndarray.*; +import org.meteoinfo.ndarray.math.ArrayUtil; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class PARadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo { + + private GenericHeader genericHeader; + private SiteConfig siteConfig; + private TaskConfig taskConfig; + private List beamConfigs; + private List cutConfigs; + private List radialHeaders; + + /** + * Constructor + */ + public PARadarDataInfo() { + this.meteoDataType = MeteoDataType.RADAR; + } + + /** + * Get radar data type + * @return Radar data type + */ + @Override + public RadarDataType getRadarDataType() { + return RadarDataType.PA; + } + + /** + * Get generic header + * @return Generic header + */ + public GenericHeader getGenericHeader() { + return this.genericHeader; + } + + /** + * Get site config + * @return Site config + */ + public SiteConfig getSiteConfig() { + return this.siteConfig; + } + + /** + * Get task config + * @return Task config + */ + public TaskConfig getTaskConfig() { + return this.taskConfig; + } + + /** + * Get cut config list + * @return Cut config list + */ + public List getCutConfigs() { + return this.cutConfigs; + } + + /** + * Get radial header list + * @return Radial header list + */ + public List getRadialHeaders() { + return this.radialHeaders; + } + + @Override + public boolean isValidFile(RandomAccessFile raf) { + try { + raf.seek(0); + byte[] bytes = new byte[4]; + raf.read(bytes); + int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + if (magic == 1297371986) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } + + /** + * Check the data file format + * @param fileName Data file name + * @return Boolean + */ + public static boolean canOpen(String fileName) { + try { + byte[] bytes = new byte[4]; + if (fileName.endsWith("bz2")) { + BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); + inputStream.read(bytes); + inputStream.close(); + } else { + RandomAccessFile raf = new RandomAccessFile(fileName, "r"); + raf.seek(0); + raf.read(bytes); + raf.close(); + } + int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + if (magic == 1297371986) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } + + @Override + void readDataInfo(InputStream raf) { + try { + genericHeader = new GenericHeader(raf); + siteConfig = new SiteConfig(raf); + this.antennaHeight = siteConfig.antennaHeight; + + //Add global attributes + this.addAttribute(new Attribute("StationCode", siteConfig.siteCode)); + this.addAttribute(new Attribute("StationName", siteConfig.siteName)); + this.addAttribute(new Attribute("StationLatitude", siteConfig.latitude)); + this.addAttribute(new Attribute("StationLongitude", siteConfig.longitude)); + this.addAttribute(new Attribute("AntennaHeight", siteConfig.antennaHeight)); + this.addAttribute(new Attribute("GroundHeight", siteConfig.groundHeight)); + this.addAttribute(new Attribute("RadarType", siteConfig.getRadarType())); + this.addAttribute(new Attribute("featureType", "RADIAL")); + this.addAttribute(new Attribute("DataType", "Radial")); + this.addAttribute(new Attribute("RadarDataType", "PA")); + + //Read task configuration + taskConfig = new TaskConfig(raf); + this.addAttribute(new Attribute("TaskName", taskConfig.taskName)); + this.addAttribute(new Attribute("TaskDescription", taskConfig.taskDescription)); + + //Read scan beam configuration + beamConfigs = new ArrayList<>(); + BeamConfig beamConfig; + for (int i = 0; i < taskConfig.beamNumber; i++) { + beamConfig = new BeamConfig(raf); + beamConfigs.add(beamConfig); + if (i == 0) { + this.beamWidthVert = beamConfig.txBeamWidthV; + } + } + + //Read cut configuration + cutConfigs = new ArrayList<>(); + CutConfig cutConfig; + for (int i = 0; i < taskConfig.cutNumber; i++) { + cutConfig = new CutConfig(raf); + cutConfigs.add(cutConfig); + if (i == 0) { + this.logResolution = cutConfig.logResolution; + this.dopplerResolution = cutConfig.dopplerResolution; + } + } + + //Read radial data + radialHeaders = new ArrayList<>(); + byte[] rhBytes = new byte[RadialHeader.length]; + while (raf.read(rhBytes) != -1) { + RadialHeader radialHeader = new RadialHeader(rhBytes); + for (int i = 0; i < radialHeader.momentNumber; i++) { + MomentHeader momentHeader = new MomentHeader(raf); + String product = this.productMap.get(momentHeader.dataType); + RadialRecord record; + if (this.recordMap.containsKey(product)) { + record = this.recordMap.get(product); + } else { + record = new RadialRecord(product); + record.setBinLength(momentHeader.binLength); + record.scale = 1.f / momentHeader.scale; + record.offset = -momentHeader.offset / (float) momentHeader.scale; + this.recordMap.put(product, record); + } + if (radialHeader.radialNumber == 1) { + record.fixedElevation.add(cutConfigs.get(radialHeader.elevationNumber - 1).elevation); + record.elevation.add(new ArrayList<>()); + record.azimuth.add(new ArrayList<>()); + record.azimuthMinIndex.add(0); + if (isVelocityGroup(record)) { + record.disResolution.add(cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution); + record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, + cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution)); + } else { + record.disResolution.add(cutConfigs.get(radialHeader.elevationNumber - 1).logResolution); + record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, + cutConfigs.get(radialHeader.elevationNumber - 1).logResolution)); + } + record.newScanData(); + } + record.elevation.get(record.elevation.size() - 1).add(radialHeader.elevation); + record.addAzimuth(radialHeader.azimuth); + byte[] bytes = new byte[momentHeader.dataLength]; + raf.read(bytes); + record.addDataBytes(bytes); + } + radialHeaders.add(radialHeader); + } + raf.close(); + + //Add dimensions and variables + RadialRecord refRadialRecord = this.recordMap.get("dBZ"); + radialDim = new Dimension(); + radialDim.setName("radial"); + radialDim.setLength(refRadialRecord.getMaxRadials()); + this.addDimension(radialDim); + scanDim = new Dimension(); + scanDim.setName("scan"); + scanDim.setLength(refRadialRecord.getScanNumber()); + this.addDimension(scanDim); + gateRDim = new Dimension(); + gateRDim.setName("gateR"); + gateRDim.setLength(refRadialRecord.getGateNumber(0)); + this.addDimension(gateRDim); + makeRefVariables(refRadialRecord); + + RadialRecord velRadialRecord = this.recordMap.get("V"); + gateVDim = new Dimension(); + gateVDim.setName("gateV"); + gateVDim.setLength(velRadialRecord.getGateNumber(0)); + this.addDimension(gateVDim); + makeVelVariables(velRadialRecord); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Get scan elevations + * @return Scan elevations + */ + public List getElevations() { + List elevations = new ArrayList<>(); + for (CutConfig cutConfig : this.cutConfigs) { + if (!elevations.contains(cutConfig.elevation)) + elevations.add(cutConfig.elevation); + } + + return elevations; + } + + @Override + public List getGlobalAttributes() { + return this.attributes; + } + + /** + * Site configure inner class + */ + static class SiteConfig { + public static int length = 128; + public String siteCode; + public String siteName; + public float latitude; + public float longitude; + public int antennaHeight; + public int groundHeight; + public float frequency; + public int antennaType; + public int TRNumber; + public int RADVersion; + public short radarType; + public byte[] reserved; + + /** + * Constructor + * @param raf InputStream object + */ + public SiteConfig(InputStream raf) throws IOException { + byte[] bytes = new byte[8]; + raf.read(bytes); + siteCode = new String(bytes).trim(); + bytes = new byte[32]; + raf.read(bytes); + siteName = new String(bytes).trim(); + bytes = new byte[4]; + raf.read(bytes); + latitude = DataConvert.bytes2Float(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + longitude = DataConvert.bytes2Float(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + antennaHeight = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + groundHeight = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + frequency = DataConvert.bytes2Float(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + antennaType = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + TRNumber = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + RADVersion = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + bytes = new byte[2]; + raf.read(bytes); + radarType = DataConvert.bytes2Short(bytes, ByteOrder.LITTLE_ENDIAN); + reserved = new byte[54]; + raf.read(reserved); + } + + /** + * Get radar type string + * @return Radar type string + */ + public String getRadarType() { + switch (radarType) { + case 7: + return "SPAR"; + case 8: + return "SPARD"; + case 43: + return "CPAR"; + case 44: + return "CPARD"; + case 69: + return "XPAR"; + case 70: + return "XPARD"; + default: + return "UNDEFINE"; + } + } + } + + /** + * Task configure inner class + */ + static class TaskConfig { + public static int length = 256; + public String taskName; + public String taskDescription; + public int polarizationType; + public int scanType; + public int beamNumber; + public int cutNumber; + public int rayOrder; + public LocalDateTime scanStartTime; + public byte[] reserves; + + /** + * Constructor + * @param raf InputStream object + */ + public TaskConfig(InputStream raf) throws IOException { + byte[] bytes = new byte[32]; + raf.read(bytes); + taskName = new String(bytes).trim(); + bytes = new byte[128]; + raf.read(bytes); + taskDescription = new String(bytes).trim(); + bytes = new byte[4]; + raf.read(bytes); + polarizationType = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + scanType = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + beamNumber = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + cutNumber = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + raf.read(bytes); + rayOrder = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + bytes = new byte[8]; + raf.read(bytes); + long seconds = DataConvert.bytes2Long(bytes, ByteOrder.LITTLE_ENDIAN); + LocalDateTime dt = LocalDateTime.of(1970, 1, 1, 0, 0); + scanStartTime = dt.plusSeconds(seconds); + reserves = new byte[68]; + raf.read(reserves); + } + } + + /** + * Scan beam configure inner class + */ + static class BeamConfig { + public static int length = 640; + public int beamIndex; + public int beamType; + public int subPulseNumber; + public float txBeamDirection; + public float txBeamWidthH; + public float txBeamWidthV; + public float txBeamGain; + public byte[] reserves1; //100 bytes + + public int subPulseStrategy; + public int subPulseModulation; + public float subPulseFrequency; + public float subPulseBandWidth; + public int subPulseWidth; + public byte[] reserves2; //492 bytes + + /** + * Constructor + * @param inputStream The input steam + */ + public BeamConfig(InputStream inputStream) throws IOException { + byte[] inBytes = new byte[BeamConfig.length]; + inputStream.read(inBytes); + ByteBuffer byteBuffer = ByteBuffer.wrap(inBytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + beamIndex = byteBuffer.getInt(); + beamType = byteBuffer.getInt(); + subPulseNumber = byteBuffer.getInt(); + txBeamDirection = byteBuffer.getFloat(); + txBeamWidthH = byteBuffer.getFloat(); + txBeamWidthV = byteBuffer.getFloat(); + txBeamGain = byteBuffer.getFloat(); + byteBuffer.position(byteBuffer.position() + 100); + + subPulseStrategy = byteBuffer.getInt(); + subPulseModulation = byteBuffer.getInt(); + subPulseFrequency = byteBuffer.getFloat(); + subPulseBandWidth = byteBuffer.getFloat(); + subPulseWidth = byteBuffer.getInt(); + } + } + + /** + * Cut configuration inner class + */ + static class CutConfig { + public static int length = 256; + public short cutIndex; + public short txBeamIndex; + public float elevation; + public float txBeamGain; + public float rxBeamWidthH; + public float rxBeamWidthV; + public float rxBeamGain; + public int processMode; // 1-PPP 2-FFT + public int waveForm; + public float N1_PRF_1; + public float N1_PRF_2; + public float N2_PRF_1; + public float N2_PRF_2; + public int unfoldMode; + public float azimuth; + public float startAngle; + public float endAngle; + public float angleResolution; + public float scanSpeed; + public float logResolution; + public float dopplerResolution; + public int maximumRange; + public int maximumRange2; + public int startRange; + public int sample_1; + public int sample_2; + public int phaseMode; + public float atmosphericLoss; + public float nyquistSpeed; + public long momentsMask; + public long momentsSizeMask; + public int miscFilterMask; + public float SQIThreshold; + public float SIGThreshold; + public float CSRThreshold; + public float LOGThreshold; + public float CPAThreshold; + public float PMIThreshold; + public float DPLOGThreshold; + public byte[] thresholdsReserved; // 4 bytes + public int dBTMask; + public int dBZMask; + public int velocity; + public int spectrumWidthMask; + public int ZDRMask; + public byte[] maskReserved; // 12 bytes + public int scanSync; + public int direction; + public short groundClutterClassifierType; + public short groundClutterFilterType; + public short groundClutterFilterNotchWidth; + public short groundClutterFilterWindow; + public byte[] reserved; // 44 bytes + + /** + * Constructor + * @param inputStream The input stream + */ + public CutConfig(InputStream inputStream) throws IOException { + byte[] bytes = new byte[CutConfig.length]; + inputStream.read(bytes); + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + cutIndex = byteBuffer.getShort(); + txBeamIndex = byteBuffer.getShort(); + elevation = byteBuffer.getFloat(); + txBeamGain = byteBuffer.getFloat(); + rxBeamWidthH = byteBuffer.getFloat(); + rxBeamWidthV = byteBuffer.getFloat(); + rxBeamGain = byteBuffer.getFloat(); + processMode = byteBuffer.getInt(); + waveForm = byteBuffer.getInt(); + N1_PRF_1 = byteBuffer.getFloat(); + N1_PRF_2 = byteBuffer.getFloat(); + N2_PRF_1 = byteBuffer.getFloat(); + N2_PRF_2 = byteBuffer.getFloat(); + unfoldMode = byteBuffer.getInt(); + azimuth = byteBuffer.getFloat(); + startAngle = byteBuffer.getFloat(); + endAngle = byteBuffer.getFloat(); + angleResolution = byteBuffer.getFloat(); + scanSpeed = byteBuffer.getFloat(); + logResolution = byteBuffer.getFloat(); + dopplerResolution = byteBuffer.getFloat(); + maximumRange = byteBuffer.getInt(); + maximumRange2 = byteBuffer.getInt(); + startRange = byteBuffer.getInt(); + sample_1 = byteBuffer.getInt(); + sample_2 = byteBuffer.getInt(); + phaseMode = byteBuffer.getInt(); + atmosphericLoss = byteBuffer.getFloat(); + nyquistSpeed = byteBuffer.getFloat(); + momentsMask = byteBuffer.getLong(); + momentsSizeMask = byteBuffer.getLong(); + miscFilterMask = byteBuffer.getInt(); + SQIThreshold = byteBuffer.getFloat(); + SIGThreshold = byteBuffer.getFloat(); + CSRThreshold = byteBuffer.getFloat(); + LOGThreshold = byteBuffer.getFloat(); + CPAThreshold = byteBuffer.getFloat(); + PMIThreshold = byteBuffer.getFloat(); + DPLOGThreshold = byteBuffer.getFloat(); + byteBuffer.getInt(); + + dBTMask = byteBuffer.getInt(); + dBZMask = byteBuffer.getInt(); + velocity = byteBuffer.getInt(); + spectrumWidthMask = byteBuffer.getInt(); + ZDRMask = byteBuffer.getInt(); + byteBuffer.position(byteBuffer.position() + 12); + + scanSync = byteBuffer.getInt(); + direction = byteBuffer.getInt(); + groundClutterClassifierType = byteBuffer.getShort(); + groundClutterFilterType = byteBuffer.getShort(); + groundClutterFilterNotchWidth = byteBuffer.getShort(); + groundClutterFilterWindow = byteBuffer.getShort(); + } + } + + /** + * Radial header inner class + */ + static class RadialHeader { + public static int length = 128; + public int radialState; + public int spotBlank; + public int sequenceNumber; + public int radialNumber; + public int elevationNumber; + public float azimuth; + public float elevation; + public long seconds; + public int microSeconds; + public int lengthOfData; + public int momentNumber; + public short scanBeamIndex; + public short horizontalEstimatedNoise; + public short verticalEstimatedNoise; + public int PRFFLAG; + public byte[] reserved04; //70 bytes + + /** + * Constructor + * @param inputStream The input stream + */ + public RadialHeader(byte[] bytes) { + ByteBuffer bf = ByteBuffer.wrap(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + radialState = bf.getInt(); + spotBlank = bf.getInt(); + sequenceNumber = bf.getInt(); + radialNumber = bf.getInt(); + elevationNumber = bf.getInt(); + azimuth = bf.getFloat(); + elevation = bf.getFloat(); + seconds = bf.getLong(); + microSeconds = bf.getInt(); + lengthOfData = bf.getInt(); + momentNumber = bf.getInt(); + scanBeamIndex = bf.getShort(); + horizontalEstimatedNoise = bf.getShort(); + verticalEstimatedNoise = bf.getShort(); + PRFFLAG = bf.getInt(); + } + } +} diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadarDataUtil.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadarDataUtil.java index 40cf3922..1c06a2de 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadarDataUtil.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadarDataUtil.java @@ -1,14 +1,19 @@ package org.meteoinfo.data.meteodata.radar; +import org.apache.commons.compress.compressors.FileNameUtil; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.io.FilenameUtils; import org.meteoinfo.common.DataConvert; import org.meteoinfo.data.meteodata.DataInfo; -import java.io.RandomAccessFile; +import java.io.*; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.LocalDateTime; import java.util.Arrays; +import java.util.zip.GZIPInputStream; public class RadarDataUtil { @@ -53,6 +58,25 @@ public class RadarDataUtil { return null; } + /** + * Get InputStream from file name. + * + * @param fileName The file name + * @return The InputStream + * @throws IOException + */ + public static InputStream getInputStream(String fileName) throws IOException { + String fileExtent = FilenameUtils.getExtension(fileName).toLowerCase(); + switch (fileExtent) { + case "bz2": + return new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); + case "gz": + return new GzipCompressorInputStream(new FileInputStream(fileName)); + default: + return new BufferedInputStream(Files.newInputStream(Paths.get(fileName))); + } + } + /** * Get radar data type * @param fileName Data file name @@ -60,21 +84,24 @@ public class RadarDataUtil { */ public static RadarDataType getRadarDataType(String fileName) { try { + InputStream inputStream = getInputStream(fileName); byte[] bytes = new byte[136]; - if (fileName.endsWith("bz2")) { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - inputStream.read(bytes); - inputStream.close(); - } else { - RandomAccessFile raf = new RandomAccessFile(fileName, "r"); - raf.seek(0); - raf.read(bytes); - raf.close(); - } + inputStream.read(bytes); + inputStream.close(); + byte[] magicBytes = Arrays.copyOf(bytes, 4); int magic = DataConvert.bytes2Int(magicBytes, ByteOrder.LITTLE_ENDIAN); if (magic == 1297371986) { - return RadarDataType.STANDARD; + byte[] inBytes = Arrays.copyOf(bytes, GenericHeader.length); + GenericHeader genericHeader = new GenericHeader(inBytes); + switch (genericHeader.genericType) { + case 1: + return RadarDataType.STANDARD; + case 16: + return RadarDataType.PA; + default: + return null; + } } String radarT = new String(bytes); @@ -86,14 +113,17 @@ public class RadarDataUtil { return RadarDataType.CC20; } - magicBytes = Arrays.copyOfRange(bytes, 14, 16); - if (Arrays.equals(magicBytes, new byte[]{1, 0})) { - return RadarDataType.SAB; + magicBytes = Arrays.copyOf(bytes, 128); + SABRadarDataInfo.RadialHeader radialHeader = new SABRadarDataInfo.RadialHeader(magicBytes); + if (radialHeader.messageType != 1) { + return null; } - - magicBytes = Arrays.copyOfRange(bytes, 8, 12); - if (Arrays.equals(magicBytes, new byte[]{16, 0, 0, 0})) { - return RadarDataType.PA; + if (radialHeader.mSecond > 86400000) { + return null; + } + LocalDateTime dateTime = radialHeader.getDateTime(); + if (dateTime.getYear() >= 1990 && dateTime.getYear() <= LocalDateTime.now().getYear()) { + return RadarDataType.SAB; } } catch (Exception e) { @@ -114,11 +144,13 @@ public class RadarDataUtil { } else { switch (radarDataType) { case STANDARD: - return new CMARadarBaseDataInfo(); + return new StandardRadarDataInfo(); + case PA: + return new PARadarDataInfo(); case SAB: return new SABRadarDataInfo(); - /*case CC: - return new CCRadarDataInfo();*/ + case CC: + return new CCRadarDataInfo(); case SC: return new SCRadarDataInfo(); default: diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadialRecord.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadialRecord.java index 60174de7..a24b3665 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadialRecord.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/RadialRecord.java @@ -10,12 +10,14 @@ import org.meteoinfo.ndarray.DataType; import org.meteoinfo.ndarray.Index; import org.meteoinfo.ndarray.math.ArrayUtil; +import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; public class RadialRecord { public String product; + private RadarDataType radarDataType = RadarDataType.STANDARD; private int binLength; private DataType dataType; private int fillValue; @@ -26,7 +28,7 @@ public class RadialRecord { public List> azimuth = new ArrayList<>(); public List azimuthMinIndex = new ArrayList<>(); public List distance = new ArrayList<>(); - public List disResolution = new ArrayList<>(); + public List disResolution = new ArrayList<>(); private final List> data = new ArrayList<>(); /** @@ -37,6 +39,22 @@ public class RadialRecord { this.product = product; } + /** + * Get radar data type + * @return Radar data type + */ + public RadarDataType getRadarDataType() { + return this.radarDataType; + } + + /** + * Set radar data type + * @param radarDataType Radar data type + */ + public void setRadarDataType(RadarDataType radarDataType) { + this.radarDataType = radarDataType; + } + /** * Set bin length and update DataType * @param value Bin length @@ -55,6 +73,14 @@ public class RadialRecord { return this.dataType; } + /** + * Set data type + * @param dataType Data type + */ + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + /** * Add an azimuth value * @param a Azimuth value @@ -265,6 +291,9 @@ public class RadialRecord { } variable.addAttribute(new Attribute("scale_factor", this.scale)); variable.addAttribute(new Attribute("add_offset", this.offset)); + if (this.radarDataType == RadarDataType.CC) { + variable.addAttribute(new Attribute("missing_value", -32768)); + } dataInfo.addVariable(variable); } @@ -299,6 +328,9 @@ public class RadialRecord { variable.addDimension(disDim); variable.addAttribute(new Attribute("scale_factor", this.scale)); variable.addAttribute(new Attribute("add_offset", this.offset)); + if (this.radarDataType == RadarDataType.CC) { + variable.addAttribute(new Attribute("missing_value", -32768)); + } dataInfo.addVariable(variable); variable = new Variable(); @@ -493,7 +525,7 @@ public class RadialRecord { public float getValue(int ei, float a, float r) { List sData = this.data.get(ei); int aziIdx = getAzimuthIndex(ei, a); - int disRes = this.disResolution.get(ei); + float disRes = this.disResolution.get(ei); int disIdx = (int) (r / disRes); Array rData = sData.get(aziIdx); float v; @@ -522,7 +554,7 @@ public class RadialRecord { */ public float interpolateValue(int ei, int ai, float r) { List sData = this.data.get(ei); - int disRes = this.disResolution.get(ei); + float disRes = this.disResolution.get(ei); float v; Array rData = sData.get(ai); float disIdx = r / disRes; diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SABRadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SABRadarDataInfo.java index 0540333e..43d2080d 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SABRadarDataInfo.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SABRadarDataInfo.java @@ -17,6 +17,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -51,25 +54,6 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf } @Override - public void readDataInfo(String fileName) { - this.fileName = fileName; - if (fileName.endsWith(".bz2")) { - try { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - try { - BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - void readDataInfo(InputStream is) { try { int index = 0; @@ -99,11 +83,11 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf record.azimuth.add(new ArrayList<>()); record.azimuthMinIndex.add(0); if (isVelocityGroup(record)) { - record.disResolution.add(radialHeader.gateSizeOfDoppler); + record.disResolution.add((float) radialHeader.gateSizeOfDoppler); record.distance.add(ArrayUtil.arrayRange1(radialHeader.rangeToFirstGateOfDop, radialHeader.gatesNumberOfDoppler, radialHeader.gateSizeOfDoppler)); } else { - record.disResolution.add(radialHeader.gateSizeOfReflectivity); + record.disResolution.add((float) radialHeader.gateSizeOfReflectivity); record.distance.add(ArrayUtil.arrayRange1(radialHeader.rangeToFirstGateOfRef, radialHeader.gatesNumberOfReflectivity, radialHeader.gateSizeOfReflectivity)); } @@ -135,6 +119,7 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf this.addAttribute(new Attribute("featureType", "RADIAL")); this.addAttribute(new Attribute("DataType", "Radial")); + this.addAttribute(new Attribute("RadarDataType", "SA/SB")); //Add dimensions and variables RadialRecord refRadialRecord = this.recordMap.get("dBZ"); @@ -172,6 +157,7 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf static class RadialHeader { public static int length = 128; + public short messageType; public int mSecond; // collection time for this radial, msecs since midnight public short julianDate; // prob "collection time" public short uRange; // unambiguous range @@ -202,6 +188,8 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf public RadialHeader(byte[] inBytes) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(inBytes); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.position(14); + messageType = byteBuffer.getShort(); byteBuffer.position(28); mSecond = byteBuffer.getInt(); julianDate = byteBuffer.getShort(); @@ -228,6 +216,15 @@ public class SABRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInf nyquist = byteBuffer.getShort(); } + /** + * Get date time + * @return Date time + */ + public LocalDateTime getDateTime() { + long total = ((long) (julianDate - 1)) * 24 * 3600 * 1000 + mSecond; + return LocalDateTime.ofInstant(Instant.ofEpochMilli(total), ZoneId.systemDefault()); + } + /** * Has reflectivity data or not * @return Has reflectivity data diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SCRadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SCRadarDataInfo.java index 2cfc28cd..c5f9fdd1 100644 --- a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SCRadarDataInfo.java +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/SCRadarDataInfo.java @@ -44,25 +44,6 @@ public class SCRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo } @Override - public void readDataInfo(String fileName) { - this.fileName = fileName; - if (fileName.endsWith(".bz2")) { - try { - BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - try { - BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(fileName))); - readDataInfo(inputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - void readDataInfo(InputStream is) { try { byte[] bytes = new byte[SCRadarDataInfo.RadarHeader.length]; @@ -94,7 +75,7 @@ public class SCRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo record.elevation.add(new ArrayList<>()); record.azimuth.add(new ArrayList<>()); record.azimuthMinIndex.add(0); - record.disResolution.add(layerParam.binWidth / 10); + record.disResolution.add(layerParam.binWidth / 10.f); record.distance.add(ArrayUtil.arrayRange1(0, gateNum, layerParam.binWidth / 10)); record.newScanData(); @@ -121,6 +102,7 @@ public class SCRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo this.addAttribute(new Attribute("AntennaHeight", radarHeader.getHeight())); this.addAttribute(new Attribute("featureType", "RADIAL")); this.addAttribute(new Attribute("DataType", "Radial")); + this.addAttribute(new Attribute("RadarDataType", "SC")); //Add dimensions and variables RadialRecord refRadialRecord = this.recordMap.get("dBZ"); diff --git a/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/StandardRadarDataInfo.java b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/StandardRadarDataInfo.java new file mode 100644 index 00000000..eb0b067f --- /dev/null +++ b/meteoinfo-data/src/main/java/org/meteoinfo/data/meteodata/radar/StandardRadarDataInfo.java @@ -0,0 +1,256 @@ +package org.meteoinfo.data.meteodata.radar; + +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.meteoinfo.common.DataConvert; +import org.meteoinfo.data.dimarray.Dimension; +import org.meteoinfo.data.meteodata.*; +import org.meteoinfo.ndarray.*; +import org.meteoinfo.ndarray.math.ArrayUtil; + +import java.io.*; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +public class StandardRadarDataInfo extends BaseRadarDataInfo implements IRadarDataInfo { + + private GenericHeader genericHeader; + private SiteConfig siteConfig; + private TaskConfig taskConfig; + private List cutConfigs; + private List radialHeaders; + + /** + * Constructor + */ + public StandardRadarDataInfo() { + this.meteoDataType = MeteoDataType.RADAR; + } + + /** + * Get radar data type + * @return Radar data type + */ + @Override + public RadarDataType getRadarDataType() { + return RadarDataType.STANDARD; + } + + /** + * Get generic header + * @return Generic header + */ + public GenericHeader getGenericHeader() { + return this.genericHeader; + } + + /** + * Get site config + * @return Site config + */ + public SiteConfig getSiteConfig() { + return this.siteConfig; + } + + /** + * Get task config + * @return Task config + */ + public TaskConfig getTaskConfig() { + return this.taskConfig; + } + + /** + * Get cut config list + * @return Cut config list + */ + public List getCutConfigs() { + return this.cutConfigs; + } + + /** + * Get radial header list + * @return Radial header list + */ + public List getRadialHeaders() { + return this.radialHeaders; + } + + @Override + public boolean isValidFile(RandomAccessFile raf) { + try { + raf.seek(0); + byte[] bytes = new byte[4]; + raf.read(bytes); + int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + if (magic == 1297371986) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } + + /** + * Check the data file format + * @param fileName Data file name + * @return Boolean + */ + public static boolean canOpen(String fileName) { + try { + byte[] bytes = new byte[4]; + if (fileName.endsWith("bz2")) { + BZip2CompressorInputStream inputStream = new BZip2CompressorInputStream(Files.newInputStream(Paths.get(fileName))); + inputStream.read(bytes); + inputStream.close(); + } else { + RandomAccessFile raf = new RandomAccessFile(fileName, "r"); + raf.seek(0); + raf.read(bytes); + raf.close(); + } + int magic = DataConvert.bytes2Int(bytes, ByteOrder.LITTLE_ENDIAN); + if (magic == 1297371986) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } + + @Override + void readDataInfo(InputStream raf) { + try { + genericHeader = new GenericHeader(raf); + siteConfig = new SiteConfig(raf); + this.antennaHeight = siteConfig.antennaHeight; + this.beamWidthVert = siteConfig.beamWidthVert; + + //Add global attributes + this.addAttribute(new Attribute("StationCode", siteConfig.siteCode)); + this.addAttribute(new Attribute("StationName", siteConfig.siteName)); + this.addAttribute(new Attribute("StationLatitude", siteConfig.latitude)); + this.addAttribute(new Attribute("StationLongitude", siteConfig.longitude)); + this.addAttribute(new Attribute("AntennaHeight", siteConfig.antennaHeight)); + this.addAttribute(new Attribute("GroundHeight", siteConfig.groundHeight)); + this.addAttribute(new Attribute("RadarType", siteConfig.getRadarType())); + this.addAttribute(new Attribute("featureType", "RADIAL")); + this.addAttribute(new Attribute("DataType", "Radial")); + this.addAttribute(new Attribute("RadarDataType", "CMA Standard")); + + //Read task configuration + taskConfig = new TaskConfig(raf); + this.addAttribute(new Attribute("TaskName", taskConfig.taskName)); + this.addAttribute(new Attribute("TaskDescription", taskConfig.taskDescription)); + + //Read radial data + cutConfigs = new ArrayList<>(); + CutConfig cutConfig; + for (int i = 0; i < taskConfig.cutNumber; i++) { + cutConfig = new CutConfig(raf); + cutConfigs.add(cutConfig); + if (i == 0) { + this.logResolution = cutConfig.logResolution; + this.dopplerResolution = cutConfig.dopplerResolution; + } + } + radialHeaders = new ArrayList<>(); + byte[] rhBytes = new byte[RadialHeader.length]; + while (raf.read(rhBytes) != -1) { + RadialHeader radialHeader = new RadialHeader(rhBytes); + for (int i = 0; i < radialHeader.momentNumber; i++) { + MomentHeader momentHeader = new MomentHeader(raf); + String product = this.productMap.get(momentHeader.dataType); + RadialRecord record; + if (this.recordMap.containsKey(product)) { + record = this.recordMap.get(product); + } else { + record = new RadialRecord(product); + record.setBinLength(momentHeader.binLength); + record.scale = 1.f / momentHeader.scale; + record.offset = -momentHeader.offset / (float) momentHeader.scale; + this.recordMap.put(product, record); + } + if (radialHeader.radialNumber == 1) { + record.fixedElevation.add(cutConfigs.get(radialHeader.elevationNumber - 1).elevation); + record.elevation.add(new ArrayList<>()); + record.azimuth.add(new ArrayList<>()); + record.azimuthMinIndex.add(0); + if (isVelocityGroup(record)) { + record.disResolution.add((float) cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution); + record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, + cutConfigs.get(radialHeader.elevationNumber - 1).dopplerResolution)); + } else { + record.disResolution.add((float) cutConfigs.get(radialHeader.elevationNumber - 1).logResolution); + record.distance.add(ArrayUtil.arrayRange1(0, momentHeader.dataLength / momentHeader.binLength, + cutConfigs.get(radialHeader.elevationNumber - 1).logResolution)); + } + record.newScanData(); + } + record.elevation.get(record.elevation.size() - 1).add(radialHeader.elevation); + record.addAzimuth(radialHeader.azimuth); + byte[] bytes = new byte[momentHeader.dataLength]; + raf.read(bytes); + record.addDataBytes(bytes); + } + radialHeaders.add(radialHeader); + } + raf.close(); + + //Add dimensions and variables + RadialRecord refRadialRecord = this.recordMap.get("dBZ"); + radialDim = new Dimension(); + radialDim.setName("radial"); + radialDim.setLength(refRadialRecord.getMaxRadials()); + this.addDimension(radialDim); + scanDim = new Dimension(); + scanDim.setName("scan"); + scanDim.setLength(refRadialRecord.getScanNumber()); + this.addDimension(scanDim); + gateRDim = new Dimension(); + gateRDim.setName("gateR"); + gateRDim.setLength(refRadialRecord.getGateNumber(0)); + this.addDimension(gateRDim); + makeRefVariables(refRadialRecord); + + RadialRecord velRadialRecord = this.recordMap.get("V"); + gateVDim = new Dimension(); + gateVDim.setName("gateV"); + gateVDim.setLength(velRadialRecord.getGateNumber(0)); + this.addDimension(gateVDim); + makeVelVariables(velRadialRecord); + + /*Dimension xyzDim = new Dimension(DimensionType.OTHER); + xyzDim.setShortName("xyz"); + xyzDim.setDimValue(Array.factory(DataType.INT, new int[]{3}, new int[]{1,2,3})); + this.addDimension(xyzDim); + for (String product : this.recordMap.keySet()) { + this.recordMap.get(product).makeVariables(this, xyzDim); + }*/ + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Get scan elevations + * @return Scan elevations + */ + public List getElevations() { + List elevations = new ArrayList<>(); + for (CutConfig cutConfig : this.cutConfigs) { + if (!elevations.contains(cutConfig.elevation)) + elevations.add(cutConfig.elevation); + } + + return elevations; + } + +} diff --git a/meteoinfo-lab/milconfig.xml b/meteoinfo-lab/milconfig.xml index 76c62cf7..7e77fb57 100644 --- a/meteoinfo-lab/milconfig.xml +++ b/meteoinfo-lab/milconfig.xml @@ -1,13 +1,11 @@ - + - - @@ -16,17 +14,19 @@ + + - + - + - + - + @@ -34,5 +34,5 @@
- + diff --git a/pom.xml b/pom.xml index e546a626..b5f85742 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ UTF-8 1.8 - 3.8.9 + 3.8.10 8 8 8