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.text.SimpleDateFormat;
011    import java.util.Date;
012    
013    import edu.nrao.sss.measure.ArcUnits;
014    import edu.nrao.sss.measure.Latitude;
015    import edu.nrao.sss.measure.Longitude;
016    import edu.nrao.sss.util.SourceNotFoundException;
017    
018    /**
019     * IMCE's SkyBot Resolver source locator.
020     * <p>
021     * This locator sends a CGI query to IMCE
022     * (<a href="http://www.imcce.fr/imcce_en.html">Institut de Mechanique
023     * Celeste et de Calcul des Ephemerides</a>) and parses the response.
024     * The web address for the query is 
025     * <a href="http://www.imcce.fr/webservices/skybot/skybotresolver_query.php">
026     *          http://www.imcce.fr/webservices/skybot/skybotresolver_query.php</a>.
027     * </p>
028     * <p>
029     * <b>Version Info:</b>
030     * <table style="margin-left:2em">
031     *   <tr><td>$Revision: 555 $</td></tr>
032     *   <tr><td>$Date: 2007-04-24 09:05:24 -0600 (Tue, 24 Apr 2007) $</td></tr>
033     *   <tr><td>$Author: dharland $</td></tr>
034     * </table></p>
035     * 
036     * @author David M. Harland
037     * @since 2007-04-18
038     */
039    public class SkyBotResolver
040      implements SourceLocator
041    {
042      private static final String CGI_SCRIPT =
043        "http://www.imcce.fr/webservices/skybot/skybotresolver_query.php";
044      
045      private static final String CONST_PARAMS = "?-out=object&-mime=text";
046      
047      private static final String QUERY_BASE = CGI_SCRIPT + CONST_PARAMS;
048      
049      private static final char PARAM_DELIM = '&';
050      
051      private static final String ENCODING = "UTF-8";
052      
053      private static final String COMMENT_TOKEN = "#";
054      
055      private static final SimpleDateFormat DATE_FORMATTER =
056        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
057    
058      private static final int RA_POS  = 2;
059      private static final int DEC_POS = 3;
060      
061      //============================================================================
062      // 
063      //============================================================================
064    
065      /* (non-Javadoc)
066       * @see SourceLocator#findPosition(java.lang.String, java.util.Date)
067       */
068      public SkyPosition findPosition(String sourceName, Date time)
069        throws SourceNotFoundException
070      {
071        String skybotQuery = buildQuery(sourceName, time);
072          
073        BufferedReader page = getPageReader(skybotQuery);
074          
075        return getPositionFrom(page);
076      }
077      
078      /* (non-Javadoc)
079       * SourceLocator#findPosition(java.lang.String)
080       */
081      public SkyPosition findPosition(String sourceName)
082        throws SourceNotFoundException
083      {
084        String skybotQuery = buildQuery(sourceName);
085          
086        BufferedReader page = getPageReader(skybotQuery);
087          
088        return getPositionFrom(page);
089      }
090    
091      //============================================================================
092      // 
093      //============================================================================
094    
095      /** Builds a query string based on name of body and time of "now". */
096      private String buildQuery(String bodyName)
097        throws SourceNotFoundException
098      {
099        return buildQuery(bodyName, "now");
100      }
101      
102      /** Builds a query string based on name of body and time. */
103      private String buildQuery(String bodyName, Date time)
104        throws SourceNotFoundException
105      {
106        return buildQuery(bodyName, DATE_FORMATTER.format(time));
107      }
108      
109      /** Builds a query string using the two parameters. */
110      private String buildQuery(String bodyName, String dateText)
111        throws SourceNotFoundException
112      {
113        StringBuilder query = new StringBuilder(QUERY_BASE);
114        
115        try
116        {
117          //Date
118          query.append(PARAM_DELIM)
119               .append("-ep=")
120               .append(URLEncoder.encode(dateText, ENCODING));
121          
122          //Body name
123          query.append(PARAM_DELIM)
124               .append("-obj=")
125               .append(URLEncoder.encode(bodyName, ENCODING));
126        }
127        catch (UnsupportedEncodingException uee)
128        {
129          throw new SourceNotFoundException(
130            "Problem encoding date and/or name parameters for use in URL.", uee);
131        }
132        
133        return query.toString();
134      }
135    
136      /** Turns the text into a URL then into a BufferedReader. */
137      private BufferedReader getPageReader(String skybotAddress)
138        throws SourceNotFoundException
139      {
140        try
141        {
142          URL skybot = new URL(skybotAddress);
143    
144          return new BufferedReader(new InputStreamReader(skybot.openStream()));
145        }
146        catch (MalformedURLException mue)
147        {
148          throw new SourceNotFoundException("Problem with this URL: " +
149                                            skybotAddress, mue);
150        }
151        catch (IOException ioe)
152        {
153          throw new SourceNotFoundException("Problem opening query results.", ioe);
154        }
155      }
156    
157      /** Reads the page, looking for position information. */
158      private SkyPosition getPositionFrom(BufferedReader page)
159        throws SourceNotFoundException
160      {
161        try
162        {
163          confirmFlagLine(page.readLine(), page);
164          
165          page.readLine();  //Skip "ticket" line
166          
167          confirmColumnHeadings(page.readLine());
168          
169          SkyPosition position = makePosition(page.readLine());
170    
171          page.close();
172          
173          return position;
174        }
175        catch (IOException ioe)
176        {
177          throw new SourceNotFoundException("Problem reading query results.", ioe);
178        }
179        catch (IllegalArgumentException iae)
180        {
181          throw new SourceNotFoundException("Unexpected results format.", iae);
182        }
183      }
184      
185      /**
186       * Makes sure line contains "flag" and that value is not 0 or -1.
187       * 0 = source-not-found; -1 = error.
188       */
189      private void confirmFlagLine(String line, BufferedReader page)
190        throws SourceNotFoundException
191      {
192        if (!line.contains("flag"))
193          throw new IllegalArgumentException("First line did not contain 'flag'.");
194        
195        int flag = Integer.parseInt(line.substring(line.indexOf(": ")+2));
196        
197        if (flag < 1)
198        {
199          String explanation = "";
200          
201          while (true)
202          {
203            try {
204              line = page.readLine();
205            }
206            catch (IOException ex) {
207              throw new SourceNotFoundException("Problem reading query results."
208                                                , ex);
209            }
210            if (line == null)
211              break;
212            
213            if (!line.startsWith(COMMENT_TOKEN))
214            {
215              explanation = line;
216              break;
217            }
218          }
219          
220          throw new IllegalArgumentException(explanation);
221        }
222      }
223      
224      /** Confirms that we understand the data in the results line. */
225      private void confirmColumnHeadings(String line)
226      {
227        String[] columns = line.split(",");
228        
229        if (!columns[RA_POS].contains("RA(h)"))
230          throw new IllegalArgumentException(
231            "Expected RA, in hours, in column " + RA_POS + " of " + line);
232        
233        if (!columns[DEC_POS].contains("DE(deg)"))
234          throw new IllegalArgumentException(
235            "Expected RA, in hours, in column " + DEC_POS + " of " + line);
236      }
237      
238      /**
239       * Parses skyBotLine and returns a sky position using the portion
240       * of the line that represents the RA and Dec.
241       */
242      private SkyPosition makePosition(String skyBotLine)
243      {
244        SimpleSkyPosition position =
245          new SimpleSkyPosition(CelestialCoordinateSystem.EQUATORIAL, Epoch.J2000);
246        
247        String[] tokens = skyBotLine.split("\\|");
248        
249        if (tokens.length != 7)
250          throw new IllegalArgumentException(skyBotLine);
251    
252        position.setLongitude(Longitude.parse(tokens[RA_POS] +
253                                              ArcUnits.HOUR.getSymbol()));
254    
255        position.setLatitude(Latitude.parse(tokens[DEC_POS] +
256                                            ArcUnits.DEGREE.getSymbol()));
257        return position;
258      }
259    
260      //============================================================================
261      // 
262      //============================================================================
263      /*
264      public static void main(String[] args) throws Exception
265      {
266        SkyBotResolver locator = new SkyBotResolver();
267        
268        String[] bodies = {"mars", "pluto", "ida", "ceres", "lunch", ""};
269        
270        for (int b=0; b < bodies.length; b++)
271        {
272          try
273          {
274            SkyPosition position = locator.findPosition(bodies[b]);
275        
276            System.out.print(bodies[b] + ": ");
277            System.out.print(position.getLongitude().toStringHms());
278            System.out.print(", ");
279            System.out.println(position.getLatitude().toStringDms());
280          }
281          catch (SourceNotFoundException ex)
282          {
283            System.out.println(ex.getMessage() + "  " + ex.getCause().getMessage());
284          }
285        }
286        
287        Calendar cal = Calendar.getInstance();
288        
289        for (int day=1; day <= 10; day++)
290        {
291          Date time = cal.getTime();
292          
293          SkyPosition position = locator.findPosition("Mars", time);
294          
295          System.out.print(time + ": ");
296          System.out.print(position.getLongitude().toStringHms());
297          System.out.print(", ");
298          System.out.println(position.getLatitude().toStringDms());
299    
300          cal.add(Calendar.DATE, 1);
301        }
302      }
303      */
304    }