Building Client-Side VO Applications in Java

In this exercise we look at how to build VO client applications in Java. The primary focus is on finding and retrieving astronomical data (images, spectra, etc.) via the data access layer. First we examine the Java-based VO client library which we will use for our applications. We then build and execute several applications which demonstrate the use of the library to build simple client data analysis applications.

Java is used for these exercises due to its platform independence, so that we don't have to be concerned about platform issues in running the demonstration applications, and because Java is the defacto standard language for VO system software. For science applications one ideally wants to be able to develop in a range of standard languages and environments. While some support for other languages (Python, C/C++, Perl, etc.) and environments (IRAF, IDL, etc.) is available, we are only just getting started developing VO client-side support for such a wide range of environments. Our sample applications should be simple enough to demonstrate the client side of VO without need to understand the details of Java.


Java VO Client Interface V1.0

The dalclient package provides a first cut at an interface to the VO for writing client data analysis applications. Applications deal with data discovery queries via an object API which implements the data model defined by each DAL service, providing object-oriented access to metadata describing generic catalogs, images, spectra, and so forth. This provides a high level, random access interface, while isolating the application from the details of the underlying wire protocol, including (so far as possible) the specific protocol version used by a service. A table-oriented interface supporting both the VOTable and CSV formats is also provided.

This first version supports only Java, and functionality is limited to queries and data retrieval via the current VO Data Access Layer (DAL) services. Future versions will provide equivalent functionality in multiple languages, and integrated support for registry queries for automated service discovery plus the ability to simultaneously query multiple services and merge the results. While the interface already supports building queries against multiple services, the current implementation will only query the first such service specified.

Requirements

This code was developed for Java 1.4.2 and should work with Java 1.4 or later versions. The VOTWrap class from JHU is used for VOTable access. This internally uses either of the JAVOT or SAVOT packages to parse and access VOTables.

Interface Summary

This section provides an overview of the dalclient package, including a brief guide to basic usage. Reference documentation in Javadoc format is also available.

The dalclient interface consists of a generic DAL base class, implementing the basic parameter-based query, which is then subclassed for each type of data supported by the DAL interfaces. Hence basic catalog access via the cone search service is provided by the ConeQuery classes, and image access via the simple image access protocol is provided by the SiapQuery classes.

ConeQuery Classes

The ConeQuery classes implement the simple cone search query, used for generic catalog access. Since there is no data model for a generic catalog query, the query response object for a cone search uses the UCD of each returned table field as the lookup key. For each row of the returned table one can use the getAttribute method to fetch catalog fields directly using the UCD as the key.

       cone = new ConeConnection()		# Connection context
       cone = new ConeConnection(service-url)
		 cone.addService(service-url)
	    cone.getServiceCount()
	      cone.getServiceURL(index)

       query = cone.getConeQuery()		# Query context
       query = cone.getConeQuery(ra, dec, sr)
	      query.addParameter(name, value)

	      qr = query.execute()		# VOTable -> Dataset|Record
	        query.executeCSV()		# VOTable -> CSV
	        query.executeCSV(fname)
      vot = query.executeVOTable()		# query as VOTable
	   is = query.executeRAW()		# raw XML

SiapQuery Classes

The SiapQuery classes implement the simple image access query, used to discover candidate image datasets which can subsequently be downloaded if desired. In this case the query response implements the SIAP data model, which defines a number of standard attributes used to describe each candidate image dataset. The getDataset method can be used to retrieve the image associated with a given row of the query response.

       siap = new SiapConnection()
       siap = new SiapConnection(service-url)
		 siap.addService(service-url)
	    siap.getServiceCount()
	      siap.getServiceURL(index)

       query = siap.getSiapQuery()
       query = siap.getSiapQuery(ra, dec, size)
       query = siap.getSiapQuery(ra, dec, size, format)
       query = siap.getSiapQuery(ra, dec, ra_size, dec_size)
       query = siap.getSiapQuery(ra, dec, ra_size, dec_size, format)
	      query.addParameter(name, value)

	      qr = query.execute()		# VOTable -> Dataset
	        query.executeCSV()
	        query.executeCSV(fname)
      vot = query.executeVOTable()
	   is = query.executeRAW()

SsapQuery Classes

Support for the simple spectral access protocol (SSAP) will be added once the interface is stable (the target for this is late 2005). The interface will be similar to that for cone and SIAP, but the query parameters will be somewhat different, allowing queries by spectral and time bandpass as well as other parameters. The returned dataset metadata as captured in the query response will differ significantly as the data models have evolved significantly since SIAP was introduced.

QueryResponse Class

The response to a DAL query can be processed directly as a VOTable, the generic table container format used by the wire protocol. This requires knowledge within the client code of the details of the DAL protocol used, but provides a generic way to process the results from any query. The QueryResponse class goes one step further and provides an object API based on the data model for a particular object as defined by the DAL interfaces (catalog, image, spectrum, etc.). This permits an application to interact with a remote data object service without detailed knowledge of the underlying DAL protocol used. In addition since the query response table is stored in memory in a hash table, efficient random access by keyword is provided.

The QueryResponse class is a sequence of dataset descriptors corresponding to the rows of the query response VOTable. Each dataset is described by a set of standard data model attributes. A data provider may add additional non-standard attributes which will flow through all layers the interface to the client code. These are not visible in the standard data model via the normal query-by-attribute interface, but can be accessed by traversing the response record as a list.

	Get dataset descriptor:

	   qr.getRecordCount()			# number of descriptors
	  rec = qr.getRecord(i)			# get a descriptor

	Access by dataset attribute:

	v = rec.getAttribute(attrname)		# may return null

		 v.boolValue()
		  v.intValue()
		v.floatValue()
	       v.doubleValue()
	       v.stringValue()

	Get dataset file corresponding to descriptor:

	      rec.getDataset(path)		# fetch data to given path
       path = rec.getDataset()			# fetch to autogenerated path

The getDataset method may optionally be used to generate and retrieve the dataset described by a given query response record. A filename will be automatically generated if one is not provided. An attempt is made to determine the type of data returned (FITS, JPEG, etc.) and set the file extension automatically, however this is not always possible.

Basic Usage

Regardless of the type of data being accessed, the basic interface is the same:


Sample Client Programs

The "dalclient" directory in the summer school software release includes both the source code for the initial version of the dalclient package as well as a number of sample Java programs illustrating how to use the interface. Note these are not intended to be real applications! They have intentionally been kept quite simple, to demonstrate how to build client VO applications without being distracted by details.

To get started, open a terminal window (the details will depend upon which platform you are using) and navigate to the dalclient directory in the NVO summer school software release:

    % cd $NVOSS_HOME/java/dev/dalclient
    % ls
    CVS                  QueryRecord.java     chart.java    siap1.java     
    ConeConnection.java  QueryResponse.java   cone1.java    siap2.java
    ConeQuery.java       README               cone2.java    siap3.java
    DALConnection.java   SiapConnection.java  cone3.java    siap4.java
    DALQuery.java        SiapQuery.java       doc
    QRAttribute.java     build.xml            package.html

Before we can run any of the test programs we need to build the package. For Java this is done with the ant tool:

    % ant
    Buildfile: build.xml

    init:
	[mkdir] Created dir: D:\nvo\nvoss2005\java\dev\dalclient\classes

    compile:
	 [echo] building
	[javac] Compiling 17 source files to D:\nvo\nvoss2005\java\dev\dalclient\classes

    BUILD SUCCESSFUL
    Total time: 6 seconds

Now we can try out the demonstration client applications. In what follows, all of the applications can be run either with arguments on the command line, or with no arguments in which case a set of built-in arguments are used. To simplify things we mostly use the no-args option here, but later we can try re-running the applications at other positions on the sky, or querying other services, to dyamically explore the VO. In all cases we are accessing real VO services, hence a good Internet connection is required to run the applications.

Application Cone1

    Call a cone search service and print selected fields of the table.
    Usage:  cone1 ra dec sr [serviceURL]

