teichmann@5844: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5844: * Software engineering by Intevation GmbH teichmann@5844: * teichmann@5844: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5844: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5844: * documentation coming with Dive4Elements River for details. teichmann@5844: */ teichmann@5844: teichmann@5829: package org.dive4elements.river.importer.parsers; felix@4681: teichmann@5829: import org.dive4elements.artifacts.common.utils.FileTools; felix@4681: teichmann@5829: import org.dive4elements.river.importer.XY; teichmann@4735: teichmann@5829: import org.dive4elements.river.utils.EpsilonComparator; felix@4681: felix@4681: import java.io.File; felix@4681: import java.io.IOException; felix@4681: teichmann@4735: import java.util.ArrayList; teichmann@4735: import java.util.HashMap; teichmann@4735: import java.util.List; teichmann@4735: import java.util.Map; teichmann@4735: import java.util.TreeMap; felix@4681: teichmann@4735: import java.util.regex.Matcher; teichmann@4735: import java.util.regex.Pattern; felix@4681: teichmann@4735: import org.apache.log4j.Logger; felix@4681: felix@4681: felix@4681: /** felix@4681: * To create cross-sections, generate: Map> from files felix@4681: * in da66 format. felix@4681: */ felix@4715: public class DA66Parser extends LineParser implements CrossSectionParser felix@4681: { felix@4681: /** Private logger. */ felix@4681: private static Logger logger = Logger.getLogger(DA66Parser.class); felix@4681: felix@4712: private static String HEAD_HEAD = "00"; felix@4712: private static String HEAD_GEOM = "66"; // "Values" felix@4712: private static String HEAD_ENDG = "88"; // Probably never used. felix@4712: felix@4717: /** Regex to match lines of files in da66 format. */ felix@4712: private static final Pattern LINE_PATTERN = felix@4712: Pattern.compile("^([0-9 -]{2})" + // Type (00|66|88) felix@4681: "([0-9 -]{5})" + // unset felix@4681: "([0-9 -]{2})" + // id felix@4681: "([0-9 -]{9})" + // station felix@4681: "([0-9 -]{2})" + // running number felix@4712: "([0-9 -]{1})?" + // point id felix@4681: /* felix@4681: Would be great if we could express the pattern as this: felix@4681: ([0-9 -]{1})([0-9 -JKMLMNOPQR]{7})([0-9 -]{7})+ felix@4681: */ felix@4712: "([0-9 -JKMLMNOPQR]{7})?" + // y felix@4712: "([0-9 -]{7})?" + // z felix@4712: "([0-9 -]{1})?" + // point id felix@4712: "([0-9 -JKMLMNOPQR]{7})?" + // y felix@4712: "([0-9 -]{7})?" + // z felix@4712: "([0-9 -]{1})?" + // point id felix@4712: "([0-9 -JKMLMNOPQR]{7})?" + // y felix@4712: "([0-9 -]{7})?" + // z felix@4712: "([0-9 -]{1})?" + // point id felix@4712: "([0-9 -JKMLMNOPQR]{7})?" + // y felix@4712: "([0-9 -]{7})?" // z felix@4681: ); felix@4681: felix@4723: felix@4712: /** Indices to match group of main regex. */ felix@4712: private static enum FIELD { felix@4712: HEAD ( 1), felix@4712: UNSET ( 2), felix@4712: ID ( 3), felix@4712: STATION ( 4), felix@4712: RUNNR ( 5), felix@4712: POINT_1_ID( 6), felix@4712: POINT_1_Y ( 7), felix@4712: POINT_1_Z ( 8), felix@4712: POINT_2_ID( 9), felix@4712: POINT_2_Y (10), felix@4712: POINT_2_Z (11), felix@4712: POINT_3_ID(12), felix@4712: POINT_3_Y (13), felix@4712: POINT_3_Z (14), felix@4712: POINT_4_ID(15), felix@4712: POINT_4_Y (16), felix@4712: POINT_4_Z (17); teichmann@4735: felix@4712: private int idx; felix@4712: FIELD(int idx) { felix@4712: this.idx = idx; felix@4712: } felix@4712: int getIdx() { felix@4712: return idx; felix@4712: } felix@4712: } felix@4712: felix@4723: felix@4712: /** Header lines of da66 can define a type. */ felix@4681: private static enum Type { felix@4681: DATE ( 0), felix@4681: HEKTOSTONE_LEFT ( 1), //grm. "Standlinie" felix@4681: HEKTOSTONE_RIGHT ( 2), felix@4681: CHANNEL_LEFT ( 3), //grm. "Fahrrinne" felix@4681: CHANNEL_RIGHT ( 4), felix@4681: CHANNEL_2_LEFT ( 5), felix@4681: CHANNEL_2_RIGHT ( 6), felix@4681: GIW_1972 ( 7), felix@4681: GROIN_DIST_LEFT ( 8), //grm. "Buhnenkopfabstand links" felix@4681: GROIN_HEIGHT_LEFT ( 9), felix@4681: GROIN_SLOPE_LEFT (10), felix@4681: GROIN_DIST_RIGHT (11), felix@4681: GROIN_HEIGHT_RIGHT (12), felix@4681: GROIN_SLOPE_RIGHT (13), felix@4681: STRIKE_LEFT (14), //grm. "Streichlinie links" felix@4681: AXIS (15), felix@4681: STRIKE_RIGHT (16), felix@4681: GROIN_BACK_SLOPE_LEFT (17), //grm. "Buhnenrueckenneigung" felix@4681: GROIN_BACK_SLOPE_RIGHT (18), felix@4681: GIW_1932 (19), felix@4681: GIW_1982 (20), felix@4681: STAND_ISLAND_1 (21), felix@4681: STAND_ISLAND_2 (22), felix@4681: STAND_ISLAND_3 (23), felix@4681: STAND_ISLAND_4 (24), felix@4681: UNSPECIFIED_1 (25), felix@4681: UNSPECIFIED_2 (26), felix@4681: HHW (27), felix@4681: OLD_PROFILE_NULL (28), felix@4681: AW_1978 (29), felix@4681: SIGN_LEFT (30), felix@4681: SIGN_RIGHT (31), felix@4681: DIST_SIGNAL_CHANNEL_LEFT (32), felix@4681: DIST_SIGNAL_CHANNEL_RIGHT(33), felix@4681: UNSPECIFIED_3 (34), felix@4681: UNSPECIFIED_4 (35), felix@4681: UNSPECIFIED_5 (36), felix@4681: UNSPECIFIED_6 (37), felix@4681: SHORE_LEFT (38), felix@4681: SHORE_RIGHT (39), felix@4681: UNSPECIFIED_7 (40); felix@4681: felix@4681: private final int id; felix@4681: Type(int id) { felix@4681: this.id = id; felix@4681: } felix@4681: public int getId() { felix@4681: return id; felix@4681: } felix@4681: } felix@4681: felix@4723: felix@4681: /** Available types. */ felix@4681: private static HashMap typeMap; felix@4681: felix@4723: felix@4681: /** Types we can deal with. */ felix@4681: private static List implementedTypes; felix@4681: felix@4723: felix@4681: static { felix@4681: typeMap = new HashMap(); felix@4681: for (Type t: Type.values()) { felix@4681: typeMap.put(new Integer(t.getId()), t); felix@4681: } felix@4712: // TODO populate and respect header type. felix@4681: implementedTypes = new ArrayList(); felix@4681: //implementedTypes.add(..); felix@4681: } felix@4681: felix@4712: felix@4712: /** The current line to which add points. */ felix@4712: private List currentLine; felix@4712: felix@4712: felix@4712: /** Data collected so far, last element will be currentLine. */ felix@4681: protected Map> data; felix@4681: felix@4681: felix@4717: /** Trivial constructor. */ felix@4681: public DA66Parser() { teichmann@4735: data = new TreeMap>(EpsilonComparator.CMP); felix@4681: } felix@4681: felix@4717: felix@4717: /** Get the description of the cross section parsed. */ felix@4715: @Override felix@4715: public String getDescription() { felix@4729: return FileTools.removeExtension(getFileName()); felix@4715: } felix@4715: felix@4717: felix@4717: /** Get the year of this cross sections measurement. */ felix@4715: @Override felix@4715: public Integer getYear() { felix@4726: return null; felix@4715: } felix@4715: felix@4717: felix@4717: /** felix@4717: * Return the data parsed. felix@4717: * @return map of stations (km) to list of points. felix@4717: */ felix@4715: @Override felix@4681: public Map> getData() { felix@4681: return data; felix@4681: } felix@4681: felix@4681: felix@4791: /** felix@4791: * Walk a directory tree, parse its *.da66 files and store the felix@4791: * data found. felix@4791: */ felix@4681: public void parseDA66s(File root, final Callback callback) { felix@4681: felix@4681: FileTools.walkTree(root, new FileTools.FileVisitor() { felix@4681: @Override felix@4681: public boolean visit(File file) { felix@4681: if (file.isFile() && file.canRead() felix@4681: && file.getName().toLowerCase().endsWith(".d66") felix@4715: && (callback == null || callback.accept(file))) { felix@4681: reset(); felix@4681: try { felix@4681: parse(file); felix@4681: logger.info("parsing done"); felix@4681: if (callback != null) { felix@4715: callback.parsed(DA66Parser.this); felix@4681: } felix@4681: } felix@4681: catch (IOException ioe) { felix@4681: logger.error("IOException while parsing file"); felix@4681: return false; felix@4681: } felix@4681: } felix@4681: return true; felix@4681: } felix@4681: }); felix@4681: } felix@4681: felix@4681: felix@4712: /** felix@4712: * Get the Index of the last cross-section lines point. felix@4712: * @return last points index, -1 if not available. felix@4712: */ felix@4712: private int lastPointIdx() { felix@4712: if (currentLine == null || currentLine.isEmpty()) { felix@4712: return -1; felix@4712: } felix@4712: XY lastPoint = this.currentLine.get(currentLine.size()-1); felix@4712: return lastPoint.getIndex(); felix@4712: } felix@4712: felix@4712: felix@4720: /** Returns station, deciding if it could in cm, in which case convert. */ felix@4720: private double stationInKm(double station) { felix@4720: if (station > 10000) { felix@4720: return station/100000d; felix@4720: } felix@4720: else { felix@4720: return station; felix@4720: } felix@4720: } felix@4720: felix@4791: felix@4746: /** Apply the convention how to deal with numbers < -99.999 .*/ felix@4746: private String applyLetterConvention(String orig) { felix@4746: if (orig.endsWith("-")) { felix@4746: return "-" + orig.replace("-",""); felix@4746: } felix@4746: else if (orig.endsWith("J")) { felix@4746: return "-" + orig.replace("J","1"); felix@4746: } felix@4746: else if (orig.endsWith("K")) { felix@4746: return "-" + orig.replace("K","2"); felix@4746: } felix@4746: else if (orig.endsWith("L")) { felix@4746: return "-" + orig.replace("L","3"); felix@4746: } felix@4746: else if (orig.endsWith("M")) { felix@4746: return "-" + orig.replace("M","4"); felix@4746: } felix@4746: else if (orig.endsWith("N")) { felix@4746: return "-" + orig.replace("N","5"); felix@4746: } felix@4746: else if (orig.endsWith("O")) { felix@4746: return "-" + orig.replace("O","6"); felix@4746: } felix@4746: else if (orig.endsWith("P")) { felix@4746: return "-" + orig.replace("P","7"); felix@4746: } felix@4746: else if (orig.endsWith("Q")) { felix@4746: return "-" + orig.replace("Q","8"); felix@4746: } felix@4746: else if (orig.endsWith("R")) { felix@4746: return "-" + orig.replace("R","9"); felix@4746: } felix@4746: else { felix@4746: return orig; felix@4746: } felix@4746: } felix@4720: felix@4712: /** felix@4712: * Add a Point (YZ,Index) to the current cross section line. felix@4712: * @param y The y coordinate of new point. felix@4712: * @param z The z coordinate of new point. felix@4712: * @param idx Ignored, the parameter of new point. felix@4712: * @return true if point could been added, false otherwise (e.g. not felix@4712: * parsable y or z values. felix@4712: */ felix@4712: private boolean addPoint(String y, String z, String idx) { felix@4712: if (z == null || y == null || idx == null) { felix@4712: logger.error("Incomplete point definition"); felix@4712: return false; felix@4712: } felix@4712: teichmann@4735: double iy; teichmann@4735: double iz; felix@4746: // Handle letter convention. felix@4746: y = applyLetterConvention(y); felix@4712: try { felix@4727: iy = Double.parseDouble(y) / 1000d; felix@4727: iz = Double.parseDouble(z) / 1000d; felix@4712: } felix@4712: catch(java.lang.NumberFormatException nfe) { felix@4712: logger.error("Could not parse Number: " + nfe.getMessage()); felix@4712: return false; felix@4712: } felix@4712: felix@4712: // We ignore idx, and increment instead. teichmann@4735: int index; felix@4712: int lastPointIdx = lastPointIdx(); felix@4712: if (lastPointIdx <= 0) { felix@4712: index = 1; felix@4712: } else { felix@4712: index = lastPointIdx + 1; felix@4712: } felix@4712: felix@4712: currentLine.add(new XY(iy, iz, index)); felix@4712: return true; felix@4712: } felix@4712: felix@4712: felix@4712: /** Called before consuming first line of file. */ felix@4712: public void reset() { felix@4712: data.clear(); felix@4712: currentLine = new ArrayList(); felix@4712: } felix@4712: felix@4712: felix@4712: /** felix@4712: * Called for each line. Try to extract info from a da66 line. felix@4712: */ felix@4681: @Override felix@4681: protected void handleLine(int lineNum, String line) { felix@4712: String head = line.substring(0,2); felix@4712: if (HEAD_HEAD.equals(head)) { felix@4715: //logger.debug("New station"); felix@4712: Matcher m = LINE_PATTERN.matcher(line); felix@4712: if (m.find()) { felix@4712: // Actually matches! felix@4792: // TODO 'move' last line to match river axis felix@4792: // TODO find river axis intersection felix@4712: currentLine = new ArrayList(); felix@4720: double station = stationInKm(Double.parseDouble(m.group(FIELD.STATION.getIdx()))); felix@4720: data.put(station, currentLine); felix@4712: } felix@4712: else { felix@4712: logger.error("HEAD line bad."); felix@4712: } felix@4681: } felix@4712: else if (HEAD_GEOM.equals(head)) { felix@4681: Matcher m = LINE_PATTERN.matcher(line); felix@4712: if (m.find()) { felix@4712: //logger.info("Station: " + m.group(FIELD.STATION.getIdx())); felix@4712: // TODO if last station differs, error and abort felix@4712: if (m.group(FIELD.POINT_1_ID.getIdx()) != null) { felix@4712: // Point 1 felix@4712: if(addPoint( felix@4712: m.group(FIELD.POINT_1_Y.getIdx()), felix@4712: m.group(FIELD.POINT_1_Z.getIdx()), felix@4712: m.group(FIELD.POINT_1_ID.getIdx()))) { felix@4712: // Point added. felix@4712: } felix@4712: else { felix@4712: // Problematic point. felix@4712: logger.error("A point could not be added"); felix@4712: } felix@4712: } felix@4712: if (m.group(FIELD.POINT_2_ID.getIdx()) != null) { felix@4712: // Point 2 felix@4712: if(addPoint( felix@4712: m.group(FIELD.POINT_2_Y.getIdx()), felix@4712: m.group(FIELD.POINT_2_Z.getIdx()), felix@4712: m.group(FIELD.POINT_2_ID.getIdx()))) { felix@4712: // Point added. felix@4712: } felix@4712: else { felix@4712: // Problematic point. felix@4712: logger.error("A point could not be added"); felix@4712: } felix@4712: } felix@4712: if (m.group(FIELD.POINT_3_ID.getIdx()) != null) { felix@4712: // Point 3 felix@4712: if(addPoint( felix@4712: m.group(FIELD.POINT_3_Y.getIdx()), felix@4712: m.group(FIELD.POINT_3_Z.getIdx()), felix@4712: m.group(FIELD.POINT_3_ID.getIdx()))) { felix@4712: // Point added. felix@4712: } felix@4712: else { felix@4712: // Problematic point. felix@4712: logger.error("A point could not be added"); felix@4712: } felix@4712: } felix@4712: if (m.group(FIELD.POINT_4_ID.getIdx()) != null) { felix@4712: // Point 4 felix@4712: if(addPoint( felix@4712: m.group(FIELD.POINT_4_Y.getIdx()), felix@4712: m.group(FIELD.POINT_4_Z.getIdx()), felix@4712: m.group(FIELD.POINT_4_ID.getIdx()))) { felix@4712: // Point added. felix@4712: } felix@4712: else { felix@4712: // Problematic point. felix@4712: logger.error("A point could not be added"); felix@4712: } felix@4712: } felix@4712: } felix@4712: else { felix@4712: logger.warn("Line could not be parsed: "); felix@4712: logger.warn(line); felix@4712: } felix@4681: } felix@4712: else if (HEAD_GEOM.equals(head)) { felix@4712: logger.debug("Hit a 88"); felix@4712: } felix@4712: else { felix@4712: logger.error("Do not know how to treat da66 line:"); felix@4712: logger.error(line); felix@4712: } felix@4712: } felix@4712: felix@4712: felix@4712: /** Called when file is fully consumed. */ felix@4712: @Override felix@4712: protected void finish() { felix@4792: // TODO 'move' last line to match river axis felix@4712: logger.info("Parsed " + data.size() + " lines"); felix@4681: } felix@4681: felix@4681: felix@4681: /** Parses files given as arguments. */ felix@4681: public static void main(String [] args) { felix@4681: felix@4681: DA66Parser parser = new DA66Parser(); felix@4681: felix@4719: logger.warn("Start parsing files."); felix@4681: for (String arg: args) { felix@4681: parser.parseDA66s(new File(arg), null); felix@4681: logger.warn("Parsing a file."); felix@4681: } felix@4719: logger.error("Finished parsing files."); felix@4681: } felix@4681: } felix@4681: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :