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();
}
}
}
|