Our first application performs a simple cone search and prints selected fields using the ConeQuery class to access selected table columns directly via their UCD tag. To run the cone1 application type java dalclient.cone1 as in the following example (note one must include the package name, "dalclient" in this case):

    % java dalclient.cone1
    # Query: http://chart.stsci.edu/GSCVO/GSC22VO.jsp?RA=12.0&DEC=12.0&SR=0.1
    # returns 36 records containing 14 attributes each
    # --- Summary output ---
    id=N3233000:4280        ra=12.06964142  dec=11.93743758 class=Star
    id=N3233000:4440        ra=12.00563246  dec=11.98930768 class=Star
    id=N3233000:4396        ra=12.04016664  dec=11.97401059 class=Star
    id=N3233000:4174        ra=11.97466466  dec=11.90447683 class=Star
    id=N3233000:4182        ra=11.98926385  dec=11.90476293 class=Star
    id=N3233000:4321        ra=12.0236914   dec=11.95191743 class=Star
    id=N3233000:90          ra=11.98445133  dec=11.95106419 class=Star
    id=N3233000:4184        ra=12.01401469  dec=11.90488566 class=Star
    id=N3233000:4266        ra=12.02581304  dec=11.93266205 class=Star
    id=N3233000:94          ra=12.02150008  dec=11.93815298 class=Star
    id=N3233000:4274        ra=12.0502686   dec=11.93381342 class=Star
    id=N3233000:96          ra=12.01875118  dec=11.93194831 class=Star
    id=N3233000:4289        ra=11.98131438  dec=11.94162927 class=Non star
    id=N3233000:4322        ra=11.9158309   dec=11.9526625  class=Non star
    id=N3233000:95          ra=11.94963356  dec=11.93363352 class=Star
    id=N3233000:4327        ra=11.91464261  dec=11.95481215 class=Non star
    id=N3233000:4626        ra=11.92744157  dec=12.04707863 class=Star
    id=N3233000:4588        ra=11.90452932  dec=12.0284553  class=Non star
    id=N3233000:4465        ra=11.9564025   dec=11.99919811 class=Non star

If we look at the actual VOTable returned by the service for this cone search we see the following fields:

<TABLE><FIELD ID="ID" ucd="ID_MAIN" datatype="char" arraysize="14*"/>
  <FIELD ID="RA" ucd="POS_EQ_RA_MAIN" datatype="double" unit="deg"/>
  <FIELD ID="Dec" ucd="POS_EQ_DEC_MAIN" datatype="double" unit="deg"/>
  <FIELD ID="RAErr" ucd="ERROR" datatype="double" unit="deg"/>
  <FIELD ID="DecErr" ucd="ERROR" datatype="double" unit="deg"/>
  <FIELD ID="Epoch" ucd="TIME_EPOCH" datatype="double" unit="deg"/>
  <FIELD ID="Fmag" ucd="PHOT_PHG_R" datatype="double" unit="mag"/>
  <FIELD ID="FmagErr" ucd="ERROR" datatype="double" unit="mag"/>
  <FIELD ID="Jmag" ucd="PHOT_PHG_B" datatype="double" unit="mag"/>
  <FIELD ID="JmagErr" ucd="ERROR" datatype="double" unit="mag"/>
  <FIELD ID="Vmag" ucd="PHOT_PHG_V" datatype="double" unit="mag"/>
  <FIELD ID="VmagErr" ucd="ERROR" datatype="double" unit="mag"/>
  <FIELD ID="Nmag" ucd="PHOT_PHG_N" datatype="double" unit="mag"/>
  <FIELD ID="NmagErr" ucd="ERROR" datatype="double" unit="mag"/>
  <FIELD ID="classification" ucd="CLASS_OBJECT" datatype="char" arraysize="*"/>
  <FIELD ID="semiMajor" ucd="EXTENSION_RAD" datatype="double"/>
  <FIELD ID="eccentricity" ucd="PHYS_ECCENTRICITY" datatype="double"/>
  <FIELD ID="positionAngle" ucd="POS_POS-ANG" datatype="double" unit="deg"/>
  <FIELD ID="status" ucd="CODE_QUALITY" datatype="double"/>

