mirror of
https://github.com/meteoinfo/MeteoInfo.git
synced 2025-12-08 20:36:05 +00:00
Better Bufr data support including multi-category messages in one data file
This commit is contained in:
parent
be48f3c9cc
commit
b776981ba6
5
.idea/compiler.xml
generated
5
.idea/compiler.xml
generated
@ -28,4 +28,9 @@
|
|||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
<bytecodeTargetLevel target="11" />
|
<bytecodeTargetLevel target="11" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="meteoinfo-data" options="-extdirs D:\MyProgram\java\MeteoInfoDev\MeteoInfo\meteoinfo-data\lib" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
||||||
14
.idea/inspectionProfiles/Project_Default.xml
generated
14
.idea/inspectionProfiles/Project_Default.xml
generated
@ -811,10 +811,12 @@
|
|||||||
<inspection_tool class="CyclomaticComplexity" enabled="false" level="WARNING" enabled_by_default="false">
|
<inspection_tool class="CyclomaticComplexity" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
<option name="m_limit" value="10" />
|
<option name="m_limit" value="10" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CyclomaticComplexityInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="DanglingJavadoc" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="DanglingJavadoc" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="DataClassPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="DataClassPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="DataProviderReturnType" enabled="false" level="ERROR" enabled_by_default="false" />
|
<inspection_tool class="DataProviderReturnType" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
<inspection_tool class="DateToString" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="DateToString" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="DeclarativeUnresolvedReference" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="DeclareCollectionAsInterface" enabled="false" level="WARNING" enabled_by_default="false">
|
<inspection_tool class="DeclareCollectionAsInterface" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
<option name="ignoreLocalVariables" value="false" />
|
<option name="ignoreLocalVariables" value="false" />
|
||||||
<option name="ignorePrivateMethodsAndFields" value="false" />
|
<option name="ignorePrivateMethodsAndFields" value="false" />
|
||||||
@ -955,6 +957,7 @@
|
|||||||
<inspection_tool class="ExplicitArrayFilling" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ExplicitArrayFilling" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ExplicitThis" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
<inspection_tool class="ExplicitThis" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
<inspection_tool class="ExplicitToImplicitClassMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ExplicitToImplicitClassMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ExpressionComparedToItself" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ExpressionMayBeFactorized" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
<inspection_tool class="ExpressionMayBeFactorized" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="ExtendsAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ExtendsAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ExtendsConcreteCollection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ExtendsConcreteCollection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
@ -1284,6 +1287,7 @@
|
|||||||
<inspection_tool class="InfinitePropertiesLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="InfinitePropertiesLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="InfiniteRecursion" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="InfiniteRecursion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="InfiniteTransitionLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="InfiniteTransitionLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="InfixCallToOrdinary" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="InjectedReferences" enabled="true" level="ERROR" enabled_by_default="true" />
|
<inspection_tool class="InjectedReferences" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
<inspection_tool class="InjectionNotApplicable" enabled="true" level="ERROR" enabled_by_default="true" />
|
<inspection_tool class="InjectionNotApplicable" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
<inspection_tool class="InlineClassDeprecatedMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="InlineClassDeprecatedMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
@ -1433,6 +1437,7 @@
|
|||||||
<inspection_tool class="Junit4Converter" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
<inspection_tool class="Junit4Converter" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
<inspection_tool class="Junit4RunWithInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="Junit4RunWithInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="JvmCoverageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="JvmCoverageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JvmLinesOfCodeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="KDocMissingDocumentation" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="KDocMissingDocumentation" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="KDocUnresolvedReference" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="KDocUnresolvedReference" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
@ -1507,10 +1512,12 @@
|
|||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="LocaleText" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="LocaleText" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="LogStatementGuardedByLogCondition" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="LogStatementGuardedByLogCondition" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LogStatementNotGuardedByLogCondition" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="LoggingConditionDisagreesWithLogStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="LoggingConditionDisagreesWithLogStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LoggingGuardedByCondition" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="LoggingPlaceholderCountMatchesArgumentCount" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="LoggingPlaceholderCountMatchesArgumentCount" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="LoggingSimilarMessage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="LoggingSimilarMessage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="LoggingStringTemplateAsArgument" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="LoggingStringTemplateAsArgument" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
@ -1534,12 +1541,14 @@
|
|||||||
<inspection_tool class="MalformedDataProvider" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MalformedDataProvider" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="MalformedFormatString" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MalformedFormatString" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="MalformedXPath" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MalformedXPath" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="MaliciousLibrariesLocal" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ManualArrayCopy" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ManualArrayCopy" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ManualArrayToCollectionCopy" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ManualArrayToCollectionCopy" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ManualMinMaxCalculation" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ManualMinMaxCalculation" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="MapGetWithNotNullAssertionOperator" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
<inspection_tool class="MapGetWithNotNullAssertionOperator" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
<inspection_tool class="MapReplaceableByEnumMap" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MapReplaceableByEnumMap" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="MappingBeforeCount" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MappingBeforeCount" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="MarkdownDocumentationCommentsMigration" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="MarkdownIncorrectTableFormatting" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
<inspection_tool class="MarkdownIncorrectTableFormatting" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="MarkdownIncorrectlyNumberedListItem" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="MarkdownIncorrectlyNumberedListItem" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="MarkdownLinkDestinationWithSpaces" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="MarkdownLinkDestinationWithSpaces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
@ -2142,7 +2151,9 @@
|
|||||||
<option name="ignoreCloneable" value="false" />
|
<option name="ignoreCloneable" value="false" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="RedundantInnerClassModifier" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="RedundantInnerClassModifier" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="RedundantJavaTimeOperations" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="RedundantLabelMigration" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="RedundantLabelMigration" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="RedundantLabeledReturnOnLastExpressionInLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="RedundantLabeledSwitchRuleCodeBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="RedundantLabeledSwitchRuleCodeBlock" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="RedundantLambdaArrow" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
<inspection_tool class="RedundantLambdaArrow" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="RedundantLambdaOrAnonymousFunction" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
<inspection_tool class="RedundantLambdaOrAnonymousFunction" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
@ -2280,6 +2291,7 @@
|
|||||||
<inspection_tool class="ReturnSeparatedFromComputation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
<inspection_tool class="ReturnSeparatedFromComputation" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="ReturnThis" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="ReturnThis" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="ReuseOfLocalVariable" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
<inspection_tool class="ReuseOfLocalVariable" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="RunBlocking" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="RuntimeExec" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="RuntimeExec" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="SSBasedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="SSBasedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
@ -2422,6 +2434,7 @@
|
|||||||
<option name="onlyWarnOnLoop" value="true" />
|
<option name="onlyWarnOnLoop" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="StringTemplateMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="StringTemplateMigration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="StringTemplateReverseMigration" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
<inspection_tool class="StringToUpperWithoutLocale" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="StringToUpperWithoutLocale" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="StringTokenizer" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="StringTokenizer" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="StringTokenizerDelimiter" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="StringTokenizerDelimiter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
@ -2679,6 +2692,7 @@
|
|||||||
<inspection_tool class="UnspecifiedActionsPlace" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="UnspecifiedActionsPlace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="UnstableApiUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="UnstableApiUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="UnstableTypeUsedInSignature" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="UnstableTypeUsedInSignature" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnsupportedCharacter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="UnsupportedChronoFieldUnitCall" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="UnsupportedChronoFieldUnitCall" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="UnusedAssignment" enabled="false" level="WARNING" enabled_by_default="false">
|
<inspection_tool class="UnusedAssignment" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
<option name="REPORT_PREFIX_EXPRESSIONS" value="false" />
|
<option name="REPORT_PREFIX_EXPRESSIONS" value="false" />
|
||||||
|
|||||||
@ -42,11 +42,6 @@
|
|||||||
<artifactId>meteoinfo-data</artifactId>
|
<artifactId>meteoinfo-data</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>meteoinfo-projection</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>meteoinfo-image</artifactId>
|
<artifactId>meteoinfo-image</artifactId>
|
||||||
|
|||||||
@ -417,9 +417,8 @@ public class RandomAccessFile implements DataInput, DataOutput {
|
|||||||
* write will occur.
|
* write will occur.
|
||||||
*
|
*
|
||||||
* @return the offset from the start of the file in bytes.
|
* @return the offset from the start of the file in bytes.
|
||||||
* @throws IOException if an I/O error occurrs.
|
|
||||||
*/
|
*/
|
||||||
public long getFilePointer() throws IOException {
|
public long getFilePointer() {
|
||||||
return filePosition;
|
return filePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
String version = GlobalUtil.class.getPackage().getImplementationVersion();
|
String version = GlobalUtil.class.getPackage().getImplementationVersion();
|
||||||
if (version == null || version.equals("")) {
|
if (version == null || version.equals("")) {
|
||||||
version = "3.9.6";
|
version = "3.9.7";
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
<netcdf.version>5.6.0</netcdf.version>
|
<netcdf.version>5.6.0</netcdf.version>
|
||||||
|
<burf.version>5.6.1-SNAPSHOT</burf.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
@ -51,12 +52,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>edu.ucar</groupId>
|
<groupId>edu.ucar</groupId>
|
||||||
<artifactId>cdm-core</artifactId>
|
<artifactId>cdm-core</artifactId>
|
||||||
<version>${netcdf.version}</version>
|
<version>5.6.1-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>edu.ucar</groupId>
|
<groupId>edu.ucar</groupId>
|
||||||
<artifactId>bufr</artifactId>
|
<artifactId>bufr</artifactId>
|
||||||
<version>${netcdf.version}</version>
|
<version>5.6.1-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>edu.ucar</groupId>
|
<groupId>edu.ucar</groupId>
|
||||||
@ -135,6 +136,20 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<optimize>true</optimize>
|
||||||
|
<!-- 导入\lib下的本地jar包 -->
|
||||||
|
<compilerArguments>
|
||||||
|
<extdirs>${project.basedir}\lib</extdirs>
|
||||||
|
</compilerArguments>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ public class MeteoDataInfo {
|
|||||||
private int _lonIdx;
|
private int _lonIdx;
|
||||||
private DrawType2D drawType2D;
|
private DrawType2D drawType2D;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is Lont/Lat
|
/// Is Lon/Lat
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public boolean IsLonLat;
|
public boolean IsLonLat;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -50,6 +50,7 @@ public class Variable {
|
|||||||
private boolean isSwath = false;
|
private boolean isSwath = false;
|
||||||
private int varId;
|
private int varId;
|
||||||
private boolean dimVar = false;
|
private boolean dimVar = false;
|
||||||
|
private boolean memberOfStructure = false;
|
||||||
private List<Integer> levelIdxs = new ArrayList<>();
|
private List<Integer> levelIdxs = new ArrayList<>();
|
||||||
private List<Integer> varInLevelIdxs = new ArrayList<>();
|
private List<Integer> varInLevelIdxs = new ArrayList<>();
|
||||||
private Array cachedData;
|
private Array cachedData;
|
||||||
@ -560,6 +561,24 @@ public class Variable {
|
|||||||
dimVar = value;
|
dimVar = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if the variable is a member of a structure
|
||||||
|
*
|
||||||
|
* @return Is a member of a structure or not
|
||||||
|
*/
|
||||||
|
public boolean isMemberOfStructure() {
|
||||||
|
return this.memberOfStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the variable is a member of a structure
|
||||||
|
*
|
||||||
|
* @param value Boolean
|
||||||
|
*/
|
||||||
|
public void setMemberOfStructure(boolean value) {
|
||||||
|
this.memberOfStructure = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get level index list - for ARL data
|
* Get level index list - for ARL data
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for bit counters
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/8/13
|
||||||
|
*/
|
||||||
|
public interface BitCounter {
|
||||||
|
int getNumberRows();
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count size of compressed fields
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Jul 4, 2008
|
||||||
|
*/
|
||||||
|
public class BitCounterCompressed implements BitCounter {
|
||||||
|
|
||||||
|
private final DataDescriptor dkey; // the field to count
|
||||||
|
private final int nrows; // number of (obs) in the compression
|
||||||
|
private final int bitOffset; // starting position of the compressed data, relative to start of data section
|
||||||
|
private int dataWidth; // bitWidth of incremental values
|
||||||
|
private BitCounterCompressed[][] nested; // used if the dkey is a structure = nested[innerRows][dkey.subkeys.size]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This counts the size of an array of Structures or Sequences, ie Structure(n)
|
||||||
|
*
|
||||||
|
* @param dkey is a structure or a sequence - so has subKeys
|
||||||
|
* @param n numbers of rows in the table
|
||||||
|
* @param bitOffset number of bits taken up by the count variable (non-zero only for sequences)
|
||||||
|
*/
|
||||||
|
public BitCounterCompressed(DataDescriptor dkey, int n, int bitOffset) {
|
||||||
|
this.dkey = dkey;
|
||||||
|
this.nrows = n;
|
||||||
|
this.bitOffset = bitOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDataWidth(int dataWidth) {
|
||||||
|
this.dataWidth = dataWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartingBitPos() {
|
||||||
|
return bitOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitPos(int msgOffset) {
|
||||||
|
return bitOffset + dkey.bitWidth + 6 + dataWidth * msgOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalBits() {
|
||||||
|
if (nested == null)
|
||||||
|
return dkey.bitWidth + 6 + dataWidth * nrows;
|
||||||
|
else {
|
||||||
|
int totalBits = 0;
|
||||||
|
for (BitCounterCompressed[] counters : nested) {
|
||||||
|
if (counters == null)
|
||||||
|
continue;
|
||||||
|
for (BitCounterCompressed counter : counters)
|
||||||
|
if (counter != null)
|
||||||
|
totalBits += counter.getTotalBits();
|
||||||
|
}
|
||||||
|
if (dkey.replicationCountSize > 0)
|
||||||
|
totalBits += dkey.replicationCountSize + 6; // 6 boit count, 6 bit extra
|
||||||
|
return totalBits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitCounterCompressed[] getNestedCounters(int innerIndex) {
|
||||||
|
return nested[innerIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNestedCounters(int innerDimensionSize) {
|
||||||
|
nested = new BitCounterCompressed[innerDimensionSize][dkey.getSubKeys().size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of nested fields
|
||||||
|
*
|
||||||
|
* @return 1 if no nested fields, otherwise count of nested fields
|
||||||
|
*/
|
||||||
|
public int ncounters() {
|
||||||
|
if (nested == null)
|
||||||
|
return 1;
|
||||||
|
else {
|
||||||
|
int ncounters = 0;
|
||||||
|
for (BitCounterCompressed[] counters : nested) {
|
||||||
|
if (counters == null)
|
||||||
|
continue;
|
||||||
|
for (BitCounterCompressed counter : counters)
|
||||||
|
if (counter != null)
|
||||||
|
ncounters += counter.ncounters();
|
||||||
|
}
|
||||||
|
return ncounters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Formatter out, int indent) {
|
||||||
|
for (int i = 0; i < indent; i++)
|
||||||
|
out.format(" ");
|
||||||
|
out.format("%8d %8d %4d %s %n", getTotalBits(), bitOffset, dataWidth, dkey.name);
|
||||||
|
if (nested != null) {
|
||||||
|
for (BitCounterCompressed[] counters : nested) {
|
||||||
|
if (counters == null)
|
||||||
|
continue;
|
||||||
|
for (BitCounterCompressed counter : counters)
|
||||||
|
if (counter != null)
|
||||||
|
counter.show(out, indent + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumberRows() {
|
||||||
|
return nrows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.nc2.util.Indent;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the size of nested tables, for uncompressed messages.
|
||||||
|
* <p>
|
||||||
|
* A top-level BitCounterUncompressed counts bits for one row = obs = dataset.
|
||||||
|
* obs = new BitCounterUncompressed(root, 1, 0);
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since May 10, 2008
|
||||||
|
*/
|
||||||
|
public class BitCounterUncompressed implements BitCounter {
|
||||||
|
private final DataDescriptor parent; // represents the table - fields/cols are the subKeys of dkey
|
||||||
|
private final int nrows; // number of rows in this table
|
||||||
|
private final int replicationCountSize; // number of bits taken up by the count variable (non-zero only for sequences)
|
||||||
|
|
||||||
|
private Map<DataDescriptor, Integer> bitPosition;
|
||||||
|
private Map<DataDescriptor, BitCounterUncompressed[]> subCounters; // nested tables; null for regular fields
|
||||||
|
private int[] startBit; // from start of data section, for each row
|
||||||
|
private int countBits; // total nbits in this table
|
||||||
|
private int bitOffset; // count bits
|
||||||
|
|
||||||
|
private static boolean debug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This counts the size of an array of Structures or Sequences, ie Structure(n)
|
||||||
|
*
|
||||||
|
* @param parent is a structure or a sequence - so has subKeys
|
||||||
|
* @param nrows numbers of rows in the table, equals 1 for top level
|
||||||
|
* @param replicationCountSize number of bits taken up by the count variable (non-zero only for sequences)
|
||||||
|
*/
|
||||||
|
BitCounterUncompressed(DataDescriptor parent, int nrows, int replicationCountSize) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.nrows = nrows;
|
||||||
|
this.replicationCountSize = replicationCountSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not used yet
|
||||||
|
public void setBitOffset(DataDescriptor dkey) {
|
||||||
|
if (bitPosition == null)
|
||||||
|
bitPosition = new HashMap<>(2 * parent.getSubKeys().size());
|
||||||
|
bitPosition.put(dkey, bitOffset);
|
||||||
|
bitOffset += dkey.getBitWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset(DataDescriptor dkey) {
|
||||||
|
return bitPosition.get(dkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track nested Tables.
|
||||||
|
*
|
||||||
|
* @param subKey subKey is a structure or a sequence - so itself has subKeys
|
||||||
|
* @param n numbers of rows in the nested table
|
||||||
|
* @param row which row in the parent Table this belongs to
|
||||||
|
* @param replicationCountSize number of bits taken up by the count (non-zero for sequences)
|
||||||
|
* @return nested ReplicationCounter
|
||||||
|
*/
|
||||||
|
BitCounterUncompressed makeNested(DataDescriptor subKey, int n, int row, int replicationCountSize) {
|
||||||
|
if (subCounters == null)
|
||||||
|
subCounters = new HashMap<>(5); // assumes DataDescriptor.equals is ==
|
||||||
|
|
||||||
|
// one for each row in this table
|
||||||
|
BitCounterUncompressed[] subCounter = subCounters.computeIfAbsent(subKey, k -> new BitCounterUncompressed[nrows]);
|
||||||
|
|
||||||
|
BitCounterUncompressed rc = new BitCounterUncompressed(subKey, n, replicationCountSize);
|
||||||
|
subCounter[row] = rc;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitCounterUncompressed[] getNested(DataDescriptor subKey) {
|
||||||
|
return (subCounters == null) ? null : subCounters.get(subKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// total bits of this table and all subtables
|
||||||
|
int countBits(int startBit) {
|
||||||
|
countBits = replicationCountSize;
|
||||||
|
this.startBit = new int[nrows];
|
||||||
|
|
||||||
|
for (int i = 0; i < nrows; i++) {
|
||||||
|
this.startBit[i] = startBit + countBits;
|
||||||
|
if (debug)
|
||||||
|
System.out.println(" BitCounterUncompressed row " + i + " startBit=" + this.startBit[i]);
|
||||||
|
|
||||||
|
for (DataDescriptor nd : parent.subKeys) {
|
||||||
|
BitCounterUncompressed[] bitCounter = (subCounters == null) ? null : subCounters.get(nd);
|
||||||
|
if (bitCounter == null) // a regular field
|
||||||
|
countBits += nd.getBitWidth();
|
||||||
|
else {
|
||||||
|
if (debug)
|
||||||
|
System.out.println(" ---------> nested " + nd.getFxyName() + " starts at =" + (startBit + countBits));
|
||||||
|
countBits += bitCounter[i].countBits(startBit + countBits);
|
||||||
|
if (debug)
|
||||||
|
System.out.println(" <--------- nested " + nd.getFxyName() + " ends at =" + (startBit + countBits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return countBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountBits() {
|
||||||
|
return countBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumberRows() {
|
||||||
|
return nrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartBit(int row) {
|
||||||
|
if (row >= startBit.length)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
return startBit[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toString(Formatter f, Indent indent) {
|
||||||
|
f.format("%s dds=%s, ", indent, parent.getFxyName());
|
||||||
|
f.format("nrows=%d%n", nrows);
|
||||||
|
if (subCounters == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
indent.incr();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
// Map<DataDescriptor, BitCounterUncompressed[]> subCounters; // nested tables; null for regular fields
|
||||||
|
for (BitCounterUncompressed[] bcus : subCounters.values()) {
|
||||||
|
if (bcus == null)
|
||||||
|
f.format("%s%d: null", indent, count);
|
||||||
|
else {
|
||||||
|
for (BitCounterUncompressed bcu : bcus)
|
||||||
|
bcu.toString(f, indent);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
indent.decr();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for reading data that has been bit packed.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Apr 7, 2008
|
||||||
|
*/
|
||||||
|
public class BitReader {
|
||||||
|
|
||||||
|
private static final int BIT_LENGTH = Byte.SIZE;
|
||||||
|
private static final int BYTE_BITMASK = 0xFF;
|
||||||
|
private static final long LONG_BITMASK = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
private RandomAccessFile raf;
|
||||||
|
private long startPos;
|
||||||
|
|
||||||
|
private byte[] data;
|
||||||
|
private int dataPos;
|
||||||
|
|
||||||
|
private byte bitBuf;
|
||||||
|
private int bitPos; // Current bit position in bitBuf.
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
public BitReader(byte[] test) {
|
||||||
|
this.data = test;
|
||||||
|
this.dataPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param raf the RandomAccessFile
|
||||||
|
* @param startPos points to start of data in data section, in bytes
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public BitReader(RandomAccessFile raf, long startPos) throws IOException {
|
||||||
|
this.raf = raf;
|
||||||
|
this.startPos = startPos;
|
||||||
|
raf.seek(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the next byte in the stream
|
||||||
|
*/
|
||||||
|
public void incrByte() {
|
||||||
|
this.bitPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position file at bitOffset from startPos
|
||||||
|
*
|
||||||
|
* @param bitOffset bit offset from starting position
|
||||||
|
* @throws IOException on io error
|
||||||
|
*/
|
||||||
|
public void setBitOffset(int bitOffset) throws IOException {
|
||||||
|
if (bitOffset % 8 == 0) {
|
||||||
|
raf.seek(startPos + bitOffset / 8);
|
||||||
|
bitPos = 0;
|
||||||
|
bitBuf = 0;
|
||||||
|
} else {
|
||||||
|
raf.seek(startPos + bitOffset / 8);
|
||||||
|
bitPos = 8 - (bitOffset % 8);
|
||||||
|
bitBuf = (byte) raf.read();
|
||||||
|
bitBuf &= 0xff >> (8 - bitPos); // mask off consumed bits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPos() {
|
||||||
|
if (raf != null) {
|
||||||
|
return raf.getFilePointer();
|
||||||
|
} else {
|
||||||
|
return dataPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next nb bits and return an Unsigned Long .
|
||||||
|
*
|
||||||
|
* @param nb the number of bits to convert to int, must be 0 <= nb <= 64.
|
||||||
|
* @return result
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public long bits2UInt(int nb) throws IOException {
|
||||||
|
assert nb <= 64;
|
||||||
|
assert nb >= 0;
|
||||||
|
|
||||||
|
long result = 0;
|
||||||
|
int bitsLeft = nb;
|
||||||
|
|
||||||
|
while (bitsLeft > 0) {
|
||||||
|
|
||||||
|
// we ran out of bits - fetch the next byte...
|
||||||
|
if (bitPos == 0) {
|
||||||
|
bitBuf = nextByte();
|
||||||
|
bitPos = BIT_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- retrieve bit from current byte ----------
|
||||||
|
// how many bits to read from the current byte
|
||||||
|
int size = Math.min(bitsLeft, bitPos);
|
||||||
|
// move my part to start
|
||||||
|
int myBits = bitBuf >> (bitPos - size);
|
||||||
|
// mask-off sign-extending
|
||||||
|
myBits &= BYTE_BITMASK;
|
||||||
|
// mask-off bits of next value
|
||||||
|
myBits &= ~(BYTE_BITMASK << size);
|
||||||
|
|
||||||
|
// -- put bit to result ----------------------
|
||||||
|
// where to place myBits inside of result
|
||||||
|
int shift = bitsLeft - size;
|
||||||
|
assert shift >= 0;
|
||||||
|
|
||||||
|
// put it there
|
||||||
|
result |= myBits << shift;
|
||||||
|
|
||||||
|
// -- put bit to result ----------------------
|
||||||
|
// update information on what we consumed
|
||||||
|
bitsLeft -= size;
|
||||||
|
bitPos -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next nb bits and return an Signed Long .
|
||||||
|
*
|
||||||
|
* @param nb the number of bits to convert to int, must be <= 64.
|
||||||
|
* @return result
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public long bits2SInt(int nb) throws IOException {
|
||||||
|
|
||||||
|
long result = bits2UInt(nb);
|
||||||
|
|
||||||
|
// check if we're negative
|
||||||
|
if (getBit(result, nb)) {
|
||||||
|
// it's negative! reset leading bit
|
||||||
|
result = setBit(result, nb, false);
|
||||||
|
// build 2's-complement
|
||||||
|
result = ~result & LONG_BITMASK;
|
||||||
|
result = result + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte nextByte() throws IOException {
|
||||||
|
if (raf != null) {
|
||||||
|
int result = raf.read();
|
||||||
|
if (result == -1)
|
||||||
|
throw new EOFException();
|
||||||
|
return (byte) result;
|
||||||
|
} else {
|
||||||
|
return data[dataPos++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long setBit(long decimal, int N, boolean value) {
|
||||||
|
return value ? decimal | (1 << (N - 1)) : decimal & ~(1 << (N - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getBit(long decimal, int N) {
|
||||||
|
int constant = 1 << (N - 1);
|
||||||
|
return (decimal & constant) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,633 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.point.BufrField;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.point.StandardFields;
|
||||||
|
import thredds.client.catalog.Catalog;
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.Attribute;
|
||||||
|
import ucar.nc2.NetcdfFile;
|
||||||
|
import ucar.nc2.NetcdfFiles;
|
||||||
|
import ucar.nc2.Sequence;
|
||||||
|
import ucar.nc2.constants.FeatureType;
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
import ucar.nc2.util.Indent;
|
||||||
|
import ucar.unidata.geoloc.StationImpl;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for converting BUFR files to CDM
|
||||||
|
* DataDescriptor tree becomes FieldConverter tree with annotations.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/8/13
|
||||||
|
*/
|
||||||
|
public class BufrConfig {
|
||||||
|
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrConfig.class);
|
||||||
|
|
||||||
|
public static BufrConfig scanEntireFile(RandomAccessFile raf) {
|
||||||
|
return new BufrConfig(raf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BufrConfig openFromMessage(RandomAccessFile raf, Message m, Element iospParam) throws IOException {
|
||||||
|
BufrConfig config = new BufrConfig(raf, m);
|
||||||
|
if (iospParam != null)
|
||||||
|
config.merge(iospParam);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String filename;
|
||||||
|
private Message message;
|
||||||
|
private StandardFields.StandardFieldsFromMessage standardFields;
|
||||||
|
private FieldConverter rootConverter;
|
||||||
|
private int messHash;
|
||||||
|
private FeatureType featureType;
|
||||||
|
private Map<String, BufrStation> map;
|
||||||
|
private long start = Long.MAX_VALUE;
|
||||||
|
private long end = Long.MIN_VALUE;
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open file as a stream of BUFR messages, create config file.
|
||||||
|
*
|
||||||
|
* Examine length of sequences, annotate
|
||||||
|
*
|
||||||
|
* @param bufrFilename open this file
|
||||||
|
*
|
||||||
|
* @throws java.io.IOException on IO error
|
||||||
|
*
|
||||||
|
* private BufrConfig(String bufrFilename, boolean read) throws IOException {
|
||||||
|
* this.filename = bufrFilename;
|
||||||
|
* try {
|
||||||
|
* scanBufrFile(new RandomAccessFile(bufrFilename, "r"), read);
|
||||||
|
* } catch (Exception e) {
|
||||||
|
* e.printStackTrace();
|
||||||
|
* throw new RuntimeException(e.getMessage());
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
private BufrConfig(RandomAccessFile raf) {
|
||||||
|
this.filename = raf.getLocation();
|
||||||
|
try {
|
||||||
|
scanBufrFile(raf);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufrConfig(RandomAccessFile raf, Message m) throws IOException {
|
||||||
|
this.filename = raf.getLocation();
|
||||||
|
this.message = m;
|
||||||
|
this.messHash = m.hashCode();
|
||||||
|
this.rootConverter = new FieldConverter(m.ids.getCenterId(), m.getRootDataDescriptor());
|
||||||
|
standardFields = StandardFields.extract(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message getMessage() {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldConverter getRootConverter() {
|
||||||
|
return rootConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, BufrStation> getStationMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessHash() {
|
||||||
|
return messHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeatureType getFeatureType() {
|
||||||
|
return featureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldConverter getStandardField(BufrCdmIndexProto.FldType want) {
|
||||||
|
for (FieldConverter fld : rootConverter.flds)
|
||||||
|
if (fld.type == want)
|
||||||
|
return fld;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getNobs() {
|
||||||
|
return countObs;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void merge(Element iospParam) {
|
||||||
|
assert iospParam.getName().equals("iospParam");
|
||||||
|
Element bufr2nc = iospParam.getChild("bufr2nc", Catalog.ncmlNS);
|
||||||
|
if (bufr2nc == null)
|
||||||
|
return;
|
||||||
|
for (Element child : bufr2nc.getChildren("fld", Catalog.ncmlNS))
|
||||||
|
merge(child, rootConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void merge(Element jdom, FieldConverter parent) {
|
||||||
|
if (jdom == null || parent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FieldConverter fld = null;
|
||||||
|
|
||||||
|
// find the corresponding field
|
||||||
|
String idxName = jdom.getAttributeValue("idx");
|
||||||
|
if (idxName != null) {
|
||||||
|
try {
|
||||||
|
int idx = Integer.parseInt(idxName);
|
||||||
|
fld = parent.getChild(idx);
|
||||||
|
} catch (NumberFormatException ne) {
|
||||||
|
log.info("BufrConfig cant find Child member index={} for file = {}", idxName, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld == null) {
|
||||||
|
String fxyName = jdom.getAttributeValue("fxy");
|
||||||
|
if (fxyName != null) {
|
||||||
|
fld = parent.findChildByFxyName(fxyName);
|
||||||
|
if (fld == null) {
|
||||||
|
log.info("BufrConfig cant find Child member fxy={} for file = {}", fxyName, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld == null) {
|
||||||
|
String name = jdom.getAttributeValue("name");
|
||||||
|
if (name != null) {
|
||||||
|
fld = parent.findChild(name);
|
||||||
|
if (fld == null) {
|
||||||
|
log.info("BufrConfig cant find Child member name={} for file = {}", name, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld == null) {
|
||||||
|
log.info("BufrConfig must have idx, name or fxy attribute = {} for file = {}", jdom, filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = jdom.getAttributeValue("action");
|
||||||
|
if (action != null && !action.isEmpty())
|
||||||
|
fld.setAction(action);
|
||||||
|
|
||||||
|
if (jdom.getChildren("fld") != null) {
|
||||||
|
for (Element child : jdom.getChildren("fld", Catalog.ncmlNS)) {
|
||||||
|
merge(child, fld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
private StandardFields.StandardFieldsFromStructure extract;
|
||||||
|
private boolean hasStations;
|
||||||
|
private boolean hasDate;
|
||||||
|
private int countObs;
|
||||||
|
|
||||||
|
private void scanBufrFile(RandomAccessFile raf) throws Exception {
|
||||||
|
NetcdfFile ncd = null;
|
||||||
|
countObs = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
MessageScanner scanner = new MessageScanner(raf);
|
||||||
|
Message protoMessage = scanner.getFirstDataMessage();
|
||||||
|
if (protoMessage == null)
|
||||||
|
throw new IOException("No message found!");
|
||||||
|
|
||||||
|
messHash = protoMessage.hashCode();
|
||||||
|
standardFields = StandardFields.extract(protoMessage);
|
||||||
|
rootConverter = new FieldConverter(protoMessage.ids.getCenterId(), protoMessage.getRootDataDescriptor());
|
||||||
|
|
||||||
|
if (standardFields.hasStation()) {
|
||||||
|
hasStations = true;
|
||||||
|
map = new HashMap<>(1000);
|
||||||
|
}
|
||||||
|
featureType = guessFeatureType(standardFields);
|
||||||
|
hasDate = standardFields.hasTime();
|
||||||
|
|
||||||
|
ncd = NetcdfFiles.open(raf.getLocation()); // LOOK opening another raf
|
||||||
|
Attribute centerAtt = ncd.findGlobalAttribute(BufrIosp2.centerId);
|
||||||
|
int center = (centerAtt == null) ? 0 : centerAtt.getNumericValue().intValue();
|
||||||
|
|
||||||
|
Sequence seq = (Sequence) ncd.getRootGroup().findVariableLocal(BufrIosp2.obsRecordName);
|
||||||
|
extract = new StandardFields.StandardFieldsFromStructure(center, seq);
|
||||||
|
|
||||||
|
StructureDataIterator iter = seq.getStructureIterator();
|
||||||
|
processSeq(iter, rootConverter, true);
|
||||||
|
|
||||||
|
setStandardActions(rootConverter);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (ncd != null)
|
||||||
|
ncd.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FeatureType guessFeatureType(StandardFields.StandardFieldsFromMessage standardFields) {
|
||||||
|
if (standardFields.hasStation())
|
||||||
|
return FeatureType.STATION;
|
||||||
|
if (standardFields.hasTime())
|
||||||
|
return FeatureType.POINT;
|
||||||
|
return FeatureType.ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStandardActions(FieldConverter fld) {
|
||||||
|
fld.setAction(fld.makeAction());
|
||||||
|
if (fld.flds == null)
|
||||||
|
return;
|
||||||
|
for (FieldConverter child : fld.flds)
|
||||||
|
setStandardActions(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private CalendarDate today = CalendarDate.present();
|
||||||
|
|
||||||
|
private void processSeq(StructureDataIterator sdataIter, FieldConverter parent, boolean isTop) throws IOException {
|
||||||
|
try {
|
||||||
|
while (sdataIter.hasNext()) {
|
||||||
|
StructureData sdata = sdataIter.next();
|
||||||
|
|
||||||
|
if (isTop) {
|
||||||
|
countObs++;
|
||||||
|
|
||||||
|
if (hasStations)
|
||||||
|
processStations(parent, sdata);
|
||||||
|
if (hasDate) {
|
||||||
|
extract.extract(sdata);
|
||||||
|
CalendarDate date = extract.makeCalendarDate();
|
||||||
|
if (Math.abs(date.getDifferenceInMsecs(today)) > 1000L * 3600 * 24 * 100) {
|
||||||
|
extract.makeCalendarDate();
|
||||||
|
}
|
||||||
|
long msecs = date.getMillis();
|
||||||
|
if (this.start > msecs) {
|
||||||
|
this.start = msecs;
|
||||||
|
}
|
||||||
|
if (this.end < msecs) {
|
||||||
|
this.end = msecs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (StructureMembers.Member m : sdata.getMembers()) {
|
||||||
|
if (m.getDataType() == DataType.SEQUENCE) {
|
||||||
|
FieldConverter fld = parent.getChild(count);
|
||||||
|
ArraySequence data = (ArraySequence) sdata.getArray(m);
|
||||||
|
int n = data.getStructureDataCount();
|
||||||
|
fld.trackSeqCounts(n);
|
||||||
|
processSeq(data.getStructureDataIterator(), fld, false);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sdataIter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processStations(FieldConverter parent, StructureData sdata) {
|
||||||
|
BufrStation station = new BufrStation();
|
||||||
|
station.read(parent, sdata);
|
||||||
|
|
||||||
|
BufrStation check = map.get(station.getName());
|
||||||
|
if (check == null)
|
||||||
|
map.put(station.getName(), station);
|
||||||
|
else {
|
||||||
|
check.count++;
|
||||||
|
if (!station.equals(check))
|
||||||
|
log.warn("bad station doesnt equal " + station + " != " + check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BufrStation extends StationImpl {
|
||||||
|
public int count = 1;
|
||||||
|
|
||||||
|
void read(FieldConverter parent, StructureData sdata) {
|
||||||
|
extract.extract(sdata);
|
||||||
|
|
||||||
|
setName(extract.getStationId());
|
||||||
|
setLatitude(extract.getFieldValueD(BufrCdmIndexProto.FldType.lat));
|
||||||
|
setLongitude(extract.getFieldValueD(BufrCdmIndexProto.FldType.lon));
|
||||||
|
if (extract.hasField(BufrCdmIndexProto.FldType.stationDesc))
|
||||||
|
setDescription(extract.getFieldValueS(BufrCdmIndexProto.FldType.stationDesc));
|
||||||
|
if (extract.hasField(BufrCdmIndexProto.FldType.wmoId))
|
||||||
|
setWmoId(extract.getFieldValueS(BufrCdmIndexProto.FldType.wmoId));
|
||||||
|
if (extract.hasField(BufrCdmIndexProto.FldType.heightOfStation))
|
||||||
|
setAltitude(extract.getFieldValueD(BufrCdmIndexProto.FldType.heightOfStation));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* void read(FieldConverter parent, StructureData sdata) {
|
||||||
|
* int count = 0;
|
||||||
|
* List<FieldConverter> flds = parent.getChildren(); // asssume these track exactly the members
|
||||||
|
* for (StructureMembers.Member m : sdata.getMembers()) {
|
||||||
|
* FieldConverter fld = flds.get(count++);
|
||||||
|
* if (fld.getType() == null) continue;
|
||||||
|
*
|
||||||
|
* switch (fld.getType()) {
|
||||||
|
* case stationId:
|
||||||
|
* setName( readString(sdata, m));
|
||||||
|
* break;
|
||||||
|
* case stationDesc:
|
||||||
|
* setDescription(sdata.getScalarString(m));
|
||||||
|
* break;
|
||||||
|
* case wmoId:
|
||||||
|
* setWmoId(readString(sdata, m));
|
||||||
|
* break;
|
||||||
|
* case lat:
|
||||||
|
* setLatitude(sdata.convertScalarDouble(m));
|
||||||
|
* break;
|
||||||
|
* case lon:
|
||||||
|
* setLongitude(sdata.convertScalarDouble(m));
|
||||||
|
* break;
|
||||||
|
* case height:
|
||||||
|
* setAltitude(sdata.convertScalarDouble(m));
|
||||||
|
* break;
|
||||||
|
* case heightOfStation:
|
||||||
|
* setAltitude(sdata.convertScalarDouble(m));
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* String readString(StructureData sdata, StructureMembers.Member m) {
|
||||||
|
* if (m.getDataType().isString())
|
||||||
|
* return sdata.getScalarString(m);
|
||||||
|
* else if (m.getDataType().isIntegral())
|
||||||
|
* return Integer.toString(sdata.convertScalarInt(m));
|
||||||
|
* else if (m.getDataType().isNumeric())
|
||||||
|
* return Double.toString(sdata.convertScalarDouble(m));
|
||||||
|
* else return "type "+ m.getDataType();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BufrStation that = (BufrStation) o;
|
||||||
|
|
||||||
|
if (Double.compare(that.alt, alt) != 0)
|
||||||
|
return false;
|
||||||
|
if (Double.compare(that.lat, lat) != 0)
|
||||||
|
return false;
|
||||||
|
if (Double.compare(that.lon, lon) != 0)
|
||||||
|
return false;
|
||||||
|
if (!Objects.equals(desc, that.desc))
|
||||||
|
return false;
|
||||||
|
if (!name.equals(that.name))
|
||||||
|
return false;
|
||||||
|
return Objects.equals(wmoId, that.wmoId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(lat);
|
||||||
|
result = (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(lon);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(alt);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
result = 31 * result + name.hashCode();
|
||||||
|
result = 31 * result + (desc != null ? desc.hashCode() : 0);
|
||||||
|
result = 31 * result + (wmoId != null ? wmoId.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public static class FieldConverter implements BufrField {
|
||||||
|
DataDescriptor dds;
|
||||||
|
List<FieldConverter> flds;
|
||||||
|
BufrCdmIndexProto.FldType type;
|
||||||
|
BufrCdmIndexProto.FldAction action;
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
int max;
|
||||||
|
boolean isSeq;
|
||||||
|
|
||||||
|
private FieldConverter(int center, DataDescriptor dds) {
|
||||||
|
this.dds = dds;
|
||||||
|
this.type = StandardFields.findField(center, dds.getFxyName());
|
||||||
|
|
||||||
|
if (dds.getSubKeys() != null) {
|
||||||
|
this.flds = new ArrayList<>(dds.getSubKeys().size());
|
||||||
|
for (DataDescriptor subdds : dds.getSubKeys()) {
|
||||||
|
FieldConverter subfld = new FieldConverter(center, subdds);
|
||||||
|
flds.add(subfld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return dds.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return dds.getDesc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnits() {
|
||||||
|
return dds.getUnits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getFxy() {
|
||||||
|
return dds.getFxy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFxyName() {
|
||||||
|
return dds.getFxyName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufrCdmIndexProto.FldAction getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufrCdmIndexProto.FldType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FieldConverter> getChildren() {
|
||||||
|
return flds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSeq() {
|
||||||
|
return isSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScale() {
|
||||||
|
return dds.getScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReference() {
|
||||||
|
return dds.getRefVal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitWidth() {
|
||||||
|
return dds.getBitWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(String action) {
|
||||||
|
try {
|
||||||
|
this.action = BufrCdmIndexProto.FldAction.valueOf(action);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Unknown action {}", action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(BufrCdmIndexProto.FldAction action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldConverter findChild(String want) {
|
||||||
|
for (FieldConverter child : flds) {
|
||||||
|
String name = child.dds.getName();
|
||||||
|
if (name != null && name.equals(want))
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldConverter findChildByFxyName(String fxyName) {
|
||||||
|
for (FieldConverter child : flds) {
|
||||||
|
String name = child.dds.getFxyName();
|
||||||
|
if (name != null && name.equals(fxyName))
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldConverter getChild(int i) {
|
||||||
|
return flds.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void trackSeqCounts(int n) {
|
||||||
|
isSeq = true;
|
||||||
|
if (n > max)
|
||||||
|
max = n;
|
||||||
|
if (n < min)
|
||||||
|
min = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showRange(Formatter f) {
|
||||||
|
if (!isSeq)
|
||||||
|
return;
|
||||||
|
if (max == min)
|
||||||
|
f.format(" isConstant='%d'", max);
|
||||||
|
else if (max < 2)
|
||||||
|
f.format(" isBinary='true'");
|
||||||
|
else
|
||||||
|
f.format(" range='[%d,%d]'", min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufrCdmIndexProto.FldAction makeAction() {
|
||||||
|
if (!isSeq)
|
||||||
|
return null;
|
||||||
|
if (max == 0)
|
||||||
|
return BufrCdmIndexProto.FldAction.remove;
|
||||||
|
if (max < 2)
|
||||||
|
return BufrCdmIndexProto.FldAction.asMissing;
|
||||||
|
else
|
||||||
|
return BufrCdmIndexProto.FldAction.asArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show(Formatter f, Indent indent, int index) {
|
||||||
|
boolean hasContent = false;
|
||||||
|
if (isSeq)
|
||||||
|
f.format("%s<fld idx='%d' name='%s'", indent, index, dds.getName());
|
||||||
|
else
|
||||||
|
f.format("%s<fld idx='%d' fxy='%s' name='%s' desc='%s' units='%s' bits='%d'", indent, index, dds.getFxyName(),
|
||||||
|
dds.getName(), dds.getDesc(), dds.getUnits(), dds.getBitWidth());
|
||||||
|
|
||||||
|
if (type != null)
|
||||||
|
f.format(" type='%s'", type);
|
||||||
|
showRange(f);
|
||||||
|
f.format(" action='%s'", makeAction());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if (type != null) {
|
||||||
|
* f.format(">%n");
|
||||||
|
* indent.incr();
|
||||||
|
* f.format("%s<type>%s</type>%n", indent, type);
|
||||||
|
* indent.decr();
|
||||||
|
* hasContent = true;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (flds != null) {
|
||||||
|
f.format(">%n");
|
||||||
|
indent.incr();
|
||||||
|
int subidx = 0;
|
||||||
|
for (FieldConverter cc : flds) {
|
||||||
|
cc.show(f, indent, subidx++);
|
||||||
|
}
|
||||||
|
indent.decr();
|
||||||
|
hasContent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasContent)
|
||||||
|
f.format("%s</fld>%n", indent);
|
||||||
|
else
|
||||||
|
f.format(" />%n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
public void show(Formatter out) {
|
||||||
|
if (standardFields != null)
|
||||||
|
out.format("Standard Fields%n%s%n%n", standardFields);
|
||||||
|
|
||||||
|
Indent indent = new Indent(2);
|
||||||
|
out.format("<bufr2nc location='%s' hash='%s' featureType='%s'>%n", filename, Integer.toHexString(messHash),
|
||||||
|
featureType);
|
||||||
|
indent.incr();
|
||||||
|
int index = 0;
|
||||||
|
for (FieldConverter fld : rootConverter.flds) {
|
||||||
|
fld.show(out, indent, index++);
|
||||||
|
}
|
||||||
|
indent.decr();
|
||||||
|
out.format("</bufr2nc>%n");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents Section 3 of a BUFR message.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since May 10, 2008
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class BufrDataDescriptionSection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset to start of BufrDataDescriptionSection.
|
||||||
|
*/
|
||||||
|
private final long offset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of data sets.
|
||||||
|
*/
|
||||||
|
private final int ndatasets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data type (observed or compressed).
|
||||||
|
*/
|
||||||
|
private final int datatype;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of data set descriptors.
|
||||||
|
*/
|
||||||
|
private final List<Short> descriptors = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a BufrDataDescriptionSection object by reading section 3 from a BUFR file.
|
||||||
|
*
|
||||||
|
* @param raf RandomAccessFile, position must be on a BUFR section 3
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public BufrDataDescriptionSection(RandomAccessFile raf) throws IOException {
|
||||||
|
offset = raf.getFilePointer();
|
||||||
|
int length = BufrNumbers.uint3(raf);
|
||||||
|
long EOS = offset + length;
|
||||||
|
|
||||||
|
// reserved byte
|
||||||
|
raf.read();
|
||||||
|
|
||||||
|
// octets 5-6 number of datasets
|
||||||
|
ndatasets = BufrNumbers.uint2(raf);
|
||||||
|
|
||||||
|
// octet 7 data type bit 2 is for compressed data 192 or 64,
|
||||||
|
// non-compressed data is 0 or 128
|
||||||
|
datatype = raf.read();
|
||||||
|
|
||||||
|
// get descriptors
|
||||||
|
int ndesc = (length - 7) / 2;
|
||||||
|
for (int i = 0; i < ndesc; i++) {
|
||||||
|
int ch1 = raf.read();
|
||||||
|
int ch2 = raf.read();
|
||||||
|
short fxy = (short) ((ch1 << 8) + (ch2));
|
||||||
|
descriptors.add(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset for any offset discrepancies
|
||||||
|
raf.seek(EOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset to the beginning of BufrDataDescriptionSection.
|
||||||
|
*
|
||||||
|
* @return offset in bytes of BUFR record
|
||||||
|
*/
|
||||||
|
public final long getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of data sets in this record.
|
||||||
|
*
|
||||||
|
* @return datasets
|
||||||
|
*/
|
||||||
|
public final int getNumberDatasets() {
|
||||||
|
return ndatasets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data type (compressed or non-compressed).
|
||||||
|
*
|
||||||
|
* @return datatype
|
||||||
|
*/
|
||||||
|
public final int getDataType() {
|
||||||
|
return datatype;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observation data
|
||||||
|
*
|
||||||
|
* @return true if observation data
|
||||||
|
*/
|
||||||
|
public boolean isObserved() {
|
||||||
|
return (datatype & 0x80) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is data compressed?
|
||||||
|
*
|
||||||
|
* @return true if data is compressed
|
||||||
|
*/
|
||||||
|
public boolean isCompressed() {
|
||||||
|
return (datatype & 0x40) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get list of data descriptors as Shorts
|
||||||
|
*
|
||||||
|
* @return descriptors as List<Short>
|
||||||
|
*/
|
||||||
|
public final List<Short> getDataDescriptors() {
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get list of data descriptors as Strings
|
||||||
|
*
|
||||||
|
* @return descriptors as List<String>
|
||||||
|
*/
|
||||||
|
public final List<String> getDescriptors() {
|
||||||
|
List<String> desc = new ArrayList<>();
|
||||||
|
for (short fxy : descriptors)
|
||||||
|
desc.add(Descriptor.makeString(fxy));
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,12 @@ package org.meteoinfo.data.meteodata.bufr;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
|
import ucar.nc2.Group;
|
||||||
|
import ucar.nc2.NetcdfFile;
|
||||||
|
import ucar.nc2.iosp.AbstractIOServiceProvider;
|
||||||
|
import ucar.nc2.iosp.IOServiceProvider;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
@ -15,11 +20,6 @@ import java.util.List;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.meteoinfo.common.DataConvert;
|
import org.meteoinfo.common.DataConvert;
|
||||||
import ucar.nc2.iosp.bufr.BufrTableLookup;
|
|
||||||
import ucar.nc2.iosp.bufr.DataDescriptor;
|
|
||||||
import ucar.nc2.iosp.bufr.Descriptor;
|
|
||||||
import ucar.nc2.iosp.bufr.Message;
|
|
||||||
import ucar.nc2.iosp.bufr.MessageScanner;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -40,6 +40,59 @@ public class BufrDataInfo {
|
|||||||
// <editor-fold desc="Get Set Methods">
|
// <editor-fold desc="Get Set Methods">
|
||||||
// </editor-fold>
|
// </editor-fold>
|
||||||
// <editor-fold desc="Methods">
|
// <editor-fold desc="Methods">
|
||||||
|
|
||||||
|
public static NetcdfFile open(String location) throws IOException {
|
||||||
|
IOServiceProvider spi = new BufrIosp2();
|
||||||
|
NetcdfFile ncFile = BufrDataInfo.build(spi, location);
|
||||||
|
spi.buildFinish(ncFile);
|
||||||
|
|
||||||
|
return ncFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetcdfFile build(IOServiceProvider spi, String location) throws IOException {
|
||||||
|
RandomAccessFile raf = new RandomAccessFile(location, "r");
|
||||||
|
NetcdfFile.Builder builder = NetcdfFile.builder().setIosp((AbstractIOServiceProvider) spi).setLocation(location);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Group.Builder root = Group.builder().setName("");
|
||||||
|
spi.build(raf, root, null);
|
||||||
|
builder.setRootGroup(root);
|
||||||
|
|
||||||
|
String id = root.getAttributeContainer().findAttributeString("_Id", null);
|
||||||
|
if (id != null) {
|
||||||
|
builder.setId(id);
|
||||||
|
}
|
||||||
|
String title = root.getAttributeContainer().findAttributeString("_Title", null);
|
||||||
|
if (title != null) {
|
||||||
|
builder.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
try {
|
||||||
|
raf.close();
|
||||||
|
} catch (Throwable t2) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
spi.close();
|
||||||
|
} catch (Throwable t1) {
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
try {
|
||||||
|
spi.close();
|
||||||
|
} catch (Throwable t1) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
raf.close();
|
||||||
|
} catch (Throwable t2) {
|
||||||
|
}
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read first message
|
* Read first message
|
||||||
*
|
*
|
||||||
@ -49,7 +102,7 @@ public class BufrDataInfo {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public Message readFirstMessage(String fileName) throws FileNotFoundException, IOException {
|
public Message readFirstMessage(String fileName) throws FileNotFoundException, IOException {
|
||||||
ucar.unidata.io.RandomAccessFile br = new ucar.unidata.io.RandomAccessFile(fileName, "r");
|
RandomAccessFile br = new RandomAccessFile(fileName, "r");
|
||||||
MessageScanner ms = new MessageScanner(br);
|
MessageScanner ms = new MessageScanner(br);
|
||||||
Message m = ms.getFirstDataMessage();
|
Message m = ms.getFirstDataMessage();
|
||||||
br.close();
|
br.close();
|
||||||
@ -63,7 +116,7 @@ public class BufrDataInfo {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public List<Message> readMessages(String fileName) throws IOException {
|
public List<Message> readMessages(String fileName) throws IOException {
|
||||||
ucar.unidata.io.RandomAccessFile br = new ucar.unidata.io.RandomAccessFile(fileName, "r");
|
RandomAccessFile br = new RandomAccessFile(fileName, "r");
|
||||||
MessageScanner ms = new MessageScanner(br);
|
MessageScanner ms = new MessageScanner(br);
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
while(ms.hasNext()) {
|
while(ms.hasNext()) {
|
||||||
@ -83,11 +136,13 @@ public class BufrDataInfo {
|
|||||||
bw = new RandomAccessFile(fileName, "rw");
|
bw = new RandomAccessFile(fileName, "rw");
|
||||||
} catch (FileNotFoundException ex) {
|
} catch (FileNotFoundException ex) {
|
||||||
Logger.getLogger(BufrDataInfo.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(BufrDataInfo.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the data file created by previos step
|
* Close the data file created by previous step
|
||||||
*/
|
*/
|
||||||
public void closeDataFile() {
|
public void closeDataFile() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents Section 4 of a BUFR message.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since May 10, 2008
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class BufrDataSection {
|
||||||
|
private final long dataPos;
|
||||||
|
private final int dataLength;
|
||||||
|
|
||||||
|
public BufrDataSection(long dataPos, int dataLength) {
|
||||||
|
this.dataPos = dataPos;
|
||||||
|
this.dataLength = dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDataPos() {
|
||||||
|
return dataPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDataLength() {
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing the IdentificationSection (section 1) of a BUFR record.
|
||||||
|
* Handles editions 2,3,4.
|
||||||
|
*
|
||||||
|
* @author Robb Kambic
|
||||||
|
* @author caron
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class BufrIdentificationSection {
|
||||||
|
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrIdentificationSection.class);
|
||||||
|
private static final boolean warnDate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master Table number.
|
||||||
|
*/
|
||||||
|
private final int master_table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identification of subcenter .
|
||||||
|
*/
|
||||||
|
private final int subcenter_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identification of center.
|
||||||
|
*/
|
||||||
|
private final int center_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Sequence Number.
|
||||||
|
*/
|
||||||
|
private final int update_sequence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional section exists.
|
||||||
|
*/
|
||||||
|
private final boolean hasOptionalSection;
|
||||||
|
private final int optionalSectionLen;
|
||||||
|
private final long optionalSectionPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data category.
|
||||||
|
*/
|
||||||
|
private final int category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data sub category.
|
||||||
|
*/
|
||||||
|
private final int subCategory;
|
||||||
|
|
||||||
|
private final int localSubCategory; // edition >= 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table Version numbers.
|
||||||
|
*/
|
||||||
|
private final int master_table_version;
|
||||||
|
private final int local_table_version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time of the obs (nominal)
|
||||||
|
*/
|
||||||
|
private final int year, month, day, hour, minute, second;
|
||||||
|
|
||||||
|
private final byte[] localUse;
|
||||||
|
|
||||||
|
// *** constructors *******************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a <tt>BufrIdentificationSection</tt> object from a raf.
|
||||||
|
*
|
||||||
|
* @param raf RandomAccessFile with Section 1 content
|
||||||
|
* @param is the BufrIndicatorSection, needed for the bufr edition number
|
||||||
|
* @throws IOException if raf contains no valid BUFR file
|
||||||
|
*/
|
||||||
|
public BufrIdentificationSection(RandomAccessFile raf, BufrIndicatorSection is) throws IOException {
|
||||||
|
|
||||||
|
// section 1 octet 1-3 (length of section)
|
||||||
|
int length = BufrNumbers.int3(raf);
|
||||||
|
|
||||||
|
// master table octet 4
|
||||||
|
master_table = raf.read();
|
||||||
|
|
||||||
|
if (is.getBufrEdition() < 4) {
|
||||||
|
if (length < 17)
|
||||||
|
throw new IOException("Invalid BUFR message on " + raf.getLocation());
|
||||||
|
|
||||||
|
if (is.getBufrEdition() == 2) {
|
||||||
|
subcenter_id = 255;
|
||||||
|
// Center octet 5-6
|
||||||
|
center_id = BufrNumbers.int2(raf);
|
||||||
|
|
||||||
|
} else { // edition 3
|
||||||
|
// Center octet 5
|
||||||
|
subcenter_id = raf.read();
|
||||||
|
// Center octet 6
|
||||||
|
center_id = raf.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sequence number octet 7
|
||||||
|
update_sequence = raf.read();
|
||||||
|
|
||||||
|
// Optional section octet 8
|
||||||
|
int optional = raf.read();
|
||||||
|
hasOptionalSection = (optional & 0x80) != 0;
|
||||||
|
|
||||||
|
// Category octet 9
|
||||||
|
category = raf.read();
|
||||||
|
|
||||||
|
// Category octet 10
|
||||||
|
subCategory = raf.read();
|
||||||
|
localSubCategory = -1; // not used
|
||||||
|
|
||||||
|
// master table version octet 11
|
||||||
|
master_table_version = raf.read();
|
||||||
|
|
||||||
|
// local table version octet 12
|
||||||
|
local_table_version = raf.read();
|
||||||
|
|
||||||
|
// octets 13-17 (reference time of forecast)
|
||||||
|
int lyear = raf.read();
|
||||||
|
if (lyear > 100)
|
||||||
|
lyear -= 100;
|
||||||
|
year = lyear + 2000;
|
||||||
|
int tempMonth = raf.read();
|
||||||
|
month = (tempMonth == 0) ? 1 : tempMonth; // joda time does not allow 0 month
|
||||||
|
int tempDay = raf.read();
|
||||||
|
day = (tempDay == 0) ? 1 : tempDay; // joda time does not allow 0 day
|
||||||
|
hour = raf.read();
|
||||||
|
minute = raf.read();
|
||||||
|
second = 0;
|
||||||
|
if (warnDate && (tempMonth == 0 || tempDay == 0)) {
|
||||||
|
// From manual on codes
|
||||||
|
// When accuracy of the time does not define a time unit, then the value for this unit shall be set to zero
|
||||||
|
// (e.g. for a
|
||||||
|
// SYNOP observation at 09 UTC, minute = 0, second = 0.
|
||||||
|
// NCEP codes their BUFR table messages with 0/0/0 0:0:0 in edition 3
|
||||||
|
log.warn(raf.getLocation() + ": month or day is zero, set to 1. {}/{}/{} {}:{}:{}", year, tempMonth, tempDay,
|
||||||
|
hour, minute, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = length - 17;
|
||||||
|
localUse = new byte[n];
|
||||||
|
int nRead = raf.read(localUse);
|
||||||
|
if (nRead != localUse.length)
|
||||||
|
throw new IOException("Error reading BUFR local use field.");
|
||||||
|
} else { // BUFR Edition 4 and above are slightly different
|
||||||
|
if (length < 22)
|
||||||
|
throw new IOException("Invalid BUFR message");
|
||||||
|
|
||||||
|
// Center octet 5 - 6
|
||||||
|
center_id = BufrNumbers.int2(raf);
|
||||||
|
|
||||||
|
// Sub Center octet 7-8
|
||||||
|
subcenter_id = BufrNumbers.int2(raf);
|
||||||
|
|
||||||
|
// Update sequence number octet 9
|
||||||
|
update_sequence = raf.read();
|
||||||
|
|
||||||
|
// Optional section octet 10
|
||||||
|
int optional = raf.read();
|
||||||
|
// Most Sig. Bit = 1 : has optional section
|
||||||
|
// 0 : does not have an optional section
|
||||||
|
hasOptionalSection = (optional & 0x80) != 0;
|
||||||
|
|
||||||
|
// Category octet 11
|
||||||
|
category = raf.read();
|
||||||
|
|
||||||
|
// International Sub Category octet 12
|
||||||
|
subCategory = raf.read();
|
||||||
|
|
||||||
|
// Local Sub Category Octet 13 - just read this for now
|
||||||
|
localSubCategory = raf.read();
|
||||||
|
|
||||||
|
// master table version octet 14
|
||||||
|
master_table_version = raf.read();
|
||||||
|
|
||||||
|
// local table version octet 15
|
||||||
|
local_table_version = raf.read();
|
||||||
|
// octets 16-22 (reference time of forecast)
|
||||||
|
|
||||||
|
// Octet 16-17 is the 4-digit year
|
||||||
|
year = BufrNumbers.int2(raf);
|
||||||
|
month = raf.read();
|
||||||
|
day = raf.read();
|
||||||
|
hour = raf.read();
|
||||||
|
minute = raf.read();
|
||||||
|
second = raf.read();
|
||||||
|
|
||||||
|
int n = length - 22;
|
||||||
|
localUse = new byte[n];
|
||||||
|
int nRead = raf.read(localUse);
|
||||||
|
if (nRead != localUse.length)
|
||||||
|
throw new IOException("Error reading BUFR local use field.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip optional section, but store position so can read if caller wants it
|
||||||
|
if (hasOptionalSection) {
|
||||||
|
int optionalLen = BufrNumbers.int3(raf);
|
||||||
|
if (optionalLen % 2 != 0)
|
||||||
|
optionalLen++;
|
||||||
|
optionalSectionLen = optionalLen - 4;
|
||||||
|
raf.skipBytes(1);
|
||||||
|
optionalSectionPos = raf.getFilePointer();
|
||||||
|
raf.skipBytes(optionalSectionLen);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
optionalSectionLen = -1;
|
||||||
|
optionalSectionPos = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identification of center.
|
||||||
|
*
|
||||||
|
* @return center id as int
|
||||||
|
*/
|
||||||
|
public final int getCenterId() {
|
||||||
|
return center_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identification of subcenter.
|
||||||
|
*
|
||||||
|
* @return subcenter as int
|
||||||
|
*/
|
||||||
|
public final int getSubCenterId() {
|
||||||
|
return subcenter_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get update sequence.
|
||||||
|
*
|
||||||
|
* @return update_sequence
|
||||||
|
*/
|
||||||
|
public final int getUpdateSequence() {
|
||||||
|
return update_sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return record header time as a CalendarDate
|
||||||
|
*
|
||||||
|
* @return referenceTime
|
||||||
|
*/
|
||||||
|
public final CalendarDate getReferenceTime() {
|
||||||
|
int sec = (second < 0 || second > 59) ? 0 : second;
|
||||||
|
return CalendarDate.of(null, year, month, day, hour, minute, sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getSubCategory() {
|
||||||
|
return subCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getLocalSubCategory() {
|
||||||
|
return localSubCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getMasterTableId() {
|
||||||
|
return master_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getMasterTableVersion() {
|
||||||
|
return master_table_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getLocalTableVersion() {
|
||||||
|
return local_table_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* last bytes of the id section are "reserved for local use by ADP centers.
|
||||||
|
*
|
||||||
|
* @return local use bytes, if any.
|
||||||
|
*/
|
||||||
|
public final byte[] getLocalUseBytes() {
|
||||||
|
return localUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final byte[] getOptiondsalSection(RandomAccessFile raf) throws IOException {
|
||||||
|
if (!hasOptionalSection)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] optionalSection = new byte[optionalSectionLen - 4];
|
||||||
|
raf.seek(optionalSectionPos);
|
||||||
|
int nRead = raf.read(optionalSection);
|
||||||
|
if (nRead != optionalSection.length)
|
||||||
|
log.warn("Error reading optional section -- expected " + optionalSection.length + " but read " + nRead);
|
||||||
|
return optionalSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing the IndicatorSection (section 0) of a BUFR record.
|
||||||
|
* Handles editions 2,3,4.
|
||||||
|
*
|
||||||
|
* @author Robb Kambic
|
||||||
|
* @author caron
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class BufrIndicatorSection {
|
||||||
|
private final long startPos;
|
||||||
|
private final int bufrLength; // Length in bytes of BUFR record.
|
||||||
|
private final int edition;
|
||||||
|
|
||||||
|
// *** constructors *******************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a <tt>BufrIndicatorSection</tt> object from a raf.
|
||||||
|
*
|
||||||
|
* @param raf RandomAccessFile with IndicatorSection content
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public BufrIndicatorSection(RandomAccessFile raf) throws IOException {
|
||||||
|
this.startPos = raf.getFilePointer() - 4; // start of BUFR message, including "BUFR"
|
||||||
|
bufrLength = BufrNumbers.uint3(raf);
|
||||||
|
edition = raf.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the byte length of this BUFR record.
|
||||||
|
*
|
||||||
|
* @return length in bytes of BUFR record
|
||||||
|
*/
|
||||||
|
public final int getBufrLength() {
|
||||||
|
return bufrLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the edition of the BUFR specification used.
|
||||||
|
*
|
||||||
|
* @return edition number of BUFR specification
|
||||||
|
*/
|
||||||
|
public final int getBufrEdition() {
|
||||||
|
return edition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get starting position in the file. This should point to the "BUFR" chars .
|
||||||
|
*
|
||||||
|
* @return byte offset in file of start of BUFR meessage.
|
||||||
|
*/
|
||||||
|
public final long getStartPos() {
|
||||||
|
return startPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2020 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.*;
|
||||||
|
import ucar.nc2.constants.DataFormatType;
|
||||||
|
import ucar.nc2.iosp.AbstractIOServiceProvider;
|
||||||
|
import ucar.nc2.util.CancelTask;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IOSP for BUFR data - version 2, using the preprocessor.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/8/13
|
||||||
|
*/
|
||||||
|
public class BufrIosp2 extends AbstractIOServiceProvider {
|
||||||
|
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrIosp2.class);
|
||||||
|
|
||||||
|
public static final String obsRecordName = "obs";
|
||||||
|
public static final String fxyAttName = "BUFR:TableB_descriptor";
|
||||||
|
public static final String centerId = "BUFR:centerId";
|
||||||
|
|
||||||
|
// debugging
|
||||||
|
private static boolean debugIter;
|
||||||
|
|
||||||
|
public static void setDebugFlags(ucar.nc2.util.DebugFlags debugFlag) {
|
||||||
|
debugIter = debugFlag.isSet("Bufr/iter");
|
||||||
|
}
|
||||||
|
|
||||||
|
//private Structure obsStructure;
|
||||||
|
//private Message protoMessage; // prototypical message: all messages in the file must be the same.
|
||||||
|
private MessageScanner scanner;
|
||||||
|
private List<Message> protoMessages; // prototypical messages: the messages with different category.
|
||||||
|
private List<RootVariable> rootVariables;
|
||||||
|
private HashSet<Integer> messHash;
|
||||||
|
private boolean isSingle;
|
||||||
|
private BufrConfig config;
|
||||||
|
private Element iospParam;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidFile(RandomAccessFile raf) throws IOException {
|
||||||
|
return MessageScanner.isValidFile(raf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBuilder() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void build(String fileName) throws IOException {
|
||||||
|
RandomAccessFile raf = new RandomAccessFile(fileName, "r");
|
||||||
|
Group.Builder rootGroup = new Group.Builder().setName("");
|
||||||
|
|
||||||
|
this.build(raf, rootGroup, null);
|
||||||
|
System.out.println(rootGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(RandomAccessFile raf, Group.Builder rootGroup, CancelTask cancelTask) throws IOException {
|
||||||
|
super.open(raf, rootGroup.getNcfile(), cancelTask);
|
||||||
|
|
||||||
|
scanner = new MessageScanner(raf);
|
||||||
|
Message protoMessage = scanner.getFirstDataMessage();
|
||||||
|
if (protoMessage == null)
|
||||||
|
throw new IOException("No data messages in the file= " + raf.getLocation());
|
||||||
|
if (!protoMessage.isTablesComplete())
|
||||||
|
throw new IllegalStateException("BUFR file has incomplete tables");
|
||||||
|
|
||||||
|
// get all prototype messages - contains different message category in a Bufr data file
|
||||||
|
protoMessages = new ArrayList<>();
|
||||||
|
protoMessages.add(protoMessage);
|
||||||
|
int category = protoMessage.ids.getCategory();
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
Message message = scanner.next();
|
||||||
|
if (message.ids.getCategory() != category) {
|
||||||
|
protoMessages.add(message);
|
||||||
|
category = message.ids.getCategory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just get the fields
|
||||||
|
BufrConfig config = BufrConfig.openFromMessage(raf, protoMessage, iospParam);
|
||||||
|
|
||||||
|
// this fills the netcdf object
|
||||||
|
if (this.protoMessages.size() == 1) {
|
||||||
|
new BufrIospBuilder(protoMessage, config, rootGroup, raf.getLocation());
|
||||||
|
} else {
|
||||||
|
List<BufrConfig> configs = new ArrayList<>();
|
||||||
|
for (Message message : protoMessages) {
|
||||||
|
configs.add(BufrConfig.openFromMessage(raf, message, iospParam));
|
||||||
|
}
|
||||||
|
new BufrIospBuilder(protoMessage, configs, rootGroup, raf.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
isSingle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildFinish(NetcdfFile ncfile) {
|
||||||
|
// support multiple root variables in one Bufr data file
|
||||||
|
this.rootVariables = new ArrayList<>();
|
||||||
|
if (this.protoMessages.size() == 1) {
|
||||||
|
Structure obsStructure = (Structure) ncfile.findVariable(obsRecordName);
|
||||||
|
// The proto DataDescriptor must have a link to the Sequence object to read nested Sequences.
|
||||||
|
connectSequences(obsStructure.getVariables(), protoMessages.get(0).getRootDataDescriptor().getSubKeys());
|
||||||
|
this.rootVariables.add(new RootVariable(protoMessages.get(0), obsStructure));
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < this.protoMessages.size(); i++) {
|
||||||
|
Structure variable = (Structure) ncfile.getVariables().get(i);
|
||||||
|
Message message = protoMessages.get(i);
|
||||||
|
connectSequences(variable.getVariables(), message.getRootDataDescriptor().getSubKeys());
|
||||||
|
this.rootVariables.add(new RootVariable(message, variable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectSequences(List<Variable> variables, List<DataDescriptor> dataDescriptors) {
|
||||||
|
for (Variable v : variables) {
|
||||||
|
if (v instanceof Sequence) {
|
||||||
|
findDataDescriptor(dataDescriptors, v.getShortName()).ifPresent(dds -> dds.refersTo = (Sequence) v);
|
||||||
|
}
|
||||||
|
if (v instanceof Structure) { // recurse
|
||||||
|
findDataDescriptor(dataDescriptors, v.getShortName())
|
||||||
|
.ifPresent(dds -> connectSequences(((Structure) v).getVariables(), dds.getSubKeys()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<DataDescriptor> findDataDescriptor(List<DataDescriptor> dataDescriptors, String name) {
|
||||||
|
Optional<DataDescriptor> ddsOpt = dataDescriptors.stream().filter(d -> name.equals(d.name)).findFirst();
|
||||||
|
if (ddsOpt.isPresent()) {
|
||||||
|
return ddsOpt;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("DataDescriptor does not contain " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
|
||||||
|
super.open(raf, ncfile, cancelTask);
|
||||||
|
|
||||||
|
scanner = new MessageScanner(raf);
|
||||||
|
Message protoMessage = scanner.getFirstDataMessage();
|
||||||
|
if (protoMessage == null)
|
||||||
|
throw new IOException("No data messages in the file= " + ncfile.getLocation());
|
||||||
|
if (!protoMessage.isTablesComplete())
|
||||||
|
throw new IllegalStateException("BUFR file has incomplete tables");
|
||||||
|
|
||||||
|
// just get the fields
|
||||||
|
BufrConfig config = BufrConfig.openFromMessage(raf, protoMessage, iospParam);
|
||||||
|
|
||||||
|
// this fills the netcdf object
|
||||||
|
Construct2 construct = new Construct2(protoMessage, config, ncfile);
|
||||||
|
Structure obsStructure = construct.getObsStructure();
|
||||||
|
ncfile.finish();
|
||||||
|
isSingle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for BufrMessageViewer
|
||||||
|
public void open(RandomAccessFile raf, NetcdfFile ncfile, Message single) throws IOException {
|
||||||
|
this.raf = raf;
|
||||||
|
|
||||||
|
Message protoMessage = single;
|
||||||
|
protoMessage.getRootDataDescriptor(); // construct the data descriptors, check for complete tables
|
||||||
|
if (!protoMessage.isTablesComplete())
|
||||||
|
throw new IllegalStateException("BUFR file has incomplete tables");
|
||||||
|
|
||||||
|
BufrConfig config = BufrConfig.openFromMessage(raf, protoMessage, null);
|
||||||
|
|
||||||
|
// this fills the netcdf object
|
||||||
|
Construct2 construct = new Construct2(protoMessage, config, ncfile);
|
||||||
|
Structure obsStructure = construct.getObsStructure();
|
||||||
|
isSingle = true;
|
||||||
|
|
||||||
|
ncfile.finish();
|
||||||
|
this.ncfile = ncfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object sendIospMessage(Object message) {
|
||||||
|
if (message instanceof Element) {
|
||||||
|
iospParam = (Element) message;
|
||||||
|
iospParam.detach();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.sendIospMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public BufrConfig getConfig() {
|
||||||
|
return config;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public Element getElem() {
|
||||||
|
return iospParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nelems = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Array readData(Variable v2, Section section) {
|
||||||
|
RootVariable rootVariable = findRootSequence(v2);
|
||||||
|
Structure obsStructure = rootVariable.getVariable();
|
||||||
|
return new ArraySequence(obsStructure.makeStructureMembers(), new SeqIter(rootVariable), nelems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureDataIterator getStructureIterator(Structure s, int bufferSize) {
|
||||||
|
RootVariable rootVariable = findRootSequence(s);
|
||||||
|
return isSingle ? new SeqIterSingle(rootVariable) : new SeqIter(rootVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Structure findRootSequence() {
|
||||||
|
return (Structure) this.ncfile.findVariable(BufrIosp2.obsRecordName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find root sequence from root variable list
|
||||||
|
private RootVariable findRootSequence(Variable var) {
|
||||||
|
for (RootVariable rootVariable : this.rootVariables) {
|
||||||
|
if (rootVariable.getVariable().getShortName().equals(var.getShortName())) {
|
||||||
|
return rootVariable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// root variable contains prototype message and corresponding variable
|
||||||
|
private class RootVariable {
|
||||||
|
private Message protoMessage;
|
||||||
|
private Structure variable;
|
||||||
|
|
||||||
|
public RootVariable(Message message, Structure variable) {
|
||||||
|
this.protoMessage = message;
|
||||||
|
this.variable = variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message getProtoMessage() {
|
||||||
|
return this.protoMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Structure getVariable() {
|
||||||
|
return this.variable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SeqIter implements StructureDataIterator {
|
||||||
|
StructureDataIterator currIter;
|
||||||
|
int recnum;
|
||||||
|
// add its own prototype message and observation structure
|
||||||
|
Message protoMessage;
|
||||||
|
Structure obsStructure;
|
||||||
|
|
||||||
|
SeqIter(Message message, Structure structure) {
|
||||||
|
this.protoMessage = message;
|
||||||
|
this.obsStructure = structure;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
SeqIter(RootVariable rootVariable) {
|
||||||
|
this(rootVariable.protoMessage, rootVariable.variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureDataIterator reset() {
|
||||||
|
recnum = 0;
|
||||||
|
currIter = null;
|
||||||
|
scanner.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() throws IOException {
|
||||||
|
if (currIter == null) {
|
||||||
|
currIter = readNextMessage();
|
||||||
|
if (currIter == null) {
|
||||||
|
nelems = recnum;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currIter.hasNext()) {
|
||||||
|
currIter = readNextMessage();
|
||||||
|
return hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureData next() throws IOException {
|
||||||
|
recnum++;
|
||||||
|
return currIter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StructureDataIterator readNextMessage() throws IOException {
|
||||||
|
if (!scanner.hasNext())
|
||||||
|
return null;
|
||||||
|
Message m = scanner.next();
|
||||||
|
if (m == null) {
|
||||||
|
log.warn("BUFR scanner hasNext() true but next() null!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (m.containsBufrTable()) // data messages only
|
||||||
|
return readNextMessage();
|
||||||
|
|
||||||
|
// mixed messages
|
||||||
|
if (!protoMessage.equals(m)) {
|
||||||
|
if (messHash == null)
|
||||||
|
messHash = new HashSet<>(20);
|
||||||
|
if (!messHash.contains(m.hashCode())) {
|
||||||
|
log.warn("File " + raf.getLocation() + " has different BUFR message types hash=" + protoMessage.hashCode()
|
||||||
|
+ "; skipping");
|
||||||
|
messHash.add(m.hashCode());
|
||||||
|
}
|
||||||
|
return readNextMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayStructure as = readMessage(m);
|
||||||
|
return as.getStructureDataIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayStructure readMessage(Message m) throws IOException {
|
||||||
|
ArrayStructure as;
|
||||||
|
if (m.dds.isCompressed()) {
|
||||||
|
MessageCompressedDataReader reader = new MessageCompressedDataReader();
|
||||||
|
as = reader.readEntireMessage(obsStructure, protoMessage, m, raf, null);
|
||||||
|
} else {
|
||||||
|
MessageUncompressedDataReader reader = new MessageUncompressedDataReader();
|
||||||
|
as = reader.readEntireMessage(obsStructure, protoMessage, m, raf, null);
|
||||||
|
}
|
||||||
|
return as;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentRecno() {
|
||||||
|
return recnum - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (currIter != null)
|
||||||
|
currIter.close();
|
||||||
|
currIter = null;
|
||||||
|
if (debugIter)
|
||||||
|
System.out.printf("BUFR read recnum %d%n", recnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SeqIterSingle implements StructureDataIterator {
|
||||||
|
StructureDataIterator currIter;
|
||||||
|
int recnum;
|
||||||
|
// add its own prototype message and observation structure
|
||||||
|
Message protoMessage;
|
||||||
|
Structure obsStructure;
|
||||||
|
|
||||||
|
SeqIterSingle(Message message, Structure structure) {
|
||||||
|
protoMessage = message;
|
||||||
|
obsStructure = structure;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
SeqIterSingle(RootVariable rootVariable) {
|
||||||
|
this(rootVariable.protoMessage, rootVariable.variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureDataIterator reset() {
|
||||||
|
recnum = 0;
|
||||||
|
currIter = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() throws IOException {
|
||||||
|
if (currIter == null) {
|
||||||
|
currIter = readProtoMessage();
|
||||||
|
if (currIter == null) {
|
||||||
|
nelems = recnum;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currIter.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureData next() throws IOException {
|
||||||
|
recnum++;
|
||||||
|
return currIter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StructureDataIterator readProtoMessage() throws IOException {
|
||||||
|
Message m = protoMessage;
|
||||||
|
ArrayStructure as;
|
||||||
|
if (m.dds.isCompressed()) {
|
||||||
|
MessageCompressedDataReader reader = new MessageCompressedDataReader();
|
||||||
|
as = reader.readEntireMessage(obsStructure, protoMessage, m, raf, null);
|
||||||
|
} else {
|
||||||
|
MessageUncompressedDataReader reader = new MessageUncompressedDataReader();
|
||||||
|
as = reader.readEntireMessage(obsStructure, protoMessage, m, raf, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return as.getStructureDataIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentRecno() {
|
||||||
|
return recnum - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (currIter != null)
|
||||||
|
currIter.close();
|
||||||
|
currIter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDetailInfo() {
|
||||||
|
Formatter ff = new Formatter();
|
||||||
|
ff.format("%s", super.getDetailInfo());
|
||||||
|
protoMessages.get(0).dump(ff);
|
||||||
|
ff.format("%n");
|
||||||
|
config.show(ff);
|
||||||
|
return ff.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFileTypeId() {
|
||||||
|
return DataFormatType.BUFR.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFileTypeDescription() {
|
||||||
|
return "WMO Binary Universal Form";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,467 @@
|
|||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import ucar.ma2.DataType;
|
||||||
|
import ucar.nc2.*;
|
||||||
|
import ucar.nc2.constants.AxisType;
|
||||||
|
import ucar.nc2.constants.CDM;
|
||||||
|
import ucar.nc2.constants.CF;
|
||||||
|
import ucar.nc2.constants._Coordinate;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.CodeFlagTables;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construction of the Netcdf objects using builders.
|
||||||
|
*/
|
||||||
|
class BufrIospBuilder {
|
||||||
|
private static Logger log = org.slf4j.LoggerFactory.getLogger(BufrIospBuilder.class);
|
||||||
|
private static final boolean warnUnits = false;
|
||||||
|
|
||||||
|
private final Group.Builder rootGroup;
|
||||||
|
private Sequence.Builder recordStructure;
|
||||||
|
private final Formatter coordinates = new Formatter();
|
||||||
|
|
||||||
|
private int tempNo = 1; // fishy
|
||||||
|
|
||||||
|
BufrIospBuilder(Message proto, BufrConfig bufrConfig, Group.Builder root, String location) {
|
||||||
|
this.rootGroup = root;
|
||||||
|
this.recordStructure = Sequence.builder().setName(BufrIosp2.obsRecordName);
|
||||||
|
this.rootGroup.addVariable(recordStructure);
|
||||||
|
|
||||||
|
// global Attributes
|
||||||
|
AttributeContainerMutable atts = root.getAttributeContainer();
|
||||||
|
atts.addAttribute(CDM.HISTORY, "Read using CDM BufrIosp2");
|
||||||
|
if (bufrConfig.getFeatureType() != null) {
|
||||||
|
atts.addAttribute(CF.FEATURE_TYPE, bufrConfig.getFeatureType().toString());
|
||||||
|
}
|
||||||
|
atts.addAttribute("location", location);
|
||||||
|
|
||||||
|
atts.addAttribute("BUFR:categoryName", proto.getLookup().getCategoryName());
|
||||||
|
atts.addAttribute("BUFR:subCategoryName", proto.getLookup().getSubCategoryName());
|
||||||
|
atts.addAttribute("BUFR:centerName", proto.getLookup().getCenterName());
|
||||||
|
atts.addAttribute("BUFR:category", proto.ids.getCategory());
|
||||||
|
atts.addAttribute("BUFR:subCategory", proto.ids.getSubCategory());
|
||||||
|
atts.addAttribute("BUFR:localSubCategory", proto.ids.getLocalSubCategory());
|
||||||
|
atts.addAttribute(BufrIosp2.centerId, proto.ids.getCenterId());
|
||||||
|
atts.addAttribute("BUFR:subCenter", proto.ids.getSubCenterId());
|
||||||
|
atts.addAttribute("BUFR:table", proto.ids.getMasterTableId());
|
||||||
|
atts.addAttribute("BUFR:tableVersion", proto.ids.getMasterTableVersion());
|
||||||
|
atts.addAttribute("BUFR:localTableVersion", proto.ids.getLocalTableVersion());
|
||||||
|
atts.addAttribute("Conventions", "BUFR/CDM");
|
||||||
|
atts.addAttribute("BUFR:edition", proto.is.getBufrEdition());
|
||||||
|
|
||||||
|
|
||||||
|
String header = proto.getHeader();
|
||||||
|
if (header != null && !header.isEmpty()) {
|
||||||
|
atts.addAttribute("WMO Header", header);
|
||||||
|
}
|
||||||
|
|
||||||
|
makeObsRecord(bufrConfig);
|
||||||
|
String coordS = coordinates.toString();
|
||||||
|
if (!coordS.isEmpty()) {
|
||||||
|
recordStructure.addAttribute(new Attribute("coordinates", coordS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BufrIospBuilder(Message proto, List<BufrConfig> bufrConfigs, Group.Builder root, String location) {
|
||||||
|
this.rootGroup = root;
|
||||||
|
|
||||||
|
// global Attributes
|
||||||
|
AttributeContainerMutable atts = root.getAttributeContainer();
|
||||||
|
atts.addAttribute(CDM.HISTORY, "Read using CDM BufrIosp2");
|
||||||
|
atts.addAttribute("location", location);
|
||||||
|
|
||||||
|
atts.addAttribute("BUFR:categoryName", proto.getLookup().getCategoryName());
|
||||||
|
atts.addAttribute("BUFR:subCategoryName", proto.getLookup().getSubCategoryName());
|
||||||
|
atts.addAttribute("BUFR:centerName", proto.getLookup().getCenterName());
|
||||||
|
atts.addAttribute(BufrIosp2.centerId, proto.ids.getCenterId());
|
||||||
|
atts.addAttribute("BUFR:subCenter", proto.ids.getSubCenterId());
|
||||||
|
atts.addAttribute("BUFR:table", proto.ids.getMasterTableId());
|
||||||
|
atts.addAttribute("BUFR:tableVersion", proto.ids.getMasterTableVersion());
|
||||||
|
atts.addAttribute("BUFR:localTableVersion", proto.ids.getLocalTableVersion());
|
||||||
|
atts.addAttribute("Conventions", "BUFR/CDM");
|
||||||
|
atts.addAttribute("BUFR:edition", proto.is.getBufrEdition());
|
||||||
|
|
||||||
|
String header = proto.getHeader();
|
||||||
|
if (header != null && !header.isEmpty()) {
|
||||||
|
atts.addAttribute("WMO Header", header);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BufrConfig bufrConfig : bufrConfigs) {
|
||||||
|
String varName = proto.getLookup().getCategoryName(bufrConfig.getMessage().ids.getCategory());
|
||||||
|
Sequence.Builder rs = Sequence.builder().setName(varName);
|
||||||
|
this.rootGroup.addVariable(rs);
|
||||||
|
makeObsRecord(bufrConfig, rs);
|
||||||
|
String coordS = coordinates.toString();
|
||||||
|
if (!coordS.isEmpty()) {
|
||||||
|
rs.addAttribute(new Attribute("coordinates", coordS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sequence.Builder getObsStructure() {
|
||||||
|
return recordStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeObsRecord(BufrConfig bufrConfig) {
|
||||||
|
BufrConfig.FieldConverter root = bufrConfig.getRootConverter();
|
||||||
|
for (BufrConfig.FieldConverter fld : root.flds) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
if (!dkey.isOkForVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
addSequence(rootGroup, recordStructure, fld);
|
||||||
|
|
||||||
|
} else if (dkey.replication > 1) {
|
||||||
|
|
||||||
|
List<BufrConfig.FieldConverter> subFlds = fld.flds;
|
||||||
|
List<DataDescriptor> subKeys = dkey.subKeys;
|
||||||
|
if (subKeys.size() == 1) { // only one member
|
||||||
|
DataDescriptor subDds = dkey.subKeys.get(0);
|
||||||
|
BufrConfig.FieldConverter subFld = subFlds.get(0);
|
||||||
|
if (subDds.dpi != null) {
|
||||||
|
addDpiStructure(recordStructure, fld, subFld);
|
||||||
|
|
||||||
|
} else if (subDds.replication == 1) { // one member not a replication
|
||||||
|
Variable.Builder v = addVariable(rootGroup, recordStructure, subFld, dkey.replication);
|
||||||
|
v.setSPobject(fld); // set the replicating field as SPI object
|
||||||
|
|
||||||
|
} else { // one member is a replication (two replications in a row)
|
||||||
|
addStructure(rootGroup, recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
} else if (subKeys.size() > 1) {
|
||||||
|
addStructure(rootGroup, recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // replication == 1
|
||||||
|
addVariable(rootGroup, recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeObsRecord(BufrConfig bufrConfig, Sequence.Builder rs) {
|
||||||
|
BufrConfig.FieldConverter root = bufrConfig.getRootConverter();
|
||||||
|
for (BufrConfig.FieldConverter fld : root.flds) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
if (!dkey.isOkForVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
addSequence(rootGroup, rs, fld);
|
||||||
|
|
||||||
|
} else if (dkey.replication > 1) {
|
||||||
|
|
||||||
|
List<BufrConfig.FieldConverter> subFlds = fld.flds;
|
||||||
|
List<DataDescriptor> subKeys = dkey.subKeys;
|
||||||
|
if (subKeys.size() == 1) { // only one member
|
||||||
|
DataDescriptor subDds = dkey.subKeys.get(0);
|
||||||
|
BufrConfig.FieldConverter subFld = subFlds.get(0);
|
||||||
|
if (subDds.dpi != null) {
|
||||||
|
addDpiStructure(rs, fld, subFld);
|
||||||
|
|
||||||
|
} else if (subDds.replication == 1) { // one member not a replication
|
||||||
|
Variable.Builder v = addVariable(rootGroup, rs, subFld, dkey.replication);
|
||||||
|
v.setSPobject(fld); // set the replicating field as SPI object
|
||||||
|
|
||||||
|
} else { // one member is a replication (two replications in a row)
|
||||||
|
addStructure(rootGroup, rs, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
} else if (subKeys.size() > 1) {
|
||||||
|
addStructure(rootGroup, rs, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // replication == 1
|
||||||
|
addVariable(rootGroup, rs, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStructure(Group.Builder group, Structure.Builder parent, BufrConfig.FieldConverter fld, int count) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findUniqueName(parent, fld.getName(), "struct");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
Structure.Builder struct = Structure.builder().setName(uname);
|
||||||
|
struct.setDimensionsAnonymous(new int[]{count}); // anon vector
|
||||||
|
for (BufrConfig.FieldConverter subKey : fld.flds) {
|
||||||
|
addMember(group, struct, subKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
struct.setSPobject(fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSequence(Group.Builder group, Structure.Builder parent, BufrConfig.FieldConverter fld) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findUniqueName(parent, fld.getName(), "seq");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
Sequence.Builder seq = Sequence.builder().setName(uname);
|
||||||
|
for (BufrConfig.FieldConverter subKey : fld.flds) {
|
||||||
|
addMember(group, seq, subKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.addMemberVariable(seq);
|
||||||
|
seq.setSPobject(fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMember(Group.Builder group, Structure.Builder parent, BufrConfig.FieldConverter fld) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
addSequence(group, parent, fld);
|
||||||
|
} else if (dkey.replication > 1) {
|
||||||
|
List<DataDescriptor> subKeys = dkey.subKeys;
|
||||||
|
if (subKeys.size() == 1) {
|
||||||
|
BufrConfig.FieldConverter subFld = fld.flds.get(0);
|
||||||
|
Variable.Builder v = addVariable(group, parent, subFld, dkey.replication);
|
||||||
|
v.setSPobject(fld); // set the replicating field as SPI object
|
||||||
|
|
||||||
|
} else {
|
||||||
|
addStructure(group, parent, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
addVariable(group, parent, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDpiStructure(Structure.Builder parent, BufrConfig.FieldConverter parentFld,
|
||||||
|
BufrConfig.FieldConverter dpiField) {
|
||||||
|
DataDescriptor dpiKey = dpiField.dds;
|
||||||
|
String uname = findUniqueName(parent, dpiField.getName(), "struct");
|
||||||
|
dpiKey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
Structure.Builder struct = Structure.builder().setName(uname);
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
int n = parentFld.dds.replication;
|
||||||
|
struct.setDimensionsAnonymous(new int[]{n}); // anon vector
|
||||||
|
|
||||||
|
Variable.Builder v = Variable.builder().setName("name");
|
||||||
|
v.setDataType(DataType.STRING); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v = Variable.builder().setName("data");
|
||||||
|
v.setDataType(DataType.FLOAT); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
struct.setSPobject(dpiField); // ??
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDpiSequence(Structure.Builder parent, BufrConfig.FieldConverter fld) {
|
||||||
|
Structure.Builder struct = Structure.builder().setName("statistics");
|
||||||
|
struct.setDimensionsAnonymous(new int[]{fld.dds.replication}); // scalar
|
||||||
|
|
||||||
|
Variable.Builder v = Variable.builder().setName("name");
|
||||||
|
v.setDataType(DataType.STRING); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v = Variable.builder().setName("data");
|
||||||
|
v.setDataType(DataType.FLOAT); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable.Builder addVariable(Group.Builder group, Structure.Builder struct, BufrConfig.FieldConverter fld,
|
||||||
|
int count) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findGloballyUniqueName(fld.getName(), "unknown");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
Variable.Builder v = Variable.builder().setName(uname);
|
||||||
|
if (count > 1) {
|
||||||
|
v.setDimensionsAnonymous(new int[]{count}); // anon vector
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld.getDesc() != null) {
|
||||||
|
v.addAttribute(new Attribute(CDM.LONG_NAME, fld.getDesc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld.getUnits() == null) {
|
||||||
|
if (warnUnits) {
|
||||||
|
log.warn("dataDesc.units == null for " + uname);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String units = fld.getUnits();
|
||||||
|
if (ucar.nc2.iosp.bufr.DataDescriptor.isCodeTableUnit(units)) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, "CodeTable " + fld.dds.getFxyName()));
|
||||||
|
} else if (ucar.nc2.iosp.bufr.DataDescriptor.isFlagTableUnit(units)) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, "FlagTable " + fld.dds.getFxyName()));
|
||||||
|
} else if (!ucar.nc2.iosp.bufr.DataDescriptor.isInternationalAlphabetUnit(units) && !units.startsWith("Numeric")) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, units));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataDescriptor dataDesc = fld.dds;
|
||||||
|
if (dataDesc.type == 1) {
|
||||||
|
v.setDataType(DataType.CHAR);
|
||||||
|
int size = dataDesc.bitWidth / 8;
|
||||||
|
v.setDimensionsAnonymous(new int[]{size});
|
||||||
|
|
||||||
|
} else if ((dataDesc.type == 2) && CodeFlagTables.hasTable(dataDesc.fxy)) { // enum
|
||||||
|
int nbits = dataDesc.bitWidth;
|
||||||
|
int nbytes = (nbits % 8 == 0) ? nbits / 8 : nbits / 8 + 1;
|
||||||
|
|
||||||
|
CodeFlagTables ct = CodeFlagTables.getTable(dataDesc.fxy);
|
||||||
|
if (nbytes == 1) {
|
||||||
|
v.setDataType(DataType.ENUM1);
|
||||||
|
} else if (nbytes == 2) {
|
||||||
|
v.setDataType(DataType.ENUM2);
|
||||||
|
} else if (nbytes == 4) {
|
||||||
|
v.setDataType(DataType.ENUM4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v.removeAttribute(CDM.UNITS);
|
||||||
|
v.addAttribute(new Attribute("BUFR:CodeTable", ct.getName() + " (" + dataDesc.getFxyName() + ")"));
|
||||||
|
|
||||||
|
EnumTypedef type = group.findOrAddEnumTypedef(ct.getName(), ct.getMap());
|
||||||
|
v.setEnumTypeName(type.getShortName());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int nbits = dataDesc.bitWidth;
|
||||||
|
// use of unsigned seems fishy, since only time it uses high bit is for missing
|
||||||
|
// not necessarily true, just when they "add one bit" to deal with missing case
|
||||||
|
if (nbits < 9) {
|
||||||
|
v.setDataType(DataType.BYTE);
|
||||||
|
if (nbits == 8) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else {
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (byte) BufrNumbers.missingValue(nbits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (nbits < 17) {
|
||||||
|
v.setDataType(DataType.SHORT);
|
||||||
|
if (nbits == 16) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else {
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) BufrNumbers.missingValue(nbits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (nbits < 33) {
|
||||||
|
v.setDataType(DataType.INT);
|
||||||
|
if (nbits == 32) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else {
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
v.setDataType(DataType.LONG);
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, BufrNumbers.missingValue(nbits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// value = scale_factor * packed + add_offset
|
||||||
|
// bpacked = (value * 10^scale - refVal)
|
||||||
|
// (bpacked + refVal) / 10^scale = value
|
||||||
|
// value = bpacked * 10^-scale + refVal * 10^-scale
|
||||||
|
// scale_factor = 10^-scale
|
||||||
|
// add_ofset = refVal * 10^-scale
|
||||||
|
int scale10 = dataDesc.scale;
|
||||||
|
double scale = (scale10 == 0) ? 1.0 : Math.pow(10.0, -scale10);
|
||||||
|
if (scale10 != 0) {
|
||||||
|
v.addAttribute(new Attribute(CDM.SCALE_FACTOR, (float) scale));
|
||||||
|
}
|
||||||
|
if (dataDesc.refVal != 0) {
|
||||||
|
v.addAttribute(new Attribute(CDM.ADD_OFFSET, (float) scale * dataDesc.refVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
annotate(v, fld);
|
||||||
|
v.addAttribute(new Attribute(BufrIosp2.fxyAttName, dataDesc.getFxyName()));
|
||||||
|
v.addAttribute(new Attribute("BUFR:bitWidth", dataDesc.bitWidth));
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v.setSPobject(fld);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findUniqueName(Structure.Builder<?> struct, String want, String def) {
|
||||||
|
if (want == null) {
|
||||||
|
return def + tempNo++;
|
||||||
|
}
|
||||||
|
|
||||||
|
String vwant = NetcdfFiles.makeValidCdmObjectName(want);
|
||||||
|
Optional<Variable.Builder<?>> oldV = struct.findMemberVariable(vwant);
|
||||||
|
if (!oldV.isPresent()) {
|
||||||
|
return vwant;
|
||||||
|
}
|
||||||
|
|
||||||
|
int seq = 2;
|
||||||
|
while (true) {
|
||||||
|
String wantSeq = vwant + "-" + seq;
|
||||||
|
oldV = struct.findMemberVariable(wantSeq);
|
||||||
|
if (!oldV.isPresent()) {
|
||||||
|
return wantSeq;
|
||||||
|
}
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// force globally unique variable names, even when they are in different Structures.
|
||||||
|
// this allows us to promote structure members without worrying about name collisions
|
||||||
|
private Map<String, Integer> names = new HashMap<>(100);
|
||||||
|
|
||||||
|
private String findGloballyUniqueName(String want, String def) {
|
||||||
|
if (want == null) {
|
||||||
|
return def + tempNo++;
|
||||||
|
}
|
||||||
|
|
||||||
|
String vwant = NetcdfFiles.makeValidCdmObjectName(want);
|
||||||
|
Integer have = names.get(vwant);
|
||||||
|
if (have == null) {
|
||||||
|
names.put(vwant, 1);
|
||||||
|
return vwant;
|
||||||
|
} else {
|
||||||
|
have = have + 1;
|
||||||
|
String wantSeq = vwant + "-" + have;
|
||||||
|
names.put(vwant, have);
|
||||||
|
return wantSeq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void annotate(Variable.Builder v, BufrConfig.FieldConverter fld) {
|
||||||
|
if (fld.type == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fld.type) {
|
||||||
|
case lat:
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, CDM.LAT_UNITS));
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
|
||||||
|
coordinates.format("%s ", v.shortName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case lon:
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, CDM.LON_UNITS));
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
|
||||||
|
coordinates.format("%s ", v.shortName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case height:
|
||||||
|
case heightOfStation:
|
||||||
|
case heightAboveStation:
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Height.toString()));
|
||||||
|
coordinates.format("%s ", v.shortName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case stationId:
|
||||||
|
v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.STATION_ID));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case wmoId:
|
||||||
|
v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.STATION_WMOID));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that contains static methods for converting multiple
|
||||||
|
* bytes into one float or integer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final class BufrNumbers {
|
||||||
|
|
||||||
|
// used to check missing values when value is packed with all 1's
|
||||||
|
private static final long[] missing_value = new long[2049];
|
||||||
|
|
||||||
|
static {
|
||||||
|
long accum = 0;
|
||||||
|
for (int i = 0; i < 65; i++) {
|
||||||
|
missing_value[i] = accum;
|
||||||
|
accum = accum * 2 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMissing(long raw, int bitWidth) {
|
||||||
|
return (raw == BufrNumbers.missing_value[bitWidth]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long missingValue(int bitWidth) {
|
||||||
|
return BufrNumbers.missing_value[bitWidth];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** if missing value is not defined use this value. */
|
||||||
|
private static final int UNDEFINED = -9999;
|
||||||
|
|
||||||
|
/** Convert 2 bytes into a signed integer. */
|
||||||
|
static int int2(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
|
||||||
|
return int2(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 2 bytes to a signed integer. */
|
||||||
|
private static int int2(int a, int b) {
|
||||||
|
if ((a == 0xff && b == 0xff)) // all bits set to one
|
||||||
|
return UNDEFINED;
|
||||||
|
|
||||||
|
return (1 - ((a & 128) >> 6)) * ((a & 127) << 8 | b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read 3 bytes and turn into a signed integer. */
|
||||||
|
static int int3(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
int c = raf.read();
|
||||||
|
|
||||||
|
return int3(a, b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 3 bytes to signed integer. */
|
||||||
|
private static int int3(int a, int b, int c) {
|
||||||
|
return (1 - ((a & 128) >> 6)) * ((a & 127) << 16 | b << 8 | c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 4 bytes into a signed integer. */
|
||||||
|
public static int int4(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
int c = raf.read();
|
||||||
|
int d = raf.read();
|
||||||
|
|
||||||
|
return int4(a, b, c, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 4 bytes into a signed integer. */
|
||||||
|
private static int int4(int a, int b, int c, int d) {
|
||||||
|
// all bits set to ones
|
||||||
|
if (a == 0xff && b == 0xff && c == 0xff && d == 0xff)
|
||||||
|
return UNDEFINED;
|
||||||
|
|
||||||
|
return (1 - ((a & 128) >> 6)) * ((a & 127) << 24 | b << 16 | c << 8 | d);
|
||||||
|
} // end int4
|
||||||
|
|
||||||
|
/** Convert 2 bytes into an unsigned integer. */
|
||||||
|
static int uint2(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
|
||||||
|
return uint2(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 2 bytes to an unsigned integer. */
|
||||||
|
private static int uint2(int a, int b) {
|
||||||
|
return a << 8 | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read 3 bytes and convert into an unsigned integer. */
|
||||||
|
public static int uint3(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
int c = raf.read();
|
||||||
|
|
||||||
|
return uint3(a, b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 3 bytes into an unsigned int. */
|
||||||
|
private static int uint3(int a, int b, int c) {
|
||||||
|
return a << 16 | b << 8 | c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read 4 bytes and convert into a float value. */
|
||||||
|
public static float float4(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
int c = raf.read();
|
||||||
|
int d = raf.read();
|
||||||
|
|
||||||
|
return float4(a, b, c, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 4 bytes to a float. */
|
||||||
|
private static float float4(int a, int b, int c, int d) {
|
||||||
|
int sgn, mant, exp;
|
||||||
|
|
||||||
|
mant = b << 16 | c << 8 | d;
|
||||||
|
if (mant == 0)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
sgn = -(((a & 128) >> 6) - 1);
|
||||||
|
exp = (a & 127) - 64;
|
||||||
|
|
||||||
|
return (float) (sgn * Math.pow(16.0, exp - 6) * mant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read 8 bytes and convert into a signed long. */
|
||||||
|
public static long int8(RandomAccessFile raf) throws IOException {
|
||||||
|
int a = raf.read();
|
||||||
|
int b = raf.read();
|
||||||
|
int c = raf.read();
|
||||||
|
int d = raf.read();
|
||||||
|
int e = raf.read();
|
||||||
|
int f = raf.read();
|
||||||
|
int g = raf.read();
|
||||||
|
int h = raf.read();
|
||||||
|
|
||||||
|
return (1 - ((a & 128) >> 6))
|
||||||
|
* ((long) (a & 127) << 56 | (long) b << 48 | (long) c << 40 | (long) d << 32 | e << 24 | f << 16 | g << 8 | h);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.*;
|
||||||
|
import ucar.nc2.wmo.CommonCodeTable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up info in BUFR tables.
|
||||||
|
* Allows local center overrides for BUFR tables
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/22/13
|
||||||
|
*/
|
||||||
|
public class BufrTableLookup {
|
||||||
|
|
||||||
|
public static BufrTableLookup factory(Message m) throws IOException {
|
||||||
|
return new BufrTableLookup(m.is.getBufrEdition(), m.ids.getCenterId(), m.ids.getSubCenterId(),
|
||||||
|
m.ids.getMasterTableId(), m.ids.getMasterTableVersion(), m.ids.getLocalTableVersion(), m.ids.getCategory(),
|
||||||
|
m.ids.getSubCategory(), m.ids.getLocalSubCategory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* static public BufrTableLookup factory(int bufrEdition, int center, int subCenter, int masterId, int masterVersion,
|
||||||
|
* int localVersion,
|
||||||
|
* int category, int subCategory, int localSubCategory) {
|
||||||
|
* return new BufrTableLookup(bufrEdition, center, subCenter, masterId, masterVersion, localVersion, category,
|
||||||
|
* subCategory, localSubCategory);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private int center, subCenter, masterId, masterVersion, localVersion, bufrEdition, category, subCategory,
|
||||||
|
localSubCategory;
|
||||||
|
|
||||||
|
private BufrTableLookup(int bufrEdition, int center, int subCenter, int masterId, int masterVersion, int localVersion,
|
||||||
|
int category, int subCategory, int localSubCategory) throws IOException {
|
||||||
|
this.bufrEdition = bufrEdition;
|
||||||
|
this.center = center;
|
||||||
|
this.subCenter = subCenter;
|
||||||
|
this.masterId = masterId;
|
||||||
|
this.masterVersion = masterVersion;
|
||||||
|
this.localVersion = localVersion;
|
||||||
|
this.category = category;
|
||||||
|
this.subCategory = subCategory;
|
||||||
|
this.localSubCategory = localSubCategory;
|
||||||
|
|
||||||
|
tlookup = new TableLookup(center, subCenter, masterVersion, localVersion, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufrEdition() {
|
||||||
|
return bufrEdition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenter() {
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSubCenter() {
|
||||||
|
return subCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMasterTableId() {
|
||||||
|
return masterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMasterTableVersion() {
|
||||||
|
return masterVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalTableVersion() {
|
||||||
|
return localVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSubCategory() {
|
||||||
|
return subCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalSubCategory() {
|
||||||
|
return localSubCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCenterName() {
|
||||||
|
String name = CommonCodeTable.getCenterNameBufr(getCenter(), getBufrEdition());
|
||||||
|
String subname = CommonCodeTable.getSubCenterName(getCenter(), getSubCenter());
|
||||||
|
if (subname != null)
|
||||||
|
name = name + " / " + subname;
|
||||||
|
return getCenter() + "." + getSubCenter() + " (" + name + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCenterNo() {
|
||||||
|
return getCenter() + "." + getSubCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableName() {
|
||||||
|
return getMasterTableId() + "." + getMasterTableVersion() + "." + getLocalTableVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryFullName() { // throws IOException {
|
||||||
|
String catName = getCategoryName();
|
||||||
|
String subcatName = getSubCategoryName();
|
||||||
|
|
||||||
|
if (subcatName != null)
|
||||||
|
return getCategoryNo() + "=" + catName + " / " + subcatName;
|
||||||
|
else
|
||||||
|
return getCategoryNo() + "=" + catName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubCategoryName() { // throws IOException {
|
||||||
|
String subcatName = null;
|
||||||
|
if (center == 7)
|
||||||
|
subcatName = NcepTable.getDataSubcategory(getCategory(), getSubCategory());
|
||||||
|
if (subcatName == null)
|
||||||
|
subcatName = CommonCodeTable.getDataSubcategoy(getCategory(), getSubCategory());
|
||||||
|
return subcatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return TableA.getDataCategoryName(getCategory());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName(int cat) {
|
||||||
|
return TableA.getDataCategoryName(cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryNo() {
|
||||||
|
String result = getCategory() + "." + getSubCategory();
|
||||||
|
if (getLocalSubCategory() >= 0)
|
||||||
|
result += "." + getLocalSubCategory();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private TableLookup tlookup;
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableLookup(TableLookup tlookup) {
|
||||||
|
this.tlookup = tlookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableA.Descriptor getDescriptorTableA(int code) {
|
||||||
|
return tlookup.getDescriptorTableA(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableB.Descriptor getDescriptorTableB(short fxy) {
|
||||||
|
return tlookup.getDescriptorTableB(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableD.Descriptor getDescriptorTableD(short fxy) {
|
||||||
|
return tlookup.getDescriptorTableD(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWmoTableBName() {
|
||||||
|
return tlookup.getWmoTableBName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalTableBName() {
|
||||||
|
return tlookup.getLocalTableBName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalTableDName() {
|
||||||
|
return tlookup.getLocalTableDName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWmoTableDName() {
|
||||||
|
return tlookup.getWmoTableDName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufrTables.Mode getMode() {
|
||||||
|
return tlookup.getMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showMissingFields(List<Short> ddsList, Formatter out) {
|
||||||
|
for (short fxy : ddsList) {
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
if (f == 3) {
|
||||||
|
List<Short> sublist = getDescriptorListTableD(fxy);
|
||||||
|
if (sublist == null)
|
||||||
|
out.format("%s, ", ucar.nc2.iosp.bufr.Descriptor.makeString(fxy));
|
||||||
|
else
|
||||||
|
showMissingFields(sublist, out);
|
||||||
|
|
||||||
|
} else if (f == 0) { // skip the 2- operators for now
|
||||||
|
TableB.Descriptor b = getDescriptorTableB(fxy);
|
||||||
|
if (b == null)
|
||||||
|
out.format("%s, ", ucar.nc2.iosp.bufr.Descriptor.makeString(fxy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDescriptorListTableD(String fxy) {
|
||||||
|
short id = ucar.nc2.iosp.bufr.Descriptor.getFxy(fxy);
|
||||||
|
List<Short> seq = getDescriptorListTableD(id);
|
||||||
|
if (seq == null)
|
||||||
|
return null;
|
||||||
|
List<String> result = new ArrayList<>(seq.size());
|
||||||
|
for (Short s : seq)
|
||||||
|
result.add(Descriptor.makeString(s));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Short> getDescriptorListTableD(short id) {
|
||||||
|
TableD.Descriptor d = getDescriptorTableD(id);
|
||||||
|
if (d != null)
|
||||||
|
return d.getSequence();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,473 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.ma2.DataType;
|
||||||
|
import ucar.ma2.InvalidRangeException;
|
||||||
|
import ucar.nc2.*;
|
||||||
|
import ucar.nc2.constants.AxisType;
|
||||||
|
import ucar.nc2.constants.CDM;
|
||||||
|
import ucar.nc2.constants.CF;
|
||||||
|
import ucar.nc2.constants._Coordinate;
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
import ucar.nc2.ft.point.bufr.StandardFields;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.CodeFlagTables;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufrIosp2 delegates the construction of the Netcdf objects to Construct2.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/8/13
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Construct2 {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Construct2.class);
|
||||||
|
private static final boolean warnUnits = false;
|
||||||
|
|
||||||
|
private NetcdfFile ncfile;
|
||||||
|
|
||||||
|
private Sequence recordStructure;
|
||||||
|
private int centerId;
|
||||||
|
private Formatter coordinates = new Formatter();
|
||||||
|
|
||||||
|
Construct2(Message proto, BufrConfig bufrConfig, NetcdfFile nc) throws IOException {
|
||||||
|
this.ncfile = nc;
|
||||||
|
|
||||||
|
// dkeyRoot = dds.getDescriptorRoot();
|
||||||
|
// int nbits = dds.getTotalBits();
|
||||||
|
// int inputBytes = (nbits % 8 == 0) ? nbits / 8 : nbits / 8 + 1;
|
||||||
|
// int outputBytes = dds.getTotalBytes();
|
||||||
|
|
||||||
|
// the category
|
||||||
|
// int cat = proto.ids.getCategory();
|
||||||
|
// int subcat = proto.ids.getSubCategory();
|
||||||
|
|
||||||
|
// global Attributes
|
||||||
|
ncfile.addAttribute(null, new Attribute(CDM.HISTORY, "Read using CDM BufrIosp2"));
|
||||||
|
if (bufrConfig.getFeatureType() != null)
|
||||||
|
ncfile.addAttribute(null, CF.FEATURE_TYPE, bufrConfig.getFeatureType().toString());
|
||||||
|
ncfile.addAttribute(null, "location", nc.getLocation());
|
||||||
|
|
||||||
|
ncfile.addAttribute(null, "BUFR:categoryName", proto.getLookup().getCategoryName());
|
||||||
|
ncfile.addAttribute(null, "BUFR:subCategoryName", proto.getLookup().getSubCategoryName());
|
||||||
|
ncfile.addAttribute(null, "BUFR:centerName", proto.getLookup().getCenterName());
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:category", proto.ids.getCategory()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:subCategory", proto.ids.getSubCategory()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:localSubCategory", proto.ids.getLocalSubCategory()));
|
||||||
|
ncfile.addAttribute(null, new Attribute(BufrIosp2.centerId, proto.ids.getCenterId()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:subCenter", proto.ids.getSubCenterId()));
|
||||||
|
// ncfile.addAttribute(null, "BUFR:tableName", proto.ids.getMasterTableFilename()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:table", proto.ids.getMasterTableId()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:tableVersion", proto.ids.getMasterTableVersion()));
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:localTableVersion", proto.ids.getLocalTableVersion()));
|
||||||
|
ncfile.addAttribute(null, "Conventions", "BUFR/CDM");
|
||||||
|
ncfile.addAttribute(null, new Attribute("BUFR:edition", proto.is.getBufrEdition()));
|
||||||
|
|
||||||
|
centerId = proto.ids.getCenterId();
|
||||||
|
|
||||||
|
String header = proto.getHeader();
|
||||||
|
if (header != null && !header.isEmpty())
|
||||||
|
ncfile.addAttribute(null, new Attribute("WMO Header", header));
|
||||||
|
|
||||||
|
makeObsRecord(bufrConfig);
|
||||||
|
String coordS = coordinates.toString();
|
||||||
|
if (!coordS.isEmpty())
|
||||||
|
recordStructure.addAttribute(new Attribute("coordinates", coordS));
|
||||||
|
|
||||||
|
ncfile.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
Sequence getObsStructure() {
|
||||||
|
return recordStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeObsRecord(BufrConfig bufrConfig) {
|
||||||
|
recordStructure = new Sequence(ncfile, null, null, BufrIosp2.obsRecordName);
|
||||||
|
ncfile.addVariable(null, recordStructure);
|
||||||
|
|
||||||
|
BufrConfig.FieldConverter root = bufrConfig.getRootConverter();
|
||||||
|
for (BufrConfig.FieldConverter fld : root.flds) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
if (!dkey.isOkForVariable())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
addSequence(recordStructure, fld);
|
||||||
|
|
||||||
|
} else if (dkey.replication > 1) {
|
||||||
|
|
||||||
|
List<BufrConfig.FieldConverter> subFlds = fld.flds;
|
||||||
|
List<DataDescriptor> subKeys = dkey.subKeys;
|
||||||
|
if (subKeys.size() == 1) { // only one member
|
||||||
|
DataDescriptor subDds = dkey.subKeys.get(0);
|
||||||
|
BufrConfig.FieldConverter subFld = subFlds.get(0);
|
||||||
|
if (subDds.dpi != null) {
|
||||||
|
addDpiStructure(recordStructure, fld, subFld);
|
||||||
|
|
||||||
|
} else if (subDds.replication == 1) { // one member not a replication
|
||||||
|
Variable v = addVariable(recordStructure, subFld, dkey.replication);
|
||||||
|
v.setSPobject(fld); // set the replicating field as SPI object
|
||||||
|
|
||||||
|
} else { // one member is a replication (two replications in a row)
|
||||||
|
addStructure(recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
} else if (subKeys.size() > 1) {
|
||||||
|
addStructure(recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // replication == 1
|
||||||
|
addVariable(recordStructure, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStructure(Structure parent, BufrConfig.FieldConverter fld, int count) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findUniqueName(parent, fld.getName(), "struct");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
// String structName = dataDesc.name != null ? dataDesc.name : "struct" + structNum++;
|
||||||
|
Structure struct = new Structure(ncfile, null, parent, uname);
|
||||||
|
try {
|
||||||
|
struct.setDimensionsAnonymous(new int[]{count}); // anon vector
|
||||||
|
} catch (InvalidRangeException e) {
|
||||||
|
log.error("illegal count= " + count + " for " + fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BufrConfig.FieldConverter subKey : fld.flds)
|
||||||
|
addMember(struct, subKey);
|
||||||
|
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
struct.setSPobject(fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSequence(Structure parent, BufrConfig.FieldConverter fld) {
|
||||||
|
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findUniqueName(parent, fld.getName(), "seq");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
// String seqName = ftype == (FeatureType.STATION_PROFILE) ? "profile" : "seq";
|
||||||
|
// String seqName = dataDesc.name != null ? dataDesc.name : "seq" + seqNum++;
|
||||||
|
|
||||||
|
Sequence seq = new Sequence(ncfile, null, parent, uname);
|
||||||
|
seq.setDimensions(""); // scalar
|
||||||
|
|
||||||
|
for (BufrConfig.FieldConverter subKey : fld.flds)
|
||||||
|
addMember(seq, subKey);
|
||||||
|
|
||||||
|
parent.addMemberVariable(seq);
|
||||||
|
seq.setSPobject(fld);
|
||||||
|
|
||||||
|
dkey.refersTo = seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMember(Structure parent, BufrConfig.FieldConverter fld) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
|
||||||
|
if (dkey.replication == 0)
|
||||||
|
addSequence(parent, fld);
|
||||||
|
|
||||||
|
else if (dkey.replication > 1) {
|
||||||
|
List<DataDescriptor> subKeys = dkey.subKeys;
|
||||||
|
if (subKeys.size() == 1) {
|
||||||
|
BufrConfig.FieldConverter subFld = fld.flds.get(0);
|
||||||
|
Variable v = addVariable(parent, subFld, dkey.replication);
|
||||||
|
v.setSPobject(fld); // set the replicating field as SPI object
|
||||||
|
|
||||||
|
} else {
|
||||||
|
addStructure(parent, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
addVariable(parent, fld, dkey.replication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDpiStructure(Structure parent, BufrConfig.FieldConverter parentFld,
|
||||||
|
BufrConfig.FieldConverter dpiField) {
|
||||||
|
DataDescriptor dpiKey = dpiField.dds;
|
||||||
|
String uname = findUniqueName(parent, dpiField.getName(), "struct");
|
||||||
|
dpiKey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
// String structName = findUnique(parent, dpiField.name);
|
||||||
|
Structure struct = new Structure(ncfile, null, parent, uname);
|
||||||
|
int n = parentFld.dds.replication;
|
||||||
|
try {
|
||||||
|
struct.setDimensionsAnonymous(new int[]{n}); // anon vector
|
||||||
|
} catch (InvalidRangeException e) {
|
||||||
|
log.error("illegal count= " + 1 + " for " + dpiField);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable v = new Variable(ncfile, null, struct, "name");
|
||||||
|
v.setDataType(DataType.STRING); // scalar
|
||||||
|
v.setDimensions(""); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v = new Variable(ncfile, null, struct, "data");
|
||||||
|
v.setDataType(DataType.FLOAT); // scalar
|
||||||
|
v.setDimensions(""); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
struct.setSPobject(dpiField); // ??
|
||||||
|
|
||||||
|
// add some fake dkeys corresponding to above
|
||||||
|
// DataDescriptor nameDD = new DataDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDpiSequence(Structure parent, BufrConfig.FieldConverter fld) {
|
||||||
|
Structure struct = new Structure(ncfile, null, parent, "statistics");
|
||||||
|
try {
|
||||||
|
struct.setDimensionsAnonymous(new int[]{fld.dds.replication}); // scalar
|
||||||
|
} catch (InvalidRangeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Variable v = new Variable(ncfile, null, struct, "name");
|
||||||
|
v.setDataType(DataType.STRING); // scalar
|
||||||
|
v.setDimensions(""); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v = new Variable(ncfile, null, struct, "data");
|
||||||
|
v.setDataType(DataType.FLOAT); // scalar
|
||||||
|
v.setDimensions(""); // scalar
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
parent.addMemberVariable(struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable addVariable(Structure struct, BufrConfig.FieldConverter fld, int count) {
|
||||||
|
DataDescriptor dkey = fld.dds;
|
||||||
|
String uname = findGloballyUniqueName(fld.getName(), "unknown");
|
||||||
|
dkey.name = uname; // name may need to be changed for uniqueness
|
||||||
|
|
||||||
|
Variable v = new Variable(ncfile, null, struct, uname);
|
||||||
|
try {
|
||||||
|
if (count > 1)
|
||||||
|
v.setDimensionsAnonymous(new int[]{count}); // anon vector
|
||||||
|
else
|
||||||
|
v.setDimensions(""); // scalar
|
||||||
|
} catch (InvalidRangeException e) {
|
||||||
|
log.error("illegal count= " + count + " for " + fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld.getDesc() != null)
|
||||||
|
v.addAttribute(new Attribute(CDM.LONG_NAME, fld.getDesc()));
|
||||||
|
|
||||||
|
if (fld.getUnits() == null) {
|
||||||
|
if (warnUnits)
|
||||||
|
log.warn("dataDesc.units == null for " + uname);
|
||||||
|
} else {
|
||||||
|
String units = fld.getUnits();
|
||||||
|
if (DataDescriptor.isCodeTableUnit(units)) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, "CodeTable " + fld.dds.getFxyName()));
|
||||||
|
} else if (DataDescriptor.isFlagTableUnit(units)) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, "FlagTable " + fld.dds.getFxyName()));
|
||||||
|
} else if (!DataDescriptor.isInternationalAlphabetUnit(units) && !units.startsWith("Numeric")) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, units));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataDescriptor dataDesc = fld.dds;
|
||||||
|
if (dataDesc.type == 1) {
|
||||||
|
v.setDataType(DataType.CHAR);
|
||||||
|
int size = dataDesc.bitWidth / 8;
|
||||||
|
try {
|
||||||
|
v.setDimensionsAnonymous(new int[]{size});
|
||||||
|
} catch (InvalidRangeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ((dataDesc.type == 2) && CodeFlagTables.hasTable(dataDesc.fxy)) { // enum
|
||||||
|
int nbits = dataDesc.bitWidth;
|
||||||
|
int nbytes = (nbits % 8 == 0) ? nbits / 8 : nbits / 8 + 1;
|
||||||
|
|
||||||
|
CodeFlagTables ct = CodeFlagTables.getTable(dataDesc.fxy);
|
||||||
|
if (nbytes == 1)
|
||||||
|
v.setDataType(DataType.ENUM1);
|
||||||
|
else if (nbytes == 2)
|
||||||
|
v.setDataType(DataType.ENUM2);
|
||||||
|
else if (nbytes == 4)
|
||||||
|
v.setDataType(DataType.ENUM4);
|
||||||
|
|
||||||
|
// v.removeAttribute(CDM.UNITS);
|
||||||
|
v.addAttribute(new Attribute("BUFR:CodeTable", ct.getName() + " (" + dataDesc.getFxyName() + ")"));
|
||||||
|
|
||||||
|
Group g = struct.getParentGroupOrRoot();
|
||||||
|
if (g == null)
|
||||||
|
log.warn("Struct parent group is null.");
|
||||||
|
EnumTypedef enumTypedef = g.findEnumeration(ct.getName());
|
||||||
|
if (enumTypedef == null) {
|
||||||
|
enumTypedef = new EnumTypedef(ct.getName(), ct.getMap());
|
||||||
|
g.addEnumeration(enumTypedef);
|
||||||
|
}
|
||||||
|
v.setEnumTypedef(enumTypedef);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int nbits = dataDesc.bitWidth;
|
||||||
|
// use of unsigned seems fishy, since only time it uses high bit is for missing
|
||||||
|
// not necessarily true, just when they "add one bit" to deal with missing case
|
||||||
|
if (nbits < 9) {
|
||||||
|
v.setDataType(DataType.BYTE);
|
||||||
|
if (nbits == 8) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (byte) BufrNumbers.missingValue(nbits)));
|
||||||
|
|
||||||
|
} else if (nbits < 17) {
|
||||||
|
v.setDataType(DataType.SHORT);
|
||||||
|
if (nbits == 16) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (short) BufrNumbers.missingValue(nbits)));
|
||||||
|
|
||||||
|
} else if (nbits < 33) {
|
||||||
|
v.setDataType(DataType.INT);
|
||||||
|
if (nbits == 32) {
|
||||||
|
v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
} else
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, (int) BufrNumbers.missingValue(nbits)));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
v.setDataType(DataType.LONG);
|
||||||
|
v.addAttribute(new Attribute(CDM.MISSING_VALUE, BufrNumbers.missingValue(nbits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// value = scale_factor * packed + add_offset
|
||||||
|
// bpacked = (value * 10^scale - refVal)
|
||||||
|
// (bpacked + refVal) / 10^scale = value
|
||||||
|
// value = bpacked * 10^-scale + refVal * 10^-scale
|
||||||
|
// scale_factor = 10^-scale
|
||||||
|
// add_ofset = refVal * 10^-scale
|
||||||
|
int scale10 = dataDesc.scale;
|
||||||
|
double scale = (scale10 == 0) ? 1.0 : Math.pow(10.0, -scale10);
|
||||||
|
if (scale10 != 0)
|
||||||
|
v.addAttribute(new Attribute(CDM.SCALE_FACTOR, (float) scale));
|
||||||
|
if (dataDesc.refVal != 0)
|
||||||
|
v.addAttribute(new Attribute(CDM.ADD_OFFSET, (float) scale * dataDesc.refVal));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
annotate(v, fld);
|
||||||
|
v.addAttribute(new Attribute(BufrIosp2.fxyAttName, dataDesc.getFxyName()));
|
||||||
|
v.addAttribute(new Attribute("BUFR:bitWidth", dataDesc.bitWidth));
|
||||||
|
struct.addMemberVariable(v);
|
||||||
|
|
||||||
|
v.setSPobject(fld);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int tempNo = 1;
|
||||||
|
|
||||||
|
private String findUniqueName(Structure struct, String want, String def) {
|
||||||
|
if (want == null)
|
||||||
|
return def + tempNo++;
|
||||||
|
|
||||||
|
String vwant = NetcdfFile.makeValidCdmObjectName(want);
|
||||||
|
Variable oldV = struct.findVariable(vwant);
|
||||||
|
if (oldV == null)
|
||||||
|
return vwant;
|
||||||
|
|
||||||
|
int seq = 2;
|
||||||
|
while (true) {
|
||||||
|
String wantSeq = vwant + "-" + seq;
|
||||||
|
oldV = struct.findVariable(wantSeq);
|
||||||
|
if (oldV == null)
|
||||||
|
return wantSeq;
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// force globally unique variable names, even when they are in different Structures.
|
||||||
|
// this allows us to promote structure members without worrying about name collisions
|
||||||
|
private Map<String, Integer> names = new HashMap<>(100);
|
||||||
|
|
||||||
|
private String findGloballyUniqueName(String want, String def) {
|
||||||
|
if (want == null)
|
||||||
|
return def + tempNo++;
|
||||||
|
|
||||||
|
String vwant = NetcdfFile.makeValidCdmObjectName(want);
|
||||||
|
Integer have = names.get(vwant);
|
||||||
|
if (have == null) {
|
||||||
|
names.put(vwant, 1);
|
||||||
|
return vwant;
|
||||||
|
} else {
|
||||||
|
have = have + 1;
|
||||||
|
String wantSeq = vwant + "-" + have;
|
||||||
|
names.put(vwant, have);
|
||||||
|
return wantSeq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void annotate(Variable v, BufrConfig.FieldConverter fld) {
|
||||||
|
if (fld.type == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (fld.type) {
|
||||||
|
case lat:
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, CDM.LAT_UNITS));
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
|
||||||
|
coordinates.format("%s ", v.getShortName());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case lon:
|
||||||
|
v.addAttribute(new Attribute(CDM.UNITS, CDM.LON_UNITS));
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
|
||||||
|
coordinates.format("%s ", v.getShortName());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case height:
|
||||||
|
case heightOfStation:
|
||||||
|
case heightAboveStation:
|
||||||
|
v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Height.toString()));
|
||||||
|
coordinates.format("%s ", v.getShortName());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case stationId:
|
||||||
|
v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.STATION_ID));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case wmoId:
|
||||||
|
v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.STATION_WMOID));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void annotateObs(Sequence recordStructure) {
|
||||||
|
StandardFields.StandardFieldsFromStructure extract =
|
||||||
|
new StandardFields.StandardFieldsFromStructure(centerId, recordStructure);
|
||||||
|
|
||||||
|
try (Formatter f = new Formatter()) {
|
||||||
|
String name = extract.getFieldName(BufrCdmIndexProto.FldType.lat);
|
||||||
|
if (name != null)
|
||||||
|
f.format("%s ", name);
|
||||||
|
name = extract.getFieldName(BufrCdmIndexProto.FldType.lon);
|
||||||
|
if (name != null)
|
||||||
|
f.format("%s ", name);
|
||||||
|
name = extract.getFieldName(BufrCdmIndexProto.FldType.height);
|
||||||
|
if (name != null)
|
||||||
|
f.format("%s ", name);
|
||||||
|
name = extract.getFieldName(BufrCdmIndexProto.FldType.heightAboveStation);
|
||||||
|
if (name != null)
|
||||||
|
f.format("%s ", name);
|
||||||
|
|
||||||
|
recordStructure.addAttribute(new Attribute("coordinates", f.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,429 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableB;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableC;
|
||||||
|
import ucar.nc2.Sequence;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Essentially a TableB entry, modified by any relevent TableC operators.
|
||||||
|
* TableD has been expanded.
|
||||||
|
* Replication gets made into nested DataDescriptors, which we map to Structures (fixed replication) or
|
||||||
|
* Sequences (deferred replication).
|
||||||
|
* Most of the processing is done by DataDescriptorTreeConstructor.convert().
|
||||||
|
* Here we encapsulate the final result, ready to map to the CDM.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Apr 5, 2008
|
||||||
|
*/
|
||||||
|
public class DataDescriptor {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataDescriptor.class);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
// from the TableB.Descriptor
|
||||||
|
short fxy;
|
||||||
|
int f, x, y;
|
||||||
|
String name;
|
||||||
|
private String units, desc, source;
|
||||||
|
private boolean localOverride;
|
||||||
|
boolean bad; // no descriptor found
|
||||||
|
|
||||||
|
// may get modified by TableC operators
|
||||||
|
int scale;
|
||||||
|
int refVal;
|
||||||
|
int bitWidth;
|
||||||
|
int type; // 0 = isNumeric, 1 = isString, 2 = isEnum, 3 = compound;
|
||||||
|
|
||||||
|
// replication info
|
||||||
|
List<DataDescriptor> subKeys;
|
||||||
|
int replication = 1; // number of replications, essentially dk.y when sk.f == 1
|
||||||
|
int replicationCountSize; // for delayed replication : size of count in bits
|
||||||
|
int repetitionCountSize; // for delayed repetition
|
||||||
|
|
||||||
|
AssociatedField assField; // associated field == 02 04 Y, Y number of extra bits
|
||||||
|
Sequence refersTo; // needed for nested sequence objects
|
||||||
|
DataDescriptorTreeConstructor.DataPresentIndicator dpi;
|
||||||
|
|
||||||
|
DataDescriptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataDescriptor(short fxy, BufrTableLookup lookup) {
|
||||||
|
this.fxy = fxy;
|
||||||
|
this.f = (fxy & 0xC000) >> 14;
|
||||||
|
this.x = (fxy & 0x3F00) >> 8;
|
||||||
|
this.y = fxy & 0xFF;
|
||||||
|
|
||||||
|
TableB.Descriptor db;
|
||||||
|
if (f == 0) {
|
||||||
|
db = lookup.getDescriptorTableB(fxy);
|
||||||
|
if (db != null)
|
||||||
|
setDescriptor(db);
|
||||||
|
else {
|
||||||
|
bad = true;
|
||||||
|
this.name = "*NOT FOUND";
|
||||||
|
}
|
||||||
|
} else if (f == 1) // replication
|
||||||
|
this.type = 3; // compound
|
||||||
|
|
||||||
|
else if (f == 2) {
|
||||||
|
this.name = TableC.getOperatorName(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if unit string indicates that the data are 7-bit coded characters following
|
||||||
|
* the International Reference Alphabet (formally known as the International Alphabet
|
||||||
|
* No.5 (IA5)) Recommendation/International Standard from the International Telegraph
|
||||||
|
* and Telephone Consultative Committee (CCITT)
|
||||||
|
* <p>
|
||||||
|
* https://www.itu.int/rec/T-REC-T.50/en
|
||||||
|
*
|
||||||
|
* @param unitString unit
|
||||||
|
* @return If true, treat the data as 7-bit coded International Reference Alphabet Characters
|
||||||
|
*/
|
||||||
|
public static boolean isInternationalAlphabetUnit(String unitString) {
|
||||||
|
String testUnitString = unitString.toLowerCase();
|
||||||
|
return testUnitString.startsWith("ccitt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the unit string indicates that we are dealing with data associated with a code table
|
||||||
|
*
|
||||||
|
* @param unitString unit
|
||||||
|
* @return If true, the unit indicates we are working with data associated with a code table
|
||||||
|
*/
|
||||||
|
public static boolean isCodeTableUnit(String unitString) {
|
||||||
|
String testUnitString = unitString.toLowerCase();
|
||||||
|
return testUnitString.equalsIgnoreCase("Code Table") || testUnitString.equalsIgnoreCase("Code_Table")
|
||||||
|
|| testUnitString.startsWith("codetable");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the unit string indicates that we are dealing with data associated with a flag table
|
||||||
|
*
|
||||||
|
* @param unitString unit
|
||||||
|
* @return If true, the unit indicates we are working with data associated with a flag table
|
||||||
|
*/
|
||||||
|
public static boolean isFlagTableUnit(String unitString) {
|
||||||
|
String testUnitString = unitString.toLowerCase();
|
||||||
|
return testUnitString.equalsIgnoreCase("Flag Table") || testUnitString.equalsIgnoreCase("Flag_Table")
|
||||||
|
|| testUnitString.startsWith("flagtable");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDescriptor(TableB.Descriptor d) {
|
||||||
|
this.name = d.getName().trim();
|
||||||
|
this.units = d.getUnits().trim();
|
||||||
|
this.desc = d.getDesc();
|
||||||
|
this.refVal = d.getRefVal();
|
||||||
|
this.scale = d.getScale();
|
||||||
|
this.bitWidth = d.getDataWidth();
|
||||||
|
this.localOverride = d.getLocalOverride();
|
||||||
|
this.source = d.getSource();
|
||||||
|
|
||||||
|
if (isInternationalAlphabetUnit(units)) {
|
||||||
|
this.type = 1; // String
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOK what about flag table ??
|
||||||
|
if (isCodeTableUnit(units)) {
|
||||||
|
this.type = 2; // enum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for dpi fields
|
||||||
|
* DataDescriptor makeStatField(String statType) {
|
||||||
|
* DataDescriptor statDD = new DataDescriptor();
|
||||||
|
* statDD.name = name + "_" + statType;
|
||||||
|
* statDD.units = units;
|
||||||
|
* statDD.refVal = 0;
|
||||||
|
*
|
||||||
|
* return statDD;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
// for associated fields
|
||||||
|
DataDescriptor makeAssociatedField(int bitWidth) {
|
||||||
|
DataDescriptor assDD = new DataDescriptor();
|
||||||
|
assDD.name = name + "_associated_field";
|
||||||
|
assDD.units = "";
|
||||||
|
assDD.refVal = 0;
|
||||||
|
assDD.scale = 0;
|
||||||
|
assDD.bitWidth = bitWidth;
|
||||||
|
assDD.type = 0;
|
||||||
|
|
||||||
|
assDD.f = 0;
|
||||||
|
assDD.x = 31;
|
||||||
|
assDD.y = 22;
|
||||||
|
assDD.fxy = (short) ((f << 14) + (x << 8) + (y));
|
||||||
|
|
||||||
|
return assDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AssociatedField {
|
||||||
|
int nbits;
|
||||||
|
int nfields;
|
||||||
|
String dataFldName;
|
||||||
|
|
||||||
|
AssociatedField(int nbits) {
|
||||||
|
this.nbits = nbits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DataDescriptor> getSubKeys() {
|
||||||
|
return subKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isOkForVariable() {
|
||||||
|
return (f == 0) || (f == 1) || ((f == 2) && (x == 5) || ((f == 2) && (x == 24) && (y == 255)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
if ((f == 0) || (f == 3)) {
|
||||||
|
return (x >= 48) || (y >= 192);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocalOverride() {
|
||||||
|
return localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFxyName() {
|
||||||
|
return Descriptor.makeString(f, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public short getFxy() {
|
||||||
|
return fxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScale() {
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRefVal() {
|
||||||
|
return refVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnits() {
|
||||||
|
return units;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float convert(long raw) {
|
||||||
|
if (ucar.nc2.iosp.bufr.BufrNumbers.isMissing(raw, bitWidth))
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
// bpacked = (value * 10^scale - refVal)
|
||||||
|
// value = (bpacked + refVal) / 10^scale
|
||||||
|
float fscale = (float) Math.pow(10.0, -scale); // LOOK precompute ??
|
||||||
|
float fval = (raw + refVal);
|
||||||
|
return fscale * fval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float convert(long raw, int scale, int refVal, int bitWidth) {
|
||||||
|
if (BufrNumbers.isMissing(raw, bitWidth))
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
// bpacked = (value * 10^scale - refVal)
|
||||||
|
// value = (bpacked + refVal) / 10^scale
|
||||||
|
float fscale = (float) Math.pow(10.0, -scale); // LOOK precompute ??
|
||||||
|
float fval = (raw + refVal);
|
||||||
|
return fscale * fval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer info from the "proto message" to another message with the exact same structure.
|
||||||
|
*
|
||||||
|
* @param fromList transfer from here
|
||||||
|
* @param toList to here
|
||||||
|
*/
|
||||||
|
static void transferInfo(List<DataDescriptor> fromList, List<DataDescriptor> toList) { // get info from proto
|
||||||
|
// message
|
||||||
|
if (fromList.size() != toList.size())
|
||||||
|
throw new IllegalArgumentException("list sizes dont match " + fromList.size() + " != " + toList.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < fromList.size(); i++) {
|
||||||
|
DataDescriptor from = fromList.get(i);
|
||||||
|
DataDescriptor to = toList.get(i);
|
||||||
|
to.refersTo = from.refersTo;
|
||||||
|
to.name = from.name;
|
||||||
|
|
||||||
|
if (from.getSubKeys() != null)
|
||||||
|
transferInfo(from.getSubKeys(), to.getSubKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private int total_nbytesCDM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* count the bits used by the data in this dd and its children
|
||||||
|
* only accurate for not compressed, and not variable length
|
||||||
|
*
|
||||||
|
* @return bits used by the data in the file
|
||||||
|
*/
|
||||||
|
int countBits() {
|
||||||
|
int total_nbits = 0;
|
||||||
|
total_nbytesCDM = 0;
|
||||||
|
|
||||||
|
for (DataDescriptor dd : subKeys) {
|
||||||
|
if (dd.subKeys != null) {
|
||||||
|
total_nbits += dd.countBits();
|
||||||
|
total_nbytesCDM += dd.total_nbytesCDM;
|
||||||
|
|
||||||
|
} else if (dd.f == 0) {
|
||||||
|
total_nbits += dd.bitWidth;
|
||||||
|
total_nbytesCDM += dd.getByteWidthCDM();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replication
|
||||||
|
if (replication > 1) {
|
||||||
|
total_nbits *= replication;
|
||||||
|
total_nbytesCDM *= replication;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitWidth() {
|
||||||
|
return bitWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of bytes the CDM datatype will take
|
||||||
|
*
|
||||||
|
* @return the number of bytes the CDM datatype will take
|
||||||
|
*/
|
||||||
|
int getByteWidthCDM() {
|
||||||
|
if (type == 1) // string
|
||||||
|
return bitWidth / 8;
|
||||||
|
|
||||||
|
if (type == 3) // compound
|
||||||
|
return total_nbytesCDM;
|
||||||
|
|
||||||
|
// numeric or enum
|
||||||
|
if (bitWidth < 9)
|
||||||
|
return 1;
|
||||||
|
if (bitWidth < 17)
|
||||||
|
return 2;
|
||||||
|
if (bitWidth < 33)
|
||||||
|
return 4;
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
String id = getFxyName();
|
||||||
|
StringBuilder sbuff = new StringBuilder();
|
||||||
|
if (f == 0) {
|
||||||
|
sbuff.append(getFxyName()).append(": ");
|
||||||
|
sbuff.append(name).append(" units=").append(units);
|
||||||
|
if (type == 0) {
|
||||||
|
sbuff.append(" scale=").append(scale).append(" refVal=").append(refVal);
|
||||||
|
sbuff.append(" nbits=").append(bitWidth);
|
||||||
|
} else if (type == 1) {
|
||||||
|
sbuff.append(" nchars=").append(bitWidth / 8);
|
||||||
|
} else {
|
||||||
|
sbuff.append(" enum nbits=").append(bitWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (f == 1) {
|
||||||
|
sbuff.append(id).append(": ").append("Replication");
|
||||||
|
if (replication != 1)
|
||||||
|
sbuff.append(" count=").append(replication);
|
||||||
|
if (replicationCountSize != 0)
|
||||||
|
sbuff.append(" replicationCountSize=").append(replicationCountSize);
|
||||||
|
if (repetitionCountSize != 0)
|
||||||
|
sbuff.append(" repetitionCountSize=").append(repetitionCountSize);
|
||||||
|
if (name != null)
|
||||||
|
sbuff.append(": " + name);
|
||||||
|
|
||||||
|
} else if (f == 2) {
|
||||||
|
String desc = TableC.getOperatorName(x);
|
||||||
|
if (desc == null)
|
||||||
|
desc = "Operator";
|
||||||
|
sbuff.append(id).append(": ").append(desc);
|
||||||
|
|
||||||
|
} else
|
||||||
|
sbuff.append(id).append(": ").append(name);
|
||||||
|
|
||||||
|
return sbuff.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// stuff for the root
|
||||||
|
boolean isVarLength;
|
||||||
|
boolean isBad;
|
||||||
|
int total_nbits;
|
||||||
|
|
||||||
|
public int getTotalBits() {
|
||||||
|
return total_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVarLength() {
|
||||||
|
return isVarLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// LOOK need different hashCode, reader assumes using object id
|
||||||
|
public boolean equals2(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DataDescriptor that = (DataDescriptor) o;
|
||||||
|
|
||||||
|
if (fxy != that.fxy)
|
||||||
|
return false;
|
||||||
|
if (replication != that.replication)
|
||||||
|
return false;
|
||||||
|
if (type != that.type)
|
||||||
|
return false;
|
||||||
|
return Objects.equals(subKeys, that.subKeys);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode2() {
|
||||||
|
int result = (int) fxy;
|
||||||
|
result = 31 * result + type;
|
||||||
|
result = 31 * result + getListHash();
|
||||||
|
result = 31 * result + replication;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// has to use hashCode2, so cant use list.hashCode()
|
||||||
|
private int getListHash() {
|
||||||
|
if (subKeys == null)
|
||||||
|
return 0;
|
||||||
|
int result = 1;
|
||||||
|
for (DataDescriptor e : subKeys)
|
||||||
|
result = 31 * result + (e == null ? 0 : e.hashCode2());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,498 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableD;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a list of data descriptors to a tree of DataDescriptor objects.
|
||||||
|
* Expand Table D, process table C operators.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Jul 14, 2008
|
||||||
|
*/
|
||||||
|
public class DataDescriptorTreeConstructor {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataDescriptorTreeConstructor.class);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private DataDescriptor root;
|
||||||
|
|
||||||
|
public DataDescriptor factory(BufrTableLookup lookup, BufrDataDescriptionSection dds) {
|
||||||
|
root = new DataDescriptor();
|
||||||
|
|
||||||
|
// convert ids to DataDescriptor
|
||||||
|
List<DataDescriptor> keys = decode(dds.getDataDescriptors(), lookup);
|
||||||
|
|
||||||
|
// deal with f3-60-4
|
||||||
|
keys = preflatten(keys);
|
||||||
|
|
||||||
|
// try to find useful struct names
|
||||||
|
grabCompoundNames(keys);
|
||||||
|
|
||||||
|
// make replicated keys into subKeys, constituting a tree
|
||||||
|
List<DataDescriptor> tree = replicate(keys);
|
||||||
|
|
||||||
|
// flatten the compounds
|
||||||
|
root.subKeys = new ArrayList<>();
|
||||||
|
flatten(root.subKeys, tree);
|
||||||
|
|
||||||
|
// process the operators
|
||||||
|
operate(root.subKeys);
|
||||||
|
|
||||||
|
// count the size
|
||||||
|
root.total_nbits = root.countBits();
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert ids to DataDescriptors, expand table D
|
||||||
|
private List<DataDescriptor> decode(List<Short> keyDesc, BufrTableLookup lookup) {
|
||||||
|
if (keyDesc == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<DataDescriptor> keys = new ArrayList<>();
|
||||||
|
for (short id : keyDesc) {
|
||||||
|
DataDescriptor dd = new DataDescriptor(id, lookup);
|
||||||
|
keys.add(dd);
|
||||||
|
if (dd.f == 3) {
|
||||||
|
TableD.Descriptor tdd = lookup.getDescriptorTableD(dd.fxy);
|
||||||
|
if (tdd == null || tdd.getSequence() == null) {
|
||||||
|
dd.bad = true;
|
||||||
|
} else {
|
||||||
|
dd.name = tdd.getName();
|
||||||
|
dd.subKeys = decode(tdd.getSequence(), lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for replication, move replicated items into subtree
|
||||||
|
private List<DataDescriptor> replicate(List<DataDescriptor> keys) {
|
||||||
|
List<DataDescriptor> tree = new ArrayList<>();
|
||||||
|
Iterator<DataDescriptor> dkIter = keys.iterator();
|
||||||
|
while (dkIter.hasNext()) {
|
||||||
|
DataDescriptor dk = dkIter.next();
|
||||||
|
if (dk.f == 1) {
|
||||||
|
dk.subKeys = new ArrayList<>();
|
||||||
|
dk.replication = dk.y; // replication count
|
||||||
|
|
||||||
|
if (dk.replication == 0) { // delayed replication
|
||||||
|
root.isVarLength = true; // variable sized data == deferred replication == sequence data
|
||||||
|
|
||||||
|
// the next one is the replication count size : does not count in field count (x)
|
||||||
|
DataDescriptor replication = dkIter.next();
|
||||||
|
|
||||||
|
// see https://github.com/Unidata/netcdf-java/issues/1282
|
||||||
|
if (replication.x == 31)
|
||||||
|
dk.replicationCountSize = replication.bitWidth;
|
||||||
|
// Not sure about the following hard codes values and if the previous condition (replication.x == 31) already
|
||||||
|
// captures those cases automatically. Ideally need an expert for BUFR to look over these.
|
||||||
|
else if (replication.y == 0)
|
||||||
|
dk.replicationCountSize = 1; // ??
|
||||||
|
else if (replication.y == 1)
|
||||||
|
dk.replicationCountSize = 8;
|
||||||
|
else if (replication.y == 2)
|
||||||
|
dk.replicationCountSize = 16;
|
||||||
|
else if (replication.y == 11)
|
||||||
|
dk.repetitionCountSize = 8;
|
||||||
|
else if (replication.y == 12)
|
||||||
|
dk.repetitionCountSize = 16;
|
||||||
|
else
|
||||||
|
log.error("Unknown replication type= " + replication);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer to the subKey list
|
||||||
|
for (int j = 0; j < dk.x && dkIter.hasNext(); j++) {
|
||||||
|
dk.subKeys.add(dkIter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
// recurse
|
||||||
|
dk.subKeys = replicate(dk.subKeys);
|
||||||
|
|
||||||
|
} else if ((dk.f == 3) && (dk.subKeys != null)) {
|
||||||
|
dk.subKeys = replicate(dk.subKeys); // do at all levels
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.add(dk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use case:
|
||||||
|
* 3-62-1 : HEADR
|
||||||
|
* 0-4-194 : FORECAST TIME
|
||||||
|
* 0-1-205 : STATION NUMBER -- 6 DIGITS
|
||||||
|
* 0-1-198 : REPORT IDENTIFIER
|
||||||
|
* 0-5-2 : Latitude (coarse accuracy)
|
||||||
|
* 0-6-2 : Longitude (coarse accuracy)
|
||||||
|
* 0-10-194: GRID-POINT ELEVATION
|
||||||
|
* 0-2-196 : CLASS OF PROFILE OUTPUT
|
||||||
|
* 3-60-2 :
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-1 : Delayed descriptor replication factor
|
||||||
|
* 3-62-2 : PROFILE
|
||||||
|
* 0-10-4 : Pressure
|
||||||
|
* 0-12-1 : Temperature/dry-bulb temperature
|
||||||
|
* 0-11-3 : u-component
|
||||||
|
*
|
||||||
|
* where the 3-62-2 should be replicated.
|
||||||
|
* This is from NCEP bufrtab.ETACLS1. Not sure if others use this idiom.
|
||||||
|
*
|
||||||
|
* Use case 2:
|
||||||
|
* not just top level
|
||||||
|
* 3-61-37 : TMPSQ1 SYNOPTIC REPORT TEMPERATURE DATA
|
||||||
|
* 0-33-193: QMAT
|
||||||
|
* 0-12-101: TMDB
|
||||||
|
* 0-33-194: QMDD
|
||||||
|
* 0-12-103: TMDP
|
||||||
|
* 0-2-38 : MSST
|
||||||
|
* 0-33-218: QMST
|
||||||
|
* 0-22-43 : SST1
|
||||||
|
* 3-60-4 : DRP1BIT
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-38 : TMPSQ2 SYNOPTIC REPORT WET BULB TEMPERATURE DATA
|
||||||
|
* 0-2-39 : MWBT
|
||||||
|
* 0-12-102: TMWB
|
||||||
|
* 0-13-3 : REHU
|
||||||
|
* 3-60-4 : DRP1BIT
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-39 : TMPSQ3 SYNOPTIC REPORT MAXIMUM AND MINIMUM TEMPERATURE DATA
|
||||||
|
* 0-4-31 : DTH
|
||||||
|
* 0-12-111: MXTM
|
||||||
|
* 0-4-31 : DTH
|
||||||
|
* 0-12-112: MITM
|
||||||
|
*
|
||||||
|
* I think that a 3-60-4 should just be flattened:
|
||||||
|
* 3-61-37 : TMPSQ1 SYNOPTIC REPORT TEMPERATURE DATA
|
||||||
|
* 0-33-193: QMAT
|
||||||
|
* 0-12-101: TMDB
|
||||||
|
* 0-33-194: QMDD
|
||||||
|
* 0-12-103: TMDP
|
||||||
|
* 0-2-38 : MSST
|
||||||
|
* 0-33-218: QMST
|
||||||
|
* 0-22-43 : SST1
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-38 : TMPSQ2 SYNOPTIC REPORT WET BULB TEMPERATURE DATA
|
||||||
|
* 0-2-39 : MWBT
|
||||||
|
* 0-12-102: TMWB
|
||||||
|
* 0-13-3 : REHU
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-39 : TMPSQ3 SYNOPTIC REPORT MAXIMUM AND MINIMUM TEMPERATURE DATA
|
||||||
|
* 0-4-31 : DTH
|
||||||
|
* 0-12-111: MXTM
|
||||||
|
* 0-4-31 : DTH
|
||||||
|
* 0-12-112: MITM
|
||||||
|
*/
|
||||||
|
|
||||||
|
// LOOK this is NCEP specific !!
|
||||||
|
static boolean isNcepDRP(DataDescriptor key) {
|
||||||
|
return key.f == 3 && key.x == 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DataDescriptor> preflatten(List<DataDescriptor> tree) {
|
||||||
|
if (tree == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// do we need to flatten, ie have f3604 ??
|
||||||
|
boolean flatten = false;
|
||||||
|
for (DataDescriptor key : tree) {
|
||||||
|
if (isNcepDRP(key))
|
||||||
|
flatten = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flatten) {
|
||||||
|
List<DataDescriptor> result = new ArrayList<>(tree.size());
|
||||||
|
for (DataDescriptor key : tree) {
|
||||||
|
if (isNcepDRP(key)) {
|
||||||
|
result.addAll(key.subKeys); // remove f3604
|
||||||
|
} else {
|
||||||
|
result.add(key); // leave others
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tree = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recurse
|
||||||
|
for (DataDescriptor key : tree) {
|
||||||
|
key.subKeys = preflatten(key.subKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* try to grab names of compounds (structs)
|
||||||
|
* if f=1 is followed by f=3, eg:
|
||||||
|
* 0-40-20 : GQisFlagQualDetailed - Quality flag for the system
|
||||||
|
* 1-01-010: replication
|
||||||
|
* 3-40-2 : (IASI Level 1c band description)
|
||||||
|
* 0-25-140: Start channel
|
||||||
|
* 0-25-141: End channel
|
||||||
|
* 0-25-142: Channel scale factor
|
||||||
|
* 1-01-087: replication
|
||||||
|
* 3-40-3 : (IASI Level 1c 100 channels)
|
||||||
|
* 1-04-100: replication
|
||||||
|
* 2-01-136: Operator= change data width
|
||||||
|
* 0-5-42 : Channel number
|
||||||
|
* 2-01-000: Operator= change data width
|
||||||
|
* 0-14-46 : Scaled IASI radiance
|
||||||
|
* 0-2-19 : Satellite instruments
|
||||||
|
* 0-25-51 : AVHRR channel combination
|
||||||
|
* 1-01-007: replication
|
||||||
|
* 3-40-4 : (IASI Level 1c AVHRR single scene)
|
||||||
|
* 0-5-60 : Y angular position from centre of gravity
|
||||||
|
* 0-5-61 : Z angular position from centre of gravity
|
||||||
|
* 0-25-85 : Fraction of clear pixels in HIRS FOV
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* sequence:
|
||||||
|
* 3-60-4 : DRP1BIT
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-38 : TMPSQ2 SYNOPTIC REPORT WET BULB TEMPERATURE DATA
|
||||||
|
* 0-2-39 : MWBT
|
||||||
|
* 0-12-102: TMWB
|
||||||
|
* 0-13-3 : REHU
|
||||||
|
*
|
||||||
|
* which has been preflattened into:
|
||||||
|
*
|
||||||
|
* 1-01-000: replication
|
||||||
|
* 0-31-0 : DRF1BIT
|
||||||
|
* 3-61-38 : TMPSQ2 SYNOPTIC REPORT WET BULB TEMPERATURE DATA
|
||||||
|
* 0-2-39 : MWBT
|
||||||
|
* 0-12-102: TMWB
|
||||||
|
* 0-13-3 : REHU
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void grabCompoundNames(List<DataDescriptor> tree) {
|
||||||
|
|
||||||
|
for (int i = 0; i < tree.size(); i++) {
|
||||||
|
DataDescriptor key = tree.get(i);
|
||||||
|
if (key.bad)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((key.f == 3) && (key.subKeys != null)) {
|
||||||
|
grabCompoundNames(key.subKeys);
|
||||||
|
|
||||||
|
} else if (key.f == 1 && key.x == 1 && i < tree.size() - 1) { // replicator with 1 element
|
||||||
|
DataDescriptor nextKey = tree.get(i + 1);
|
||||||
|
if (nextKey.f == 3) { // the one element is a compound
|
||||||
|
if (nextKey.name != null && !nextKey.name.isEmpty())
|
||||||
|
key.name = nextKey.name;
|
||||||
|
|
||||||
|
} else if (key.y == 0 && i < tree.size() - 2) { // seq has an extra key before the 3
|
||||||
|
DataDescriptor nnKey = tree.get(i + 2);
|
||||||
|
if (nnKey.f == 3)
|
||||||
|
if (nnKey.name != null && !nnKey.name.isEmpty())
|
||||||
|
key.name = nnKey.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// flatten the compounds (type 3); but dont remove bad ones
|
||||||
|
private void flatten(List<DataDescriptor> result, List<DataDescriptor> tree) {
|
||||||
|
|
||||||
|
for (DataDescriptor key : tree) {
|
||||||
|
if (key.bad) {
|
||||||
|
root.isBad = true;
|
||||||
|
result.add(key); // add it anyway so we can see it in debug
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key.f == 3) && (key.subKeys != null)) {
|
||||||
|
flatten(result, key.subKeys);
|
||||||
|
|
||||||
|
} else if (key.f == 1) { // flatten the subtrees
|
||||||
|
List<DataDescriptor> subTree = new ArrayList<>();
|
||||||
|
flatten(subTree, key.subKeys);
|
||||||
|
key.subKeys = subTree;
|
||||||
|
result.add(key);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
result.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataDescriptor changeWidth; // 02 01 Y
|
||||||
|
private DataDescriptor changeScale; // 02 02 Y
|
||||||
|
private DataDescriptor changeRefval; // 02 03 Y
|
||||||
|
private DataDescriptor changeWtf; // 02 07 Y
|
||||||
|
private DataPresentIndicator dpi; // assume theres only one in effect at a time
|
||||||
|
|
||||||
|
private void operate(List<DataDescriptor> tree) {
|
||||||
|
if (tree == null)
|
||||||
|
return;
|
||||||
|
boolean hasAssFields = false;
|
||||||
|
// boolean hasDpiFields = false;
|
||||||
|
DataDescriptor.AssociatedField assField = null; // 02 04 Y
|
||||||
|
|
||||||
|
Iterator<DataDescriptor> iter = tree.iterator();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
DataDescriptor dd = iter.next();
|
||||||
|
|
||||||
|
if (dd.f == 2) {
|
||||||
|
if (dd.x == 1) {
|
||||||
|
changeWidth = (dd.y == 0) ? null : dd;
|
||||||
|
iter.remove();
|
||||||
|
|
||||||
|
} else if (dd.x == 2) {
|
||||||
|
changeScale = (dd.y == 0) ? null : dd;
|
||||||
|
iter.remove();
|
||||||
|
// throw new UnsupportedOperationException("2-2-Y (change scale)");
|
||||||
|
|
||||||
|
} else if (dd.x == 3) {
|
||||||
|
changeRefval = (dd.y == 255) ? null : dd;
|
||||||
|
iter.remove();
|
||||||
|
// throw new UnsupportedOperationException("2-3-Y (change reference values)"); // untested - no examples
|
||||||
|
|
||||||
|
} else if (dd.x == 4) {
|
||||||
|
assField = (dd.y == 0) ? null : new DataDescriptor.AssociatedField(dd.y);
|
||||||
|
iter.remove();
|
||||||
|
hasAssFields = true;
|
||||||
|
|
||||||
|
} else if (dd.x == 5) { // char data - this allows arbitrary string to be inserted
|
||||||
|
dd.type = 1; // String
|
||||||
|
dd.bitWidth = dd.y * 8;
|
||||||
|
dd.name = "Note";
|
||||||
|
|
||||||
|
} else if (dd.x == 6) {
|
||||||
|
// see L3-82 (3.1.6.5)
|
||||||
|
// "Y bits of data are described by the immediately following descriptor". could they speak English?
|
||||||
|
iter.remove();
|
||||||
|
if ((dd.y != 0) && iter.hasNext()) { // fnmoc using 2-6-0 as cancel (apparently)
|
||||||
|
DataDescriptor next = iter.next();
|
||||||
|
next.bitWidth = dd.y; // LOOK should it be dd.bitWidth??
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (dd.x == 7) {
|
||||||
|
changeWtf = (dd.y == 0) ? null : dd;
|
||||||
|
iter.remove();
|
||||||
|
|
||||||
|
} else if (dd.x == 36) {
|
||||||
|
if (iter.hasNext()) {
|
||||||
|
DataDescriptor dpi_dd = iter.next(); // this should be a replicated data present field
|
||||||
|
dpi = new DataPresentIndicator(tree, dpi_dd);
|
||||||
|
dd.dpi = dpi;
|
||||||
|
dpi_dd.dpi = dpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ((dd.x == 37) && (dd.y == 255)) { // cancel dpi
|
||||||
|
dpi = null;
|
||||||
|
|
||||||
|
} else if ((dd.x == 24) && (dd.y == 255)) {
|
||||||
|
dd.dpi = dpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (dd.subKeys != null) {
|
||||||
|
operate(dd.subKeys);
|
||||||
|
|
||||||
|
} else if (dd.f == 0) {
|
||||||
|
|
||||||
|
if (dd.type != 3) { // numeric or string or enum, not compound
|
||||||
|
if (changeWidth != null)
|
||||||
|
dd.bitWidth += changeWidth.y - 128;
|
||||||
|
if (changeScale != null)
|
||||||
|
dd.scale += changeScale.y - 128;
|
||||||
|
if (changeRefval != null)
|
||||||
|
dd.refVal += changeRefval.y - 128; // LOOK wrong
|
||||||
|
|
||||||
|
if (changeWtf != null && dd.type == 0) {
|
||||||
|
// see I.2 – BUFR Table C — 4
|
||||||
|
// For Table B elements, which are not CCITT IA5 (character data), code tables, or flag tables:
|
||||||
|
// 1. Add Y to the existing scale factor
|
||||||
|
// 2. Multiply the existing reference value by 10 Y
|
||||||
|
// 3. Calculate ((10 x Y) + 2) ÷ 3, disregard any fractional remainder and add the result to the existing
|
||||||
|
// bit width.
|
||||||
|
// HAHAHAHAHAHAHAHA
|
||||||
|
int y = changeWtf.y;
|
||||||
|
dd.scale += y;
|
||||||
|
dd.refVal *= Math.pow(10, y);
|
||||||
|
int wtf = ((10 * y) + 2) / 3;
|
||||||
|
dd.bitWidth += wtf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assField != null) {
|
||||||
|
assField.nfields++;
|
||||||
|
dd.assField = assField;
|
||||||
|
assField.dataFldName = dd.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAssFields)
|
||||||
|
addAssFields(tree);
|
||||||
|
// if (hasDpiFields) addDpiFields(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAssFields(List<DataDescriptor> tree) {
|
||||||
|
if (tree == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while (index < tree.size()) {
|
||||||
|
DataDescriptor dd = tree.get(index);
|
||||||
|
if (dd.assField != null) {
|
||||||
|
DataDescriptor.AssociatedField assField = dd.assField;
|
||||||
|
|
||||||
|
if ((dd.f == 0) && (dd.x == 31) && (dd.y == 21)) { // the meaning field
|
||||||
|
dd.name = assField.dataFldName + "_associated_field_significance";
|
||||||
|
dd.assField = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
DataDescriptor assDD = dd.makeAssociatedField(assField.nbits);
|
||||||
|
tree.add(index, assDD);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DataPresentIndicator {
|
||||||
|
DataDescriptor dataPresent; // replication of bit present field
|
||||||
|
List<DataDescriptor> linear; // linear list of dds
|
||||||
|
|
||||||
|
DataPresentIndicator(List<DataDescriptor> tree, DataDescriptor dpi_dd) {
|
||||||
|
this.dataPresent = dpi_dd;
|
||||||
|
linear = new ArrayList<>();
|
||||||
|
linearize(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getNfields() {
|
||||||
|
return dataPresent.replication;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void linearize(List<DataDescriptor> tree) {
|
||||||
|
for (DataDescriptor dd : tree) {
|
||||||
|
if (dd.f == 0) {
|
||||||
|
linear.add(dd);
|
||||||
|
|
||||||
|
} else if (dd.f == 1) {
|
||||||
|
for (int i = 0; i < dd.replication; i++) // whut about defered replication hahahahahah
|
||||||
|
linearize(dd.getSubKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.nc2.util.Indent;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for debugging BUFR descriptors
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Nov 16, 2009
|
||||||
|
*/
|
||||||
|
class DebugOut {
|
||||||
|
Formatter f;
|
||||||
|
Indent indent;
|
||||||
|
int fldno; // track fldno to compare with EU output
|
||||||
|
|
||||||
|
DebugOut(Formatter f) {
|
||||||
|
this.f = f;
|
||||||
|
this.indent = new Indent(2);
|
||||||
|
this.indent.setIndentLevel(0);
|
||||||
|
this.fldno = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String indent() {
|
||||||
|
return indent.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableB;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableC;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableD;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static methods to manipulate the f-x-y descriptors
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Oct 25, 2008
|
||||||
|
*/
|
||||||
|
public class Descriptor {
|
||||||
|
|
||||||
|
public static String makeString(short fxy) {
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
int x = (fxy & 0x3F00) >> 8;
|
||||||
|
int y = fxy & 0xFF;
|
||||||
|
return makeString(f, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String makeString(int f, int x, int y) {
|
||||||
|
return String.format("%d-%d-%d", f, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isWmoRange(short fxy) {
|
||||||
|
int x = (fxy & 0x3F00) >> 8;
|
||||||
|
int y = fxy & 0xFF;
|
||||||
|
return (x < 48 && y < 192);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short getFxy(String name) {
|
||||||
|
String[] tok = name.split("-");
|
||||||
|
int f = (tok.length > 0) ? Integer.parseInt(tok[0]) : 0;
|
||||||
|
int x = (tok.length > 1) ? Integer.parseInt(tok[1]) : 0;
|
||||||
|
int y = (tok.length > 2) ? Integer.parseInt(tok[2]) : 0;
|
||||||
|
return (short) ((f << 14) + (x << 8) + (y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short getFxy2(String fxxyyy) {
|
||||||
|
int fxy = Integer.parseInt(fxxyyy.trim());
|
||||||
|
int y = fxy % 1000;
|
||||||
|
fxy /= 1000;
|
||||||
|
int x = fxy % 100;
|
||||||
|
int f1 = fxy / 100;
|
||||||
|
return (short) ((f1 << 14) + (x << 8) + (y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains a BUFR table entry
|
||||||
|
public static boolean isBufrTable(short fxy) {
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
int x = (fxy & 0x3F00) >> 8;
|
||||||
|
int y = (fxy & 0xFF);
|
||||||
|
return (f == 0) && (x == 0) && (y < 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short getFxy(short f, short x, short y) {
|
||||||
|
return (short) ((f << 14) + (x << 8) + (y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] descType = {"tableB", "replication", "tableC-operators", "tableD"};
|
||||||
|
|
||||||
|
public static void show(Formatter out, short fxy, BufrTableLookup lookup) {
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
|
||||||
|
if (f == 0) {
|
||||||
|
TableB.Descriptor b = lookup.getDescriptorTableB(fxy);
|
||||||
|
if (b == null)
|
||||||
|
out.format("%-8s: NOT FOUND!!", makeString(fxy));
|
||||||
|
else
|
||||||
|
out.format("%-8s: %s", b.getFxy(), b.getName());
|
||||||
|
|
||||||
|
} else if (f == 1) {
|
||||||
|
out.format("%-8s: %s", makeString(fxy), descType[1]);
|
||||||
|
|
||||||
|
} else if (f == 2) {
|
||||||
|
int x = (fxy & 0x3F00) >> 8;
|
||||||
|
out.format("%-8s: Operator= %s", makeString(fxy), TableC.getOperatorName(x));
|
||||||
|
|
||||||
|
} else if (f == 3) {
|
||||||
|
TableD.Descriptor d = lookup.getDescriptorTableD(fxy);
|
||||||
|
if (d == null)
|
||||||
|
out.format("%-8s: NOT FOUND!!", makeString(fxy));
|
||||||
|
else
|
||||||
|
out.format("%-8s: %s", d.getFxy(), d.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getName(short fxy, BufrTableLookup lookup) {
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
|
||||||
|
if (f == 0) {
|
||||||
|
TableB.Descriptor b = lookup.getDescriptorTableB(fxy);
|
||||||
|
if (b == null)
|
||||||
|
return ("**NOT FOUND!!");
|
||||||
|
else
|
||||||
|
return b.getName();
|
||||||
|
|
||||||
|
} else if (f == 1) {
|
||||||
|
return descType[1];
|
||||||
|
|
||||||
|
} else if (f == 2) {
|
||||||
|
int x = (fxy & 0x3F00) >> 8;
|
||||||
|
return TableC.getOperatorName(x);
|
||||||
|
|
||||||
|
} else if (f == 3) {
|
||||||
|
TableD.Descriptor d = lookup.getDescriptorTableD(fxy);
|
||||||
|
if (d == null)
|
||||||
|
return "**NOT FOUND!!";
|
||||||
|
else
|
||||||
|
return d.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "illegal F=" + f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableA;
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.*;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableB;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableD;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.WmoXmlReader;
|
||||||
|
import ucar.nc2.wmo.Util;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BUFR allows you to encode a BUFR table in BUFR.
|
||||||
|
* if table is embedded, all entries must be from it
|
||||||
|
* LOOK: may be NCEP specific ?
|
||||||
|
*
|
||||||
|
* @author John
|
||||||
|
* @since 8/11/11
|
||||||
|
*/
|
||||||
|
public class EmbeddedTable {
|
||||||
|
private static final boolean showB = false;
|
||||||
|
private static final boolean showD = false;
|
||||||
|
|
||||||
|
private final RandomAccessFile raf;
|
||||||
|
private final BufrIdentificationSection ids;
|
||||||
|
|
||||||
|
private List<Message> messages = new ArrayList<>();
|
||||||
|
private boolean tableRead;
|
||||||
|
private TableA a;
|
||||||
|
private TableB b;
|
||||||
|
private TableD d;
|
||||||
|
private Structure seq1, seq2, seq3, seq4;
|
||||||
|
private TableLookup tlookup;
|
||||||
|
|
||||||
|
EmbeddedTable(Message m, RandomAccessFile raf) {
|
||||||
|
this.raf = raf;
|
||||||
|
this.ids = m.ids;
|
||||||
|
a = new TableA("embed", raf.getLocation());
|
||||||
|
b = new TableB("embed", raf.getLocation());
|
||||||
|
d = new TableD("embed", raf.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTable(Message m) {
|
||||||
|
messages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void read2() throws IOException {
|
||||||
|
Message proto = messages.get(0);
|
||||||
|
|
||||||
|
// make root sub key data descriptors name as null, so the following construct
|
||||||
|
// will have seq2 and seq3 variables
|
||||||
|
DataDescriptor root = proto.getRootDataDescriptor();
|
||||||
|
for (DataDescriptor ds : root.subKeys) {
|
||||||
|
ds.name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufrConfig config = BufrConfig.openFromMessage(raf, proto, null);
|
||||||
|
Construct2 construct = new Construct2(proto, config, new NetcdfFileSubclass());
|
||||||
|
|
||||||
|
Sequence obs = construct.getObsStructure();
|
||||||
|
seq1 = (Structure) obs.findVariable("seq1");
|
||||||
|
seq2 = (Structure) obs.findVariable("seq2");
|
||||||
|
seq3 = (Structure) obs.findVariable("seq3");
|
||||||
|
seq4 = (Structure) seq3.findVariable("seq4");
|
||||||
|
|
||||||
|
// read all the messages
|
||||||
|
ArrayStructure data;
|
||||||
|
for (Message m : messages) {
|
||||||
|
if (!m.dds.isCompressed()) {
|
||||||
|
MessageUncompressedDataReader reader = new MessageUncompressedDataReader();
|
||||||
|
data = reader.readEntireMessage(obs, proto, m, raf, null);
|
||||||
|
} else {
|
||||||
|
MessageCompressedDataReader reader = new MessageCompressedDataReader();
|
||||||
|
data = reader.readEntireMessage(obs, proto, m, raf, null);
|
||||||
|
}
|
||||||
|
while (data.hasNext()) {
|
||||||
|
StructureData sdata = (StructureData) data.next();
|
||||||
|
add(sdata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(StructureData data) throws IOException {
|
||||||
|
for (StructureMembers.Member m : data.getMembers()) {
|
||||||
|
if (showB)
|
||||||
|
System.out.printf("%s%n", m);
|
||||||
|
if (m.getDataType() == DataType.SEQUENCE) {
|
||||||
|
if (m.getName().equals("seq1")) {
|
||||||
|
ArraySequence seq = data.getArraySequence(m);
|
||||||
|
StructureDataIterator iter = seq.getStructureDataIterator();
|
||||||
|
while (iter.hasNext())
|
||||||
|
addTableEntryA(iter.next());
|
||||||
|
} else if (m.getName().equals("seq2")) {
|
||||||
|
ArraySequence seq = data.getArraySequence(m);
|
||||||
|
StructureDataIterator iter = seq.getStructureDataIterator();
|
||||||
|
while (iter.hasNext())
|
||||||
|
addTableEntryB(iter.next());
|
||||||
|
} else if (m.getName().equals("seq3")) {
|
||||||
|
ArraySequence seq = data.getArraySequence(m);
|
||||||
|
StructureDataIterator iter = seq.getStructureDataIterator();
|
||||||
|
while (iter.hasNext())
|
||||||
|
addTableEntryD(iter.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTableEntryA(StructureData sdata) {
|
||||||
|
int scale = 0, refVal = 0, width = 0;
|
||||||
|
String entry = "", line1 = "", line2 = "";
|
||||||
|
List<StructureMembers.Member> members = sdata.getMembers();
|
||||||
|
List<Variable> vars = seq1.getVariables();
|
||||||
|
for (int i = 0; i < vars.size(); i++) {
|
||||||
|
Variable v = vars.get(i);
|
||||||
|
StructureMembers.Member m = members.get(i);
|
||||||
|
String data = sdata.getScalarString(m);
|
||||||
|
Attribute att = v.attributes().findAttribute(BufrIosp2.fxyAttName);
|
||||||
|
switch (att.getStringValue()) {
|
||||||
|
case "0-0-1":
|
||||||
|
entry = sdata.getScalarString(m);
|
||||||
|
System.out.println(entry);
|
||||||
|
break;
|
||||||
|
case "0-0-2":
|
||||||
|
line1 = sdata.getScalarString(m);
|
||||||
|
System.out.println(line1);
|
||||||
|
break;
|
||||||
|
case "0-0-3":
|
||||||
|
line2 = sdata.getScalarString(m);
|
||||||
|
System.out.println(line2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = Integer.parseInt(entry);
|
||||||
|
|
||||||
|
// split name and description from appended line 1 and 2
|
||||||
|
String desc = (line1 + line2).trim();
|
||||||
|
String name = "";
|
||||||
|
int pos = desc.indexOf(' ');
|
||||||
|
if (pos > 0) {
|
||||||
|
name = desc.substring(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableA.Descriptor d = a.addDescriptor(code, desc);
|
||||||
|
d.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTableEntryB(StructureData sdata) {
|
||||||
|
String name = "", units = "", signScale = null, signRef = null;
|
||||||
|
int scale = 0, refVal = 0, width = 0;
|
||||||
|
short x1 = 0, y1 = 0;
|
||||||
|
List<StructureMembers.Member> members = sdata.getMembers();
|
||||||
|
List<Variable> vars = seq2.getVariables();
|
||||||
|
for (int i = 0; i < vars.size(); i++) {
|
||||||
|
Variable v = vars.get(i);
|
||||||
|
StructureMembers.Member m = members.get(i);
|
||||||
|
String data = sdata.getScalarString(m);
|
||||||
|
if (showB)
|
||||||
|
System.out.printf("%s == %s%n", v, data);
|
||||||
|
|
||||||
|
Attribute att = v.attributes().findAttribute(BufrIosp2.fxyAttName);
|
||||||
|
switch (att.getStringValue()) {
|
||||||
|
case "0-0-10":
|
||||||
|
sdata.getScalarString(m);
|
||||||
|
break;
|
||||||
|
case "0-0-11":
|
||||||
|
String x = sdata.getScalarString(m);
|
||||||
|
x1 = Short.parseShort(x.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-12":
|
||||||
|
String y = sdata.getScalarString(m);
|
||||||
|
y1 = Short.parseShort(y.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-13":
|
||||||
|
name = sdata.getScalarString(m);
|
||||||
|
break;
|
||||||
|
case "0-0-14":
|
||||||
|
name += sdata.getScalarString(m); // append both lines
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "0-0-15":
|
||||||
|
units = sdata.getScalarString(m);
|
||||||
|
units = WmoXmlReader.cleanUnit(units.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-16":
|
||||||
|
signScale = sdata.getScalarString(m).trim();
|
||||||
|
break;
|
||||||
|
case "0-0-17":
|
||||||
|
String scaleS = sdata.getScalarString(m);
|
||||||
|
scale = Integer.parseInt(scaleS.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-18":
|
||||||
|
signRef = sdata.getScalarString(m).trim();
|
||||||
|
break;
|
||||||
|
case "0-0-19":
|
||||||
|
String refS = sdata.getScalarString(m);
|
||||||
|
refVal = Integer.parseInt(refS.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-20":
|
||||||
|
String widthS = sdata.getScalarString(m);
|
||||||
|
width = Integer.parseInt(widthS.trim());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showB)
|
||||||
|
System.out.printf("%n");
|
||||||
|
|
||||||
|
// split name and description from appended line 1 and 2
|
||||||
|
String desc = null;
|
||||||
|
name = name.trim();
|
||||||
|
int pos = name.indexOf(' ');
|
||||||
|
if (pos > 0) {
|
||||||
|
desc = Util.cleanName(name.substring(pos + 1));
|
||||||
|
name = name.substring(0, pos);
|
||||||
|
name = Util.cleanName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("-".equals(signScale))
|
||||||
|
scale = -1 * scale;
|
||||||
|
if ("-".equals(signRef))
|
||||||
|
refVal = -1 * refVal;
|
||||||
|
|
||||||
|
b.addDescriptor(x1, y1, scale, refVal, width, name, units, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTableEntryD(StructureData sdata) throws IOException {
|
||||||
|
String name = null;
|
||||||
|
short x1 = 0, y1 = 0;
|
||||||
|
List<Short> dds = null;
|
||||||
|
|
||||||
|
List<StructureMembers.Member> members = sdata.getMembers();
|
||||||
|
List<Variable> vars = seq3.getVariables();
|
||||||
|
for (int i = 0; i < vars.size(); i++) {
|
||||||
|
Variable v = vars.get(i);
|
||||||
|
StructureMembers.Member m = members.get(i);
|
||||||
|
if (m.getName().equals("seq4")) {
|
||||||
|
dds = getDescriptors(sdata.getArraySequence(m));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Attribute att = v.attributes().findAttribute(BufrIosp2.fxyAttName);
|
||||||
|
if (att != null) {
|
||||||
|
if (showD)
|
||||||
|
System.out.printf("%s == %s%n", v, sdata.getScalarString(m));
|
||||||
|
switch (att.getStringValue()) {
|
||||||
|
case "0-0-10":
|
||||||
|
sdata.getScalarString(m);
|
||||||
|
break;
|
||||||
|
case "0-0-11":
|
||||||
|
String x = sdata.getScalarString(m);
|
||||||
|
x1 = Short.parseShort(x.trim());
|
||||||
|
break;
|
||||||
|
case "0-0-12":
|
||||||
|
String y = sdata.getScalarString(m);
|
||||||
|
y1 = Short.parseShort(y.trim());
|
||||||
|
break;
|
||||||
|
case "2-5-64":
|
||||||
|
name = sdata.getScalarString(m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showD)
|
||||||
|
System.out.printf("%n");
|
||||||
|
|
||||||
|
name = Util.cleanName(name);
|
||||||
|
|
||||||
|
d.addDescriptor(x1, y1, name, dds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Short> getDescriptors(ArraySequence seqdata) throws IOException {
|
||||||
|
List<Short> list = new ArrayList<>();
|
||||||
|
String fxyS = null;
|
||||||
|
List<Variable> vars = seq4.getVariables();
|
||||||
|
|
||||||
|
StructureDataIterator iter = seqdata.getStructureDataIterator();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
StructureData sdata = iter.next();
|
||||||
|
|
||||||
|
List<StructureMembers.Member> members = sdata.getMembers();
|
||||||
|
for (int i = 0; i < vars.size(); i++) {
|
||||||
|
Variable v = vars.get(i);
|
||||||
|
StructureMembers.Member m = members.get(i);
|
||||||
|
String data = sdata.getScalarString(m);
|
||||||
|
if (showD)
|
||||||
|
System.out.printf("%s == %s%n", v, data);
|
||||||
|
|
||||||
|
Attribute att = v.attributes().findAttribute(BufrIosp2.fxyAttName);
|
||||||
|
if (att != null && att.getStringValue().equals("0-0-30"))
|
||||||
|
fxyS = sdata.getScalarString(m);
|
||||||
|
}
|
||||||
|
if (showD)
|
||||||
|
System.out.printf("%n");
|
||||||
|
|
||||||
|
if (fxyS != null) {
|
||||||
|
short id = Descriptor.getFxy2(fxyS);
|
||||||
|
list.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableLookup getTableLookup() throws IOException {
|
||||||
|
if (!tableRead) {
|
||||||
|
read2();
|
||||||
|
tableRead = true;
|
||||||
|
tlookup = new TableLookup(ids, a, b, d);
|
||||||
|
}
|
||||||
|
return tlookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import com.google.re2j.Matcher;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsolates a complete BUFR message.
|
||||||
|
* A message has a DataDescriptor and one or more "datasets" aka "data subsets" aka "observations" aka "obs".
|
||||||
|
* Table lookup is done through getLookup().
|
||||||
|
*/
|
||||||
|
public class Message {
|
||||||
|
private static final Pattern wmoPattern = Pattern.compile(".*([IJ]..... ....) .*");
|
||||||
|
|
||||||
|
public BufrIndicatorSection is;
|
||||||
|
public BufrIdentificationSection ids;
|
||||||
|
public BufrDataDescriptionSection dds;
|
||||||
|
public BufrDataSection dataSection;
|
||||||
|
|
||||||
|
private RandomAccessFile raf;
|
||||||
|
private BufrTableLookup lookup;
|
||||||
|
private DataDescriptor root;
|
||||||
|
|
||||||
|
private String header; // wmo header
|
||||||
|
private long startPos; // starting pos in raf
|
||||||
|
private byte[] raw; // raw bytes
|
||||||
|
|
||||||
|
// bit counting
|
||||||
|
BitCounterUncompressed[] counterDatasets; // uncompressed: one for each dataset
|
||||||
|
int msg_nbits;
|
||||||
|
|
||||||
|
public Message(RandomAccessFile raf, BufrIndicatorSection is, BufrIdentificationSection ids,
|
||||||
|
BufrDataDescriptionSection dds, BufrDataSection dataSection) throws IOException {
|
||||||
|
this.raf = raf;
|
||||||
|
this.is = is;
|
||||||
|
this.ids = ids;
|
||||||
|
this.dds = dds;
|
||||||
|
this.dataSection = dataSection;
|
||||||
|
lookup = BufrTableLookup.factory(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTableLookup(TableLookup lookup) {
|
||||||
|
this.lookup.setTableLookup(lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (raf != null)
|
||||||
|
raf.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of datasets in this message.
|
||||||
|
*
|
||||||
|
* @return number of datasets in this message
|
||||||
|
*/
|
||||||
|
public int getNumberDatasets() {
|
||||||
|
return dds.getNumberDatasets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarDate getReferenceTime() {
|
||||||
|
return ids.getReferenceTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// the WMO header is in here somewhere when the message comes over the IDD
|
||||||
|
public void setHeader(String header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
// where the message starts in the file
|
||||||
|
public void setStartPos(long startPos) {
|
||||||
|
this.startPos = startPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartPos() {
|
||||||
|
return startPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRawBytes(byte[] raw) {
|
||||||
|
this.raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawBytes() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractWMO() {
|
||||||
|
Matcher matcher = wmoPattern.matcher(header);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the byte length of the entire BUFR record.
|
||||||
|
*
|
||||||
|
* @return length in bytes of BUFR record
|
||||||
|
*/
|
||||||
|
public long getMessageSize() {
|
||||||
|
return is.getBufrLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the root of the DataDescriptor tree.
|
||||||
|
*
|
||||||
|
* @return root DataDescriptor
|
||||||
|
*/
|
||||||
|
public DataDescriptor getRootDataDescriptor() {
|
||||||
|
if (root == null)
|
||||||
|
root = new DataDescriptorTreeConstructor().factory(lookup, dds);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean usesLocalTable() throws IOException {
|
||||||
|
DataDescriptor root = getRootDataDescriptor();
|
||||||
|
return usesLocalTable(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean usesLocalTable(DataDescriptor dds) {
|
||||||
|
for (DataDescriptor key : dds.getSubKeys()) {
|
||||||
|
if (key.isLocal())
|
||||||
|
return true;
|
||||||
|
if ((key.getSubKeys() != null) && usesLocalTable(key))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this message contains a BUFR table
|
||||||
|
*
|
||||||
|
* @return true if message contains a BUFR table
|
||||||
|
*/
|
||||||
|
public boolean containsBufrTable() {
|
||||||
|
for (Short key : dds.getDataDescriptors()) {
|
||||||
|
if (ucar.nc2.iosp.bufr.Descriptor.isBufrTable(key))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all descriptors were found in the tables.
|
||||||
|
*
|
||||||
|
* @return true if all dds were found.
|
||||||
|
*/
|
||||||
|
public boolean isTablesComplete() {
|
||||||
|
DataDescriptor root = getRootDataDescriptor();
|
||||||
|
return !root.isBad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufrTableLookup getLookup() {
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// bit counting
|
||||||
|
|
||||||
|
public boolean isBitCountOk() {
|
||||||
|
getRootDataDescriptor(); // make sure root is calculated
|
||||||
|
getTotalBits(); // make sure bits are counted
|
||||||
|
// int nbitsGiven = 8 * (dataSection.getDataLength() - 4);
|
||||||
|
int nbytesCounted = getCountedDataBytes();
|
||||||
|
int nbytesGiven = dataSection.getDataLength();
|
||||||
|
return Math.abs(nbytesCounted - nbytesGiven) <= 1; // radiosondes dataLen not even number of bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountedDataBytes() {
|
||||||
|
int msg_nbytes = msg_nbits / 8;
|
||||||
|
if (msg_nbits % 8 != 0)
|
||||||
|
msg_nbytes++;
|
||||||
|
msg_nbytes += 4;
|
||||||
|
if (msg_nbytes % 2 != 0)
|
||||||
|
msg_nbytes++; // LOOK seems to be violated by some messages
|
||||||
|
return msg_nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountedDataBits() {
|
||||||
|
return msg_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offset of this obs from the start of the message data.
|
||||||
|
* Use only for non compressed data
|
||||||
|
*
|
||||||
|
* @param obsOffsetInMessage index of obs in the message
|
||||||
|
* @return offset in bits
|
||||||
|
* <p/>
|
||||||
|
* public int getBitOffset(int obsOffsetInMessage) {
|
||||||
|
* if (dds.isCompressed())
|
||||||
|
* throw new IllegalArgumentException("cant call BufrMessage.getBitOffset() on compressed message");
|
||||||
|
* <p/>
|
||||||
|
* if (!root.isVarLength)
|
||||||
|
* return root.total_nbits * obsOffsetInMessage;
|
||||||
|
* <p/>
|
||||||
|
* getTotalBits(); // make sure its been set
|
||||||
|
* return nestedTableCounter[obsOffsetInMessage].getStartBit();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BitCounterUncompressed getBitCounterUncompressed(int obsOffsetInMessage) {
|
||||||
|
if (dds.isCompressed())
|
||||||
|
throw new IllegalArgumentException("cant call BufrMessage.getBitOffset() on compressed message");
|
||||||
|
|
||||||
|
calcTotalBits(null); // make sure its been set
|
||||||
|
return counterDatasets[obsOffsetInMessage];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the total number of bits taken by the data in the data section of the message.
|
||||||
|
* This is the counted number.
|
||||||
|
*
|
||||||
|
* @return total number of bits
|
||||||
|
*/
|
||||||
|
public int getTotalBits() {
|
||||||
|
if (msg_nbits == 0)
|
||||||
|
calcTotalBits(null);
|
||||||
|
return msg_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets msg_nbits as side-effect
|
||||||
|
public int calcTotalBits(Formatter out) {
|
||||||
|
try {
|
||||||
|
if (!dds.isCompressed()) {
|
||||||
|
MessageUncompressedDataReader reader = new MessageUncompressedDataReader();
|
||||||
|
reader.readData(null, this, raf, null, false, out);
|
||||||
|
} else {
|
||||||
|
MessageCompressedDataReader reader = new MessageCompressedDataReader();
|
||||||
|
reader.readData(null, this, raf, null, out);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return msg_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override hashcode to be consistent with equals.
|
||||||
|
*
|
||||||
|
* @return the hash code of dds.getDescriptors()
|
||||||
|
*/
|
||||||
|
public int hashCode() {
|
||||||
|
int result = 17;
|
||||||
|
result += 37 * result + getDDShashcode();
|
||||||
|
// result += 37 * result + ids.getCenterId();
|
||||||
|
// result += 37 * result + ids.getSubCenter_id();
|
||||||
|
result += 37 * result + ids.getCategory();
|
||||||
|
result += 37 * result + ids.getSubCategory();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDDShashcode() {
|
||||||
|
root = getRootDataDescriptor();
|
||||||
|
return root.hashCode2();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufrMessage is equal if they have the same dds.
|
||||||
|
*
|
||||||
|
* @param obj other BufrMessage
|
||||||
|
* @return true if equals
|
||||||
|
*/
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof Message))
|
||||||
|
return false;
|
||||||
|
Message o = (Message) obj;
|
||||||
|
if (!dds.getDataDescriptors().equals(o.dds.getDataDescriptors()))
|
||||||
|
return false;
|
||||||
|
if (ids.getCenterId() != o.ids.getCenterId())
|
||||||
|
return false;
|
||||||
|
// if (ids.getSubCenter_id() != o.ids.getSubCenter_id()) return false;
|
||||||
|
if (ids.getCategory() != o.ids.getCategory())
|
||||||
|
return false;
|
||||||
|
return ids.getSubCategory() == o.ids.getSubCategory();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// perhaps move this into a helper class - started from ucar.bufr.Dump
|
||||||
|
|
||||||
|
|
||||||
|
public void showMissingFields(Formatter out) throws IOException {
|
||||||
|
lookup.showMissingFields(dds.getDataDescriptors(), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump(Formatter out) { // throws IOException {
|
||||||
|
|
||||||
|
int listHash = dds.getDataDescriptors().hashCode();
|
||||||
|
out.format(" BUFR edition %d time= %s wmoHeader=%s hash=[0x%x] listHash=[0x%x] (%d) %n", is.getBufrEdition(),
|
||||||
|
getReferenceTime(), getHeader(), hashCode(), listHash, listHash);
|
||||||
|
out.format(" Category= %s %n", lookup.getCategoryFullName());
|
||||||
|
out.format(" Center= %s %n", lookup.getCenterName());
|
||||||
|
out.format(" Table= %s %n", lookup.getTableName());
|
||||||
|
out.format(" Table B= wmoTable= %s localTable= %s mode=%s%n", lookup.getWmoTableBName(),
|
||||||
|
lookup.getLocalTableBName(), lookup.getMode());
|
||||||
|
out.format(" Table D= wmoTable= %s localTable= %s%n", lookup.getWmoTableDName(), lookup.getLocalTableDName());
|
||||||
|
|
||||||
|
out.format(" DDS nsubsets=%d type=0x%x isObs=%b isCompressed=%b%n", dds.getNumberDatasets(), dds.getDataType(),
|
||||||
|
dds.isObserved(), dds.isCompressed());
|
||||||
|
|
||||||
|
long startPos = is.getStartPos();
|
||||||
|
long startData = dataSection.getDataPos();
|
||||||
|
out.format(" startPos=%d len=%d endPos=%d dataStart=%d dataLen=%d dataEnd=%d %n", startPos, is.getBufrLength(),
|
||||||
|
(startPos + is.getBufrLength()), startData, dataSection.getDataLength(),
|
||||||
|
startData + dataSection.getDataLength());
|
||||||
|
|
||||||
|
dumpDesc(out, dds.getDataDescriptors(), lookup, 4);
|
||||||
|
|
||||||
|
out.format("%n CDM Nested Table=%n");
|
||||||
|
DataDescriptor root = new DataDescriptorTreeConstructor().factory(lookup, dds);
|
||||||
|
dumpKeys(out, root, 4);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* int nbits = m.getTotalBits();
|
||||||
|
* int nbytes = (nbits % 8 == 0) ? nbits / 8 : nbits / 8 + 1;
|
||||||
|
* out.format(" totalBits = %d (%d bytes) outputBytes= %d isVarLen=%s isCompressed=%s\n\n",
|
||||||
|
* nbits, nbytes, root.getByteWidthCDM(), root.isVarLength(), m.dds.isCompressed());
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpDesc(Formatter out, List<Short> desc, BufrTableLookup table, int indent) {
|
||||||
|
if (desc == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (Short fxy : desc) {
|
||||||
|
for (int i = 0; i < indent; i++)
|
||||||
|
out.format(" ");
|
||||||
|
Descriptor.show(out, fxy, table);
|
||||||
|
out.format("%n");
|
||||||
|
int f = (fxy & 0xC000) >> 14;
|
||||||
|
if (f == 3) {
|
||||||
|
List<Short> sublist = table.getDescriptorListTableD(fxy);
|
||||||
|
dumpDesc(out, sublist, table, indent + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpKeys(Formatter out, DataDescriptor tree, int indent) {
|
||||||
|
for (DataDescriptor key : tree.subKeys) {
|
||||||
|
for (int i = 0; i < indent; i++)
|
||||||
|
out.format(" ");
|
||||||
|
out.format("%s%n", key);
|
||||||
|
if (key.getSubKeys() != null)
|
||||||
|
dumpKeys(out, key, indent + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dumpHeader(Formatter out) {
|
||||||
|
|
||||||
|
out.format(" BUFR edition %d time= %s wmoHeader=%s %n", is.getBufrEdition(), getReferenceTime(), getHeader());
|
||||||
|
out.format(" Category= %d %s %s %n", lookup.getCategory(), lookup.getCategoryName(), lookup.getCategoryNo());
|
||||||
|
out.format(" Center= %s %s %n", lookup.getCenterName(), lookup.getCenterNo());
|
||||||
|
out.format(" Table= %d.%d local= %d wmoTables= %s,%s localTables= %s,%s %n", ids.getMasterTableId(),
|
||||||
|
ids.getMasterTableVersion(), ids.getLocalTableVersion(), lookup.getWmoTableBName(), lookup.getWmoTableDName(),
|
||||||
|
lookup.getLocalTableBName(), lookup.getLocalTableDName());
|
||||||
|
|
||||||
|
out.format(" DDS nsubsets=%d type=0x%x isObs=%b isCompressed=%b%n", dds.getNumberDatasets(), dds.getDataType(),
|
||||||
|
dds.isObserved(), dds.isCompressed());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dumpHeaderShort(Formatter out) {
|
||||||
|
out.format(" %s, Cat= %s, Center= %s (%s), Table= %d.%d.%d %n", getHeader(), lookup.getCategoryName(),
|
||||||
|
lookup.getCenterName(), lookup.getCenterNo(), ids.getMasterTableId(), ids.getMasterTableVersion(),
|
||||||
|
ids.getLocalTableVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.Sequence;
|
||||||
|
import ucar.nc2.Structure;
|
||||||
|
import ucar.nc2.iosp.BitReader;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads through the data of a message.
|
||||||
|
* Can count bits / transfer all or some data to an Array.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Nov 15, 2009
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Within one message there are n obs (datasets) and s fields in each dataset.
|
||||||
|
* For compressed datasets, storage order is data(fld, obs) (obs varying fastest) :
|
||||||
|
*
|
||||||
|
* Ro1, NBINC1, I11, I12, . . . I1n
|
||||||
|
* Ro2, NBINC2, I21, I22, . . . I2n
|
||||||
|
* ...
|
||||||
|
* Ros, NBINCs, Is1, Is2, . . . Isn
|
||||||
|
*
|
||||||
|
* where Ro1, Ro2, . . . Ros are local reference values (number of bits as Table B) for field i.
|
||||||
|
* NBINC1 . . . NBINCs contain, as 6-bit quantities, the number of bits occupied by the increments that follow.
|
||||||
|
* If NBINC1 = 0, all values of element I are equal to Ro1; in such cases, the increments shall be omitted.
|
||||||
|
* For character data, NBINC shall contain the number of octets occupied by the character element.
|
||||||
|
* However, if the character data in all subsets are identical NBINC=0.
|
||||||
|
* Iij is the increment for the ith field and the jth obs.
|
||||||
|
*
|
||||||
|
* A replicated field (structure) takes a group of fields and replicates them.
|
||||||
|
* Let C be the entire compressed block for the ith field, as above.
|
||||||
|
*
|
||||||
|
* Ci = Roi, NBINCi, Ii1, Ii2, . . . Iin
|
||||||
|
*
|
||||||
|
* data:
|
||||||
|
*
|
||||||
|
* C1, (C2, C3)*r, ... Cs
|
||||||
|
*
|
||||||
|
* where r is set in the data descriptor, and is the same for all datasets.
|
||||||
|
*
|
||||||
|
* A delayed replicated field (sequence) takes a group of fields and replicates them, with the number of replications
|
||||||
|
* in the data :
|
||||||
|
*
|
||||||
|
* C1, dr, 6bits, (C2, C3)*dr, ... Cs
|
||||||
|
*
|
||||||
|
* where the width (nbits) of dr is set in the data descriptor. This dr must be the same for each dataset in the
|
||||||
|
* message.
|
||||||
|
* For some reason there is an extra 6 bits after the dr. My guess its a programming mistake that is now needed.
|
||||||
|
* There is no description of this case in the spec or the guide.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* --------------------------
|
||||||
|
*
|
||||||
|
* We use an ArrayStructureMA to hold the data, and fill it sequentially as we scan the message.
|
||||||
|
* Each field is held in an Array stored in the member.getDataArray().
|
||||||
|
* An iterator is stored in member.getDataObject() which keeps track of where we are.
|
||||||
|
* For fixed length nested Structures, we need fld(dataset, inner) but we have fld(inner, dataset) se we transpose the
|
||||||
|
* dimensions
|
||||||
|
* before we set the iterator.
|
||||||
|
* For Sequences, inner.length is the same for all datasets in the message. However, it may vary across messages.
|
||||||
|
* However, we
|
||||||
|
* only iterate over the inner sequence, never across all messages. So the implementation can be specific to the
|
||||||
|
* meassage.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MessageCompressedDataReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all datasets from a single message
|
||||||
|
*
|
||||||
|
* @param s outer variables
|
||||||
|
* @param proto prototype message, has been processed
|
||||||
|
* @param m read this message
|
||||||
|
* @param raf from this file
|
||||||
|
* @param f output bit count debugging info (may be null)
|
||||||
|
* @return ArrayStructure with all the data from the message in it.
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public ArrayStructure readEntireMessage(Structure s, Message proto, Message m, RandomAccessFile raf, Formatter f)
|
||||||
|
throws IOException {
|
||||||
|
// transfer info (refersTo, name) from the proto message
|
||||||
|
DataDescriptor.transferInfo(proto.getRootDataDescriptor().getSubKeys(), m.getRootDataDescriptor().getSubKeys());
|
||||||
|
|
||||||
|
// allocate ArrayStructureMA for outer structure
|
||||||
|
int n = m.getNumberDatasets();
|
||||||
|
ArrayStructureMA ama = ArrayStructureMA.factoryMA(s, new int[]{n});
|
||||||
|
setIterators(ama);
|
||||||
|
|
||||||
|
// map dkey to Member recursively
|
||||||
|
HashMap<DataDescriptor, StructureMembers.Member> map = new HashMap<>(100);
|
||||||
|
associateMessage2Members(ama.getStructureMembers(), m.getRootDataDescriptor(), map);
|
||||||
|
|
||||||
|
readData(m, raf, f, new Request(ama, map, null));
|
||||||
|
|
||||||
|
return ama;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read some or all datasets from a single message
|
||||||
|
*
|
||||||
|
* @param ama place data into here in order (may be null). iterators must be already set.
|
||||||
|
* @param m read this message
|
||||||
|
* @param raf from this file
|
||||||
|
* @param r which datasets, relative to this message. null == all.
|
||||||
|
* @param f output bit count debugging info (may be null)
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public void readData(ArrayStructureMA ama, Message m, RandomAccessFile raf, Range r, Formatter f) throws IOException {
|
||||||
|
// map dkey to Member recursively
|
||||||
|
HashMap<DataDescriptor, StructureMembers.Member> map = null;
|
||||||
|
if (ama != null) {
|
||||||
|
map = new HashMap<>(2 * ama.getMembers().size());
|
||||||
|
associateMessage2Members(ama.getStructureMembers(), m.getRootDataDescriptor(), map);
|
||||||
|
}
|
||||||
|
|
||||||
|
readData(m, raf, f, new Request(ama, map, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
// manage the request
|
||||||
|
private static class Request {
|
||||||
|
ArrayStructureMA ama; // data goes here, may be null
|
||||||
|
HashMap<DataDescriptor, StructureMembers.Member> map; // map of DataDescriptor to members of ama, may be null
|
||||||
|
Range r; // requested range
|
||||||
|
DpiTracker dpiTracker; // may be null
|
||||||
|
int outerRow; // if inner process needs to know what row its on
|
||||||
|
|
||||||
|
Request(ArrayStructureMA ama, HashMap<DataDescriptor, StructureMembers.Member> map, Range r) {
|
||||||
|
this.ama = ama;
|
||||||
|
this.map = map;
|
||||||
|
this.r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wantRow(int row) {
|
||||||
|
if (ama == null)
|
||||||
|
return false;
|
||||||
|
if (r == null)
|
||||||
|
return true;
|
||||||
|
return r.contains(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An iterator is stored in member.getDataObject() which keeps track of where we are.
|
||||||
|
// For fixed length nested Structures, we need fld(dataset, inner1, inner2, ...) but we have fld(inner1, inner2, ... ,
|
||||||
|
// dataset)
|
||||||
|
// so we permute the dimensions
|
||||||
|
// before we set the iterator.
|
||||||
|
public static void setIterators(ArrayStructureMA ama) {
|
||||||
|
StructureMembers sms = ama.getStructureMembers();
|
||||||
|
for (StructureMembers.Member sm : sms.getMembers()) {
|
||||||
|
Array data = sm.getDataArray();
|
||||||
|
if (data instanceof ArrayStructureMA) {
|
||||||
|
setIterators((ArrayStructureMA) data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int[] shape = data.getShape();
|
||||||
|
if ((shape.length > 1) && (sm.getDataType() != DataType.CHAR)) {
|
||||||
|
Array datap;
|
||||||
|
if (shape.length == 2)
|
||||||
|
datap = data.transpose(0, 1);
|
||||||
|
else {
|
||||||
|
int[] pdims = new int[shape.length]; // (0,1,2,3...) -> (1,2,3...,0)
|
||||||
|
for (int i = 0; i < shape.length - 1; i++)
|
||||||
|
pdims[i] = i + 1;
|
||||||
|
datap = data.permute(pdims);
|
||||||
|
}
|
||||||
|
sm.setDataObject(datap.getIndexIterator());
|
||||||
|
} else {
|
||||||
|
sm.setDataObject(data.getIndexIterator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void associateMessage2Members(StructureMembers members, DataDescriptor parent,
|
||||||
|
HashMap<DataDescriptor, StructureMembers.Member> map) {
|
||||||
|
for (DataDescriptor dkey : parent.getSubKeys()) {
|
||||||
|
if (dkey.name == null) {
|
||||||
|
if (dkey.getSubKeys() != null)
|
||||||
|
associateMessage2Members(members, dkey, map);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StructureMembers.Member m = members.findMember(dkey.name);
|
||||||
|
if (m != null) {
|
||||||
|
map.put(dkey, m);
|
||||||
|
|
||||||
|
if (m.getDataType() == DataType.STRUCTURE) {
|
||||||
|
ArrayStructure nested = (ArrayStructure) m.getDataArray();
|
||||||
|
if (dkey.getSubKeys() != null)
|
||||||
|
associateMessage2Members(nested.getStructureMembers(), dkey, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (dkey.getSubKeys() != null)
|
||||||
|
associateMessage2Members(members, dkey, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read / count the bits in a compressed message
|
||||||
|
private int readData(Message m, RandomAccessFile raf, Formatter f, Request req) throws IOException {
|
||||||
|
|
||||||
|
BitReader reader = new BitReader(raf, m.dataSection.getDataPos() + 4);
|
||||||
|
DataDescriptor root = m.getRootDataDescriptor();
|
||||||
|
if (root.isBad)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DebugOut out = (f == null) ? null : new DebugOut(f);
|
||||||
|
BitCounterCompressed[] counterFlds = new BitCounterCompressed[root.subKeys.size()]; // one for each field LOOK why
|
||||||
|
// not m.counterFlds ?
|
||||||
|
readData(out, reader, counterFlds, root, 0, m.getNumberDatasets(), req);
|
||||||
|
|
||||||
|
m.msg_nbits = 0;
|
||||||
|
for (BitCounterCompressed counter : counterFlds)
|
||||||
|
if (counter != null)
|
||||||
|
m.msg_nbits += counter.getTotalBits();
|
||||||
|
return m.msg_nbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param out debug info; may be null
|
||||||
|
* @param reader raf wrapper for bit reading
|
||||||
|
* @param fldCounters one for each field
|
||||||
|
* @param parent parent.subkeys() holds the fields
|
||||||
|
* @param bitOffset bit offset from beginning of data
|
||||||
|
* @param ndatasets number of compressed datasets
|
||||||
|
* @param req for writing into the ArrayStructure;
|
||||||
|
* @return bitOffset
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
private int readData(DebugOut out, BitReader reader, BitCounterCompressed[] fldCounters, DataDescriptor parent,
|
||||||
|
int bitOffset, int ndatasets, Request req) throws IOException {
|
||||||
|
|
||||||
|
List<DataDescriptor> flds = parent.getSubKeys();
|
||||||
|
for (int fldidx = 0; fldidx < flds.size(); fldidx++) {
|
||||||
|
DataDescriptor dkey = flds.get(fldidx);
|
||||||
|
if (!dkey.isOkForVariable()) { // dds with no data to read
|
||||||
|
|
||||||
|
// the dpi nightmare
|
||||||
|
if ((dkey.f == 2) && (dkey.x == 36)) {
|
||||||
|
req.dpiTracker = new DpiTracker(dkey.dpi, dkey.dpi.getNfields());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%s %d %s (%s) %n", out.indent(), out.fldno++, dkey.name, dkey.getFxyName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitCounterCompressed counter = new BitCounterCompressed(dkey, ndatasets, bitOffset);
|
||||||
|
fldCounters[fldidx] = counter;
|
||||||
|
|
||||||
|
// sequence
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
reader.setBitOffset(bitOffset);
|
||||||
|
int count = (int) reader.bits2UInt(dkey.replicationCountSize);
|
||||||
|
bitOffset += dkey.replicationCountSize;
|
||||||
|
|
||||||
|
reader.bits2UInt(6);
|
||||||
|
if (null != out)
|
||||||
|
out.f.format("%s--sequence %s bitOffset=%d replication=%s %n", out.indent(), dkey.getFxyName(), bitOffset,
|
||||||
|
count);
|
||||||
|
bitOffset += 6; // LOOK seems to be an extra 6 bits.
|
||||||
|
|
||||||
|
counter.addNestedCounters(count);
|
||||||
|
|
||||||
|
// make an ArrayObject of ArraySequence, place it into the data array
|
||||||
|
bitOffset = makeArraySequenceCompressed(out, reader, counter, dkey, bitOffset, ndatasets, count, req);
|
||||||
|
// if (null != out) out.f.format("--back %s %d %n", dkey.getFxyName(), bitOffset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// structure
|
||||||
|
if (dkey.type == 3) {
|
||||||
|
if (null != out)
|
||||||
|
out.f.format("%s--structure %s bitOffset=%d replication=%s %n", out.indent(), dkey.getFxyName(), bitOffset,
|
||||||
|
dkey.replication);
|
||||||
|
|
||||||
|
// p 11 of "standard", doesnt really describe the case of replication AND compression
|
||||||
|
counter.addNestedCounters(dkey.replication);
|
||||||
|
for (int i = 0; i < dkey.replication; i++) {
|
||||||
|
BitCounterCompressed[] nested = counter.getNestedCounters(i);
|
||||||
|
req.outerRow = i;
|
||||||
|
if (null != out) {
|
||||||
|
out.f.format("%n");
|
||||||
|
out.indent.incr();
|
||||||
|
bitOffset = readData(out, reader, nested, dkey, bitOffset, ndatasets, req);
|
||||||
|
out.indent.decr();
|
||||||
|
} else {
|
||||||
|
bitOffset = readData(null, reader, nested, dkey, bitOffset, ndatasets, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (null != out) out.f.format("--back %s %d %n", dkey.getFxyName(), bitOffset);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other fields
|
||||||
|
|
||||||
|
StructureMembers.Member member;
|
||||||
|
IndexIterator iter = null;
|
||||||
|
ArrayStructure dataDpi = null; // if iter is missing - for the dpi case
|
||||||
|
if (req.map != null) {
|
||||||
|
member = req.map.get(dkey);
|
||||||
|
iter = (IndexIterator) member.getDataObject();
|
||||||
|
if (iter == null) {
|
||||||
|
dataDpi = (ArrayStructure) member.getDataArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setBitOffset(bitOffset); // ?? needed ??
|
||||||
|
|
||||||
|
// char data special case
|
||||||
|
if (dkey.type == 1) {
|
||||||
|
int nc = dkey.bitWidth / 8;
|
||||||
|
byte[] minValue = new byte[nc];
|
||||||
|
for (int i = 0; i < nc; i++)
|
||||||
|
minValue[i] = (byte) reader.bits2UInt(8);
|
||||||
|
int dataWidth = (int) reader.bits2UInt(6); // incremental data width in bytes
|
||||||
|
counter.setDataWidth(8 * dataWidth);
|
||||||
|
int totalWidth = dkey.bitWidth + 6 + 8 * dataWidth * ndatasets; // total width in bits for this compressed set
|
||||||
|
// of values
|
||||||
|
bitOffset += totalWidth; // bitOffset now points to the next field
|
||||||
|
|
||||||
|
if (null != out)
|
||||||
|
out.f.format("%s read %d %s (%s) bitWidth=%d defValue=%s dataWidth=%d n=%d bitOffset=%d %n", out.indent(),
|
||||||
|
out.fldno++, dkey.name, dkey.getFxyName(), dkey.bitWidth, new String(minValue, StandardCharsets.UTF_8),
|
||||||
|
dataWidth, ndatasets, bitOffset);
|
||||||
|
|
||||||
|
if (iter != null) {
|
||||||
|
for (int dataset = 0; dataset < ndatasets; dataset++) {
|
||||||
|
if (dataWidth == 0) { // use the min value
|
||||||
|
if (req.wantRow(dataset))
|
||||||
|
for (int i = 0; i < nc; i++)
|
||||||
|
iter.setCharNext((char) minValue[i]); // ??
|
||||||
|
|
||||||
|
} else { // read the incremental value
|
||||||
|
int nt = Math.min(nc, dataWidth);
|
||||||
|
byte[] incValue = new byte[nc];
|
||||||
|
for (int i = 0; i < nt; i++)
|
||||||
|
incValue[i] = (byte) reader.bits2UInt(8);
|
||||||
|
for (int i = nt; i < nc; i++) // can dataWidth < n ?
|
||||||
|
incValue[i] = 0;
|
||||||
|
|
||||||
|
if (req.wantRow(dataset))
|
||||||
|
for (int i = 0; i < nc; i++) {
|
||||||
|
int cval = incValue[i];
|
||||||
|
if (cval < 32 || cval > 126)
|
||||||
|
cval = 0; // printable ascii KLUDGE!
|
||||||
|
iter.setCharNext((char) cval); // ??
|
||||||
|
}
|
||||||
|
if (out != null)
|
||||||
|
out.f.format(" %s,", new String(incValue, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// numeric fields
|
||||||
|
int useBitWidth = dkey.bitWidth;
|
||||||
|
|
||||||
|
// a dpi Field needs to be substituted
|
||||||
|
boolean isDpi = ((dkey.f == 0) && (dkey.x == 31) && (dkey.y == 31));
|
||||||
|
boolean isDpiField = false;
|
||||||
|
if ((dkey.f == 2) && (dkey.x == 24) && (dkey.y == 255)) {
|
||||||
|
isDpiField = true;
|
||||||
|
DataDescriptor dpiDD = req.dpiTracker.getDpiDD(req.outerRow);
|
||||||
|
useBitWidth = dpiDD.bitWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
long dataMin = reader.bits2UInt(useBitWidth);
|
||||||
|
int dataWidth = (int) reader.bits2UInt(6); // increment data width - always in 6 bits, so max is 2^6 = 64
|
||||||
|
if (dataWidth > useBitWidth && (null != out))
|
||||||
|
out.f.format(" BAD WIDTH ");
|
||||||
|
if (dkey.type == 1)
|
||||||
|
dataWidth *= 8; // char data count is in bytes
|
||||||
|
counter.setDataWidth(dataWidth);
|
||||||
|
|
||||||
|
int totalWidth = useBitWidth + 6 + dataWidth * ndatasets; // total width in bits for this compressed set of values
|
||||||
|
bitOffset += totalWidth; // bitOffset now points to the next field
|
||||||
|
|
||||||
|
if (null != out)
|
||||||
|
out.f.format("%s read %d, %s (%s) bitWidth=%d dataMin=%d (%f) dataWidth=%d n=%d bitOffset=%d %n", out.indent(),
|
||||||
|
out.fldno++, dkey.name, dkey.getFxyName(), useBitWidth, dataMin, dkey.convert(dataMin), dataWidth,
|
||||||
|
ndatasets, bitOffset);
|
||||||
|
|
||||||
|
// numeric fields
|
||||||
|
|
||||||
|
// if dataWidth == 0, just use min value, otherwise read the compressed value here
|
||||||
|
for (int dataset = 0; dataset < ndatasets; dataset++) {
|
||||||
|
long value = dataMin;
|
||||||
|
|
||||||
|
if (dataWidth > 0) {
|
||||||
|
long cv = reader.bits2UInt(dataWidth);
|
||||||
|
if (BufrNumbers.isMissing(cv, dataWidth))
|
||||||
|
value = BufrNumbers.missingValue(useBitWidth); // set to missing value
|
||||||
|
else // add to minimum
|
||||||
|
value += cv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround for malformed messages
|
||||||
|
if (dataWidth > useBitWidth) {
|
||||||
|
long missingVal = BufrNumbers.missingValue(useBitWidth);
|
||||||
|
if ((value & missingVal) != value) // overflow
|
||||||
|
value = missingVal; // replace with missing value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.wantRow(dataset)) {
|
||||||
|
if (isDpiField) {
|
||||||
|
if (dataDpi != null) {
|
||||||
|
DataDescriptor dpiDD = req.dpiTracker.getDpiDD(req.outerRow);
|
||||||
|
StructureMembers sms = dataDpi.getStructureMembers();
|
||||||
|
StructureMembers.Member m0 = sms.getMember(0);
|
||||||
|
IndexIterator iter2 = (IndexIterator) m0.getDataObject();
|
||||||
|
iter2.setObjectNext(dpiDD.getName());
|
||||||
|
|
||||||
|
StructureMembers.Member m1 = sms.getMember(1);
|
||||||
|
iter2 = (IndexIterator) m1.getDataObject();
|
||||||
|
iter2.setFloatNext(dpiDD.convert(value));
|
||||||
|
}
|
||||||
|
} else if (iter != null) {
|
||||||
|
iter.setLongNext(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// since dpi must be the same for all datasets, just keep the first one
|
||||||
|
if (isDpi && (dataset == 0))
|
||||||
|
req.dpiTracker.setDpiValue(req.outerRow, value); // keep track of dpi values in the tracker - perhaps not
|
||||||
|
// expose
|
||||||
|
|
||||||
|
if ((out != null) && (dataWidth > 0))
|
||||||
|
out.f.format(" %d (%f)", value, dkey.convert(value));
|
||||||
|
}
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read in the data into an ArrayStructureMA, holding an ArrayObject() of ArraySequence
|
||||||
|
private int makeArraySequenceCompressed(DebugOut out, BitReader reader, BitCounterCompressed bitCounterNested,
|
||||||
|
DataDescriptor seqdd, int bitOffset, int ndatasets, int count, Request req) throws IOException {
|
||||||
|
|
||||||
|
// construct ArrayStructureMA and associated map
|
||||||
|
ArrayStructureMA ama = null;
|
||||||
|
StructureMembers members = null;
|
||||||
|
HashMap<DataDescriptor, StructureMembers.Member> nmap = null;
|
||||||
|
if (req.map != null) {
|
||||||
|
Sequence seq = (Sequence) seqdd.refersTo;
|
||||||
|
int[] shape = {ndatasets, count}; // seems unlikely this can handle recursion
|
||||||
|
ama = ArrayStructureMA.factoryMA(seq, shape);
|
||||||
|
setIterators(ama);
|
||||||
|
|
||||||
|
members = ama.getStructureMembers();
|
||||||
|
nmap = new HashMap<>(2 * members.getMembers().size());
|
||||||
|
associateMessage2Members(members, seqdd, nmap);
|
||||||
|
}
|
||||||
|
Request nreq = new Request(ama, nmap, req.r);
|
||||||
|
|
||||||
|
// iterate over the number of replications, reading ndataset compressed values at each iteration
|
||||||
|
if (out != null)
|
||||||
|
out.indent.incr();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
BitCounterCompressed[] nested = bitCounterNested.getNestedCounters(i);
|
||||||
|
nreq.outerRow = i;
|
||||||
|
bitOffset = readData(out, reader, nested, seqdd, bitOffset, ndatasets, nreq);
|
||||||
|
}
|
||||||
|
if (out != null)
|
||||||
|
out.indent.decr();
|
||||||
|
|
||||||
|
// add ArraySequence to the ArrayObject in the outer structure
|
||||||
|
if (req.map != null) {
|
||||||
|
StructureMembers.Member m = req.map.get(seqdd);
|
||||||
|
ArrayObject arrObj = (ArrayObject) m.getDataArray();
|
||||||
|
|
||||||
|
// we need to break ama into separate sequences, one for each dataset
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i < ndatasets; i++) {
|
||||||
|
ArraySequence arrSeq = new ArraySequence(members, new SequenceIterator(start, count, ama), count);
|
||||||
|
arrObj.setObject(i, arrSeq);
|
||||||
|
start += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DpiTracker {
|
||||||
|
DataDescriptorTreeConstructor.DataPresentIndicator dpi;
|
||||||
|
boolean[] isPresent;
|
||||||
|
List<DataDescriptor> dpiDD;
|
||||||
|
|
||||||
|
DpiTracker(DataDescriptorTreeConstructor.DataPresentIndicator dpi, int nPresentFlags) {
|
||||||
|
this.dpi = dpi;
|
||||||
|
isPresent = new boolean[nPresentFlags];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDpiValue(int fldidx, long value) {
|
||||||
|
isPresent[fldidx] = (value == 0); // present if the value is zero
|
||||||
|
}
|
||||||
|
|
||||||
|
DataDescriptor getDpiDD(int fldPresentIndex) {
|
||||||
|
if (dpiDD == null) {
|
||||||
|
dpiDD = new ArrayList<>();
|
||||||
|
for (int i = 0; i < isPresent.length; i++) {
|
||||||
|
if (isPresent[i])
|
||||||
|
dpiDD.add(dpi.linear.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dpiDD.get(fldPresentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDpiDDs(DataDescriptor dkey) {
|
||||||
|
return (dkey.f == 2) && (dkey.x == 24) && (dkey.y == 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDpiField(DataDescriptor dkey) {
|
||||||
|
return (dkey.f == 2) && (dkey.x == 24) && (dkey.y == 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.unidata.io.KMPMatch;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequentially scans a BUFR file, extracts the messages.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since May 9, 2008
|
||||||
|
*/
|
||||||
|
public class MessageScanner {
|
||||||
|
// static public final int MAX_MESSAGE_SIZE = 500 * 1000; // GTS allows up to 500 Kb messages (ref?)
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MessageScanner.class);
|
||||||
|
|
||||||
|
private static final KMPMatch matcher = new KMPMatch("BUFR".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is this a valid BUFR file.
|
||||||
|
*
|
||||||
|
* @param raf check this file
|
||||||
|
* @return true if its a BUFR file
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public static boolean isValidFile(RandomAccessFile raf) throws IOException {
|
||||||
|
raf.seek(0);
|
||||||
|
if (!raf.searchForward(matcher, 40 * 1000))
|
||||||
|
return false; // must find "BUFR" in first 40k
|
||||||
|
raf.skipBytes(4);
|
||||||
|
BufrIndicatorSection is = new BufrIndicatorSection(raf);
|
||||||
|
if (is.getBufrEdition() > 4)
|
||||||
|
return false;
|
||||||
|
// if(is.getBufrLength() > MAX_MESSAGE_SIZE) return false;
|
||||||
|
return !(is.getBufrLength() > raf.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
|
||||||
|
private RandomAccessFile raf;
|
||||||
|
private boolean useEmbeddedTables;
|
||||||
|
|
||||||
|
private int countMsgs;
|
||||||
|
private int countObs;
|
||||||
|
private byte[] header;
|
||||||
|
private long startPos;
|
||||||
|
private long lastPos;
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
private EmbeddedTable embedTable;
|
||||||
|
|
||||||
|
public MessageScanner(RandomAccessFile raf) throws IOException {
|
||||||
|
this(raf, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageScanner(RandomAccessFile raf, long startPos, boolean useEmbeddedTables) throws IOException {
|
||||||
|
startPos = (startPos < 30) ? 0 : startPos - 30; // look for the header
|
||||||
|
this.raf = raf;
|
||||||
|
lastPos = startPos;
|
||||||
|
this.useEmbeddedTables = useEmbeddedTables;
|
||||||
|
raf.seek(startPos);
|
||||||
|
raf.order(RandomAccessFile.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message getFirstDataMessage() throws IOException {
|
||||||
|
while (hasNext()) {
|
||||||
|
Message m = next();
|
||||||
|
if (m == null)
|
||||||
|
continue;
|
||||||
|
if (m.containsBufrTable())
|
||||||
|
continue; // not data
|
||||||
|
if (m.getNumberDatasets() == 0)
|
||||||
|
continue; // empty
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
lastPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() throws IOException {
|
||||||
|
if (lastPos >= raf.length())
|
||||||
|
return false;
|
||||||
|
raf.seek(lastPos);
|
||||||
|
boolean more = raf.searchForward(matcher, -1); // will scan to end for another BUFR header
|
||||||
|
if (more) {
|
||||||
|
long stop = raf.getFilePointer();
|
||||||
|
int sizeHeader = (int) (stop - lastPos);
|
||||||
|
if (sizeHeader > 30)
|
||||||
|
sizeHeader = 30;
|
||||||
|
header = new byte[sizeHeader];
|
||||||
|
startPos = stop - sizeHeader;
|
||||||
|
raf.seek(startPos);
|
||||||
|
int nRead = raf.read(header);
|
||||||
|
if (nRead != header.length) {
|
||||||
|
log.warn("Unable to read full BUFR header. Got " + nRead + " but expected " + header.length);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug && countMsgs % 100 == 0)
|
||||||
|
System.out.printf("%d ", countMsgs);
|
||||||
|
return more;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message next() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
long start = raf.getFilePointer();
|
||||||
|
raf.seek(start + 4);
|
||||||
|
|
||||||
|
BufrIndicatorSection is = new BufrIndicatorSection(raf);
|
||||||
|
BufrIdentificationSection ids = new BufrIdentificationSection(raf, is);
|
||||||
|
BufrDataDescriptionSection dds = new BufrDataDescriptionSection(raf);
|
||||||
|
|
||||||
|
long dataPos = raf.getFilePointer();
|
||||||
|
int dataLength = BufrNumbers.uint3(raf);
|
||||||
|
BufrDataSection dataSection = new BufrDataSection(dataPos, dataLength);
|
||||||
|
lastPos = dataPos + dataLength + 4; // position to the end message plus 1
|
||||||
|
// nbytes += lastPos - startPos;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* length consistency checks
|
||||||
|
* if (is.getBufrLength() > MAX_MESSAGE_SIZE) {
|
||||||
|
* log.warn("Illegal length - BUFR message at pos "+start+" header= "+cleanup(header)+" size= "+is.getBufrLength()
|
||||||
|
* );
|
||||||
|
* return null;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (is.getBufrEdition() > 4) {
|
||||||
|
log.warn("Illegal edition - BUFR message at pos " + start + " header= " + cleanup(header));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.getBufrEdition() < 2) {
|
||||||
|
log.warn("Edition " + is.getBufrEdition() + " is not supported - BUFR message at pos " + start + " header= "
|
||||||
|
+ cleanup(header));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that end section is correct
|
||||||
|
long ending = dataPos + dataLength;
|
||||||
|
raf.seek(dataPos + dataLength);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (raf.read() != 55) {
|
||||||
|
log.warn("Missing End of BUFR message at pos= {} header= {} file= {}", ending, cleanup(header),
|
||||||
|
raf.getLocation());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// allow off by one : may happen when dataLength rounded to even bytes
|
||||||
|
if (raf.read() != 55) {
|
||||||
|
raf.seek(dataPos + dataLength - 1); // see if byte before is a '7'
|
||||||
|
if (raf.read() != 55) {
|
||||||
|
log.warn("Missing End of BUFR message at pos= {} header= {} edition={} file= {}", ending, cleanup(header),
|
||||||
|
is.getBufrEdition(), raf.getLocation());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
log.info("End of BUFR message off-by-one at pos= {} header= {} edition={} file= {}", ending, cleanup(header),
|
||||||
|
is.getBufrEdition(), raf.getLocation());
|
||||||
|
lastPos--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message m = new Message(raf, is, ids, dds, dataSection);
|
||||||
|
m.setHeader(cleanup(header));
|
||||||
|
m.setStartPos(start);
|
||||||
|
|
||||||
|
if (useEmbeddedTables && m.containsBufrTable()) {
|
||||||
|
if (embedTable == null)
|
||||||
|
embedTable = new EmbeddedTable(m, raf);
|
||||||
|
embedTable.addTable(m);
|
||||||
|
} else if (embedTable != null) {
|
||||||
|
m.setTableLookup(embedTable.getTableLookup());
|
||||||
|
}
|
||||||
|
|
||||||
|
countMsgs++;
|
||||||
|
countObs += dds.getNumberDatasets();
|
||||||
|
raf.seek(start + is.getBufrLength());
|
||||||
|
return m;
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
log.error("Error reading message at " + lastPos, ioe);
|
||||||
|
lastPos = raf.getFilePointer(); // dont do an infinite loop
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableLookup getTableLookup() throws IOException {
|
||||||
|
while (hasNext()) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
return (embedTable != null) ? embedTable.getTableLookup() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getMessageBytesFromLast(ucar.nc2.iosp.bufr.Message m) throws IOException {
|
||||||
|
long startPos = m.getStartPos();
|
||||||
|
int length = (int) (lastPos - startPos);
|
||||||
|
byte[] result = new byte[length];
|
||||||
|
|
||||||
|
raf.seek(startPos);
|
||||||
|
raf.readFully(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getMessageBytes(Message m) throws IOException {
|
||||||
|
long startPos = m.getStartPos();
|
||||||
|
int length = m.is.getBufrLength();
|
||||||
|
byte[] result = new byte[length];
|
||||||
|
|
||||||
|
raf.seek(startPos);
|
||||||
|
raf.readFully(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalObs() {
|
||||||
|
return countObs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalMessages() {
|
||||||
|
return countMsgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the WMO header is in here somewhere when the message comes over the IDD
|
||||||
|
private static String cleanup(byte[] h) {
|
||||||
|
byte[] bb = new byte[h.length];
|
||||||
|
int count = 0;
|
||||||
|
for (byte b : h) {
|
||||||
|
if (b >= 32 && b < 127)
|
||||||
|
bb[count++] = b;
|
||||||
|
}
|
||||||
|
return new String(bb, 0, count, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long writeCurrentMessage(WritableByteChannel out) throws IOException {
|
||||||
|
long nbytes = lastPos - startPos;
|
||||||
|
return raf.readToByteChannel(out, startPos, nbytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,370 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.Sequence;
|
||||||
|
import ucar.nc2.Structure;
|
||||||
|
import ucar.nc2.Variable;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data for uncompressed messages.
|
||||||
|
*
|
||||||
|
* Within one message there are n obs (datasets) and s fields in each dataset.
|
||||||
|
* For uncompressed datasets, storage order is data(obs, fld) (fld varying fastest) :
|
||||||
|
*
|
||||||
|
* R11, R12, R13, . . . R1s
|
||||||
|
* R21, R22, R23, . . . R2s
|
||||||
|
* ....
|
||||||
|
* Rn1, Rn2, Rn3, . . . Rns
|
||||||
|
*
|
||||||
|
* where Rij is the jth value of the ith data subset.
|
||||||
|
* the datasets each occupy an identical number of bits, unless delayed replication is used,
|
||||||
|
* and are not necessarily aligned on octet boundaries.
|
||||||
|
*
|
||||||
|
* A replicated field (structure) takes a group of fields and replicates them:
|
||||||
|
*
|
||||||
|
* Ri1, (Ri2, Ri3)*r, . . . Ris
|
||||||
|
*
|
||||||
|
* where r is set in the data descriptor, and is the same for all datasets.
|
||||||
|
*
|
||||||
|
* A delayed replicated field (sequence) takes a group of fields and replicates them, and adds the number of
|
||||||
|
* replications
|
||||||
|
* in the data :
|
||||||
|
*
|
||||||
|
* Ri1, dri, (Ri2, Ri3)*dri, . . . Ris
|
||||||
|
*
|
||||||
|
* where the width (nbits) of dr is set in the data descriptor. This dr can be different for each dataset in the
|
||||||
|
* message.
|
||||||
|
* It can be 0. When it has a bit width of 1, it indicates an optional set of fields.
|
||||||
|
*
|
||||||
|
* --------------------------
|
||||||
|
*
|
||||||
|
* We use an ArrayStructureBB to hold the data, and fill it sequentially as we scan the message.
|
||||||
|
* Fixed length nested Structures are kept in the ArrayStructureBB.
|
||||||
|
* Variable length objects (Strings, Sequences) are added to the heap.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MessageUncompressedDataReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all datasets from a single message
|
||||||
|
*
|
||||||
|
* @param s outer variables
|
||||||
|
* @param proto prototype message, has been processed
|
||||||
|
* @param m read this message
|
||||||
|
* @param raf from this file
|
||||||
|
* @param f output bit count debugging info (may be null)
|
||||||
|
* @return ArraySTructure with all the data from the message in it.
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
ArrayStructure readEntireMessage(Structure s, Message proto, Message m, RandomAccessFile raf, Formatter f)
|
||||||
|
throws IOException {
|
||||||
|
// transfer info from proto message
|
||||||
|
DataDescriptor.transferInfo(proto.getRootDataDescriptor().getSubKeys(), m.getRootDataDescriptor().getSubKeys());
|
||||||
|
|
||||||
|
// allocate ArrayStructureBB for outer structure
|
||||||
|
// This assumes that all of the fields and all of the datasets are being read
|
||||||
|
StructureMembers members = s.makeStructureMembers();
|
||||||
|
ArrayStructureBB.setOffsets(members);
|
||||||
|
|
||||||
|
int n = m.getNumberDatasets();
|
||||||
|
ArrayStructureBB abb = new ArrayStructureBB(members, new int[] {n});
|
||||||
|
ByteBuffer bb = abb.getByteBuffer();
|
||||||
|
bb.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
boolean addTime = false; // (s.findVariable(BufrIosp2.TIME_NAME) != null);
|
||||||
|
readData(abb, m, raf, null, addTime, f);
|
||||||
|
return abb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read some or all datasets from a single message
|
||||||
|
*
|
||||||
|
* @param abb place data into here in order (may be null)
|
||||||
|
* @param m read this message
|
||||||
|
* @param raf from this file
|
||||||
|
* @param r which datasets, relative to this message. null == all.
|
||||||
|
* @param addTime add the time coordinate
|
||||||
|
* @param f output bit count debugging info (may be null)
|
||||||
|
* @return number of datasets read
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
public int readData(ArrayStructureBB abb, Message m, RandomAccessFile raf, Range r, boolean addTime, Formatter f)
|
||||||
|
throws IOException {
|
||||||
|
BitReader reader = new BitReader(raf, m.dataSection.getDataPos() + 4);
|
||||||
|
DataDescriptor root = m.getRootDataDescriptor();
|
||||||
|
if (root.isBad)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Request req = new Request(abb, r);
|
||||||
|
|
||||||
|
int n = m.getNumberDatasets();
|
||||||
|
m.counterDatasets = new BitCounterUncompressed[n]; // one for each dataset
|
||||||
|
m.msg_nbits = 0;
|
||||||
|
|
||||||
|
// loop over the rows
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (f != null)
|
||||||
|
f.format("Count bits in observation %d%n", i);
|
||||||
|
// the top table always has exactly one "row", since we are working with a single obs
|
||||||
|
m.counterDatasets[i] = new BitCounterUncompressed(root, 1, 0);
|
||||||
|
DebugOut out = (f == null) ? null : new DebugOut(f);
|
||||||
|
|
||||||
|
req.setRow(i);
|
||||||
|
if (req.wantRow() && addTime) {
|
||||||
|
req.bb.putInt(0); // placeholder for time assumes an int
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
readData(out, reader, m.counterDatasets[i], root.subKeys, 0, req);
|
||||||
|
m.msg_nbits += m.counterDatasets[i].countBits(m.msg_nbits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Request {
|
||||||
|
ArrayStructureBB abb;
|
||||||
|
ByteBuffer bb;
|
||||||
|
Range r;
|
||||||
|
int row;
|
||||||
|
|
||||||
|
Request(ArrayStructureBB abb, Range r) {
|
||||||
|
this.abb = abb;
|
||||||
|
if (abb != null)
|
||||||
|
bb = abb.getByteBuffer();
|
||||||
|
this.r = r;
|
||||||
|
this.row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request setRow(int row) {
|
||||||
|
this.row = row;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wantRow() {
|
||||||
|
if (abb == null)
|
||||||
|
return false;
|
||||||
|
if (r == null)
|
||||||
|
return true;
|
||||||
|
return r.contains(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* count/read the bits in one row of a "nested table", defined by List<DataDescriptor> dkeys.
|
||||||
|
*
|
||||||
|
* @param out optional debug output, may be null
|
||||||
|
* @param reader read data with this
|
||||||
|
* @param dkeys the fields of the table
|
||||||
|
* @param table put the results here
|
||||||
|
* @param nestedRow which row of the table
|
||||||
|
* @param req read data into here, may be null
|
||||||
|
* @throws IOException on read error
|
||||||
|
*/
|
||||||
|
private void readData(DebugOut out, BitReader reader, BitCounterUncompressed table, List<DataDescriptor> dkeys,
|
||||||
|
int nestedRow, Request req) throws IOException {
|
||||||
|
|
||||||
|
for (DataDescriptor dkey : dkeys) {
|
||||||
|
if (!dkey.isOkForVariable()) {// misc skip
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%s %d %s (%s) %n", out.indent(), out.fldno++, dkey.name, dkey.getFxyName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sequence
|
||||||
|
if (dkey.replication == 0) {
|
||||||
|
|
||||||
|
// find out how many objects in the sequence
|
||||||
|
int count = (int) reader.bits2UInt(dkey.replicationCountSize);
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%4d delayed replication count=%d %n", out.fldno++, count);
|
||||||
|
if ((out != null) && (count > 0)) {
|
||||||
|
out.f.format("%4d %s read sequence %s count= %d bitSize=%d start at=0x%x %n", out.fldno, out.indent(),
|
||||||
|
dkey.getFxyName(), count, dkey.replicationCountSize, reader.getPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the data
|
||||||
|
BitCounterUncompressed bitCounterNested = table.makeNested(dkey, count, nestedRow, dkey.replicationCountSize);
|
||||||
|
ArraySequence seq = makeArraySequenceUncompressed(out, reader, bitCounterNested, dkey, req);
|
||||||
|
|
||||||
|
if (req.wantRow()) {
|
||||||
|
int index = req.abb.addObjectToHeap(seq);
|
||||||
|
req.bb.putInt(index); // an index into the Heap
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compound
|
||||||
|
if (dkey.type == 3) {
|
||||||
|
BitCounterUncompressed nested = table.makeNested(dkey, dkey.replication, nestedRow, 0);
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%4d %s read structure %s count= %d%n", out.fldno, out.indent(), dkey.getFxyName(),
|
||||||
|
dkey.replication);
|
||||||
|
|
||||||
|
for (int i = 0; i < dkey.replication; i++) {
|
||||||
|
if (out != null) {
|
||||||
|
out.f.format("%s read row %d (struct %s) %n", out.indent(), i, dkey.getFxyName());
|
||||||
|
out.indent.incr();
|
||||||
|
readData(out, reader, nested, dkey.subKeys, i, req);
|
||||||
|
out.indent.decr();
|
||||||
|
} else {
|
||||||
|
readData(null, reader, nested, dkey.subKeys, i, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// char data
|
||||||
|
if (dkey.type == 1) {
|
||||||
|
byte[] vals = readCharData(dkey, reader, req);
|
||||||
|
if (out != null) {
|
||||||
|
String s = new String(vals, StandardCharsets.UTF_8);
|
||||||
|
out.f.format("%4d %s read char %s (%s) width=%d end at= 0x%x val=<%s>%n", out.fldno++, out.indent(),
|
||||||
|
dkey.getFxyName(), dkey.getName(), dkey.bitWidth, reader.getPos(), s);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise read a number
|
||||||
|
long val = readNumericData(dkey, reader, req);
|
||||||
|
if (out != null)
|
||||||
|
out.f.format("%4d %s read %s (%s %s) bitWidth=%d end at= 0x%x raw=%d convert=%f%n", out.fldno++, out.indent(),
|
||||||
|
dkey.getFxyName(), dkey.getName(), dkey.getUnits(), dkey.bitWidth, reader.getPos(), val, dkey.convert(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readCharData(DataDescriptor dkey, BitReader reader, Request req) throws IOException {
|
||||||
|
int nchars = dkey.getByteWidthCDM();
|
||||||
|
byte[] b = new byte[nchars];
|
||||||
|
for (int i = 0; i < nchars; i++)
|
||||||
|
b[i] = (byte) reader.bits2UInt(8);
|
||||||
|
|
||||||
|
if (req.wantRow()) {
|
||||||
|
for (int i = 0; i < nchars; i++)
|
||||||
|
req.bb.put(b[i]);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long readNumericData(DataDescriptor dkey, BitReader reader, Request req) throws IOException {
|
||||||
|
// numeric data
|
||||||
|
long result = reader.bits2UInt(dkey.bitWidth);
|
||||||
|
|
||||||
|
if (req.wantRow()) {
|
||||||
|
|
||||||
|
// place into byte buffer
|
||||||
|
if (dkey.getByteWidthCDM() == 1) {
|
||||||
|
req.bb.put((byte) result);
|
||||||
|
|
||||||
|
} else if (dkey.getByteWidthCDM() == 2) {
|
||||||
|
byte b1 = (byte) (result & 0xff);
|
||||||
|
byte b2 = (byte) ((result & 0xff00) >> 8);
|
||||||
|
req.bb.put(b2);
|
||||||
|
req.bb.put(b1);
|
||||||
|
|
||||||
|
} else if (dkey.getByteWidthCDM() == 4) {
|
||||||
|
byte b1 = (byte) (result & 0xff);
|
||||||
|
byte b2 = (byte) ((result & 0xff00) >> 8);
|
||||||
|
byte b3 = (byte) ((result & 0xff0000) >> 16);
|
||||||
|
byte b4 = (byte) ((result & 0xff000000) >> 24);
|
||||||
|
req.bb.put(b4);
|
||||||
|
req.bb.put(b3);
|
||||||
|
req.bb.put(b2);
|
||||||
|
req.bb.put(b1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
byte b1 = (byte) (result & 0xff);
|
||||||
|
byte b2 = (byte) ((result & 0xff00) >> 8);
|
||||||
|
byte b3 = (byte) ((result & 0xff0000) >> 16);
|
||||||
|
byte b4 = (byte) ((result & 0xff000000) >> 24);
|
||||||
|
byte b5 = (byte) ((result & 0xff00000000L) >> 32);
|
||||||
|
byte b6 = (byte) ((result & 0xff0000000000L) >> 40);
|
||||||
|
byte b7 = (byte) ((result & 0xff000000000000L) >> 48);
|
||||||
|
byte b8 = (byte) ((result & 0xff00000000000000L) >> 56);
|
||||||
|
req.bb.put(b8);
|
||||||
|
req.bb.put(b7);
|
||||||
|
req.bb.put(b6);
|
||||||
|
req.bb.put(b5);
|
||||||
|
req.bb.put(b4);
|
||||||
|
req.bb.put(b3);
|
||||||
|
req.bb.put(b2);
|
||||||
|
req.bb.put(b1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read in the data into an ArrayStructureBB, wrapped by an ArraySequence
|
||||||
|
private ArraySequence makeArraySequenceUncompressed(DebugOut out, BitReader reader,
|
||||||
|
BitCounterUncompressed bitCounterNested, DataDescriptor seqdd, Request req) throws IOException {
|
||||||
|
|
||||||
|
int count = bitCounterNested.getNumberRows(); // the actual number of rows in this sequence
|
||||||
|
ArrayStructureBB abb = null;
|
||||||
|
StructureMembers members = null;
|
||||||
|
|
||||||
|
if (req.wantRow()) {
|
||||||
|
Sequence seq = seqdd.refersTo;
|
||||||
|
assert seq != null;
|
||||||
|
|
||||||
|
// for the obs structure
|
||||||
|
int[] shape = {count};
|
||||||
|
|
||||||
|
// allocate ArrayStructureBB for outer structure
|
||||||
|
// LOOK why is this different from ArrayStructureBB.setOffsets() ?
|
||||||
|
int offset = 0;
|
||||||
|
members = seq.makeStructureMembers();
|
||||||
|
for (StructureMembers.Member m : members.getMembers()) {
|
||||||
|
m.setDataParam(offset);
|
||||||
|
|
||||||
|
Variable mv = seq.findVariable(m.getName());
|
||||||
|
BufrConfig.FieldConverter fld = (BufrConfig.FieldConverter) mv.getSPobject();
|
||||||
|
DataDescriptor dk = fld.dds;
|
||||||
|
if (dk.replication == 0) // LOOK
|
||||||
|
offset += 4;
|
||||||
|
else
|
||||||
|
offset += dk.getByteWidthCDM();
|
||||||
|
|
||||||
|
if (m.getStructureMembers() != null)
|
||||||
|
ArrayStructureBB.setOffsets(m.getStructureMembers());
|
||||||
|
}
|
||||||
|
|
||||||
|
abb = new ArrayStructureBB(members, shape);
|
||||||
|
ByteBuffer bb = abb.getByteBuffer();
|
||||||
|
bb.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
Request nreq = new Request(abb, null);
|
||||||
|
|
||||||
|
// loop through nested obs
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (out != null) {
|
||||||
|
out.f.format("%s read row %d (seq %s) %n", out.indent(), i, seqdd.getFxyName());
|
||||||
|
out.indent.incr();
|
||||||
|
readData(out, reader, bitCounterNested, seqdd.getSubKeys(), i, nreq);
|
||||||
|
out.indent.decr();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
readData(null, reader, bitCounterNested, seqdd.getSubKeys(), i, nreq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return abb != null ? new ArraySequence(members, abb.getStructureDataIterator(), count) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.BufrTables;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableA;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableB;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.tables.TableD;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsolates lookup into the BUFR Tables.
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Jul 14, 2008
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class TableLookup {
|
||||||
|
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableLookup.class);
|
||||||
|
private static final boolean showErrors = false;
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
private TableA localTableA = null;
|
||||||
|
private final TableB localTableB;
|
||||||
|
private final TableD localTableD;
|
||||||
|
|
||||||
|
private final TableB wmoTableB;
|
||||||
|
private final TableD wmoTableD;
|
||||||
|
private final BufrTables.Mode mode;
|
||||||
|
|
||||||
|
public TableLookup(int center, int subcenter, int masterTableVersion, int local, int cat) throws IOException {
|
||||||
|
this.wmoTableB = BufrTables.getWmoTableB(masterTableVersion);
|
||||||
|
this.wmoTableD = BufrTables.getWmoTableD(masterTableVersion);
|
||||||
|
|
||||||
|
BufrTables.Tables tables = BufrTables.getLocalTables(center, subcenter, masterTableVersion, local, cat);
|
||||||
|
if (tables != null) {
|
||||||
|
this.localTableB = tables.b;
|
||||||
|
this.localTableD = tables.d;
|
||||||
|
this.mode = (tables.mode == null) ? BufrTables.Mode.localOverride : tables.mode;
|
||||||
|
} else {
|
||||||
|
this.localTableB = null;
|
||||||
|
this.localTableD = null;
|
||||||
|
this.mode = BufrTables.Mode.localOverride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableLookup(BufrIdentificationSection ids, TableB b, TableD d) throws IOException {
|
||||||
|
this.wmoTableB = BufrTables.getWmoTableB(ids.getMasterTableVersion());
|
||||||
|
this.wmoTableD = BufrTables.getWmoTableD(ids.getMasterTableVersion());
|
||||||
|
this.localTableB = b;
|
||||||
|
this.localTableD = d;
|
||||||
|
this.mode = BufrTables.Mode.localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableLookup(BufrIdentificationSection ids, TableA a, TableB b, TableD d) throws IOException {
|
||||||
|
this.wmoTableB = BufrTables.getWmoTableB(ids.getMasterTableVersion());
|
||||||
|
this.wmoTableD = BufrTables.getWmoTableD(ids.getMasterTableVersion());
|
||||||
|
this.localTableA = a;
|
||||||
|
this.localTableB = b;
|
||||||
|
this.localTableD = d;
|
||||||
|
this.mode = BufrTables.Mode.localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWmoTableBName() {
|
||||||
|
return wmoTableB.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalTableAName() {
|
||||||
|
return localTableA == null ? "none" : localTableA.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalTableBName() {
|
||||||
|
return localTableB == null ? "none" : localTableB.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalTableDName() {
|
||||||
|
return localTableD == null ? "none" : localTableD.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWmoTableDName() {
|
||||||
|
return wmoTableD.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufrTables.Mode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableA getLocalTableA() {
|
||||||
|
return localTableA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableB getLocalTableB() {
|
||||||
|
return localTableB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableD getLocalTableD() {
|
||||||
|
return localTableD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableA.Descriptor getDescriptorTableA(int code) {
|
||||||
|
if (localTableA != null) {
|
||||||
|
return localTableA.getDescriptor(code);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableB.Descriptor getDescriptorTableB(short fxy) {
|
||||||
|
TableB.Descriptor b = null;
|
||||||
|
boolean isWmoRange = ucar.nc2.iosp.bufr.Descriptor.isWmoRange(fxy);
|
||||||
|
|
||||||
|
if (isWmoRange && (mode == BufrTables.Mode.wmoOnly)) {
|
||||||
|
b = wmoTableB.getDescriptor(fxy);
|
||||||
|
|
||||||
|
} else if (isWmoRange && (mode == BufrTables.Mode.wmoLocal)) {
|
||||||
|
b = wmoTableB.getDescriptor(fxy);
|
||||||
|
if ((b == null) && (localTableB != null))
|
||||||
|
b = localTableB.getDescriptor(fxy);
|
||||||
|
|
||||||
|
} else if (isWmoRange && (mode == BufrTables.Mode.localOverride)) {
|
||||||
|
if (localTableB != null)
|
||||||
|
b = localTableB.getDescriptor(fxy);
|
||||||
|
if (b == null)
|
||||||
|
b = wmoTableB.getDescriptor(fxy);
|
||||||
|
else
|
||||||
|
b.setLocalOverride(true);
|
||||||
|
|
||||||
|
} else if (!isWmoRange) {
|
||||||
|
if (localTableB != null)
|
||||||
|
b = localTableB.getDescriptor(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b == null) { // look forward in standard WMO table; often the version number of the message is wrong
|
||||||
|
b = BufrTables.getWmoTableBlatest().getDescriptor(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b == null && showErrors)
|
||||||
|
log.warn(" TableLookup cant find Table B descriptor = {} in tables {}, {} mode={}", ucar.nc2.iosp.bufr.Descriptor.makeString(fxy),
|
||||||
|
getLocalTableBName(), getWmoTableBName(), mode);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableD.Descriptor getDescriptorTableD(short fxy) {
|
||||||
|
TableD.Descriptor d = null;
|
||||||
|
boolean isWmoRange = ucar.nc2.iosp.bufr.Descriptor.isWmoRange(fxy);
|
||||||
|
|
||||||
|
if (isWmoRange && (mode == BufrTables.Mode.wmoOnly)) {
|
||||||
|
d = wmoTableD.getDescriptor(fxy);
|
||||||
|
|
||||||
|
} else if (isWmoRange && (mode == BufrTables.Mode.wmoLocal)) {
|
||||||
|
d = wmoTableD.getDescriptor(fxy);
|
||||||
|
if ((d == null) && (localTableD != null))
|
||||||
|
d = localTableD.getDescriptor(fxy);
|
||||||
|
|
||||||
|
} else if (isWmoRange && (mode == BufrTables.Mode.localOverride)) {
|
||||||
|
if (localTableD != null)
|
||||||
|
d = localTableD.getDescriptor(fxy);
|
||||||
|
if (d == null)
|
||||||
|
d = wmoTableD.getDescriptor(fxy);
|
||||||
|
else
|
||||||
|
d.setLocalOverride(true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (localTableD != null)
|
||||||
|
d = localTableD.getDescriptor(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d == null) { // look forward in standard WMO table; often the version number of the message is wrong
|
||||||
|
d = BufrTables.getWmoTableDlatest().getDescriptor(fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d == null && showErrors)
|
||||||
|
log.warn(String.format(" TableLookup cant find Table D descriptor %s in tables %s,%s mode=%s%n",
|
||||||
|
Descriptor.makeString(fxy), getLocalTableDName(), getWmoTableDName(), mode));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.point;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.BufrConfig;
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
import ucar.nc2.stream.NcStream;
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
import ucar.unidata.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage cdm index (ncx) for Bufr files.
|
||||||
|
* Covers BufrCdmIndexProto
|
||||||
|
* Never completed for operational use, could redo as needed
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/14/13
|
||||||
|
*/
|
||||||
|
public class BufrCdmIndex {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrCdmIndex.class);
|
||||||
|
|
||||||
|
public static final String MAGIC_START = "BufrCdmIndex";
|
||||||
|
public static final String NCX_IDX = ".ncx";
|
||||||
|
private static final int version = 1;
|
||||||
|
|
||||||
|
public static File calcIndexFile(String bufrFilename) {
|
||||||
|
File bufrFile = new File(bufrFilename);
|
||||||
|
String name = bufrFile.getName();
|
||||||
|
File result = new File(bufrFile.getParent(), name + BufrCdmIndex.NCX_IDX);
|
||||||
|
if (result.exists())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
int pos = name.indexOf('.');
|
||||||
|
if (pos > 0) {
|
||||||
|
name = name.substring(0, pos);
|
||||||
|
result = new File(bufrFile.getParent(), name + BufrCdmIndex.NCX_IDX);
|
||||||
|
if (result.exists())
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean writeIndex(String bufrFilename, BufrConfig config, File idxFile) throws IOException {
|
||||||
|
return new BufrCdmIndex().writeIndex2(bufrFilename, config, idxFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufrCdmIndex readIndex(String indexFilename) throws IOException {
|
||||||
|
BufrCdmIndex index = new BufrCdmIndex();
|
||||||
|
try (RandomAccessFile raf = RandomAccessFile.acquire(indexFilename)) {
|
||||||
|
index.readIndex(raf);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MAGIC_START
|
||||||
|
* version
|
||||||
|
* sizeIndex
|
||||||
|
* BufrCdmIndexProto (sizeIndex bytes)
|
||||||
|
*/
|
||||||
|
private boolean writeIndex2(String bufrFilename, BufrConfig config, File indexFile) throws IOException {
|
||||||
|
if (indexFile.exists()) {
|
||||||
|
if (!indexFile.delete())
|
||||||
|
log.warn(" BufrCdmIndex cant delete index file {}", indexFile.getPath());
|
||||||
|
}
|
||||||
|
log.debug(" createIndex for {}", indexFile.getPath());
|
||||||
|
|
||||||
|
try (RandomAccessFile raf = new RandomAccessFile(indexFile.getPath(), "rw")) {
|
||||||
|
raf.order(RandomAccessFile.BIG_ENDIAN);
|
||||||
|
//// header message
|
||||||
|
raf.write(MAGIC_START.getBytes(StandardCharsets.UTF_8));
|
||||||
|
raf.writeInt(version);
|
||||||
|
|
||||||
|
// build it
|
||||||
|
BufrCdmIndexProto.BufrIndex.Builder indexBuilder = BufrCdmIndexProto.BufrIndex.newBuilder();
|
||||||
|
indexBuilder.setFilename(bufrFilename);
|
||||||
|
root = buildField(config.getRootConverter());
|
||||||
|
indexBuilder.setRoot(root);
|
||||||
|
indexBuilder.setStart(config.getStart());
|
||||||
|
indexBuilder.setEnd(config.getEnd());
|
||||||
|
indexBuilder.setNobs(config.getNobs());
|
||||||
|
|
||||||
|
Map<String, BufrConfig.BufrStation> smaps = config.getStationMap();
|
||||||
|
if (smaps != null) {
|
||||||
|
List<BufrConfig.BufrStation> stations = new ArrayList<>(smaps.values());
|
||||||
|
Collections.sort(stations);
|
||||||
|
for (BufrConfig.BufrStation s : stations) {
|
||||||
|
indexBuilder.addStations(buildStation(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write it
|
||||||
|
BufrCdmIndexProto.BufrIndex index = indexBuilder.build();
|
||||||
|
byte[] b = index.toByteArray();
|
||||||
|
NcStream.writeVInt(raf, b.length); // message size
|
||||||
|
raf.write(b); // message - all in one gulp
|
||||||
|
|
||||||
|
log.debug(" file size = {} bytes", raf.length());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean writeIndex(BufrCdmIndex index, BufrField root, File indexFile) throws IOException {
|
||||||
|
if (indexFile.exists()) {
|
||||||
|
if (!indexFile.delete())
|
||||||
|
log.warn(" BufrCdmIndex cant delete index file {}", indexFile.getPath());
|
||||||
|
}
|
||||||
|
log.debug(" createIndex for {}", indexFile.getPath());
|
||||||
|
|
||||||
|
try (RandomAccessFile raf = new RandomAccessFile(indexFile.getPath(), "rw")) {
|
||||||
|
raf.order(RandomAccessFile.BIG_ENDIAN);
|
||||||
|
//// header message
|
||||||
|
raf.write(MAGIC_START.getBytes(StandardCharsets.UTF_8));
|
||||||
|
raf.writeInt(version);
|
||||||
|
|
||||||
|
// build it
|
||||||
|
BufrCdmIndexProto.BufrIndex.Builder indexBuilder = BufrCdmIndexProto.BufrIndex.newBuilder();
|
||||||
|
indexBuilder.setFilename(index.bufrFilename);
|
||||||
|
BufrCdmIndexProto.Field rootf = buildField(root);
|
||||||
|
indexBuilder.setRoot(rootf);
|
||||||
|
indexBuilder.setStart(index.start);
|
||||||
|
indexBuilder.setEnd(index.end);
|
||||||
|
indexBuilder.setNobs(index.nobs);
|
||||||
|
|
||||||
|
if (index.stations != null) {
|
||||||
|
for (BufrCdmIndexProto.Station s : index.stations) {
|
||||||
|
indexBuilder.addStations(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write it
|
||||||
|
BufrCdmIndexProto.BufrIndex indexOut = indexBuilder.build();
|
||||||
|
byte[] b = indexOut.toByteArray();
|
||||||
|
NcStream.writeVInt(raf, b.length); // message size
|
||||||
|
raf.write(b); // message - all in one gulp
|
||||||
|
log.debug(" write BufrCdmIndexProto= {} bytes", b.length);
|
||||||
|
|
||||||
|
log.debug(" file size = {} bytes", raf.length());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufrCdmIndexProto.Station buildStation(BufrConfig.BufrStation s) {
|
||||||
|
BufrCdmIndexProto.Station.Builder builder = BufrCdmIndexProto.Station.newBuilder();
|
||||||
|
|
||||||
|
builder.setId(s.getName());
|
||||||
|
builder.setCount(s.count);
|
||||||
|
if (s.getWmoId() != null)
|
||||||
|
builder.setWmoId(s.getWmoId());
|
||||||
|
if (s.getDescription() != null)
|
||||||
|
builder.setDesc(s.getDescription());
|
||||||
|
builder.setLat(s.getLatitude());
|
||||||
|
builder.setLon(s.getLongitude());
|
||||||
|
builder.setAlt(s.getAltitude());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufrCdmIndexProto.Field buildField(BufrField fld) {
|
||||||
|
BufrCdmIndexProto.Field.Builder fldBuilder = BufrCdmIndexProto.Field.newBuilder();
|
||||||
|
|
||||||
|
fldBuilder.setFxy(fld.getFxy());
|
||||||
|
fldBuilder.setScale(fld.getScale());
|
||||||
|
fldBuilder.setReference(fld.getReference());
|
||||||
|
fldBuilder.setBitWidth(fld.getBitWidth());
|
||||||
|
|
||||||
|
if (fld.getName() != null)
|
||||||
|
fldBuilder.setName(fld.getName());
|
||||||
|
|
||||||
|
if (fld.getDesc() != null)
|
||||||
|
fldBuilder.setDesc(fld.getDesc());
|
||||||
|
|
||||||
|
if (fld.getUnits() != null)
|
||||||
|
fldBuilder.setUnits(fld.getUnits());
|
||||||
|
|
||||||
|
if (fld.getChildren() != null) {
|
||||||
|
for (BufrField child : fld.getChildren())
|
||||||
|
fldBuilder.addFlds(buildField(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fld.getAction() != null && fld.getAction() != BufrCdmIndexProto.FldAction.none)
|
||||||
|
fldBuilder.setAction(fld.getAction());
|
||||||
|
|
||||||
|
if (fld.getType() != null)
|
||||||
|
fldBuilder.setType(fld.getType());
|
||||||
|
|
||||||
|
if (fld.isSeq()) {
|
||||||
|
fldBuilder.setMin(fld.getMin());
|
||||||
|
fldBuilder.setMax(fld.getMax());
|
||||||
|
}
|
||||||
|
|
||||||
|
return fldBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public String idxFilename;
|
||||||
|
public String bufrFilename;
|
||||||
|
public BufrCdmIndexProto.Field root;
|
||||||
|
public List<BufrCdmIndexProto.Station> stations;
|
||||||
|
public long start, end;
|
||||||
|
public long nobs;
|
||||||
|
|
||||||
|
protected boolean readIndex(RandomAccessFile raf) {
|
||||||
|
this.idxFilename = raf.getLocation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
raf.order(RandomAccessFile.BIG_ENDIAN);
|
||||||
|
raf.seek(0);
|
||||||
|
|
||||||
|
//// header message
|
||||||
|
if (!NcStream.readAndTest(raf, MAGIC_START.getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
log.error("BufrCdmIndex {}: invalid index", raf.getLocation());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int indexVersion = raf.readInt();
|
||||||
|
boolean versionOk = (indexVersion == version);
|
||||||
|
if (!versionOk) {
|
||||||
|
log.warn("BufrCdmIndex {}: index found version={}, want version= {}", raf.getLocation(), indexVersion, version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = NcStream.readVInt(raf);
|
||||||
|
if ((size < 0) || (size > 100 * 1000 * 1000)) {
|
||||||
|
log.warn("BufrCdmIndex {}: invalid or empty index ", raf.getLocation());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] m = new byte[size];
|
||||||
|
raf.readFully(m);
|
||||||
|
|
||||||
|
BufrCdmIndexProto.BufrIndex proto = BufrCdmIndexProto.BufrIndex.parseFrom(m);
|
||||||
|
bufrFilename = proto.getFilename();
|
||||||
|
root = proto.getRoot();
|
||||||
|
stations = proto.getStationsList();
|
||||||
|
start = proto.getStart();
|
||||||
|
end = proto.getEnd();
|
||||||
|
nobs = proto.getNobs();
|
||||||
|
|
||||||
|
// showProtoRoot(root);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("Error reading index " + raf.getLocation(), t);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showIndex(Formatter f) {
|
||||||
|
f.format("BufrCdmIndex %n");
|
||||||
|
f.format(" idxFilename=%s%n", idxFilename);
|
||||||
|
f.format(" bufrFilename=%s%n", bufrFilename);
|
||||||
|
f.format(" dates=[%s,%s]%n", CalendarDate.of(start), CalendarDate.of(end));
|
||||||
|
f.format(" nobs=%s%n", nobs);
|
||||||
|
if (stations != null) {
|
||||||
|
f.format(" # stations=%d%n", stations.size());
|
||||||
|
int count = 0;
|
||||||
|
for (BufrCdmIndexProto.Station s : stations)
|
||||||
|
count += s.getCount();
|
||||||
|
f.format(" # stations obs=%d%n", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,416 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.point;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import thredds.client.catalog.Catalog;
|
||||||
|
import ucar.ma2.*;
|
||||||
|
import ucar.nc2.Attribute;
|
||||||
|
import ucar.nc2.Structure;
|
||||||
|
import ucar.nc2.Variable;
|
||||||
|
import ucar.nc2.VariableSimpleIF;
|
||||||
|
import ucar.nc2.constants.FeatureType;
|
||||||
|
import ucar.nc2.dataset.NetcdfDataset;
|
||||||
|
import ucar.nc2.dataset.SequenceDS;
|
||||||
|
import ucar.nc2.dataset.VariableDS;
|
||||||
|
import ucar.nc2.ft.*;
|
||||||
|
import ucar.nc2.ft.point.*;
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
import ucar.nc2.iosp.IOServiceProvider;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.BufrIosp2;
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
import ucar.nc2.time.CalendarDateRange;
|
||||||
|
import ucar.nc2.time.CalendarDateUnit;
|
||||||
|
import ucar.nc2.util.CancelTask;
|
||||||
|
import ucar.nc2.util.Indent;
|
||||||
|
import ucar.unidata.geoloc.EarthLocation;
|
||||||
|
import ucar.unidata.geoloc.LatLonRect;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use BufrConfig to make BUFR files into PointFeatureDataset
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/14/13
|
||||||
|
*/
|
||||||
|
public class BufrFeatureDatasetFactory implements FeatureDatasetFactory {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrFeatureDatasetFactory.class);
|
||||||
|
private static CalendarDateUnit bufrDateUnits = CalendarDateUnit.of(null, "msecs since 1970-01-01T00:00:00");
|
||||||
|
private static String bufrAltUnits = "m"; // LOOK fake
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object isMine(FeatureType wantFeatureType, NetcdfDataset ncd, Formatter errlog) {
|
||||||
|
IOServiceProvider iosp = ncd.getIosp();
|
||||||
|
return (iosp instanceof BufrIosp2) ? true : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FeatureType[] getFeatureTypes() {
|
||||||
|
return new FeatureType[]{FeatureType.ANY_POINT};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FeatureDataset open(FeatureType ftype, NetcdfDataset ncd, Object analysis, CancelTask task, Formatter errlog)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
// must have an index file
|
||||||
|
File indexFile = BufrCdmIndex.calcIndexFile(ncd.getLocation());
|
||||||
|
if (indexFile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
BufrCdmIndex index = BufrCdmIndex.readIndex(indexFile.getPath());
|
||||||
|
return new BufrStationDataset(ncd, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void show(Element parent, Indent indent, Formatter f) {
|
||||||
|
if (parent == null)
|
||||||
|
return;
|
||||||
|
for (Element child : parent.getChildren("fld", Catalog.ncmlNS)) {
|
||||||
|
String idx = child.getAttributeValue("idx");
|
||||||
|
String fxy = child.getAttributeValue("fxy");
|
||||||
|
String name = child.getAttributeValue("name");
|
||||||
|
String action = child.getAttributeValue("action");
|
||||||
|
f.format("%sidx='%s' fxy='%s' name='%s' action='%s'%n", indent, idx, fxy, name, action);
|
||||||
|
indent.incr();
|
||||||
|
show(child, indent, f);
|
||||||
|
indent.decr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSeq(Structure struct, Element parent) {
|
||||||
|
if (parent == null || struct == null)
|
||||||
|
return;
|
||||||
|
List<Variable> vars = struct.getVariables();
|
||||||
|
for (Element child : parent.getChildren("fld", Catalog.ncmlNS)) {
|
||||||
|
String idxS = child.getAttributeValue("idx");
|
||||||
|
int idx = Integer.parseInt(idxS);
|
||||||
|
if (idx < 0 || idx >= vars.size()) {
|
||||||
|
log.error("Bad index = {}", child);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Variable want = vars.get(idx);
|
||||||
|
struct.removeMemberVariable(want);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BufrStationDataset extends PointDatasetImpl {
|
||||||
|
private Munge munger;
|
||||||
|
private BufrCdmIndex index;
|
||||||
|
private SequenceDS obs;
|
||||||
|
|
||||||
|
private BufrStationDataset(NetcdfDataset ncfile, BufrCdmIndex index) {
|
||||||
|
super(ncfile, FeatureType.STATION);
|
||||||
|
this.index = index;
|
||||||
|
|
||||||
|
// create the list of data variables
|
||||||
|
munger = new Munge();
|
||||||
|
obs = (SequenceDS) ncfile.findVariable(BufrIosp2.obsRecordName);
|
||||||
|
this.dataVariables = munger.makeDataVariables(index, obs);
|
||||||
|
|
||||||
|
BufrStationCollection bufrCollection = new BufrStationCollection(ncfile.getLocation());
|
||||||
|
setPointFeatureCollection(bufrCollection);
|
||||||
|
|
||||||
|
CalendarDateRange dateRange = CalendarDateRange.of(CalendarDate.of(index.start), CalendarDate.of(index.end));
|
||||||
|
setDateRange(dateRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FeatureType getFeatureType() {
|
||||||
|
return FeatureType.STATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getDetailInfo(Formatter sf) {
|
||||||
|
super.getDetailInfo(sf);
|
||||||
|
index.showIndex(sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BufrStationCollection extends StationTimeSeriesCollectionImpl {
|
||||||
|
StandardFields.StandardFieldsFromStructure extract;
|
||||||
|
|
||||||
|
private BufrStationCollection(String name) {
|
||||||
|
super(name, null, null);
|
||||||
|
|
||||||
|
// need the center id to match the standard fields
|
||||||
|
Attribute centerAtt = netcdfDataset.findGlobalAttribute(BufrIosp2.centerId);
|
||||||
|
int center = (centerAtt == null) ? 0 : centerAtt.getNumericValue().intValue();
|
||||||
|
this.extract = new StandardFields.StandardFieldsFromStructure(center, obs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.timeUnit = bufrDateUnits;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(); // cant happen
|
||||||
|
}
|
||||||
|
|
||||||
|
this.altUnits = "m"; // LOOK fake units
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StationHelper createStationHelper() {
|
||||||
|
StationHelper stationHelper = new StationHelper();
|
||||||
|
for (BufrCdmIndexProto.Station s : index.stations)
|
||||||
|
stationHelper.addStation(new BufrStation(s));
|
||||||
|
|
||||||
|
return stationHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BufrStation extends StationTimeSeriesFeatureImpl {
|
||||||
|
private BufrStation(BufrCdmIndexProto.Station proto) {
|
||||||
|
super(proto.getId(), proto.getDesc(), proto.getWmoId(), proto.getLat(), proto.getLon(), proto.getAlt(),
|
||||||
|
bufrDateUnits, bufrAltUnits, proto.getCount(), StructureData.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PointFeatureIterator getPointFeatureIterator() throws IOException {
|
||||||
|
return new BufrStationIterator(obs.getStructureIterator(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public StructureData getFeatureData() {
|
||||||
|
return StructureData.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterates over the records for this station
|
||||||
|
public class BufrStationIterator extends PointIteratorFromStructureData {
|
||||||
|
public BufrStationIterator(StructureDataIterator structIter, Filter filter) {
|
||||||
|
super(structIter, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PointFeature makeFeature(int recnum, StructureData sdata) throws IOException {
|
||||||
|
extract.extract(sdata);
|
||||||
|
String stationId = extract.getStationId();
|
||||||
|
if (!stationId.equals(s.getName()))
|
||||||
|
return null;
|
||||||
|
CalendarDate date = extract.makeCalendarDate();
|
||||||
|
return new BufrStationPoint(s, date.getMillis(), 0, munger.munge(sdata)); // LOOK obsTime, nomTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BufrStationPoint extends PointFeatureImpl implements StationFeatureHas {
|
||||||
|
StructureData sdata;
|
||||||
|
|
||||||
|
public BufrStationPoint(EarthLocation location, double obsTime, double nomTime, StructureData sdata) {
|
||||||
|
super(BufrStation.this, location, obsTime, nomTime, bufrDateUnits);
|
||||||
|
this.sdata = sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public StructureData getDataAll() {
|
||||||
|
return sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public StructureData getFeatureData() {
|
||||||
|
return sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StationFeature getStationFeature() {
|
||||||
|
return BufrStation.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten into a PointFeatureCollection
|
||||||
|
// if empty, may return null
|
||||||
|
@Override
|
||||||
|
public PointFeatureCollection flatten(LatLonRect boundingBox, CalendarDateRange dateRange) throws IOException {
|
||||||
|
return new BufrPointFeatureCollection(boundingBox, dateRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BufrPointFeatureCollection extends PointCollectionImpl {
|
||||||
|
StationHelper stationsWanted;
|
||||||
|
PointFeatureIterator.Filter filter;
|
||||||
|
|
||||||
|
BufrPointFeatureCollection(LatLonRect boundingBox, CalendarDateRange dateRange) throws IOException {
|
||||||
|
super("BufrPointFeatureCollection", bufrDateUnits, bufrAltUnits);
|
||||||
|
setBoundingBox(boundingBox);
|
||||||
|
if (dateRange != null) {
|
||||||
|
getInfo();
|
||||||
|
info.setCalendarDateRange(dateRange);
|
||||||
|
}
|
||||||
|
createStationHelper();
|
||||||
|
stationsWanted = getStationHelper().subset(boundingBox);
|
||||||
|
if (dateRange != null)
|
||||||
|
filter = new PointIteratorFiltered.SpaceAndTimeFilter(null, dateRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PointFeatureIterator getPointFeatureIterator() throws IOException {
|
||||||
|
return new BufrRecordIterator(obs.getStructureIterator(), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterates once over all the records
|
||||||
|
public class BufrRecordIterator extends PointIteratorFromStructureData {
|
||||||
|
int countHere;
|
||||||
|
|
||||||
|
public BufrRecordIterator(StructureDataIterator structIter, Filter filter) {
|
||||||
|
super(structIter, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PointFeature makeFeature(int recnum, StructureData sdata) throws IOException {
|
||||||
|
extract.extract(sdata);
|
||||||
|
String stationId = extract.getStationId();
|
||||||
|
StationFeature want = stationsWanted.getStation(stationId);
|
||||||
|
if (want == null)
|
||||||
|
return null;
|
||||||
|
CalendarDate date = extract.makeCalendarDate();
|
||||||
|
countHere++;
|
||||||
|
return new BufrPoint(want, date.getMillis(), 0, munger.munge(sdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
log.debug(String.format("BufrRecordIterator passed %d features super claims %d%n", countHere,
|
||||||
|
getInfo().nfeatures));
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BufrPoint extends PointFeatureImpl implements StationPointFeature {
|
||||||
|
StructureData sdata;
|
||||||
|
|
||||||
|
public BufrPoint(StationFeature want, double obsTime, double nomTime, StructureData sdata) {
|
||||||
|
super(BufrPointFeatureCollection.this, want, obsTime, nomTime, bufrDateUnits);
|
||||||
|
this.sdata = sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public StructureData getDataAll() {
|
||||||
|
return sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public StructureData getFeatureData() {
|
||||||
|
return sdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StationFeature getStation() {
|
||||||
|
return (StationFeature) location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Action {
|
||||||
|
BufrCdmIndexProto.FldAction what;
|
||||||
|
|
||||||
|
private Action(BufrCdmIndexProto.FldAction what) {
|
||||||
|
this.what = what;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Munge {
|
||||||
|
String sdataName;
|
||||||
|
boolean needed;
|
||||||
|
protected Map<String, Action> actions = new HashMap<>(32);
|
||||||
|
protected Map<String, StructureData> missingData = new HashMap<>(32);
|
||||||
|
protected Map<String, VariableDS> vars = new HashMap<>(32);
|
||||||
|
|
||||||
|
List<VariableSimpleIF> makeDataVariables(BufrCdmIndex index, Structure obs) {
|
||||||
|
this.sdataName = obs.getShortName() + "Munged";
|
||||||
|
|
||||||
|
List<Variable> members = obs.getVariables();
|
||||||
|
List<VariableSimpleIF> result = new ArrayList<>(members.size());
|
||||||
|
|
||||||
|
List<BufrCdmIndexProto.Field> flds = index.root.getFldsList();
|
||||||
|
int count = 0;
|
||||||
|
for (Variable v : members) {
|
||||||
|
BufrCdmIndexProto.Field fld = flds.get(count++);
|
||||||
|
if (fld.getAction() != null && fld.getAction() != BufrCdmIndexProto.FldAction.none) {
|
||||||
|
needed = true;
|
||||||
|
Action act = new Action(fld.getAction());
|
||||||
|
actions.put(v.getShortName(), act);
|
||||||
|
|
||||||
|
if (fld.getAction() == BufrCdmIndexProto.FldAction.remove) {
|
||||||
|
continue; // skip
|
||||||
|
|
||||||
|
} else if (fld.getAction() == BufrCdmIndexProto.FldAction.asMissing) {
|
||||||
|
// promote the children
|
||||||
|
Structure s = (Structure) v;
|
||||||
|
for (Variable child : s.getVariables()) {
|
||||||
|
result.add(child);
|
||||||
|
vars.put(child.getShortName(), (VariableDS) child); // track ones we may have to create missing values for
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.getDataType() == DataType.SEQUENCE)
|
||||||
|
continue;
|
||||||
|
result.add(v);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
StructureData munge(StructureData org) throws IOException {
|
||||||
|
return needed ? new StructureDataMunged2(org) : org;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOK needs to be ported to immutable StructureDataComposite
|
||||||
|
private class StructureDataMunged2 extends StructureDataComposite {
|
||||||
|
|
||||||
|
StructureDataMunged2(StructureData sdata) throws IOException {
|
||||||
|
add(sdata);
|
||||||
|
for (StructureMembers.Member m : sdata.getMembers()) {
|
||||||
|
Action act = actions.get(m.getName());
|
||||||
|
if (act == null) {
|
||||||
|
// do nothing
|
||||||
|
|
||||||
|
} else if (act.what == BufrCdmIndexProto.FldAction.remove) {
|
||||||
|
this.members.hideMember(m);
|
||||||
|
|
||||||
|
} else if (act.what == BufrCdmIndexProto.FldAction.asMissing) { // 0 or 1
|
||||||
|
int pos = this.members.hideMember(m);
|
||||||
|
ArraySequence seq = sdata.getArraySequence(m);
|
||||||
|
StructureDataIterator iter = seq.getStructureDataIterator();
|
||||||
|
if (iter.hasNext()) {
|
||||||
|
add(pos, iter.next());
|
||||||
|
} else {
|
||||||
|
// missing data
|
||||||
|
add(pos, makeMissing(m, seq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StructureData makeMissing(StructureMembers.Member seqm, ArraySequence seq) {
|
||||||
|
StructureData result = missingData.get(seqm.getName());
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
StructureMembers sm = seq.getStructureMembers().toBuilder(false).build();
|
||||||
|
StructureDataW resultW = new StructureDataW(sm);
|
||||||
|
for (StructureMembers.Member m : sm.getMembers()) {
|
||||||
|
VariableDS var = vars.get(m.getName());
|
||||||
|
Array missingData = var.getMissingDataArray(m.getShape());
|
||||||
|
resultW.setMemberData(m, missingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
missingData.put(seqm.getName(), resultW);
|
||||||
|
return resultW;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Munge
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.point;
|
||||||
|
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for BUFR field.
|
||||||
|
* Used in writing index, so we can make changes in BufrCdmIndexPanel
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/20/13
|
||||||
|
*/
|
||||||
|
public interface BufrField {
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
String getDesc();
|
||||||
|
|
||||||
|
String getUnits();
|
||||||
|
|
||||||
|
short getFxy();
|
||||||
|
|
||||||
|
String getFxyName();
|
||||||
|
|
||||||
|
BufrCdmIndexProto.FldAction getAction();
|
||||||
|
|
||||||
|
BufrCdmIndexProto.FldType getType();
|
||||||
|
|
||||||
|
boolean isSeq();
|
||||||
|
|
||||||
|
int getMin();
|
||||||
|
|
||||||
|
int getMax();
|
||||||
|
|
||||||
|
int getScale();
|
||||||
|
|
||||||
|
int getReference();
|
||||||
|
|
||||||
|
int getBitWidth();
|
||||||
|
|
||||||
|
List<? extends BufrField> getChildren();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.point;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.BufrIosp2;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.DataDescriptor;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.Message;
|
||||||
|
import ucar.ma2.DataType;
|
||||||
|
import ucar.ma2.StructureData;
|
||||||
|
import ucar.ma2.StructureMembers;
|
||||||
|
import ucar.nc2.Attribute;
|
||||||
|
import ucar.nc2.Structure;
|
||||||
|
import ucar.nc2.Variable;
|
||||||
|
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
|
||||||
|
import ucar.nc2.time.CalendarDate;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract standard fields from BUFR
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/7/13
|
||||||
|
*/
|
||||||
|
public class StandardFields {
|
||||||
|
private static int nflds = 50;
|
||||||
|
private static Map<BufrCdmIndexProto.FldType, List<String>> type2Flds = new HashMap<>(2 * nflds);
|
||||||
|
private static Map<String, TypeAndOrder> fld2type = new HashMap<>(2 * nflds);
|
||||||
|
private static Map<Integer, Map<String, BufrCdmIndexProto.FldType>> locals = new HashMap<>(10);
|
||||||
|
|
||||||
|
static {
|
||||||
|
// first choice
|
||||||
|
addField("0-1-1", BufrCdmIndexProto.FldType.wmoBlock);
|
||||||
|
addField("0-1-2", BufrCdmIndexProto.FldType.wmoId);
|
||||||
|
addField("0-1-18", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-4-1", BufrCdmIndexProto.FldType.year);
|
||||||
|
addField("0-4-2", BufrCdmIndexProto.FldType.month);
|
||||||
|
addField("0-4-3", BufrCdmIndexProto.FldType.day);
|
||||||
|
addField("0-4-4", BufrCdmIndexProto.FldType.hour);
|
||||||
|
addField("0-4-5", BufrCdmIndexProto.FldType.minute);
|
||||||
|
addField("0-4-6", BufrCdmIndexProto.FldType.sec);
|
||||||
|
addField("0-5-1", BufrCdmIndexProto.FldType.lat);
|
||||||
|
addField("0-6-1", BufrCdmIndexProto.FldType.lon);
|
||||||
|
addField("0-7-30", BufrCdmIndexProto.FldType.heightOfStation);
|
||||||
|
|
||||||
|
// second choice
|
||||||
|
addField("0-1-15", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-1-19", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-4-7", BufrCdmIndexProto.FldType.sec);
|
||||||
|
addField("0-4-43", BufrCdmIndexProto.FldType.doy);
|
||||||
|
addField("0-5-2", BufrCdmIndexProto.FldType.lat);
|
||||||
|
addField("0-6-2", BufrCdmIndexProto.FldType.lon);
|
||||||
|
addField("0-7-1", BufrCdmIndexProto.FldType.heightOfStation);
|
||||||
|
|
||||||
|
// third choice
|
||||||
|
addField("0-1-62", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-1-63", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-7-2", BufrCdmIndexProto.FldType.height);
|
||||||
|
addField("0-7-10", BufrCdmIndexProto.FldType.height);
|
||||||
|
addField("0-7-7", BufrCdmIndexProto.FldType.height);
|
||||||
|
|
||||||
|
// 4th choice LOOK
|
||||||
|
addField("0-1-5", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-1-6", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
// addField("0-1-7", BufrCdmIndexProto.FldType.stationId); satellite id
|
||||||
|
addField("0-1-8", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-1-10", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-1-11", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
addField("0-7-6", BufrCdmIndexProto.FldType.heightAboveStation);
|
||||||
|
addField("0-7-7", BufrCdmIndexProto.FldType.heightAboveStation);
|
||||||
|
|
||||||
|
// locals
|
||||||
|
/*
|
||||||
|
* Map<String, BufrCdmIndexProto.FldType> ncep = new HashMap<String, BufrCdmIndexProto.FldType>(10);
|
||||||
|
* ncep.put("0-1-198", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
* locals.put(7, ncep);
|
||||||
|
*/
|
||||||
|
|
||||||
|
Map<String, BufrCdmIndexProto.FldType> uu = new HashMap<>(10);
|
||||||
|
uu.put("0-1-194", BufrCdmIndexProto.FldType.stationId);
|
||||||
|
locals.put(59, uu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TypeAndOrder {
|
||||||
|
BufrCdmIndexProto.FldType type;
|
||||||
|
int order;
|
||||||
|
|
||||||
|
private TypeAndOrder(BufrCdmIndexProto.FldType type, int order) {
|
||||||
|
this.type = type;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addField(String fld, BufrCdmIndexProto.FldType type) {
|
||||||
|
List<String> list = type2Flds.computeIfAbsent(type, k -> new ArrayList<>());
|
||||||
|
list.add(fld); // keep in order
|
||||||
|
|
||||||
|
TypeAndOrder tao = new TypeAndOrder(type, list.size() - 1);
|
||||||
|
fld2type.put(fld, tao);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private static TypeAndOrder findTao(int center, String key) {
|
||||||
|
Map<String, BufrCdmIndexProto.FldType> local = locals.get(center);
|
||||||
|
if (local != null) {
|
||||||
|
BufrCdmIndexProto.FldType result = local.get(key);
|
||||||
|
if (result != null)
|
||||||
|
return new TypeAndOrder(result, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fld2type.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufrCdmIndexProto.FldType findField(int center, String key) {
|
||||||
|
Map<String, BufrCdmIndexProto.FldType> local = locals.get(center);
|
||||||
|
if (local != null) {
|
||||||
|
BufrCdmIndexProto.FldType result = local.get(key);
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findStandardField(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufrCdmIndexProto.FldType findStandardField(String key) {
|
||||||
|
TypeAndOrder tao = fld2type.get(key);
|
||||||
|
return (tao == null) ? null : tao.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public static StandardFieldsFromMessage extract(Message m) {
|
||||||
|
StandardFieldsFromMessage result = new StandardFieldsFromMessage();
|
||||||
|
extract(m.ids.getCenterId(), m.getRootDataDescriptor(), result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extract(int center, DataDescriptor dds, StandardFieldsFromMessage extract) {
|
||||||
|
for (DataDescriptor subdds : dds.getSubKeys()) {
|
||||||
|
extract.match(center, subdds);
|
||||||
|
|
||||||
|
if (subdds.getSubKeys() != null)
|
||||||
|
extract(center, subdds, extract);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StandardFieldsFromMessage {
|
||||||
|
|
||||||
|
Map<BufrCdmIndexProto.FldType, List<DataDescriptor>> typeMap = new TreeMap<>();
|
||||||
|
|
||||||
|
void match(int center, DataDescriptor dds) {
|
||||||
|
String name = dds.getFxyName();
|
||||||
|
BufrCdmIndexProto.FldType type = findField(center, name);
|
||||||
|
if (type == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// got a match
|
||||||
|
List<DataDescriptor> list = typeMap.computeIfAbsent(type, k -> new ArrayList<>(3));
|
||||||
|
list.add(dds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasStation() {
|
||||||
|
if (typeMap.get(BufrCdmIndexProto.FldType.lat) == null)
|
||||||
|
return false;
|
||||||
|
if (typeMap.get(BufrCdmIndexProto.FldType.lon) == null)
|
||||||
|
return false;
|
||||||
|
if (typeMap.get(BufrCdmIndexProto.FldType.stationId) != null)
|
||||||
|
return true;
|
||||||
|
return typeMap.get(BufrCdmIndexProto.FldType.wmoId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTime() {
|
||||||
|
if (typeMap.get(BufrCdmIndexProto.FldType.year) == null)
|
||||||
|
return false;
|
||||||
|
if (typeMap.get(BufrCdmIndexProto.FldType.month) == null)
|
||||||
|
return false;
|
||||||
|
return typeMap.get(BufrCdmIndexProto.FldType.day) != null || typeMap.get(BufrCdmIndexProto.FldType.doy) != null;
|
||||||
|
// if (typeMap.get(BufrCdmIndexProto.FldType.hour) == null) return false; // LOOK could assume 0:0 ??
|
||||||
|
// if (typeMap.get(BufrCdmIndexProto.FldType.minute) == null) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try (Formatter f = new Formatter()) {
|
||||||
|
for (BufrCdmIndexProto.FldType type : typeMap.keySet()) {
|
||||||
|
f.format(" %20s: ", type);
|
||||||
|
List<DataDescriptor> list = typeMap.get(type);
|
||||||
|
for (DataDescriptor dds : list) {
|
||||||
|
f.format(" %s", dds.getName());
|
||||||
|
if (dds.getDesc() != null)
|
||||||
|
f.format("=%s", dds.getDesc());
|
||||||
|
f.format(",");
|
||||||
|
}
|
||||||
|
f.format(" %n");
|
||||||
|
}
|
||||||
|
return f.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StandardFieldsFromStructure {
|
||||||
|
|
||||||
|
private static class Field {
|
||||||
|
TypeAndOrder tao;
|
||||||
|
String memberName;
|
||||||
|
String valueS;
|
||||||
|
int value = -1;
|
||||||
|
double valueD = Double.NaN;
|
||||||
|
double scale = 1.0;
|
||||||
|
double offset;
|
||||||
|
boolean hasScale;
|
||||||
|
|
||||||
|
private Field(TypeAndOrder tao, Variable v) {
|
||||||
|
this.tao = tao;
|
||||||
|
this.memberName = v.getShortName();
|
||||||
|
Attribute att = v.attributes().findAttribute("scale_factor");
|
||||||
|
if (att != null && !att.isString()) {
|
||||||
|
scale = att.getNumericValue().doubleValue();
|
||||||
|
hasScale = true;
|
||||||
|
}
|
||||||
|
att = v.attributes().findAttribute("add_offset");
|
||||||
|
if (att != null && !att.isString()) {
|
||||||
|
offset = att.getNumericValue().doubleValue();
|
||||||
|
hasScale = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<BufrCdmIndexProto.FldType, Field> map = new HashMap<>();
|
||||||
|
|
||||||
|
public StandardFieldsFromStructure(int center, Structure obs) {
|
||||||
|
// run through all available fields - LOOK we are not recursing into sub sequences
|
||||||
|
for (Variable v : obs.getVariables()) {
|
||||||
|
Attribute att = v.attributes().findAttribute(BufrIosp2.fxyAttName);
|
||||||
|
if (att == null)
|
||||||
|
continue;
|
||||||
|
String key = att.getStringValue();
|
||||||
|
TypeAndOrder tao = findTao(center, key);
|
||||||
|
if (tao == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Field oldFld = map.get(tao.type);
|
||||||
|
if (oldFld == null) {
|
||||||
|
Field fld = new Field(tao, v);
|
||||||
|
map.put(tao.type, fld);
|
||||||
|
} else {
|
||||||
|
if (oldFld.tao.order < tao.order) { // replace old one
|
||||||
|
Field fld = new Field(tao, v);
|
||||||
|
map.put(tao.type, fld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract standard fields values from specific StructureData
|
||||||
|
public void extract(StructureData sdata) {
|
||||||
|
StructureMembers sm = sdata.getStructureMembers();
|
||||||
|
for (Field fld : map.values()) {
|
||||||
|
StructureMembers.Member m = sm.findMember(fld.memberName);
|
||||||
|
DataType dtype = m.getDataType();
|
||||||
|
if (dtype.isString())
|
||||||
|
fld.valueS = sdata.getScalarString(m).trim();
|
||||||
|
else if (dtype.isIntegral()) {
|
||||||
|
fld.value = sdata.convertScalarInt(m);
|
||||||
|
fld.valueD = fld.value;
|
||||||
|
} else if (dtype.isNumeric())
|
||||||
|
fld.valueD = sdata.convertScalarDouble(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasField(BufrCdmIndexProto.FldType type) {
|
||||||
|
return null != map.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFieldName(BufrCdmIndexProto.FldType type) {
|
||||||
|
Field fld = map.get(type);
|
||||||
|
return (fld == null) ? null : fld.memberName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFieldValueS(BufrCdmIndexProto.FldType type) {
|
||||||
|
Field fld = map.get(type);
|
||||||
|
if (fld == null)
|
||||||
|
return null;
|
||||||
|
if (fld.valueS != null)
|
||||||
|
return fld.valueS;
|
||||||
|
if (fld.value != -1)
|
||||||
|
return Integer.toString(fld.value);
|
||||||
|
if (!Double.isNaN(fld.valueD))
|
||||||
|
return Double.toString(fld.valueD);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFieldValue(BufrCdmIndexProto.FldType type) {
|
||||||
|
Field fld = map.get(type);
|
||||||
|
return (fld == null) ? -1 : fld.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFieldValueD(BufrCdmIndexProto.FldType type) {
|
||||||
|
Field fld = map.get(type);
|
||||||
|
if (fld == null)
|
||||||
|
return Double.NaN;
|
||||||
|
if (fld.hasScale)
|
||||||
|
return fld.valueD * fld.scale + fld.offset;
|
||||||
|
return fld.valueD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStationId() {
|
||||||
|
if (hasField(BufrCdmIndexProto.FldType.stationId))
|
||||||
|
return getFieldValueS(BufrCdmIndexProto.FldType.stationId);
|
||||||
|
if (hasField(BufrCdmIndexProto.FldType.wmoBlock) && hasField(BufrCdmIndexProto.FldType.wmoId))
|
||||||
|
return getFieldValue(BufrCdmIndexProto.FldType.wmoBlock) + "/" + getFieldValue(BufrCdmIndexProto.FldType.wmoId);
|
||||||
|
if (hasField(BufrCdmIndexProto.FldType.wmoId))
|
||||||
|
return Integer.toString(getFieldValue(BufrCdmIndexProto.FldType.wmoId));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarDate makeCalendarDate() {
|
||||||
|
if (!hasField(BufrCdmIndexProto.FldType.year))
|
||||||
|
return null;
|
||||||
|
int year = getFieldValue(BufrCdmIndexProto.FldType.year);
|
||||||
|
|
||||||
|
int hour = !hasField(BufrCdmIndexProto.FldType.hour) ? 0 : getFieldValue(BufrCdmIndexProto.FldType.hour);
|
||||||
|
int minute = !hasField(BufrCdmIndexProto.FldType.minute) ? 0 : getFieldValue(BufrCdmIndexProto.FldType.minute);
|
||||||
|
int sec = !hasField(BufrCdmIndexProto.FldType.sec) ? 0 : getFieldValue(BufrCdmIndexProto.FldType.sec);
|
||||||
|
if (sec < 0) {
|
||||||
|
sec = 0;
|
||||||
|
} else if (sec > 0) {
|
||||||
|
Field fld = map.get(BufrCdmIndexProto.FldType.sec);
|
||||||
|
if (fld.scale != 0) {
|
||||||
|
sec = (int) (sec * fld.scale); // throw away msecs
|
||||||
|
}
|
||||||
|
if (sec < 0 || sec > 59)
|
||||||
|
sec = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasField(BufrCdmIndexProto.FldType.month) && hasField(BufrCdmIndexProto.FldType.day)) {
|
||||||
|
int month = getFieldValue(BufrCdmIndexProto.FldType.month);
|
||||||
|
int day = getFieldValue(BufrCdmIndexProto.FldType.day);
|
||||||
|
return CalendarDate.of(null, year, month, day, hour, minute, sec);
|
||||||
|
|
||||||
|
} else if (hasField(BufrCdmIndexProto.FldType.doy)) {
|
||||||
|
int doy = getFieldValue(BufrCdmIndexProto.FldType.doy);
|
||||||
|
return CalendarDate.withDoy(null, year, doy, hour, minute, sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import org.jdom2.JDOMException;
|
||||||
|
import org.jdom2.input.SAXBuilder;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.Descriptor;
|
||||||
|
import ucar.nc2.wmo.CommonCodeTable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** Read BUFR Code / Flag tables. */
|
||||||
|
public class CodeFlagTables {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CodeFlagTables.class);
|
||||||
|
private static final String CodeFlagFilename = "wmo/BUFRCREX_37_0_0_CodeFlag_en.xml";
|
||||||
|
static Map<Short, CodeFlagTables> tableMap;
|
||||||
|
|
||||||
|
public static CodeFlagTables getTable(short id) {
|
||||||
|
if (tableMap == null)
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (id == 263)
|
||||||
|
return useCC(id, 5); // 0-1-7
|
||||||
|
if (id == 526)
|
||||||
|
return useCC(id, 7); // 0-2-14
|
||||||
|
if (id == 531)
|
||||||
|
return useCC(id, 8); // 0-2-19
|
||||||
|
if (id == 5699)
|
||||||
|
return useCC(id, 3); // 0-22-67
|
||||||
|
if (id == 5700)
|
||||||
|
return useCC(id, 4); // 0-22-68
|
||||||
|
|
||||||
|
return tableMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CodeFlagTables useCC(short fxy, int cc) {
|
||||||
|
CodeFlagTables cft = tableMap.get(fxy);
|
||||||
|
if (cft == null) {
|
||||||
|
CommonCodeTable cct = CommonCodeTable.getTable(cc);
|
||||||
|
cft = new CodeFlagTables(fxy, cct.getTableName(), cct.getMap());
|
||||||
|
tableMap.put(fxy, cft);
|
||||||
|
}
|
||||||
|
return cft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasTable(short id) {
|
||||||
|
if (tableMap == null)
|
||||||
|
init();
|
||||||
|
CodeFlagTables result = tableMap.get(id);
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void init() {
|
||||||
|
tableMap = new HashMap<>(300);
|
||||||
|
init(tableMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Short, CodeFlagTables> getTables() {
|
||||||
|
if (tableMap == null)
|
||||||
|
init();
|
||||||
|
return tableMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <Exp_CodeFlagTables_E>
|
||||||
|
* <No>837</No>
|
||||||
|
* <FXY>002119</FXY>
|
||||||
|
* <ElementName_E>Instrument operations</ElementName_E>
|
||||||
|
* <CodeFigure>0</CodeFigure>
|
||||||
|
* <EntryName_E>Intermediate frequency calibration mode (IF CAL)</EntryName_E>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_CodeFlagTables_E>
|
||||||
|
*
|
||||||
|
* <BUFRCREX_19_1_1_CodeFlag_en>
|
||||||
|
* <No>2905</No>
|
||||||
|
* <FXY>020042</FXY>
|
||||||
|
* <ElementName_en>Airframe icing present</ElementName_en>
|
||||||
|
* <CodeFigure>2</CodeFigure>
|
||||||
|
* <EntryName_en>Reserved</EntryName_en>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFRCREX_19_1_1_CodeFlag_en>
|
||||||
|
*
|
||||||
|
* <BUFRCREX_22_0_1_CodeFlag_en>
|
||||||
|
* <No>3183</No>
|
||||||
|
* <FXY>020063</FXY>
|
||||||
|
* <ElementName_en>Special phenomena</ElementName_en>
|
||||||
|
* <CodeFigure>31</CodeFigure>
|
||||||
|
* <EntryName_en>Slight coloration of clouds at sunrise associated with a tropical disturbance</EntryName_en>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFRCREX_22_0_1_CodeFlag_en>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void init(Map<Short, CodeFlagTables> table) {
|
||||||
|
String filename = BufrTables.RESOURCE_PATH + CodeFlagFilename;
|
||||||
|
try (InputStream is = CodeFlagTables.class.getResourceAsStream(filename)) {
|
||||||
|
SAXBuilder builder = new SAXBuilder();
|
||||||
|
builder.setExpandEntities(false);
|
||||||
|
org.jdom2.Document tdoc = builder.build(is);
|
||||||
|
Element root = tdoc.getRootElement();
|
||||||
|
|
||||||
|
List<Element> elems = root.getChildren();
|
||||||
|
for (Element elem : elems) {
|
||||||
|
String fxyS = elem.getChildText("FXY");
|
||||||
|
String desc = elem.getChildText("ElementName_en");
|
||||||
|
|
||||||
|
short fxy = Descriptor.getFxy2(fxyS);
|
||||||
|
CodeFlagTables ct = table.get(fxy);
|
||||||
|
if (ct == null) {
|
||||||
|
ct = new CodeFlagTables(fxy, desc);
|
||||||
|
table.put(fxy, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
String line = elem.getChildText("No");
|
||||||
|
String codeS = elem.getChildText("CodeFigure");
|
||||||
|
String value = elem.getChildText("EntryName_en");
|
||||||
|
|
||||||
|
if ((codeS == null) || (value == null))
|
||||||
|
continue;
|
||||||
|
if (value.toLowerCase().startsWith("reserved"))
|
||||||
|
continue;
|
||||||
|
if (value.toLowerCase().startsWith("not used"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int code;
|
||||||
|
if (codeS.toLowerCase().contains("all")) {
|
||||||
|
code = -1;
|
||||||
|
} else
|
||||||
|
try {
|
||||||
|
code = Integer.parseInt(codeS);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.debug("NumberFormatException on line " + line + " in " + codeS);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ct.addValue((short) code, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | JDOMException e) {
|
||||||
|
log.error("Can't read BUFR code table " + filename, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// TODO Make Immutable
|
||||||
|
private short fxy;
|
||||||
|
private String name;
|
||||||
|
private Map<Integer, String> map; // needs to be integer for EnumTypedef
|
||||||
|
|
||||||
|
CodeFlagTables(short fxy, String name) {
|
||||||
|
this.fxy = fxy;
|
||||||
|
this.name = (name == null) ? fxy() : name; // StringUtil2.replace(name, ' ', "_") + "("+fxy()+")";
|
||||||
|
map = new HashMap<>(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeFlagTables(short fxy, String name, Map<Integer, String> map) {
|
||||||
|
this.fxy = fxy;
|
||||||
|
this.name = (name == null) ? fxy() : name;
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, String> getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addValue(int value, String text) {
|
||||||
|
map.put(value, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getId() {
|
||||||
|
return fxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fxy() {
|
||||||
|
int f = fxy >> 14;
|
||||||
|
int x = (fxy & 0xff00) >> 8;
|
||||||
|
int y = (fxy & 0xff);
|
||||||
|
|
||||||
|
return f + "-" + x + "-" + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BufrRead mnemonic.java 1.0 05/09/2008
|
||||||
|
*
|
||||||
|
* @author Robb Kambic
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.google.re2j.Matcher;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.Descriptor;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that reads a mnemonic table. It doesn't include X < 48 and Y < 192 type of
|
||||||
|
* descriptors because they are already stored in the latest WMO tables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NcepMnemonic {
|
||||||
|
|
||||||
|
// | HEADR | 362001 | TABLE D ENTRY - PROFILE COORDINATES | |
|
||||||
|
private static final Pattern fields3 = Pattern.compile("^\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|\\s+(.*)\\s*\\|");
|
||||||
|
private static final Pattern fields2 = Pattern.compile("^\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|");
|
||||||
|
private static final Pattern fields5 =
|
||||||
|
Pattern.compile("^\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|\\s+(.*)\\s+\\|");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern to get 3 integers from beginning of line.
|
||||||
|
*/
|
||||||
|
private static final Pattern ints6 = Pattern.compile("^\\d{6}");
|
||||||
|
|
||||||
|
private static final int XlocalCutoff = 48;
|
||||||
|
private static final int YlocalCutoff = 192;
|
||||||
|
|
||||||
|
private static final boolean debugTable = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read NCEP mnemonic BUFR tables.
|
||||||
|
*
|
||||||
|
* @return true on success.
|
||||||
|
*/
|
||||||
|
public static boolean read(InputStream ios, BufrTables.Tables tables) throws IOException {
|
||||||
|
if (ios == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (tables.b == null)
|
||||||
|
tables.b = new TableB("fake", "fake");
|
||||||
|
if (tables.d == null)
|
||||||
|
tables.d = new TableD("fake", "fake");
|
||||||
|
|
||||||
|
HashMap<String, String> number = new HashMap<>(); // key = mnemonic value = fxy
|
||||||
|
HashMap<String, String> desc = new HashMap<>(); // key = mnemonic value = description
|
||||||
|
HashMap<String, String> mnseq = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
BufferedReader dataIS = new BufferedReader(new InputStreamReader(ios, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// read mnemonic table
|
||||||
|
Matcher m;
|
||||||
|
// read header info and disregard
|
||||||
|
while (true) {
|
||||||
|
String line = dataIS.readLine();
|
||||||
|
if (line == null)
|
||||||
|
throw new RuntimeException("Bad NCEP mnemonic BUFR table ");
|
||||||
|
if (line.contains("MNEMONIC"))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// read mnemonic, number, and description
|
||||||
|
// | HEADR | 362001 | TABLE D ENTRY - PROFILE COORDINATES |
|
||||||
|
while (true) {
|
||||||
|
String line = dataIS.readLine();
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
if (line.contains("MNEMONIC"))
|
||||||
|
break;
|
||||||
|
if (line.contains("----"))
|
||||||
|
continue;
|
||||||
|
if (line.startsWith("*"))
|
||||||
|
continue;
|
||||||
|
if (line.startsWith("| "))
|
||||||
|
continue;
|
||||||
|
m = fields3.matcher(line);
|
||||||
|
if (m.find()) {
|
||||||
|
String mnu = m.group(1).trim();
|
||||||
|
String fxy = m.group(2).trim();
|
||||||
|
if (fxy.startsWith("3")) {
|
||||||
|
number.put(mnu, fxy);
|
||||||
|
desc.put(mnu, m.group(3).replace("TABLE D ENTRY - ", "").trim());
|
||||||
|
} else if (fxy.startsWith("0")) {
|
||||||
|
number.put(mnu, fxy);
|
||||||
|
desc.put(mnu, m.group(3).replace("TABLE B ENTRY - ", "").trim());
|
||||||
|
} else if (fxy.startsWith("A")) {
|
||||||
|
number.put(mnu, fxy);
|
||||||
|
desc.put(mnu, m.group(3).replace("TABLE A ENTRY - ", "").trim());
|
||||||
|
}
|
||||||
|
} else if (debugTable) {
|
||||||
|
System.out.println("bad mnemonic, number, and description: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read in sequences using mnemonics
|
||||||
|
// | ETACLS1 | HEADR {PROFILE} SURF FLUX HYDR D10M {SLYR} XTRA |
|
||||||
|
while (true) {
|
||||||
|
String line = dataIS.readLine();
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
if (line.contains("MNEMONIC"))
|
||||||
|
break;
|
||||||
|
if (line.contains("----"))
|
||||||
|
continue;
|
||||||
|
if (line.startsWith("| "))
|
||||||
|
continue;
|
||||||
|
if (line.startsWith("*"))
|
||||||
|
continue;
|
||||||
|
m = fields2.matcher(line);
|
||||||
|
if (m.find()) {
|
||||||
|
String mnu = m.group(1).trim();
|
||||||
|
if (mnseq.containsKey(mnu)) { // concat lines with same mnu
|
||||||
|
String value = mnseq.get(mnu);
|
||||||
|
value = value + " " + m.group(2);
|
||||||
|
mnseq.put(mnu, value);
|
||||||
|
} else {
|
||||||
|
mnseq.put(mnu, m.group(2));
|
||||||
|
}
|
||||||
|
} else if (debugTable) {
|
||||||
|
System.out.println("bad sequence mnemonic: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create sequences, replacing mnemonics with numbers
|
||||||
|
for (Map.Entry<String, String> ent : mnseq.entrySet()) {
|
||||||
|
String seq = ent.getValue();
|
||||||
|
seq = seq.replaceAll("\\<", "1-1-0 0-31-0 ");
|
||||||
|
seq = seq.replaceAll("\\>", "");
|
||||||
|
seq = seq.replaceAll("\\{", "1-1-0 0-31-1 ");
|
||||||
|
seq = seq.replaceAll("\\}", "");
|
||||||
|
seq = seq.replaceAll("\\(", "1-1-0 0-31-2 ");
|
||||||
|
seq = seq.replaceAll("\\)", "");
|
||||||
|
|
||||||
|
StringTokenizer stoke = new StringTokenizer(seq, " ");
|
||||||
|
List<Short> list = new ArrayList<>();
|
||||||
|
while (stoke.hasMoreTokens()) {
|
||||||
|
String mn = stoke.nextToken();
|
||||||
|
if (mn.charAt(1) == '-') {
|
||||||
|
list.add(Descriptor.getFxy(mn));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// element descriptor needs hyphens
|
||||||
|
m = ints6.matcher(mn);
|
||||||
|
if (m.find()) {
|
||||||
|
String F = mn.substring(0, 1);
|
||||||
|
String X = removeLeading0(mn.substring(1, 3));
|
||||||
|
String Y = removeLeading0(mn.substring(3));
|
||||||
|
list.add(Descriptor.getFxy(F + "-" + X + "-" + Y));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (mn.startsWith("\"")) {
|
||||||
|
int idx = mn.lastIndexOf('"');
|
||||||
|
String count = mn.substring(idx + 1);
|
||||||
|
list.add(Descriptor.getFxy("1-1-" + count));
|
||||||
|
mn = mn.substring(1, idx);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (mn.startsWith(".")) {
|
||||||
|
String des = mn.substring(mn.length() - 4);
|
||||||
|
mn = mn.replace(des, "....");
|
||||||
|
|
||||||
|
}
|
||||||
|
String fxy = number.get(mn);
|
||||||
|
String F = fxy.substring(0, 1);
|
||||||
|
String X = removeLeading0(fxy.substring(1, 3));
|
||||||
|
String Y = removeLeading0(fxy.substring(3));
|
||||||
|
list.add(Descriptor.getFxy(F + "-" + X + "-" + Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
String fxy = number.get(ent.getKey());
|
||||||
|
String X = removeLeading0(fxy.substring(1, 3));
|
||||||
|
String Y = removeLeading0(fxy.substring(3));
|
||||||
|
// these are in latest tables
|
||||||
|
if (XlocalCutoff > Integer.parseInt(X) && YlocalCutoff > Integer.parseInt(Y))
|
||||||
|
continue;
|
||||||
|
// key = F + "-" + X + "-" + Y;
|
||||||
|
|
||||||
|
short seqX = Short.parseShort(X.trim());
|
||||||
|
short seqY = Short.parseShort(Y.trim());
|
||||||
|
|
||||||
|
tables.d.addDescriptor(seqX, seqY, ent.getKey(), list);
|
||||||
|
// short id = Descriptor.getFxy(key);
|
||||||
|
// sequences.put(Short.valueOf(id), tableD);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add some static repetition sequences
|
||||||
|
// LOOK why?
|
||||||
|
List<Short> list = new ArrayList<>();
|
||||||
|
// 16 bit delayed repetition
|
||||||
|
list.add(Descriptor.getFxy("1-1-0"));
|
||||||
|
list.add(Descriptor.getFxy("0-31-2"));
|
||||||
|
tables.d.addDescriptor((short) 60, (short) 1, "", list);
|
||||||
|
// tableD = new DescriptorTableD("", "3-60-1", list, false);
|
||||||
|
// tableD.put( "3-60-1", d);
|
||||||
|
// short id = Descriptor.getFxy("3-60-1");
|
||||||
|
// sequences.put(Short.valueOf(id), tableD);
|
||||||
|
|
||||||
|
list = new ArrayList<>();
|
||||||
|
// 8 bit delayed repetition
|
||||||
|
list.add(Descriptor.getFxy("1-1-0"));
|
||||||
|
list.add(Descriptor.getFxy("0-31-1"));
|
||||||
|
tables.d.addDescriptor((short) 60, (short) 2, "", list);
|
||||||
|
// tableD = new DescriptorTableD("", "3-60-2", list, false);
|
||||||
|
// tableD.put( "3-60-2", d);
|
||||||
|
// id = Descriptor.getFxy("3-60-2");
|
||||||
|
// sequences.put(Short.valueOf(id), tableD);
|
||||||
|
|
||||||
|
list = new ArrayList<>();
|
||||||
|
// 8 bit delayed repetition
|
||||||
|
list.add(Descriptor.getFxy("1-1-0"));
|
||||||
|
list.add(Descriptor.getFxy("0-31-1"));
|
||||||
|
tables.d.addDescriptor((short) 60, (short) 3, "", list);
|
||||||
|
// tableD = new DescriptorTableD("", "3-60-3", list, false);
|
||||||
|
// tableD.put( "3-60-3", d);
|
||||||
|
// id = Descriptor.getFxy("3-60-3");
|
||||||
|
// sequences.put(Short.valueOf(id), tableD);
|
||||||
|
|
||||||
|
list = new ArrayList<>();
|
||||||
|
// 1 bit delayed repetition
|
||||||
|
list.add(Descriptor.getFxy("1-1-0"));
|
||||||
|
list.add(Descriptor.getFxy("0-31-0"));
|
||||||
|
tables.d.addDescriptor((short) 60, (short) 4, "", list);
|
||||||
|
// tableD = new DescriptorTableD("", "3-60-4", list, false);
|
||||||
|
// tableD.put( "3-60-4", d);
|
||||||
|
// id = Descriptor.getFxy("3-60-4");
|
||||||
|
// sequences.put(Short.valueOf(id), tableD);
|
||||||
|
|
||||||
|
// add in element descriptors
|
||||||
|
// MNEMONIC | SCAL | REFERENCE | BIT | UNITS
|
||||||
|
// | FTIM | 0 | 0 | 24 | SECONDS |-------------|
|
||||||
|
|
||||||
|
// tableB = new TableB(tablename, tablename);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line = dataIS.readLine();
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
if (line.contains("MNEMONIC"))
|
||||||
|
break;
|
||||||
|
if (line.startsWith("| "))
|
||||||
|
continue;
|
||||||
|
if (line.startsWith("*"))
|
||||||
|
continue;
|
||||||
|
m = fields5.matcher(line);
|
||||||
|
if (m.find()) {
|
||||||
|
if (m.group(1).equals("")) {
|
||||||
|
// do nothing
|
||||||
|
|
||||||
|
} else if (number.containsKey(m.group(1).trim())) { // add descriptor to tableB
|
||||||
|
String fxy = number.get(m.group(1).trim());
|
||||||
|
String X = fxy.substring(1, 3);
|
||||||
|
String Y = fxy.substring(3);
|
||||||
|
String mnu = m.group(1).trim();
|
||||||
|
String descr = desc.get(mnu);
|
||||||
|
|
||||||
|
short x = Short.parseShort(X.trim());
|
||||||
|
short y = Short.parseShort(Y.trim());
|
||||||
|
|
||||||
|
// these are in latest tables so skip LOOK WHY
|
||||||
|
if (XlocalCutoff > x && YlocalCutoff > y)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int scale = Integer.parseInt(m.group(2).trim());
|
||||||
|
int refVal = Integer.parseInt(m.group(3).trim());
|
||||||
|
int width = Integer.parseInt(m.group(4).trim());
|
||||||
|
String units = m.group(5).trim();
|
||||||
|
|
||||||
|
tables.b.addDescriptor(x, y, scale, refVal, width, mnu, units, descr);
|
||||||
|
|
||||||
|
} else if (debugTable) {
|
||||||
|
System.out.println("bad element descriptors: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ios.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOK why ?
|
||||||
|
// default for NCEP
|
||||||
|
// 0; 63; 0; 0; 0; 16; Numeric; Byte count
|
||||||
|
tables.b.addDescriptor((short) 63, (short) 0, 0, 0, 16, "Byte count", "Numeric", null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String removeLeading0(String number) {
|
||||||
|
if (number.length() == 2 && number.startsWith("0")) {
|
||||||
|
number = number.substring(1);
|
||||||
|
} else if (number.length() == 3 && number.startsWith("00")) {
|
||||||
|
number = number.substring(2);
|
||||||
|
} else if (number.length() == 3 && number.startsWith("0")) {
|
||||||
|
number = number.substring(1);
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import ucar.unidata.util.StringUtil2;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ncep local table overrides
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since 8/22/13
|
||||||
|
*/
|
||||||
|
public class NcepTable {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NcepTable.class);
|
||||||
|
|
||||||
|
private static void readNcepTable(String location) throws IOException {
|
||||||
|
try (InputStream ios = BufrTables.openStream(location)) {
|
||||||
|
BufferedReader dataIS = new BufferedReader(new InputStreamReader(ios, StandardCharsets.UTF_8));
|
||||||
|
int count = 0;
|
||||||
|
while (true) {
|
||||||
|
String line = dataIS.readLine();
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
count++;
|
||||||
|
|
||||||
|
String[] flds = line.split(";");
|
||||||
|
if (flds.length < 3) {
|
||||||
|
log.warn("{} BAD split == {}", count, line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fldidx = 0;
|
||||||
|
try {
|
||||||
|
int cat = Integer.parseInt(flds[fldidx++].trim());
|
||||||
|
int subcat = Integer.parseInt(flds[fldidx++].trim());
|
||||||
|
String desc = StringUtil2.remove(flds[fldidx++], '"');
|
||||||
|
entries.add(new TableEntry(cat, subcat, desc));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{} {} BAD line == {}", count, fldidx, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<TableEntry> entries;
|
||||||
|
|
||||||
|
private static class TableEntry {
|
||||||
|
public int cat, subcat;
|
||||||
|
public String value;
|
||||||
|
|
||||||
|
public TableEntry(int cat, int subcat, String value) {
|
||||||
|
this.cat = cat;
|
||||||
|
this.subcat = subcat;
|
||||||
|
this.value = value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void init() {
|
||||||
|
entries = new ArrayList<>(100);
|
||||||
|
String location = "resource:/resources/bufrTables/local/ncep/DataSubCategories.csv";
|
||||||
|
try {
|
||||||
|
readNcepTable(location);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getDataSubcategory(int cat, int subcat) {
|
||||||
|
if (entries == null)
|
||||||
|
init();
|
||||||
|
|
||||||
|
for (TableEntry p : entries) {
|
||||||
|
if ((p.cat == cat) && (p.subcat == subcat))
|
||||||
|
return p.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import org.jdom2.input.SAXBuilder;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read standard WMO Table A (data categories).
|
||||||
|
*/
|
||||||
|
public class TableA {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableA.class);
|
||||||
|
private static final String TABLEA_FILENAME = "wmo/BUFR_37_0_0_TableA_en.xml";
|
||||||
|
private static Map<Integer, Descriptor> tableA;
|
||||||
|
private final String name;
|
||||||
|
private final String location;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <BUFR_19_1_1_TableA_en>
|
||||||
|
* <No>27</No>
|
||||||
|
* <CodeFigure>28</CodeFigure>
|
||||||
|
* <Meaning_en>Precision orbit (satellite)</Meaning_en>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFR_19_1_1_TableA_en>
|
||||||
|
*
|
||||||
|
* <Exp_BUFRTableA_E>
|
||||||
|
* <No>4</No>
|
||||||
|
* <CodeFigure>3</CodeFigure>
|
||||||
|
* <Meaning_E>Vertical soundings (satellite)</Meaning_E>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_BUFRTableA_E>
|
||||||
|
*/
|
||||||
|
private static void init() {
|
||||||
|
String filename = BufrTables.RESOURCE_PATH + TABLEA_FILENAME;
|
||||||
|
try (InputStream is = CodeFlagTables.class.getResourceAsStream(filename)) {
|
||||||
|
|
||||||
|
HashMap<Integer, Descriptor> map = new HashMap<>(100);
|
||||||
|
SAXBuilder builder = new SAXBuilder();
|
||||||
|
builder.setExpandEntities(false);
|
||||||
|
org.jdom2.Document tdoc = builder.build(is);
|
||||||
|
Element root = tdoc.getRootElement();
|
||||||
|
|
||||||
|
List<Element> elems = root.getChildren();
|
||||||
|
for (Element elem : elems) {
|
||||||
|
String line = elem.getChildText("No");
|
||||||
|
String codeS = elem.getChildText("CodeFigure");
|
||||||
|
String desc = elem.getChildText("Meaning_en");
|
||||||
|
|
||||||
|
try {
|
||||||
|
int code = Integer.parseInt(codeS);
|
||||||
|
Descriptor descriptor = new Descriptor(code, desc);
|
||||||
|
map.put(code, descriptor);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.debug("NumberFormatException on line " + line + " in " + codeS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
tableA = map;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Can't read BUFR code table " + filename, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableA(String name, String location) {
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
tableA = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Descriptor getDescriptor(int code) {
|
||||||
|
if (tableA == null)
|
||||||
|
init();
|
||||||
|
return tableA.get(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data category description, from table A
|
||||||
|
*
|
||||||
|
* @param cat data category
|
||||||
|
* @return category description, or null if not found
|
||||||
|
*/
|
||||||
|
public static String getDataCategory(int cat) {
|
||||||
|
if (tableA == null)
|
||||||
|
init();
|
||||||
|
Descriptor descriptor = tableA.get(cat);
|
||||||
|
return descriptor != null ? descriptor.getDescription() : "Unknown category=" + cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data category name, from table A
|
||||||
|
*
|
||||||
|
* @param cat data category
|
||||||
|
* @return category name, or null if not found
|
||||||
|
*/
|
||||||
|
public static String getDataCategoryName(int cat) {
|
||||||
|
if (tableA == null)
|
||||||
|
init();
|
||||||
|
Descriptor descriptor = tableA.get(cat);
|
||||||
|
return descriptor != null ? descriptor.getName() : "obs_" + cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableA.Descriptor addDescriptor(int code, String description) {
|
||||||
|
TableA.Descriptor d = new TableA.Descriptor(code, description);
|
||||||
|
tableA.put(code, d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Descriptor implements Comparable<Descriptor> {
|
||||||
|
private int code;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private boolean localOverride;
|
||||||
|
|
||||||
|
Descriptor(int code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
this.name = "obs_" + String.valueOf(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get code
|
||||||
|
*
|
||||||
|
* @return Code
|
||||||
|
*/
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(code) + " " + getName() + " " +
|
||||||
|
this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Descriptor o) {
|
||||||
|
return code - o.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return ((code >= 102) && (code <= 239));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalOverride(boolean isOverride) {
|
||||||
|
this.localOverride = isOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getLocalOverride() {
|
||||||
|
return localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import org.meteoinfo.data.meteodata.bufr.DataDescriptor;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BUFR Table B - Data descriptors
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Sep 25, 2008
|
||||||
|
*/
|
||||||
|
public class TableB {
|
||||||
|
private final String name;
|
||||||
|
private final String location;
|
||||||
|
private final Map<Short, Descriptor> map;
|
||||||
|
|
||||||
|
public TableB(String name, String location) {
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
map = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDescriptor(short x, short y, int scale, int refVal, int width, String name, String units,
|
||||||
|
String desc) {
|
||||||
|
short id = (short) ((x << 8) + y);
|
||||||
|
map.put(id, new Descriptor(x, y, scale, refVal, width, name, units, desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Descriptor getDescriptor(short id) {
|
||||||
|
return map.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Descriptor> getDescriptors() {
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Short> getKeys() {
|
||||||
|
return map.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Formatter out) {
|
||||||
|
List<Short> sortKeys = new ArrayList<>(getKeys());
|
||||||
|
Collections.sort(sortKeys);
|
||||||
|
|
||||||
|
out.format("Table B %s %n", name);
|
||||||
|
for (Short key : sortKeys) {
|
||||||
|
Descriptor dd = getDescriptor(key);
|
||||||
|
if (dd != null)
|
||||||
|
dd.show(out);
|
||||||
|
out.format("%n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite pattern - collection of TableB
|
||||||
|
*/
|
||||||
|
public static class Composite extends TableB {
|
||||||
|
List<TableB> list = new ArrayList<>(3);
|
||||||
|
|
||||||
|
public Composite(String name, String location) {
|
||||||
|
super(name, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTable(TableB b) {
|
||||||
|
list.add(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Descriptor getDescriptor(short id) {
|
||||||
|
for (TableB b : list) {
|
||||||
|
Descriptor d = b.getDescriptor(id);
|
||||||
|
if (d != null)
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Descriptor> getDescriptors() {
|
||||||
|
ArrayList<Descriptor> result = new ArrayList<>(3000);
|
||||||
|
for (TableB b : list)
|
||||||
|
result.addAll(b.getDescriptors());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Short> getKeys() {
|
||||||
|
ArrayList<Short> result = new ArrayList<>(3000);
|
||||||
|
for (TableB b : list)
|
||||||
|
result.addAll(b.getKeys());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// inner class
|
||||||
|
@Immutable
|
||||||
|
public class Descriptor implements Comparable<Descriptor> {
|
||||||
|
|
||||||
|
private final short x, y;
|
||||||
|
private final int scale;
|
||||||
|
private final int refVal;
|
||||||
|
private final int dataWidth;
|
||||||
|
private final String units;
|
||||||
|
private final String name;
|
||||||
|
private final String desc;
|
||||||
|
private final boolean numeric;
|
||||||
|
private boolean localOverride;
|
||||||
|
|
||||||
|
Descriptor(short x, short y, int scale, int refVal, int width, String name, String units, String desc) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.scale = scale;
|
||||||
|
this.refVal = refVal;
|
||||||
|
this.dataWidth = width;
|
||||||
|
this.name = name.trim();
|
||||||
|
this.units = units.trim().intern();
|
||||||
|
this.desc = desc;
|
||||||
|
|
||||||
|
this.numeric = !DataDescriptor.isInternationalAlphabetUnit(units);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScale() {
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRefVal() {
|
||||||
|
return refVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDataWidth() {
|
||||||
|
return dataWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnits() {
|
||||||
|
return units;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() { // optional - use as long name
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fxy as a short
|
||||||
|
*
|
||||||
|
* @return fxy encoded as a short
|
||||||
|
*/
|
||||||
|
public short getId() {
|
||||||
|
return (short) ((x << 8) + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fxy as a String, eg 0-5-22
|
||||||
|
*
|
||||||
|
* @return fxy encoded as a String
|
||||||
|
*/
|
||||||
|
public String getFxy() {
|
||||||
|
return "0-" + x + "-" + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is descriptor numeric or String
|
||||||
|
*
|
||||||
|
* @return true if numeric
|
||||||
|
*/
|
||||||
|
public boolean isNumeric() {
|
||||||
|
return numeric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return ((x >= 48) || (y >= 192));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalOverride(boolean isOverride) {
|
||||||
|
this.localOverride = isOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getLocalOverride() {
|
||||||
|
return localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
Formatter out = new Formatter();
|
||||||
|
show(out);
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return getLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void show(Formatter out) {
|
||||||
|
out.format(" %8s scale=%d refVal=%d width=%d units=(%s) name=(%s)", getFxy(), scale, refVal, dataWidth, units,
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Descriptor o) {
|
||||||
|
return getId() - o.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BUFR Table C - Data operators
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Oct 25, 2008
|
||||||
|
*/
|
||||||
|
public class TableC {
|
||||||
|
private static final String[] tableCdesc = new String[38];
|
||||||
|
|
||||||
|
static {
|
||||||
|
tableCdesc[1] = "change data width";
|
||||||
|
tableCdesc[2] = "change scale";
|
||||||
|
tableCdesc[3] = "change reference value";
|
||||||
|
tableCdesc[4] = "add associated field";
|
||||||
|
tableCdesc[5] = "signify character";
|
||||||
|
tableCdesc[6] = "signify data width for next descriptor";
|
||||||
|
tableCdesc[7] = "increase scale, reference value, and data width";
|
||||||
|
tableCdesc[21] = "data not present";
|
||||||
|
tableCdesc[22] = "quality information follows";
|
||||||
|
tableCdesc[23] = "substituted values operator";
|
||||||
|
tableCdesc[24] = "first order statistics";
|
||||||
|
tableCdesc[25] = "difference statistics";
|
||||||
|
tableCdesc[32] = "replaced/retained values";
|
||||||
|
tableCdesc[35] = "cancel backward data reference";
|
||||||
|
tableCdesc[36] = "define data present bit-map";
|
||||||
|
tableCdesc[37] = "use/cancel data present bit-map";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getOperatorName(int index) {
|
||||||
|
if ((index < 0) || (index >= tableCdesc.length))
|
||||||
|
return "unknown";
|
||||||
|
return (tableCdesc[index] == null) ? "unknown" : tableCdesc[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BUFR Table D - Data sequences
|
||||||
|
*
|
||||||
|
* @author caron
|
||||||
|
* @since Sep 25, 2008
|
||||||
|
*/
|
||||||
|
public class TableD {
|
||||||
|
private String name;
|
||||||
|
private String location;
|
||||||
|
private Map<Short, Descriptor> map;
|
||||||
|
|
||||||
|
public TableD(String name, String location) {
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
map = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Descriptor addDescriptor(short x, short y, String name, List<Short> seq) {
|
||||||
|
short id = (short) ((3 << 14) + (x << 8) + y);
|
||||||
|
Descriptor d = new Descriptor(x, y, name, seq);
|
||||||
|
map.put(id, d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Descriptor getDescriptor(short id) {
|
||||||
|
return map.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Descriptor> getDescriptors() {
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Formatter out) {
|
||||||
|
Collection<Short> keys = map.keySet();
|
||||||
|
List<Short> sortKeys = new ArrayList<>(keys);
|
||||||
|
Collections.sort(sortKeys);
|
||||||
|
|
||||||
|
out.format("Table D %s %n", name);
|
||||||
|
for (Short key : sortKeys) {
|
||||||
|
Descriptor dd = map.get(key);
|
||||||
|
dd.show(out, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Descriptor implements Comparable<Descriptor> {
|
||||||
|
private short x, y;
|
||||||
|
private String name;
|
||||||
|
private List<Short> seq;
|
||||||
|
private boolean localOverride;
|
||||||
|
|
||||||
|
Descriptor(short x, short y, String name, List<Short> seq) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.name = name;
|
||||||
|
this.seq = seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Short> getSequence() {
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFeature(short f) {
|
||||||
|
seq.add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fxy as a short
|
||||||
|
*
|
||||||
|
* @return fxy encoded as a short
|
||||||
|
*/
|
||||||
|
public short getId() {
|
||||||
|
return (short) ((3 << 14) + (x << 8) + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fxy as a String, eg 3-4-22
|
||||||
|
*
|
||||||
|
* @return fxy encoded as a String
|
||||||
|
*/
|
||||||
|
public String getFxy() {
|
||||||
|
return "3-" + x + "-" + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return getFxy() + " " + getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Formatter out, boolean oneline) {
|
||||||
|
out.format(" %8s: name=(%s) seq=", getFxy(), name);
|
||||||
|
if (oneline) {
|
||||||
|
for (short s : seq)
|
||||||
|
out.format(" %s,", ucar.nc2.iosp.bufr.Descriptor.makeString(s));
|
||||||
|
out.format("%n");
|
||||||
|
} else {
|
||||||
|
for (short s : seq)
|
||||||
|
out.format(" %s%n", ucar.nc2.iosp.bufr.Descriptor.makeString(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Descriptor o) {
|
||||||
|
return getId() - o.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return ((x >= 48) || (y >= 192));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalOverride(boolean isOverride) {
|
||||||
|
this.localOverride = isOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getLocalOverride() {
|
||||||
|
return localOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,458 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 1998-2020 University Corporation for Atmospheric Research/Unidata
|
||||||
|
* See LICENSE for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.meteoinfo.data.meteodata.bufr.tables;
|
||||||
|
|
||||||
|
import org.jdom2.Element;
|
||||||
|
import org.jdom2.JDOMException;
|
||||||
|
import org.jdom2.input.SAXBuilder;
|
||||||
|
import ucar.nc2.wmo.Util;
|
||||||
|
import ucar.unidata.util.StringUtil2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read WMO BUFR XML formats
|
||||||
|
*
|
||||||
|
* @author John
|
||||||
|
* @since 8/10/11
|
||||||
|
*/
|
||||||
|
public class WmoXmlReader {
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WmoXmlReader.class);
|
||||||
|
|
||||||
|
public enum Version {
|
||||||
|
BUFR_14_1_0, BUFR_14_2_0, BUFR_15_1_1, BUFR_16_0_0, BUFR_WMO;
|
||||||
|
|
||||||
|
String[] getElemNamesB() {
|
||||||
|
if (this == BUFR_14_1_0) {
|
||||||
|
return new String[]{"BC_TableB_BUFR14_1_0_CREX_6_1_0", "ElementName_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_14_2_0) {
|
||||||
|
return new String[]{"Exporting_BCTableB_E", "ElementName"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_15_1_1) {
|
||||||
|
return new String[]{"Exp_JointTableB_E", "ElementName_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_16_0_0) {
|
||||||
|
return new String[]{"Exp_BUFRCREXTableB_E", "ElementName_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_WMO) { // from now on this is the element name
|
||||||
|
return new String[]{null, "ElementName_en"};
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] getElemNamesD() {
|
||||||
|
if (this == BUFR_14_1_0) {
|
||||||
|
return new String[]{"B_TableD_BUFR14_1_0_CREX_6_1_0", "ElementName1_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_14_2_0) {
|
||||||
|
return new String[]{"Exporting_BUFRTableD_E", "ElementName1"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_15_1_1) {
|
||||||
|
return new String[]{"Exp_BUFRTableD_E", "ElementName_E", "ExistingElementName_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_16_0_0) {
|
||||||
|
return new String[]{"Exp_BUFRTableD_E", "ElementName_E", "ExistingElementName_E"};
|
||||||
|
|
||||||
|
} else if (this == BUFR_WMO) {
|
||||||
|
return new String[]{null, "ElementName_en"};
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 14.1
|
||||||
|
* <BC_TableB_BUFR14_1_0_CREX_6_1_0>
|
||||||
|
* <SNo>1</SNo>
|
||||||
|
* <Class>00</Class>
|
||||||
|
* <FXY>000001</FXY>
|
||||||
|
* <ElementName_E>Table A: entry</ElementName_E>
|
||||||
|
* <ElementName_F>Table A : entr?e</ElementName_F>
|
||||||
|
* <ElementName_R>??????? ?: ???????</ElementName_R>
|
||||||
|
* <ElementName_S>Tabla A: elemento</ElementName_S>
|
||||||
|
* <BUFR_Unit>CCITT IA5</BUFR_Unit>
|
||||||
|
* <BUFR_Scale>0</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>24</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>Character</CREX_Unit>
|
||||||
|
* <CREX_Scale>0</CREX_Scale>
|
||||||
|
* <CREX_DataWidth>3</CREX_DataWidth>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* <NotesToTable_E>Notes: (see)#BUFR14_1_0_CREX6_1_0_Notes.doc#BC_Cl000</NotesToTable_E>
|
||||||
|
* </BC_TableB_BUFR14_1_0_CREX_6_1_0>
|
||||||
|
*
|
||||||
|
* 14.2
|
||||||
|
* <Exporting_BCTableB_E>
|
||||||
|
* <No>2</No>
|
||||||
|
* <ClassNo>00</ClassNo>
|
||||||
|
* <ClassName>BUFR/CREX table entries</ClassName>
|
||||||
|
* <FXY>000002</FXY>
|
||||||
|
* <ElementName>Table A: data category description, line 1 </ElementName>
|
||||||
|
* <BUFR_Unit>CCITT IA5 </BUFR_Unit>
|
||||||
|
* <BUFR_Scale>0</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>256</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>Character</CREX_Unit>
|
||||||
|
* <CREX_Scale>0</CREX_Scale>
|
||||||
|
* <CREX_DataWidth>32</CREX_DataWidth>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exporting_BCTableB_E>
|
||||||
|
*
|
||||||
|
* 15.1
|
||||||
|
* <Exp_JointTableB_E>
|
||||||
|
* <No>1</No>
|
||||||
|
* <ClassNo>00</ClassNo>
|
||||||
|
* <ClassName_E>BUFR/CREX table entries</ClassName_E>
|
||||||
|
* <FXY>000001</FXY>
|
||||||
|
* <ElementName_E>Table A: entry</ElementName_E>
|
||||||
|
* <BUFR_Unit>CCITT IA5</BUFR_Unit>
|
||||||
|
* <BUFR_Scale>0</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>24</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>Character</CREX_Unit>
|
||||||
|
* <CREX_Scale>0</CREX_Scale>
|
||||||
|
* <CREX_DataWidth_Char>3</CREX_DataWidth_Char>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_JointTableB_E>
|
||||||
|
*
|
||||||
|
* 16.0
|
||||||
|
* <Exp_BUFRCREXTableB_E>
|
||||||
|
* <No>681</No>
|
||||||
|
* <ClassNo>13</ClassNo>
|
||||||
|
* <ClassName_E>Hydrographic and hydrological elements</ClassName_E>
|
||||||
|
* <FXY>013060</FXY>
|
||||||
|
* <ElementName_E>Total accumulated precipitation</ElementName_E>
|
||||||
|
* <BUFR_Unit>kg m-2</BUFR_Unit>
|
||||||
|
* <BUFR_Scale>1</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>-1</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>17</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>kg m-2</CREX_Unit>
|
||||||
|
* <CREX_Scale>1</CREX_Scale>
|
||||||
|
* <CREX_DataWidth_Char>5</CREX_DataWidth_Char>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_BUFRCREXTableB_E>
|
||||||
|
*
|
||||||
|
* <BUFRCREX_17_0_0_TableB_en>
|
||||||
|
* <No>8</No>
|
||||||
|
* <ClassNo>00</ClassNo>
|
||||||
|
* <ClassName_en>BUFR/CREX table entries</ClassName_en>
|
||||||
|
* <FXY>000008</FXY>
|
||||||
|
* <ElementName_en>BUFR Local table version number</ElementName_en>
|
||||||
|
* <Note_en>(see Note 4)</Note_en>
|
||||||
|
* <BUFR_Unit>CCITT IA5</BUFR_Unit>
|
||||||
|
* <BUFR_Scale>0</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>16</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>Character</CREX_Unit>
|
||||||
|
* <CREX_Scale>0</CREX_Scale>
|
||||||
|
* <CREX_DataWidth_Char>2</CREX_DataWidth_Char>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFRCREX_17_0_0_TableB_en>
|
||||||
|
*
|
||||||
|
* <BUFRCREX_22_0_1_TableB_en>
|
||||||
|
* <No>1018</No>
|
||||||
|
* <ClassNo>21</ClassNo>
|
||||||
|
* <ClassName_en>BUFR/CREX Radar data</ClassName_en>
|
||||||
|
* <FXY>021073</FXY>
|
||||||
|
* <ElementName_en>Satellite altimeter instrument mode</ElementName_en>
|
||||||
|
* <BUFR_Unit>Flag table</BUFR_Unit>
|
||||||
|
* <BUFR_Scale>0</BUFR_Scale>
|
||||||
|
* <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
|
||||||
|
* <BUFR_DataWidth_Bits>9</BUFR_DataWidth_Bits>
|
||||||
|
* <CREX_Unit>Flag table</CREX_Unit>
|
||||||
|
* <CREX_Scale>0</CREX_Scale>
|
||||||
|
* <CREX_DataWidth_Char>3</CREX_DataWidth_Char>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFRCREX_22_0_1_TableB_en>
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void readWmoXmlTableB(InputStream ios, TableB b) throws IOException {
|
||||||
|
org.jdom2.Document doc;
|
||||||
|
try {
|
||||||
|
SAXBuilder builder = new SAXBuilder();
|
||||||
|
builder.setExpandEntities(false);
|
||||||
|
doc = builder.build(ios);
|
||||||
|
} catch (JDOMException e) {
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Element root = doc.getRootElement();
|
||||||
|
|
||||||
|
// what elements do we need to parse tableB?
|
||||||
|
String[] elems = elementsUsedFromTableB(root);
|
||||||
|
|
||||||
|
List<Element> unrecognizedSequenceTermElements = new ArrayList<>();
|
||||||
|
List<Element> featList = root.getChildren();
|
||||||
|
for (Element elem : featList) {
|
||||||
|
Element ce = null;
|
||||||
|
for (int nameTest = 1; nameTest < elems.length; nameTest++) {
|
||||||
|
ce = elem.getChild(elems[nameTest]);
|
||||||
|
if (ce != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ce == null) {
|
||||||
|
unrecognizedSequenceTermElements.add(elem);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = Util.cleanName(ce.getTextNormalize());
|
||||||
|
String units = cleanUnit(elem.getChildTextNormalize("BUFR_Unit"));
|
||||||
|
int x = 0, y = 0, scale = 0, reference = 0, width = 0;
|
||||||
|
|
||||||
|
String fxy = null;
|
||||||
|
String s = null;
|
||||||
|
try {
|
||||||
|
fxy = elem.getChildTextNormalize("FXY");
|
||||||
|
int xy = Integer.parseInt(cleanNumber(fxy));
|
||||||
|
x = xy / 1000;
|
||||||
|
y = xy % 1000;
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn(" key {} name '{}' fails parsing", fxy, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = elem.getChildTextNormalize("BUFR_Scale");
|
||||||
|
scale = Integer.parseInt(cleanNumber(s));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn(" key {} name '{}' has bad scale='{}'", fxy, name, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = elem.getChildTextNormalize("BUFR_ReferenceValue");
|
||||||
|
reference = Integer.parseInt(cleanNumber(s));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn(" key {} name '{}' has bad reference='{}'", fxy, name, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = elem.getChildTextNormalize("BUFR_DataWidth_Bits");
|
||||||
|
width = Integer.parseInt(cleanNumber(s));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn(" key {} name '{}' has bad width='{}'", fxy, name, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.addDescriptor((short) x, (short) y, scale, reference, width, name, units, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
logUnrecognizedElements(unrecognizedSequenceTermElements, "B", b.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
ios.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String cleanNumber(String s) {
|
||||||
|
return StringUtil2.remove(s, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String cleanUnit(String unit) {
|
||||||
|
String result = StringUtil2.remove(unit, 176);
|
||||||
|
return StringUtil2.replace(result, (char) 65533, "2"); // seems to be a superscript 2 in some language
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] elementsUsedFromTableD(Element root) {
|
||||||
|
return elementsUsedFromTable(root, "D");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] elementsUsedFromTableB(Element root) {
|
||||||
|
return elementsUsedFromTable(root, "B");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] elementsUsedFromTable(Element root, String tableType) {
|
||||||
|
String[] elems = null;
|
||||||
|
// does the table have its own enum value? If so, use it.
|
||||||
|
for (Version v : Version.values()) {
|
||||||
|
boolean match = root.getAttributes().stream().anyMatch(attr -> attr.getValue().contains(v.toString()));
|
||||||
|
if (match) {
|
||||||
|
elems = tableType.equals("B") ? v.getElemNamesB() : v.getElemNamesD();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exact table match not found. Try seeing if the table uses
|
||||||
|
// the sequence element from a version defined in the Version enum.
|
||||||
|
// Note: will stop on the first version that works, as defined by
|
||||||
|
// the order of the Version enum. might not be correct.
|
||||||
|
if (elems == null) {
|
||||||
|
for (Version v : Version.values()) {
|
||||||
|
elems = tableType.equals("B") ? v.getElemNamesB() : v.getElemNamesD();
|
||||||
|
List<Element> featList = null;
|
||||||
|
if ((elems != null) && (elems.length > 0)) {
|
||||||
|
featList = root.getChildren(elems[0]);
|
||||||
|
}
|
||||||
|
if (featList != null && !featList.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logUnrecognizedElements(List<Element> unrecognizedSequenceTermElements, String tableType,
|
||||||
|
String location) {
|
||||||
|
// not every sequence entry in the WMO xml table D files is processed. This has caused trouble before.
|
||||||
|
// this is a pretty specific, low level debug message to hopefully give a clue to us in the future
|
||||||
|
// that if we are having trouble decoding BUFR messages, maybe we're not fully parsing the WMO xml TableD
|
||||||
|
// entries, and so the sequence being used might not be the full sequence necessary to decode.
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
if (unrecognizedSequenceTermElements.size() > 0) {
|
||||||
|
StringBuilder msgBuilder = new StringBuilder();
|
||||||
|
msgBuilder.append(String.format("%d Unprocessed sequences in WMO table %s %s",
|
||||||
|
unrecognizedSequenceTermElements.size(), tableType, location));
|
||||||
|
if (tableType.equals("D")) {
|
||||||
|
String tableDChecker = "bufr/src/test/java/ucar/nc2/iosp/bufr/tables/WmoTableDVariations.java";
|
||||||
|
msgBuilder
|
||||||
|
.append(String.format("This might be ok, but to know for sure, consider running %s", tableDChecker));
|
||||||
|
}
|
||||||
|
log.debug(msgBuilder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <B_TableD_BUFR14_1_0_CREX_6_1_0>
|
||||||
|
* <SNo>2647</SNo>
|
||||||
|
* <Category>10</Category>
|
||||||
|
* <FXY1>310013</FXY1>
|
||||||
|
* <ElementName1_E>(AVHRR (GAC) report)</ElementName1_E>
|
||||||
|
* <FXY2>004005</FXY2>
|
||||||
|
* <ElementName2_E>Minute</ElementName2_E>
|
||||||
|
* <Remarks_E>Minute</Remarks_E>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </B_TableD_BUFR14_1_0_CREX_6_1_0>
|
||||||
|
*
|
||||||
|
* 14.2.0
|
||||||
|
* <Exporting_BUFRTableD_E>
|
||||||
|
* <No>2901</No>
|
||||||
|
* <Category>10</Category>
|
||||||
|
* <CategoryOfSequences>Vertical sounding sequences (satellite data)</CategoryOfSequences>
|
||||||
|
* <FXY1>310025</FXY1>
|
||||||
|
* <ElementName1>(SSMIS Temperature data record)</ElementName1>
|
||||||
|
* <FXY2>004006</FXY2>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exporting_BUFRTableD_E>
|
||||||
|
*
|
||||||
|
* 15.1.1
|
||||||
|
* <Exp_BUFRTableD_E>
|
||||||
|
* <No>102</No>
|
||||||
|
* <Category>01</Category>
|
||||||
|
* <CategoryOfSequences_E>Location and identification sequences</CategoryOfSequences_E>
|
||||||
|
* <FXY1>301034</FXY1>
|
||||||
|
* <Title_E>(Buoy/platform - fixed)</Title_E>
|
||||||
|
* <FXY2>001005</FXY2>
|
||||||
|
* <ElementName_E>Buoy/platform identifier</ElementName_E>
|
||||||
|
* <ExistingElementName_E>Buoy/platform identifier</ExistingElementName_E>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_BUFRTableD_E>
|
||||||
|
*
|
||||||
|
* 16.0.0
|
||||||
|
* <Exp_BUFRTableD_E>
|
||||||
|
* <No>402</No>
|
||||||
|
* <Category>02</Category>
|
||||||
|
* <CategoryOfSequences_E>Meteorological sequences common to surface data</CategoryOfSequences_E>
|
||||||
|
* <FXY1>302001</FXY1>
|
||||||
|
* <FXY2>010051</FXY2>
|
||||||
|
* <ElementName_E>Pressure reduced to mean sea level</ElementName_E>
|
||||||
|
* <ExistingElementName_E>Pressure reduced to mean sea level</ExistingElementName_E>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </Exp_BUFRTableD_E>
|
||||||
|
*
|
||||||
|
* <BUFR_19_1_1_TableD_en>
|
||||||
|
* <No>4</No>
|
||||||
|
* <Category>00</Category>
|
||||||
|
* <CategoryOfSequences_en>BUFR table entries sequences</CategoryOfSequences_en>
|
||||||
|
* <FXY1>300003</FXY1>
|
||||||
|
* <Title_en>(F, X, Y of descriptor to be added or defined)</Title_en>
|
||||||
|
* <FXY2>000011</FXY2>
|
||||||
|
* <ElementName_en>X descriptor to be added or defined</ElementName_en>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFR_19_1_1_TableD_en>
|
||||||
|
*
|
||||||
|
* <BUFR_22_0_1_TableD_en>
|
||||||
|
* <No>5874</No>
|
||||||
|
* <Category>15</Category>
|
||||||
|
* <CategoryOfSequences_en>Oceanographic report sequences</CategoryOfSequences_en>
|
||||||
|
* <FXY1>315004</FXY1>
|
||||||
|
* <Title_en>(XBT temperature profile data sequence)</Title_en>
|
||||||
|
* <FXY2>025061</FXY2>
|
||||||
|
* <ElementName_en>Software identification and version number</ElementName_en>
|
||||||
|
* <Status>Operational</Status>
|
||||||
|
* </BUFR_22_0_1_TableD_en>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void readWmoXmlTableD(InputStream ios, TableD tableD) throws IOException {
|
||||||
|
org.jdom2.Document doc;
|
||||||
|
try {
|
||||||
|
SAXBuilder builder = new SAXBuilder();
|
||||||
|
builder.setExpandEntities(false);
|
||||||
|
doc = builder.build(ios);
|
||||||
|
} catch (JDOMException e) {
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
int currSeqno = -1;
|
||||||
|
TableD.Descriptor currDesc = null;
|
||||||
|
|
||||||
|
Element root = doc.getRootElement();
|
||||||
|
|
||||||
|
// what elements do we need to parse tableD?
|
||||||
|
String[] elems = elementsUsedFromTableD(root);
|
||||||
|
List<Element> unrecognizedSequenceTermElements = new ArrayList<>();
|
||||||
|
List<Element> featList = root.getChildren();
|
||||||
|
for (Element elem : featList) {
|
||||||
|
// see if element in table is recognized
|
||||||
|
Element ce = null;
|
||||||
|
for (int nameTest = 1; nameTest < elems.length; nameTest++) {
|
||||||
|
ce = elem.getChild(elems[nameTest]);
|
||||||
|
if (ce != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ce == null) {
|
||||||
|
unrecognizedSequenceTermElements.add(elem);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String seqs = elem.getChildTextNormalize("FXY1");
|
||||||
|
int seq = Integer.parseInt(seqs);
|
||||||
|
|
||||||
|
if (currSeqno != seq) {
|
||||||
|
int y = seq % 1000;
|
||||||
|
int w = seq / 1000;
|
||||||
|
int x = w % 100;
|
||||||
|
String seqName = Util.cleanName(ce.getTextNormalize());
|
||||||
|
currDesc = tableD.addDescriptor((short) x, (short) y, seqName, new ArrayList<>());
|
||||||
|
currSeqno = seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fnos = elem.getChildTextNormalize("FXY2");
|
||||||
|
int fno = Integer.parseInt(fnos);
|
||||||
|
int y = fno % 1000;
|
||||||
|
int w = fno / 1000;
|
||||||
|
int x = w % 100;
|
||||||
|
int f = w / 100;
|
||||||
|
int fxy = (f << 14) + (x << 8) + y;
|
||||||
|
currDesc.addFeature((short) fxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
logUnrecognizedElements(unrecognizedSequenceTermElements, "D", tableD.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
ios.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.meteoinfo.data.meteodata.netcdf;
|
package org.meteoinfo.data.meteodata.netcdf;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -14,6 +15,8 @@ import org.meteoinfo.data.dimarray.Dimension;
|
|||||||
import org.meteoinfo.data.meteodata.Attribute;
|
import org.meteoinfo.data.meteodata.Attribute;
|
||||||
import org.meteoinfo.data.meteodata.Variable;
|
import org.meteoinfo.data.meteodata.Variable;
|
||||||
import org.meteoinfo.ndarray.*;
|
import org.meteoinfo.ndarray.*;
|
||||||
|
import org.meteoinfo.ndarray.math.ArrayMath;
|
||||||
|
import org.meteoinfo.ndarray.math.ArrayUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -158,6 +161,7 @@ public class NCUtil {
|
|||||||
var.setShortName(ncVar.getShortName());
|
var.setShortName(ncVar.getShortName());
|
||||||
var.setDataType(convertDataType(ncVar.getDataType()));
|
var.setDataType(convertDataType(ncVar.getDataType()));
|
||||||
var.setDescription(ncVar.getDescription());
|
var.setDescription(ncVar.getDescription());
|
||||||
|
var.setMemberOfStructure(ncVar.isMemberOfStructure());
|
||||||
var.setDimensions(convertDimensions(ncVar.getDimensions()));
|
var.setDimensions(convertDimensions(ncVar.getDimensions()));
|
||||||
for (ucar.nc2.Attribute ncAttr : ncVar.getAttributes()) {
|
for (ucar.nc2.Attribute ncAttr : ncVar.getAttributes()) {
|
||||||
var.addAttribute(convertAttribute(ncAttr));
|
var.addAttribute(convertAttribute(ncAttr));
|
||||||
@ -184,4 +188,223 @@ public class NCUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pack data from variable
|
||||||
|
* @param var The variable
|
||||||
|
* @return Pack data
|
||||||
|
*/
|
||||||
|
public static double[] getPackData(ucar.nc2.Variable var) {
|
||||||
|
double add_offset = 0, scale_factor = 1, missingValue = Double.NaN;
|
||||||
|
for (int i = 0; i < var.getAttributes().size(); i++) {
|
||||||
|
ucar.nc2.Attribute att = var.getAttributes().get(i);
|
||||||
|
String attName = att.getShortName();
|
||||||
|
if (attName.equals("add_offset")) {
|
||||||
|
add_offset = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attName.equals("scale_factor")) {
|
||||||
|
scale_factor = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attName.equals("missing_value")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MODIS NetCDF data
|
||||||
|
if (attName.equals("_FillValue")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new double[]{add_offset, scale_factor, missingValue};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pack data from variable
|
||||||
|
* @param var The variable
|
||||||
|
* @return Pack data
|
||||||
|
*/
|
||||||
|
public static double[] getPackData(Variable var) {
|
||||||
|
double add_offset = 0, scale_factor = 1, missingValue = Double.NaN;
|
||||||
|
for (int i = 0; i < var.getAttributes().size(); i++) {
|
||||||
|
Attribute att = var.getAttributes().get(i);
|
||||||
|
String attName = att.getShortName();
|
||||||
|
if (attName.equals("add_offset")) {
|
||||||
|
add_offset = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attName.equals("scale_factor")) {
|
||||||
|
scale_factor = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attName.equals("missing_value")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MODIS NetCDF data
|
||||||
|
if (attName.equals("_FillValue")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new double[]{add_offset, scale_factor, missingValue};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get missing value from variable
|
||||||
|
* @param var The variable
|
||||||
|
* @return Missing value
|
||||||
|
*/
|
||||||
|
public static double getMissingValue(Variable var) {
|
||||||
|
double missingValue = Double.NaN;
|
||||||
|
for (int i = 0; i < var.getAttributes().size(); i++) {
|
||||||
|
Attribute att = var.getAttributes().get(i);
|
||||||
|
String attName = att.getShortName();
|
||||||
|
|
||||||
|
if (attName.equals("missing_value")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MODIS NetCDF data
|
||||||
|
if (attName.equals("_FillValue")) {
|
||||||
|
try {
|
||||||
|
missingValue = Double.parseDouble(att.getValue(0).toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return missingValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data array from an ucar ArraySequence
|
||||||
|
*
|
||||||
|
* @param parentArray The ucar ArraySequence
|
||||||
|
* @param memberName Member name
|
||||||
|
* @return Read data array
|
||||||
|
*/
|
||||||
|
public static Array readSequence(ucar.ma2.ArrayStructure parentArray, String memberName) throws IOException {
|
||||||
|
ucar.ma2.StructureMembers.Member member = parentArray.findMember(memberName);
|
||||||
|
ucar.ma2.Array r = parentArray.extractMemberArray(member);
|
||||||
|
|
||||||
|
return convertArray(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data array from an ucar ArrayObject with ArraySequence elements
|
||||||
|
*
|
||||||
|
* @param parentArray The ucar ArrayObject with ArraySequence elements
|
||||||
|
* @param memberName Member name
|
||||||
|
* @param index Record index
|
||||||
|
* @param missingValue Missing value
|
||||||
|
* @return Read data array
|
||||||
|
*/
|
||||||
|
public static Array readSequenceRecord(ucar.ma2.ArrayObject parentArray, String memberName,
|
||||||
|
int index, double missingValue) throws IOException {
|
||||||
|
int n = (int) parentArray.getSize();
|
||||||
|
ucar.ma2.IndexIterator pIter = parentArray.getIndexIterator();
|
||||||
|
ucar.ma2.StructureMembers.Member member = null;
|
||||||
|
while (pIter.hasNext()) {
|
||||||
|
ucar.ma2.ArrayStructure sArray = (ucar.ma2.ArrayStructure) pIter.getObjectNext();
|
||||||
|
if (sArray != null) {
|
||||||
|
member = sArray.findMember(memberName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataType dataType = convertDataType(member.getDataType());
|
||||||
|
Array r = Array.factory(dataType, new int[]{n});
|
||||||
|
pIter = parentArray.getIndexIterator();
|
||||||
|
IndexIterator rIter = r.getIndexIterator();
|
||||||
|
while (pIter.hasNext()) {
|
||||||
|
ucar.ma2.ArrayStructure sArray = (ucar.ma2.ArrayStructure) pIter.getObjectNext();
|
||||||
|
if (sArray == null) {
|
||||||
|
rIter.setObjectNext(missingValue);
|
||||||
|
} else {
|
||||||
|
member = sArray.findMember(memberName);
|
||||||
|
ucar.ma2.Array a = sArray.extractMemberArray(member);
|
||||||
|
if (a.getSize() > index) {
|
||||||
|
rIter.setObjectNext(a.getObject(index));
|
||||||
|
} else {
|
||||||
|
rIter.setObjectNext(missingValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data array from an ucar ArrayObject with ArraySequence elements
|
||||||
|
*
|
||||||
|
* @param parentArray The ucar ArrayObject with ArraySequence elements
|
||||||
|
* @param memberName Member name
|
||||||
|
* @param index Record index
|
||||||
|
* @param missingValue Missing value
|
||||||
|
* @return Read data array
|
||||||
|
*/
|
||||||
|
public static Array readSequenceRecord(ucar.ma2.ArrayObject parentArray, String memberName,
|
||||||
|
int index) throws IOException {
|
||||||
|
return readSequenceRecord(parentArray, memberName, index, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data array from an ucar ArrayObject with ArraySequence elements
|
||||||
|
*
|
||||||
|
* @param parentArray The ucar ArrayObject with ArraySequence elements
|
||||||
|
* @param memberName Member name
|
||||||
|
* @param index Station index
|
||||||
|
* @return Read data array
|
||||||
|
*/
|
||||||
|
public static Array readSequenceStation(ucar.ma2.ArrayObject parentArray, String memberName,
|
||||||
|
int index) throws IOException {
|
||||||
|
int n = (int) parentArray.getSize();
|
||||||
|
ucar.ma2.ArrayStructure sArray = (ucar.ma2.ArrayStructure) parentArray.getObject(index);
|
||||||
|
if (sArray == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ucar.ma2.StructureMembers.Member member = sArray.findMember(memberName);
|
||||||
|
ucar.ma2.Array r = sArray.extractMemberArray(member);
|
||||||
|
|
||||||
|
return convertArray(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack an array
|
||||||
|
* @param a The array
|
||||||
|
* @param variable The variable including packing parameters
|
||||||
|
* @return Unpacked data
|
||||||
|
*/
|
||||||
|
public static Array arrayUnPack(Array a, Variable variable) {
|
||||||
|
double[] packValues = getPackData(variable);
|
||||||
|
double addOffset = packValues[0];
|
||||||
|
double scaleFactor = packValues[1];
|
||||||
|
double missingValue = packValues[2];
|
||||||
|
|
||||||
|
return ArrayUtil.unPack(a, missingValue, scaleFactor, addOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -143,9 +143,9 @@ public class NetCDFDataInfo extends DataInfo implements IGridDataInfo, IStationD
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file type identifer
|
* Get file type identifier
|
||||||
*
|
*
|
||||||
* @return File type identifer
|
* @return File type identifier
|
||||||
*/
|
*/
|
||||||
public String getFileTypeId() {
|
public String getFileTypeId() {
|
||||||
return fileTypeId;
|
return fileTypeId;
|
||||||
@ -1760,6 +1760,7 @@ public class NetCDFDataInfo extends DataInfo implements IGridDataInfo, IStationD
|
|||||||
dimType = DimensionType.Y;
|
dimType = DimensionType.Y;
|
||||||
break;
|
break;
|
||||||
case "time":
|
case "time":
|
||||||
|
case "valid_time":
|
||||||
dimType = DimensionType.T;
|
dimType = DimensionType.T;
|
||||||
break;
|
break;
|
||||||
case "level":
|
case "level":
|
||||||
@ -3100,7 +3101,10 @@ public class NetCDFDataInfo extends DataInfo implements IGridDataInfo, IStationD
|
|||||||
scale_factor = packData[1];
|
scale_factor = packData[1];
|
||||||
missingValue = packData[2];
|
missingValue = packData[2];
|
||||||
if (add_offset != 0 || scale_factor != 1) {
|
if (add_offset != 0 || scale_factor != 1) {
|
||||||
//ArrayMath.fill_value = missingValue;
|
data = ArrayUtil.convertToDataType(data, org.meteoinfo.ndarray.DataType.DOUBLE);
|
||||||
|
if (!Double.isNaN(missingValue)) {
|
||||||
|
ArrayMath.replaceValue(data, missingValue, Double.NaN);
|
||||||
|
}
|
||||||
data = ArrayMath.add(ArrayMath.mul(data, scale_factor), add_offset);
|
data = ArrayMath.add(ArrayMath.mul(data, scale_factor), add_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,11 +47,6 @@
|
|||||||
<artifactId>meteoinfo-image</artifactId>
|
<artifactId>meteoinfo-image</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>meteoinfo-data</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>meteoinfo-chart</artifactId>
|
<artifactId>meteoinfo-chart</artifactId>
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import org.meteoinfo.geometry.shape.PolylineZShape;
|
|||||||
import org.meteoinfo.ndarray.Array;
|
import org.meteoinfo.ndarray.Array;
|
||||||
import org.meteoinfo.ndarray.DataType;
|
import org.meteoinfo.ndarray.DataType;
|
||||||
import org.meteoinfo.ndarray.math.ArrayUtil;
|
import org.meteoinfo.ndarray.math.ArrayUtil;
|
||||||
import ucar.nc2.util.IO;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|||||||
@ -1,32 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<MeteoInfo File="milconfig.xml" Type="configurefile">
|
<MeteoInfo File="milconfig.xml" Type="configurefile">
|
||||||
<Path OpenPath="D:\Working\MIScript\Jython\mis\plot_types\contour">
|
<Path OpenPath="D:\Working\MIScript\Jython\mis\io\burf">
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\pie"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\map"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\map\geoshow"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\map\geoshow"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\dataset"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\dataset"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\netcdf"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math\fft"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math\fft"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io"/>
|
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\grads"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\grads"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types"/>
|
||||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\contour"/>
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\contour"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\hdf"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\satellite"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\netcdf"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\micaps"/>
|
||||||
|
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\burf"/>
|
||||||
</Path>
|
</Path>
|
||||||
<File>
|
<File>
|
||||||
<OpenedFiles>
|
<OpenedFiles>
|
||||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl\slice\slice_2d.py"/>
|
<OpenedFile File="D:\Working\MIScript\Jython\mis\io\burf\bufr_gfs_1.py"/>
|
||||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\common_math\fft\fft_5.py"/>
|
<OpenedFile File="D:\Working\MIScript\Jython\mis\io\burf\bufr_gdas_3.py"/>
|
||||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\io\grads\verticle_plot_2.py"/>
|
|
||||||
</OpenedFiles>
|
</OpenedFiles>
|
||||||
<RecentFiles>
|
<RecentFiles>
|
||||||
<RecentFile File="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl\slice\slice_2d.py"/>
|
<RecentFile File="D:\Working\MIScript\Jython\mis\io\burf\bufr_gfs_1.py"/>
|
||||||
<RecentFile File="D:\Working\MIScript\Jython\mis\common_math\fft\fft_5.py"/>
|
<RecentFile File="D:\Working\MIScript\Jython\mis\io\burf\bufr_gdas_3.py"/>
|
||||||
<RecentFile File="D:\Working\MIScript\Jython\mis\io\grads\verticle_plot_2.py"/>
|
|
||||||
</RecentFiles>
|
</RecentFiles>
|
||||||
</File>
|
</File>
|
||||||
<Font>
|
<Font>
|
||||||
|
|||||||
@ -5,15 +5,23 @@ from .dimdatafile import DimDataFile
|
|||||||
class BUFRDataFile(DimDataFile):
|
class BUFRDataFile(DimDataFile):
|
||||||
|
|
||||||
def __init__(self, dataset=None, access='r', bufrdata=None):
|
def __init__(self, dataset=None, access='r', bufrdata=None):
|
||||||
super(BUFRDataFile, self).__init__(dataset, access)
|
"""
|
||||||
|
Create a BUFR data file object.
|
||||||
|
|
||||||
|
:param dataset: (*MeteoDataInfo*) Underline dataset.
|
||||||
|
:param access: (*string*) File access.
|
||||||
|
:param bufrdata: (*object*) Bufr data.
|
||||||
|
"""
|
||||||
|
DimDataFile.__init__(self, dataset, access)
|
||||||
self.bufrdata = bufrdata
|
self.bufrdata = bufrdata
|
||||||
|
|
||||||
|
|
||||||
def write_indicator(self, bufrlen, edition=3):
|
def write_indicator(self, bufrlen, edition=3):
|
||||||
"""
|
"""
|
||||||
Write indicator section with arbitrary length.
|
Write indicator section with arbitrary length.
|
||||||
|
|
||||||
:param bufrlen: (*int*) The total length of the message.
|
:param bufrlen: (*int*) The total length of the message.
|
||||||
:param edition: (*int*) Bruf edition.
|
:param edition: (*int*) Burf edition.
|
||||||
|
|
||||||
:returns: (*int*) Indicator section length.
|
:returns: (*int*) Indicator section length.
|
||||||
"""
|
"""
|
||||||
@ -24,7 +32,7 @@ class BUFRDataFile(DimDataFile):
|
|||||||
Write indicator section with correct length.
|
Write indicator section with correct length.
|
||||||
|
|
||||||
:param bufrlen: (*int*) The total length of the message.
|
:param bufrlen: (*int*) The total length of the message.
|
||||||
:param edition: (*int*) Bruf edition.
|
:param edition: (*int*) Burf edition.
|
||||||
"""
|
"""
|
||||||
self.bufrdata.reWriteIndicatorSection(bufrlen, edition)
|
self.bufrdata.reWriteIndicatorSection(bufrlen, edition)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@ -34,23 +34,27 @@ class DimDataFile(object):
|
|||||||
self._variables = []
|
self._variables = []
|
||||||
if not dataset is None:
|
if not dataset is None:
|
||||||
self.filename = dataset.getFileName()
|
self.filename = dataset.getFileName()
|
||||||
for v in dataset.getDataInfo().getVariables():
|
|
||||||
self._variables.append(DimVariable(v))
|
|
||||||
self.nvar = dataset.getDataInfo().getVariableNum()
|
self.nvar = dataset.getDataInfo().getVariableNum()
|
||||||
self.fill_value = dataset.getMissingValue()
|
self.fill_value = dataset.getMissingValue()
|
||||||
self.proj = dataset.getProjectionInfo()
|
self.proj = dataset.getProjectionInfo()
|
||||||
self.projection = self.proj
|
self.projection = self.proj
|
||||||
|
for v in dataset.getDataInfo().getVariables():
|
||||||
|
self._variables.append(DimVariable.factory(v, self))
|
||||||
self.arldata = arldata
|
self.arldata = arldata
|
||||||
self.bufrdata = bufrdata
|
self.bufrdata = bufrdata
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, basestring):
|
if isinstance(key, basestring):
|
||||||
var = self.dataset.getDataInfo().getVariable(key)
|
for var in self._variables:
|
||||||
if var is None:
|
if var.name == key:
|
||||||
print(key + ' is not a variable name')
|
return var
|
||||||
raise ValueError()
|
|
||||||
else:
|
for var in self._variables:
|
||||||
return DimVariable(self.dataset.getDataInfo().getVariable(key), self)
|
if var.short_name == key:
|
||||||
|
return var
|
||||||
|
|
||||||
|
print(key + ' is not a variable name')
|
||||||
|
raise ValueError()
|
||||||
else:
|
else:
|
||||||
print(key + ' is not a variable name')
|
print(key + ' is not a variable name')
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
@ -67,7 +71,7 @@ class DimDataFile(object):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close the opended dataset
|
Close the opened dataset
|
||||||
"""
|
"""
|
||||||
if not self.dataset is None:
|
if not self.dataset is None:
|
||||||
self.dataset.close()
|
self.dataset.close()
|
||||||
|
|||||||
Binary file not shown.
@ -20,41 +20,69 @@ import mipylib.numeric as np
|
|||||||
import mipylib.miutil as miutil
|
import mipylib.miutil as miutil
|
||||||
import datetime
|
import datetime
|
||||||
import numbers
|
import numbers
|
||||||
|
import warnings
|
||||||
|
|
||||||
# Dimension variable
|
# Dimension variable
|
||||||
class DimVariable(object):
|
class DimVariable(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def factory(variable=None, dataset=None, ncvariable=None):
|
||||||
|
"""
|
||||||
|
Factor method.
|
||||||
|
"""
|
||||||
|
if variable.getDataType().isStructure():
|
||||||
|
return StructureVariable(variable, dataset)
|
||||||
|
else:
|
||||||
|
return DimVariable(variable, dataset, ncvariable)
|
||||||
|
|
||||||
# variable must be org.meteoinfo.data.meteodata.Variable
|
# variable must be org.meteoinfo.data.meteodata.Variable
|
||||||
# dataset is DimDataFile
|
# dataset is DimDataFile
|
||||||
def __init__(self, variable=None, dataset=None, ncvariable=None):
|
def __init__(self, variable=None, dataset=None, ncvariable=None):
|
||||||
self.variable = variable
|
self._variable = variable
|
||||||
self.dataset = dataset
|
self.dataset = dataset
|
||||||
self.ncvariable = ncvariable
|
self.ncvariable = ncvariable
|
||||||
if not variable is None:
|
if not variable is None:
|
||||||
self.name = variable.getName()
|
|
||||||
self.dtype = np.dtype.fromjava(variable.getDataType())
|
self.dtype = np.dtype.fromjava(variable.getDataType())
|
||||||
self.dims = variable.getDimensions()
|
self.dims = variable.getDimensions()
|
||||||
self.ndim = variable.getDimNumber()
|
self.ndim = variable.getDimNumber()
|
||||||
self.attributes = variable.getAttributes()
|
self.attributes = variable.getAttributes()
|
||||||
elif not ncvariable is None:
|
elif not ncvariable is None:
|
||||||
self.name = ncvariable.getShortName()
|
|
||||||
self.dtype = ncvariable.getDataType()
|
self.dtype = ncvariable.getDataType()
|
||||||
self.dims = ncvariable.getDimensions()
|
self.dims = ncvariable.getDimensions()
|
||||||
self.ndim = len(self.dims)
|
self.ndim = len(self.dims)
|
||||||
self.attributes = list(ncvariable.getAttributes())
|
self.attributes = list(ncvariable.getAttributes())
|
||||||
else:
|
else:
|
||||||
self.name = None
|
|
||||||
self.dtype = None
|
self.dtype = None
|
||||||
self.dims = None
|
self.dims = None
|
||||||
self.ndim = 0
|
self.ndim = 0
|
||||||
self.attributes = None
|
self.attributes = None
|
||||||
self.proj = None if dataset is None else dataset.projection
|
self.proj = None if dataset is None else dataset.projection
|
||||||
self.projection = self.proj
|
self.projection = self.proj
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
if self._variable is not None:
|
||||||
|
return self._variable.getName()
|
||||||
|
|
||||||
|
if self.ncvariable is not None:
|
||||||
|
return self.ncvariable.getFullName()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_name(self):
|
||||||
|
if self._variable is not None:
|
||||||
|
return self._variable.getShortName()
|
||||||
|
|
||||||
|
if self.ncvariable is not None:
|
||||||
|
return self.ncvariable.getShortName()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
len = 1
|
len = 1
|
||||||
if not self.variable is None:
|
if not self._variable is None:
|
||||||
for dim in self.variable.getDimensions():
|
for dim in self._variable.getDimensions():
|
||||||
len = len * dim.getLength()
|
len = len * dim.getLength()
|
||||||
return len
|
return len
|
||||||
|
|
||||||
@ -78,7 +106,7 @@ class DimVariable(object):
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
def __getitem__(self, indices):
|
def __getitem__(self, indices):
|
||||||
if self.variable.getDataType() in [DataType.STRUCTURE, DataType.SEQUENCE]:
|
if self._variable.getDataType() in [DataType.STRUCTURE, DataType.SEQUENCE]:
|
||||||
if isinstance(indices, str): #metadata
|
if isinstance(indices, str): #metadata
|
||||||
return self.member_array(indices)
|
return self.member_array(indices)
|
||||||
else:
|
else:
|
||||||
@ -105,16 +133,17 @@ class DimVariable(object):
|
|||||||
else:
|
else:
|
||||||
indices1.append(ii)
|
indices1.append(ii)
|
||||||
indices = indices1
|
indices = indices1
|
||||||
|
|
||||||
if len(indices) < self.ndim:
|
if self.ndim > 0:
|
||||||
indices = list(indices)
|
if len(indices) < self.ndim:
|
||||||
for _ in range(self.ndim - len(indices)):
|
indices = list(indices)
|
||||||
indices.append(slice(None))
|
for _ in range(self.ndim - len(indices)):
|
||||||
indices = tuple(indices)
|
indices.append(slice(None))
|
||||||
|
indices = tuple(indices)
|
||||||
if len(indices) != self.ndim:
|
|
||||||
print('indices must be ' + str(self.ndim) + ' dimensions!')
|
if len(indices) != self.ndim:
|
||||||
return None
|
print('indices must be ' + str(self.ndim) + ' dimensions!')
|
||||||
|
return None
|
||||||
|
|
||||||
if not self.proj is None and not self.proj.isLonLat():
|
if not self.proj is None and not self.proj.isLonLat():
|
||||||
xlim = None
|
xlim = None
|
||||||
@ -237,7 +266,7 @@ class DimVariable(object):
|
|||||||
ranges.append(tlist)
|
ranges.append(tlist)
|
||||||
k = tlist
|
k = tlist
|
||||||
elif isinstance(k, basestring):
|
elif isinstance(k, basestring):
|
||||||
dim = self.variable.getDimension(i)
|
dim = self._variable.getDimension(i)
|
||||||
kvalues = k.split(':')
|
kvalues = k.split(':')
|
||||||
sv = float(kvalues[0])
|
sv = float(kvalues[0])
|
||||||
sidx = dim.getValueIndex(sv)
|
sidx = dim.getValueIndex(sv)
|
||||||
@ -265,7 +294,7 @@ class DimVariable(object):
|
|||||||
n = abs(eidx - sidx) + 1
|
n = abs(eidx - sidx) + 1
|
||||||
size.append(n)
|
size.append(n)
|
||||||
if n > 1:
|
if n > 1:
|
||||||
dim = self.variable.getDimension(i)
|
dim = self._variable.getDimension(i)
|
||||||
#if dim.isReverse():
|
#if dim.isReverse():
|
||||||
# step = -step
|
# step = -step
|
||||||
dim = dim.extract(sidx, eidx, step)
|
dim = dim.extract(sidx, eidx, step)
|
||||||
@ -283,7 +312,7 @@ class DimVariable(object):
|
|||||||
ranges.append(rr)
|
ranges.append(rr)
|
||||||
else:
|
else:
|
||||||
if len(k) > 1:
|
if len(k) > 1:
|
||||||
dim = self.variable.getDimension(i)
|
dim = self._variable.getDimension(i)
|
||||||
dim = dim.extract(k)
|
dim = dim.extract(k)
|
||||||
#dim.setReverse(False)
|
#dim.setReverse(False)
|
||||||
dims.append(dim)
|
dims.append(dim)
|
||||||
@ -316,6 +345,23 @@ class DimVariable(object):
|
|||||||
"""
|
"""
|
||||||
return np.array(self.dataset.read(self.name))
|
return np.array(self.dataset.read(self.name))
|
||||||
|
|
||||||
|
def get_pack_paras(self):
|
||||||
|
"""
|
||||||
|
Get pack parameters.
|
||||||
|
:return: missing_value, scale_factor, add_offset
|
||||||
|
"""
|
||||||
|
pack_paras = NCUtil.getPackData(self._variable)
|
||||||
|
|
||||||
|
return pack_paras[2], pack_paras[1], pack_paras[0]
|
||||||
|
|
||||||
|
def is_member(self):
|
||||||
|
"""
|
||||||
|
Whether the variable is a member of a structure.
|
||||||
|
|
||||||
|
:return: Is a member of a structure or not.
|
||||||
|
"""
|
||||||
|
return self._variable.isMemberOfStructure()
|
||||||
|
|
||||||
def get_members(self):
|
def get_members(self):
|
||||||
"""
|
"""
|
||||||
Get structure members. Only valid for Structure data type.
|
Get structure members. Only valid for Structure data type.
|
||||||
@ -324,7 +370,7 @@ class DimVariable(object):
|
|||||||
"""
|
"""
|
||||||
a = self.read()
|
a = self.read()
|
||||||
if a._array.getDataType() != DataType.STRUCTURE:
|
if a._array.getDataType() != DataType.STRUCTURE:
|
||||||
print 'This method is only valid for structure array!'
|
print('This method is only valid for structure array!')
|
||||||
return None
|
return None
|
||||||
a = a._array.getArrayObject()
|
a = a._array.getArrayObject()
|
||||||
return a.getMembers()
|
return a.getMembers()
|
||||||
@ -343,28 +389,32 @@ class DimVariable(object):
|
|||||||
a = a._array.getArrayObject()
|
a = a._array.getArrayObject()
|
||||||
return a.findMember(member)
|
return a.findMember(member)
|
||||||
|
|
||||||
def member_array(self, member, indices=None):
|
def member_array(self, member, index=None, rec=0):
|
||||||
"""
|
"""
|
||||||
Extract member array. Only valid for Structure data type.
|
Extract member array. Only valid for Structure data type.
|
||||||
|
|
||||||
:param member: (*string*) Member name.
|
:param member: (*string*) Member name.
|
||||||
:param indices: (*slice*) Indices.
|
:param index: (*slice*) Index.
|
||||||
|
:param rec: (*int*) Record index.
|
||||||
|
|
||||||
:returns: (*array*) Extracted member array.
|
:returns: (*array*) Extracted member array.
|
||||||
"""
|
"""
|
||||||
a = self.read()
|
a = self.read()
|
||||||
if a._array.getDataType() != DataType.STRUCTURE:
|
|
||||||
print('This method is only valid for structure array!')
|
|
||||||
return None
|
|
||||||
|
|
||||||
a = a._array.getArrayObject()
|
a = a._array.getArrayObject()
|
||||||
|
is_structure = isinstance(a, ArrayStructure)
|
||||||
if isinstance(member, basestring):
|
if isinstance(member, basestring):
|
||||||
member = a.findMember(member)
|
if is_structure:
|
||||||
|
member = a.findMember(member)
|
||||||
|
else:
|
||||||
|
member = a.getObject(rec).findMember(member)
|
||||||
|
|
||||||
if member is None:
|
if member is None:
|
||||||
raise KeyError('The member %s not exists!' % member)
|
raise KeyError('The member %s not exists!' % member)
|
||||||
|
|
||||||
self.dataset.reopen()
|
if is_structure:
|
||||||
a = a.extractMemberArray(member)
|
a = a.extractMemberArray(member)
|
||||||
|
else:
|
||||||
|
a = a.getObject(rec).extractMemberArray(member)
|
||||||
if a.getDataType() in [NCDataType.SEQUENCE, NCDataType.STRUCTURE]:
|
if a.getDataType() in [NCDataType.SEQUENCE, NCDataType.STRUCTURE]:
|
||||||
return StructureArray(a)
|
return StructureArray(a)
|
||||||
|
|
||||||
@ -373,11 +423,46 @@ class DimVariable(object):
|
|||||||
if r.size == 1:
|
if r.size == 1:
|
||||||
return r[0]
|
return r[0]
|
||||||
|
|
||||||
if not indices is None:
|
if not index is None:
|
||||||
r = r.__getitem__(indices)
|
r = r.__getitem__(index)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
# def member_array(self, member, indices=None):
|
||||||
|
# """
|
||||||
|
# Extract member array. Only valid for Structure data type.
|
||||||
|
#
|
||||||
|
# :param member: (*string*) Member name.
|
||||||
|
# :param indices: (*slice*) Indices.
|
||||||
|
#
|
||||||
|
# :returns: (*array*) Extracted member array.
|
||||||
|
# """
|
||||||
|
# a = self.read()
|
||||||
|
# if a._array.getDataType() != DataType.STRUCTURE:
|
||||||
|
# print('This method is only valid for structure array!')
|
||||||
|
# return None
|
||||||
|
#
|
||||||
|
# a = a._array.getArrayObject()
|
||||||
|
# if isinstance(member, basestring):
|
||||||
|
# member = a.findMember(member)
|
||||||
|
# if member is None:
|
||||||
|
# raise KeyError('The member %s not exists!' % member)
|
||||||
|
#
|
||||||
|
# self.dataset.reopen()
|
||||||
|
# a = a.extractMemberArray(member)
|
||||||
|
# if a.getDataType() in [NCDataType.SEQUENCE, NCDataType.STRUCTURE]:
|
||||||
|
# return StructureArray(a)
|
||||||
|
#
|
||||||
|
# a = NCUtil.convertArray(a)
|
||||||
|
# r = np.array(a)
|
||||||
|
# if r.size == 1:
|
||||||
|
# return r[0]
|
||||||
|
#
|
||||||
|
# if not indices is None:
|
||||||
|
# r = r.__getitem__(indices)
|
||||||
|
#
|
||||||
|
# return r
|
||||||
|
|
||||||
def dimlen(self, idx):
|
def dimlen(self, idx):
|
||||||
"""
|
"""
|
||||||
Get dimension length.
|
Get dimension length.
|
||||||
@ -414,7 +499,7 @@ class DimVariable(object):
|
|||||||
:param attr: (*string or Attribute*) Attribute or Attribute name
|
:param attr: (*string or Attribute*) Attribute or Attribute name
|
||||||
"""
|
"""
|
||||||
if isinstance(attr, str):
|
if isinstance(attr, str):
|
||||||
attr = self.variable.findAttribute(attr)
|
attr = self._variable.findAttribute(attr)
|
||||||
if attr is None:
|
if attr is None:
|
||||||
return None
|
return None
|
||||||
v = np.array(attr.getValues())
|
v = np.array(attr.getValues())
|
||||||
@ -447,17 +532,17 @@ class DimVariable(object):
|
|||||||
def adddim(self, dimtype, dimvalue):
|
def adddim(self, dimtype, dimvalue):
|
||||||
if isinstance(dimvalue, np.NDArray):
|
if isinstance(dimvalue, np.NDArray):
|
||||||
dimvalue = dimvalue.aslist()
|
dimvalue = dimvalue.aslist()
|
||||||
self.variable.addDimension(dimtype, dimvalue)
|
self._variable.addDimension(dimtype, dimvalue)
|
||||||
self.ndim = self.variable.getDimNumber()
|
self.ndim = self._variable.getDimNumber()
|
||||||
|
|
||||||
def setdim(self, dimtype, dimvalue, index=None, reverse=False):
|
def setdim(self, dimtype, dimvalue, index=None, reverse=False):
|
||||||
if isinstance(dimvalue, np.NDArray):
|
if isinstance(dimvalue, np.NDArray):
|
||||||
dimvalue = dimvalue.aslist()
|
dimvalue = dimvalue.aslist()
|
||||||
if index is None:
|
if index is None:
|
||||||
self.variable.setDimension(dimtype, dimvalue, reverse)
|
self._variable.setDimension(dimtype, dimvalue, reverse)
|
||||||
else:
|
else:
|
||||||
self.variable.setDimension(dimtype, dimvalue, reverse, index)
|
self._variable.setDimension(dimtype, dimvalue, reverse, index)
|
||||||
self.ndim = self.variable.getDimNumber()
|
self.ndim = self._variable.getDimNumber()
|
||||||
|
|
||||||
def setdimrev(self, idx, reverse):
|
def setdimrev(self, idx, reverse):
|
||||||
self.dims[idx].setReverse(reverse)
|
self.dims[idx].setReverse(reverse)
|
||||||
@ -487,6 +572,151 @@ class DimVariable(object):
|
|||||||
self.ncvariable.addAttribute(ncattr)
|
self.ncvariable.addAttribute(ncattr)
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
|
class StructureVariable(DimVariable):
|
||||||
|
|
||||||
|
def __init__(self, variable=None, dataset=None, parent_variable=None):
|
||||||
|
"""
|
||||||
|
Structure variable.
|
||||||
|
|
||||||
|
:param variable: (*Structure*) NC Structure object.
|
||||||
|
:param dataset: (*DimDataFile*) Data file.
|
||||||
|
:param parent_variable: (*StructureVariable*) Parent structure variable.
|
||||||
|
"""
|
||||||
|
DimVariable.__init__(self, variable, dataset)
|
||||||
|
|
||||||
|
self._parent_variable = parent_variable
|
||||||
|
|
||||||
|
if dataset is not None:
|
||||||
|
datainfo = dataset.dataset.getDataInfo()
|
||||||
|
if not datainfo.isOpened():
|
||||||
|
datainfo.reOpen()
|
||||||
|
|
||||||
|
self._ncfile = datainfo.getFile()
|
||||||
|
self._ncvar = self._ncfile.findVariable(self.name)
|
||||||
|
self._variables = []
|
||||||
|
for var in self._ncvar.getVariables():
|
||||||
|
self._variables.append(MemberVariable.factory(NCUtil.convertVariable(var), dataset, self))
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, basestring):
|
||||||
|
for var in self._variables:
|
||||||
|
if var.name == key:
|
||||||
|
return var
|
||||||
|
|
||||||
|
for var in self._variables:
|
||||||
|
if var.short_name == key:
|
||||||
|
return var
|
||||||
|
|
||||||
|
raise ValueError(key + ' is not a variable name')
|
||||||
|
else:
|
||||||
|
return np.array(self.dataset.read(self.name))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def variables(self):
|
||||||
|
"""
|
||||||
|
Get all variables.
|
||||||
|
"""
|
||||||
|
return self._variables
|
||||||
|
|
||||||
|
@property
|
||||||
|
def varnames(self):
|
||||||
|
"""
|
||||||
|
Get all variable names.
|
||||||
|
"""
|
||||||
|
names = []
|
||||||
|
for var in self._variables:
|
||||||
|
names.append(var.short_name)
|
||||||
|
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
class MemberVariable(DimVariable):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def factory(variable=None, dataset=None, parent_variable=None):
|
||||||
|
"""
|
||||||
|
Factor method.
|
||||||
|
"""
|
||||||
|
if variable.getDataType().isStructure():
|
||||||
|
return StructureVariable(variable, dataset, parent_variable)
|
||||||
|
else:
|
||||||
|
return MemberVariable(variable, dataset, parent_variable)
|
||||||
|
|
||||||
|
def __init__(self, variable=None, dataset=None, parent_variable=None):
|
||||||
|
"""
|
||||||
|
Structure variable.
|
||||||
|
|
||||||
|
:param variable: (*Structure*) NC Structure object.
|
||||||
|
:param dataset: (*DimDataFile*) Data file.
|
||||||
|
:param array: (*NCArray*) NC Array.
|
||||||
|
"""
|
||||||
|
DimVariable.__init__(self, variable, dataset)
|
||||||
|
|
||||||
|
self._parent_variable = parent_variable
|
||||||
|
|
||||||
|
if dataset is not None:
|
||||||
|
datainfo = dataset.dataset.getDataInfo()
|
||||||
|
if not datainfo.isOpened():
|
||||||
|
datainfo.reOpen()
|
||||||
|
|
||||||
|
self._ncfile = datainfo.getFile()
|
||||||
|
self._ncvar = self._ncfile.findVariable(self.name)
|
||||||
|
|
||||||
|
def __getitem__(self, key=0, station=None):
|
||||||
|
if isinstance(key, int):
|
||||||
|
return self.read_array(record=key)
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
if key == slice(None):
|
||||||
|
return self.read()
|
||||||
|
else:
|
||||||
|
return self.read_array(station=key.start)
|
||||||
|
|
||||||
|
def read_array(self, record=0, station=None):
|
||||||
|
"""
|
||||||
|
Read data array.
|
||||||
|
|
||||||
|
:param record: (*int*) Record index. Default is 0.
|
||||||
|
:param station: (*int*) station index. Default is `None`, means all stations.
|
||||||
|
:return: (*array*) Data array.
|
||||||
|
"""
|
||||||
|
a = self._parent_variable.read()
|
||||||
|
a = a._array.getArrayObject()
|
||||||
|
missing_value, scale_factor, add_offset = self.get_pack_paras()
|
||||||
|
is_structure = isinstance(a, ArrayStructure)
|
||||||
|
if is_structure:
|
||||||
|
r = NCUtil.readSequence(a, self.short_name)
|
||||||
|
else:
|
||||||
|
if station is None:
|
||||||
|
r = NCUtil.readSequenceRecord(a, self.short_name, record, missing_value)
|
||||||
|
else:
|
||||||
|
r = NCUtil.readSequenceStation(a, self.short_name, station)
|
||||||
|
|
||||||
|
if r is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
r = ArrayUtil.unPack(r, missing_value, scale_factor, add_offset)
|
||||||
|
return np.array(r)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def variables(self):
|
||||||
|
"""
|
||||||
|
Get all variables.
|
||||||
|
"""
|
||||||
|
return self._variables
|
||||||
|
|
||||||
|
@property
|
||||||
|
def varnames(self):
|
||||||
|
"""
|
||||||
|
Get all variable names.
|
||||||
|
"""
|
||||||
|
names = []
|
||||||
|
for var in self._variables:
|
||||||
|
names.append(var.short_name)
|
||||||
|
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
class StructureArray(object):
|
class StructureArray(object):
|
||||||
|
|
||||||
def __init__(self, array):
|
def __init__(self, array):
|
||||||
@ -540,12 +770,12 @@ class StructureArray(object):
|
|||||||
else:
|
else:
|
||||||
return self._array.getObject(rec).findMember(member)
|
return self._array.getObject(rec).findMember(member)
|
||||||
|
|
||||||
def member_array(self, member, indices=None, rec=0):
|
def member_array(self, member, index=None, rec=0):
|
||||||
"""
|
"""
|
||||||
Extract member array. Only valid for Structure data type.
|
Extract member array. Only valid for Structure data type.
|
||||||
|
|
||||||
:param member: (*string*) Member name.
|
:param member: (*string*) Member name.
|
||||||
:param indices: (*slice*) Indices.
|
:param index: (*slice*) Index.
|
||||||
:param rec: (*int*) Record index.
|
:param rec: (*int*) Record index.
|
||||||
|
|
||||||
:returns: (*array*) Extracted member array.
|
:returns: (*array*) Extracted member array.
|
||||||
@ -572,8 +802,8 @@ class StructureArray(object):
|
|||||||
if r.size == 1:
|
if r.size == 1:
|
||||||
return r[0]
|
return r[0]
|
||||||
|
|
||||||
if not indices is None:
|
if not index is None:
|
||||||
r = r.__getitem__(indices)
|
r = r.__getitem__(index)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@ -583,7 +813,7 @@ class TDimVariable(object):
|
|||||||
# variable must be org.meteoinfo.data.meteodata.Variable
|
# variable must be org.meteoinfo.data.meteodata.Variable
|
||||||
# dataset is DimDataFiles
|
# dataset is DimDataFiles
|
||||||
def __init__(self, variable, dataset):
|
def __init__(self, variable, dataset):
|
||||||
self.variable = variable
|
self._variable = variable
|
||||||
self.dataset = dataset
|
self.dataset = dataset
|
||||||
self.name = variable.getName()
|
self.name = variable.getName()
|
||||||
self.dtype = np.dtype.fromjava(variable.getDataType())
|
self.dtype = np.dtype.fromjava(variable.getDataType())
|
||||||
@ -599,7 +829,7 @@ class TDimVariable(object):
|
|||||||
self.tnum = len(times)
|
self.tnum = len(times)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.variable is None:
|
if self._variable is None:
|
||||||
return 'None'
|
return 'None'
|
||||||
|
|
||||||
r = str(self.dtype) + ' ' + self.name + '('
|
r = str(self.dtype) + ' ' + self.name + '('
|
||||||
@ -609,7 +839,7 @@ class TDimVariable(object):
|
|||||||
dimname = 'null'
|
dimname = 'null'
|
||||||
r = r + dimname + ','
|
r = r + dimname + ','
|
||||||
r = r[:-1] + '):'
|
r = r[:-1] + '):'
|
||||||
attrs = self.variable.getAttributes()
|
attrs = self._variable.getAttributes()
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
r = r + '\n\t' + self.name + ': ' + attr.toString()
|
r = r + '\n\t' + self.name + ': ' + attr.toString()
|
||||||
return r
|
return r
|
||||||
@ -628,7 +858,7 @@ class TDimVariable(object):
|
|||||||
indices = tuple(indices)
|
indices = tuple(indices)
|
||||||
|
|
||||||
if len(indices) != self.ndim:
|
if len(indices) != self.ndim:
|
||||||
print 'indices must be ' + str(self.ndim) + ' dimensions!'
|
print('indices must be ' + str(self.ndim) + ' dimensions!')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
k = indices[0]
|
k = indices[0]
|
||||||
|
|||||||
Binary file not shown.
@ -118,10 +118,13 @@ def addfile(fname, access='r', dtype='netcdf', keepopen=False, **kwargs):
|
|||||||
|
|
||||||
meteodata = MeteoDataInfo()
|
meteodata = MeteoDataInfo()
|
||||||
meteodata.openData(fname, keepopen)
|
meteodata.openData(fname, keepopen)
|
||||||
if meteodata.getDataInfo().getDataType() == MeteoDataType.RADAR:
|
datainfo = meteodata.getDataInfo()
|
||||||
|
if datainfo.getDataType() == MeteoDataType.RADAR:
|
||||||
datafile = RadarDataFile(meteodata, access=access)
|
datafile = RadarDataFile(meteodata, access=access)
|
||||||
elif meteodata.getDataInfo().isRadial():
|
elif datainfo.isRadial():
|
||||||
datafile = RadarDataFile(meteodata, access=access)
|
datafile = RadarDataFile(meteodata, access=access)
|
||||||
|
elif datainfo.getDataType() == MeteoDataType.NETCDF and datainfo.getFileTypeId() == 'BUFR':
|
||||||
|
datafile = BUFRDataFile(meteodata, access=access)
|
||||||
else:
|
else:
|
||||||
datafile = DimDataFile(meteodata, access=access)
|
datafile = DimDataFile(meteodata, access=access)
|
||||||
return datafile
|
return datafile
|
||||||
|
|||||||
@ -224,6 +224,15 @@ public enum DataType {
|
|||||||
return (this == DataType.ENUM1) || (this == DataType.ENUM2) || (this == DataType.ENUM4);
|
return (this == DataType.ENUM1) || (this == DataType.ENUM2) || (this == DataType.ENUM4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is structure
|
||||||
|
*
|
||||||
|
* @return true if structure and sequence
|
||||||
|
*/
|
||||||
|
public boolean isStructure() {
|
||||||
|
return (this == DataType.STRUCTURE || this == DataType.SEQUENCE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a number to this data type
|
* Convert a number to this data type
|
||||||
* @param n The number
|
* @param n The number
|
||||||
|
|||||||
@ -2329,6 +2329,14 @@ public class ArrayUtil {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an Array
|
||||||
|
*
|
||||||
|
* @param a Input array
|
||||||
|
* @param sections
|
||||||
|
* @param axis
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static List<Array> arraySplit(Array a, int sections, int axis) {
|
public static List<Array> arraySplit(Array a, int sections, int axis) {
|
||||||
int[] shape = a.getShape();
|
int[] shape = a.getShape();
|
||||||
if (axis == -1) {
|
if (axis == -1) {
|
||||||
@ -2342,6 +2350,31 @@ public class ArrayUtil {
|
|||||||
return arrays;
|
return arrays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack array by missing value, scale factor and add offset
|
||||||
|
*
|
||||||
|
* @param a Input array
|
||||||
|
* @param missingValue Missing value
|
||||||
|
* @param scaleFactor Scale factor
|
||||||
|
* @param addOffset Add offset
|
||||||
|
* @return Output array
|
||||||
|
*/
|
||||||
|
public static Array unPack(Array a, double missingValue, double scaleFactor, double addOffset) {
|
||||||
|
if (!Double.isNaN(missingValue)) {
|
||||||
|
a = ArrayUtil.convertToDataType(a, DataType.DOUBLE);
|
||||||
|
ArrayMath.replaceValue(a, missingValue, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaleFactor != 1) {
|
||||||
|
a = ArrayMath.mul(a, scaleFactor);
|
||||||
|
}
|
||||||
|
if (addOffset != 0) {
|
||||||
|
a = ArrayMath.add(a, addOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort array along an axis
|
* Sort array along an axis
|
||||||
*
|
*
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@ -35,7 +35,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<revision>3.9.6</revision>
|
<revision>3.9.7</revision>
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
<maven.compiler.release>8</maven.compiler.release>
|
<maven.compiler.release>8</maven.compiler.release>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user