package org.autoplot.pds; import gov.nasa.pds.label.Label; import gov.nasa.pds.label.object.ArrayObject; import gov.nasa.pds.label.object.FieldDescription; import gov.nasa.pds.label.object.TableObject; import gov.nasa.pds.label.object.TableRecord; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.autoplot.datasource.AbstractDataSource; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.URISplit; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.qds.DDataSet; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.ops.Ops; import org.das2.qds.util.DataSetBuilder; import org.das2.util.monitor.NullProgressMonitor; import org.das2.util.monitor.ProgressMonitor; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * PDS4 file source. This is pointed at PDS4 xml files and will return data * they describe. * @author jbf */ public class PdsDataSource extends AbstractDataSource { public PdsDataSource(URI uri) { super(uri); } /** * bootstrap routine for getting data from fields of a TableObject. TODO: rewrite so that * multiple fields are read at once. * @param t * @param columnName * @return * @throws IOException */ private QDataSet getFromTable( TableObject t, String[] columnNames ) throws IOException { int ncols= columnNames.length; int[] icols= new int[ncols]; DataSetBuilder dsb= new DataSetBuilder(2,100,ncols); for ( int i=0; i axisNames ) throws XPathExpressionException { XPathFactory factory= XPathFactory.newInstance(); XPath xpath= factory.newXPath(); String name = (String)xpath.evaluate( "axis_name", n, XPathConstants.STRING ); Double sequence_number = (Double)xpath.evaluate( "sequence_number", n, XPathConstants.NUMBER ); axisNames.put( sequence_number.intValue(), name ); } /** * return the name of the independent parameter that works in this axis. * This currently assumes the first node with this axisName is the * independent axis. * * For example, with https://space.physics.uiowa.edu/voyager/data/voyager-2-pws-wf/data/1987/vg2_pws_wf_1987-04-21T17_v0.9.xml, * if axisName=='time' then the result will be "Epoch" * * @param axisName the axis name * @return the independent variable for the axis. */ private static String resolveIndependentAxis( Document doc, String axisName ) throws XPathExpressionException { XPathFactory factory= XPathFactory.newInstance(); XPath xpath= factory.newXPath(); String s= "Product_Observational/File_Area_Observational/Array[Axis_Array/axis_name='"+axisName +"']"; NodeList oo= (NodeList) xpath.evaluate( s, doc, XPathConstants.NODESET ); if ( oo.getLength()>0 ) { Node o = oo.item(0); String name = (String)xpath.evaluate( "name", o, XPathConstants.STRING ); return name; } return null; } /** * look through the PDS label document to see if dependencies can be * identified. Presently, this is simply one other dataset with the * same axis (as in sample_offset) or the same axis name as something * that has a time unit (Epoch). * @see https://space.physics.uiowa.edu/voyager/data/voyager-2-pws-wf/data/1987/vg2_pws_wf_1987-04-21T17_v0.9.xml * @param doc the parsed document for the label XML * @param depend the name of the data for the dependant variable, e.g. Waveform * @return ( Epoch, sample_offset, Waveform ) */ public static List seekDependencies( Document doc, List depend ) throws XPathExpressionException { if ( depend.size()==1 ) { // always will have one element. XPathFactory factory= XPathFactory.newInstance(); XPath xpath= factory.newXPath(); String name= depend.get(0); Map axisNames= new LinkedHashMap<>(); NodeList oo= (NodeList) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/Axis_Array", doc, XPathConstants.NODESET ); for ( int i=0; i(depend); depend.add(0,n1); if ( !n2.equals(name) ) { depend.add(1,n2); } } else if ( axisNames.get(1)!=null ) { String n1= resolveIndependentAxis( doc, axisNames.get(1) ); depend= new LinkedList<>(depend); if ( !n1.equals(name) ) { depend.add(0,n1); } } } return depend; } @Override public org.das2.qds.QDataSet getDataSet(ProgressMonitor mon) throws Exception { String name= getParam("arg_0",""); URISplit split= URISplit.parse( getURI() ); URL fileUrl= PdsDataSourceFactory.getFileResource( split.resourceUri.toURL(), mon ); File xmlfile = DataSetURI.getFile( split.resourceUri.toURL() ,new NullProgressMonitor()); DataSetURI.getFile(fileUrl,mon ); Label label = Label.open( xmlfile.toURI().toURL() ); Document doc= readXML(xmlfile); List names= new ArrayList<>(); String X= getParam("X",""); if ( !X.equals("") ) { names.add(X); } String Y= getParam("Y",""); if ( !Y.equals("") ) { names.add(Y); } String Z= getParam("Z",""); if ( !Z.equals("") ) { names.add(Z); } if ( !name.equals("") ) { names.add(name); } names= seekDependencies(doc, names ); //TODO: Call a routine which scans through the document looking for //dependencies. See vap+pds:https://space.physics.uiowa.edu/voyager/data/voyager-2-pws-wf/data/1987/vg2_pws_wf_1987-04-21T17_v0.9.xml?Waveform //which shows where the time and time offset arrays can be identified for Waveform. QDataSet result=null; QDataSet[] results= new QDataSet[names.size()]; // see which parameters will come from tables. for ( TableObject t : label.getObjects( TableObject.class) ) { List tableColumnNames= new ArrayList<>(); List datasetColumnIndexes= new ArrayList<>(); for ( int i=0; i0 ) { QDataSet bresults= getFromTable( t, tableColumnNames.toArray(new String[tableColumnNames.size()]) ); int iresults= 0; for ( int i:datasetColumnIndexes ) { results[i]= Ops.unbundle( bresults, iresults ); iresults++; } } } for ( int i=0; i0 && units==null ) { result1.putProperty( QDataSet.UNITS, Units.lookupUnits(sunits) ); } if ( units==null || !UnitsUtil.isTimeLocation(units) ) { String labl= (String) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/name/text()", doc ); if ( labl.length()==0 ) labl= name; ((MutablePropertyDataSet)results[i]).putProperty( QDataSet.LABEL, labl ); String title= (String) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/description/text()", doc ); if ( title.length()>0 ) { result1.putProperty( QDataSet.TITLE, title.trim() ); } } String sfillValue= (String) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/Special_Constants/invalid_constant/text()", doc ); String svalidMax= (String) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/Special_Constants/valid_maximum/text()", doc ); String svalidMin= (String) xpath.evaluate( "//Product_Observational/File_Area_Observational/Array[name='"+name+"']/Special_Constants/valid_minimum/text()", doc ); if ( sfillValue.trim().length()>0 ) { double fillValue= Double.parseDouble(sfillValue); result1.putProperty( QDataSet.FILL_VALUE, fillValue ); } if ( svalidMax.trim().length()>0 ) { double validMax= Double.parseDouble(svalidMax); result1.putProperty( QDataSet.VALID_MAX, validMax ); } if ( svalidMin.trim().length()>0 ) { double validMin= Double.parseDouble(svalidMin); result1.putProperty( QDataSet.VALID_MIN, validMin ); } } } } } if ( result==null ) { switch (results.length) { case 1: result= results[0]; break; case 2: result= Ops.link( results[0], results[1] ); break; case 3: result= Ops.link( results[0], results[1], results[2] ); break; default: break; } } if ( result instanceof MutablePropertyDataSet ) { ((MutablePropertyDataSet)result).makeImmutable(); } return result; } }