package edu.oregonstate.cartography.grid; import edu.oregonstate.cartography.gui.ProgressIndicator; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.StringTokenizer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class EsriASCIIGridReader { volatile private Exception producerConsumerException = null; private EsriASCIIGridReader() { } /** * Returns whether a reader references valid data that can be read. * @param br * @return */ public static boolean canRead(BufferedReader br) { try { GridHeaderImporter header = new GridHeaderImporter(); header.readHeader(br, true); return header.isValid(); } catch (IOException exc) { return false; } } /** * Returns whether a file references valid data that can be rea * @param filePath * @return */ public static boolean canRead(String filePath) { BufferedReader br = null; try { File file = new File(filePath); FileInputStream fis = new FileInputStream(file.getAbsolutePath()); InputStreamReader in = new InputStreamReader(fis); br = new BufferedReader(in); return EsriASCIIGridReader.canRead(br); } catch (FileNotFoundException exc) { return false; } finally { if (br != null) { try { br.close(); } catch (Throwable exc) { } } } } /** Read a Grid from a file in ESRI ASCII format. * @param filePath The path to the file to be read. * @return The read grid. * @throws java.io.IOException */ public static Grid read(String filePath) throws java.io.IOException { return EsriASCIIGridReader.read(filePath, null); } /** Read a Grid from a file in ESRI ASCII format. * @param filePath The path to the file to be read. * @param progressIndicator A WorkerProgress to inform about the progress. * @return The read grid. * @throws java.io.IOException */ public static Grid read(String filePath, ProgressIndicator progressIndicator) throws java.io.IOException { File file = new File(filePath); InputStream fis = new FileInputStream(file.getAbsolutePath()); EsriASCIIGridReader esriReader = new EsriASCIIGridReader(); Grid grid = esriReader.read(fis, progressIndicator); if (progressIndicator != null && progressIndicator.isCancelled()) { return null; } return grid; } /** Read a grid from an InputStream. * @param input The stream to read from. * @param progressIndicator A WorkerProgress to inform about the progress. * @return The read grid. * @throws java.io.IOException */ public static Grid readStream(InputStream input, ProgressIndicator progressIndicator) throws IOException { EsriASCIIGridReader esriReader = new EsriASCIIGridReader(); Grid grid = esriReader.read(input, progressIndicator); if (progressIndicator != null && progressIndicator.isCancelled()) { return null; } return grid; } /** Read a grid from a stream in ESRI ASCII format. * @param input The stream to read from. The stream is closed at the end. * @param progressIndicator A WorkerProgress to inform about the progress. * @return The read grid. * @throws java.io.IOException */ public Grid read(InputStream input, ProgressIndicator progressIndicator) throws IOException { // initialize the progress monitor at the beginning if (progressIndicator != null) { progressIndicator.start(); } BufferedReader br = null; try { InputStreamReader in = new InputStreamReader(input); br = new BufferedReader(in); GridHeaderImporter header = new GridHeaderImporter(); String firstGridLine = header.readHeader(br, true); Grid grid = new Grid(header.getCols(), header.getRows(), header.getCellSize()); grid.setWest(header.getWest()); grid.setSouth(header.getSouth()); // http://www.java2s.com/Code/Java/Threads/ProducerconsumerforJ2SE15usingconcurrent.htm // a limited capacity of around 64 works fastest on the system used // for development with large grids BlockingQueue<String> q = new LinkedBlockingQueue<>(64); q.put(firstGridLine); Producer producer = new Producer(q, br); Thread producerThread = new Thread(producer); producerThread.start(); Consumer consumer = new Consumer(q, grid, header.getNoDataValue(), progressIndicator); Thread consumerThread = new Thread(consumer); consumerThread.start(); try { producerThread.join(); } catch (InterruptedException ex) { consumerThread.interrupt(); } consumerThread.join(); return grid; } catch (InterruptedException ex) { return null; } finally { try { if (br != null) { br.close(); } } catch (IOException exc) { } if (producerConsumerException != null) { throw new IOException(producerConsumerException); } } } /** * Indicates end of file. */ private static final String EOF = "END_OF_FILE"; /** * Reads the grid body line by line. */ private class Producer implements Runnable { private final BlockingQueue<String> queue; private final BufferedReader reader; Producer(BlockingQueue<String> queue, BufferedReader reader) { this.queue = queue; this.reader = reader; } @Override public void run() { try { // read file line by line and store read lines in blocking queue String line; while ((line = reader.readLine()) != null // check whether this thread has been interrupted && !Thread.currentThread().isInterrupted() // check whether consumer thread has encountered an exception && producerConsumerException == null) { queue.put(line); } // add end-of-file object queue.put(EOF); } catch (IOException | InterruptedException ex) { // store the exception for the main thread producerConsumerException = ex; } } } /** * Parses grid lines. */ private class Consumer implements Runnable { private final BlockingQueue<String> queue; private final float noDataValue; private final Grid grid; private final ProgressIndicator progressIndicator; private int counter = 0; Consumer(BlockingQueue<String> queue, Grid grid, float noDataValue, ProgressIndicator progressIndicator) { this.queue = queue; this.grid = grid; this.noDataValue = noDataValue; this.progressIndicator = progressIndicator; } @Override public void run() { try { final int nCols = grid.getCols(); final int nRows = grid.getRows(); final int nbrValues = nRows * nCols; do { String str = queue.take(); // test for end of file if (EOF.equals(str)) { // make sure the correct number of values has been read if (counter != nbrValues) { throw new IOException("invalid Esri Ascii grid file"); } break; } // split each line in tokens and parse the tokens for a float. // One row in the grid might not correspond to a grid row. // StringTokenizer is faster than String.split() // About 50% of the time in this spent with tokenizing the string, // and 50% is spent with Float.parseFloat(). StringTokenizer tokenizer = new StringTokenizer(str, " \t"); while (tokenizer.hasMoreTokens() // check whether this thread has been interrupted && !Thread.currentThread().isInterrupted() // check whether producer thread has encountered an exception && producerConsumerException == null) { int col = counter % nCols; int row = counter / nCols; ++counter; // make sure we do not read too many cell values if (counter > nbrValues) { throw new IOException("corrupt Esri Ascii grid file"); } float v = Float.parseFloat(tokenizer.nextToken()); grid.setValue(v == noDataValue ? Float.NaN : v, col, row); } // update progress info if (progressIndicator != null) { int row = counter / nCols; int perc = (int) ((row + 1d) / nRows * 100); if (!progressIndicator.progress(perc)) { counter = nbrValues; break; } } } while (counter < nbrValues // check whether this thread has been interrupted && !Thread.currentThread().isInterrupted() // check whether producer thread has encountered an exception && producerConsumerException == null); } catch (IOException | InterruptedException | NumberFormatException ex) { // store the exception for the main thread producerConsumerException = ex; } } } }