com.boundlessgeo.wps.grass.GrassProcesses.java Source code

Java tutorial

Introduction

Here is the source code for com.boundlessgeo.wps.grass.GrassProcesses.java

Source

/* Copyright (c) 2013 - 2014 Boundless - http://boundlessgeo.com All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package com.boundlessgeo.wps.grass;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import static java.io.File.pathSeparator;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.exec.environment.EnvironmentUtils;
import org.apache.commons.io.FileUtils;
import org.geoserver.platform.GeoServerExtensions;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.process.factory.StaticMethodsProcessFactory;
import org.geotools.referencing.CRS;
import org.geotools.text.Text;
import org.geotools.util.KVP;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.feature.type.Name;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

public class GrassProcesses extends StaticMethodsProcessFactory<GrassProcesses> {
    enum Env {
        LINUX, MAC, WINDOWS, UNKNOWN
    }

    private static final Logger LOGGER = Logging.getLogger("org.geoserver.wps.grass");
    static String EXEC;
    static String BIN;
    final static Env SYSTEM;
    private static final SecureRandom random = new SecureRandom();

    static {
        String system = System.getProperty("os.name").toLowerCase();
        if (system.contains("nix") || system.contains("nux") || system.contains("aix")) {
            SYSTEM = Env.LINUX;
        } else if (system.contains("mac")) {
            SYSTEM = Env.MAC;
        } else if (system.contains("win")) {
            SYSTEM = Env.WINDOWS;
        } else {
            SYSTEM = Env.UNKNOWN;
        }
    }

    public GrassProcesses() {
        super(Text.text("Geographic Resources Analysis Support System"), "grass", GrassProcesses.class);

        String grass = GeoServerExtensions.getProperty("GRASS");
        String grass_mod = GeoServerExtensions.getProperty("GRASS_MODULES");
        if (grass != null) {
            LOGGER.info("defined GRASS=" + grass);
            EXEC = grass;
        } else if (SYSTEM == Env.LINUX) {
            EXEC = "/usr/local/bin/grass70";
            LOGGER.info("default GRASS=" + EXEC);
        } else if (SYSTEM == Env.MAC) {
            EXEC = "/Applications/GRASS-7.0.app/Contents/MacOS/grass70";
            LOGGER.info("default GRASS=" + EXEC);
        } else if (SYSTEM == Env.WINDOWS) {
            if (new File("C:\\Program Files (x86)").exists()) {
                EXEC = "C:\\Program Files (x86)\\GRASS GIS 7.0.0\\grass70.bat";
            } else {
                EXEC = "C:\\Program Files\\GRASS GIS 7.0.0\\grass70.bat";
            }
            LOGGER.info("default GRASS=" + EXEC);
        } else {
            LOGGER.warning("GRASS default executable unavailable for '" + System.getProperty("os.name")
                    + "'. Please use GRASS environmental variable, context parameter or system property");
            EXEC = null;
        }
        if (grass_mod != null) {
            LOGGER.info("defined GRASS_MODULES=" + grass_mod);
            BIN = grass_mod;
        } else if (SYSTEM == Env.LINUX) {
            BIN = "/usr/lib/grass70/bin";
            LOGGER.info("default GRASS_MODULES=" + BIN);
        } else if (SYSTEM == Env.MAC) {
            BIN = "/Applications/GRASS-7.0.app/Contents/MacOS/bin";
            LOGGER.info("default GRASS_MODULES=" + BIN);
        } else if (SYSTEM == Env.WINDOWS) {
            if (new File("C:\\Program Files (x86)").exists()) {
                BIN = "C:\\Program Files (x86)\\GRASS GIS 7.0.0\\bin";
            } else {
                BIN = "C:\\Program Files\\GRASS GIS 7.0.0\\bin";
            }

            LOGGER.info("default GRASS_MODULES=" + BIN);
        } else {
            LOGGER.warning("GRASS modules unavailable for '" + System.getProperty("os.name")
                    + "'. Please use GRASS_MODULES environmental variable, context parameter or system property");
            BIN = null;
        }
        if (EXEC != null) {
            File exec = new File(EXEC);
            if (!exec.exists()) {
                LOGGER.warning(EXEC + " does not exist");
                EXEC = null;
            }
            if (!exec.canExecute()) {
                LOGGER.warning(EXEC + " not executable");
                EXEC = null;
            }
        }
        if (BIN != null) {
            File exec = new File(BIN);
            if (!exec.exists()) {
                LOGGER.warning(BIN + " does not exist");
                BIN = null;
            }
        }
    }

    @Override
    public Set<Name> getNames() {
        if (EXEC == null) {
            // do not advertise grass processes if executable is not available
            return Collections.emptySet();
        }
        return super.getNames();
    }

    @DescribeProcess(title = "GRASS Version", description = "Retreive the version of GRASS used for computation")
    @DescribeResult(description = "Version")
    public static String version() {
        if (EXEC == null) {
            return "unavailable";
        }
        CommandLine cmd = new CommandLine(EXEC);
        cmd.addArgument("-v");

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);
        ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
        executor.setWatchdog(watchdog);
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            executor.setStreamHandler(new PumpStreamHandler(outputStream));

            LOGGER.info("exec: " + cmd.toString());
            int exitValue = executor.execute(cmd);
            return outputStream.toString();
        } catch (ExecuteException huh) {
            return "exit code: " + huh.getExitValue() + " (" + huh.getMessage() + ")";
        } catch (IOException e) {
            return "unavailable: " + e.getClass().getSimpleName() + ":" + e.getMessage();
        }
    }

    @DescribeProcess(title = "r.viewshed", description = "Computes the viewshed of a point on an elevation raster map.")
    @DescribeResult(description = "area visible from provided location")
    public static GridCoverage2D viewshed(
            @DescribeParameter(name = "dem", description = "digitial elevation model") GridCoverage2D dem,
            @DescribeParameter(name = "x", description = "x location in map units") double x,
            @DescribeParameter(name = "y", description = "y location in map units") double y) throws Exception {

        String COMMAND = "viewshed";
        //Stage files in a temporary location
        File geodb = Files.createTempDirectory("grassdata").toFile();
        geodb.deleteOnExit();
        File location = new File(geodb, COMMAND + Long.toString(random.nextLong()) + "location");

        // stage dem file
        File file = new File(geodb, "dem.tif");
        //The file must exist for FileImageOutputStreamExtImplSpi to create the output stream
        if (!file.exists()) {
            file.getParentFile().mkdirs();
            file.createNewFile();
        }
        final GeoTiffFormat format = new GeoTiffFormat();
        GridCoverageWriter writer = format.getWriter(file);
        writer.write(dem, null);
        LOGGER.info("Staging file:" + file);

        // use file to create location with (returns PERMANENT mapset)
        File mapset = location(location, file);

        DefaultExecutor executor = new DefaultExecutor();
        executor.setWatchdog(new ExecuteWatchdog(60000));
        executor.setStreamHandler(new PumpStreamHandler(System.out));
        executor.setWorkingDirectory(mapset);

        Map<String, String> env = customEnv(geodb, location, mapset);

        // EXPORT IMPORT DEM
        // r.in.gdal input=~/grassdata/viewshed/PERMANENT/dem.tif output=dem --overwrite

        File r_in_gdal = bin("r.in.gdal");
        CommandLine cmd = new CommandLine(r_in_gdal);
        cmd.addArgument("input=${file}");
        cmd.addArgument("output=dem");
        cmd.addArgument("--overwrite");
        cmd.setSubstitutionMap(new KVP("file", file));
        try {
            LOGGER.info(cmd.toString());
            executor.setExitValue(0);
            int exitValue = executor.execute(cmd, env);
        } catch (ExecuteException fail) {
            LOGGER.warning(r_in_gdal.getName() + ":" + fail.getLocalizedMessage());
            throw fail;
        }

        // EXECUTE VIEWSHED
        File r_viewshed = bin("r.viewshed");
        cmd = new CommandLine(r_viewshed);
        cmd.addArgument("input=dem");
        cmd.addArgument("output=viewshed");
        cmd.addArgument("coordinates=${x},${y}");
        cmd.addArgument("--overwrite");
        cmd.setSubstitutionMap(new KVP("x", x, "y", y));

        try {
            LOGGER.info(cmd.toString());
            executor.setExitValue(0);
            int exitValue = executor.execute(cmd, env);
        } catch (ExecuteException fail) {
            LOGGER.warning(r_viewshed.getName() + ":" + fail.getLocalizedMessage());
            throw fail;
        }

        // EXECUTE EXPORT VIEWSHED
        // r.out.gdal --overwrite input=viewshed@PERMANENT output=/Users/jody/grassdata/viewshed/viewshed.tif format=GTiff
        File viewshed = new File(location, "viewshed.tif");

        File r_out_gdal = bin("r.out.gdal");
        cmd = new CommandLine(r_out_gdal);
        cmd.addArgument("input=viewshed");
        cmd.addArgument("output=${viewshed}");
        cmd.addArgument("--overwrite");
        cmd.addArgument("format=GTiff");
        cmd.setSubstitutionMap(new KVP("viewshed", viewshed));

        try {
            LOGGER.info(cmd.toString());
            executor.setExitValue(0);
            int exitValue = executor.execute(cmd, env);
        } catch (ExecuteException fail) {
            LOGGER.warning(r_out_gdal.getName() + ":" + fail.getLocalizedMessage());
            throw fail;
        }

        // STAGE RESULT

        if (!viewshed.exists()) {
            throw new IOException("Generated viweshed.tif not found");
        }
        GeoTiffReader reader = format.getReader(viewshed);
        GridCoverage2D coverage = reader.read(null);
        cleanup(new File(env.get("GISRC")));
        return coverage;
    }

    private static void cleanup(File... files) {
        for (File file : files) {
            FileUtils.deleteQuietly(file);
        }
    }

    private static File bin(String command) {
        File exec;
        if (SYSTEM == Env.WINDOWS) {
            exec = new File(new File(BIN), command + ".bat");
            if (!exec.exists()) {
                exec = new File(new File(BIN), command + ".exe");
            }
        } else {
            exec = new File(new File(BIN), command);
        }

        if (!exec.exists()) {
            throw new IllegalStateException(command + " not found:" + exec);
        }
        if (!exec.canExecute()) {
            throw new IllegalStateException(command + " not executable:" + exec);
        }
        return exec;
    }

    /**
     * Define environment variable for independent grass operation.
     * see: http://grasswiki.osgeo.org/wiki/GRASS_and_Shell
     * @param geodb
     * @param location
     * @param mapset
     * @return
     * @throws IOException
     */
    private static Map<String, String> customEnv(File geodb, File location, File mapset) throws IOException {
        Map<String, String> env = EnvironmentUtils.getProcEnvironment();
        // GRASS ENV
        File GISBASE = new File(EXEC).getParentFile();
        String GRASS_VERSION = "7.0.0";
        EnvironmentUtils.addVariableToEnvironment(env, "GISBASE=" + GISBASE);
        EnvironmentUtils.addVariableToEnvironment(env, "GRASS_VERSION=" + GRASS_VERSION);

        File GISRC = new File(System.getProperty("user.home"),
                ".grassrc." + GRASS_VERSION + "." + location.getName());
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(GISRC))) {
            writer.write("GISDBASE: " + geodb);
            writer.newLine();
            writer.write("LOCATION_NAME: " + location.getName());
            writer.newLine();
            writer.write("MAPSET: PERMANENT");
            writer.newLine();
            writer.write("GRASS_GUI: text");
            writer.newLine();
        }
        EnvironmentUtils.addVariableToEnvironment(env, "GISRC=" + GISRC);

        // SYSTEM ENV
        String bin = new File(GISBASE, "bin").getAbsolutePath();
        String scripts = new File(GISBASE, "scripts").getAbsolutePath();
        String lib = new File(GISBASE, "lib").getAbsolutePath();
        if (SYSTEM == Env.WINDOWS) {
            String PATH = env.get("PATH") + pathSeparator + bin + pathSeparator + scripts + pathSeparator + lib;
            EnvironmentUtils.addVariableToEnvironment(env, "PATH=" + PATH);
        } else {
            String PATH = env.get("PATH") + pathSeparator + bin + pathSeparator + scripts;
            EnvironmentUtils.addVariableToEnvironment(env, "PATH=" + PATH);
            if (SYSTEM == Env.LINUX) {
                String LD_LIBRARY_PATH = (env.containsKey("LD_LIBRARY_PATH")
                        ? env.get("LD_LIBRARY_PATH") + pathSeparator
                        : "") + lib;
                EnvironmentUtils.addVariableToEnvironment(env, "LD_LIBRARY_PATH=" + LD_LIBRARY_PATH);
            } else if (SYSTEM == Env.MAC) {
                String DYLD_LIBRARY_PATH = (env.containsKey("DYLD_LIBRARY_PATH")
                        ? env.get("DYLD_LIBRARY_PATH") + pathSeparator
                        : "") + lib;
                EnvironmentUtils.addVariableToEnvironment(env, "DYLD_LIBRARY_PATH=" + DYLD_LIBRARY_PATH);
            }
        }
        return env;
    }

    /**
     * Define a GISBASE/LOCATION_NAME/PERMANENT for the provided dem.
     *
     * The dem is staged in GISBASE/dem.tif and then moved to
     * GISBASE/LOCATION_NAME/PERMANENT/dem.tif
     *
     * @param operation
     *            Name used for the location on disk
     * @param dem
     *            File used to establish CRS and Bounds for the location
     * @return Array of files consisting of {GISBASE, LOCATION, MAPSET, dem.tif}
     * @throws Exception
     */
    static File location(File location, File raster) throws Exception {
        // grass70 + ' -c ' + myfile + ' -e ' + location_path
        CommandLine cmd = new CommandLine(EXEC);
        cmd.addArgument("-c");
        cmd.addArgument("${raster}");
        cmd.addArgument("-e");
        cmd.addArgument("${location}");
        cmd.setSubstitutionMap(new KVP("raster", raster, "location", location));
        LOGGER.info(cmd.toString());

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);
        ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
        executor.setWatchdog(watchdog);
        executor.setStreamHandler(new PumpStreamHandler(System.out));

        LOGGER.info(cmd.toString());
        try {
            int exitValue = executor.execute(cmd);
        } catch (ExecuteException fail) {
            LOGGER.warning("grass70:" + fail.getLocalizedMessage());
            throw fail;
        }

        File mapset = new File(location, "PERMANENT");
        if (!mapset.exists()) {
            throw new IllegalStateException("Did not create mapset " + mapset);
        }
        return location;
    }

    /**
     * Define a GISBASE/LOCATION_NAME/PERMANENT for the provided dem.
     *
     * The dem is staged in GISBASE/dem.tif and then moved to
     * GISBASE/LOCATION_NAME/PERMANENT/dem.tif
     *
     * @param operation
     *            Name used for the location on disk
     * @param dem
     *            File used to establish CRS and Bounds for the location
     * @return Array of files consisting of {GISBASE, LOCATION, MAPSET, dem.tif}
     * @throws Exception
     */
    static File[] location(String operation, GridCoverage2D dem) throws Exception {
        File geodb = new File(System.getProperty("user.home"), "grassdata");
        //File location = Files.createTempDirectory(geodb.toPath(),operation).toFile();
        File location = new File(geodb, operation);
        File mapset = new File(location, "PERMANENT");
        File file = new File(geodb, "dem.tif");

        final GeoTiffFormat format = new GeoTiffFormat();
        GridCoverageWriter writer = format.getWriter(file);
        writer.write(dem, null);
        System.out.println("Staging file:" + file);

        // grass70 + ' -c ' + myfile + ' -e ' + location_path
        CommandLine cmd = new CommandLine(EXEC);
        cmd.addArgument("-c");
        cmd.addArgument("${file}");
        cmd.addArgument("-e");
        cmd.addArgument("${location}");
        cmd.setSubstitutionMap(new KVP("file", file, "location", location));
        LOGGER.info(cmd.toString());

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);
        ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
        executor.setWatchdog(watchdog);
        executor.setStreamHandler(new PumpStreamHandler(System.out));
        int exitValue = executor.execute(cmd);

        File origional = file;
        file = new File(mapset, file.getName());
        Files.move(origional.toPath(), file.toPath());
        return new File[] { geodb, location, mapset, file };
    }

    /**
     * Define a GISBASE/LOCATION_NAME for the provided dem.
     *
     * @param operation Name used for the location on disk
     * @param dem File used to establish CRS and Bounds for the location
     * @return
     * @throws Exception
     */
    static File location(CoordinateReferenceSystem crs) throws Exception {
        String code = CRS.toSRS(crs, true);
        File geodb = new File(System.getProperty("user.home"), "grassdata");
        File location = Files.createTempDirectory(geodb.toPath(), code).toFile();
        KVP kvp = new KVP("geodb", geodb, "location", location);

        // grass70 + ' -c epsg:' + myepsg + ' -e ' + location_path
        CommandLine cmd = new CommandLine(EXEC);
        cmd.addArgument("-c");
        cmd.addArgument("epsg:" + code);
        cmd.addArgument("-e");
        cmd.addArgument("${location}");
        cmd.setSubstitutionMap(kvp);

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);
        executor.setWatchdog(new ExecuteWatchdog(60000));
        executor.setStreamHandler(new PumpStreamHandler(System.out));

        int exitValue = executor.execute(cmd);
        return location;
    }

}