55package ucar .nc2 .iosp .bufr ;
66
77import 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+
1210import org .jdom2 .Element ;
1311import ucar .ma2 .Array ;
1412import 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 ();
0 commit comments