org.n52.v3d.terrainserver.povraywts.WebTerrainServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.n52.v3d.terrainserver.povraywts.WebTerrainServlet.java

Source

/***************************************************************************************
 * Copyright (C) 2011 by 52 North Initiative for Geospatial Open Source Software GmbH  *
 *                                                                                     *
 * Contact: Benno Schmidt & Martin May, 52 North Initiative for Geospatial Open Source *
 * Software GmbH, Martin-Luther-King-Weg 24, 48155 Muenster, Germany, info@52north.org *
 *                                                                                     *
 * This program is free software; you can redistribute and/or modify it under the      *
 * terms of the GNU General Public License version 2 as published by the Free Software *
 * Foundation.                                                                         *
 *                                                                                     *
 * This program is distributed WITHOUT ANY WARRANTY; even without the implied WARRANTY *
 * OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public  *
 * License for more details.                                                           *
 *                                                                                     *
 * You should have received a copy of the GNU General Public License along with this   *
 * program (see gnu-gpl v2.txt). If not, write to the Free Software Foundation, Inc.,  *
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, or visit the Free Software *
 * Foundation web page, http://www.fsf.org.                                            *
 **************************************************************************************/
package org.n52.v3d.terrainserver.povraywts;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.n52.v3d.terrainserver.demservice.DEMServiceHelpers;
import org.n52.v3d.triturus.core.T3dException;
import org.n52.v3d.triturus.core.T3dExceptionMessage;
import org.n52.v3d.triturus.gisimplm.GmEnvelope;
import org.n52.v3d.triturus.gisimplm.GmSimple2dGridGeometry;
import org.n52.v3d.triturus.survey.Wgs84Helper;
import org.n52.v3d.triturus.t3dutil.T3dColor;
import org.n52.v3d.triturus.t3dutil.T3dTimeList;
import org.n52.v3d.triturus.t3dutil.T3dVector;
import org.n52.v3d.triturus.t3dutil.operatingsystem.FileTools;
import org.n52.v3d.triturus.t3dutil.operatingsystem.TimeSliceAssigner;
import org.n52.v3d.triturus.vgis.VgElevationGrid;
import org.n52.v3d.triturus.vgis.VgEnvelope;
import org.n52.v3d.triturus.vgis.VgPoint;
import org.n52.v3d.triturus.vispovray.PovrayScene;
import org.n52.v3d.triturus.vscene.VsCamera;
import org.n52.v3d.triturus.vscene.VsSimpleScene;
import org.n52.v3d.triturus.vscene.VsViewpoint;
import org.n52.v3d.triturus.web.HttpRequestParams;
import org.n52.v3d.triturus.web.HttpStandardResponse;
import org.n52.v3d.triturus.web.IoHttpURLReader;
import org.n52.v3d.triturus.web.IoWMSConnector;
import org.n52.v3d.triturus.web.MimeTypeHelper;
import org.n52.v3d.triturus.web.WMSRequestConfig;

/**
 * Web Terrain Service (OGC-WTS 0.x) implementation using POV-Ray as rendering engine.<br /><br />
 * <i>German:</i> Implementierung eines Web Terrain Services (WTS) unter Nutzung von POV-Ray als Renderer.<br />
 * Beispielaufruf:
 * <tt>http://<hostname>/WebTerrainServlet?REQUEST=GetView&SRS=EPSG:31466&BBOX=2592761.3,5741340.4,2600772.4,5753813.3</tt>
 * @author Benno Schmidt
 */
public class WebTerrainServlet extends HttpServlet {
    private Log sLogger = LogFactory.getLog(WebTerrainServlet.class);

    private static int mCounter = 0;
    private static short mRendererInstances = 0;
    private static TimeSliceAssigner mTimeSliceAssigner = null;

    // Einstellungen aus Deployment-Deskriptor:
    private String mCapabilitiesFile;
    private String mSourceGridPath;
    private String mShellCommand;
    private String mShellCommandParams;
    private boolean mShellCommandQuot;
    private String mWorkingDirectory;
    private String mPovrayInstallationPath;
    private String mPovrayExec;
    private boolean mPovrayWin;
    private String mDefaultDrape;
    private String mTileLocator = "TK25";
    private double mMaxArea = 1000000000.; // 1000 km^2
    private double mMinCellSize = 50.;
    private double mMinCellSizeLatLon = 4.629627e-4;
    private double mSearchRadiusMin = 49.99;
    private boolean mWebConnectProxySet;
    private String mWebConnectProxyHost;
    private int mWebConnectProxyPort;
    private String mWebConnectNonProxyHosts;
    private String mCopyrightTextContent;
    private String mCopyrightTextFont;
    private int mCopyrightTextSize;
    private T3dColor mCopyrightTextColor = new T3dColor();
    private T3dColor mPedestalColor = new T3dColor();
    private int mSessionMaxInactiveInterval = 5 * 60; // 10 min
    private short mMaxRendererInstances = 3;
    private boolean mUseTimeSlices = true;
    private int mMaxWaitTime = 5;
    private long mTimeSliceDuration = 4000;
    private long mRendererTimeout = 20000;
    private boolean mRendererImmediateTermination = false;
    private String mMonProtUser = "wtsadmin";
    private String mMonProtPasswd = "geheim";
    private boolean mLocalDebug = false;
    private String mErrMsgFile = "";

    private static final short sPNGOutput = 1;
    private static final short sJPEGOutput = 2;
    private static final short sBMPOutput = 3;

    /**
     * liest die Ablaufparameter aus dem Deployment-Deskriptor und bertrgt die Werte in entsprechende
     * Member-Variablen.<p>
     */
    public void fetchInitParameters() {
        mCapabilitiesFile = this.getInitParameter("CapabilitiesFile");
        mSourceGridPath = this.getInitParameter("SourceGridPath");
        mShellCommand = this.getInitParameter("ShellCommand");
        mShellCommand = this.getInitParameter("ShellCommand");
        mShellCommandParams = this.getInitParameter("ShellCommandParams");
        mShellCommandQuot = Boolean.valueOf(this.getInitParameter("ShellCommandQuot")).booleanValue();
        mWorkingDirectory = this.getInitParameter("WorkingDirectory");
        mPovrayInstallationPath = this.getInitParameter("PovrayInstallationPath");
        mPovrayExec = this.getInitParameter("PovrayExec");
        mPovrayWin = Boolean.valueOf(this.getInitParameter("PovrayWin")).booleanValue();
        mDefaultDrape = this.getInitParameter("DefaultDrape");
        mTileLocator = this.getInitParameter("TileLocator");
        mMaxArea = Double.parseDouble(this.getInitParameter("MaxArea"));
        mMinCellSize = Double.parseDouble(this.getInitParameter("MinCellSize"));
        mMinCellSizeLatLon = Double.parseDouble(this.getInitParameter("MinCellSizeLatLon"));
        mSearchRadiusMin = Double.parseDouble(this.getInitParameter("SearchRadiusMin"));
        mWebConnectProxySet = Boolean.valueOf(this.getInitParameter("WebConnectProxySet")).booleanValue();
        mWebConnectProxyHost = this.getInitParameter("WebConnectProxyHost");
        mWebConnectProxyPort = Integer.parseInt(this.getInitParameter("WebConnectProxyPort"));
        mWebConnectNonProxyHosts = this.getInitParameter("WebConnectNonProxyHosts");
        mCopyrightTextContent = this.getInitParameter("CopyrightTextContent");
        mCopyrightTextFont = this.getInitParameter("CopyrightTextFont");
        mCopyrightTextSize = Integer.parseInt(this.getInitParameter("CopyrightTextSize"));
        mCopyrightTextColor.setHexEncodedValue(this.getInitParameter("CopyrightTextColor"));
        mPedestalColor.setHexEncodedValue(this.getInitParameter("PedestalColor"));
        mSessionMaxInactiveInterval = Integer.parseInt(this.getInitParameter("SessionMaxInactiveInterval"));
        mMaxRendererInstances = Short.parseShort(this.getInitParameter("MaxRendererInstances"));
        mUseTimeSlices = Boolean.valueOf(this.getInitParameter("UseTimeSlices")).booleanValue();
        mMaxWaitTime = Integer.parseInt(this.getInitParameter("MaxWaitTime"));
        mTimeSliceDuration = Long.parseLong(this.getInitParameter("TimeSliceDuration"));
        mRendererTimeout = Long.parseLong(this.getInitParameter("RendererTimeout"));
        mRendererImmediateTermination = Boolean.valueOf(this.getInitParameter("RendererImmediateTermination"))
                .booleanValue();
        mMonProtUser = this.getInitParameter("MonProtUser");
        mMonProtPasswd = this.getInitParameter("MonProtPasswd");
        mLocalDebug = Boolean.valueOf(this.getInitParameter("LocalDebug")).booleanValue();
        mErrMsgFile = this.getInitParameter("ErrMsgFile");
    }

