1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.files.rinex;
18 import java.io.BufferedReader;
19 import java.io.IOException;
20 import java.io.Reader;
21 import java.nio.CharBuffer;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import org.hipparchus.util.FastMath;
30 import org.orekit.data.DataFilter;
31 import org.orekit.data.DataSource;
32 import org.orekit.data.LineOrientedFilteringReader;
33 import org.orekit.errors.OrekitException;
34 import org.orekit.errors.OrekitMessages;
35 import org.orekit.gnss.SatelliteSystem;
36
37
38
39
40
41
42 public class HatanakaCompressFilter implements DataFilter {
43
44
45 private static final Pattern RINEX_2_PATTERN = Pattern.compile("^(\\w{4}\\d{3}[0a-x](?:\\d{2})?\\.\\d{2})[dD]$");
46
47
48 private static final Pattern RINEX_3_PATTERN = Pattern.compile("^(\\w{9}_\\w{1}_\\d{11}_\\d{2}\\w_\\d{2}\\w{1}_\\w{2})\\.crx$");
49
50
51
52
53
54
55
56
57 public HatanakaCompressFilter() {
58
59 }
60
61
62 @Override
63 public DataSource filter(final DataSource original) {
64
65 final String oName = original.getName();
66 final DataSource.Opener oOpener = original.getOpener();
67
68 final Matcher rinex2Matcher = RINEX_2_PATTERN.matcher(oName);
69 if (rinex2Matcher.matches()) {
70
71 final String fName = rinex2Matcher.group(1) + "o";
72 final DataSource.ReaderOpener fOpener = () -> new HatanakaReader(oName, oOpener.openReaderOnce());
73 return new DataSource(fName, fOpener);
74 }
75
76 final Matcher rinex3Matcher = RINEX_3_PATTERN.matcher(oName);
77 if (rinex3Matcher.matches()) {
78
79 final String fName = rinex3Matcher.group(1) + ".rnx";
80 final DataSource.ReaderOpener fOpener = () -> new HatanakaReader(oName, oOpener.openReaderOnce());
81 return new DataSource(fName, fOpener);
82 }
83
84
85 return original;
86
87 }
88
89
90 private static class HatanakaReader extends LineOrientedFilteringReader {
91
92
93 private final CompactRinexFormat format;
94
95
96
97
98
99
100 HatanakaReader(final String name, final Reader input)
101 throws IOException {
102 super(name, input);
103 format = CompactRinexFormat.getFormat(name, getBufferedReader());
104 }
105
106
107 @Override
108 protected CharSequence filterLine(final int lineNumber, final String originalLine) throws IOException {
109 return format.uncompressSection(originalLine);
110 }
111
112 }
113
114
115 private static class NumericDifferential {
116
117
118 private final int fieldLength;
119
120
121 private final int decimalPlaces;
122
123
124 private final long[] state;
125
126
127 private int nbComponents;
128
129
130 private CharSequence uncompressed;
131
132
133
134
135
136
137 NumericDifferential(final int fieldLength, final int decimalPlaces, final int order) {
138 this.fieldLength = fieldLength;
139 this.decimalPlaces = decimalPlaces;
140 this.state = new long[order + 1];
141 this.nbComponents = 0;
142 }
143
144
145
146
147 public void accept(final CharSequence sequence) {
148
149
150 state[nbComponents] = Long.parseLong(sequence.toString());
151
152
153 for (int i = nbComponents; i > 0; --i) {
154 state[i - 1] += state[i];
155 }
156
157 if (++nbComponents == state.length) {
158
159 --nbComponents;
160 }
161
162
163 final String unscaled = Long.toString(FastMath.abs(state[0]));
164 final int length = unscaled.length();
165 final int digits = FastMath.max(length, decimalPlaces);
166 final int padding = fieldLength - (digits + (state[0] < 0 ? 2 : 1));
167 final StringBuilder builder = new StringBuilder();
168 for (int i = 0; i < padding; ++i) {
169 builder.append(' ');
170 }
171 if (state[0] < 0) {
172 builder.append('-');
173 }
174 if (length > decimalPlaces) {
175 builder.append(unscaled, 0, length - decimalPlaces);
176 }
177 builder.append('.');
178 for (int i = decimalPlaces; i > 0; --i) {
179 builder.append(i > length ? '0' : unscaled.charAt(length - i));
180 }
181
182 uncompressed = builder;
183
184 }
185
186
187
188
189 public CharSequence getUncompressed() {
190 return uncompressed;
191 }
192
193 }
194
195
196 private static class TextDifferential {
197
198
199 private final CharBuffer state;
200
201
202
203
204 TextDifferential(final int fieldLength) {
205 this.state = CharBuffer.allocate(fieldLength);
206 for (int i = 0; i < fieldLength; ++i) {
207 state.put(i, ' ');
208 }
209 }
210
211
212
213
214 public void accept(final CharSequence sequence) {
215
216
217 final int length = FastMath.min(state.capacity(), sequence.length());
218 for (int i = 0; i < length; ++i) {
219 final char c = sequence.charAt(i);
220 if (c == '&') {
221
222 state.put(i, ' ');
223 } else if (c != ' ') {
224
225 state.put(i, c);
226 }
227 }
228
229 }
230
231
232
233
234 public CharSequence getUncompressed() {
235 return state;
236 }
237
238 }
239
240
241 private static class CombinedDifferentials {
242
243
244 private final NumericDifferential[] observations;
245
246
247 private final TextDifferential flags;
248
249
250
251
252
253 CombinedDifferentials(final int nbObs) {
254 this.observations = new NumericDifferential[nbObs];
255 this.flags = new TextDifferential(2 * nbObs);
256 }
257
258 }
259
260
261 private abstract static class CompactRinexFormat {
262
263
264 private static final int LABEL_START = 60;
265
266
267 private static final String CRINEX_VERSION_TYPE = "CRINEX VERS / TYPE";
268
269
270 private static final String CRINEX_PROG_DATE = "CRINEX PROG / DATE";
271
272
273 private static final String NB_OF_SATELLITES = "# OF SATELLITES";
274
275
276 private static final String END_OF_HEADER = "END OF HEADER";
277
278
279 private static final int DEFAULT_NB_SAT = 500;
280
281
282 private final String name;
283
284
285 private final BufferedReader reader;
286
287
288 private int lineNumber;
289
290
291 private final Map<SatelliteSystem, Integer> maxObs;
292
293
294 private int nbSat;
295
296
297 private Section section;
298
299
300 private List<String> satellites;
301
302
303 private TextDifferential epochDifferential;
304
305
306 private NumericDifferential clockDifferential;
307
308
309 private TextDifferential satListDifferential;
310
311
312 private Map<String, CombinedDifferentials> differentials;
313
314
315
316
317
318 protected CompactRinexFormat(final String name, final BufferedReader reader) {
319 this.name = name;
320 this.reader = reader;
321 this.maxObs = new HashMap<>();
322 for (final SatelliteSystem system : SatelliteSystem.values()) {
323 maxObs.put(system, 0);
324 }
325 this.nbSat = DEFAULT_NB_SAT;
326 this.section = Section.HEADER;
327 }
328
329
330
331
332
333
334 public CharSequence uncompressSection(final String firstLine)
335 throws IOException {
336 final CharSequence uncompressed;
337 switch (section) {
338
339 case HEADER : {
340
341 final StringBuilder builder = new StringBuilder();
342 String line = firstLine;
343 lineNumber = 3;
344 while (section == Section.HEADER) {
345 if (builder.length() > 0) {
346 builder.append('\n');
347 line = readLine();
348 }
349 builder.append(parseHeaderLine(line));
350 trimTrailingSpaces(builder);
351 }
352 uncompressed = builder;
353 section = Section.EPOCH;
354 break;
355 }
356
357 case EPOCH : {
358
359 ++lineNumber;
360 uncompressed = parseEpochAndClockLines(firstLine, readLine().trim());
361 section = Section.OBSERVATION;
362 break;
363 }
364
365 default : {
366
367 final String[] lines = new String[satellites.size()];
368 ++lineNumber;
369 lines[0] = firstLine;
370 for (int i = 1; i < lines.length; ++i) {
371 lines[i] = readLine();
372 }
373 uncompressed = parseObservationLines(lines);
374 section = Section.EPOCH;
375 }
376
377 }
378
379 return uncompressed;
380
381 }
382
383
384
385
386
387 public CharSequence parseHeaderLine(final String line) {
388
389 if (isHeaderLine(NB_OF_SATELLITES, line)) {
390
391 nbSat = parseInt(line, 0, 6);
392 } else if (isHeaderLine(END_OF_HEADER, line)) {
393
394 section = Section.EPOCH;
395 }
396
397
398 return line;
399
400 }
401
402
403
404
405
406
407
408 public abstract CharSequence parseEpochAndClockLines(String epochLine, String clockLine)
409 throws IOException;
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425 protected void doParseEpochAndClockLines(final StringBuilder builder,
426 final int epochStart, final int epochLength,
427 final int eventStart, final int nbSatStart, final int satListStart,
428 final int clockLength, final int clockDecimalPlaces,
429 final String epochLine,
430 final String clockLine, final char resetChar)
431 throws IOException {
432
433 boolean loop = true;
434 String loopEpochLine = epochLine;
435 String loopClockLine = clockLine;
436 while (loop) {
437
438
439 if (epochDifferential == null || loopEpochLine.charAt(0) == resetChar) {
440 epochDifferential = new TextDifferential(epochLength);
441 satListDifferential = new TextDifferential(nbSat * 3);
442 differentials = new HashMap<>();
443 }
444
445
446 epochDifferential.accept(loopEpochLine.subSequence(epochStart,
447 FastMath.min(loopEpochLine.length(), epochStart + epochLength)));
448 if (parseInt(epochDifferential.getUncompressed(), eventStart, 1) > 1) {
449
450
451 builder.append(epochDifferential.getUncompressed());
452 trimTrailingSpaces(builder);
453 builder.append('\n');
454 final int skippedLines = parseInt(epochDifferential.getUncompressed(), nbSatStart, 3);
455 for (int i = 0; i < skippedLines; ++i) {
456 builder.append(loopClockLine);
457 trimTrailingSpaces(builder);
458 builder.append('\n');
459 loopClockLine = readLine();
460 }
461
462
463 loopEpochLine = loopClockLine;
464 loopClockLine = readLine();
465 loop = true;
466
467 } else {
468 loop = false;
469 final int n = parseInt(epochDifferential.getUncompressed(), nbSatStart, 3);
470 satellites = new ArrayList<>(n);
471 if (satListStart < loopEpochLine.length()) {
472 satListDifferential.accept(loopEpochLine.subSequence(satListStart, loopEpochLine.length()));
473 }
474 final CharSequence satListPart = satListDifferential.getUncompressed();
475 for (int i = 0; i < n; ++i) {
476 satellites.add(satListPart.subSequence(i * 3, (i + 1) * 3).toString());
477 }
478
479
480 if (!loopClockLine.isEmpty()) {
481 if (loopClockLine.length() > 2 && loopClockLine.charAt(1) == '&') {
482 clockDifferential = new NumericDifferential(clockLength, clockDecimalPlaces, parseInt(loopClockLine, 0, 1));
483 clockDifferential.accept(loopClockLine.subSequence(2, loopClockLine.length()));
484 } else if (clockDifferential == null) {
485 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
486 lineNumber, name, loopClockLine);
487 } else {
488 clockDifferential.accept(loopClockLine);
489 }
490 }
491 }
492 }
493
494 }
495
496
497
498
499 protected CharSequence getEpochPart() {
500 return epochDifferential.getUncompressed();
501 }
502
503
504
505
506 protected CharSequence getClockPart() {
507 return clockDifferential == null ? "" : clockDifferential.getUncompressed();
508 }
509
510
511
512
513 protected List<String> getSatellites() {
514 return satellites;
515 }
516
517
518
519
520
521 protected CombinedDifferentials getCombinedDifferentials(final CharSequence sat) {
522 return differentials.get(sat);
523 }
524
525
526
527
528
529 public abstract CharSequence parseObservationLines(String[] observationLines);
530
531
532
533
534
535
536 protected void doParseObservationLines(final int dataLength, final int dataDecimalPlaces,
537 final String[] observationLines) {
538
539 for (int i = 0; i < observationLines.length; ++i) {
540
541 final CharSequence line = observationLines[i];
542
543
544 final String sat = satellites.get(i);
545 CombinedDifferentials satDiffs = differentials.get(sat);
546 if (satDiffs == null) {
547 final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(sat.subSequence(0, 1).toString());
548 satDiffs = new CombinedDifferentials(maxObs.get(system));
549 differentials.put(sat, satDiffs);
550 }
551
552
553 int k = 0;
554 for (int j = 0; j < satDiffs.observations.length; ++j) {
555
556 if (k >= line.length() || line.charAt(k) == ' ') {
557
558 satDiffs.observations[j] = null;
559 } else {
560
561
562 if (k + 1 < line.length() &&
563 Character.isDigit(line.charAt(k)) &&
564 line.charAt(k + 1) == '&') {
565
566 satDiffs.observations[j] = new NumericDifferential(dataLength, dataDecimalPlaces,
567 Character.digit(line.charAt(k), 10));
568 k += 2;
569 }
570
571
572 final int start = k;
573 while (k < line.length() && line.charAt(k) != ' ') {
574 ++k;
575 }
576 try {
577 satDiffs.observations[j].accept(line.subSequence(start, k));
578 } catch (NumberFormatException nfe) {
579 throw new OrekitException(nfe,
580 OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
581 lineNumber + i - (observationLines.length - 1),
582 name, observationLines[i]);
583 }
584
585 }
586
587
588 ++k;
589
590 }
591
592 if (k < line.length()) {
593 satDiffs.flags.accept(line.subSequence(k, line.length()));
594 }
595
596 }
597
598 }
599
600
601
602
603
604
605 protected boolean isHeaderLine(final String label, final String line) {
606 return label.equals(parseString(line, LABEL_START, label.length()));
607 }
608
609
610
611
612
613 protected void updateMaxObs(final SatelliteSystem system, final int nbObs) {
614 maxObs.put(system, FastMath.max(maxObs.get(system), nbObs));
615 }
616
617
618
619
620
621 private String readLine()
622 throws IOException {
623 final String line = reader.readLine();
624 if (line == null) {
625 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
626 }
627 lineNumber++;
628 return line;
629 }
630
631
632
633
634
635
636
637 public static CompactRinexFormat getFormat(final String name, final BufferedReader reader)
638 throws IOException {
639
640
641 final String line1 = reader.readLine();
642 final String line2 = reader.readLine();
643 if (line1 == null || line2 == null) {
644 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
645 }
646
647
648 final int cVersion100 = (int) FastMath.rint(100 * parseDouble(line1, 0, 9));
649 if (cVersion100 != 100 && cVersion100 != 300) {
650 throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
651 }
652 if (!CRINEX_VERSION_TYPE.equals(parseString(line1, LABEL_START, CRINEX_VERSION_TYPE.length()))) {
653 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
654 }
655 if (!CRINEX_PROG_DATE.equals(parseString(line2, LABEL_START, CRINEX_PROG_DATE.length()))) {
656 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_HATANAKA_COMPRESSED_FILE, name);
657 }
658
659
660 return cVersion100 < 300 ? new CompactRinex1(name, reader) : new CompactRinex3(name, reader);
661
662 }
663
664
665
666
667
668
669
670 public static String parseString(final CharSequence line, final int start, final int length) {
671 if (line.length() > start) {
672 return line.subSequence(start, FastMath.min(line.length(), start + length)).toString().trim();
673 } else {
674 return null;
675 }
676 }
677
678
679
680
681
682
683
684 public static int parseInt(final CharSequence line, final int start, final int length) {
685 if (line.length() > start && parseString(line, start, length).length() > 0) {
686 return Integer.parseInt(parseString(line, start, length));
687 } else {
688 return 0;
689 }
690 }
691
692
693
694
695
696
697
698 public static double parseDouble(final CharSequence line, final int start, final int length) {
699 if (line.length() > start && parseString(line, start, length).length() > 0) {
700 return Double.parseDouble(parseString(line, start, length));
701 } else {
702 return Double.NaN;
703 }
704 }
705
706
707
708
709 public static void trimTrailingSpaces(final StringBuilder builder) {
710 for (int i = builder.length() - 1; i >= 0 && builder.charAt(i) == ' '; --i) {
711 builder.deleteCharAt(i);
712 }
713 }
714
715
716 private enum Section {
717
718
719 HEADER,
720
721
722 EPOCH,
723
724
725 OBSERVATION
726
727 }
728
729 }
730
731
732 private static class CompactRinex1 extends CompactRinexFormat {
733
734
735 private static final String NB_TYPES_OF_OBSERV = "# / TYPES OF OBSERV";
736
737
738 private static final int EPOCH_START = 0;
739
740
741 private static final int EPOCH_LENGTH = 32;
742
743
744 private static final int EVENT_START = EPOCH_START + EPOCH_LENGTH - 4;
745
746
747 private static final int NB_SAT_START = EPOCH_START + EPOCH_LENGTH - 3;
748
749
750 private static final int SAT_LIST_START = EPOCH_START + EPOCH_LENGTH;
751
752
753 private static final int SAT_LIST_LENGTH = 36;
754
755
756 private static final int MAX_SAT_EPOCH_LINE = 12;
757
758
759 private static final int CLOCK_START = SAT_LIST_START + SAT_LIST_LENGTH;
760
761
762 private static final int CLOCK_LENGTH = 12;
763
764
765 private static final int CLOCK_DECIMAL_PLACES = 9;
766
767
768 private static final int DATA_LENGTH = 14;
769
770
771 private static final int DATA_DECIMAL_PLACES = 3;
772
773
774
775
776
777 CompactRinex1(final String name, final BufferedReader reader) {
778 super(name, reader);
779 }
780
781 @Override
782
783 public CharSequence parseHeaderLine(final String line) {
784 if (isHeaderLine(NB_TYPES_OF_OBSERV, line)) {
785 for (final SatelliteSystem system : SatelliteSystem.values()) {
786 updateMaxObs(system, parseInt(line, 0, 6));
787 }
788 return line;
789 } else {
790 return super.parseHeaderLine(line);
791 }
792 }
793
794 @Override
795
796 public CharSequence parseEpochAndClockLines(final String epochLine, final String clockLine)
797 throws IOException {
798
799 final StringBuilder builder = new StringBuilder();
800 doParseEpochAndClockLines(builder,
801 EPOCH_START, EPOCH_LENGTH, EVENT_START, NB_SAT_START, SAT_LIST_START,
802 CLOCK_LENGTH, CLOCK_DECIMAL_PLACES, epochLine,
803 clockLine, '&');
804
805
806
807 final List<String> satellites = getSatellites();
808 builder.append(getEpochPart());
809 int iSat = 0;
810 while (iSat < FastMath.min(satellites.size(), MAX_SAT_EPOCH_LINE)) {
811 builder.append(satellites.get(iSat++));
812 }
813 if (getClockPart().length() > 0) {
814 while (builder.length() < CLOCK_START) {
815 builder.append(' ');
816 }
817 builder.append(getClockPart());
818 }
819
820 while (iSat < satellites.size()) {
821
822 trimTrailingSpaces(builder);
823 builder.append('\n');
824 for (int k = 0; k < SAT_LIST_START; ++k) {
825 builder.append(' ');
826 }
827 final int iSatStart = iSat;
828 while (iSat < FastMath.min(satellites.size(), iSatStart + MAX_SAT_EPOCH_LINE)) {
829 builder.append(satellites.get(iSat++));
830 }
831 }
832 trimTrailingSpaces(builder);
833 return builder;
834
835 }
836
837 @Override
838
839 public CharSequence parseObservationLines(final String[] observationLines) {
840
841
842 doParseObservationLines(DATA_LENGTH, DATA_DECIMAL_PLACES, observationLines);
843
844
845 final StringBuilder builder = new StringBuilder();
846 for (final CharSequence sat : getSatellites()) {
847 if (builder.length() > 0) {
848 trimTrailingSpaces(builder);
849 builder.append('\n');
850 }
851 final CombinedDifferentials cd = getCombinedDifferentials(sat);
852 final CharSequence flags = cd.flags.getUncompressed();
853 for (int i = 0; i < cd.observations.length; ++i) {
854 if (i > 0 && i % 5 == 0) {
855 trimTrailingSpaces(builder);
856 builder.append('\n');
857 }
858 if (cd.observations[i] == null) {
859
860 for (int j = 0; j < DATA_LENGTH + 2; ++j) {
861 builder.append(' ');
862 }
863 } else {
864 builder.append(cd.observations[i].getUncompressed());
865 if (2 * i < flags.length()) {
866 builder.append(flags.charAt(2 * i));
867 }
868 if (2 * i + 1 < flags.length()) {
869 builder.append(flags.charAt(2 * i + 1));
870 }
871 }
872 }
873 }
874 trimTrailingSpaces(builder);
875 return builder;
876
877 }
878
879 }
880
881
882 private static class CompactRinex3 extends CompactRinexFormat {
883
884
885 private static final String SYS_NB_OBS_TYPES = "SYS / # / OBS TYPES";
886
887
888 private static final int EPOCH_START = 0;
889
890
891 private static final int EPOCH_LENGTH = 41;
892
893
894 private static final int CLOCK_START = EPOCH_START + EPOCH_LENGTH;
895
896
897 private static final int CLOCK_LENGTH = 15;
898
899
900 private static final int CLOCK_DECIMAL_PLACES = 12;
901
902
903 private static final int EVENT_START = EPOCH_START + EPOCH_LENGTH - 10;
904
905
906 private static final int NB_SAT_START = EPOCH_START + EPOCH_LENGTH - 9;
907
908
909 private static final int SAT_LIST_START = EPOCH_START + EPOCH_LENGTH;
910
911
912 private static final int DATA_LENGTH = 14;
913
914
915 private static final int DATA_DECIMAL_PLACES = 3;
916
917
918
919
920
921 CompactRinex3(final String name, final BufferedReader reader) {
922 super(name, reader);
923 }
924
925 @Override
926
927 public CharSequence parseHeaderLine(final String line) {
928 if (isHeaderLine(SYS_NB_OBS_TYPES, line)) {
929 if (line.charAt(0) != ' ') {
930
931
932 updateMaxObs(SatelliteSystem.parseSatelliteSystem(parseString(line, 0, 1)),
933 parseInt(line, 1, 5));
934 }
935 return line;
936 } else {
937 return super.parseHeaderLine(line);
938 }
939 }
940
941 @Override
942
943 public CharSequence parseEpochAndClockLines(final String epochLine, final String clockLine)
944 throws IOException {
945
946 final StringBuilder builder = new StringBuilder();
947 doParseEpochAndClockLines(builder,
948 EPOCH_START, EPOCH_LENGTH, EVENT_START, NB_SAT_START, SAT_LIST_START,
949 CLOCK_LENGTH, CLOCK_DECIMAL_PLACES, epochLine,
950 clockLine, '>');
951
952
953 builder.append(getEpochPart());
954 if (getClockPart().length() > 0) {
955 while (builder.length() < CLOCK_START) {
956 builder.append(' ');
957 }
958 builder.append(getClockPart());
959 }
960
961 trimTrailingSpaces(builder);
962 return builder;
963
964 }
965
966 @Override
967
968 public CharSequence parseObservationLines(final String[] observationLines) {
969
970
971 doParseObservationLines(DATA_LENGTH, DATA_DECIMAL_PLACES, observationLines);
972
973
974 final StringBuilder builder = new StringBuilder();
975 for (final CharSequence sat : getSatellites()) {
976 if (builder.length() > 0) {
977 trimTrailingSpaces(builder);
978 builder.append('\n');
979 }
980 builder.append(sat);
981 final CombinedDifferentials cd = getCombinedDifferentials(sat);
982 final CharSequence flags = cd.flags.getUncompressed();
983 for (int i = 0; i < cd.observations.length; ++i) {
984 if (cd.observations[i] == null) {
985
986 for (int j = 0; j < DATA_LENGTH + 2; ++j) {
987 builder.append(' ');
988 }
989 } else {
990 builder.append(cd.observations[i].getUncompressed());
991 if (2 * i < flags.length()) {
992 builder.append(flags.charAt(2 * i));
993 }
994 if (2 * i + 1 < flags.length()) {
995 builder.append(flags.charAt(2 * i + 1));
996 }
997 }
998 }
999 }
1000 trimTrailingSpaces(builder);
1001 return builder;
1002
1003 }
1004
1005 }
1006
1007 }