001/* 002 * Copyright (c) 2015 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2018-10-16 03:23:34 +0000 (Tue, 16 Oct 2018) $' 007 * '$Revision: 34712 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029package org.kepler.gis.util; 030 031import java.io.BufferedReader; 032import java.io.File; 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.InputStreamReader; 036import java.util.Collections; 037import java.util.HashSet; 038import java.util.Set; 039import java.util.regex.Matcher; 040import java.util.regex.Pattern; 041import java.util.List; 042import java.util.ArrayList; 043 044import org.apache.commons.io.FilenameUtils; 045import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader; 046import org.geotools.coverage.grid.io.AbstractGridFormat; 047import org.geotools.coverage.grid.io.GridFormatFinder; 048import org.geotools.coverage.grid.io.UnknownFormat; 049import org.geotools.factory.Hints; 050import org.geotools.geometry.GeneralEnvelope; 051import org.geotools.geometry.jts.ReferencedEnvelope; 052import org.geotools.referencing.CRS; 053import org.kepler.gis.data.RasterToken; 054import org.opengis.coverage.grid.GridCoverage; 055import org.opengis.coverage.grid.GridEnvelope; 056import org.opengis.referencing.FactoryException; 057import org.opengis.referencing.crs.CoordinateReferenceSystem; 058 059import ptolemy.kernel.util.IllegalActionException; 060 061/** A collection of utilities for rasters. 062 * 063 * @author Daniel Crawl 064 * @version $Id: RasterUtilities.java 34712 2018-10-16 03:23:34Z crawl $ 065 * 066 */ 067public class RasterUtilities { 068 069 /** This class cannot be instantiated. */ 070 private RasterUtilities() { } 071 072 /** Get a coverage for a raster file. 073 * @return Returns null if the file is an unknown format. 074 */ 075 public static GridCoverage getCoverage(File file, CoordinateReferenceSystem defaultCRS) throws IllegalArgumentException, IOException { 076 AbstractGridCoverage2DReader reader = getReader(file, defaultCRS); 077 if(reader != null) { 078 return reader.read(null); 079 } 080 return null; 081 } 082 083 /** Get the grid size of a raster. 084 * @return The width and height. 085 */ 086 public static Integer[] getGridSize(RasterToken raster) throws IllegalActionException { 087 088 Integer[] sizes = new Integer[2]; 089 090 File rasterFile = raster.rasterFile(); 091 if(rasterFile != null) { 092 throw new IllegalActionException("Getting cell size from file not supported. Use RasterReader to first read the raster."); 093 } 094 095 GridEnvelope grid = raster.reader().getOriginalGridRange(); 096 sizes[0] = grid.getSpan(0); 097 sizes[1] = grid.getSpan(1); 098 099 return sizes; 100 } 101 102 /** Get a reader for a raster file. 103 * @return Returns null if file is unknown format. 104 */ 105 public static AbstractGridCoverage2DReader getReader(File file, CoordinateReferenceSystem defaultCRS) { 106 AbstractGridFormat format = GridFormatFinder.findFormat(file); 107 if(!(format instanceof UnknownFormat)) { 108 109 AbstractGridCoverage2DReader reader = format.getReader(file); 110 if(defaultCRS == null) { 111 reader = format.getReader(file); 112 } else { 113 Hints hints = new Hints(); 114 hints.put(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, defaultCRS); 115 reader = format.getReader(file, hints); 116 } 117 118 if(reader != null) { 119 _readers.add(reader); 120 } else { 121 System.err.println("ERROR: null reader for " + file); 122 } 123 return reader; 124 } 125 return null; 126 } 127 128 /** Close all readers opened by getReader() and getCoverage(). */ 129 public static void closeReaders() { 130 131 synchronized(_readers) { 132 for(AbstractGridCoverage2DReader reader : _readers) { 133 reader.dispose(); 134 } 135 _readers.clear(); 136 } 137 } 138 139 /** Convert raster file formats using gdal_translate. */ 140 public static boolean convertFileFormat(File inputFile, File outputFile, 141 Integer noData) { 142 143 List<String> args = new ArrayList<String>(); 144 args.add("gdal_translate"); 145 146 147 if(noData != null) { 148 args.add("-a_nodata"); 149 args.add(noData.toString()); 150 } 151 152 args.add(inputFile.getAbsolutePath()); 153 args.add(outputFile.getAbsolutePath()); 154 155 ProcessBuilder builder = new ProcessBuilder(args); 156 builder.redirectErrorStream(true); 157 try { 158 Process process = builder.start(); 159 /* 160 try(InputStream stdout = process.getInputStream(); 161 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 162 String line; 163 while((line = reader.readLine()) != null) { 164 System.out.println(line); 165 } 166 } finally { 167 */ 168 process.waitFor(); 169 //} 170 } catch (InterruptedException | IOException e) { 171 System.err.println("WARNING: gdal_translate " + 172 inputFile.getAbsolutePath() + " " + outputFile.getAbsolutePath() + 173 ": " + e.getMessage()); 174 return false; 175 } 176 return true; 177 } 178 179 /** Get a RasterInfo object for a raster file. This method calls "gdalinfo" 180 * to find the CRS and bounding box. 181 */ 182 public static RasterInfo getInfo(File file) throws IllegalActionException { 183 184 if(!file.exists()) { 185 throw new IllegalActionException("Raster file does not exist: " + file); 186 } 187 188 try { 189 ProcessBuilder builder = new ProcessBuilder("gdalinfo", file.getAbsolutePath()); 190 builder.redirectErrorStream(true); 191 Process process = builder.start(); 192 try(InputStream stdout = process.getInputStream(); 193 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 194 String line; 195 boolean crs = false; 196 boolean upperLeft = false; 197 boolean lowerRight = false; 198 StringBuilder buf = null; 199 double[] minCoords = new double[2]; 200 double[] maxCoords = new double[2]; 201 RasterInfo info = new RasterInfo(); 202 while((line = reader.readLine()) != null) { 203 204 if(line.startsWith("Coordinate System is:")) { 205 crs = true; 206 buf = new StringBuilder(); 207 } else if(line.startsWith("Origin = ")) { 208 crs = false; 209 if(buf == null) { 210 System.err.println("WARNING: missing WKT for " + file.getAbsolutePath() + 211 ". Does .prj file exist?"); 212 } else { 213 try { 214 // FIXME buf can be null? 215 info._crs = CRS.parseWKT(buf.toString()); 216 } catch (FactoryException e) { 217 System.err.println("WARNING: unable to parse WKT: " + e.getMessage()); 218 } 219 } 220 } else if(line.startsWith("GCP[")) { 221 info._hasGCP = true; 222 } else if(crs) { 223 buf.append(line); 224 } else { 225 Matcher matcher = UPPER_LEFT_PATTERN.matcher(line); 226 if(matcher.find()) { 227 upperLeft = true; 228 minCoords[0] = Double.parseDouble(matcher.group(1)); 229 maxCoords[1] = Double.parseDouble(matcher.group(2)); 230 continue; 231 } 232 matcher = LOWER_RIGHT_PATTERN.matcher(line); 233 if(matcher.find()) { 234 lowerRight = true; 235 maxCoords[0] = Double.parseDouble(matcher.group(1)); 236 minCoords[1] = Double.parseDouble(matcher.group(2)); 237 continue; 238 } 239 } 240 } 241 242 if(upperLeft && lowerRight) { 243 info._envelope = ReferencedEnvelope.create( 244 new GeneralEnvelope(minCoords, maxCoords), 245 info._crs); 246 //System.out.println("envelope is = " + info._envelope); 247 } 248 249 return info; 250 251 } finally { 252 process.waitFor(); 253 } 254 255 } catch (IOException e) { 256 throw new IllegalActionException("Error executing gdalinfo: " + e.getMessage()); 257 } catch (InterruptedException e) { 258 throw new IllegalActionException("Error waiting for gdalinfo to finish: " + e.getMessage()); 259 } 260 } 261 262 /** Reproject a raster file. 263 * @param rasterFile the raster file. 264 * @param destCRS the CRS to reproject to. 265 */ 266 public static File reproject(File rasterFile, CoordinateReferenceSystem destCRS) throws IllegalActionException { 267 268 // make sure projection is different 269 RasterInfo info = getInfo(rasterFile); 270 if(info.getCRS() == null) { 271 throw new IllegalActionException("Unknown projection for source raster file."); 272 } else if(info.getCRS().equals(destCRS)) { 273 return rasterFile; 274 } else { 275 276 int projectionNum; 277 try { 278 projectionNum = CRS.lookupEpsgCode(destCRS, false); 279 } catch (FactoryException e) { 280 throw new IllegalActionException("Error looking up EPSG code for destination projectin."); 281 } 282 283 String srcName = FilenameUtils.getBaseName(rasterFile.getAbsolutePath()); 284 285 // FIXME this assumes input raster is arc ascii 286 287 String destFileNameVRT = rasterFile.getParent() + 288 File.separator + 289 srcName + 290 "-" + 291 projectionNum + 292 ".vrt"; 293 294 ProcessBuilder builder = new ProcessBuilder("gdalwarp", 295 "-t_srs", 296 "EPSG:" + projectionNum, 297 "-of", 298 "VRT", 299 rasterFile.getAbsolutePath(), 300 destFileNameVRT); 301 302 builder.redirectErrorStream(true); 303 try { 304 Process process = builder.start(); 305 /* 306 try { 307 try(InputStream stdout = process.getInputStream(); 308 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 309 String line; 310 while((line = reader.readLine()) != null) { 311 System.out.println(line); 312 } 313 } 314 } finally { 315 */ 316 if(process.waitFor() != 0) { 317 throw new IllegalActionException("gdalwarp did not return 0."); 318 } 319 //} 320 } catch(IOException | InterruptedException e) { 321 throw new IllegalActionException("Error running gdalwarp: " + 322 e.getMessage()); 323 } 324 325 String destFileName = rasterFile.getParent() + 326 File.separator + 327 srcName + 328 "-" + 329 projectionNum + 330 "." + 331 FilenameUtils.getExtension(rasterFile.getAbsolutePath()); 332 333 builder = new ProcessBuilder("gdal_translate", 334 "-of", 335 "AAIGrid", 336 destFileNameVRT, 337 destFileName); 338 339 builder.redirectErrorStream(true); 340 try { 341 Process process = builder.start(); 342 /* 343 try { 344 try(InputStream stdout = process.getInputStream(); 345 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 346 String line; 347 while((line = reader.readLine()) != null) { 348 System.out.println(line); 349 } 350 } 351 } finally { 352 */ 353 if(process.waitFor() != 0) { 354 throw new IllegalActionException("gdalwarp did not return 0."); 355 } 356 //} 357 } catch(IOException | InterruptedException e) { 358 throw new IllegalActionException("Error running gdalwarp: " + 359 e.getMessage()); 360 } 361 return new File(destFileName); 362 } 363 } 364 365 /** Reproject a raster using gdalwarp to resolve ground control points. */ 366 public static void reprojectToResolveGCP(File inputFile, File outputFile) throws IllegalActionException { 367 368 369 ProcessBuilder builder = new ProcessBuilder("gdalwarp", 370 inputFile.getAbsolutePath(), outputFile.getAbsolutePath()); 371 builder.redirectErrorStream(true); 372 try { 373 Process process = builder.start(); 374 /* 375 try(InputStream stdout = process.getInputStream(); 376 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 377 String line; 378 while((line = reader.readLine()) != null) { 379 System.out.println(line); 380 } 381 } finally { 382 */ 383 process.waitFor(); 384 //} 385 } catch (IOException | InterruptedException e) { 386 throw new IllegalActionException("Error resolving GCP: " + e.getMessage()); 387 } 388 389 } 390 391 392 /** Utility class that contains information about a raster. */ 393 public static class RasterInfo { 394 395 private RasterInfo() { 396 } 397 398 public CoordinateReferenceSystem getCRS() { 399 return _crs; 400 } 401 402 public ReferencedEnvelope getEnvelope() { 403 return _envelope; 404 } 405 406 public boolean hasGCP() { 407 return _hasGCP; 408 } 409 410 private CoordinateReferenceSystem _crs; 411 private ReferencedEnvelope _envelope; 412 private boolean _hasGCP; 413 } 414 415 /** Regex for upper left of bounding box printed by gdalinfo. */ 416 private final static Pattern UPPER_LEFT_PATTERN = 417 Pattern.compile("Upper Left\\s+\\(\\s*(\\-?\\d+\\.\\d+),\\s*(\\-?\\d+\\.\\d+)"); 418 419 /** Regex for lower right of bounding box printed by gdalinfo. */ 420 private final static Pattern LOWER_RIGHT_PATTERN = 421 Pattern.compile("Lower Right\\s+\\(\\s*(\\-?\\d+\\.\\d+),\\s*(\\-?\\d+\\.\\d+)"); 422 423 /** Set of opened readers. */ 424 private static Set<AbstractGridCoverage2DReader> _readers = Collections.synchronizedSet(new HashSet<AbstractGridCoverage2DReader>()); 425 426}