package ika.geoimport; import ika.geo.GeoGrid; import ika.gui.ProgressIndicator; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Locale; import java.util.Scanner; import java.util.StringTokenizer; public class ESRIASCIIGridReader { private static class ESRIASCIIGridHeader { protected int cols = 0; protected int rows = 0; protected double west = Double.NaN; protected double south = Double.NaN; protected double cellSize = Double.NaN; protected float noDataValue = Float.NaN; /* * returns whether valid values have been found */ protected boolean isValid() { return (cols > 0 && rows > 0 && cellSize > 0 && !Double.isNaN(west) && !Double.isNaN(south)); // noDataValue is optional } /** * Reads cols, rows, west, south, cellSize and noDataValue from the header. * Throws an exception if this is not a valid header. * @param scanner Scanner must be initialized to use dot as decimal separator. * @throws IOException */ private void readHeader(Scanner scanner) throws IOException { cols = rows = 0; west = south = cellSize = Double.NaN; noDataValue = Float.NaN; while (scanner.hasNext()) { if (scanner.hasNextDouble()) { // next line starts with number, must be grid break; } String str = scanner.next().trim().toLowerCase(); if (str.equals("ncols")) { this.cols = scanner.nextInt(); } else if (str.equals("nrows")) { this.rows = scanner.nextInt(); } else if (str.equals("xllcenter") || str.equals("xllcorner")) { this.west = scanner.nextDouble(); } else if (str.equals("yllcenter") || str.equals("yllcorner")) { this.south = scanner.nextDouble(); } else if (str.equals("cellsize")) { this.cellSize = scanner.nextDouble(); } else if (str.startsWith("nodata")) { this.noDataValue = scanner.nextFloat(); } else { // make sure the line starts with a number if (!scanner.hasNextDouble()) { throw new IOException(); } // done reading the header break; } } if (!isValid()) { throw new IOException(); } } } /** * Returns whether a scanner references valid data that can be read. * @param scanner * @return * @throws IOException */ public static boolean canRead(Scanner scanner) { try { ESRIASCIIGridHeader header = new ESRIASCIIGridHeader(); header.readHeader(scanner); return header.isValid(); } catch (Exception exc) { return false; } } public static boolean canRead(String filePath) { Scanner scanner = null; try { scanner = createUSScanner(new FileInputStream(filePath)); return ESRIASCIIGridReader.canRead(scanner); } catch (Exception exc) { return false; } finally { if (scanner != null) { try { scanner.close(); } catch (Throwable exc) { } } } } /** Read a Grid from a file in ESRI ASCII format. * @param fileName The path to the file to be read. * @return The read grid. */ public static GeoGrid read(String filePath) throws java.io.IOException { return ESRIASCIIGridReader.read(filePath, null); } /** Read a Grid from a file in ESRI ASCII format. * @param fileName The path to the file to be read. * @param progress A WorkerProgress to inform about the progress. * @return The read grid. */ public static GeoGrid read(String filePath, ProgressIndicator progressIndicator) throws java.io.IOException { File file = new File(filePath); FileInputStream fis = new FileInputStream(file.getAbsolutePath()); GeoGrid grid = ESRIASCIIGridReader.read(fis, progressIndicator); if (progressIndicator != null && progressIndicator.isAborted()) { return null; } String name = file.getName(); if (!"".equals(name)) { grid.setName(name); } return grid; } /** Read a Grid from a stream in ESRI ASCII format. * @param is The stream to read from. The stream is closed at the end. * @param progress A WorkerProgress to inform about the progress. * @return The read grid. */ public static GeoGrid read(InputStream input, ProgressIndicator progressIndicator) throws IOException { // initialize the progress monitor at the beginning if (progressIndicator != null) { progressIndicator.start(); } Scanner scanner = createUSScanner(new BufferedInputStream(input)); try { ESRIASCIIGridHeader header = new ESRIASCIIGridHeader(); header.readHeader(scanner); GeoGrid grid = new GeoGrid(header.cols, header.rows, header.cellSize); grid.setWest(header.west); grid.setNorth(header.south + (header.rows - 1) * header.cellSize); // use legacy StringTokenizer, which is considerably faster than // the Scanner class, which uses regular expressions. StringTokenizer tokenizer = new StringTokenizer(scanner.nextLine(), " "); // read grid values. Rows are ordered top to bottom. for (int row = 0; row < header.rows; row++) { // update progress info if (progressIndicator != null) { int perc = (int) ((double) (row + 1) / header.rows * 100); if (!progressIndicator.progress(perc)) { return null; } } // read one row for (int col = 0; col < header.cols; col++) { // a logical row in the grid does not necesseraly correspond // to a line in the file! if (!tokenizer.hasMoreTokens()) { tokenizer = new StringTokenizer(scanner.nextLine(), " "); } final float v = Float.parseFloat(tokenizer.nextToken()); if (v == header.noDataValue || Float.isNaN(v)) { grid.setValue(Float.NaN, col, row); } else { grid.setValue(v, col, row); } } } return grid; } finally { try { // this closes the input stream scanner.close(); } catch (Exception exc) { } } } /** * Creates a scanner for ASCII text with a period as decimal separator. * @param is * @return * @throws FileNotFoundException */ private static Scanner createUSScanner(InputStream is) throws FileNotFoundException { Scanner scanner = new Scanner(is, "US-ASCII"); scanner.useLocale(Locale.US); return scanner; } }