Now we can see that indeed only selected fields of the table are being output. To see how this is done we can examine the code for cone1. The most important bits are as follows:

    // Get a new connection to the service.
    ConeConnection cone = new ConeConnection(service);
        
    // Form the query.
    ConeQuery query = cone.getConeQuery(ra, dec, sr);
        
    // Execute the query and fetch results.
    System.out.println("# Query: " + query.getQueryString(0));
    QueryResponse qr = query.execute();
    if (qr.getRecordCount() <= 0) {
        System.out.println("no records matched");
        System.exit(1);
    }   
        
    // Summarize and print selected query results.
    for (int i=0;  i < qr.getRecordCount();  i++) {
        QueryRecord r = qr.getRecord(i);
        String s_id, s_ra, s_dec, s_class;
        QRAttribute v;
            
        s_id = ((v = r.getAttribute("ID_MAIN")) != null) ?
            v.stringValue() : "<none>";
        s_ra = ((v = r.getAttribute("POS_EQ_RA_MAIN")) != null) ?
            v.stringValue() : "<unknown>";
        s_dec = ((v = r.getAttribute("POS_EQ_DEC_MAIN")) != null) ?
            v.stringValue() : "<unknown>";
        s_class = ((v = r.getAttribute("CLASS_OBJECT")) != null) ?
            v.stringValue() : "<unknown>";

        System.out.println("id=" + s_id + "\tra=" + s_ra +
            "\tdec=" + s_dec + "\tclass=" + s_class);
    }   

As we can see from this code, the client application is able to retrieve the interesting bits of the table using the getAttribute method to directly access the fields by their UCD tag, without having to be concerned about the details of the underlying encoding in VOTable.

Application Cone2

    Call a cone search service and print the results table in CSV format.
    Usage: cone2 ra dec sr [serviceURL]

To run the application type java dalclient.cone2:

    % java dalclient.cone2

    # Query: http://chart.stsci.edu/GSCVO/GSC22VO.jsp?RA=12.0&DEC=12.0&SR=0.1
    ID_MAIN,POS_EQ_RA_MAIN,POS_EQ_DEC_MAIN,ERROR,ERROR,TIME_EPOCH,PHOT_PHG_R,ERROR,P
    HOT_PHG_B,ERROR,PHOT_PHG_V,ERROR,PHOT_PHG_N,ERROR,CLASS_OBJECT,EXTENSION_RAD,PHY
    S_ECCENTRICITY,POS_POS-ANG,CODE_QUALITY
    N3233000:4280,12.06964142,11.93743758,0.430478,0.539791,1990.784302,16.29,0.43,1
    7.74,0.42,99.9,99.9,99.9,99.0,Star,2.9,0.11,6.08,1011202
    N3233000:4440,12.00563246,11.98930768,0.430478,0.539791,1990.784302,17.41,0.43,9
    9.9,99.9,99.9,99.9,99.9,99.0,Star,2.63,0.17,155.63,1010202
    N3233000:4396,12.04016664,11.97401059,0.430478,0.539791,1990.784302,16.5,0.43,18
    .69,0.42,99.9,99.9,99.9,99.0,Star,2.79,0.07,175.67,1011202
    N3233000:4174,11.97466466,11.90447683,0.430478,0.539791,1990.784302,18.17,0.44,9
    9.9,99.9,99.9,99.9,99.9,99.0,Star,2.31,0.17,121.79,1010202
    N3233000:4182,11.98926385,11.90476293,0.430478,0.539791,1990.784302,15.31,0.43,1
    6.8,0.42,99.9,99.9,99.9,99.0,Star,3.25,0.02,169.14,1011202
    N3233000:4321,12.0236914,11.95191743,0.430478,0.539791,1990.784302,16.81,0.43,17
    .75,0.42,99.9,99.9,99.9,99.0,Star,2.71,0.07,45.0,1011202

In this case we see the entire table, formatted as CSV. This is convenient for manipulating the data in non-astronomy tools such as a spreadsheet program. The code in this case is very simple as the format conversion from VOTable to CSV is handled entirely by the VO client library:

    // Get a new connection to the service.
    ConeConnection cone = new ConeConnection(service);

    // Form the query.
    ConeQuery query = cone.getConeQuery(ra, dec, sr);

    // Execute the query and fetch results.
    System.out.println("# Query: " + query.getQueryString(0));
    query.executeCSV();

In this case the executeCSV() method is used, which executes the query and prints the results in CSV format to the standard output. Other methods are available if the application wants to capture the output and process it further.

Application Cone3

    Call a cone search service and return the results as a VOTable.
    Usage:    cone3 ra dec sr [fname [serviceURL]]

Here we go one level deeper into the cone search protocol, and capture the native VOTable-formatted output of the query in a file:

    % java dalclient.cone3
    # Query: http://chart.stsci.edu/GSCVO/GSC22VO.jsp?RA=12.0&DEC=12.0&SR=0.1
    # copying query result VOTable to file cone3.xml

In this case the VOTable is left in the file "cone3.xml". Any application which operates upon VOTable can be used to examine the file, e.g., topcat, mirage, or any generic program which manipulates XML files. Here is how the output VOTable looks rendered in topcat:


Note this is the same VOTable which we encountered in application cone1 above.

Application Siap1

    Call a SIAP service and print selected fields of the result table.
    Usage:   siap1 ra dec size [serviceURL]

Our first image access application executes a simple positional query to a SIAP service and prints selected fields of the query response table:

    % java dalclient.siap1
    # Query: http://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?POS=12.0,0.0&SIZE=0.5
    # returns 34 records containing 11 attributes each
    # --- Summary output ---
    ra=12.0 dec=0.0 [300,300]   <unknown>   Digitized Sky Survey 1
    ra=12.0 dec=0.0 [300,300]   <unknown>   Digitized Sky Survey 1
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT PSPC Pointed Observations 
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT PSPC Pointed Observations 
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT PSPC Pointed Observations 
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT PSPC Pointed Observations 
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey Broad-Band
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey Broad-Band
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey (Hard)
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey (Hard)
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey (Soft)
    ra=12.0 dec=0.0 [300,300]   <unknown>   ROSAT All Sky Survey (Soft)
    ra=12.0 dec=0.0 [300,300]   <unknown>   Faint Images of the Radio Sky at
    ra=12.0 dec=0.0 [300,300]   <unknown>   Faint Images of the Radio Sky at
    ra=12.0 dec=0.0 [300,300]   <unknown>   NRAO VLA Sky Survey
    ra=12.0 dec=0.0 [300,300]   <unknown>   NRAO VLA Sky Survey
    ra=12.0 dec=0.0 [300,300]   <unknown>   SFD Dust Map
    ra=12.0 dec=0.0 [300,300]   <unknown>   SFD Dust Map

We can tell from this output that we are querying a "mosaic" service since all the candidate images have the requested position and are the same size, even though they come from multiple data collections. The field shown as "<unknown>" in the output is the image format field, which for some reason this service did not return in the query response.

The following is the the part of the code for siap1 which accesses the image attributes:

    s_ra = ((v = r.getAttribute("RA")) != null) ?
	v.stringValue() : "";
    s_dec = ((v = r.getAttribute("DEC")) != null) ?
	v.stringValue() : "";
    s_naxes = ((v = r.getAttribute("Naxes")) != null) ?
	v.stringValue() : "";
    s_naxis = ((v = r.getAttribute("Naxis")) != null) ?
	v.stringValue() : "";
    s_format = ((v = r.getAttribute("ImageFormat")) != null) ?
	v.stringValue() : "";
    s_title = ((v = r.getAttribute("Title")) != null) ?
	v.stringValue() : (((v = r.getAttribute("ID_MAIN")) != null) ?
	v.stringValue() : "");

Something new is going on here, which we did not see with the cone search. For SIA the application is able to access image attributes by the name of the corresponding attribute of the SIA data model, in this case "RA", "DEC", "Naxes", "Naxis", "ImageFormat", and "Title". The client library is automatically mapping the UCD tags used in the SIA V1.0 protocol to the associated fields of the data model, thereby shielding the application from the details of the protocol and protocol version used. The only exception is "ID_MAIN", which is a UCD. In this case the application is being clever and is looking for an ID_MAIN field to use to describe the image, in the event that "Title" is not given.

Application Siap2

    Call a SIA service and print the results in CSV format.
    Usage:    siap2 ra dec size [serviceURL]

As with the cone service, we can execute an SIAP service and output the results in CSV format:

    % java dalclient.siap2
    # Query: http://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?POS=12.0,0.0&SIZE=0.5
    Title,RA,DEC,Naxes,Naxis,Scale,Format,PixFlags,AccessReference,Filesize,VOX:LogicalName
    Digitized Sky Survey 1,12.0,0.0,2,300 300,-0.00166666666666667 0.00166666666666667,image/fits,F,http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=Digitized%20Sky%20Survey&VCOORD=12.0%2C0.0&SFACTR=0.5&RETURN=FITS,362880,1
    Digitized Sky Survey 1,12.0,0.0,2,300 300,-0.00166666666666667 0.00166666666666667,image/gif,F,http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=Digitized%20Sky%20Survey&VCOORD=12.0%2C0.0&SFACTR=0.5&RETURN=GIF,90000,1
    ROSAT PSPC Pointed Observations (2 deg field),12.0,0.0,2,300 300,-0.00166666666666667 0.00166666666666667,image/fits,F,http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%202.0%20Deg-Inten&VCOORD=12.0%2C0.0&SFACTR=0.5&RETURN=FITS,362880,2
    ROSAT PSPC Pointed Observations (2 deg field),12.0,0.0,2,300 300,-0.00166666666666667 0.00166666666666667,image/gif,F,http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%202.0%20Deg-Inten&VCOORD=12.0%2C0.0&SFACTR=0.5&RETURN=GIF,90000,2
    ROSAT PSPC Pointed Observations (1 deg field),12.0,0.0,2,300 300,-0.00166666666666667 0.00166666666666667,image/fits,F,http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%201.0%20Deg-Inten&VCOORD=12.0%2C0.0&SFACTR=0.5&RETURN=FITS,362880,3

The nice thing about CSV is that it is an open standard, allowing a wide range of tools to be used to work with data in this form. For example, we can import the output of this query directly into a spreadsheet program and operate upon it in various ways. Here is what the result of our standard test query looks like in Microsoft Excel:


Application Siap3

    Call a SIA service and return the output VOTable in a file.
    Usage:    siap3 ra dec size [fname serviceURL]]

Nothing fancy here. As with the cone3 application, this demonstrates the use of the SIAP query class to capture the results of the query in a file in VOTable format.

    % java dalclient.siap3
    # Query: http://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?POS=12.0,0.0&SIZE=0.5
    # copying query result VOTable to file siap3.xml

The source code for siap3 provides an illustration of how to use an InputStream to capture and process the raw output of the query:

    // Get a new connection to the service.
    SiapConnection siap = new SiapConnection(service);

    // Form the query.
    SiapQuery query = siap.getSiapQuery(ra, dec, size);

    // Execute the query.
    InputStream in = query.executeRaw();

    // Copy the query result VOTable to the name output file, overwriting
    // it if the file already exists.

    File file = new File(fname);  file.delete();
    FileOutputStream out = new FileOutputStream(file);

    byte buf[] = new byte[4096];  int n;
    while ((n = in.read(buf, 0, buf.length)) > 0)
	out.write(buf, 0, n);

Application Siap4

    Call a SIA service and download the first MaxImages images referenced
    in the query response table.

    Usage:    siap4 ra dec size [format [maximages [serviceURL]]]

This application goes one step further and demonstrates how to download selected images using the SIA protocol. For our test program we merely issue the query and blindly download the first 5 images listed in the query response. In a real application one would normally want to apply some client-specific heuristic to select interesting datasets from the query response list.

    % java dalclient.siap4
    # Query: http://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?POS=12.0,10.0&SIZE=0.2&FORMAT=ALL
    # returns 34 records containing 11 attributes each
    Downloading images: 
    Downloading: http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=Digitized%20Sky%20Survey&VCOORD=12.0%2C10.0&SFACTR=0.2&RETURN=FITS
    Downloaded ./data15658.fits
    Downloading: http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=Digitized%20Sky%20Survey&VCOORD=12.0%2C10.0&SFACTR=0.2&RETURN=GIF
    Downloaded ./data15659.gif
    Downloading: http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%202.0%20Deg-Inten&VCOORD=12.0%2C10.0&SFACTR=0.2&RETURN=FITS
    Downloaded ./data15660.fits
    Downloading: http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%202.0%20Deg-Inten&VCOORD=12.0%2C10.0&SFACTR=0.2&RETURN=GIF
    Downloaded ./data15661.gif
    Downloading: http://skyview.gsfc.nasa.gov/cgi-bin/pskcall?SURVEY=PSPC%201.0%20Deg-Inten&VCOORD=12.0%2C10.0&SFACTR=0.2&RETURN=FITS
    Downloaded ./data15662.fits

