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@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: aheinecke@7615: import org.apache.commons.lang.StringUtils; gernotbelger@9299: import org.apache.log4j.Logger; teichmann@5831: import org.dive4elements.river.artifacts.model.WstLine; 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: teichmann@8202: /** The log used in this class. */ teichmann@8202: private static Logger log = Logger.getLogger(WstWriter.class); ingo@446: felix@3147: /** The default unit that is written into the header of the WST. */ gernotbelger@9299: public static final String DEFAULT_UNIT = "Wasserstand [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: gernotbelger@9299: /** gernotbelger@9299: * Workaround for one use of wrongly imported files: ignore the Qs at gernotbelger@9299: * all. gernotbelger@9299: */ felix@7658: protected boolean ignoreQs; ingo@446: ingo@446: /** felix@3284: * This constructor creates a new WstWriter with a number of Q columns. ingo@446: * gernotbelger@9299: * @param columns gernotbelger@9299: * The number of columns of the resulting WST. ingo@446: */ gernotbelger@9299: public WstWriter(final int columns) { felix@7658: this(columns, false); felix@7658: } felix@7658: felix@7658: /** felix@7658: * This constructor creates a new WstWriter with a number of Q columns. felix@7658: * gernotbelger@9299: * @param columns gernotbelger@9299: * The number of columns of the resulting WST. gernotbelger@9299: * @param workaroundIgnoreQs gernotbelger@9299: * do not write QLines to shadow broken data. felix@7658: */ gernotbelger@9299: public WstWriter(final int columns, final boolean workaroundIgnoreQs) { gernotbelger@9299: this.cols = columns; gernotbelger@9299: this.columnNames = new ArrayList<>(this.cols); gernotbelger@9299: this.lines = new HashMap<>(); gernotbelger@9299: this.qs = new double[this.cols]; gernotbelger@9299: this.locale = Locale.US; gernotbelger@9299: this.ignoreQs = workaroundIgnoreQs; 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. gernotbelger@9299: * gernotbelger@9299: * @param out gernotbelger@9299: * Where to write to. ingo@446: */ gernotbelger@9299: public void write(final OutputStream out) { teichmann@8202: log.info("WstWriter.write"); ingo@446: gernotbelger@9299: final PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out))); ingo@446: gernotbelger@9299: this.qs = new double[this.cols]; ingo@749: ingo@446: writeHeader(writer); ingo@446: gernotbelger@9299: final Collection collection = new TreeMap(this.lines).values(); ingo@446: gernotbelger@9299: for (final 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: * This method is used to add a new line to the WST. ingo@446: * gernotbelger@9299: * @param wqkms gernotbelger@9299: * A 3dim double array with [W, Q, KM]. ingo@446: */ gernotbelger@9299: public void add(final double[] wqkms) { gernotbelger@9299: final Double km = wqkms[2]; ingo@446: gernotbelger@9299: WstLine line = this.lines.get(km); ingo@446: ingo@446: if (line == null) { ingo@446: line = new WstLine(km.doubleValue()); gernotbelger@9299: this.lines.put(km, line); ingo@446: } ingo@446: ingo@446: line.add(wqkms[0], wqkms[1]); ingo@446: } ingo@446: gernotbelger@9299: public void addCorrected(final double[] wqckms) { gernotbelger@9299: final Double km = wqckms[2]; ingo@446: gernotbelger@9299: WstLine line = this.lines.get(km); ingo@749: ingo@749: if (line == null) { ingo@749: line = new WstLine(km.doubleValue()); gernotbelger@9299: this.lines.put(km, line); ingo@749: } ingo@749: ingo@749: line.add(wqckms[3], wqckms[1]); ingo@749: } ingo@749: ingo@446: /** ingo@447: * Adds a further column name. ingo@447: * gernotbelger@9299: * @param name gernotbelger@9299: * The name of the new column. ingo@447: */ ingo@447: public void addColumn(String name) { ingo@447: if (name != null) { gernotbelger@9299: this.cols++; ingo@749: aheinecke@7654: int i = 0; gernotbelger@9299: final String basename = name; gernotbelger@9299: while (this.columnNames.contains(name)) { ingo@749: name = basename + "_" + i++; ingo@749: } ingo@749: gernotbelger@9299: this.columnNames.add(name); ingo@447: } ingo@447: } ingo@447: ingo@447: /** ingo@446: * This method writes the header of the WST. ingo@446: * gernotbelger@9299: * @param writer gernotbelger@9299: * The PrintWriter that creates the output. ingo@446: */ gernotbelger@9299: protected void writeHeader(final PrintWriter writer) { teichmann@8202: log.debug("WstWriter.writeHeader"); ingo@446: gernotbelger@9299: writer.println(this.cols); aheinecke@7654: aheinecke@7654: writer.print("*!column-bez-text "); aheinecke@7654: gernotbelger@9299: final List quotedNames = new ArrayList<>(this.columnNames.size()); aheinecke@7654: gernotbelger@9299: for (String name : this.columnNames) { aheinecke@7654: if (name.contains(" ")) { aheinecke@7663: name = '"' + name + '"'; aheinecke@7654: } aheinecke@7654: quotedNames.add(name); aheinecke@7654: } aheinecke@7654: writer.println(StringUtils.join(quotedNames, " ")); ingo@447: writer.print(" "); ingo@446: gernotbelger@9299: for (final String columnName : this.columnNames) { aheinecke@7654: if (columnName.length() > 9) { gernotbelger@9299: writer.printf(this.locale, "%9s", columnName.substring(columnName.length() - 9)); aheinecke@7654: } else { gernotbelger@9299: writer.printf(this.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: * This method writes a line with W values and a certain kilometer. ingo@446: * gernotbelger@9299: * @param writer gernotbelger@9299: * The PrintWriter that is used to create the output. gernotbelger@9299: * @param line gernotbelger@9299: * The WstLine that should be written to the output. ingo@446: */ gernotbelger@9299: protected void writeWLine(final PrintWriter writer, final WstLine line) { gernotbelger@9299: final double km = line.getKm(); gernotbelger@9299: final double[] qs = line.getQs(); gernotbelger@9299: final int num = line.getSize(); ingo@446: gernotbelger@9299: if (!this.ignoreQs && dischargesChanged(qs)) { ingo@446: writeQLine(writer, qs); ingo@446: } ingo@446: gernotbelger@9299: writer.printf(this.locale, "%8.3f", km); ingo@446: ingo@446: for (int i = 0; i < num; i++) { gernotbelger@9299: writer.printf(this.locale, "%9.2f", line.getW(i)); ingo@446: } ingo@446: ingo@446: writer.println(); ingo@446: } ingo@446: ingo@446: /** ingo@446: * Writes a discharge line (Q values) into a WST. ingo@446: * gernotbelger@9299: * @param qs gernotbelger@9299: * the Q values for the next range. ingo@446: */ gernotbelger@9299: protected void writeQLine(final PrintWriter writer, final 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: gernotbelger@9299: writer.printf(this.locale, "%9.2f", qs[i]); ingo@446: } ingo@446: ingo@446: writer.println(); 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: * gernotbelger@9299: * @param newQs gernotbelger@9299: * The Q values of the next line. ingo@446: * ingo@446: * @return true, if a Q value have changed, otherwise false. ingo@446: */ gernotbelger@9299: protected boolean dischargesChanged(final double[] newQs) { ingo@446: // XXX maybe there is a way to do this faster gernotbelger@9299: for (int i = 0; i < this.cols && i < this.qs.length && i < newQs.length; i++) { gernotbelger@9299: if (Math.abs(newQs[i] - this.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() { gernotbelger@9299: return this.lines; aheinecke@7604: } ingo@446: } ingo@446: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :