teichmann@5844: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5844: * Software engineering by Intevation GmbH teichmann@5844: * teichmann@5992: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5844: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5992: * documentation coming with Dive4Elements River for details. teichmann@5844: */ teichmann@5844: teichmann@5829: package org.dive4elements.river.importer.parsers; felix@5808: teichmann@5829: import org.dive4elements.artifacts.common.utils.FileTools; felix@5808: teichmann@5829: import org.dive4elements.river.importer.XY; felix@5808: teichmann@5829: import org.dive4elements.river.importer.parsers.tim.Coordinate; teichmann@5829: teichmann@5829: import org.dive4elements.river.utils.DateGuesser; teichmann@5829: import org.dive4elements.river.utils.EpsilonComparator; felix@5808: felix@5808: import java.io.File; felix@5808: import java.io.IOException; felix@5808: felix@5808: import java.util.ArrayList; felix@5808: import java.util.Calendar; felix@5808: import java.util.Date; felix@5808: import java.util.List; felix@5808: import java.util.Map; felix@5808: import java.util.TreeMap; felix@5808: felix@5808: import org.apache.log4j.Logger; felix@5808: felix@5808: felix@5808: /** felix@5808: * To create cross-sections, generate: Map> from files felix@5808: * in w80/csv format. felix@5808: */ felix@5808: public class W80CSVParser extends LineParser implements CrossSectionParser felix@5808: { felix@5808: /** Private logger. */ felix@5808: private static Logger logger = Logger.getLogger(W80CSVParser.class); felix@5808: felix@5808: felix@5808: /** The current line to which add points. */ felix@5808: private List currentLine; felix@5808: felix@5808: felix@5808: /** Data collected so far, last element will be currentLine. */ felix@5808: protected Map> data; felix@5808: felix@5808: felix@5808: /** Anchor to project to. */ felix@5808: private static class Anchor extends Coordinate { felix@5808: felix@5808: private static final double EPSILON = 1e-5; felix@5808: felix@5808: private double station; felix@5808: felix@5808: public Anchor(double x, double y, double z, double station) { felix@5808: super(x, y, z); felix@5808: this.station = station; felix@5808: } felix@5808: felix@5808: public boolean sameStation(double station) { felix@5808: return Math.abs(this.station - station) < EPSILON; felix@5808: } felix@5808: } felix@5808: felix@5808: felix@5808: /** Reference point for simple projection. */ felix@5808: private Anchor anchor; felix@5808: felix@5808: felix@5808: /** felix@5808: * Reference point for distance calculations, introduced to felix@5808: * deal with bends in the lines. felix@5808: * Array has two entrys: first is GK-Right, second GK-High. felix@5808: */ felix@5808: private double[] lastPointGK; felix@5808: felix@5808: felix@5808: /** Measurement date of anchor as listed in w80 file. */ felix@5808: private Date anchorDate; felix@5808: felix@5808: felix@5808: private double distanceToLastPoint(double gkr, double gkh) { felix@5808: double dx = gkr - lastPointGK[0]; felix@5808: double dy = gkh - lastPointGK[1]; felix@5808: double d = dx*dx + dy*dy; felix@5808: felix@5808: return Math.sqrt(d); felix@5808: } felix@5808: felix@5808: felix@5808: /** Trivial constructor. */ felix@5808: public W80CSVParser() { felix@5808: data = new TreeMap>(EpsilonComparator.CMP); felix@5808: } felix@5808: felix@5808: felix@5808: /** felix@5808: * Get the description of the cross section parsed - felix@5808: * directory name of current file. felix@5808: */ felix@5808: @Override felix@5808: public String getDescription() { felix@5808: return getInputFile().getParentFile().getName(); felix@5808: } felix@5808: felix@5808: felix@5808: /** Get the year of this cross sections measurement. */ felix@5808: @Override felix@5808: public Integer getYear() { felix@5808: if (anchorDate == null) { felix@5808: return null; felix@5808: } felix@5808: Calendar dateCalendar = Calendar.getInstance(); felix@5808: dateCalendar.setTime(anchorDate); felix@5808: return dateCalendar.get(Calendar.YEAR); felix@5808: } felix@5808: felix@5808: felix@5808: /** felix@5808: * Return the data parsed. felix@5808: * @return map of stations (km) to list of points. felix@5808: */ felix@5808: @Override felix@5808: public Map> getData() { felix@5808: return data; felix@5808: } felix@5808: felix@5808: felix@5808: /** Recursively descend root, ask the callback for every file felix@5808: * found and parse it if callback acks. When done, notify callback. */ felix@5808: public void parseW80CSVs(File root, final Callback callback) { felix@5808: felix@5808: FileTools.walkTree(root, new FileTools.FileVisitor() { felix@5808: @Override felix@5808: public boolean visit(File file) { felix@5808: if (file.isFile() && file.canRead() felix@5808: && file.getName().toLowerCase().endsWith(".csv") felix@5808: && (callback == null || callback.accept(file))) { felix@5808: reset(); felix@5808: try { felix@5808: parse(file); felix@5808: logger.info("parsing done"); felix@5808: if (callback != null) { felix@5808: callback.parsed(W80CSVParser.this); felix@5808: } felix@5808: } felix@5808: catch (IOException ioe) { felix@5808: logger.error("IOException while parsing file"); felix@5808: return false; felix@5808: } felix@5808: } felix@5808: return true; felix@5808: } felix@5808: }); felix@5808: } felix@5808: felix@5808: felix@5808: /** Called before consuming first line of file. */ felix@5808: public void reset() { felix@5808: data.clear(); felix@5808: currentLine = new ArrayList(); felix@5808: anchor = null; felix@5808: anchorDate = null; felix@5808: lastPointGK = new double[] {0d,0d}; felix@5808: } felix@5808: felix@5808: felix@5808: /** felix@5808: * Get the Index of the last cross-section lines point. felix@5808: * @return last points index, -1 if not available. felix@5808: */ felix@5808: private int getLastPointIdx() { felix@5808: if (currentLine == null || currentLine.isEmpty()) { felix@5808: return -1; felix@5808: } felix@5808: XY lastPoint = this.currentLine.get(currentLine.size()-1); felix@5808: return lastPoint.getIndex(); felix@5808: } felix@5808: felix@5808: felix@5808: private double getLastPointX() { felix@5808: if (currentLine == null || currentLine.isEmpty()) { felix@5808: return 0d; felix@5808: } felix@5808: XY lastPoint = this.currentLine.get(currentLine.size()-1); felix@5808: return lastPoint.getX(); felix@5808: } felix@5808: felix@5808: felix@5808: /** felix@5808: * Add a Point (YZ,Index) to the current cross section line. felix@6514: * @param y The y coordinate of new point in GK. felix@6514: * @param z The z coordinate of new point in GK. felix@6514: * @param height The hight (3rd coord) of point, in meter. felix@5808: * @param idx Ignored, the parameter of new point. felix@5808: * @return true if point could been added, false otherwise (e.g. not felix@5808: * parsable y or z values. felix@5808: */ felix@5808: private boolean addPoint(double gkr, double gkh, double height, String idx) { felix@5808: // Calculate distance between this and lst point (add distances). felix@5808: double d = distanceToLastPoint(gkr, gkh); felix@5808: double totalX = getLastPointX() + d; felix@5808: felix@5808: // We ignore idx, and increment instead. felix@5808: int index; felix@5808: int lastPointIdx = getLastPointIdx(); felix@5808: if (lastPointIdx <= 0) { felix@5808: index = 1; felix@5808: } else { felix@5808: index = lastPointIdx + 1; felix@5808: } felix@5808: felix@5808: this.lastPointGK[0] = gkr; felix@5808: this.lastPointGK[1] = gkh; felix@6268: currentLine.add(new XY(totalX, height, index)); felix@5808: return true; felix@5808: } felix@5808: felix@5808: // As per documentation: felix@5808: // BW;WPA;ST;UF;PN;LS;BL-LS;Y;X;Z;DL;LZK;SY;SX;SZ;BML;HS;BL-HS;H;DH;HZK;SH;WVA;BMH;BMP;DST;DB;LDS;LKZ; felix@5808: felix@5808: felix@5808: /** felix@5808: * Called for each line. Try to extract info from a w80 line. felix@5808: * @param lineNum Number of line (starting with 1). felix@5808: */ felix@5808: @Override felix@5808: protected void handleLine(int lineNum, String line) { felix@5808: // First two lines are 'comment'-like. felix@5808: if (lineNum == 1 || lineNum == 2) { felix@5808: return; felix@5808: } felix@5808: // The 'shore' field shows which side of the river the shore is measured. felix@5808: // Therefore, the points have to be added in the correct order (also felix@5808: // because later distances are calculated which cannot be felix@5808: // negative. felix@5808: String[] fields = line.split(";"); felix@5808: String station = fields[2]; felix@5808: String shore = fields[3]; felix@5808: // TODO: There is 'station' and a 'shore'-code behind. felix@5808: // 1 = left, 2 = right. none = middle felix@5808: String pointIndex = line.substring(16,21); felix@5808: // For GK, first seven digits are of interest. felix@5808: String gkRight = fields[7]; felix@5808: String gkHigh = fields[8]; felix@5808: String date = fields[10]; felix@5808: String height = fields[18]; felix@5808: String dateH = line.substring(54,60); felix@5808: String dateDec = line.substring(64,70); felix@5808: felix@5922: double stationKm; felix@5922: double gkRightKm; felix@5922: double gkHighKm; felix@5922: double heightM; felix@5922: felix@5922: try { felix@5922: stationKm = Double.parseDouble(station) / 1000d; felix@5922: gkRightKm = Double.parseDouble(gkRight.replace(",","."));//.substring(0,7)); felix@5922: gkHighKm = Double.parseDouble(gkHigh.replace(",","."));//.substring(0,7)); felix@5922: heightM = Double.parseDouble(height.replace(",",".")); felix@5922: } felix@5922: catch (java.lang.NumberFormatException nfe) { felix@5922: logger.error("Skipping malformed w80csv line #" + lineNum); felix@5922: return; felix@5922: } felix@5808: felix@5808: // New (or first) line. felix@5808: if (anchor == null || !anchor.sameStation(stationKm)) { felix@5808: anchor = new Anchor(gkRightKm, gkHighKm, heightM, stationKm); felix@5808: lastPointGK[0] = gkRightKm; felix@5808: lastPointGK[1] = gkHighKm; felix@5808: currentLine = new ArrayList(); felix@5808: data.put(stationKm, currentLine); felix@5808: currentLine.add(new XY(0d, heightM, 0)); felix@5808: try { felix@5808: anchorDate = DateGuesser.guessDate(date); felix@5808: } felix@5808: catch (IllegalArgumentException iae) { felix@5808: logger.warn("W80CSV: Invalid date '" + date + "'."); felix@5808: } felix@5808: } felix@5808: else { felix@5808: addPoint(gkRightKm, gkHighKm, heightM, pointIndex); felix@5808: } felix@5808: } felix@5808: felix@5808: felix@5808: /** Called when file is fully consumed. */ felix@5808: @Override felix@5808: protected void finish() { felix@5808: logger.info("Parsed " + data.size() + " lines"); felix@5808: } felix@5808: felix@5808: felix@5808: /** Parses files given as arguments. */ felix@5808: public static void main(String [] args) { felix@5808: felix@5808: W80CSVParser parser = new W80CSVParser(); felix@5808: felix@5808: logger.warn("Start parsing files."); felix@5808: for (String arg: args) { felix@5808: logger.warn("Parsing a file."); felix@5808: parser.parseW80CSVs(new File(arg), null); felix@5808: } felix@5808: logger.error("Finished parsing files."); felix@5808: } felix@5808: } felix@5808: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :