org.deegree.tools.rendering.dem.builder.DEMDatasetGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.deegree.tools.rendering.dem.builder.DEMDatasetGenerator.java

Source

//$HeadURL: svn+ssh://mschneider@svn.wald.intevation.org/deegree/base/trunk/resources/eclipse/files_template.xml $
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2009 by:
 Department of Geography, University of Bonn
 and
 lat/lon GmbH
    
 This library is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the Free
 Software Foundation; either version 2.1 of the License, or (at your option)
 any later version.
 This library is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 details.
 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 Contact information:
    
 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/
    
 Department of Geography, University of Bonn
 Prof. Dr. Klaus Greve
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/
    
 e-mail: info@deegree.org
 ----------------------------------------------------------------------------*/

package org.deegree.tools.rendering.dem.builder;

import static java.lang.System.currentTimeMillis;
import static org.deegree.commons.tools.CommandUtils.OPT_VERBOSE;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Blob;
import java.sql.SQLException;

import javax.vecmath.Point2f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.deegree.commons.annotations.Tool;
import org.deegree.commons.tools.CommandUtils;
import org.deegree.commons.utils.math.MathUtils;
import org.deegree.coverage.AbstractCoverage;
import org.deegree.coverage.raster.AbstractRaster;
import org.deegree.coverage.raster.cache.RasterCache;
import org.deegree.coverage.raster.data.RasterData;
import org.deegree.coverage.raster.data.RasterDataFactory;
import org.deegree.coverage.raster.data.TiledRasterData;
import org.deegree.coverage.raster.data.info.DataType;
import org.deegree.coverage.raster.data.info.RasterDataInfo;
import org.deegree.coverage.raster.geom.RasterGeoReference;
import org.deegree.coverage.raster.geom.RasterGeoReference.OriginLocation;
import org.deegree.coverage.raster.geom.RasterRect;
import org.deegree.coverage.raster.io.RasterIOOptions;
import org.deegree.coverage.raster.io.grid.GridFileReader;
import org.deegree.coverage.raster.io.grid.GridReader;
import org.deegree.coverage.raster.io.grid.GridWriter;
import org.deegree.coverage.raster.utils.Rasters;
import org.deegree.coverage.tools.RasterOptionsParser;
import org.deegree.geometry.Envelope;
import org.deegree.rendering.r3d.multiresolution.MultiresolutionMesh;
import org.deegree.tools.i18n.Messages;
import org.deegree.tools.rendering.dem.builder.dag.DAGBuilder;

/**
 * Tool for generating the binary files for {@link MultiresolutionMesh} instances (MRIndex- and PatchData-BLOBs) from
 * DEM coverages (heightfields).
 * <p>
 * Initially, the domain is divided into two right triangles. These are the root fragments of the multiresolution
 * hierarchy. Smaller fragments (with more detail) are generated by recursively bisecting the triangles.
 * 
 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
 * @author last edited by: $Author: schneider $
 * 
 * @version $Revision: $, $Date: $
 */
@Tool("Generates DEM multiresolution datasets from rasters, suitable for the WPVS.")
public class DEMDatasetGenerator {

    // private static final Logger LOG = LoggerFactory.getLogger(
    // DEMDatasetGenerator.class );

    /*
     * Command line options
     */
    private static final String OPT_OUTPUT_DIR = "out_dir";

    private static final String OPT_OUTPUT_LEVELS = "out_levels";

    private static final String OPT_OUTPUT_ROWS = "out_rows";

    private static final String OPT_MAX_HEIGHT = "max_z";

    final RasterData dataBuffer;

    /** Number of points in the heixelBuffer (x-dimension). */
    final int inputX;

    /** Number of points in the heixelBuffer (y-dimension). */
    final int inputY;

    private final int levels;

    private final int rowsPerFragment;

    private final double sampleSizeX;

    private final double sampleSizeY;

    /** Number of points in the domain (x-dimension). */
    private final int outputX;

    /** Number of points in the domain (y-dimension). */
    private final int outputY;