    /*    private String fixFilePath(String initParameter) {
    String tempString = initParameter.replace();
    return
        }*/

    /**
     * initialisiert das Servlet.<p>
     */
    public void init(ServletConfig pConf) throws ServletException {
        sLogger.debug("WebTerrainServlet: init() call");

        super.init(pConf);

        // Initialisierungsparameter aus "web.xml" lesen:
        this.fetchInitParameters();

        if (mUseTimeSlices)
            mTimeSliceAssigner = new TimeSliceAssigner(mMaxWaitTime, mTimeSliceDuration);

        T3dExceptionMessage.getInstance().readConfiguration(mErrMsgFile);
    }

    private HttpRequestParams fetchRequestParameters(HttpServletRequest pReq) {
        HttpRequestParams lReqParams = new HttpRequestParams();

        // Bekanntgabe der Anfrage-Parameter, damit diese als Defaults verfgbar und/oder damit diese
        // getypt sind und somit automatisch geparst werden:
        lReqParams.addParameter("REQUEST", "String", "GetCapabilities");
        lReqParams.addParameter("SRS", "String", "");
        lReqParams.addParameter("BBOX", "VgEnvelope", "0.0,0.0,0.0,0.0");
        lReqParams.addParameter("POI", "VgPoint", null);
        lReqParams.addParameter("YAW", "Double", "0.0"); // Azimut in Altgrad
        lReqParams.addParameter("PITCH", "Double", "-45.0"); // Neigungswinkel in Altgrad
        lReqParams.addParameter("DISTANCE", "Double", "0.0");
        lReqParams.addParameter("AOV", "Double", "25.0");
        lReqParams.addParameter("FORMAT", "String", "image/png");
        lReqParams.addParameter("WIDTH", "Integer", "640");
        lReqParams.addParameter("HEIGHT", "Integer", "480");
        lReqParams.addParameter("EXAGGERATION", "Double", "5.0");
        lReqParams.addParameter("DRAPE", "String", "");
        lReqParams.addParameter("WMSLAYERS", "String", "");
        lReqParams.addParameter("WMSRES", "Double", "1.0");
        lReqParams.addParameter("SEARCHRADIUS", "Double", "49.99");
        lReqParams.addParameter("VISADDS", "Integer", "4");
        lReqParams.addParameter("TRANSPARENT", "Boolean", "false");
        lReqParams.addParameter("BGCOLOR", "String", "0x000000");
        lReqParams.addParameter("CACHESCENE", "Boolean", "false"); // > 0 fr Caching!
        lReqParams.addParameter("EXCEPTIONS", "String", "application/vnd.ogc.se_xml");
        lReqParams.addParameter("QUALITY", "Integer", "75");
        lReqParams.addParameter("LIGHTINT", "Double", "1.0");
        lReqParams.addParameter("MONPROTUSER", "String", "");
        lReqParams.addParameter("MONPROTPASSWD", "String", "");

        lReqParams.fetchRequestParameters(pReq);

        // Rckgabe der Session-spezifischen Request-Parameter:
        return lReqParams;
    }

    /**
     * bearbeitet HTTP-Post-Anfragen an das Servlet.<p>
     * @param pRequest HTTP-Anfrage-Objekt
     * @param pResponse HTTP-Antwort-Objekt
     * @throws ServletException
     * @throws IOException
     */
    public void doPost(HttpServletRequest pRequest, HttpServletResponse pResponse)
            throws ServletException, IOException {
        this.doGet(pRequest, pResponse);
    }

