Skip to content

Commit 782a86b

Browse files
authored
Merge pull request #1396 from Yaqiang/develop
Enhance Bufr data support including multi-category messages
2 parents 012786e + ffab05a commit 782a86b

13 files changed

Lines changed: 1572 additions & 37 deletions

File tree

bufr/src/main/java/ucar/nc2/iosp/bufr/BufrConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ static BufrConfig openFromMessage(RandomAccessFile raf, Message m, Element iospP
4545
}
4646

4747
private String filename;
48+
private Message message;
4849
private StandardFields.StandardFieldsFromMessage standardFields;
4950
private FieldConverter rootConverter;
5051
private int messHash;
@@ -86,6 +87,7 @@ private BufrConfig(RandomAccessFile raf) {
8687

8788
private BufrConfig(RandomAccessFile raf, Message m) throws IOException {
8889
this.filename = raf.getLocation();
90+
this.message = m;
8991
this.messHash = m.hashCode();
9092
this.rootConverter = new FieldConverter(m.ids.getCenterId(), m.getRootDataDescriptor());
9193
standardFields = StandardFields.extract(m);
@@ -95,6 +97,10 @@ public String getFilename() {
9597
return filename;
9698
}
9799

100+
public Message getMessage() {
101+
return this.message;
102+
}
103+
98104
public FieldConverter getRootConverter() {
99105
return rootConverter;
100106
}

bufr/src/main/java/ucar/nc2/iosp/bufr/BufrIosp2.java

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
package ucar.nc2.iosp.bufr;
66

77
import java.io.IOException;
8-
import java.util.Formatter;
9-
import java.util.HashSet;
10-
import java.util.List;
11-
import java.util.Optional;
8+
import java.util.*;
9+
1210
import org.jdom2.Element;
1311
import ucar.ma2.Array;
1412
import ucar.ma2.ArraySequence;
@@ -46,9 +44,11 @@ public static void setDebugFlags(ucar.nc2.util.DebugFlags debugFlag) {
4644
debugIter = debugFlag.isSet("Bufr/iter");
4745
}
4846

49-
private Structure obsStructure;
50-
private Message protoMessage; // prototypical message: all messages in the file must be the same.
47+
// private Structure obsStructure;
48+
// private Message protoMessage; // prototypical message: all messages in the file must be the same.
5149
private MessageScanner scanner;
50+
private List<Message> protoMessages; // prototypical messages: the messages with different category.
51+
private List<RootVariable> rootVariables;
5252
private HashSet<Integer> messHash;
5353
private boolean isSingle;
5454
private BufrConfig config;
@@ -69,25 +69,57 @@ public void build(RandomAccessFile raf, Group.Builder rootGroup, CancelTask canc
6969
super.open(raf, rootGroup.getNcfile(), cancelTask);
7070

7171
scanner = new MessageScanner(raf);
72-
protoMessage = scanner.getFirstDataMessage();
72+
Message protoMessage = scanner.getFirstDataMessage();
7373
if (protoMessage == null)
7474
throw new IOException("No data messages in the file= " + raf.getLocation());
7575
if (!protoMessage.isTablesComplete())
7676
throw new IllegalStateException("BUFR file has incomplete tables");
7777

78+
// get all prototype messages - contains different message category in a Bufr data file
79+
protoMessages = new ArrayList<>();
80+
protoMessages.add(protoMessage);
81+
int category = protoMessage.ids.getCategory();
82+
while (scanner.hasNext()) {
83+
Message message = scanner.next();
84+
if (message.ids.getCategory() != category) {
85+
protoMessages.add(message);
86+
category = message.ids.getCategory();
87+
}
88+
}
89+
7890
// just get the fields
7991
config = BufrConfig.openFromMessage(raf, protoMessage, iospParam);
8092

8193
// this fills the netcdf object
82-
new BufrIospBuilder(protoMessage, config, rootGroup, raf.getLocation());
94+
if (this.protoMessages.size() == 1) {
95+
new BufrIospBuilder(protoMessage, config, rootGroup, raf.getLocation());
96+
} else {
97+
List<BufrConfig> configs = new ArrayList<>();
98+
for (Message message : protoMessages) {
99+
configs.add(BufrConfig.openFromMessage(raf, message, iospParam));
100+
}
101+
new BufrIospBuilder(protoMessage, configs, rootGroup, raf.getLocation());
102+
}
83103
isSingle = false;
84104
}
85105

86106
@Override
87107
public void buildFinish(NetcdfFile ncfile) {
88-
obsStructure = (Structure) ncfile.findVariable(obsRecordName);
89-
// The proto DataDescriptor must have a link to the Sequence object to read nested Sequences.
90-
connectSequences(obsStructure.getVariables(), protoMessage.getRootDataDescriptor().getSubKeys());
108+
// support multiple root variables in one Bufr data file
109+
this.rootVariables = new ArrayList<>();
110+
if (this.protoMessages.size() == 1) {
111+
Structure obsStructure = (Structure) ncfile.findVariable(obsRecordName);
112+
// The proto DataDescriptor must have a link to the Sequence object to read nested Sequences.
113+
connectSequences(obsStructure.getVariables(), protoMessages.get(0).getRootDataDescriptor().getSubKeys());
114+
this.rootVariables.add(new RootVariable(protoMessages.get(0), obsStructure));
115+
} else {
116+
for (int i = 0; i < this.protoMessages.size(); i++) {
117+
Structure variable = (Structure) ncfile.getVariables().get(i);
118+
Message message = protoMessages.get(i);
119+
connectSequences(variable.getVariables(), message.getRootDataDescriptor().getSubKeys());
120+
this.rootVariables.add(new RootVariable(message, variable));
121+
}
122+
}
91123
}
92124

93125
private void connectSequences(List<Variable> variables, List<DataDescriptor> dataDescriptors) {
@@ -116,27 +148,48 @@ public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask)
116148
super.open(raf, ncfile, cancelTask);
117149

118150
scanner = new MessageScanner(raf);
119-
protoMessage = scanner.getFirstDataMessage();
151+
Message protoMessage = scanner.getFirstDataMessage();
120152
if (protoMessage == null)
121153
throw new IOException("No data messages in the file= " + ncfile.getLocation());
122154
if (!protoMessage.isTablesComplete())
123155
throw new IllegalStateException("BUFR file has incomplete tables");
124156

157+
// get all prototype messages - contains different message category in a Bufr data file
158+
protoMessages = new ArrayList<>();
159+
protoMessages.add(protoMessage);
160+
int category = protoMessage.ids.getCategory();
161+
while (scanner.hasNext()) {
162+
Message message = scanner.next();
163+
if (message.ids.getCategory() != category) {
164+
protoMessages.add(message);
165+
category = message.ids.getCategory();
166+
}
167+
}
168+
125169
// just get the fields
126170
config = BufrConfig.openFromMessage(raf, protoMessage, iospParam);
127171

128172
// this fills the netcdf object
129-
Construct2 construct = new Construct2(protoMessage, config, ncfile);
130-
obsStructure = construct.getObsStructure();
173+
if (this.protoMessages.size() == 1) {
174+
Construct2 construct = new Construct2(protoMessage, config, ncfile);
175+
} else {
176+
List<BufrConfig> configs = new ArrayList<>();
177+
for (Message message : protoMessages) {
178+
configs.add(BufrConfig.openFromMessage(raf, message, iospParam));
179+
}
180+
Construct2 construct = new Construct2(protoMessage, configs, ncfile);
181+
}
182+
131183
ncfile.finish();
184+
buildFinish(ncfile);
132185
isSingle = false;
133186
}
134187

135188
// for BufrMessageViewer
136189
public void open(RandomAccessFile raf, NetcdfFile ncfile, Message single) throws IOException {
137190
this.raf = raf;
138191

139-
protoMessage = single;
192+
Message protoMessage = single;
140193
protoMessage.getRootDataDescriptor(); // construct the data descriptors, check for complete tables
141194
if (!protoMessage.isTablesComplete())
142195
throw new IllegalStateException("BUFR file has incomplete tables");
@@ -145,7 +198,7 @@ public void open(RandomAccessFile raf, NetcdfFile ncfile, Message single) throws
145198

146199
// this fills the netcdf object
147200
Construct2 construct = new Construct2(protoMessage, config, ncfile);
148-
obsStructure = construct.getObsStructure();
201+
Structure obsStructure = construct.getObsStructure();
149202
isSingle = true;
150203

151204
ncfile.finish();
@@ -175,28 +228,67 @@ public Element getElem() {
175228

176229
@Override
177230
public Array readData(Variable v2, Section section) {
178-
findRootSequence();
179-
return new ArraySequence(obsStructure.makeStructureMembers(), new SeqIter(), nelems);
231+
RootVariable rootVariable = findRootSequence(v2);
232+
Structure obsStructure = rootVariable.getVariable();
233+
return new ArraySequence(obsStructure.makeStructureMembers(), new SeqIter(rootVariable), nelems);
180234
}
181235

182236
@Override
183237
public StructureDataIterator getStructureIterator(Structure s, int bufferSize) {
184-
findRootSequence();
185-
return isSingle ? new SeqIterSingle() : new SeqIter();
238+
RootVariable rootVariable = findRootSequence(s);
239+
return isSingle ? new SeqIterSingle(rootVariable) : new SeqIter(rootVariable);
240+
}
241+
242+
private Structure findRootSequence() {
243+
return (Structure) this.ncfile.findVariable(BufrIosp2.obsRecordName);
186244
}
187245

188-
private void findRootSequence() {
189-
this.obsStructure = (Structure) this.ncfile.findVariable(BufrIosp2.obsRecordName);
246+
// find root sequence from root variable list
247+
private RootVariable findRootSequence(Variable var) {
248+
for (RootVariable rootVariable : this.rootVariables) {
249+
if (rootVariable.getVariable().getShortName().equals(var.getShortName())) {
250+
return rootVariable;
251+
}
252+
}
253+
return null;
254+
}
255+
256+
// root variable contains prototype message and corresponding variable
257+
private class RootVariable {
258+
private Message protoMessage;
259+
private Structure variable;
260+
261+
public RootVariable(Message message, Structure variable) {
262+
this.protoMessage = message;
263+
this.variable = variable;
264+
}
265+
266+
public Message getProtoMessage() {
267+
return this.protoMessage;
268+
}
269+
270+
public Structure getVariable() {
271+
return this.variable;
272+
}
190273
}
191274

192275
private class SeqIter implements StructureDataIterator {
193276
StructureDataIterator currIter;
194277
int recnum;
278+
// add its own prototype message and observation structure
279+
Message protoMessage;
280+
Structure obsStructure;
195281

196-
SeqIter() {
282+
SeqIter(Message message, Structure structure) {
283+
this.protoMessage = message;
284+
this.obsStructure = structure;
197285
reset();
198286
}
199287

288+
SeqIter(RootVariable rootVariable) {
289+
this(rootVariable.protoMessage, rootVariable.variable);
290+
}
291+
200292
@Override
201293
public StructureDataIterator reset() {
202294
recnum = 0;
@@ -286,11 +378,20 @@ public void close() {
286378
private class SeqIterSingle implements StructureDataIterator {
287379
StructureDataIterator currIter;
288380
int recnum;
381+
// add its own prototype message and observation structure
382+
Message protoMessage;
383+
Structure obsStructure;
289384

290-
SeqIterSingle() {
385+
SeqIterSingle(Message message, Structure structure) {
386+
protoMessage = message;
387+
obsStructure = structure;
291388
reset();
292389
}
293390

391+
SeqIterSingle(RootVariable rootVariable) {
392+
this(rootVariable.protoMessage, rootVariable.variable);
393+
}
394+
294395
@Override
295396
public StructureDataIterator reset() {
296397
recnum = 0;
@@ -350,7 +451,7 @@ public void close() {
350451
public String getDetailInfo() {
351452
Formatter ff = new Formatter();
352453
ff.format("%s", super.getDetailInfo());
353-
protoMessage.dump(ff);
454+
protoMessages.get(0).dump(ff);
354455
ff.format("%n");
355456
config.show(ff);
356457
return ff.toString();

bufr/src/main/java/ucar/nc2/iosp/bufr/BufrIospBuilder.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class BufrIospBuilder {
3030
private static final boolean warnUnits = false;
3131

3232
private final Group.Builder rootGroup;
33-
private final Sequence.Builder recordStructure;
33+
private Sequence.Builder recordStructure;
3434
private final Formatter coordinates = new Formatter();
3535

3636
private int tempNo = 1; // fishy
@@ -75,6 +75,42 @@ class BufrIospBuilder {
7575
}
7676
}
7777

78+
BufrIospBuilder(Message proto, List<BufrConfig> bufrConfigs, Group.Builder root, String location) {
79+
this.rootGroup = root;
80+
81+
// global Attributes
82+
AttributeContainerMutable atts = root.getAttributeContainer();
83+
atts.addAttribute(CDM.HISTORY, "Read using CDM BufrIosp2");
84+
atts.addAttribute("location", location);
85+
86+
atts.addAttribute("BUFR:categoryName", proto.getLookup().getCategoryName());
87+
atts.addAttribute("BUFR:subCategoryName", proto.getLookup().getSubCategoryName());
88+
atts.addAttribute("BUFR:centerName", proto.getLookup().getCenterName());
89+
atts.addAttribute(BufrIosp2.centerId, proto.ids.getCenterId());
90+
atts.addAttribute("BUFR:subCenter", proto.ids.getSubCenterId());
91+
atts.addAttribute("BUFR:table", proto.ids.getMasterTableId());
92+
atts.addAttribute("BUFR:tableVersion", proto.ids.getMasterTableVersion());
93+
atts.addAttribute("BUFR:localTableVersion", proto.ids.getLocalTableVersion());
94+
atts.addAttribute("Conventions", "BUFR/CDM");
95+
atts.addAttribute("BUFR:edition", proto.is.getBufrEdition());
96+
97+
String header = proto.getHeader();
98+
if (header != null && !header.isEmpty()) {
99+
atts.addAttribute("WMO Header", header);
100+
}
101+
102+
for (BufrConfig bufrConfig : bufrConfigs) {
103+
String varName = proto.getLookup().getCategoryName(bufrConfig.getMessage().ids.getCategory());
104+
Sequence.Builder rs = Sequence.builder().setName(varName);
105+
this.rootGroup.addVariable(rs);
106+
makeObsRecord(bufrConfig, rs);
107+
String coordS = coordinates.toString();
108+
if (!coordS.isEmpty()) {
109+
rs.addAttribute(new Attribute("coordinates", coordS));
110+
}
111+
}
112+
}
113+
78114
Sequence.Builder getObsStructure() {
79115
return recordStructure;
80116
}
@@ -117,6 +153,44 @@ private void makeObsRecord(BufrConfig bufrConfig) {
117153
}
118154
}
119155

156+
private void makeObsRecord(BufrConfig bufrConfig, Sequence.Builder rs) {
157+
BufrConfig.FieldConverter root = bufrConfig.getRootConverter();
158+
for (BufrConfig.FieldConverter fld : root.flds) {
159+
DataDescriptor dkey = fld.dds;
160+
if (!dkey.isOkForVariable()) {
161+
continue;
162+
}
163+
164+
if (dkey.replication == 0) {
165+
addSequence(rootGroup, rs, fld);
166+
167+
} else if (dkey.replication > 1) {
168+
169+
List<BufrConfig.FieldConverter> subFlds = fld.flds;
170+
List<DataDescriptor> subKeys = dkey.subKeys;
171+
if (subKeys.size() == 1) { // only one member
172+
DataDescriptor subDds = dkey.subKeys.get(0);
173+
BufrConfig.FieldConverter subFld = subFlds.get(0);
174+
if (subDds.dpi != null) {
175+
addDpiStructure(rs, fld, subFld);
176+
177+
} else if (subDds.replication == 1) { // one member not a replication
178+
Variable.Builder v = addVariable(rootGroup, rs, subFld, dkey.replication);
179+
v.setSPobject(fld); // set the replicating field as SPI object
180+
181+
} else { // one member is a replication (two replications in a row)
182+
addStructure(rootGroup, rs, fld, dkey.replication);
183+
}
184+
} else if (subKeys.size() > 1) {
185+
addStructure(rootGroup, rs, fld, dkey.replication);
186+
}
187+
188+
} else { // replication == 1
189+
addVariable(rootGroup, rs, fld, dkey.replication);
190+
}
191+
}
192+
}
193+
120194
private void addStructure(Group.Builder group, Structure.Builder parent, BufrConfig.FieldConverter fld, int count) {
121195
DataDescriptor dkey = fld.dds;
122196
String uname = findUniqueName(parent, fld.getName(), "struct");

bufr/src/main/java/ucar/nc2/iosp/bufr/BufrTableLookup.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,14 @@ public String getSubCategoryName() { // throws IOException {
127127
return subcatName;
128128
}
129129

130-
131130
public String getCategoryName() {
132131
return TableA.getDataCategory(getCategory());
133132
}
134133

134+
public String getCategoryName(int cat) {
135+
return TableA.getDataCategoryName(cat);
136+
}
137+
135138
public String getCategoryNo() {
136139
String result = getCategory() + "." + getSubCategory();
137140
if (getLocalSubCategory() >= 0)

0 commit comments

Comments
 (0)