    /** Maximum height value (everything higher is clipped to default value). */
    private final float maxZ;

    private final int verticesPerFragment;

    private final int trianglesPerFragment;

    private final int bytesPerTile;

    private RasterGeoReference geoReference;

    private double tileHeight;

    private final long fileSize;

    private static final int TILE_SIZE = 1000;

    private static final int BYTES_PER_NORMAL_COMPONENT = 1;

    /**
     * Creates a new <code>PatchGenerator</code> instance.
     * 
     * @param raster
     *            the dem raster
     * @param options
     *            containing information on the given raster.
     * @param levels
     *            number of levels in the generated (layered) DAG
     * @param rowsPerTile
     *            number of rows per macro triangle (tile)
     * @param maxZ
     *            the clipping z value.
     * @throws SQLException
     * @throws IOException
     */
    public DEMDatasetGenerator(AbstractRaster raster, RasterIOOptions options, int levels, int rowsPerTile,
            float maxZ) throws SQLException, IOException {

        this.dataBuffer = buildGrid(raster, options);

        if (Float.isNaN(maxZ)) {
            this.maxZ = getAsFloatSample(-1, -1, 0);
            System.out.println("Setting max height value to no data value: " + this.maxZ);
        } else {
            this.maxZ = maxZ;
        }

        // don't use dataBuffer.getWidth() here, as it seems to be happen that
        // it gets bigger than the input
        // raster (e.g. 2048 -> 2049)
        this.inputX = raster.getColumns();
        this.inputY = raster.getRows();
        this.rowsPerFragment = rowsPerTile;

        RasterGeoReference rRef = raster.getRasterReference();
        sampleSizeX = rRef.getResolutionX();
        sampleSizeY = rRef.getResolutionY();

        this.geoReference = new RasterGeoReference(OriginLocation.CENTER, rRef.getResolutionX(),
                rRef.getResolutionY(), rRef.getRotationX(), rRef.getRotationY(), 0, raster.getEnvelope().getSpan1(),
                raster.getCoordinateSystem());
        // calculate the best size
        int numSamples = Math.max(inputX, inputY);
        int nextPowerOfTwo = MathUtils.nextPowerOfTwoValue(numSamples);

        this.outputX = nextPowerOfTwo;
        this.outputY = nextPowerOfTwo;

        Envelope env = raster.getRasterReference().getEnvelope(new RasterRect(0, 0, outputX, outputY), null);

        Point2f p0 = new Point2f(0, (float) env.getSpan1());
        Point2f p1 = new Point2f(0, 0);
        Point2f p2 = new Point2f((float) env.getSpan0(), (float) env.getSpan1());

        int lowestLevel = Integer.numberOfTrailingZeros(rowsPerTile);
        int heighestLevel = Integer.numberOfTrailingZeros(outputX);
        int tL = (heighestLevel - lowestLevel) * 2;
        if (levels == -1) {
            System.out
                    .println("Setting number of levels for " + rowsPerTile + " rows per macro triangle to: " + tL);
            this.levels = tL;
        } else {
            this.levels = levels;
        }
        if (tL != this.levels) {
            System.out.println("++++WARN++++\nThe best number of levels (fitting your data) for " + rowsPerTile
                    + " rows per macro triangle is " + tL + ". You provided: " + levels + ", this will result in "
                    + ((levels < tL) ? "under" : "over") + " sampling your input data.\n++++++++++++");
        }

        /**
         * rb: a macro triangle will consist of all inner vertices + the 'half-way' vertices which make sure that two
         * triangles will fit together. for example: <code>
         * rowsPerTile = 2; 
         * verticersPerTile = 4 * 4 - 3 = 13 
         * (dots are vertices)
         *     .
         *   :/.\:
         *  :/...\:
         *  </code>
         * 
         */

        this.verticesPerFragment = (rowsPerTile + 2) * (rowsPerTile + 2) - 3;

        // rb: draw it out, it is working.
        this.trianglesPerFragment = (4 * rowsPerTile) + (2 * (rowsPerTile - 1) * rowsPerTile);

        if (getVerticesPerFragment() > 65536) {
            throw new RuntimeException(Messages.getMessage("DEMDSGEN_TOO_MANY_VERTICES"));
        }

        int bytesPerMacroTriangle = (4 + 4 * 3 * getVerticesPerFragment());

        // normal vectors
        this.bytesPerTile = (bytesPerMacroTriangle + (BYTES_PER_NORMAL_COMPONENT * 3 * getVerticesPerFragment()));

        long fs = 0;
        int level = 0;
        while (level < this.levels) {
            fs += bytesPerTile * (2l << level++);
        }
        fileSize = fs;

        double minX = raster.getEnvelope().getMin().get0();
        double minY = raster.getEnvelope().getMin().get1();
        double maxX = raster.getEnvelope().getMax().get0();
        double maxY = raster.getEnvelope().getMax().get1();

        System.out.println("\nInitializing DEMDatasetGenerator");
        System.out.println("--------------------------------\n");
        // System.out.println( "- input file: " + inputFileName );
        System.out.println("- envelope: (" + minX + "," + minY + ")-(" + maxX + "," + maxY + ")");
        System.out.println("- raster sample size: x=" + sampleSizeX + ", y=" + sampleSizeY);
        System.out.println("- bintritree levels: " + levels);
        System.out.println("- rows per tile: " + rowsPerTile);
        System.out.println("- vertices per tile: " + getVerticesPerFragment());
        System.out.println("- triangles per tile: " + getTrianglesPerFragment());
        System.out.println("- bytes per tile: " + getBytesPerTile());
        System.out.println("- filesize will be: " + fileSize + " bytes ("
                + Math.round((fileSize / (1024 * 1024d)) * 100d) / 100d + " Mb)");
        System.out.println("- WPVS translationvector should be: <TranslationToLocalCRS x=\"-" + minX + "\" y=\"-"
                + minY + "\"/>");

        outputTriangleHeights(p0, p1, p2, this.getLevels());
    }

    /**
     * @return the verticesPerFragment
     */
    public int getVerticesPerFragment() {
        return verticesPerFragment;
    }

    /**
     * @return the trianglesPerFragment
     */
    public int getTrianglesPerFragment() {
        return trianglesPerFragment;
    }

    /**
     * @return the levels
     */
    public int getLevels() {
        return levels;
    }

    /**
     * @return the rowsPerFragment
     */
    public int getRowsPerFragment() {
        return rowsPerFragment;
    }

    /**
     * @param tileHeight
     *            the tileHeight to set
     */
    public void setTileHeight(double tileHeight) {
        this.tileHeight = tileHeight;
    }

    /**
     * @return the tileHeight
     */
    public double getTileHeight() {
        return tileHeight;
    }

    /**
     * @return the bytesPerTile
     */
    public int getBytesPerTile() {
        return bytesPerTile;
    }

    /**
     * 
     */
    private void outputTriangleHeights(Point2f p0, Point2f p1, Point2f p2, int level) {
        Point2f midPoint = calcMidPoint(p1, p2);
        if (level > 0) {
            double mtHeight = (p0.distance(midPoint));
            double sampleDist = mtHeight / rowsPerFragment;
            System.out.println("At level " + level + " each macro triangle will have a height of: " + mtHeight
                    + " meters. Distance between used heixels: " + sampleDist + " meters");
            outputTriangleHeights(midPoint, p0, p1, level - 1);
        }
    }

    /**
     * @throws IOException
     */
    private TiledRasterData buildGrid(AbstractRaster raster, RasterIOOptions options) throws IOException {

        RasterGeoReference rasterReference = raster.getRasterReference()
                .createRelocatedReference(OriginLocation.OUTER);
        Envelope renv = raster.getRasterReference().relocateEnvelope(OriginLocation.OUTER, raster.getEnvelope());
        // calculate the rows.
        RasterRect rect = rasterReference.convertEnvelopeToRasterCRS(raster.getEnvelope());

        RasterCache.dispose();

        int numberOfTiles = Rasters.calcApproxTiles(rect.width, rect.height, TILE_SIZE);
        int tileWidth = Rasters.calcTileSize(rect.width, numberOfTiles);
        int tileHeight = Rasters.calcTileSize(rect.height, numberOfTiles);
        int columns = (int) Math.ceil(((double) rect.width) / tileWidth);
        int rows = (int) Math.ceil((double) rect.height / tileHeight);
        //

        RasterDataInfo inf = raster.getRasterDataInfo();
        long filesize = ((long) rows) * columns * (tileHeight * tileWidth * inf.dataSize * inf.bands);
        this.setTileHeight(
                rasterReference.getEnvelope(new RasterRect(0, 0, tileWidth, tileHeight), null).getSpan1());
        String cD = options.get(RasterIOOptions.RASTER_CACHE_DIR);
        File cacheDir = (cD == null) ? RasterCache.DEFAULT_CACHE_DIR : new File(cD);
        File heixelFile = new File(cacheDir, "heixel.grid");
        boolean createNew = !heixelFile.exists();
        if (!createNew) {
            // calculate the size of the heixel file.
            long length = heixelFile.length();
            createNew = length != filesize;
            if (!createNew) {
                System.out.println("Found a temporary storage of your data (heixel.grid) at location: "
                        + heixelFile.getAbsolutePath() + " with correct size: " + filesize + " bytes (ca. "
                        + (Math.round((filesize / (1024 * 1024d)) * 100) * 0.01)
                        + " Mb). It will be used for the creation of your Multiresolution mesh.");

            }
        }

        GridReader reader = null;

        if (createNew) {
            System.out.println("Creating grid of " + rows + "x" + columns + " (rows x columns) and " + rect.width
                    + "x" + rect.height + " samples (width x height), each tile will have " + tileWidth + "x"
                    + tileHeight + " samples (width x height), resulting file will have " + filesize
                    + " bytes (ca. " + (Math.round((filesize / (1024 * 1024d)) * 100) * 0.01) + " Mb)");
            try {
                GridWriter writer = new GridWriter(columns, rows, renv, rasterReference, heixelFile, inf);
                writer.write(raster, options);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        reader = new GridFileReader(heixelFile, options);
        return RasterDataFactory.createTiledRasterData(reader, options);
    }

    private PatchManager generateMacroTriangles(PatchManager triangleManager, float minX, float minY, float maxX,
            float maxY) {

        Point2f p0 = new Point2f(minX, maxY);
        Point2f p1 = new Point2f(minX, minY);
        Point2f p2 = new Point2f(maxX, maxY);
        Point2f p3 = new Point2f(maxX, minY);

        // prepare workers
        Worker worker1 = new Worker(this, triangleManager, "0", p0, p1, p2);
        Worker worker2 = new Worker(this, triangleManager, "1", p3, p2, p1);

        long sT = currentTimeMillis();
        // start workers in different threads
        // Thread t = new Thread( worker1, "Upper left macro triangle" );
        worker1.run();
        Thread.currentThread().setName("Lower right macro triangle");
        worker2.run();

        // // wait indefinitely for thread t to finish
        // try {
        // t.join();
        // // Finished
        // } catch ( InterruptedException e ) {
        // // Thread was interrupted
        // e.printStackTrace();
        // }
        // System.out.println( LogUtils.createDurationTimeString(
        // "Creation of triangles", sT, true ) );

        return triangleManager;
    }

    float boxMinX = 3398000 - 3396466;

    float boxMaxX = 3418001 - 3396466;

    float boxMinY = 5751999 - 5717999;

    float boxMaxY = 5764000 - 5717999;

    int j = 0;

    float getHeight(float x, float y) {

        int[] rasterCoordinate = this.geoReference.getRasterCoordinate(x, y);

        float height = getAsFloatSample(rasterCoordinate[0], rasterCoordinate[1], 0);
        // float height = 0.0f;
        // if ( x > boxMinX && x < boxMaxX && y > boxMinY && y < boxMaxY ) {
        // // height = getAsFloatSample( rasterCoordinate[0],
        // rasterCoordinate[1], 0 );
        // height = 100.0f + getAsFloatSample( rasterCoordinate[0],
        // rasterCoordinate[1], 0 );
        // }
        // // float height = getAsFloatSample( rasterCoordinate[0],
        // rasterCoordinate[1], 0 );
        if (height > maxZ) {
            height = maxZ;
        }
        return height;
    }

    private float getAsFloatSample(int rasterX, int rasterY, int band) {
        DataType dataType = this.dataBuffer.getDataType();
        switch (dataType) {
        case BYTE:
            return this.dataBuffer.getByteSample(rasterX, rasterY, band);
        case DOUBLE:
            return (float) this.dataBuffer.getDoubleSample(rasterX, rasterY, band);
        case FLOAT:
            return this.dataBuffer.getFloatSample(rasterX, rasterY, band);
        case INT:
            return this.dataBuffer.getIntSample(rasterX, rasterY, band);
        case SHORT:
            return this.dataBuffer.getShortSample(rasterX, rasterY, band);
        case UNDEFINED:
            throw new IllegalArgumentException("Unknown Data type, this cannot be.");
        case USHORT:
            return this.dataBuffer.getShortSample(rasterX, rasterY, band) & 0xffff;
        }
        throw new IllegalArgumentException("Unknown Data type, this cannot be.");
    }

    Point2f calcMidPoint(Point2f pa, Point2f pb) {
        float minX = Math.min(pb.x, pa.x);
        float minY = Math.min(pb.y, pa.y);
        float midX = (Math.abs(pb.x - pa.x) * 0.5f) + minX;
        float midY = (Math.abs(pb.y - pa.y) * 0.5f) + minY;
        return new Point2f(midX, midY);
    }

    private int patchesDone = 0;

    private class Worker implements Runnable {

        private DEMDatasetGenerator builder;

        private String startLocationCode;

        private Point2f p0, p1, p2;

        private Point3f[] tileVertices;

        private Vector3f[] vertexNormals;

        private int[][] tileTriangles;

        private PatchManager triangleManager;

        Worker(DEMDatasetGenerator builder, PatchManager triangleManager, String startLocationCode, Point2f p0,
                Point2f p1, Point2f p2) {
            this.builder = builder;
            this.triangleManager = triangleManager;
            this.startLocationCode = startLocationCode;
            this.p0 = p0;
            this.p1 = p1;
            this.p2 = p2;
            this.tileVertices = new Point3f[getVerticesPerFragment()];
            this.vertexNormals = new Vector3f[getVerticesPerFragment()];
            this.tileTriangles = new int[getTrianglesPerFragment()][3];
        }

        public void run() {
            String tria = startLocationCode.equals("0") ? "Lower right" : "Upper left";
            System.out.println(
                    "Starting a worker at location code " + startLocationCode + " (" + tria + " macro triangle).");
            createTriangleTree(p0, p1, p2, getLevels() - startLocationCode.length() + 1, startLocationCode);
        }

        /**
         * Recursive method creates the triangles from the given points, which are in world coordinates.
         * 
         * @param p0
         * @param p1
         * @param p2
         * @param level
         * @param locationCode
         */
        private MacroTriangle createTriangleTree(Point2f p0, Point2f p1, Point2f p2, int level,
                String locationCode) {

            float error = estimateError(p0, p1, p2);
            Point2f midPoint = calcMidPoint(p1, p2);
            MacroTriangle triangle = null;
            if (level > 1) {
                // generate deeper levels first (in order to enable bottom-up
                // bbox propagation)
                long time = -1;
                if (!(locationCode.substring(1)).contains("1")) {
                    time = System.currentTimeMillis();
                }

                MacroTriangle child1 = createTriangleTree(midPoint, p0, p1, level - 1, locationCode + "0");
                MacroTriangle child2 = createTriangleTree(midPoint, p2, p0, level - 1, locationCode + "1");
                // long time = System.currentTimeMillis();

                triangle = new MacroTriangle(builder, p0, p1, p2, level, locationCode, error, child1, child2);
                // if ( !( locationCode.substring( 1 ) ).contains( "1" ) ) {
                // String message = Thread.currentThread().getName() +
                // " finished creation of level: " + level + " ("
                // + locationCode + ")";
                // System.out.println( LogUtils.createDurationTimeString(
                // message, time, true ) );
                //
                // }

                patchesDone++;
            } else {
                if (level < 1) {
                    System.err.println("The level is smaller than 1, this may not be!");
                }
                triangle = new MacroTriangle(builder, p0, p1, p2, level, locationCode, error, null, null);
                patchesDone++;
            }

            if (patchesDone % 100 == 0) {
                System.out.println("Patches: " + patchesDone);
            }

            try {
                storeMacroTriangle(triangle);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(locationCode);
            return triangle;
        }

        private float estimateError(Point2f p0, Point2f p1, Point2f p2) {
            // ms: TODO implement a real error calculation
            Point2f midPoint = calcMidPoint(p1, p2);
            float dist = p0.distance(midPoint);
            float heixelDist = dist / getRowsPerFragment();
            return heixelDist;
        }

        private void storeMacroTriangle(MacroTriangle tile) throws SQLException {
            tile.generateTileData(getTileHeight(), getRowsPerFragment(), tileVertices, vertexNormals,
                    tileTriangles);

            ByteBuffer rawTileBuffer = ByteBuffer.allocate(getBytesPerTile());
            rawTileBuffer.order(ByteOrder.nativeOrder());

            // store number of vertices
            rawTileBuffer.putInt(tileVertices.length);

            // store vertices
            int i = 0;
            int pos = 0;
            for (Point3f vertex : tileVertices) {
                rawTileBuffer.putFloat(vertex.x);
                pos += 4;
                rawTileBuffer.putFloat(vertex.y);
                pos += 4;
                rawTileBuffer.putFloat(vertex.z);
                pos += 4;
                i++;
            }

            // store normals
            for (Vector3f normal : vertexNormals) {
                put(rawTileBuffer, normal.x, BYTES_PER_NORMAL_COMPONENT);
                put(rawTileBuffer, normal.y, BYTES_PER_NORMAL_COMPONENT);
                put(rawTileBuffer, normal.z, BYTES_PER_NORMAL_COMPONENT);
            }

            triangleManager.storePatch(tile, rawTileBuffer);
        }
    }

    private void put(ByteBuffer buffer, float value, int bytesPerValue) {
        if (bytesPerValue == 4) {
            buffer.putFloat(value);
        } else if (bytesPerValue == 2) {
            short s = 0;
            if (value < 1) {
                s = (short) (32768.0f * value);
            } else {
                s = (short) (32767.0f * value);
            }
            buffer.putShort(s);
        } else if (bytesPerValue == 1) {
            byte b = 0;
            if (value < 1) {
                b = (byte) (128.0f * value);
            } else {
                b = (byte) (127.0f * value);
            }
            buffer.put(b);
        }
    }

    /**
     * Generates a {@link MultiresolutionMesh} instance (MRIndex- and PatchData-BLOBs) from a file that contains binary
     * short values (a regular heightfield).
     * <p>
     * Please see the code for the initialization of the parameters.
     * 
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        CommandLineParser parser = new PosixParser();

        Options options = initOptions();
        boolean verbose = false;

        // for the moment, using the CLI API there is no way to respond to a
        // help argument; see
        // https://issues.apache.org/jira/browse/CLI-179
        if (args != null && args.length > 0) {
            for (String a : args) {
                if (a != null && a.toLowerCase().contains("help") || "-?".equals(a)) {
                    printHelp(options);
                }
            }
        }
        CommandLine line = null;
        try {
            line = parser.parse(options, args);
            verbose = line.hasOption(OPT_VERBOSE);
            init(line);
        } catch (ParseException exp) {
            System.err.println("ERROR: Invalid command line: " + exp.getMessage());
            printHelp(options);
        } catch (Throwable e) {
            System.err.println(
                    "An Exception occurred while creating a Multiresolution mesh from your data, error message: "
                            + e.getMessage());
            if (verbose) {
                e.printStackTrace();
            }
            System.exit(1);
        }
    }

    private static void init(CommandLine line) throws ParseException, IOException, SQLException {

        String t = line.getOptionValue(OPT_OUTPUT_LEVELS, "-1");
        int levels = Integer.parseInt(t);
        t = line.getOptionValue(OPT_OUTPUT_ROWS, "128");
        int rows = Integer.parseInt(t);
        t = line.getOptionValue(OPT_MAX_HEIGHT);
        float maxZ = Float.NaN;
        if (t != null) {
            maxZ = Float.parseFloat(t);
        }

        RasterIOOptions rasterIOOptions = RasterOptionsParser.parseRasterIOOptions(line);
        AbstractCoverage raster = RasterOptionsParser.loadCoverage(line, rasterIOOptions);
        if (!(raster instanceof AbstractRaster)) {
            throw new IllegalArgumentException(
                    "Given raster location is a multiresolution raster, this is not supported.");
        }

        DEMDatasetGenerator builder = new DEMDatasetGenerator((AbstractRaster) raster, rasterIOOptions, levels,
                rows, maxZ);

        t = line.getOptionValue(OPT_OUTPUT_DIR);
        File outputDir = new File(t);
        if (outputDir.getFreeSpace() < builder.fileSize) {
            System.err.println("Not enough space (" + outputDir.getFreeSpace() + " bytes ca: "
                    + (Math.round((outputDir.getFreeSpace() / (1024 * 1024d)) * 100d) / 100d)
                    + " Mb.) free in the directory: " + outputDir + " please specify a location where at least: "
                    + builder.fileSize + " bytes (ca. "
                    + (Math.round((builder.fileSize / (1024 * 1024d)) * 100d) / 100d) + " Mb) are available.");
            System.exit(2);
        }
        Blob patchesBlob = new FileBlob(new File(outputDir, MultiresolutionMesh.FRAGMENTS_FILE_NAME));

        PatchManager triangleManager = new PatchManager(builder.getLevels(), patchesBlob);
        System.out.println(triangleManager);

        // generate macro triangle blob
        double sampleSizeX = Math.abs(builder.sampleSizeX);
        double sampleSizeY = Math.abs(builder.sampleSizeY);
        float outputExtentX = (float) (builder.outputX * sampleSizeX);
        float outputExtentY = (float) (builder.outputY * sampleSizeY);
        PatchManager manager = builder.generateMacroTriangles(triangleManager, 0, 0, outputExtentX, outputExtentY);

        // write mrindex blob
        Blob mrIndexBlob = new FileBlob(new File(outputDir, MultiresolutionMesh.INDEX_FILE_NAME));
        DAGBuilder dagBuilder = new DAGBuilder(manager.getLevels(), manager);
        dagBuilder.writeBlob(mrIndexBlob, (short) 0, (short) rows);
        dagBuilder.printStats();
        mrIndexBlob.free();
    }

    private static Options initOptions() {

        Options opts = new Options();

        RasterOptionsParser.addRasterIOLineOptions(opts);

        Option opt = new Option("o", OPT_OUTPUT_DIR, true, "output directory");
        opt.setRequired(true);
        opts.addOption(opt);

        opt = new Option("ol", OPT_OUTPUT_LEVELS, true,
                "number of resolution levels in the generated multiresolution model.");
        opts.addOption(opt);

        opt = new Option("or", OPT_OUTPUT_ROWS, true, "number of rows per macrotriangle");
        opt.setDescription("32|64|128");
        opts.addOption(opt);

        opt = new Option("mh", OPT_MAX_HEIGHT, true,
                "maximum z-value, every higher value is clipped to no data value");
        opts.addOption(opt);

        CommandUtils.addDefaultOptions(opts);

        return opts;
    }

    private static void printHelp(Options options) {
        CommandUtils.printHelp(options, DEMDatasetGenerator.class.getSimpleName(), null, "outputdir");
    }

    public void disposeLoadedRasterData() {
        dataBuffer.dispose();
    }
}