File: Moc.java

package info (click to toggle)
libcds-moc-java 6.31-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 388 kB
  • sloc: java: 4,935; makefile: 18
file content (1181 lines) | stat: -rw-r--r-- 46,335 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
// Copyright 2021 - Unistra/CNRS
// The MOC API project is distributed under the terms
// of the GNU General Public License version 3.
//
//This file is part of MOC API java project.
//
//    MOC API java project is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, version 3 of the License.
//
//    MOC API java project is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    The GNU General Public License is available in COPYING file
//    along with MOC API java project.
//

package cds.moc;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.StringTokenizer;

/**
 * Multi Order Coverage Map (MOC)
 * This class provides read, write and process methods to manipulate a Multi Order Coverage Map (MOC).
 * A MOC is used to define a Coverage in space, time, etc, or combination of these physical dimensions
 * 
 * See:  IVOA MOC 2.0 standard => https://www.ivoa.net/documents/MOC/
 * 
 * This abstract class Moc describes or implements the methods generic to MOCs, 
 * whatever their type (spatial, temporal, spatio-temporal, etc).
 * This class is derived in 2 classes: Moc1D and Moc2D.
 * Moc1D is a generic class for 1 physical dimensional MOCs, which is derived into SMOC (spatial MOCs) and TMoc (temporal MOCs). 
 * Moc2D is a generic class for 2-dimensional physical MOCs, which is derived into STMOC (space-time MOC)
 * 
 * Warning: The cds.moc package has been completely revised/recoded when the IVOA MOC2.0 standard was published (2021). 
 * the HealpixMoc class is provided only to ensure compatibility with old software. It is only a wrapper to
 * the new SMoc class that it is recommended to use instead. Note that some low level methods existing in HealpixMoc
 * have not been reimplemented (cell lists by order)
 * 
 * This package manipulates and stores MOCs only as a list of ranges (unlike its predecessor 
 * which used both a hierarchical and an range architecture). 
 * This refactoring was done to allow easy extension to MOCs covering other physical dimensions 
 * (currently only SPACE, TIME or TIME.SPACE)
 * It uses code and algorithm initially developed by Jan Kotek, then refactored/extended by M.Reinecker, 
 * and re-extended for the specific needs of 2D MOCs (see Range and Range2 classes)
 * 
 * Examples of uses are available in the class cds.moc.misc.MocExample and cds.moc.misc.MocTest
 * The class cds.moc.misc.MocLint implements methods to validate the conformity of the binary or ASCII serialization 
 * of a MOC with the IVOA MOC 2.0 standard as well as its previous versions (1.1 and 1.0).
 * 
 * @author Pierre Fernique [CDS]
 * @version 6.31 - Dec 2024 - bug fixed (Range2)
 * @version 6.2 - July 2021 - perf improvement
 * @version 6.1 - May 2021 - bugs fixed version
 * @version 6.0 - April 2021 - full refactoring
 * @version 0.9 to 5 - 2011 to 2017 - predecessors
 *
 */
public abstract class Moc implements Iterable<MocCell>, Cloneable, Comparable<Moc>{
   
   /** MOC API version number */
   static public final String VERSION = "6.31";

   /** Two binary serialization - RAW is the IVOA standard */
   public static final int RAW = 0;
   public static final int COMPRESS_SINGLETON = 1;

   /** MOC serialization formats */
   static private final int UNKNOWN  = -1;        // Unknown format
   static public final int FITS      = 0;         // FITS format
   static public final int ASCII     = 1;         // ASCII format
   static public final int JSON      = 2;         // JSON format (suggested in IVOA REC)

   protected int cacheNbCells;            // Number if cells in hierarchical view (-1 if unknown -> see getNbCells();
   protected int cacheDeepestOrder;       // Hierarchical deepest required order, -1 if undefined
   protected int cacheHashCode;           // Last computed hashCode
   
   /** MOC Properties and comments */
   protected LinkedHashMap<String, String> property;   // List of properties associated to the Moc (key -> property)
   protected HashMap<String, String> comment;          // Comment associated to each properties (key -> comment)
   
   
   /******************************************** Factories & global parameters ***************************************************/
   
   static final public int LOGIC_MIN = 0;
   static final public int LOGIC_MAX = 1;
   static private int mocOrderLogic = LOGIC_MAX;
   
   /** Get the current mocOrderLogic applied for operations (see setMocOrderLogic()) */
   static public int getMocOrderLogic() { return mocOrderLogic; }
   
   /** Set the current mocOrderLogic applied for operations: Default LOGIC_MAX
    * LOGIC_MAX: MOC result for operations is returned with the Max orders of the 2 operandes => preserving area logic
    * LOGIC_MIN: MOC result for operations is returned with the Min orders of the 2 operandes => preserving observation logic
    * See IVOA 2.0 document
    * @param logic LOGIC_MIN or LOGIC_MAX
    */
   static public void setMocOrderLogic(int logic) { mocOrderLogic=logic; }  
   
   /** Generic MOC factory. Recognize the MOC ASCII or JSON string and create the associated space,
    * time or space-time MOC.
    * @param s MOC string => ex: SMOC:3/1-4... TMOC:t29/3456-6788... STMOC:t27/... s29/...
    * @return a MOC
    */
   static public Moc createMoc(String s) throws Exception {
      if( s==null ) throw new Exception("Null string MOC");
      if( s.length()==0 ) throw new Exception("Empty string MOC");
      
      Moc moc;
      int is = s.indexOf('s');
      int it = s.indexOf('t');
      if( it!=-1 && is!=-1 ) moc = new STMoc();
      else if( it!=-1 ) moc = new TMoc();
      else moc = new SMoc();
      
      moc.add( s );
      moc.add(null);   // in case of STMOC, we must pass null to flush the last buffer
      return moc;
   }
   
   
   /** Generic MOC factory. Recognize the MOC syntax (FITS, ASCII or JSON) and type (SMOC, TMOC or STMOC)
    * and create the associated MOC.
    * @param in the input stream
    * @return a MOC
    */
   static public Moc createMoc(InputStream in) throws Exception {
      Moc moc = null;
      
      // Format auto-detection
      // Read the first charactere for deciding FITS or ASCII, and reset the stream
      if(  !in.markSupported() ) in = new BufferedInputStream(in, 32*1024);
      in.mark(10);
      byte [] b = new byte[1];
      in.read(b);
      in.reset();
      int mode = b[0]=='S' ? FITS : ASCII;             // Same reader for ASCII and JSON
      in.mark(32*1024);

      // ASCII & JSON reader
      if( mode==ASCII ) {
         b = new byte[512];
         int n = in.read(b);
         in.reset();
         String s = new String(b,0,n);
         int is = s.indexOf('s');
         int it = s.indexOf('t');
         if( it!=-1 && is!=-1 ) moc = new STMoc();
         else if( it!=-1 ) moc = new TMoc();
         else moc = new SMoc();
         moc.readASCII(in); 
         
      // FITS reader
      } else {

         HeaderFits header = (new SMoc()).new HeaderFits();
         header.readHeader(in);   // HDU 0
         header.readHeader(in);   // HDU 1
         in.reset();

         // Create SMOC, TMOC or STMOC according to the header content
         String dim  = header.getStringFromHeader("MOCDIM");
         if( dim==null ) dim  = header.getStringFromHeader("MOC");  // Proto compatibility
         if( dim!=null ) {
            if( dim.equals("TIME") ) moc = new TMoc();
            else if( dim.equals("TIME.SPACE") ) moc = new STMoc();
         }
         
         // Proto compatibility
         if( moc==null && header.getStringFromHeader("TIMESYS")!=null ) moc = new TMoc();
         
         if( moc==null ) moc = new SMoc();
         moc.readFITS(in);
      }

      return moc;
   }
   
   /********************************************* Generic getters/setters ***********************************************/
   
   public void setSpaceOrder( int order ) throws Exception {
      if( this instanceof SMoc )  ((SMoc)this).setMocOrder(order);
      else if( this instanceof STMoc ) ((STMoc)this).setSpaceOrder(order);
      else throw new Exception("Unsupported physical dimension");
   }
   
   public void setTimeOrder( int order ) throws Exception {
      if( this instanceof TMoc )  ((TMoc)this).setMocOrder(order);
      else if( this instanceof STMoc ) ((STMoc)this).setTimeOrder(order);
      else throw new Exception("Unsupported physical dimension");
   }
   
   public int getSpaceOrder() {
      try {
         if( this instanceof SMoc )  return ((SMoc)this).getMocOrder();
         if( this instanceof STMoc ) return ((STMoc)this).getSpaceOrder();
      } catch( Exception e ) { }
      return -1;
   }
   
   public int getTimeOrder() {
      try {
         if( this instanceof TMoc ) return ((TMoc)this).getMocOrder();
         if( this instanceof STMoc ) return ((STMoc)this).getTimeOrder();
      } catch( Exception e ) { }
      return -1;
   }
   
   public void setSpaceSys(String sys) {}
   public String getSpaceSys() { return null; }
   
   public void setTimeSys(String sys) { }
   public String getTimeSys() { return null; }
   
   public SMoc getSpaceMoc()  throws Exception {
      if( this instanceof SMoc ) return (SMoc) this;
      if( this instanceof STMoc ) return ((STMoc)this).getSpaceMoc();
      throw new Exception("Unsupported physical dimension");
   }
   
   public TMoc getTimeMoc()  throws Exception {
      if( this instanceof TMoc ) return (TMoc) this;
      if( this instanceof STMoc ) return ((STMoc)this).getTimeMoc();
      throw new Exception("Unsupported physical dimension");
   }
   
   public STMoc getSpaceTimeMoc()  throws Exception { return STMoc.asSTMoc(this); }
   
   public boolean isSpace() { return this instanceof SMoc || this instanceof STMoc; }
   public boolean isTime()  { return this instanceof TMoc || this instanceof STMoc; }
   
   /*************************************************** Main methods **********************************************************/
   
   public Moc() {
      property = new LinkedHashMap<>();
      comment = new HashMap<>();
      clear();
   }
   
   /** Clone Moc (deep copy) */
   public abstract Moc clone() throws CloneNotSupportedException;
   
   /** Create and instance of same class, same sys, but no data nor mocorder*/
   public abstract Moc dup();
   
   public String toString() {
      try { return toASCII(); }
      catch( Exception e) { return null; }
   }
      
   public abstract String toDebug();
   
   /** Add a list of MOC elements provided in a string format (ASCII format or JSON format)
    * ex basic ASCII:   order1/npix1-npix2 npix3 ... order2/npix4 ...
    * ex JSON:          { "order1":[npix1,npix2,...], "order2":[npix3...] }
    * Note : The string can be submitted in several times. In this case, the insertion will use the last current order
    * Note : in JSON, the syntax is not checked ( in fact {, [ and " are ignored)
    */
   public void add(String s) throws Exception {
      if( s==null || s.length()==0 ) { addToken(null); return; }
      s=json2ASCII(s);
      StringTokenizer st = new StringTokenizer(s," ;,\n\r\t");
      while( st.hasMoreTokens() ) {
         String s1 = st.nextToken();
         if( s1.length()==0 ) continue;
         addToken(s1);
      }
   }
   
   private boolean flagCDim=false;
   
   // Translate JSON token syntax in ASCII token syntax if required
   protected String json2ASCII(String s ) {
      if( s==null || s.length()==0 ) return s;
      StringBuilder res = new StringBuilder();
      for( char c : s.toCharArray() ) {
         if( flagCDim &&!Character.isDigit(c) ) continue;
         if( "{}[]\"".indexOf(c)>=0 ) continue;
         flagCDim = c=='t' || c=='s';
         if( c==',' ) c=' ';
         if( c==':' ) c='/';
         res.append(c);
      }
      return res.toString();
   }
   
   /** Clear the MOC - data only (not the properties, nor the mocOrder) */
   public void clear() {
      cacheNbCells=-1;
      cacheDeepestOrder=-1;
      cacheHashCode=-1;
   }
   
   /** After adding process, required method before operating - not required for all classical API operations
    * as it is already call */
   public abstract void flush();
   
   /** Buffer size, not yet proceed */
   public int bufferSize() { return 0; }
   
   /** Degrades the resolution(s) of the MOC until the RAM size of the MOC is reduced under the specified maximum (expressed in bytes). */
   public abstract boolean reduction( long maxSize) throws Exception;
   
   /** Return true if the Moc is empty (no coverage) */
   public abstract boolean isEmpty();
   
   /** Return true if the Moc is full (full coverage) */
   public abstract boolean isFull();
   
   /** Return the coverage pourcentage of the Moc */
   public abstract double getCoverage();
   
   /** Return approximatively the amount of memory used for storing this MOC in RAM (in bytes) */
   public abstract long getMem();

   /** Return the hierarchical deepest required order - slow process, uses a cache  */ 
   public int getDeepestOrder() { 
      if( cacheDeepestOrder==-1 ) computeHierarchy();
      return cacheDeepestOrder;
   }
   
   /** Comparator. Based on Moc coverage */
   public int compareTo(Moc o) {
      if( o==null ) return 1;
      double d = getCoverage()-o.getCoverage();
      return d<0 ? -1 : d>0 ? 1 : 0;
   }
   
   /** Return the number of ranges */
   public abstract int getNbRanges();
   
   /** Set the list of ranges - Warning: no copy */
   public abstract void setRangeList( Range range );
   
   /** Acces to the list of ranges (no copy) */
   public abstract Range seeRangeList();
   
   /** Provide an Iterator on the MOC cell List (hierarchy view for Moc1D, and range highest order view for Moc2D)
    * @param flagRange true for getting range rather than all individual values
    * @return mocCell => dim,order,startVal,endVal,Moc1D
    */
   public Iterator<MocCell> iterator() { return cellIterator( false ); }
   public abstract Iterator<MocCell> cellIterator( boolean flagRange );
   
   /** Deep copy. The source is this, the target is the Moc in parameter */
   protected void clone1( Moc moc )  throws CloneNotSupportedException {
      flush();
      moc.property = (LinkedHashMap<String, String>)property.clone();
      moc.comment = (HashMap<String, String>)comment.clone();
      moc.cacheNbCells = cacheNbCells;
      moc.cacheDeepestOrder=cacheDeepestOrder;
   }
   
   /** Internal usage: Add one token element according to the format "[s|t]order/npix[-npixn]".
    * If the order is not mentioned, use the last used order (currentOrder)
    * Note: Also support JSON non standard IVOA syntax
    * @param token one token (ex: s18/23-45)
    */
   protected abstract void addToken( String token ) throws Exception ;
   
   /** Return resulting order for operations. see setMocOrderLogic() */
   protected int getMocOrder4op(int m1, int m2) {
      if( m1==-1 ) return m2;
      if( m2==-1 ) return m1;
      if( mocOrderLogic==LOGIC_MAX ) return Math.max( m1, m2);
      return Math.min( m1, m2);
   }
   
   /** Return the number of Moc cells (hierarchy Moc view) - slow process, uses a cache */
   public int getNbCells() {
      if( cacheNbCells==-1 ) computeHierarchy();
      return cacheNbCells;
   }
   
   /******************************************* Internal methods *************************************************/
   
   /** Recalculates the metrics associated with the MOC hierarchical view: 
    * the number of hierarchical cells, the deepest order used... */
   protected  void resetCache() {
      cacheNbCells=-1;
      cacheDeepestOrder=-1;
      cacheHashCode=-1;
   }

   /** Recalculates the metrics associated with the MOC hierarchical view: 
    * the number of hierarchical cells, the deepest order used... */
   protected abstract void computeHierarchy();

   /********************************************  Prototypes *******************************************************/
   
   public void accretion() throws Exception {}
   
   /***************************************************** Properties ******************************************/
   
   /** MOC propertie setter */
   public void setProperty(String key, String value) throws Exception { setProperty(key,value,null); }
   public void setProperty(String key, String value, String comment) throws Exception {
      if( key.length()>8 ) throw new Exception("Property error: Too long property key word (<= 8 characters) ["+key+"]");
      for( char c : key.toCharArray() ) {
         if( !(c>='A' && c<='Z' || c>='0' && c<='9' || c=='-' || c=='_')  ) 
            throw new Exception("Property error: keyword character not allowd (only 'A''Z'|'0''9'|'-'|'_')  ["+key+"]");
      }
      if( isReservedFitsKeyWords(key) ) throw new Exception("Property error: Reserved FITS key word ["+key+"]");
      property.put(key,value);
      if( comment==null ) this.comment.remove(key);
      else this.comment.put(key,comment);
   }

   /** Provide the list of MOC property keys*/
   public String [] getPropertyKeys() {
      String[] a = new String[property.size()];
      int i=0;
      for( String key : property.keySet() ) { a[i++]=key; }
      return a;
   }
   
   /** Provide MOC property value. */
   public String getProperty(String key) { return property.get(key); }
   
   /** Provide MOC property comment. */
   public String getComment(String key) { return comment.get(key); }
   
   
   /***************************************************** Operations ******************************************/
   
   
   public abstract boolean isCompatible(Moc moc);
   
   public abstract boolean isIncluding(Moc moc) throws Exception;
   public abstract boolean isIntersecting(Moc moc) throws Exception;
   
   /** Return the Union with another Moc */
   public Moc union(Moc moc)        throws Exception { return operation(moc,0); }
   
   /** Return the Intersection with another Moc */
   public Moc intersection(Moc moc) throws Exception { return operation(moc,1); }
   
   /** Return the subtraction with another Moc */
   public Moc subtraction(Moc moc)  throws Exception { return operation(moc,2); }
   
   /** Return the difference with another Moc (not in A & not in B)*/
   public Moc difference(Moc moc) throws Exception {
      Moc inter = intersection(moc);
      Moc union = union(moc);
      return union.subtraction(inter);
   }
   
   /** Return the complement */
   public abstract Moc complement() throws Exception;

   /** Generic operations: 0-union, 1-intersection, 2-subtraction */
   protected abstract Moc operation(Moc moc, int op) throws Exception;
   
   
   /*************************************************************** I/O *****************************************************/
   
   
   /** Read MOC from a file.
    * Support standard FITS, ASCII and non standard JSON alternative
    * @param filename file name
    * @throws Exception
    */
   public void read(String filename) throws Exception {
      File f = new File(filename);
      FileInputStream fi = null;
      BufferedInputStream bf = null;
      try {
         fi = new FileInputStream(f);
         bf = new BufferedInputStream(fi);
         read( bf );
      } finally {
         if( bf!=null ) bf.close();
         else if( fi!=null ) fi.close();
      }
   }

   /** Read MOC from a stream.
    * Support standard FITS, ASCII and non standard JSON alternative
    * @param in input stream (not closed at the end)
    * @throws Exception
    */
   public void read(InputStream in) throws Exception { read(in,UNKNOWN);  }
   public void read(InputStream in, int mode) throws Exception {

      // Auto-detection du format ?
      BufferedInputStream bis=null;
      if( mode==UNKNOWN && !in.markSupported() ) in = new BufferedInputStream(in, 32*1024);

      // Read the first charactere for deciding FITS or ASCII, and reset the stream
      in.mark(10);
      byte [] b = new byte[1];
      in.read(b);
      in.reset();
      mode = b[0]=='S' ? FITS : ASCII;             // Same reader for ASCII and JSON

      if( mode==FITS ) readFITS(in);
      else readASCII(in); 
      resetCache();
   }

   /** Read MOC from an JSON stream */
   public void readJSON(InputStream in) throws Exception { readASCII(in); }
   
   /** Read MOC from an ASCII stream */
   public void readASCII(InputStream in) throws Exception {
      clear();
      BufferedReader dis = new BufferedReader(new InputStreamReader(in));
      String s;
      while( (s=dis.readLine())!=null ) {
         if( s.length()==0 ) continue;
         if( s.charAt(0)=='#' ) continue;
         add(s);
      }
      add(null);   // ncessaire, notamment pour STMOC
      resetCache();
   }
   
   /** Read MOC from an Binary FITS stream */
   public void readFITS(InputStream in) throws Exception {
      clear();
      HeaderFits header = new HeaderFits();
      header.readHeader(in);   // HDU 0
         header.readHeader(in);  // HDU 1
      readBinary(header,in);   // Data
   }
   
   /** Read data binary part from an Binary FITS stream */
   private void readBinary(HeaderFits header, InputStream in )  throws Exception {
         int naxis1 = header.getIntFromHeader("NAXIS1");
         int naxis2 = header.getIntFromHeader("NAXIS2");
         String tform = header.getStringFromHeader("TFORM1");
         int nbyte= tform.indexOf('K')>=0 ? 8 : tform.indexOf('J')>=0 ? 4 : -1;   // entier 64 bits, sinon 32
         if( nbyte<=0 ) throw new Exception("Multi Order Coverage Map only requieres integers (32bits or 64bits)");
         
         // Store Fits Properties
         for( String k : header ) {
            if( isReservedFitsKeyWords(k) ) continue;
            setProperty(k, header.getStringFromHeader(k) );
         }

         readSpecificData( in, naxis1,naxis2, nbyte, header);
         resetCache();
   }
   
   /** Internal method: read FITS data according to the type of MOC.
    * @param in The input stream
    * @param naxis1 size of FITS row (in bytes) (generally ==nbyte, but may be 1024 for buffering)
    * @param naxis2 number of values
    * @param nbyte size of each value (in bytes)
    * @param header HDU1 header
    * @throws Exception
    */
   protected abstract void readSpecificData(InputStream in, int naxis1, int naxis2, int nbyte, HeaderFits header) throws Exception;

   public abstract void readSpecificDataRange(int nval,byte [] t,int mode) throws Exception;
   
   /** Write MOC in FITS binary serialization */
   public void write(String filename) throws Exception { write(filename,FITS); }
   
   /** Write MOC in FITS binary serialization */
   public void writeFITS(String filename) throws Exception { write(filename,FITS); }
   
   /** Write MOC in ASCII serialization */
   public void writeASCII(String filename) throws Exception { write(filename,ASCII); }
   
   /** Write MOC in JSON serialization (non IVOA standard) */
   public void writeJSON(String filename) throws Exception { write(filename,JSON); }

   
   /** Write MOC to a file
    * @param filename name of file
    * @param mode encoded format (FITS, ASCII or JSON)
    */
   public void write(String filename,int mode) throws Exception {
      if( mode!=FITS && mode!=ASCII && mode!=JSON ) throw new Exception("Unknown MOC format !");
      File f = new File(filename);
      if( f.exists() ) f.delete();
      FileOutputStream fo = null;
      BufferedOutputStream fb = null;

      try {
         fo = new FileOutputStream(f);
         fb = new BufferedOutputStream(fo);
         write(fb,mode);
      } finally {
         if( fb!=null ) fb.close();
         else if( fo!=null ) fo.close();
      }
   }
   
   /** Write MOC to a stream
    * @param output stream (not closed at the end)
    * @param mode encoded format (FITS, ASCII or JSON)
    */
   public void write(OutputStream out,int mode) throws Exception {
      if( mode==FITS ) writeFITS(out);
      else if( mode==JSON  ) writeJSON(out);
      else if( mode==ASCII ) writeASCII(out);
      else throw new Exception("Unknown MOC format !");
   }

   /** Write MOC to an output stream in binary serialization */
   public void write(OutputStream out) throws Exception { writeFITS(out); }

   /** Write MOC to an output stream in bASCII serialization */
   public abstract void writeASCII(OutputStream out) throws Exception;
   
   /** Write MOC to an output stream in JSON serialization (non IVOA standard) */
   public abstract void writeJSON(OutputStream out) throws Exception;    

   /** Write MOC to an output stream in binary FITS serialization */
   public void writeFITS(OutputStream out) throws Exception {
      writeHeader0(out);
      writeHeader1(out);
      writeData(out);
      
   }
   
      
   /******************************************** Utils  *************************************************************************/

   static protected String CR = System.getProperty("line.separator");
   
   static final public String unites[] = {"B","KB","MB","GB","TB","PB","EB","ZB"};
   static final public String getUnitDisk(long val) { return getUnitDisk(val, 0, 2); }
   static final public String getUnitDisk(long val, int unit, int format) {
      long div,rest=0;
      boolean neg=false;
      if( val<0 ) { neg=true; val=-val; }
      while (val >= 1024L && unit<unites.length-1) {
         unit++;
         div = val / 1024L;
         rest = val % 1024L;
         val=div;
      }
      NumberFormat nf = NumberFormat.getInstance();
      nf.setMaximumFractionDigits(format);
      double x = val+rest/1024.;
      return (neg?"-":"")+nf.format(x)+unites[unit];
   }

   static public long MASK_COMP = 1L<<62;
   static public long UNMASK_COMP = ~MASK_COMP;

   protected boolean isCodedComp(long a)  { return (a&MASK_COMP)!=0L; }
   protected long codeComp(long a)  { return a|MASK_COMP; }
   protected long decodeComp(long a) { return a&UNMASK_COMP; }

   /** Compression of a range list based on coded singletons by storing only start index with 62e bit forced to 1
    * @param range to be compressed
    * @param unit size of the range unit
    * @return array of long with the exact required size after compression
    */
   protected long [] compressRange( Range range, long unit ) {
      int j=0;
      
      // Final size ?
      for( int i=0; i<range.sz; i+=2) {
         if( range.r[i]+unit==range.r[i+1] ) j++;
         else j+=2;
      }
      long [] r = new long[ j ];
      if( j==range.sz ) System.arraycopy(range.r, 0, r, 0, j);
      else {
         j=0;
         for( int i=0; i<range.sz; i+=2) {
            if( range.r[i]+unit==range.r[i+1] ) r[j++] = codeComp(range.r[i]);
            else { 
               r[j++]=range.r[i];
               r[j++]=range.r[i+1];
            }
         }
      }
      return r;
   }

   /** Uncompression of an array of ranges based on coded singletons by storing only start index with 62e bit forced to 1 */
   protected Range uncompressRange( long [] r, long unit) { return uncompressRange(r,r.length,unit); }
   protected Range uncompressRange( long [] r, int sz, long unit) {
      Range range = new Range(sz);
      for( int i=0; i<sz; i++ ) {
         if( isCodedComp(r[i]) ) {
            long start = decodeComp( r[i] );
            range.push(start);
            range.push(start +unit);
         } else range.push(r[i]);
      }
      range.trimSize();
      return range;
   }

   
   /******************************************** ASCII writers  ***************************************************************/

   // ASCII paging (number of tokens and/or characters per line
   static protected final int MAXWORD=20;
   static protected final int MAXSIZE=80;
   
   /** Return Moc ASCII string */
   public String toASCII() throws Exception {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      writeASCII(out);
      return out.toString();
   }

   /** Return Moc JSON string (non IVOA standard) */
   public String toJSON() throws Exception {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      writeJSON(out);
      return out.toString();
   }
   
   /**
    * Internal method: Write ASCII Moc (1D) in an output stream
    * @param out output stream
    * @param moc Moc to write
    * @param flagNL false for avoiding automatic NL
    * @param flagRange false for avoiding range expressions (ex: 32-35 => 32 33 34 35)
    * @return the highest order found 
    * @throws Exception
    */
   static protected int writeASCII(OutputStream out, Moc1D moc, boolean flagNL, boolean flagRange) throws Exception {
      if( moc.isEmpty() ) return -1;
      StringBuilder res= new StringBuilder(50000);
      int order=-1;
      int sizeLine=0;
      int j=0;
      
      Iterator<MocCell> it = moc.cellIterator( flagRange );
      while( it.hasNext() ) {
         MocCell cell = it.next();
         
         StringBuilder s = new StringBuilder(100);
         
         // Next order ?
         boolean flagNewOrder = cell.order!=order;
         if( flagNewOrder ) { s.append(cell.order+"/"); order=cell.order; }
         s.append(cell.start+"");
         
         // Range ?
//         if( cell.end>cell.start+1 ) s.append( (cell.end-2==cell.start ? " ": "-") + (cell.end-1) ); 
         if( cell.end>cell.start+1 ) s.append( "-" + (cell.end-1) );  // PF: wee add '-' even between to consecutive cell
         
         // New line and a possible flush ?
         if( res.length()>0 ) {
            if( flagNewOrder ) {
               if( flagNL ) { res.append(CR); sizeLine=0; j++; }
               else res.append(" ");
            } else {
               if( flagNL && s.length()+sizeLine>MAXSIZE ) { res.append(CR+" "); sizeLine=1; j++; }
               else { res.append(' '); sizeLine++; }
// PF: Alternative with comma separator rather than space separator
//               if( flagNL && s.length()+sizeLine>MAXSIZE ) { res.append(CR+", "); sizeLine=1; j++; }
//               else { res.append(','); sizeLine++; }
            }
            if( j>15) { writeASCIIFlush(out,res,false); j=0; }
         }

         res.append(s);
         sizeLine+=s.length();
      }

      // Dernier flush
      writeASCIIFlush(out,res,false);

      return order;
   }
   
   /**
    * Internal method: Flush the StringBuilder in the output stream. At the end, the StringBuilder is clear to be reused
    * @param out The output Stream
    * @param s the stringBuilder to flush
    * @param nl true if a NL is inserted before
    * @throws Exception
    */
   static protected void writeASCIIFlush(OutputStream out, StringBuilder s,boolean nl) throws Exception {
      if( nl ) s.append(CR);
      out.write(s.toString().getBytes());
      s.delete(0,s.length());
   }
   
   /******************************************** FITS writers  ***************************************************************/
   
   /** Return the number of bytes used for coding each FITS value (4 for integer, 8 for long) */
   public abstract int sizeOfCoding();
   
   /** Return the number of values to write in FITS serialization */
   public abstract int getNbCoding();
   
   /** Write the primary FITS HDU */
   private void writeHeader0(OutputStream out) throws Exception {
      int n=0;
      out.write( getFitsLine("SIMPLE","T","Written by MOC java API "+VERSION) ); n+=80;
      out.write( getFitsLine("BITPIX","8") ); n+=80;
      out.write( getFitsLine("NAXIS","0") );  n+=80;
      out.write( getFitsLine("EXTEND","T") ); n+=80;
      out.write( getEndBourrage(n) );
   }
   
   /** Standardized MOC FITS keyword list (IVOA MOC 2.0) */
   private String [] FITSKEY_RESERVED = { 
         "SIMPLE","BITPIX","NAXIS","EXTEND","XTENSION","NAXIS1","NAXIS2","PCOUNT","GCOUNT","TFIELDS","TFORM1" };
   
   private String [] MOCKEY_RESERVED = { 
         "MOCVERS","MOCDIM","ORDERING","COORDSYS","TIMESYS","MOCORDER", "MOCORD_S","MOCORD_T","MOCTOOL" };
  
   /** Return true if k is a reserved FITS keywords (tables keywords) */
   private boolean isReservedFitsKeyWords(String k) {
      for( String s : FITSKEY_RESERVED ) if( s.equalsIgnoreCase(k) ) return true;
      return false;
   }

   /** Return true if k is a reserved FITS keywords (MOC keywords) */
   private boolean isReservedMocKeyWords(String k) {
      for( String s : MOCKEY_RESERVED ) if( s.equalsIgnoreCase(k) ) return true;
      return false;
   }

   /** Write the second FITS HDU */
   private void writeHeader1(OutputStream out) throws Exception {
      int n=0;
      int nbytes = sizeOfCoding();
      int naxis2 = getNbCoding();
      out.write( getFitsLine("XTENSION","BINTABLE","Multi Order Coverage map") ); n+=80;
      out.write( getFitsLine("BITPIX","8") ); n+=80;
      out.write( getFitsLine("NAXIS","2") );  n+=80;
      out.write( getFitsLine("NAXIS1",nbytes+"") );  n+=80;
      out.write( getFitsLine("NAXIS2",""+naxis2 ) );  n+=80;
      out.write( getFitsLine("PCOUNT","0") ); n+=80;
      out.write( getFitsLine("GCOUNT","1") ); n+=80;
      out.write( getFitsLine("TFIELDS","1") ); n+=80;
      out.write( getFitsLine("TFORM1",nbytes==4 ? "1J" : "1K") ); n+=80;
      
      out.write( getFitsLine("MOCVERS","2.0","MOC version",true) );    n+=80;      
      n+=writeSpecificFitsProp( out );
      out.write( getFitsLine("MOCTOOL","CDSjavaAPI-"+SMoc.VERSION,"Name of the MOC generator") );    n+=80;      

      
      // Write properties
      for( String key : getPropertyKeys() ) {
         if( isReservedFitsKeyWords(key) ) continue;
         if( isReservedMocKeyWords(key) ) continue;
         String value = getProperty(key);
         String comment = getComment(key);
         out.write( getFitsLine(key,value,comment) );
         n+=80;
      }
      
      out.write( getEndBourrage(n) );
   }
   
   /** Write specifical properties (depends of the Moc dimension:l SMOC, TMOC, STMOC...) */
   protected abstract int writeSpecificFitsProp( OutputStream out) throws Exception;

   /** Write data FITS section */
   protected void writeData(OutputStream out) throws Exception {
      int size = writeSpecificData( out);
      out.write( getBourrage(size) );
   }
   
   /** Write data (depends of the Moc dimension:l SMOC, TMOC, STMOC...) */
   protected int writeSpecificData( OutputStream out) throws Exception { return writeSpecificDataRange(out,RAW); }

   /** Write Moc data in Ranges
    * @param out output stream
    * @param mode RAW or COMP_SINGLETON
    * @return number of bytes written
    */
   public abstract int writeSpecificDataRange( OutputStream out,int mode) throws Exception;

   /** Write a val (int or long) in the outputstream out, using the buffer buf. The size of the buf determines int or long
    * @param out output stream
    * @param val value to write 
    * @param buf buffer to use
    * @return the number of bytes written
    * @throws Exception
    */
   static protected int writeVal(OutputStream out,long val,byte []buf) throws Exception {
      for( int j=0,shift=(buf.length-1)*8; j<buf.length; j++, shift-=8 ) buf[j] = (byte)( 0xFF & (val>>shift) );
      if( out!=null ) out.write( buf );
      return buf.length;
   }
   
   /** Convert 8 consecutive bytes as long (starting at the index i)
    * @param t array
    * @param i offset
    * @return long decoded
    */
   static protected long readLong(byte t[], int i) {
      int a = ((  t[i])<<24) | (((t[i+1])&0xFF)<<16) | (((t[i+2])&0xFF)<<8) | (t[i+3])&0xFF;
      int b = ((t[i+4])<<24) | (((t[i+5])&0xFF)<<16) | (((t[i+6])&0xFF)<<8) | (t[i+7])&0xFF;
      long val = (((long)a)<<32) | (b & 0xFFFFFFFFL);
      return val;
   }

   /** Code a couple (order,npix) into a unique long integer
    * @param order HEALPix order
    * @param npix HEALPix number
    * @return Uniq long ordering
    */
   static public long hpix2uniq(int order, long npix) {
      long nside = pow2(order);
      return 4*nside*nside + npix;
   }

   /** Uncode a long integer into a couple (order,npix)
    * @param uniq Uniq long ordering
    * @return HEALPix order,number
    */
   static public long [] uniq2hpix(long uniq) {
      return uniq2hpix(uniq,null);
   }

   /** Uncode a long integer into a couple (order,npix)
    * @param uniq Uniq long ordering
    * @param hpix null for reallocating target couple
    * @return HEALPix order,number
    */
   static public long [] uniq2hpix(long uniq,long [] hpix) {
      if( hpix==null ) hpix = new long[2];
      hpix[0] = log2(uniq/4)/2;
      long nside = pow2(hpix[0]);
      hpix[1] = uniq - 4*nside*nside;
      return hpix;
   }

   static public final long pow2(long order){ return 1L<<order;}
   static public final long log2(long nside){ int i=0; while((nside>>>(++i))>0); return --i; }


   /****************** Utilitaire Fits **************************/

   /** Generate FITS 80 character line => see getFitsLine(String key, String value, String comment) */
   private byte [] getFitsLine(String key, String value) {
      return getFitsLine(key,value,null);
   }

   /**
    * Generate FITS 80 character line.
    * @param key The FITS key
    * @param value The associated FITS value (can be numeric, string (quoted or not)
    * @param comment The commend, or null
    * @return the 80 character FITS line
    */
   static protected byte [] getFitsLine(String key, String value, String comment) { return getFitsLine(key,value,comment,false); }
   static protected byte [] getFitsLine(String key, String value, String comment, boolean forceStringMode) {
      int i=0,j;
      char [] a;
      byte [] b = new byte[80];

      // The keyword
      a = key.toCharArray();
      for( j=0; i<8; j++,i++) b[i]=(byte)( (j<a.length)?a[j]:' ' );

      // The associated value
      if( value!=null ) {
         b[i++]=(byte)'='; b[i++]=(byte)' ';

         a = value.toCharArray();

         // Numeric value => right align
         if( !forceStringMode && !isFitsString(value) ) {
            for( j=0; j<20-a.length; j++)  b[i++]=(byte)' ';
            for( j=0; i<80 && j<a.length; j++,i++) b[i]=(byte)a[j];

            // string => format
         } else {
            a = formatFitsString(a);
            for( j=0; i<80 && j<a.length; j++,i++) b[i]=(byte)a[j];
            while( i<30 ) b[i++]=(byte)' ';
         }
      }

      // The comment
      if( comment!=null && comment.length()>0 ) {
         if( value!=null ) { b[i++]=(byte)' ';b[i++]=(byte)'/'; b[i++]=(byte)' '; }
         a = comment.toCharArray();
         for( j=0; i<80 && j<a.length; j++,i++) b[i]=(byte) a[j];
      }

      // Bourrage
      while( i<80 ) b[i++]=(byte)' ';

      return b;
   }

   /** Generate the end of a FITS block assuming a current block size of headSize bytes
    * => insert the last END keyword */
   private byte [] getEndBourrage(int headSize) {
      int size = 2880 - headSize%2880;
      if( size<3 ) size+=2880;
      byte [] b = new byte[size];
      b[0]=(byte)'E'; b[1]=(byte)'N';b[2]=(byte)'D';
      for( int i=3; i<b.length; i++ ) b[i]=(byte)' ';
      return b;
   }