    /**
     * bearbeitet HTTP-Get-Anfragen an das Servlet.<p>
     * @param pRequest HTTP-Anfrage-Objekt
     * @param pResponse HTTP-Antwort-Objekt
     * @throws ServletException
     * @throws IOException
     */
    public void doGet(HttpServletRequest pRequest, HttpServletResponse pResponse)
            throws ServletException, IOException {
        T3dTimeList lTimeProt = new T3dTimeList(); // zur Protokollierung der Rechenzeiten
        lTimeProt.addTimeStamp("init");

        /*
        Enumeration hnames = pRequest.getHeaderNames();
        while(hnames.hasMoreElements()){
        String header = (String) hnames.nextElement();
        Enumeration headers = pRequest.getHeaders(header);
        while (headers.hasMoreElements()) {
        System.out.println("header: " + header + ",  value=" +  (String) headers.nextElement());
        //if (header.equalsIgnoreCase("accept") && pRequest.getHeader(header).startsWith("image/"))
        //    return;
        }
        }        */

        // Eindeutigen Temporrdatei-Rumpf fr aktuelle Anfrage festlegen:
        String lTmpName = "~" + (mCounter++) + "_" + new java.util.Date().getTime();

        // Objektdefinitionen fr Temporrdateien-Verwaltung:
        boolean lCacheScene = false;
        TempFileHandler lRequTmpMngr = null;
        boolean lKeepTempFilesInCaseOfError = false;
        TempFileHandler lSessionTmpMngr = null;

        try {
            // Request-Parameter holen:
            HttpRequestParams lReqParams = this.fetchRequestParameters(pRequest);
            String lSRS = (String) lReqParams.getParameterValue("SRS");
            String lRequest = (String) lReqParams.getParameterValue("REQUEST");
            GmEnvelope lBBox = (GmEnvelope) lReqParams.getParameterValue("BBOX");
            VgPoint lPoi;
            try {
                lPoi = (VgPoint) lReqParams.getParameterValue("POI");
            } catch (T3dException e) {
                lPoi = null;
            }
            double lYaw = ((Double) lReqParams.getParameterValue("YAW")).doubleValue();
            double lPitch = ((Double) lReqParams.getParameterValue("PITCH")).doubleValue();
            double lDistance;
            try {
                lDistance = ((Double) lReqParams.getParameterValue("DISTANCE")).doubleValue();
            } catch (T3dException e) {
                lDistance = 0.;
            }
            double lAov = ((Double) lReqParams.getParameterValue("AOV")).doubleValue();
            String lFormat = (String) lReqParams.getParameterValue("FORMAT");
            int lWidth = ((Integer) lReqParams.getParameterValue("WIDTH")).intValue();
            int lHeight = ((Integer) lReqParams.getParameterValue("HEIGHT")).intValue();
            double lExaggeration = ((Double) lReqParams.getParameterValue("EXAGGERATION")).doubleValue();
            String lDrape = (String) lReqParams.getParameterValue("DRAPE");
            String lWmsLayers = (String) lReqParams.getParameterValue("WMSLAYERS");
            double lWmsRes = ((Double) lReqParams.getParameterValue("WMSRES")).doubleValue();
            double lSearchRadius = ((Double) lReqParams.getParameterValue("SEARCHRADIUS")).doubleValue();
            int lVisAdds = ((Integer) lReqParams.getParameterValue("VISADDS")).intValue();
            boolean lTransparent = ((Boolean) lReqParams.getParameterValue("TRANSPARENT")).booleanValue();
            String lBgColorHex = (String) lReqParams.getParameterValue("BGCOLOR");
            lCacheScene = ((Boolean) lReqParams.getParameterValue("CACHESCENE")).booleanValue();
            String lExceptions = (String) lReqParams.getParameterValue("EXCEPTIONS");
            int lQuality = ((Integer) lReqParams.getParameterValue("QUALITY")).intValue();
            double lLightInt = ((Double) lReqParams.getParameterValue("LIGHTINT")).doubleValue();
            String lMonProtUser = (String) lReqParams.getParameterValue("MONPROTUSER");
            String lMonProtPasswd = (String) lReqParams.getParameterValue("MONPROTPASSWD");

            if (lRequest.equalsIgnoreCase("GetCapabilities")) {
                HttpStandardResponse response = new HttpStandardResponse();
                response.sendXMLFile(mCapabilitiesFile, pResponse);
                this.logGetCapabilitiesInfo(lTmpName, pRequest);
                return;
            }

            if (lRequest.equalsIgnoreCase("GetMonProtocol")) {
                // hier evtl. besseren Zugriffsschutz realisieren...
                if (pRequest.getMethod().equalsIgnoreCase("POST")) {
                    if ((mMonProtUser != null && mMonProtPasswd != null && lMonProtUser.compareTo(mMonProtUser) == 0
                            && lMonProtPasswd.compareTo(mMonProtPasswd) == 0)
                            || lMonProtUser.compareTo("wtsadmin") == 0 && lMonProtPasswd.compareTo("geheim") == 0) {
                        this.monitorProtocol(pResponse);
                    }
                }
                return;
            }

            if (!lRequest.equalsIgnoreCase("GetView")) {
                lKeepTempFilesInCaseOfError = false;
                throw new T3dException("Illegal request type " + lRequest + "...");
            }

            // Request-Parameter aufbereiten und Wertebereiche prfen:
            ParameterPreparer pp = new ParameterPreparer();
            lSRS = pp.prepareSRS(lSRS);
            lBBox = pp.prepareBBOX(lBBox, lSRS);
            lPoi = pp.preparePOI(lPoi, lSRS);
            lYaw = pp.prepareYAW(lYaw);
            lPitch = pp.preparePITCH(lPitch);
            lDistance = pp.prepareDISTANCE(lDistance);
            lAov = pp.prepareAOV(lAov);
            lWmsLayers = pp.prepareWMSLAYERS(lWmsLayers);
            lWmsRes = pp.prepareWMSRES(lWmsRes);
            lTransparent = pp.prepareTRANSPARENT(lTransparent);
            T3dColor lBgColor = pp.prepareBGCOLOR(lBgColorHex);
            lWidth = pp.prepareWIDTH(lWidth);
            lHeight = pp.prepareHEIGHT(lHeight);
            lExceptions = pp.prepareEXCEPTIONS(lExceptions);
            lQuality = pp.prepareQUALITY(lQuality);
            lLightInt = pp.prepareLIGHTINT(lLightInt);

            sLogger.debug("WebTerrainServlet (" + lTmpName + "): Received GetView request.");

            if (lSearchRadius < mSearchRadiusMin) // todo
                lSearchRadius = mSearchRadiusMin;

            if (!lBBox.hasMetricSRS()) {
                if (lBBox.hasGeographicSRS()) {
                    // metrisch bentigte Parameter umrechnen
                    lExaggeration /= Wgs84Helper.degree2meter;
                    lSearchRadius /= Wgs84Helper.degree2meter;
                } else
                    throw new T3dException("Missing SRS support for \"" + lBBox.getSRS() + "\".");
            }

            // Temporrdateien-Verwaltung gewhrleisten und ggf. Session-Objekt holen:
            WTSSession lWtsSession = new WTSSession();
            lWtsSession = this.setUpSession(lWtsSession, lCacheScene, pRequest, lBBox, lDrape, lWmsLayers);
            HttpSession lSession = lWtsSession.getHttpSession();
            lRequTmpMngr = lWtsSession.getRequTempFileHandler();
            if (lCacheScene)
                lSessionTmpMngr = lWtsSession.getSessionTempFileHandler();
            lTimeProt.setFinished("init");

            // Hhenmodell berechnen (Gridding):
            lTimeProt.addTimeStamp("dem_access");
            VgElevationGrid lTerrain = this.setUpTerrain(lCacheScene, lSession, lBBox, lHeight, lWidth,
                    lSearchRadius);
            lTimeProt.setFinished("dem_access");

            // Drape holen:
            lTimeProt.addTimeStamp("drape_access");
            String lDrapeFile = this.setUpDrape(lCacheScene, lWtsSession, lDrape, lWmsLayers, lBBox, lSRS, lWidth,
                    lTerrain, lTmpName, lWmsRes);
            if (mLocalDebug)
                System.out.println("lDrapeFile = \"" + lDrapeFile + "\"");
            lTimeProt.setFinished("drape_access");

            // POV-Ray-Szene definieren:
            lTimeProt.addTimeStamp("scene_def");
            if (lDistance == 0. && lPoi == null)
                lDistance = this.determineDefaultDistance(lTerrain, lYaw * Math.PI / 180., lExaggeration,
                        2. * lAov);
            VsSimpleScene lScene = this.defineScene(lTerrain, lDrapeFile, lVisAdds, lDistance, lPoi,
                    lYaw * Math.PI / 180., -lPitch * Math.PI / 180., lExaggeration, 2. * lAov);
            lScene.setBackgroundColor(lBgColor);
            lRequTmpMngr.addTempFile(mWorkingDirectory + "/" + lTmpName + ".pov");
            lRequTmpMngr.addTempFile(mWorkingDirectory + "/" + lTmpName + ".bat");
            lTimeProt.setFinished("scene_def");

            // POV-Ray-Umgebung setzen und Szene rendern: // todo: alles noch zu POV-Ray-spezifisch -> Interface bauen/erweitern?  / j3dwts-Servlet mit gemeinsamem Kern?
            lTimeProt.addTimeStamp("rendering");
            this.configureRenderer(lScene, lWidth, lHeight, lTmpName, lLightInt);
            String lGifEncodedDEM = null;
            if (lCacheScene) // Dateinamen fr GIF-kodiertes Hhenmodell aus Session holen
                lGifEncodedDEM = (String) lSession.getAttribute("demgif_" + lSession.getId());
            boolean lInstanceOverflow = false;
            try {
                if (mUseTimeSlices) {
                    Long lTimeSlice = mTimeSliceAssigner.getAssignedSlice();
                    if (lTimeSlice != null) {
                        long lTimeSliceStart = lTimeSlice.longValue();
                        long lCurrTime = mTimeSliceAssigner.currentTime();
                        if (lTimeSliceStart > lCurrTime) {
                            long lDelay = lTimeSliceStart - lCurrTime;
                            sLogger.debug("WebTerrainServlet (" + lTmpName + "): "
                                    + "Rendering process will be delayed for " + lDelay + " msecs...");
                            Thread.sleep(lDelay);
                        } else {
                            // Bem.: Fr lTimeSliceStart <= lCurrTime keine Verzgerung
                            if (mLocalDebug)
                                System.out.println("Rendering process will not be be delayed...");
                        }
                    } else {
                        // keine freie Zeitscheibe verfgbar
                        lKeepTempFilesInCaseOfError = false;
                        throw new T3dException("The server is too busy at the moment. Please try again later.",
                                100);
                    }
                }

                if (mRendererInstances < mMaxRendererInstances) {
                    mRendererInstances++;
                    try {
                        ((PovrayScene) lScene).setLocalDebug(mLocalDebug);

                        if (lGifEncodedDEM == null) // CACHESCENE nicht gesetzt oder 1. Aufruf (GIF-Dateiname nicht in Session)
                            ((PovrayScene) lScene).render();
                        else
                            ((PovrayScene) lScene).renderCachedDEM(lGifEncodedDEM);
                    } catch (Throwable e) {
                        mRendererInstances--;
                        lKeepTempFilesInCaseOfError = true;
                        throw e;
                    }
                    //System.out.println("mRendererInstances = " + mRendererInstances);
                    mRendererInstances--;
                } else
                    lInstanceOverflow = true;
            } catch (T3dException e) {
                lKeepTempFilesInCaseOfError = true;
                throw e;
            } catch (Exception e) {
                lKeepTempFilesInCaseOfError = true;
                throw new T3dException("For unknown reasons, the scene could not be rendered...", 400);
            }
            if (lInstanceOverflow) {
                lKeepTempFilesInCaseOfError = false;
                throw new T3dException("The server is too busy at the moment. Please try again later.", 101);
            }
            if (lCacheScene) {
                // GIF in Session legen
                lSession.setAttribute("demgif_" + lSession.getId(), ((PovrayScene) lScene).getGifEncodedDEM());
                lSessionTmpMngr.addTempFile(((PovrayScene) lScene).getGifEncodedDEM());
            } else
                lRequTmpMngr.addTempFile(((PovrayScene) lScene).getGifEncodedDEM());
            lRequTmpMngr.addTempFile(mWorkingDirectory + "/" + lTmpName + ".png");
            if (!mRendererImmediateTermination)
                lTimeProt.setFinished("rendering");

            // POV-Ray-Ergebnisbild holen:
            if (!mRendererImmediateTermination)
                lTimeProt.addTimeStamp("prepare_image");
            String resExt = MimeTypeHelper.getFileExtension(((PovrayScene) lScene).getImageFormat());
            String resFile = mWorkingDirectory + "/" + lTmpName + "." + resExt;
            sLogger.debug("WebTerrainServlet (" + lTmpName + "): Preparing image \"" + resFile + "\"...");
            File f = new File(resFile);
            ImageInputStream is = ImageIO.createImageInputStream(f);
            if (!mRendererImmediateTermination) {
                if (is == null) {
                    lKeepTempFilesInCaseOfError = true;
                    throw new T3dException("For unknown reasons, the renderer did not generate an image file.",
                            401);
                }
            } else {
                int lMaxIntervalChecks = 40;
                long lCheckInterval = mRendererTimeout / lMaxIntervalChecks;
                int ct = 0;
                while (is == null && ct < lMaxIntervalChecks) {
                    ct++;
                    try {
                        Thread.sleep(lCheckInterval);
                    } catch (Exception e2) {
                    }
                    ;
                    is = ImageIO.createImageInputStream(f);
                }
                if (is == null && ct >= lMaxIntervalChecks) {
                    lKeepTempFilesInCaseOfError = true;
                    throw new T3dException("An I/O exception occured. The renderer did not generate an image file.",
                            402);
                }
                lTimeProt.setFinished("rendering");
                lTimeProt.addTimeStamp("prepare_image");
            }
            BufferedImage lImage = this.prepareImage(is, lHeight, lWidth, lTerrain, lPitch, lYaw, lDistance,
                    lExaggeration, (lVisAdds & 4) > 0, (lVisAdds & 8) > 0);
            lTimeProt.setFinished("prepare_image");

            // Ergebnisbild als Antwort senden:
            lTimeProt.addTimeStamp("send_response");
            try {
                this.sendResponse(lFormat, pResponse, lImage, resExt, lQuality);
            } catch (Throwable e) {
                is.close();
                lKeepTempFilesInCaseOfError = false;
                throw e;
            }
            is.close();
            lTimeProt.setFinished("send_response");

            String lOutputFormatInfo = lFormat + " (" + lWidth + "x" + lHeight + ")";
            this.logGetViewInfo(lTmpName, lTerrain, lTimeProt, pRequest, lOutputFormatInfo);
            //this.removeTempFiles(lRequTmpMngr, lSessionTmpMngr, lCacheScene);
            sLogger.debug("WebTerrainServlet (" + lTmpName + "): Duly finished execution.");
        } catch (Throwable e) {
            sLogger.debug("WebTerrainServlet (" + lTmpName + "): Aborting execution. Error: " + e.getMessage());

            if (!lKeepTempFilesInCaseOfError)
                this.removeTempFiles(lRequTmpMngr, lSessionTmpMngr, lCacheScene);

            HttpStandardResponse response = new HttpStandardResponse();
            try {
                String lExceptions = (String) this.fetchRequestParameters(pRequest).getParameterValue("EXCEPTIONS");
                if (lExceptions.equalsIgnoreCase("application/vnd.ogc.se_inimage")) {
                    int lWidth = ((Integer) this.fetchRequestParameters(pRequest).getParameterValue("WIDTH"))
                            .intValue();
                    int lHeight = ((Integer) this.fetchRequestParameters(pRequest).getParameterValue("HEIGHT"))
                            .intValue();
                    String lFormat = (String) this.fetchRequestParameters(pRequest).getParameterValue("FORMAT");
                    response.sendException(T3dExceptionMessage.getInstance().translate(e), pResponse, lFormat,
                            lWidth, lHeight);
                } else
                    response.sendException(e.getMessage(), pResponse);
            } catch (Throwable e2) {
                try {
                    response.sendException(e.getMessage(), pResponse);
                } catch (Throwable e3) {
                    System.out.println("WebTerrainServlet: FATAL ERROR - " + e2.getMessage());
                }
            }
            try {
                this.logErrorInfo(lTmpName, lTimeProt, pRequest, e);
            } catch (Throwable e2) {
                System.out.println("WebTerrainServlet: FATAL ERROR - " + e2.getMessage());
            }
        }
    } // doGet()

    private WTSSession setUpSession(WTSSession pWtsSession, boolean pCacheScene, HttpServletRequest pRequest,
            VgEnvelope pBBox, String pDrape, String pWmsLayers) {
        HttpSession lSession;
        TempFileHandler lRequTmpMngr;
        TempFileHandler lSessionTmpMngr = null;

        if (pCacheScene) {
            lSession = pRequest.getSession(true);
            if (lSession == null)
                throw new T3dException("Could not get session object...", 102);

            lRequTmpMngr = new TempFileHandler();

            if (lSession.isNew()) {
                lSession.setMaxInactiveInterval(mSessionMaxInactiveInterval);
                lSessionTmpMngr = new TempFileHandler();
                lSession.setAttribute("shndlr_" + lSession.getId(), lSessionTmpMngr);
            } else {
                lSessionTmpMngr = (TempFileHandler) lSession.getAttribute("shndlr_" + lSession.getId());
                if (lSessionTmpMngr == null) {
                    // Session nicht neu, aber lTmpMngr nicht in Session, Fall tritt z. B. in JSP-Client auf.
                    lSessionTmpMngr = new TempFileHandler();
                    lSession.setAttribute("shndlr_" + lSession.getId(), lSessionTmpMngr);
                } else {
                    // Parameterwerte der letzten Anfrage holen...
                    VgEnvelope oldBBox = (VgEnvelope) lSession.getAttribute("rqBBOX_" + lSession.getId()); // BBOX
                    String oldDrape = (String) lSession.getAttribute("rqDRAPE_" + lSession.getId()); // DRAPE
                    String oldWmsLayers = (String) lSession.getAttribute("rqWMSLAYERS_" + lSession.getId()); // WMSLAYERS
                    boolean changesBBox = false, changesDrp = false;
                    // BBOX seit letzter Anfrage gendert?
                    if (oldBBox != null && !oldBBox.isSpatiallyEquivalent(pBBox))
                        changesBBox = true;
                    // DRAPE seit letzter Anfrage gendert?
                    if (oldDrape != null && oldDrape.compareTo(pDrape) != 0)
                        changesDrp = true;
                    // WMSLAYERS seit letzter Anfrage gendert?
                    if (oldWmsLayers != null && oldWmsLayers.compareTo(pWmsLayers) != 0)
                        changesDrp = true;

                    // ... und im Falle relevanter nderungen Cache-Inhalte leeren:
                    if (changesBBox) {
                        lSession.removeAttribute("terrain_" + lSession.getId());
                        lSessionTmpMngr
                                .removeTempFile((String) lSession.getAttribute("demgif_" + lSession.getId()));
                        lSession.removeAttribute("demgif_" + lSession.getId());
                    }
                    if (changesDrp || changesBBox) {
                        lSessionTmpMngr.removeTempFile((String) lSession.getAttribute("drape_" + lSession.getId()));
                        lSession.removeAttribute("drape_" + lSession.getId());
                    }
                }
                lSession.setAttribute("rqBBOX_" + lSession.getId(), pBBox); // BBOX in Session legen
                lSession.setAttribute("rqDRAPE_" + lSession.getId(), pDrape); // DRAPE in Session legen
                lSession.setAttribute("rqWMSLAYERS_" + lSession.getId(), pWmsLayers); // WMSLAYERS in Session legen
            }
        } else {
            // Fr CACHESCENE=false ggf. Objekte aus vorherigen Aufrufen mit CACHESCENE=true aus Session entfernen:
            lSession = pRequest.getSession(false);
            if (lSession != null) {
                lSession.removeAttribute("shndlr_" + lSession.getId());
                lSession.removeAttribute("terrain_" + lSession.getId());
                lSession.removeAttribute("drape_" + lSession.getId());
                lSession.removeAttribute("demgif_" + lSession.getId());
                lSession.invalidate();
            }
            lRequTmpMngr = new TempFileHandler();
        }
        pWtsSession.setHttpSession(lSession);
        pWtsSession.setRequTempFileHandler(lRequTmpMngr);
        pWtsSession.setSessionTempFileHandler(lSessionTmpMngr);
        return pWtsSession;
    }