By default the library puts the images in the current working directory, assigning an automatically-generated file name. If we examine the downloaded images we see the following:

    % ls -l data*
    -rw-rw-r--  1 dtody dtody 368640 Sep  9 12:56 data15658.fits
    -rw-rw-r--  1 dtody dtody  62594 Sep  9 12:56 data15659.gif
    -rw-rw-r--  1 dtody dtody 365760 Sep  9 12:56 data15660.fits
    -rw-rw-r--  1 dtody dtody      0 Sep  9 12:56 data15661.gif
    -rw-rw-r--  1 dtody dtody 365760 Sep  9 12:56 data15662.fits

Note that the library has automatically assigned FITS and GIF file extensions to the downloaded images. This is based on the image format specified in the query response metadata. If this is missing for some reason, a ".tmp" file extension will be assigned. We also note that one of the GIF files has a zero length - no data was returned. Probably there was no data available for the given survey (PSPC) in the given region on the sky. In this case the service should not have indicated that data was available, but computation of the overlap of a ROI with the sky coverage of a survey can be complex and false matches can occasionally occur. Ideally the chain of software should have detected the error somewhere along the line, but that did not happen in this case.

Application "Chart"

    Call the SDSS finding chart service and download a graphics image
    of the given coordinates and size.

    Usage:     chart ra dec size [serviceURL]

Blindly returning the first N images from a SIAP query is generally not all that useful. Some services however will only return at most one image in response to a query, in which case we can write a simple program to issue the query and download the referenced image. An example of such a service is the SDSS color finding chart service, which dynamically generates JPEG image cutouts for any region for which SDSS has coverage.

    % java dalclient.chart
    # Query: http://casjobs.sdss.org/vo/DR4SIAP/SIAP.asmx/getSiapInfo?&FORMAT=image/jpeg&BANDPASS=*&POS=258.127,64.017&SIZE=0.4&opt=G
    Downloaded ./data30091.jpeg

By default the test program downloads a galaxy cluster from the SDSS-C4 galaxy cluster catalog (Miller et. al. 2005). If we examine the image returned we can clearly see that the cluster is present (although the cluster appears to be off-center, the mean coordinates are used, which put the member galaxy with the highest local density at the center):


Lets looks at the source code for chart to see how this works:

    // Get a new connection to the service.
    SiapConnection siap = new SiapConnection(service);

    // Form the query.
    SiapQuery query = siap.getSiapQuery(ra, dec, size);

    // Enable the graphics overlay (SDSS specific parameter).
    query.addParameter("opt", "G");

    // Execute the query and fetch results.
    System.out.println("# Query: " + query.getQueryString(0));
    QueryResponse qr = query.execute();
    if (qr.getRecordCount() <= 0) {
	System.out.println("no images found");
	System.exit(1);
    }

    // Download the image.
    QueryRecord r = qr.getRecord(0);
    String path = r.getDataset();
    if (path != null)
	System.out.println("Downloaded " + path);
    else
	System.out.print("Download failed");

We see two new things here. First, we have used the addParameter method to add a service-specific query parameter ("opt=G") to the query. This is supposed to tell the finding chart service to draw a coordinate overlay grid on the generated graphic. If we look at the query string as echoed in the output above we see that the parameter was successfully added to the query. However, no coordinate overlay grid is drawn. Evidently the "opt" parameter is only supported by the Web-service version of the service, but nonetheless this demonstrates how to add specialized parameters to a query.

The second thing this code snippet demonstrates is how to download a dataset given a dataset descriptor record. This is done using the getDataset method, and is trivial assuming the SIAP dataset (image) descriptor contains a valid access reference. A file pathame can be specified to control where the image goes, otherwise the file will be put in the current working directory using an automatically generated filename. In our sample code we are performing almost no error checking, and the program will probably bomb with a Java exception if anything goes wrong. Production code would require more robust error detection and recovery.

That concludes our walkthrough of the dalclient code! Although our sample programs are pretty simple, they should serve to illustrate how to use the dalclient library to build real client applications.

Doug Tody (NRAO, NVO) September 2005