001 package edu.nrao.sss.model.source.parser; 002 003 import java.io.IOException; 004 import java.io.LineNumberReader; 005 import java.io.Reader; 006 import java.util.ArrayList; 007 import java.util.List; 008 009 import edu.nrao.sss.model.source.Source; 010 import edu.nrao.sss.model.source.SourceCatalog; 011 import edu.nrao.sss.util.StringUtil; 012 013 /** 014 * A reader of source catalog text files in VLBA format. 015 * <p> 016 * <b><u>Format</u></b> (starting 2007-11-24 & tweaked 2008-06-19) 017 * <pre> 018 * Field 1:10 A10 IAU name (J2000) 019 * Field 13:20 A8 IVS name (B1950) 020 * Field 23:24 I2 Right ascension (hr) 021 * Field 26:27 I2 Right ascension (min) 022 * Field 29:37 F9.6 Right ascension (sec) 023 * Field 40:42 I3 Declination (deg) 024 * Field 44:45 I3 Declination (min) 025 * Field 47:54 F8.5 Declination (sec) 026 * Field 57:61 F5.2 Inflated E/W position error (mas) 027 * Field 63-67 F5.2 Inflated N/S position error (mas) 028 * Field 69-73 F5.2 X-band total flux density integrated over entire map (Jy) or -1.00 if no data are available 029 * Field 75-79 F5.2 X-band unresolved flux density at long VLBA baselines (Jy) or -1.00 if no data are available 030 * Field 81-85 F5.2 S-band total flux density integrated over entire map (Jy) or -1.00 if no data are available 031 * Field 87:91 F5.2 S-band unresolved flux density at long VLBA baselines (Jy) or -1.00 if no data are available 032 * 033 * Missing value: -1.0 (often below detection level) 034 * 035 * Field 94:94 A1 Category: C (calibrator), N (non-calibrator), U (unreliable coordinates) 036 * C=acceptable calibrator to be used for most VLBA observations; 037 * N=Non-calibrator that may be too weak or resolved and should be tested before use; 038 * U=Non-calibrator with poor position determination. 039 * K=Acceptable at 23 GHz. 040 * Field 96:99 A4 Original catalogue name: 041 * VCS1,2,3,4,5: Original VCS surveys 042 * ICRF: ICRF observers 043 * 2007: Contains VCS6, polar cap survey 044 * 2008: Latest additions, mostly in south 045 * VERA: Sources from VERA/VLBA galactic plane surveys 046 * </pre> 047 * <b><u>Example</u></b> 048 * <pre> 049 * J0000+8141 2355+814 00h00m17.266431s +81d41'36.52552" 57.33 19.57 -1.00 -1.00 -1.00 -1.00 U-2007 050 * J0000-3221 2357-326 00h00m20.399946s -32d21'01.23315" 0.52 1.14 0.21 0.09 0.50 0.34 C-VCS2 051 * J0000+4054 2358+406 00h00m53.081556s +40d54'01.79452" 1.80 2.19 0.25 0.05 1.00 0.12 C-VCS1 052 * J0001-1551 2358-161 00h01m05.328768s -15d51'07.07599" 0.51 0.96 0.28 0.12 0.43 0.25 C-VCS1 053 * </pre> 054 * <p> 055 * <b><u>Format</u></b> (prior to 2007-11-24) 056 * <pre> 057 * Field 1:1 A1 Category: C (calibrator), N (non-calibrator), U (unreliable coordinates) 058 * K (possible VERA 22 GHz calibrator) 059 * Field 4:11 A8 IVS name (B1950) 060 * Field 13:22 A10 IAU name (J2000.0) 061 * Field 25:26 I2 Right ascension: hours 062 * Field 28:29 I2 Right ascension: minutes 063 * Field 31:39 F9.6 Right ascension: seconds 064 * Field 41:43 I3 Declination: degrees 065 * Field 45:46 I3 Declination: minutes 066 * Field 48:57 F8.5 Declination: seconds 067 * Field 58:63 F6.2 Inflated error in right ascension in mas 068 * Field 65:70 F6.2 Inflated error in declination in mas 069 * Field 73:78 F6.3 Correlation between right ascension and declination 070 * Field 80:85 I6 Number of observations used 071 * Field 88:88 A1 Blank or < or - for X-band total flux density integrated over entire map 072 * Field 89:92 F4.2 X-band total flux density integrated over entire map, Jy or n/a if no data are available 073 * Field 94:94 A1 Blank or < or - for X-band unresolved flux density at VLBA baselines, Jy 074 * Field 95:98 F4.2 X-band unresolved flux density at long VLBA baselines, Jy or n/a if no data are available 075 * Field 101:101 A1 Blank or < or - for S-band total flux density integrated over entire map 076 * Field 102:105 F4.2 S-band total flux density integrated over entire map, Jy or n/a if no data are available 077 * Field 107:107 A1 Blank or < or - for S-band unresolved flux density at VLBA baselines 078 * Field 108:111 F4.2 S-band unresolved flux density at long VLBA baselines, Jy or n/a if no data are available 079 * Field 114:116 A3 Used Band (type_: X, S, X/S. K 080 * Field 119:122 A4 Catalogue name 081 * Field 124:124 A1 Modification code: a=added in this version 082 * r=remove from this version 083 * m=modified in this version 084 * Field 125:134 A8 Date of change (YYYY.MM.DD)</pre> 085 * <b><u>Example</u></b> 086 * <pre> 087 * C 2357-326 J0000-3221 00 00 20.399949 -32 21 01.23320 0.60 1.11 0.028 40 0.14 0.08 0.48 0.35 X/S VCS2 088 * C 2358+406 J0000+4054 00 00 53.081537 +40 54 01.79341 2.36 2.13 -0.162 22 0.25 0.05 1.00 0.12 X/S VCS1 089 * C 2358-161 J0001-1551 00 01 05.328763 -15 51 07.07576 0.51 0.92 -0.757 58 0.28 0.12 0.43 0.25 X/S VCS1 090 * C 1038+52B J1041+523B 10 41 48.897635 +52 33 55.60815 0.79 0.57 -0.355 84 -1.00 -1.00 -1.00 -1.00 X/S ICRF r2007.06.14 091 * </pre> 092 * <p> 093 * <b>Version Info:</b> 094 * <table style="margin-left:2em"> 095 * <tr><td>$Revision: 1552 $</td></tr> 096 * <tr><td>$Date: 2008-09-15 16:16:57 -0600 (Mon, 15 Sep 2008) $</td></tr> 097 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 098 * </table></p> 099 * 100 * @author David M. Harland 101 * @since 2007-06-15 102 */ 103 public class VlbaSourceCatalogReader 104 extends AbstractSourceCatalogReader 105 { 106 private VlbaSourceReader sourceReader; 107 private LineNumberReader dataReader; 108 109 private List<Source> addedSources; 110 private List<Source> changedSources; 111 private List<Source> removedSources; 112 113 /** Creates a new instance. */ 114 public VlbaSourceCatalogReader() 115 { 116 sourceReader = new VlbaSourceReader(); 117 118 addedSources = new ArrayList<Source>(); 119 changedSources = new ArrayList<Source>(); 120 removedSources = new ArrayList<Source>(); 121 } 122 123 /** Prepares this instance to read from a new source. */ 124 private void reset(Reader in, SourceCatalog destination) 125 { 126 catalog = (destination == null) ? new SourceCatalog() : destination; 127 128 readWasSuccessful = false; 129 errors.clear(); 130 131 dataReader = new LineNumberReader(in); 132 133 addedSources.clear(); 134 changedSources.clear(); 135 removedSources.clear(); 136 } 137 138 /** 139 * NOTE: This method closes the incoming Reader! 140 * 141 * @see AbstractSourceCatalogReader#read(java.io.Reader, 142 * edu.nrao.sss.model.source.SourceCatalog) 143 */ 144 public boolean read(Reader in, SourceCatalog destination) 145 { 146 return read(in, destination, "1900.01.01"); 147 } 148 149 /** 150 * Reads data from {@code in} and uses it to add sources to 151 * {@code destination}. 152 * <p> 153 * Note that <i>all</i> sources in the reader are placed in the 154 * destination catalog, no matter what the modification code and 155 * date of change values are. Some clients may want to work only 156 * with those sources with modification codes. In that situation 157 * the following list methods may be used: 158 * {@link #getAddedSources()}, {@link #getModifiedSources()}, and 159 * {@link #getRemovedSources()}.</p> 160 * 161 * @param in 162 * the source of text that can be read and turned into 163 * {@code Source} objects. This method closes this 164 * reader when it has finished with it. 165 * 166 * @param destination 167 * the catalog to which the sources should be added. If this 168 * parameter is <i>null</i>, a new catalog will be created. 169 * 170 * @param earliestDate 171 * text representation of a date in form <i>yyyymmdd</i>. 172 * A source with a modification code and an effective date on or 173 * after this date will be remembered and can be accessed via the 174 * {@link #getAddedSources()}, {@link #getModifiedSources()}, and 175 * {@link #getRemovedSources()} methods. 176 * 177 * @return <i>true</i> if nothing unexpected occurred while reading data. 178 */ 179 public boolean read(Reader in, SourceCatalog destination, String earliestDate) 180 { 181 reset(in, destination); 182 183 String line = readHeaderComments(); 184 185 while (line != null) 186 { 187 if (sourceReader.textCouldBeData(line)) 188 { 189 if (!sourceReader.read(line, dataReader.getLineNumber(), histRecPrefix)) 190 errors.addAll(sourceReader.errors); 191 192 if (sourceReader.source != null) 193 { 194 if (sourceInfoOrigin != null) 195 sourceReader.source.setOriginOfInformation(sourceInfoOrigin); 196 197 catalog.addItem(sourceReader.source); 198 199 //Make note of additions, changes, and deletions 200 if (sourceReader.textFields.get(VlbaSourceReader.MOD_DATE).value 201 .compareToIgnoreCase(earliestDate) >= 0) 202 { 203 char modCode = 204 Character.toLowerCase(sourceReader.textFields.get( 205 VlbaSourceReader.MOD_CODE).value.charAt(0)); 206 String notePrefix = null; 207 switch (modCode) 208 { 209 case 'a': 210 notePrefix = "Added to catalog on "; 211 addedSources.add(sourceReader.source); 212 break; 213 214 case 'm': 215 notePrefix = "Modified on "; 216 changedSources.add(sourceReader.source); 217 break; 218 219 case 'r': 220 notePrefix = "Removed from catalog on "; 221 removedSources.add(sourceReader.source); 222 break; 223 224 default: 225 putError("Unrecognized modification code '" + modCode + 226 ". Expected 'a', 'm', or 'r'."); 227 break; 228 } 229 230 sourceReader.source.getNotes().add(notePrefix + 231 sourceReader.textFields.get(VlbaSourceReader.MOD_DATE).value); 232 } 233 } 234 } 235 //Would handle comments that are embedded in data here 236 //else 237 //{ 238 //} 239 240 line = getNextLine(); 241 } 242 243 try 244 { 245 this.dataReader.close(); 246 } 247 catch(IOException ioe) 248 { 249 putError(0, "Error closing Reader: " + ioe.getMessage()); 250 } 251 252 readWasSuccessful = getErrorCount() == 0; 253 254 return readWasSuccessful; 255 } 256 257 /** Turns the header comments into notes on the catalog. */ 258 private String readHeaderComments() 259 { 260 final String EOL = StringUtil.EOL; 261 final int PREFIX_LEN = VlbaSourceReader.COMMENT_PREFIX.length(); 262 263 int blankLineCount = 0; 264 265 StringBuilder buff = new StringBuilder(); 266 267 String line = getNextLine(); 268 269 while (line != null) 270 { 271 if (sourceReader.textCouldBeData(line)) 272 break; 273 274 line = line.trim(); 275 line = line.substring(PREFIX_LEN); 276 277 if (sourceReader.lineIsEmpty(line)) 278 { 279 blankLineCount++; 280 281 //2+ blank lines interpreted as start of new note 282 if (blankLineCount == 2) 283 { 284 catalog.getNotes().add(buff.toString()); 285 buff = new StringBuilder(); 286 } 287 } 288 else //text on this line 289 { 290 //Preserve single blank lines 291 if (blankLineCount == 1) 292 buff.append(EOL); 293 294 buff.append(line).append(EOL); 295 blankLineCount = 0; 296 } 297 298 line = getNextLine(); 299 } 300 301 //The final note 302 if (buff.length() > 0) 303 catalog.getNotes().add(buff.toString()); 304 305 return line; 306 } 307 308 /** 309 * Returns a list of sources that were marked with the "add" modification 310 * code and whose effective dates were on or after that sent to the read 311 * method. The returned list is guaranteed not to be <i>null</i>, but it 312 * may be empty. 313 */ 314 public List<Source> getAddedSources() 315 { 316 return new ArrayList<Source>(addedSources); 317 } 318 319 /** 320 * Returns a list of sources that were marked with the "modified" modification 321 * code and whose effective dates were on or after that sent to the read 322 * method. The returned list is guaranteed not to be <i>null</i>, but it 323 * may be empty. 324 */ 325 public List<Source> getModifiedSources() 326 { 327 return new ArrayList<Source>(changedSources); 328 } 329 330 /** 331 * Returns a list of sources that were marked with the "removed" modification 332 * code and whose effective dates were on or after that sent to the read 333 * method. The returned list is guaranteed not to be <i>null</i>, but it 334 * may be empty. 335 */ 336 public List<Source> getRemovedSources() 337 { 338 return new ArrayList<Source>(removedSources); 339 } 340 341 //============================================================================ 342 // 343 //============================================================================ 344 345 /** Reads lines, returning first one that does not cause an error. */ 346 private String getNextLine() 347 { 348 int attempt = 0; 349 350 while (++attempt <= 100) 351 { 352 try { 353 return dataReader.readLine(); 354 } 355 catch (IOException ex) { 356 putError("I/O Exception: " + ex.getMessage()); 357 } 358 } 359 360 throw new RuntimeException("Failed " + attempt + 361 " times while attempting to read a line."); 362 } 363 364 /** Adds a new parsing error to our list. */ 365 private void putError(String message) 366 { 367 putError(dataReader.getLineNumber(), message); 368 } 369 }