    private VgElevationGrid setUpTerrain(boolean pCacheScene, HttpSession pSession, GmEnvelope pBBox, int pHeight,
            int pWidth, double pSearchRadius) {
        VgElevationGrid lTerrain = null;
        if (pCacheScene) // Hhenmodell aus Session holen
            lTerrain = (VgElevationGrid) pSession.getAttribute("terrain_" + pSession.getId());
        if (lTerrain == null) { // CACHESCENE nicht gesetzt oder kein gltiges Terrain in Session
            DEMServiceHelpers lHlp = new DEMServiceHelpers(mMaxArea);
            lHlp.setLocalDebug(mLocalDebug);
            double lCellSize = Math.min(pBBox.getExtentX() / pHeight, pBBox.getExtentY() / pWidth);
            if (pBBox.hasGeographicSRS())
                lCellSize = Math.max(lCellSize, mMinCellSizeLatLon);
            else
                lCellSize = Math.max(lCellSize, mMinCellSize);

            try {
                lTerrain = lHlp.setUpDEM(pBBox.getLowerLeftFrontCorner(), pBBox.getUpperRightBackCorner(),
                        lCellSize, pSearchRadius, mTileLocator, mSourceGridPath);
            } catch (T3dException e) {
                String[] errGrd = lHlp.missingGridCells();
                if (errGrd != null) {
                    if (mLocalDebug) {
                        for (int i = 0; i < errGrd.length; i++)
                            System.out.println(errGrd[i]);
                    }
                }
                throw e;
            }

            if (pCacheScene)
                pSession.setAttribute("terrain_" + pSession.getId(), lTerrain); // Terrain in Session legen
        }
        return lTerrain;
    }

    private String setUpDrape(boolean pCacheScene, WTSSession pWtsSession, String pDrape, String pWmsLayers,
            GmEnvelope pBBox, String pSRS, int pWidth, VgElevationGrid pTerrain, String pTmpName, double pWmsRes) {
        String lDrapeFile = null;

        int lCase = 0;
        if (pDrape == null || pDrape.length() <= 0)
            lCase = 1; // kein Drape
        else {
            if (pDrape.toLowerCase().startsWith("http:"))
                lCase = 2; // URL als Drape (Standard-HTTP)
            if (pDrape.toLowerCase().startsWith("https:"))
                lCase = 3; // URL als Drape (HTTPS)
        }
        if (lCase == 2 && pWmsLayers != null && pWmsLayers.length() > 0)
            lCase = 4; // WMS als Drape

        if (pCacheScene) // Drape aus Session holen
            lDrapeFile = (String) pWtsSession.getHttpSession()
                    .getAttribute("drape_" + pWtsSession.getHttpSession().getId());

        if (lDrapeFile == null) { // CACHESCENE nicht gesetzt oder kein gltiger Drape-Dateiname in Session
            switch (lCase) {
            case 1: // kein Drape
                lDrapeFile = mDefaultDrape; // lokale Drape-Datei
                break;
            case 2: // HTTP-URL als Drape
                lDrapeFile = this.getImage(pTmpName, pDrape, false);
                if (lDrapeFile == null)
                    throw new T3dException(
                            "For unknown reasons, the requested drape URL (HTTP) did not provide an image.", 300);
                break;
            case 3: // HTTPS-URL als Drape
                lDrapeFile = this.getImage(pTmpName, pDrape, true);
                if (lDrapeFile == null)
                    throw new T3dException(
                            "For unknown reasons, the requested drape URL (HTTPS) did not provide an image.", 300);
                break;
            case 4: // WMS als Drape
                int lHeight = pWidth * pTerrain.numberOfRows() / pTerrain.numberOfColumns();
                lDrapeFile = this.getMapWMS(pTmpName, pDrape, pWmsLayers, pWidth, lHeight, pBBox, pSRS, pWmsRes);
                if (lDrapeFile == null)
                    throw new T3dException("For unknown reasons, the requested WMS did not provide an image.", 301);
                break;
            default:
                throw new T3dException("Logical error in WebTerrainServlet#doGet.");
            }
            if (pCacheScene) // Drape-Dateiname in Session legen
                pWtsSession.getHttpSession().setAttribute("drape_" + pWtsSession.getHttpSession().getId(),
                        lDrapeFile);
            if (lCase > 1) {
                if (pCacheScene)
                    pWtsSession.getSessionTempFileHandler().addTempFile(lDrapeFile);
                else
                    pWtsSession.getRequTempFileHandler().addTempFile(lDrapeFile);
            }
        }
        // Nachbedingung: lDrapeFile enthlt lokalen Pfad fr Bilddatei
        return lDrapeFile;
    }

    private double determineDefaultDistance(VgElevationGrid pTerrain, double pLambda, double pExaggeration,
            double lFovy) {
        // 1. VsSimpleScene instanzieren und Relief setzen
        VsSimpleScene lScene = new PovrayScene();
        // Statt PovrayScene knnte hier eine beliebige andere VsSimpleScene-Implementierung verwendet werden, da die
        // Szene nur zur Bestimmung einer Abstandsvorgabe dient...
        lScene.setTerrain(pTerrain);
        lScene.setDefaultExaggeration(pExaggeration);

        // 2. Vorgabe-Abstand ermitteln:
        VgEnvelope env = this.getRotatedBBoxEnvelope(lScene.getAspect(), pLambda);
        boolean orthographicView = false;
        if (Math.abs(lFovy) < 0.001) // eigentl.: falls lFovy = 0
            orthographicView = true;
        double radius;
        if (orthographicView)
            return -1.; // Dieser Fall ist in this.defineScene() gesondert handzuhaben...
        else
            radius = 0.5 * Math.max(env.getExtentX(), env.getExtentY()) / Math.tan(lFovy / 2. * Math.PI / 180.);
        return radius / lScene.getScale();
    }

