001 package edu.nrao.sss.astronomy; 002 003 import java.io.BufferedReader; 004 import java.io.IOException; 005 import java.io.InputStreamReader; 006 import java.io.UnsupportedEncodingException; 007 import java.net.MalformedURLException; 008 import java.net.URL; 009 import java.net.URLEncoder; 010 import java.util.Date; 011 012 import edu.nrao.sss.geom.EarthPosition; 013 import edu.nrao.sss.measure.Latitude; 014 import edu.nrao.sss.measure.LocalSiderealTime; 015 import edu.nrao.sss.measure.Longitude; 016 017 /** 018 * A celestial coordinate system converter provided by NASA's 019 * High Energy Astrophysics Science Archive Research Center (HEASARC). 020 * <p> 021 * This converter sends a CGI query to HEASARC and parses the response. 022 * Coordinate converter web addresses: 023 * <a href="http://heasarc.gsfc.nasa.gov/cgi-bin/Tools/convcoord/convcoord.pl"> 024 * CGI</a>, 025 * <a href="http://heasarc.gsfc.nasa.gov/Tools/convcoord_help.html">Help</a>.</p> 026 * <p> 027 * <b>Version Info:</b> 028 * <table style="margin-left:2em"> 029 * <tr><td>$Revision: 1137 $</td></tr> 030 * <tr><td>$Date: 2008-02-29 16:06:15 -0700 (Fri, 29 Feb 2008) $</td></tr> 031 * <tr><td>$Author: dharland $</td></tr> 032 * </table></p> 033 * 034 * @author David M. Harland 035 * @since 2007-04-13 036 */ 037 public class HeasarcCoordConverter 038 implements CelestialCoordinateConverter 039 { 040 private static final String CGI_SCRIPT = 041 "http://heasarc.gsfc.nasa.gov/cgi-bin/Tools/convcoord/convcoord.pl"; 042 043 private static final String CONST_PARAMS = 044 "?Resolver=GRB%2FSIMBAD%2FNED&NoCache=on&Epoch=&Output=Batch"; 045 046 private static final String QUERY_BASE = CGI_SCRIPT + CONST_PARAMS; 047 048 private static final char PARAM_DELIM = '&'; 049 050 private static final char POS_DELIM = ','; 051 052 private static final String ENCODING = "UTF-8"; 053 054 /** 055 * Returns a new position that is equivalent to {@code position}, 056 * but in the given coordinate system and epoch. 057 * <p> 058 * <b>Note:</b> the success of this method depends on an active internet 059 * connection.</p> 060 * 061 * @param position 062 * the position to be converted. 063 * 064 * @param toSystem 065 * the coordinate system for the returned position. 066 * 067 * @param toEpoch 068 * the epoch for the returned position. 069 * 070 * @param observer 071 * the location of the the observer; not used by this converter. 072 * (This converter cannot handle the <tt>HORIZONTAL</tt> coordinate system.) 073 * 074 * @param lst 075 * the local sidereal time at the observer's location. 076 * Used for getting the coordinates of <tt>position</tt> at this time. 077 * If this value is <i>null</i>, those coordinates will be calculated 078 * as of the current time on the system clock. 079 * 080 * @return a new position that is equivalent to {@code thisPosition}, 081 * but is expressed in a coordinate system of {@code toSystem} 082 * for epoch {@code toEpoch}. 083 * 084 * @throws CoordinateConversionException if anything goes wrong during 085 * conversion. The exception thrown will have a non-null 086 * cause. This is a list of the types of causes and the most 087 * common explanation for each: 088 * <dl> 089 * <dt>UnsupportedEncodingException</dt> 090 * <dd>This cause is very unlikely. An exception of this type 091 * would arise if for some reason the information in 092 * {@code thisPosition} could not be converted into 093 * UTF-8</dd> 094 * <dt>MalformedURLException</dt> 095 * <dd>Thrown if we build the query string improperly or if 096 * HEASARC moves or eliminates its service.</dd> 097 * <dt>IOException</dt> 098 * <dd>Thrown if we have problems opening, reading, or 099 * closing the results of the query.</dd> 100 * <dt>IllegalArgumentException</dt> 101 * <dd>There are a couple of situations that lead to this 102 * type of cause. The most common is if our latitude 103 * and/or longitude classes could not parse the query 104 * results. The next most common would be calling this 105 * method with a {@code toSystem} and/or {@code toEpoch} 106 * value for which this method was not prepared.</dd> 107 * <dt>NullPointerException</dt> 108 * <dd>The most common situation that leads to this type of 109 * cause is if one or more of the parameters is 110 * <i>null</i>.</dd> 111 * </dl> 112 */ 113 public SkyPosition createFrom(SkyPosition position, 114 CelestialCoordinateSystem toSystem, 115 Epoch toEpoch, 116 EarthPosition observer, 117 LocalSiderealTime lst) 118 throws CoordinateConversionException 119 { 120 SkyPosition newPosition = null; 121 122 String queryString = null; //Used in exception messages 123 124 CelestialCoordinateSystem fromSystem = position.getCoordinateSystem(); 125 Epoch fromEpoch = position.getEpoch(); 126 127 try 128 { 129 //No need to convert 130 if (toSystem.equals(fromSystem) && toEpoch.equals(fromEpoch)) 131 { 132 newPosition = SimpleSkyPosition.copy(position, lst.toDate()); 133 } 134 //Call NASA's HEASARC query service 135 else 136 { 137 checkToFrom(fromSystem, fromEpoch); 138 139 queryString = buildQuery(position, lst); 140 141 URL heasarc = new URL(queryString); 142 143 BufferedReader page = 144 new BufferedReader(new InputStreamReader(heasarc.openStream())); 145 146 //First line returned has sexagesimal, which we won't use 147 page.readLine(); 148 149 //Second line has decimal positions, which we'll use 150 newPosition = makePosition(page.readLine(), toSystem, toEpoch); 151 152 page.close(); 153 } 154 } 155 catch (UnsupportedEncodingException uee) 156 { 157 throw new CoordinateConversionException( 158 "Problem encoding position data for use in URL.", uee); 159 } 160 catch (MalformedURLException mue) 161 { 162 throw new CoordinateConversionException( 163 "Problem with this URL: " + queryString, mue); 164 } 165 catch (IOException ioe) 166 { 167 throw new CoordinateConversionException( 168 "Problem opening, reading, or closing query results.", ioe); 169 } 170 catch (IllegalArgumentException iae) 171 { 172 throw new CoordinateConversionException( 173 "Problem creating position from query results.", iae); 174 } 175 catch (NullPointerException npe) 176 { 177 if (position == null || toSystem == null || toEpoch == null) 178 { 179 throw new CoordinateConversionException( 180 "Make sure all parameters are non-null.", npe); 181 } 182 else 183 { 184 throw new CoordinateConversionException("Unexpected NPE.", npe); 185 } 186 } 187 188 return newPosition; 189 } 190 191 /** 192 * Returns a string that represents a query URI. 193 * The parameters of the query are based on the given sky position. 194 */ 195 private String buildQuery(SkyPosition pos, LocalSiderealTime lst) 196 throws UnsupportedEncodingException 197 { 198 StringBuilder query = new StringBuilder(QUERY_BASE); 199 200 //Coordinate Sytem or Epoch 201 query.append(PARAM_DELIM).append("CoordType="); 202 if (pos.getCoordinateSystem().equals(CelestialCoordinateSystem.EQUATORIAL)) 203 { 204 query.append(URLEncoder.encode(pos.getEpoch().toString(), ENCODING)); 205 } 206 else 207 { 208 query.append(URLEncoder.encode(pos.getCoordinateSystem().toString(), 209 ENCODING)); 210 } 211 212 //Longitude & Latitude 213 Date time = (lst == null) ? new Date() : lst.toDate(); 214 215 String posText = 216 new StringBuilder(pos.getLongitude(time).toStringHms()).append(POS_DELIM) 217 .append(pos.getLatitude(time).toStringDms()).toString(); 218 219 query.append(PARAM_DELIM).append("CoordVal="); 220 query.append(URLEncoder.encode(posText, ENCODING)); 221 222 return query.toString(); 223 } 224 225 /** 226 * Parses heasarcLine and returns a sky position using the portion 227 * of the line that represents the given coordinate system and epoch. 228 */ 229 private SkyPosition makePosition(String heasarcLine, 230 CelestialCoordinateSystem coordSys, 231 Epoch epoch) 232 throws IllegalArgumentException 233 { 234 checkToFrom(coordSys, epoch); 235 236 SimpleSkyPosition newPos = new SimpleSkyPosition(); 237 238 newPos.setCoordinateSystem(coordSys); 239 newPos.setEpoch(epoch); 240 241 int lonIndex = -1; 242 int latIndex = -1; 243 244 switch (coordSys) 245 { 246 case EQUATORIAL: 247 if (epoch.equals(Epoch.J2000)) 248 { 249 lonIndex = 0; 250 latIndex = 1; 251 } 252 else if (epoch.equals(Epoch.B1950)) 253 { 254 lonIndex = 2; 255 latIndex = 3; 256 } 257 break; 258 259 case GALACTIC: 260 lonIndex = 4; 261 latIndex = 5; 262 break; 263 264 case ECLIPTIC: 265 lonIndex = 6; 266 latIndex = 7; 267 break; 268 } 269 270 if (lonIndex < 0 || latIndex < 0) 271 throw new IllegalArgumentException( 272 "PROGRAMMER ERROR: makePosition is unprepared for coordSys=" + coordSys + 273 ", epoch=" + epoch + ", or both."); 274 275 String[] tokens = heasarcLine.split("\\|"); 276 277 newPos.setLongitude(Longitude.parse(tokens[lonIndex])); 278 newPos.setLatitude ( Latitude.parse(tokens[latIndex])); 279 280 return newPos; 281 } 282 283 /** Throws exception if system or epoch can't be converted. */ 284 private void checkToFrom(CelestialCoordinateSystem system, Epoch epoch) 285 { 286 switch (system) 287 { 288 case ECLIPTIC: //intentional fall-through 289 case EQUATORIAL: //intentional fall-through 290 case GALACTIC: 291 break; 292 293 default: 294 throw new IllegalArgumentException( 295 "HEASARC cannot convert to or from celestial coordinate system " + 296 system); 297 } 298 299 if (!epoch.equals(Epoch.B1950) && !epoch.equals(Epoch.J2000)) 300 throw new IllegalArgumentException( 301 "HEASARC cannot convert to or from epoch" + epoch); 302 } 303 304 //============================================================================ 305 // 306 //============================================================================ 307 /* 308 public static void main(String[] args) throws Exception 309 { 310 HeasarcCoordConverter conv = new HeasarcCoordConverter(); 311 312 SkyPosition original = new SimpleSkyPosition(); 313 SkyPosition converted = conv.createFrom(original, 314 CelestialCoordinateSystem.GALACTIC, 315 Epoch.J2000); 316 System.out.println(original); 317 System.out.println(converted); 318 } 319 */ 320 }