   /** Generate the end of a FITS block assuming a current block size of headSize bytes */
   static protected byte [] getBourrage(int currentPos) {
      int size = 2880 - currentPos%2880;
      byte [] b = new byte[size];
      return b;
   }

   /** Fully read buf.length bytes from in input stream */
   static public void readFully(InputStream in, byte buf[]) throws IOException {
      readFully(in,buf,0,buf.length);
   }

   /** Fully read len bytes from in input stream and store the result in buf[]
    * from offset position. */
   static public void readFully(InputStream in,byte buf[],int offset, int len) throws IOException {
      int m;
      for( int n=0; n<len; n+=m ) {
         m = in.read(buf,offset+n,(len-n)<512 ? len-n : 512);
         if( m==-1 ) throw new EOFException();
      }
   }

   /**
    * Test si c'est une chaine  la FITS (ni numrique, ni boolen)
    * @param s la chaine  tester
    * @return true si s est une chaine ni numrique, ni boolenne
    * ATTENTION: NE PREND PAS EN COMPTE LES NOMBRES IMAGINAIRES
    */
   static private boolean isFitsString(String s) {
      if( s.length()==0 ) return true;
      char c = s.charAt(0);
      if( s.length()==1 && (c=='T' || c=='F') ) return false;   // boolean
      if( !Character.isDigit(c) && c!='.' && c!='-' && c!='+' && c!='E' && c!='e' ) return true;
      try {
         Double.valueOf(s);
         return false;
      } catch( Exception e ) { return true; }
   }

   static private char [] formatFitsString(char [] a) {
      if( a.length==0 ) return a;
      StringBuffer s = new StringBuffer();
      int i;
      boolean flagQuote = a[0]=='\''; // Chaine dj quote ?

      s.append('\'');

      // recopie sans les quotes
      for( i= flagQuote ? 1:0; i<a.length- (flagQuote ? 1:0); i++ ) {
         if( !flagQuote && a[i]=='\'' ) s.append('\'');  // Double quotage
         s.append(a[i]);
      }

      // bourrage de blanc si <8 caractres + 1re quote
      for( ; i< (flagQuote ? 9:8); i++ ) s.append(' ');

      // ajout de la dernire quote
      s.append('\'');

      return s.toString().toCharArray();
   }


   /** Manage Header Fits */
   class HeaderFits implements Iterable<String>{

      private LinkedHashMap<String,String> header;     // List of header key/value
      private int sizeHeader=0;                    // Header size in bytes
      
      /** Pick up FITS value from a 80 character array
       * @param buffer line buffer
       * @return Parsed FITS value
       */
      private String getValue(byte [] buffer) {
         int i;
         boolean quote = false;
         boolean blanc=true;
         int offset = 9;

         for( i=offset ; i<80; i++ ) {
            if( !quote ) {
               if( buffer[i]==(byte)'/' ) break;   // on the comment
            } else {
               if( buffer[i]==(byte)'\'') break;   // on the next quote
            }

            if( blanc ) {
               if( buffer[i]!=(byte)' ' ) blanc=false;
               if( buffer[i]==(byte)'\'' ) { quote=true; offset=i+1; }
            }
         }
         return (new String(buffer, 0, offset, i-offset)).trim();
      }

      /** Pick up FITS key from a 80 character array
       * @param buffer line buffer
       * @return Parsed key value
       */
      private String getKey(byte [] buffer) {
         return new String(buffer, 0, 0, 8).trim();
      }

      /** Parse FITS header from a stream until next 2880 FITS block after the END key.
       * Memorize FITS key/value couples
       * @param dis input stream
       */
      private void readHeader(InputStream dis) throws Exception {
         int blocksize = 2880;
         int fieldsize = 80;
         String key, value;
         int linesRead = 0;
         sizeHeader=0;

         header = new LinkedHashMap<>(200);
         byte[] buffer = new byte[fieldsize];

         while (true) {
            readFully(dis,buffer);

            key =  getKey(buffer);
            if( linesRead==0 && !key.equals("SIMPLE") && !key.equals("XTENSION") ) throw new Exception("Not a MOC FITS format");
            sizeHeader+=fieldsize;
            linesRead++;
            if( key.equals("END" ) ) break;
            if( buffer[8] != '=' ) continue;
            value=getValue(buffer);
            header.put(key, value);
         }

         // Skip end of last block
         int bourrage = blocksize - sizeHeader%blocksize;
         if( bourrage!=blocksize ) {
            byte [] tmp = new byte[bourrage];
            readFully(dis,tmp);
            sizeHeader+=bourrage;
         }
      }

      /**
       * Provide integer value associated to a FITS key
       * @param key FITs key (with or without trailing blanks)
       * @return corresponding integer value
       */
      public int getIntFromHeader(String key) throws NumberFormatException,NullPointerException {
         String s = header.get(key.trim());
         return (int)Double.parseDouble(s.trim());
      }

      /**
       * Provide string value associated to a FITS key
       * @param key FITs key (with or without trailing blanks)
       * @return corresponding string value without quotes (')
       */
      public String getStringFromHeader(String key) throws NullPointerException {
         String s = header.get(key.trim());
         if( s==null || s.length()==0 ) return s;
         if( s.charAt(0)=='\'' ) return s.substring(1,s.length()-1).trim();
         return s;
      }
      
      public Iterator<String> iterator() {
         return header.keySet().iterator();
      }
   }
}