teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.exports; ingo@446: ingo@446: import java.io.BufferedWriter; ingo@446: import java.io.OutputStream; ingo@446: import java.io.OutputStreamWriter; ingo@446: import java.io.PrintWriter; ingo@446: ingo@447: import java.util.ArrayList; ingo@446: import java.util.Collection; ingo@446: import java.util.HashMap; ingo@447: import java.util.List; ingo@446: import java.util.Locale; ingo@446: import java.util.Map; ingo@446: import java.util.TreeMap; ingo@446: ingo@446: import org.apache.log4j.Logger; aheinecke@7615: import org.apache.commons.lang.StringUtils; ingo@446: teichmann@5831: import org.dive4elements.river.artifacts.model.WstLine; ingo@446: ingo@446: ingo@446: /** ingo@446: * A writer that creates WSTs. ingo@446: * felix@7656: * Wst files follow this basic structure: felix@7656: * felix@7656: * HEADER felix@7656: * Q-LINE felix@7656: * W-LINES felix@7656: * Q-LINE felix@7656: * W-LINES felix@7656: * ... felix@7656: * felix@7656: * where each *LINE consists of X columns that are specified in the header. felix@7656: * ingo@446: * @author Ingo Weinzierl ingo@446: */ ingo@446: public class WstWriter { ingo@446: felix@3147: /** The logger used in this class. */ ingo@446: private static Logger logger = Logger.getLogger(WstWriter.class); ingo@446: felix@3147: /** The default unit that is written into the header of the WST. */ ingo@446: public static final String DEFAULT_UNIT = "Wassserstand [NN + m]"; ingo@446: felix@3147: /** The lines that need to be included for the export. */ ingo@446: protected Map lines; ingo@446: felix@3147: /** The column names. */ ingo@447: protected List columnNames; ingo@447: felix@3147: /** The locale used to format the values. */ ingo@446: protected Locale locale; ingo@446: felix@3147: /** The number of discharge columns. */ ingo@446: protected int cols; ingo@446: felix@3147: /** The last Q values. */ ingo@446: protected double[] qs; ingo@446: ingo@446: ingo@446: /** felix@3284: * This constructor creates a new WstWriter with a number of Q columns. ingo@446: * felix@7656: * @param columns The number of columns of the resulting WST. ingo@446: */ felix@7656: public WstWriter(int columns) { felix@7656: this.cols = columns; ingo@447: this.columnNames = new ArrayList(cols); ingo@447: this.lines = new HashMap(); ingo@447: this.qs = new double[cols]; ingo@447: this.locale = Locale.US; ingo@446: } ingo@446: ingo@446: ingo@446: /** ingo@446: * This method is used to create the WST from the data that has been ingo@446: * inserted using add(double[]) before. felix@3284: * @param out Where to write to. ingo@446: */ ingo@446: public void write(OutputStream out) { ingo@446: logger.info("WstWriter.write"); ingo@446: ingo@446: PrintWriter writer = new PrintWriter( ingo@446: new BufferedWriter( ingo@446: new OutputStreamWriter(out))); ingo@446: ingo@749: this.qs = new double[cols]; ingo@749: ingo@446: writeHeader(writer); ingo@446: ingo@446: Collection collection = new TreeMap(lines).values(); ingo@446: ingo@446: for (WstLine line: collection) { ingo@446: writeWLine(writer, line); ingo@446: } ingo@446: ingo@446: writer.flush(); ingo@446: writer.close(); ingo@446: } ingo@446: ingo@446: ingo@446: /** ingo@446: * This method is used to add a new line to the WST. ingo@446: * felix@7656: * @param wqkms A 3dim double array with [W, Q, KM]. ingo@446: */ ingo@446: public void add(double[] wqkms) { ingo@446: Double km = wqkms[2]; ingo@446: ingo@446: WstLine line = lines.get(km); ingo@446: ingo@446: if (line == null) { ingo@446: line = new WstLine(km.doubleValue()); ingo@446: lines.put(km, line); ingo@446: } ingo@446: ingo@446: line.add(wqkms[0], wqkms[1]); ingo@446: } ingo@446: ingo@446: ingo@749: public void addCorrected(double[] wqckms) { ingo@749: Double km = wqckms[2]; ingo@749: ingo@749: WstLine line = lines.get(km); ingo@749: ingo@749: if (line == null) { ingo@749: line = new WstLine(km.doubleValue()); ingo@749: lines.put(km, line); ingo@749: } ingo@749: ingo@749: line.add(wqckms[3], wqckms[1]); ingo@749: } ingo@749: ingo@749: ingo@446: /** ingo@447: * Adds a further column name. ingo@447: * ingo@447: * @param name The name of the new column. ingo@447: */ ingo@447: public void addColumn(String name) { ingo@447: if (name != null) { ingo@749: cols++; ingo@749: aheinecke@7654: int i = 0; ingo@749: String basename = name; ingo@749: while (columnNames.contains(name)) { ingo@749: name = basename + "_" + i++; ingo@749: } ingo@749: ingo@447: columnNames.add(name); ingo@447: } ingo@447: } ingo@447: ingo@447: ingo@447: /** ingo@446: * This method writes the header of the WST. ingo@446: * ingo@446: * @param writer The PrintWriter that creates the output. ingo@446: */ ingo@446: protected void writeHeader(PrintWriter writer) { ingo@446: logger.debug("WstWriter.writeHeader"); ingo@446: ingo@446: writer.println(cols); aheinecke@7654: aheinecke@7654: writer.print("*!column-bez-text "); aheinecke@7654: aheinecke@7654: List quotedNames = new ArrayList(columnNames.size()); aheinecke@7654: aheinecke@7654: for (String name: columnNames) { aheinecke@7654: if (name.contains(" ")) { aheinecke@7654: name = "'" + name + "'"; aheinecke@7654: } aheinecke@7654: quotedNames.add(name); aheinecke@7654: } aheinecke@7654: writer.println(StringUtils.join(quotedNames, " ")); ingo@447: writer.print(" "); ingo@446: aheinecke@7654: for (String columnName: columnNames) { aheinecke@7654: if (columnName.length() > 9) { aheinecke@7654: writer.printf(locale, "%9s", aheinecke@7654: columnName.substring(columnName.length() - 9)); aheinecke@7654: } else { aheinecke@7654: writer.printf(locale, "%9s", columnName); aheinecke@7654: // This is weird but i was to lazy to lookup aheinecke@7654: // how to do this another way. aheinecke@7654: for (int i = 9 - columnName.length(); i > 0; i--) { aheinecke@7654: writer.print(" "); aheinecke@7654: } aheinecke@7654: } aheinecke@7654: } ingo@447: ingo@447: writer.println(); ingo@446: ingo@446: writer.write("* KM "); ingo@446: writer.write(DEFAULT_UNIT); ingo@446: writer.println(); ingo@446: } ingo@446: ingo@446: ingo@446: /** ingo@446: * This method writes a line with W values and a certain kilometer. ingo@446: * ingo@446: * @param writer The PrintWriter that is used to create the output. ingo@446: * @param line The WstLine that should be written to the output. ingo@446: */ ingo@446: protected void writeWLine(PrintWriter writer, WstLine line) { ingo@446: double km = line.getKm(); ingo@446: double[] qs = line.getQs(); ingo@446: int num = line.getSize(); ingo@446: ingo@446: if (dischargesChanged(qs)) { ingo@446: writeQLine(writer, qs); ingo@446: } ingo@446: ingo@446: writer.printf(locale, "%8.3f", km); ingo@446: ingo@446: for (int i = 0; i < num; i++) { ingo@446: writer.printf(locale, "%9.2f", line.getW(i)); ingo@446: } ingo@446: ingo@446: writer.println(); ingo@446: } ingo@446: ingo@446: ingo@446: /** ingo@446: * Writes a discharge line (Q values) into a WST. ingo@446: * ingo@446: * @param qs the Q values for the next range. ingo@446: */ ingo@446: protected void writeQLine(PrintWriter writer, double[] qs) { ingo@446: writer.write("*\u001f "); ingo@446: ingo@749: for (int i = 0; i < qs.length; i++) { ingo@446: this.qs[i] = qs[i]; ingo@446: ingo@446: writer.printf(locale, "%9.2f", qs[i]); ingo@446: } ingo@446: ingo@446: writer.println(); ingo@446: } ingo@446: ingo@446: ingo@446: /** ingo@446: * This method determines if a Q has changed from the last line to the ingo@446: * current one. ingo@446: * ingo@446: * @param newQs The Q values of the next line. ingo@446: * ingo@446: * @return true, if a Q value have changed, otherwise false. ingo@446: */ ingo@446: protected boolean dischargesChanged(double[] newQs) { ingo@446: // XXX maybe there is a way to do this faster aheinecke@7604: for (int i = 0; i < cols && i < qs.length && i < newQs.length; i++) { ingo@446: if (Math.abs(newQs[i] - qs[i]) >= 0.001) { ingo@446: return true; ingo@446: } ingo@446: } ingo@446: ingo@446: return false; ingo@446: } aheinecke@7604: aheinecke@7604: /** aheinecke@7604: * Get the lines that have alreay been added to this writer felix@7656: * lines are a map with km as the key and a wstline as value. felix@7656: */ aheinecke@7604: public Map getLines() { aheinecke@7604: return lines; aheinecke@7604: } ingo@446: } ingo@446: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :