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 :