001/* 002 * Copyright (c) 2016 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2016-04-21 05:55:25 +0000 (Thu, 21 Apr 2016) $' 007 * '$Revision: 34480 $' 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.actor.landscape; 030 031import java.io.BufferedReader; 032import java.io.File; 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.InputStreamReader; 036import java.util.ArrayList; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Set; 040 041import org.apache.commons.io.FilenameUtils; 042import org.geotools.geometry.jts.ReferencedEnvelope; 043import org.geotools.referencing.crs.DefaultGeographicCRS; 044import org.kepler.gis.data.RasterToken; 045import org.kepler.gis.util.RasterUtilities; 046import org.kepler.gis.util.RasterUtilities.RasterInfo; 047import org.opengis.referencing.FactoryException; 048import org.opengis.referencing.operation.TransformException; 049 050import ptolemy.actor.TypedAtomicActor; 051import ptolemy.actor.TypedIOPort; 052import ptolemy.actor.parameters.PortParameter; 053import ptolemy.data.BooleanToken; 054import ptolemy.data.StringToken; 055import ptolemy.data.Token; 056import ptolemy.data.expr.Parameter; 057import ptolemy.data.expr.StringParameter; 058import ptolemy.data.type.BaseType; 059import ptolemy.kernel.CompositeEntity; 060import ptolemy.kernel.util.Attribute; 061import ptolemy.kernel.util.IllegalActionException; 062import ptolemy.kernel.util.NameDuplicationException; 063import ptolemy.kernel.util.Workspace; 064 065/** Create a Landscape file from component raster layers. 066 * 067 * @author Daniel Crawl 068 * @version $Id: CreateLandscape.java 34480 2016-04-21 05:55:25Z crawl $ 069 */ 070public class CreateLandscape extends TypedAtomicActor { 071 072 public CreateLandscape(CompositeEntity container, String name) 073 throws IllegalActionException, NameDuplicationException { 074 super(container, name); 075 076 elevation = new TypedIOPort(this, "elevation", true, false); 077 _createRasterInputPort(elevation); 078 079 slope = new TypedIOPort(this, "slope", true, false); 080 _createRasterInputPort(slope); 081 082 aspect = new TypedIOPort(this, "aspect", true, false); 083 _createRasterInputPort(aspect); 084 085 fuel = new TypedIOPort(this, "fuel", true, false); 086 _createRasterInputPort(fuel); 087 088 canopyCover = new TypedIOPort(this, "canopyCover", true, false); 089 _createRasterInputPort(canopyCover); 090 091 canopyHeight = new TypedIOPort(this, "canopyHeight", true, false); 092 _createRasterInputPort(canopyHeight); 093 094 canopyBaseHeight = new TypedIOPort(this, "canopyBaseHeight", true, false); 095 _createRasterInputPort(canopyBaseHeight); 096 097 canopyBulkDensity = new TypedIOPort(this, "canopyBulkDensity", true, false); 098 _createRasterInputPort(canopyBulkDensity); 099 100 duff = new TypedIOPort(this, "duff", true, false); 101 _createRasterInputPort(duff); 102 103 woody = new TypedIOPort(this, "woody", true, false); 104 _createRasterInputPort(woody); 105 106 107 lcpmake = new StringParameter(this, "lcpmake"); 108 lcpmake.setToken("lcpmake"); 109 110 outputName = new PortParameter(this, "outputName"); 111 outputName.setTypeEquals(BaseType.STRING); 112 outputName.getPort().setTypeEquals(BaseType.STRING); 113 outputName.setStringMode(true); 114 new Attribute(outputName.getPort(), "_showName"); 115 116 deleteInputsOnWrapup = new Parameter(this, "deleteInputsOnWrapup"); 117 deleteInputsOnWrapup.setTypeEquals(BaseType.BOOLEAN); 118 deleteInputsOnWrapup.setToken(BooleanToken.FALSE); 119 120 deleteOutputOnWrapup = new Parameter(this, "deleteOutputOnWrapup"); 121 deleteOutputOnWrapup.setTypeEquals(BaseType.BOOLEAN); 122 deleteOutputOnWrapup.setToken(BooleanToken.FALSE); 123 124 out = new TypedIOPort(this, "out", false, true); 125 out.setTypeEquals(BaseType.STRING); 126 } 127 128 /** Clone this actor into the specified workspace. */ 129 @Override 130 public Object clone(Workspace workspace) throws CloneNotSupportedException { 131 CreateLandscape newObject = (CreateLandscape)super.clone(workspace); 132 newObject._deleteOnWrapup = new HashSet<File>(); 133 return newObject; 134 } 135 136 @Override 137 public void fire() throws IllegalActionException { 138 139 super.fire(); 140 141 outputName.update(); 142 Token token = outputName.getToken(); 143 if(token == null) { 144 throw new IllegalActionException(this, "Must specify output name."); 145 } 146 String outputNameStr = ((StringToken)token).stringValue(); 147 if(outputNameStr.trim().isEmpty()) { 148 throw new IllegalActionException(this, "Must specify output name."); 149 } 150 151 // strip off trailing .lcp since lcpmake automatically adds it 152 int index = outputNameStr.lastIndexOf(".lcp"); 153 if(index > -1) { 154 outputNameStr = outputNameStr.substring(0, index); 155 } 156 157 String elevationStr = _readRasterInput(elevation); 158 String slopeStr = _readRasterInput(slope); 159 String aspectStr = _readRasterInput(aspect); 160 String fuelStr = _readRasterInput(fuel); 161 String canopyCoverStr = _readRasterInput(canopyCover); 162 String canopyHeightStr = _readRasterInput(canopyHeight); 163 String canopyBaseHeightStr = _readRasterInput(canopyBaseHeight); 164 String canopyBulkDensityStr = _readRasterInput(canopyBulkDensity); 165 String duffStr = _readRasterInput(duff); 166 String woodyStr = _readRasterInput(woody); 167 168 List<String> args = new ArrayList<String>(); 169 170 args.add(_lcpMakeStr); 171 172 args.add("-landscape"); 173 args.add(outputNameStr); 174 175 // add .lcp back to output name 176 outputNameStr += ".lcp"; 177 178 args.add("-latitude"); 179 args.add(String.valueOf(_latitude)); 180 181 args.add("-elevation"); 182 args.add(elevationStr); 183 184 args.add("-slope"); 185 args.add(slopeStr); 186 187 args.add("-aspect"); 188 args.add(aspectStr); 189 190 args.add("-fuel"); 191 args.add(fuelStr); 192 193 args.add("-cover"); 194 args.add(canopyCoverStr); 195 196 if(canopyHeightStr != null) { 197 args.add("-height"); 198 args.add(canopyHeightStr); 199 } 200 201 if(canopyBaseHeightStr != null) { 202 args.add("-base"); 203 args.add(canopyBaseHeightStr); 204 } 205 206 if(canopyBulkDensityStr != null) { 207 args.add("-density"); 208 args.add(canopyBulkDensityStr); 209 } 210 211 if(duffStr != null) { 212 args.add("-duff"); 213 args.add(duffStr); 214 } 215 216 if(woodyStr != null) { 217 args.add("-woody"); 218 args.add(woodyStr); 219 } 220 221 ProcessBuilder builder = new ProcessBuilder(args); 222 try { 223 Process process = builder.start(); 224 if(_debugging) { 225 try(InputStream stdout = process.getInputStream(); 226 BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));) { 227 String line; 228 while((line = reader.readLine()) != null) { 229 _debug(line); 230 } 231 } 232 } 233 if(process.waitFor() != 0) { 234 throw new IllegalActionException(this, "lcpmake return non-zero."); 235 } 236 237 boolean deleteOutput = ((BooleanToken)deleteOutputOnWrapup.getToken()).booleanValue(); 238 if(deleteOutput) { 239 _deleteOnWrapup.add(new File(outputNameStr)); 240 } 241 } catch (IOException | InterruptedException e) { 242 throw new IllegalActionException(this, e, "Error running lcpmake."); 243 } 244 245 out.broadcast(new StringToken(outputNameStr)); 246 247 } 248 249 @Override 250 public void preinitialize() throws IllegalActionException { 251 252 super.preinitialize(); 253 254 _latitude = null; 255 256 _checkRasterInputAvailable(elevation); 257 _checkRasterInputAvailable(slope); 258 _checkRasterInputAvailable(aspect); 259 _checkRasterInputAvailable(fuel); 260 _checkRasterInputAvailable(canopyCover); 261 262 263 _lcpMakeStr = ((StringToken)lcpmake.getToken()).stringValue(); 264 if(_lcpMakeStr.trim().isEmpty()) { 265 throw new IllegalActionException(this, "Must specify path to lcpmake binary."); 266 } 267 268 _deleteInputs = ((BooleanToken)deleteInputsOnWrapup.getToken()).booleanValue(); 269 270 } 271 272 @Override 273 public void wrapup() throws IllegalActionException { 274 275 super.wrapup(); 276 277 for(File file: _deleteOnWrapup) { 278 if(!file.delete()) { 279 System.err.println("WARNING: could not delete " + file); 280 } 281 282 // delete .aux.xml if exists 283 String name = file.getAbsolutePath(); 284 File auxFile = new File(name + ".aux.xml"); 285 if(auxFile.exists()) { 286 if(!auxFile.delete()) { 287 System.err.println("WARNING: could not delete " + auxFile); 288 } 289 } 290 291 // delete .prj if exists 292 String nameNoExtension = FilenameUtils.removeExtension(name); 293 File prjFile = new File(nameNoExtension + ".prj"); 294 if(prjFile.exists()) { 295 if(!prjFile.delete()) { 296 System.err.println("WARNING: could not delete " + prjFile); 297 } 298 } 299 } 300 301 _deleteOnWrapup.clear(); 302 303 } 304 305 /////////////////////////////////////////////////////////////////// 306 //// public variables //// 307 308 /** Elevation raster (required). */ 309 public TypedIOPort elevation; 310 311 /** Slope raster (required). */ 312 public TypedIOPort slope; 313 314 /** Aspect raster (required). */ 315 public TypedIOPort aspect; 316 317 /** Fuel models raster (required). */ 318 public TypedIOPort fuel; 319 320 /** Canoopy cover raster (required). */ 321 public TypedIOPort canopyCover; 322 323 /** Canopy height raster (optional). */ 324 public TypedIOPort canopyHeight; 325 326 /** Canopy base height raster (optional). */ 327 public TypedIOPort canopyBaseHeight; 328 329 /** Canopy bulk density raster (optional). */ 330 public TypedIOPort canopyBulkDensity; 331 332 /** Duff raster (optional). */ 333 public TypedIOPort duff; 334 335 /** Woody raster (optional). */ 336 public TypedIOPort woody; 337 338 /** Path and name to lcpmake program. */ 339 public StringParameter lcpmake; 340 341 /** Path and name of landscape file to write. */ 342 public PortParameter outputName; 343 344 /** If true, delete input raster files when workflow stops execution. */ 345 public Parameter deleteInputsOnWrapup; 346 347 /** If true, delete output raster files when workflow stops execution. */ 348 public Parameter deleteOutputOnWrapup; 349 350 /** Path and name of landscape file that's created. */ 351 public TypedIOPort out; 352 353 354 /////////////////////////////////////////////////////////////////// 355 //// private methods //// 356 357 /** Create an input port for a raster band. */ 358 private void _createRasterInputPort(TypedIOPort port) throws IllegalActionException, NameDuplicationException { 359 port.setTypeEquals(RasterToken.RASTER); 360 new Attribute(port, "_showName"); 361 } 362 363 /** Check that an input port is connected. */ 364 private void _checkRasterInputAvailable(TypedIOPort port) throws IllegalActionException { 365 if(port.numberOfSources() < 1) { 366 throw new IllegalActionException(this, "Must specify " + port.getName() + " raster."); 367 } 368 } 369 370 /** Read a raster input port and return the file path. Returns null if port not connected. */ 371 private String _readRasterInput(TypedIOPort port) throws IllegalActionException { 372 if(port.numberOfSources() > 0) { 373 RasterToken token = (RasterToken) port.get(0); 374 File file = token.rasterFile(); 375 if(file == null) { 376 throw new IllegalActionException(this, 377 "No file in raster " + port.getName()); 378 } 379 380 if(!file.exists()) { 381 throw new IllegalActionException(this, 382 "Raster does not exist: " + file); 383 } 384 385 if(_deleteInputs) { 386 _deleteOnWrapup.add(file); 387 } 388 389 if(_latitude == null) { 390 RasterInfo info = RasterUtilities.getInfo(file); 391 ReferencedEnvelope envelope = info.getEnvelope(); 392 ReferencedEnvelope wgs84Envelope; 393 try { 394 wgs84Envelope = envelope.transform(DefaultGeographicCRS.WGS84, true); 395 } catch (TransformException | FactoryException e) { 396 throw new IllegalActionException(this, "Error converting to WGS84"); 397 } 398 _latitude = wgs84Envelope.getMedian(1); 399 //System.out.println("lat = " + _latitude); 400 } 401 402 return file.getAbsolutePath(); 403 } 404 return null; 405 } 406 407 /////////////////////////////////////////////////////////////////// 408 //// private variables //// 409 410 /** Path and name to lcpmake. */ 411 private String _lcpMakeStr; 412 413 /** Latitude of center of input rasters. */ 414 private Double _latitude; 415 416 /** Set of files to delete on wrapup. */ 417 Set<File> _deleteOnWrapup = new HashSet<File>(); 418 419 /** If true, delete input raster in wrapup(). */ 420 private boolean _deleteInputs = false; 421}