1 /* Copyright (c) 2002-2012 The University of the West Indies
2  *
3  * Contact: robert.lancashire@uwimona.edu.jm
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2.1 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package jspecview.export;
21 
22 import java.io.IOException;
23 
24 import javajs.util.DF;
25 import javajs.util.OC;
26 import javajs.util.Lst;
27 import javajs.util.PT;
28 
29 import jspecview.api.JSVExporter;
30 import jspecview.common.Coordinate;
31 import jspecview.common.ExportType;
32 import jspecview.common.Spectrum;
33 import jspecview.common.JSViewer;
34 import jspecview.common.PanelData;
35 import jspecview.source.JDXReader;
36 import jspecview.source.JDXDataObject;
37 
38 /**
39  * class <code>JDXExporter</code> contains methods for exporting a
40  * JCAMP-DX Spectrum in one of the compression formats DIF, FIX, PAC, SQZ or
41  * as x, y values.
42  * @author Debbie-Ann Facey
43  * @author Khari A. Bryan
44  * @author Craig A.D. Walters
45  * @author Prof Robert J. Lancashire
46  */
47 
48 public class JDXExporter implements JSVExporter {
49 
50 	public static final String newLine = System.getProperty("line.separator");
51 	private OC out;
52 	private ExportType type;
53 	private Spectrum spectrum;
54 	private JSViewer vwr;
55 
JDXExporter()56 	public JDXExporter() {
57 
58 	}
59   /**
60    * The factor divisor used in compressing spectral data in one of DIF, SQZ,
61    * PAC and FIX formats
62    */
63   private static final double FACTOR_DIVISOR = 1000000;
64 
65   /**
66    * Exports spectrum in one of several formats
67    * @param type
68    * @param out
69    * @param startIndex
70    * @param endIndex
71    * @param spectrum the spectrum
72    * @return data if path is null
73    * @throws IOException
74    */
75   @Override
exportTheSpectrum(JSViewer viewer, ExportType type, OC out, Spectrum spectrum, int startIndex, int endIndex, PanelData pd, boolean asBase64)76 	public String exportTheSpectrum(JSViewer viewer, ExportType type, OC out, Spectrum spectrum, int startIndex, int endIndex, PanelData pd, boolean asBase64) throws IOException{
77   	this.out = out;
78   	this.type = type;
79   	this.spectrum = spectrum;
80   	this.vwr = viewer;
81     toStringAux(startIndex, endIndex);
82     out.closeChannel();
83     return "OK " + out.getByteCount() + " bytes";
84   }
85 
86   /**
87    * Auxiliary function for the toString functions
88    *
89    * @param startIndex
90    *        the start Coordinate Index
91    * @param endIndex
92    *        the end Coordinate Index
93    */
toStringAux(int startIndex, int endIndex)94   private void toStringAux(int startIndex, int endIndex) {
95 
96     Coordinate[] newXYCoords = spectrum.getXYCoords();
97     String tabDataSet = "", tmpDataClass = "XYDATA";
98 
99     if (spectrum.isHZtoPPM()) {
100       // convert back to Hz.
101       Coordinate[] xyCoords = newXYCoords;
102       newXYCoords = new Coordinate[xyCoords.length];
103       for (int i = 0; i < xyCoords.length; i++)
104         newXYCoords[i] = xyCoords[i].copy();
105       Coordinate.applyScale(newXYCoords, spectrum.getObservedFreq(), 1);
106     }
107 
108     double xCompFactor = spectrum.getXFactor();
109     boolean isIntegerX = areIntegers(newXYCoords, startIndex, endIndex, 1.0, true);
110     if (!isIntegerX && !areIntegers(newXYCoords, startIndex, endIndex, xCompFactor, true))
111       xCompFactor = 1;
112 
113     double minY = Coordinate.getMinY(newXYCoords, startIndex, endIndex);
114     double maxY = Coordinate.getMaxY(newXYCoords, startIndex, endIndex);
115     double yCompFactor = spectrum.getYFactor();
116 
117     switch (type) {
118     case XY:
119       yCompFactor = 1;
120       tmpDataClass = (spectrum.isContinuous() ?  "XYDATA" : "XYPOINTS");
121       break;
122     case PAC:
123       yCompFactor = 1;
124       break;
125     default:
126       boolean isIntegerY = areIntegers(newXYCoords, startIndex, endIndex, 1.0, false);
127       if (!isIntegerY && !areIntegers(newXYCoords, startIndex, endIndex, yCompFactor, false)) {
128         yCompFactor = (maxY - minY) / FACTOR_DIVISOR;
129       }
130       break;
131     }
132     int step = 1;
133     if (spectrum.isExportXAxisLeftToRight() != (spectrum.getFirstX() < spectrum.getLastX())) {
134       int t = startIndex;
135       startIndex = endIndex;
136       endIndex = t;
137       step = -1;
138     }
139     switch (type) {
140     case DIF:
141     case DIFDUP:
142       tabDataSet = JDXCompressor.compressDIF(newXYCoords, startIndex, endIndex, step,
143           xCompFactor, yCompFactor, type == ExportType.DIFDUP);
144       break;
145     case FIX:
146       tabDataSet = JDXCompressor.compressFIX(newXYCoords, startIndex, endIndex, step,
147           xCompFactor, yCompFactor);
148       break;
149     case PAC:
150       tabDataSet = JDXCompressor.compressPAC(newXYCoords, startIndex, endIndex, step,
151           xCompFactor, yCompFactor);
152       break;
153     case SQZ:
154       tabDataSet = JDXCompressor.compressSQZ(newXYCoords, startIndex, endIndex, step,
155           xCompFactor, yCompFactor);
156       break;
157     case XY:
158       tabDataSet = JDXCompressor.getXYList(newXYCoords, startIndex, endIndex, step);
159       break;
160     default:
161 			break;
162     }
163 
164     String varList = JDXReader.getVarList(tmpDataClass);
165     getHeaderString(tmpDataClass, minY, maxY,
166         xCompFactor, yCompFactor, startIndex, endIndex);
167     out.append("##" + tmpDataClass + "= " + varList + newLine);
168     out.append(tabDataSet);
169     out.append("##END=");
170   }
171 
172   /**
173    * Returns the String for the header of the spectrum
174    * @param tmpDataClass
175    *        the dataclass
176    * @param minY
177    * @param maxY
178    * @param tmpXFactor
179    *        the x factor
180    * @param tmpYFactor
181    *        the y factor
182    * @param startIndex
183    *        the index of the starting coordinate
184    * @param endIndex
185    *        the index of the ending coordinate
186    */
getHeaderString(String tmpDataClass, double minY, double maxY, double tmpXFactor, double tmpYFactor, int startIndex, int endIndex)187   private void getHeaderString(String tmpDataClass,
188                                         double minY, double maxY,
189                                         double tmpXFactor, double tmpYFactor,
190                                         int startIndex, int endIndex) {
191 
192     //final String CORE_STR = "TITLE,ORIGIN,OWNER,DATE,TIME,DATATYPE,JCAMPDX";
193 
194     // start of header
195     out.append("##TITLE= ").append(spectrum.getTitle()).append(
196         newLine);
197     out.append("##JCAMP-DX= 5.01").append(newLine); /*+ getJcampdx()*/
198     out.append("##DATA TYPE= ").append(spectrum.getDataType()).append(
199         newLine);
200     out.append("##DATA CLASS= ").append(tmpDataClass).append(
201         newLine);
202     out.append("##ORIGIN= ").append(spectrum.getOrigin()).append(
203         newLine);
204     out.append("##OWNER= ").append(spectrum.getOwner()).append(
205         newLine);
206     String d = spectrum.getDate();
207     String longdate = "";
208     String currentTime = vwr.apiPlatform.getDateFormat(null);
209     if (spectrum.getLongDate().equals("") || d.length() != 8) {
210       longdate = currentTime + " $$ export date from JSpecView";
211     } else if (d.length() == 8) { // give a 50 year window; Y2K compliant
212       longdate = (d.charAt(0) < '5' ? "20" : "19") + d + " " + spectrum.getTime();
213     } else {
214       longdate = spectrum.getLongDate();
215     }
216     out.append("##LONGDATE= ").append(longdate).append(newLine);
217 
218     // optional header
219     Lst<String[]> headerTable = spectrum.getHeaderTable();
220     for (int i = 0; i < headerTable.size(); i++) {
221       String[] entry = headerTable.get(i);
222       String label = entry[0];
223       String dataSet = entry[1];
224       String nl = (dataSet.startsWith("<") && dataSet.contains("</") ? newLine
225           : "");
226       out.append(label).append("= ").append(nl).append(dataSet).append(
227           newLine);
228     }
229     double observedFreq = spectrum.getObservedFreq();
230     if (!spectrum.is1D())
231       out.append("##NUM DIM= ").append("" + spectrum.getNumDim()).append(
232           newLine);
233     if (observedFreq != JDXDataObject.ERROR)
234       out.append("##.OBSERVE FREQUENCY= ").append("" + observedFreq).append(
235           newLine);
236     String nuc = spectrum.getObservedNucleus();
237     if (!"".equals(nuc))
238       out.append("##.OBSERVE NUCLEUS= ").append(nuc).append(
239           newLine);
240     //now need to put pathlength here
241 
242     // last part of header
243 
244     //boolean toHz = (observedFreq != JDXSpectrum.ERROR && !spec.getDataType()
245       //  .toUpperCase().contains("FID"));
246     out.append("##XUNITS= ").append(spectrum.isHZtoPPM() ? "HZ" : spectrum.getXUnits()).append(
247         newLine);
248     out.append("##YUNITS= ").append(spectrum.getYUnits()).append(
249         newLine);
250     out.append("##XFACTOR= ").append(fixExponentInt(tmpXFactor))
251         .append(newLine);
252     out.append("##YFACTOR= ").append(fixExponentInt(tmpYFactor))
253         .append(newLine);
254     double f = (spectrum.isHZtoPPM() ? observedFreq : 1);
255     Coordinate[] xyCoords = spectrum.getXYCoords();
256     out.append("##FIRSTX= ").append(
257         fixExponentInt(xyCoords[startIndex].getXVal() * f)).append(
258         newLine);
259     out.append("##FIRSTY= ").append(
260         fixExponentInt(xyCoords[startIndex].getYVal())).append(
261         newLine);
262     out.append("##LASTX= ").append(
263         fixExponentInt(xyCoords[endIndex].getXVal() * f)).append(
264         newLine);
265     out.append("##NPOINTS= ").append("" + (Math.abs(endIndex - startIndex) + 1))
266         .append(newLine);
267     out.append("##MINY= ").append(fixExponentInt(minY)).append(
268         newLine);
269     out.append("##MAXY= ").append(fixExponentInt(maxY)).append(
270         newLine);
271   }
272 
areIntegers(Coordinate[] xyCoords, int startIndex, int endIndex, double factor, boolean isX)273   private static boolean areIntegers(Coordinate[] xyCoords, int startIndex,
274                                      int endIndex, double factor, boolean isX) {
275     for (int i = startIndex; i <= endIndex; i++) {
276       double x = (isX ? xyCoords[i].getXVal() : xyCoords[i].getYVal()) / factor;
277       if (isAlmostInteger(x))
278           return false;
279     }
280     return true;
281   }
282 
isAlmostInteger(double x)283 	private static boolean isAlmostInteger(double x) {
284 	  return (x != 0 && Math.abs(x - Math.floor(x)) / x > 1e-8);
285 	}
286 
fixExponentInt(double x)287 	private static String fixExponentInt(double x) {
288 	  return (x == Math.floor(x) ? String.valueOf((int) x) : PT.rep(fixExponent(x), "E+00", ""));
289 	}
290 
291 	/**
292 	 * JCAMP-DX requires 1.5E[+|-]nn or 1.5E[+|-]nnn only
293 	 * not Java's 1.5E3 or 1.5E-2
294 	 *
295 	 * @param x
296 	 * @return exponent fixed
297 	 */
fixExponent(double x)298 	private static String fixExponent(double x) {
299 	  String s = DF.formatDecimalDbl(x, -7); // "0.000000"
300 	  int pt = s.indexOf("E");
301 	  if (pt < 0) {
302 	    return s;
303 	  }
304 	  // 4.3E+3
305 	  // 4.3E-3
306 	  if (s.length() == pt + 3)
307 	    s = s.substring(0, pt + 2) + "0" + s.substring(pt + 2);
308 	  return s;
309 	}
310 
311 
312 }
313