    private VsSimpleScene defineScene(VgElevationGrid pTerrain, String pDrape, int pVisAdds, double pDistance,
            VgPoint pPoi, double pLambda, double pPhi, double pExaggeration, double lFovy) {
        // Szene instanzieren, Relief und Drape setzen:
        VsSimpleScene lScene = new PovrayScene(); // todo: dynamisch instanziieren; Klassenname und Renderer-spez. Properties-Dateiname aus "web.xml"
        ((PovrayScene) lScene).setRendererTimeout(mRendererTimeout);
        ((PovrayScene) lScene).setImmediateTermination(mRendererImmediateTermination);
        lScene.setTerrain(pTerrain);
        lScene.setDrape(pDrape);
        lScene.setDefaultExaggeration(pExaggeration);

        // Szene mit Kamera und Ansichtspunkt versehen:
        VsCamera lCam = new VsCamera();
        lScene.addCamera(lCam);
        VsViewpoint lViewpoint = new VsViewpoint();
        lCam.addViewpoint(lViewpoint);

        // Prfen, ob im Weiteren orthografische Ansicht zu generieren ist:
        boolean orthographicView = false;
        if (Math.abs(lFovy) < 0.001) // eigentl.: falls lFovy = 0
            orthographicView = true;

        // Aktuelle Ansichtspunktinformation setzen, falls kein POI in Anfrage angegeben:
        double radius, x, y, z, d_xy;
        if (pPoi == null) {
            if (!orthographicView) {
                // Bem.: Bedingung pDistance >= 0 ist sicherzustellen.
                if (pDistance > 0.)
                    radius = lScene.getScale() * pDistance;
                else { // pDistance = 0; z. B., falls im Request nicht angegeben
                    VgEnvelope env = this.getRotatedBBoxEnvelope(lScene.getAspect(), pLambda);
                    radius = 0.5 * Math.max(env.getExtentX(), env.getExtentY())
                            / Math.tan(lFovy / 2. * Math.PI / 180.);
                }
                lCam.setProjection(VsCamera.PerspectiveView);
                lCam.setFovy(lFovy);
            } else { // orthografische Ansicht
                radius = 10.; // beliebig, ungleich 0, >> 1 (wegen Clipping-Plane in POV-Ray)
                lCam.setProjection(VsCamera.OrthographicView);
            }
            d_xy = radius * Math.cos(pPhi);
            x = -d_xy * Math.sin(pLambda);
            y = -d_xy * Math.cos(pLambda);
            z = radius * Math.sin(pPhi); // -pi/2. <= pPhi <= pi/2.!
            //System.out.println("d_xy = " + d_xy);
            //System.out.println("x = " + x + ", y = " + y + ", z = " + z);

            double z_offset = 0.5 * (lScene.normZMax() + lScene.normZMin());
            lViewpoint.setLookFrom(lScene.denorm(new T3dVector(x, y, z / pExaggeration + z_offset)));
            lViewpoint.setLookAt(lScene.denorm(new T3dVector(0., 0., z_offset)));
            if (d_xy <= 1.e-6) // Sonderfall Draufsicht
                lViewpoint.setLookUp(new T3dVector(Math.sin(pLambda), Math.cos(pLambda), 0.));
        }

        // Ansichtspunktinformation setzen, falls POI in Anfrage angegeben:
        if (pPoi != null) {
            if (orthographicView)
                throw new T3dException("Please specify a POI, or choose an perspective view.");
            lCam.setProjection(VsCamera.PerspectiveView);
            lCam.setFovy(lFovy);

            T3dVector normPoi = lScene.norm(pPoi);

            if (Math.abs(pDistance) < 0.0001) {
                radius = -10.; // beliebig, < 0
                d_xy = radius * Math.cos(pPhi);
                x = -d_xy * Math.sin(pLambda);
                y = -d_xy * Math.cos(pLambda);
                z = radius * Math.sin(pPhi); // -pi/2. <= pPhi <= pi/2.

                lViewpoint.setLookFrom(pPoi);
                lViewpoint.setLookAt(lScene.denorm(
                        new T3dVector(normPoi.getX() + x, normPoi.getY() + y, normPoi.getZ() + z / pExaggeration)));
            } else {
                radius = lScene.getScale() * pDistance;
                d_xy = radius * Math.cos(pPhi);
                x = -d_xy * Math.sin(pLambda);
                y = -d_xy * Math.cos(pLambda);
                z = radius * Math.sin(pPhi); // -pi/2. <= pPhi <= pi/2.

                lViewpoint.setLookFrom(lScene.denorm(
                        new T3dVector(normPoi.getX() + x, normPoi.getY() + y, normPoi.getZ() + z / pExaggeration)));
                lViewpoint.setLookAt(pPoi);
            }
            if (d_xy <= 1.e-6) // Sonderfall Draufsicht
                lViewpoint.setLookUp(new T3dVector(Math.sin(pLambda), Math.cos(pLambda), 0.));
            //System.out.println("d_xy = " + d_xy);
            //System.out.println("x = " + x + ", y = " + y + ", z = " + z);
        }

        //System.out.println("lookFrom = " + lScene.getCurrentViewpoint().getLookFrom());
        //System.out.println("lookAt = " + lScene.getCurrentViewpoint().getLookAt());
        //System.out.println("lookUp = " + lScene.getCurrentViewpoint().getLookUp());

        // Erweiterte Darzustellungsparameter setzen:
        lScene.drawBBoxShape(false);
        if ((pVisAdds & 1) > 0)
            lScene.drawBBoxShape(true);
        lScene.drawTerrainPedestal(false);
        if ((pVisAdds & 2) > 0) {
            lScene.setPedestalColor(mPedestalColor);
            lScene.drawTerrainPedestal(true);
        }

        // Generierte Szene zurckgeben:
        return lScene;
    }

    private VgEnvelope getRotatedBBoxEnvelope(double pAspect, double pAngle) {
        VgEnvelope bBox;
        if (pAspect > 1.)
            bBox = new GmEnvelope(-pAspect, pAspect, -1., 1., 0., 0.);
        else
            bBox = new GmEnvelope(-1., 1., -pAspect, pAspect, 0., 0.);
        return ((GmEnvelope) bBox).rotateXY(pAngle).envelope();
    }

    private String getMapWMS(String pTmpName, String lDrapeFile, String lWmsLayers, int pWidth, int pHeight,
            VgEnvelope lBBox, String lSRS, double pWmsRes) {
        // WMS-Konfiguration:
        String layers[] = lWmsLayers.split(",");
        float resFact = (float) pWmsRes;
        WMSRequestConfig lWmsRequest = new WMSRequestConfig(lDrapeFile, // URL-Prfix
                layers, // abzufragende Layer
                lBBox, // BBox
                lSRS, // SRS
                Math.round(((float) pWidth) * resFact), Math.round(((float) pHeight) * resFact), // Bildbreite und -hhe
                "image/jpeg"); // Bildformat // todo
        lWmsRequest.setStyles(true, "");

        // WMS-Zugriff:
        IoWMSConnector lConn = new IoWMSConnector(lWmsRequest);
        if (mWebConnectProxySet)
            lConn.connector().setProxy(mWebConnectProxyHost, mWebConnectProxyPort);
        sLogger.debug("WebTerrainServlet (" + pTmpName + "): WMS request \""
                + lConn.getRequestConfiguration().getMapRequestURL() + "\"");
        String resFile = mWorkingDirectory + "/" + pTmpName + "-wms.jpg";
        try {
            lConn.getMap(resFile);
        } catch (Exception e) {
            return null; // von aufrufender Methode abzufangen
        }
        return resFile;
    }

    private String getImage(String pTmpName, String pDrapeURL, boolean pHttps) // todo: HTTPS-Zugriff wird noch nicht untersttzt
    {
        if (mWebConnectProxySet) {
            System.setProperty("proxySet", "true");
            //System.out.println("mWebConnectProxyHost = " + mWebConnectProxyHost);
            System.setProperty("http.proxyHost", mWebConnectProxyHost);
            //System.out.println("mWebConnectProxyPort = " + mWebConnectProxyPort);
            System.setProperty("http.proxyPort", "" + mWebConnectProxyPort);
            //System.out.println("mWebConnectNonProxyHosts = " + mWebConnectNonProxyHosts);
            System.setProperty("http.nonProxyHosts", mWebConnectNonProxyHosts);
        } else
            System.setProperty("proxySet", "false");

        IoHttpURLReader lConn = new IoHttpURLReader(pDrapeURL);
        String fileExtension = FileTools.getExtension(pDrapeURL);
        //String resFile = mWorkingDirectory + "/" + pTmpName + "-drp.jpg";
        String resFile = mWorkingDirectory + "/" + pTmpName + "-drp." + fileExtension;
        sLogger.debug("WebTerrainServlet (" + pTmpName + "): Image request \"" + lConn.getURL() + "\"");

        try {
            lConn.getContent(resFile);
        } catch (Exception e) {
            return null; // von aufrufender Methode abzufangen
        }
        return resFile;
    }

    private void configureRenderer(VsSimpleScene pScene, int pWidth, int pHeight, String pTmpName,
            double pLightIntensity) {
        ((PovrayScene) pScene).setShellCommand(mShellCommand);
        ((PovrayScene) pScene).setShellCommandParams(mShellCommandParams);
        ((PovrayScene) pScene).setShellCommandQuot(mShellCommandQuot);
        ((PovrayScene) pScene).setWorkingDirectory(mWorkingDirectory);
        ((PovrayScene) pScene).setPovrayInstallationPath(mPovrayInstallationPath);
        ((PovrayScene) pScene).setPovrayExecutable(mPovrayExec);
        ((PovrayScene) pScene).setPovrayWin(mPovrayWin);
        ((PovrayScene) pScene).setImageFormat("image/png");
        ((PovrayScene) pScene).setImageSize(pWidth, pHeight);
        ((PovrayScene) pScene).setTempName(pTmpName);
        ((PovrayScene) pScene).setQuality((short) 3); // erstmal nicht von auen konfigurierbar
        ((PovrayScene) pScene).setLightIntensity(2. * pLightIntensity);
        ((PovrayScene) pScene).setDisplayVisible(false);
    }

    private BufferedImage prepareImage(ImageInputStream pImageIn, int pHeight, int pWidth, VgElevationGrid pTerrain,
            double pPitch, double pYaw, double pDistance, double pExaggeration, boolean pDrawNorthArrow,
            boolean pHints) {
        BufferedImage lImage = null;
        Iterator iter = ImageIO.getImageReaders(pImageIn); // liefert PNG-ImageReader
        ImageReader reader = (ImageReader) iter.next();
        reader.setInput(pImageIn, true); // seek forward only?

        boolean ready = false;
        int lMaxIntervalChecks = 40;
        long lCheckInterval = mRendererTimeout / lMaxIntervalChecks;
        int ct = 0;
        while ((!ready) && (ct < lMaxIntervalChecks)) {
            try {
                lImage = reader.read(0);
                ready = true;
            } catch (IOException e) {
                ct++;
                try {
                    Thread.sleep(lCheckInterval);
                } catch (Exception e2) {
                }
                ;
            }
        }
        if (!ready)
            throw new T3dException("An I/O exception occured. The renderer did not generate an image file.", 403);

        // Copyright-Text und Nordpfeil ergnzen:
        this.addAnnotations(lImage, pHeight, pWidth, pPitch, pYaw, pDrawNorthArrow);
        // Ggf. weitere Information (Hints) ergnzen:
        if (pHints)
            this.addHints(lImage, pTerrain, pDistance, pYaw, pExaggeration);

        return lImage;
    }

    private void addAnnotations(BufferedImage pImage, int pHeight, int pWidth, double pPitch, double pYaw,
            boolean pDrawNorthArrow) {
        if (mCopyrightTextContent.length() > 0) {
            Graphics2D g = pImage.createGraphics();
            g.drawImage(pImage, 0, 0, null);
            g.setColor(new java.awt.Color(mCopyrightTextColor.getRed(), mCopyrightTextColor.getGreen(),
                    mCopyrightTextColor.getBlue()));

            // 1. Copyright-Vermerk
            // Etwas unschn: Durch JPEG-Komprimierung wird Text (insb. bei kleiner Font-Gre) wird unscharf...
            // TODO: Abhilfe evtl. durch Hintergrund?
            Font font = new Font(mCopyrightTextFont, Font.BOLD /* Style als int, siehe ggf. API-Dok.*/,
                    mCopyrightTextSize);
            g.setFont(font);
            // mehrzeilige Copyright-Texte erlauben:
            StringTokenizer str = new StringTokenizer(mCopyrightTextContent, "\n");
            int spacePerRow = mCopyrightTextSize;
            int rows = str.countTokens();
            int startPos = spacePerRow * rows;
            int currRow = 0;
            while (str.hasMoreTokens()) {
                int yPos = pHeight - (startPos - (currRow * spacePerRow)) + spacePerRow / 2;
                g.drawString(str.nextToken().trim(), 5, yPos);
                currRow++;
            }

            // 2. Nordpfeil
            if (pDrawNorthArrow) {
                // Zeichenparameter:
                double radius = 35.;
                double phi = 15.;
                // Symbolkonstruktion:
                int rx = (int) radius;
                int ry = (int) Math.round(radius * Math.sin(-pPitch * Math.PI / 180.));
                int mx = pWidth - rx - 5;
                int my = pHeight - ry - 5;
                int dx = (int) (radius * Math.sin(pYaw * Math.PI / 180.));
                int dy = (int) (radius * Math.sin(-pPitch * Math.PI / 180.) * Math.cos(pYaw * Math.PI / 180.));
                int px = mx - dx, py = my - dy; // Pfeilspitze
                int qlx = mx + (int) (radius * Math.sin((pYaw + phi) * Math.PI / 180.));
                int qly = my + (int) (radius * Math.sin(-pPitch * Math.PI / 180.)
                        * Math.cos((pYaw + phi) * Math.PI / 180.));
                int qrx = mx + (int) (radius * Math.sin((pYaw - phi) * Math.PI / 180.));
                int qry = my + (int) (radius * Math.sin(-pPitch * Math.PI / 180.)
                        * Math.cos((pYaw - phi) * Math.PI / 180.));
                // Ellipse zeichnen:
                g.setStroke(new BasicStroke(2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
                g.drawOval(mx - rx, my - ry, 2 * rx, 2 * ry);
                // Striche fr Pfeil zeichnen:

                g.setStroke(new BasicStroke(1.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));

                boolean fillArrow = true;
                if (fillArrow)
                    g.fill(new Polygon(new int[] { px, qlx, qrx }, new int[] { py, qly, qry }, 3));
                else {
                    g.drawLine(px, py, qlx, qly);
                    g.drawLine(px, py, qrx, qry);
                    g.drawLine(qlx, qly, qrx, qry);
                }
            }

            g.dispose();
        }
    }

    private void addHints(BufferedImage pImage, VgElevationGrid pTerrain, double pDistance, double pYaw,
            double pExaggeration) {
        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
        dfs.setDecimalSeparator('.');
        DecimalFormat df = new DecimalFormat("0.0", dfs);

        String deltaX = df.format(pTerrain.getGeometry().envelope().getExtentX() / 1000.);
        String deltaY = df.format(pTerrain.getGeometry().envelope().getExtentY() / 1000.);

        String hint = "DIST: " + df.format(pDistance) + ", EXAGG: " + pExaggeration + ", YAW: " + pYaw + ", BBOX: "
                + deltaX + " x " + deltaY + ", DZ: " + df.format(pTerrain.elevationDifference());

        Graphics2D g = pImage.createGraphics();
        g.drawImage(pImage, 0, 0, null);
        g.setColor(new java.awt.Color(mCopyrightTextColor.getRed(), mCopyrightTextColor.getGreen(),
                mCopyrightTextColor.getBlue()));
        Font font = new Font(mCopyrightTextFont, Font.BOLD /* Style als int, siehe ggf. API-Dok.*/,
                mCopyrightTextSize);
        g.setFont(font);
        g.drawString(hint, 5, mCopyrightTextSize + 5);
        g.dispose();
    }

    private void sendResponse(String lFormat, HttpServletResponse pResponse, BufferedImage lImage, String resExt,
            int pQuality) {
        try {
            OutputStream out = pResponse.getOutputStream();
            short lOutputFormat = -1;
            if (lFormat == null || lFormat.length() <= 0) // sollte vorher abgefangen werden
                throw new T3dException("No output format specified.");
            if (lFormat.equalsIgnoreCase("image/png"))
                lOutputFormat = sPNGOutput;
            if (lFormat.equalsIgnoreCase("image/jpeg"))
                lOutputFormat = sJPEGOutput;
            if (lFormat.equalsIgnoreCase("image/bmp"))
                lOutputFormat = sBMPOutput;
            if (lOutputFormat <= 0) // sollte vorher abgefangen werden
                throw new T3dException("Unsupported output format \"" + lFormat + "\". ");

            switch (lOutputFormat) {
            case sPNGOutput:
                ImageIO.setUseCache(false); // wichtig!
                pResponse.setContentType("image/png");
                //pResponse.setHeader("Cache-Control", "no-store");
                try {
                    ImageIO.write(lImage, resExt, out); // resExt ist informaler Formatname...
                } catch (Exception e) {
                    throw new T3dException("Did not finish PNG image send process. " + e.getMessage(), 103);
                }
                // out.flush(); // Flushing besser vermeiden, damit Server die Lnge der Anbtweort bestimmen kann
                break;
            case sJPEGOutput:
                try {
                    //ImageIO.setUseCache(false); // wichtig!
                    pResponse.setContentType("image/jpeg");
                    ImageIO.write(lImage, "jpeg", out);
                    //                        JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(out); // JPEG-Encoder instanziieren
                    //                        JPEGEncodeParam prm = enc.getDefaultJPEGEncodeParam(lImage);
                    //                        prm.setQuality(((float) pQuality) / 100.f, false);
                    //                        enc.setJPEGEncodeParam(prm);
                    //                        enc.encode(lImage); // Bild als JPEG encoden und an Client senden
                } catch (Exception e) {
                    throw new T3dException("Did not finish JPEG image send process. " + e.getMessage(), 104);
                }
                break;
            case sBMPOutput:
                try {
                    // Merkwrdig, dass nachstehender Code praktisch das korrekte Resultat liefert... (todo)
                    pResponse.setContentType("image/bmp");
                    ImageIO.write(lImage, "bmp", out);
                    //                        JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(out); // JPEG-Encoder instanziieren
                    //                        JPEGEncodeParam prm = enc.getDefaultJPEGEncodeParam(lImage);
                    //                        prm.setQuality(1.0f, false); // Qualitt auf 100% setzen
                    //                        enc.setJPEGEncodeParam(prm);
                    //                        enc.encode(lImage); // Bild als JPG encoden und an Client senden
                    //                        ImageIO.write(lImage, "jpg", out); // !
                } catch (Exception e) {
                    throw new T3dException("Did not finish BMP image send process. " + e.getMessage(), 105);
                }
                break;
            }
            out.close();
        } catch (IOException e) {
            throw new T3dException("An I/O exception occured. The servlet could not send an image reponse.", 106);
        }
    }

    private void removeTempFiles(TempFileHandler pRequTmpMngr, TempFileHandler pSessionTmpMngr,
            boolean pCacheScene) {
        if (pRequTmpMngr != null)
            pRequTmpMngr.removeTempFiles(); // nach jeder HTTP-Anfrage zu lschende Temporrdateien

        if (!pCacheScene && pSessionTmpMngr != null)
            pSessionTmpMngr.removeTempFiles(); // nach jeder Session zu lschende Temporrdateien
    }

    private void logGetCapabilitiesInfo(String pTmpName, HttpServletRequest pRequest) {
        try {
            PrintWriter lDat = new PrintWriter(new FileWriter(mWorkingDirectory + "/" + pTmpName + ".log"));

            lDat.println("REMOTE HOST: " + pRequest.getRemoteHost());
            lDat.println("REMOTE ADDRESS: " + pRequest.getRemoteAddr());
            //lDat.println("REMOTE USER: " + pRequest.getRemoteUser());
            lDat.println("QUERY STRING: " + pRequest.getQueryString());
            lDat.println("SESSION-ID: " + pRequest.getRequestedSessionId());

            lDat.close();
        } catch (IOException e) {
            throw new T3dException(e.getMessage());
        }
    }

    private void logGetViewInfo(String pTmpName, VgElevationGrid pTerrain, T3dTimeList pTimeProt,
            HttpServletRequest pRequest, String pOutputInfo) {
        try {
            PrintWriter lDat = new PrintWriter(new FileWriter(mWorkingDirectory + "/" + pTmpName + ".log"));

            // Berarbeitungszeit und weitere Infos protokollieren:
            lDat.println("REMOTE HOST: " + pRequest.getRemoteHost());
            lDat.println("REMOTE ADDRESS: " + pRequest.getRemoteAddr());
            //lDat.println("REMOTE USER: " + pRequest.getRemoteUser());
            lDat.println("QUERY STRING: " + pRequest.getQueryString());
            lDat.println("SESSION-ID: " + pRequest.getRequestedSessionId());
            lDat.println("BBOX: " + pTerrain.getGeometry().envelope());
            lDat.println("BBOX-SIZE: " + pTerrain.getGeometry().envelope().areaXY());
            lDat.println("CELLSIZE: " + ((GmSimple2dGridGeometry) pTerrain.getGeometry()).getDeltaX()); // quidist.
            lDat.println("OUTPUT FORMAT: " + pOutputInfo);
            lDat.println("PROCESSING_TIMES [msec]: ");
            String[] lTimeProtStr = pTimeProt.protocol();
            for (int i = 0; i < lTimeProtStr.length; i++)
                lDat.println(lTimeProtStr[i]);

            lDat.close();
        } catch (IOException e) {
            throw new T3dException(e.getMessage());
        }
    }

    private void logErrorInfo(String pTmpName, T3dTimeList pTimeProt, HttpServletRequest pRequest, Throwable pExc) {
        try {
            PrintWriter lDat = new PrintWriter(new FileWriter(mWorkingDirectory + "/" + pTmpName + ".log"));

            lDat.println("REMOTE HOST: " + pRequest.getRemoteHost());
            lDat.println("REMOTE ADDRESS: " + pRequest.getRemoteAddr());
            //lDat.println("REMOTE USER: " + pRequest.getRemoteUser());
            lDat.println("QUERY STRING: " + pRequest.getQueryString());
            lDat.println("SESSION-ID: " + pRequest.getRequestedSessionId());
            lDat.println("PROCESSING_TIMES [msec]: ");
            String[] lTimeProtStr = pTimeProt.protocol();
            for (int i = 0; i < lTimeProtStr.length; i++)
                lDat.println(lTimeProtStr[i]);
            lDat.println("ERROR: " + pExc.getMessage());
            lDat.println("STACK TRACE: ");
            pExc.printStackTrace(lDat);

            lDat.close();
        } catch (IOException e) {
            throw new T3dException(e.getMessage());
        }
    }

    private void monitorProtocol(HttpServletResponse pResponse) {
        HttpStandardResponse response = new HttpStandardResponse();
        String res = "";

        File dir = new File(mWorkingDirectory);
        String[] children = dir.list();
        if (children == null) // dir ex. nicht oder ist kein Verzeichnis
            res = "Can not access monitor information...";
        else {
            int ctGif = 0, ctPov = 0, ctBat = 0, ctJpg = 0, ctPng = 0, ctLog = 0;
            for (int i = 0; i < children.length; i++) {
                String filename = children[i];
                if (filename.endsWith(".gif"))
                    ctGif++;
                if (filename.endsWith(".jpg"))
                    ctJpg++;
                if (filename.endsWith(".pov"))
                    ctPov++;
                if (filename.endsWith(".bat"))
                    ctBat++;
                if (filename.endsWith(".png"))
                    ctPng++;
                if (filename.endsWith(".log"))
                    ctLog++;
            }
            res = res + "Held DEM files:           " + ctGif + "\n";
            res = res + "Held Drape files:         " + ctJpg + "\n";
            res = res + "Outstanding batches:      " + ctBat + " (" + ctPov + " scene descriptions)\n";
            res = res + "Held result image files:  " + ctPng + "\n\n";
            res = res + "Accurate executions:      " + ctLog + "\n";
        }

        res = res + "\n";
        res = res + "Time-slice assigner:      ";
        if (!mUseTimeSlices)
            res = res + "not present" + "\n";
        else {
            res = res + "present" + "\n";
            res = res + "Slice interval:           " + mTimeSliceAssigner.getSliceInterval() + " msec\n";
            res = res + "Max. wait-time:           " + mTimeSliceAssigner.maxWaitTime() + " intervals (";
            res = res + (mTimeSliceAssigner.getSliceInterval() * mTimeSliceAssigner.maxWaitTime()) + " msec)\n";
        }

        res = res + "\n";
        res = res + "Session lifetime:         " + mSessionMaxInactiveInterval + " sec\n";

        res = res + "\n";
        res = res + "Max. renderer instances:  " + mMaxRendererInstances + "\n";

        res = res + "\n";
        res = res + "Web connection proxies:   ";
        if (!mWebConnectProxySet)
            res = res + "not set" + "\n";
        else {
            res = res + "set" + "\n";
            res = res + "Proxy host:               " + mWebConnectProxyHost + "\n";
            res = res + "Proxy port:               " + mWebConnectProxyPort + "\n";
            res = res + "Non-proxy hosts:          " + mWebConnectNonProxyHosts + "\n";
        }

        response.sendMessage(res, pResponse);
    }

}