view gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/verticalcrosssection/VerticalCrossSectionOutputState.java @ 1115:f953c9a559d8

Added license file and license headers. gnv-artifacts/trunk@1260 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 02 Nov 2010 17:46:55 +0000
parents 51f3edc9d743
children
line wrap: on
line source
/*
 * Copyright (c) 2010 by Intevation GmbH
 *
 * This program is free software under the LGPL (>=v2.1)
 * Read the file LGPL.txt coming with the software for details
 * or visit http://www.gnu.org/licenses/ if it does not exist.
 */

package de.intevation.gnv.state.profile.verticalcrosssection;

import au.com.bytecode.opencsv.CSVWriter;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.apache.log4j.Logger;
import org.jfree.chart.ChartTheme;

import com.vividsolutions.jts.geom.Coordinate;

import de.intevation.artifacts.CallContext;
import de.intevation.gnv.artifacts.cache.CacheFactory;
import de.intevation.gnv.artifacts.context.GNVArtifactContext;
import de.intevation.gnv.artifacts.ressource.RessourceFactory;
import de.intevation.gnv.chart.Chart;
import de.intevation.gnv.chart.ChartLabels;
import de.intevation.gnv.chart.VerticalCrossSectionChart;
import de.intevation.gnv.exports.DefaultProfile;
import de.intevation.gnv.exports.Export;
import de.intevation.gnv.exports.VerticalCrossODVExport;
import de.intevation.gnv.geobackend.base.Result;
import de.intevation.gnv.geobackend.base.ResultDescriptor;
import de.intevation.gnv.geobackend.base.query.QueryExecutor;
import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory;
import de.intevation.gnv.geobackend.base.query.exception.QueryException;
import de.intevation.gnv.geobackend.sde.datasources.RasterObject;
import de.intevation.gnv.jfreechart.PolygonDataset;
import de.intevation.gnv.jfreechart.PolygonSeries;
import de.intevation.gnv.math.AttributedXYColumns;
import de.intevation.gnv.math.HeightValue;
import de.intevation.gnv.math.IJKey;
import de.intevation.gnv.math.Interpolation3D;
import de.intevation.gnv.math.LinearMetrics;
import de.intevation.gnv.math.QueriedXYDepth;
import de.intevation.gnv.math.XYColumn;
import de.intevation.gnv.raster.Filter;
import de.intevation.gnv.raster.IsoAttributeGenerator;
import de.intevation.gnv.raster.IsoPolygonSeriesProducer;
import de.intevation.gnv.raster.Palette;
import de.intevation.gnv.raster.PaletteManager;
import de.intevation.gnv.raster.PolygonDatasetProducer;
import de.intevation.gnv.raster.Raster;
import de.intevation.gnv.raster.Vectorizer;
import de.intevation.gnv.state.InputData;
import de.intevation.gnv.state.describedata.KeyValueDescibeData;
import de.intevation.gnv.state.exception.StateException;
import de.intevation.gnv.state.timeseries.TimeSeriesOutputState;
import de.intevation.gnv.statistics.Statistics;
import de.intevation.gnv.statistics.VerticalCrossSectionStatistics;
import de.intevation.gnv.utils.DistanceCalculator;
import de.intevation.gnv.utils.StringUtils;
import de.intevation.gnv.utils.WKTUtils;

/**
 * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a>
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
 */
public class VerticalCrossSectionOutputState extends TimeSeriesOutputState {

    public static final String CHART_TYPE = "verticalcrosssection";

    public static final Integer GROUND_FILL_INDEX = Integer.valueOf(-2);

    public static final boolean USE_INDEX_BUFFER =
        Boolean.getBoolean("gnv.vertical.cross.section.index.buffer");

    public static final String[] ATTRIBUTE_LIST = {
        "SHAPE",
        "Z",
        "YORDINATE",
        "IPOSITION",
        "JPOSITION",
        "KPOSITION"
    };

    private static Logger log = Logger.getLogger(
        VerticalCrossSectionOutputState.class);

    private String ijkQueryID = "horizontalprofile_meshpoint_cross_ij";

    private String rangeLabel;

    /**
     * The UID of this Class
     */
    private static final long serialVersionUID = 3233620652465061860L;

    /**
     * Constructor
     */
    public VerticalCrossSectionOutputState() {
        super();
        super.domainLable = "chart.verticalcrosssection.title.xaxis";
        this.rangeLabel   = "chart.verticalcrosssection.title.yaxis";
    }

    @Override
    public void initialize(String uuid, CallContext callContext)
    throws StateException {
        super.initialize(uuid, callContext);

        getChartResult(uuid, callContext);
    }

    @Override
    protected ChartLabels createChartLabels(Locale locale, String uuid) {
        RessourceFactory factory = RessourceFactory.getInstance();
        InputData input          = inputData.get(parameterValuesName);
        String parameterName     = input.getDescription()[0];

        if (parameterName == null)
            parameterName = "parameterid";

        return new ChartLabels(
            createChartTitle(locale, uuid),
            createChartSubtitle(locale, uuid),
            factory.getRessource(locale, domainLable, domainLable),
            factory.getRessource(locale, rangeLabel, rangeLabel),
            parameterName
        );
    }


    @Override
    protected String createChartSubtitle(Locale locale, String uuid) {
        InputData data = inputData.get(dateValueName);
        String date    = data.getDescription()[0];

        if (date == null)
            date = "dateid";

        RessourceFactory factory = RessourceFactory.getInstance();
        String chartType         = factory.getRessource(
            locale, CHART_TYPE, CHART_TYPE);

        return chartType + ": " + date;
    }


    @Override
    protected Object getChartResult(String uuid, CallContext callContext) {
        log.debug("VerticalCrossSectionOutputState.getChartResult");
        String key = getHash();

        CacheFactory factory = CacheFactory.getInstance();
        if (factory.isInitialized()) {
            log.info("Using a cachce - key: " + key);
            Cache cache = factory.getCache();

            Element element = cache.get(key);
            if (element != null)
                return element.getObjectValue();

            log.debug("No results in cache yet.");
            Object obj = getData(uuid, callContext);
            cache.put(new Element(key, obj));

            return obj;
        }
        else {
            log.info("Not using a cache.");
            return getData(uuid, callContext);
        }
    }


    /**
     * Retrieves the data used to create a VerticalCrossProfileChart.
     *
     * @param uuid The UUID of the current artifact.
     * @param callContext The CallContext object.
     * @return the data used to create a VerticalCrossProfileChart.
     */
    protected Object getData(String uuid, CallContext callContext) {
        Collection<Result> result = null;
        InputData meshId          = inputData.get("meshid");
        String    meshLine        = getLineString();

        if (meshLine == null) {
            log.error("mesh_linestring is not defined");
            throw new IllegalStateException("missing mesh_linestring");
        }

        if (meshId == null) {
            log.error("meshid is not defined");
            throw new IllegalStateException("missing meshid");
        }

        Coordinate [] coords = WKTUtils.toCoordinates(
            meshLine);

        if (coords == null) {
            throw new IllegalStateException("cannot read coordinates");
        }

        try {
            String additionWhere = USE_INDEX_BUFFER
                ? WKTUtils.worldCoordinatesToIndex(
                    coords,
                    result,
                    meshId.getValue(),
                    ijkQueryID)
                : WKTUtils.TRUE_EXPRESSION;

            String[] addedFilterValues = StringUtils.append(
                generateFilterValuesFromInputData(),
                additionWhere);

            QueryExecutor exec = QueryExecutorFactory
                .getInstance()
                .getQueryExecutor();

            result = exec.executeQuery(queryID, addedFilterValues);
        }
        catch (QueryException qe) {
            log.error(qe, qe);
        }

        Object obj = process(
            Arrays.asList(coords),
            preProcess(result),
            callContext);

        return obj;
    }


    protected String getLineString() {
        InputData meshLine = inputData.get("mesh_linestring");

        return meshLine != null ? meshLine.getValue() : null;
    }


    @Override
    protected String getSelectedInputDataName(String uuid, String id) {
        Collection values = getCollection(id, uuid);

        if (values != null) {
            Iterator it = values.iterator();

            while (it.hasNext()) {
                KeyValueDescibeData data = (KeyValueDescibeData) it.next();

                if (data.isSelected()) {
                    return data.getValue();
                }
            }
        }
        return null;
    }

    private static int getGroundInterpolation(CallContext callContext) {
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();

        String interpolation = (String)context.get(
            GNVArtifactContext.VERTICAL_CROSS_SECTION_GROUND_INTERPOLATION_KEY);

        return RasterObject.getInterpolationType(interpolation);
    }

    private static Dimension getRasterSize(CallContext callContext) {
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
        Dimension size = (Dimension)context.get(
            GNVArtifactContext.VERTICAL_CROSS_SECTION_SAMPLES_KEY);
        return size != null
            ? size
            : GNVArtifactContext.DEFAULT_VERTICAL_CROSS_SECTION_SAMPLES;
    }

    private static List<Filter.Factory> getFilterFactories(
        CallContext callContext
    ) {
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
        List<Filter.Factory> factories = (List<Filter.Factory>)context.get(
            GNVArtifactContext.VERTICAL_CROSS_SECTION_FILTER_FACTORIES_KEY);
        return factories != null
            ? factories
            : new ArrayList<Filter.Factory>();
    }

    private static Map<Integer, PaletteManager> getPalettes(
        CallContext callContext
    ) {
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
        Map<Integer, PaletteManager> palettes =
            (Map<Integer, PaletteManager>)context.get(
                GNVArtifactContext.PALETTES_KEY);
        return palettes != null
            ? palettes
            : new HashMap<Integer, PaletteManager>();
    }

    private static Paint getGroundFill(CallContext callContext) {
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
        Paint fill = (Paint)context.get(
            GNVArtifactContext.VERTICAL_CROSS_SECTION_GROUND_FILL_KEY);
        return fill != null
            ? fill
            : GNVArtifactContext.DEFAULT_VERTICAL_CROSS_SECTION_GROUND_FILL;
    }

    public static final double EPSILON = 1e-5d;


    /**
     * Finalize the data used for chart generation. Isolines are added, colors
     * are assigned to polygons and the seabed is added.
     *
     * @param path The path which have been inserted while parameterization.
     * @param columns The data used to be displayed in a 2D chart.
     * @param callContext The CallContext object.
     * @return the finalized data ready for chart generation.
     */
    protected Object process(
        List<Coordinate>    path,
        AttributedXYColumns columns,
        CallContext         callContext
    ) {
        Integer parameterId =
            (Integer)columns.getAttribute("GROUP1"); // XXX: hardcoded

        if (parameterId == null) {
            log.error("missing parameter id");
            return null;
        }

        Map<Integer, PaletteManager> paletteManagers =
            getPalettes(callContext);

        PaletteManager paletteManager = paletteManagers.get(parameterId);

        if (paletteManager == null) {
            log.error("no palette found for parameter id " + parameterId);
            return null;
        }

        boolean debug = log.isDebugEnabled();

        if (debug) {
            log.debug("using palette '" + paletteManager.getName() + "'");
        }

        Dimension            rasterSize      = getRasterSize(callContext);
        List<Filter.Factory> filterFactories = getFilterFactories(callContext);
        Interpolation3D      interpolation   = new Interpolation3D(rasterSize);

        double distance = DistanceCalculator.calculateDistance(path);

        if (distance < EPSILON) {
            log.warn("distance too short for interpolation");
            return null;
        }

        boolean success = interpolation.interpolate(
            path,
            columns.getXYColumns(),
            0d,
            distance,
            LinearMetrics.INSTANCE,
            new QueriedXYDepth(
                getGroundInterpolation(callContext)));

        if (!success) {
            log.warn("interpolation failed");
            return null;
        }

        // Do the post processing
        Raster raster = new Raster(
            interpolation.getRaster(),
            rasterSize.width);

        for (Filter.Factory factory: filterFactories) {
            raster = factory.create().filter(raster);
        }

        if (debug) {
            log.debug("to indexed raster");
        }

        // scan for regions with base palette
        Palette basePalette = paletteManager.getBase();

        int [] intRaster = raster.toIndexed(basePalette);

        // produce JFreeChart compatible polygons

        if (debug) {
            log.debug("vectorize indexed raster");
        }

        double maxDepth = interpolation.getMaxDepth();

        PolygonDatasetProducer pdsp = new PolygonDatasetProducer(
            0, 0,
            distance, maxDepth);

        int numRegions = new Vectorizer(intRaster, rasterSize.width)
            .process(pdsp);

        PolygonDataset pds = pdsp.getPolygonDataset();

        // Count number of colors before generating seabed
        // because its used to determine the number of iso lines.
        int numColors = pds.getSeriesCount();

        if (debug) {
            log.debug("number of regions: " + numRegions);
            log.debug("number of colors:  " + numColors);
        }

        // generate seabed polygon

        PolygonSeries seabed = OutputHelper.createSeabedPolygon(
            interpolation,
            GROUND_FILL_INDEX);

        if (seabed != null) {
            pds.addSeries(seabed);
        }

        // generate iso lines

        int numIso;

             if (numColors <  5) { numIso = 5; }
        else if (numColors < 10) { numIso = 2; }
        else                     { numIso = 0; }

        Palette isoPalette;

        if (numIso == 0) { // same palette
            isoPalette = basePalette;
            /* intRaster = intRaster; */
        }
        else {
            isoPalette = paletteManager.getLevel(numIso);
            intRaster  = raster.toIndexed(isoPalette);
        }

        IsoPolygonSeriesProducer ipsp = new IsoPolygonSeriesProducer(
            0, 0,
            distance, maxDepth);

        numRegions = new Vectorizer(false, intRaster, rasterSize.width)
            .process(ipsp);

        IsoAttributeGenerator iag = new IsoAttributeGenerator(isoPalette);
        Collection<PolygonSeries> ps = ipsp.getSeries(iag);
        ipsp.clear();

        if (debug) {
            log.debug("num of iso regions: " + numRegions);
            log.debug("num of iso series:  " + ps.size());
        }

        pds.addAllSeries(ps);

        columns.setInterpolation(interpolation);
        columns.setPolygonDataset(pds);

        return columns;
    }


    /**
     * Pre-process the data returned by database query. The resulting data is
     * not ready for chart creation!
     *
     * @param results Data returned by database.
     * @return Pre-processed data which is not ready for chart creation yet.
     */
    protected AttributedXYColumns preProcess(Collection results) {

        AttributedXYColumns attColumns = new AttributedXYColumns();
        Map<IJKey, XYColumn> map       = new HashMap<IJKey, XYColumn>(1013);
        Iterator   iter                = results.iterator();

        int sIdx = -1;
        int iIdx = -1;
        int jIdx = -1;
        int kIdx = -1;
        int vIdx = -1;
        int zIdx = -1;

        while (iter.hasNext()) {
            Result result = (Result) iter.next();

            if (sIdx == -1) {
                ResultDescriptor rd = result.getResultDescriptor();
                int columnCount     = rd.getColumnCount();

                sIdx = rd.getColumnIndex("SHAPE");
                iIdx = rd.getColumnIndex("IPOSITION");
                jIdx = rd.getColumnIndex("JPOSITION");
                kIdx = rd.getColumnIndex("KPOSITION");
                vIdx = rd.getColumnIndex("YORDINATE");
                zIdx = rd.getColumnIndex("Z");

                for (int i = 0; i < columnCount; i++) {
                    String colName = rd.getColumnName(i);

                    if (!StringUtils.contains(ATTRIBUTE_LIST, colName)) {
                        attColumns.setAttribute(
                            colName,
                            result.getObject(colName));
                    }
                }
            }

            double v = result.getDouble(vIdx);
            double z = result.getDouble(zIdx);
            int    i = result.getInteger(iIdx);
            int    j = result.getInteger(jIdx);
            int    k = result.getInteger(kIdx);

            IJKey key = new IJKey(i, j);

            XYColumn col = (XYColumn)map.get(key);

            if (col == null) {
                Coordinate coord = WKTUtils.toCoordinate(result.getString(sIdx));
                if (coord == null) coord = new Coordinate();
                col = new XYColumn(coord.x, coord.y, i, j);
                map.put(key, col);
            }

            col.add(new HeightValue(z, v, k));
        }

        ArrayList<XYColumn> cols = new ArrayList<XYColumn>(map.values());
        attColumns.setXYColumns(cols);

        return attColumns;
    }


    /**
     * This <code>getChart</code> method returns a 2D VerticalCrossSectionChart
     * displaying polygon data with isolines and a legend describing the colors
     * used in that chart.
     *
     * @param chartLables Labels used to decorate the chart.
     * @param theme The theme used to adjust the look of the chart.
     * @param parameters A collection with parameters this chart contains.
     * @param measurements A collection with measurement this chart contains.
     * @param dates A collection with dates this chart contains.
     * @param result The data collection used to be displayed in this chart.
     * @param locale The Locale used to determine the language.
     * @param uuid The uuid of the current artifact.
     * @param linesVisible A boolean property to determine the visibility of
     * lines connecting two points in a chart (not used in this chart type).
     * @param shapesVisible A boolean property to determine the visiblity of
     * datapoints in this chart (not used in this chart type).
     * @param callContext The CallContext object.
     * @return a 2D chart representing the data as polygons.
     */
    @Override
    protected Chart getChart(
        ChartLabels  chartLables,
        ChartTheme   theme,
        Collection   parameters,
        Collection   measurements,
        Collection   dates,
        Object       result,
        Locale       locale,
        String       uuid,
        boolean      linesVisible,
        boolean      shapesVisible,
        CallContext  callContext
    ) {
        Chart chart = null;

        if (CACHE_CHART) {
            log.info("Try to get verticalcrosssection chart from cache.");
            chart = (Chart) getChartFromCache(uuid, callContext);
        }

        if (chart != null)
            return chart;

        log.info("Chart not in cache yet.");

        if (!(result instanceof AttributedXYColumns)) {
            log.error("result of wrong type");
            return null;
        }

        AttributedXYColumns columns = (AttributedXYColumns)result;

        Integer parameterId =
            (Integer)columns.getAttribute("GROUP1"); // XXX: hardcoded

        if (parameterId == null) {
            log.error("missing parameter id");
            return null;
        }

        Map<Integer, PaletteManager> paletteManagers =
            getPalettes(callContext);

        PaletteManager paletteManager = paletteManagers.get(parameterId);

        if (paletteManager == null) {
            log.error("no palette found for parameter id " + parameterId);
            return null;
        }

        HashMap<Integer, Paint> special = new HashMap<Integer, Paint>();
        special.put(GROUND_FILL_INDEX, getGroundFill(callContext));

        chart = new VerticalCrossSectionChart(
            columns,
            paletteManager.getBase(),
            special,
            locale,
            chartLables);

        chart.generateChart();
        ((VerticalCrossSectionChart)chart).setBackgroundPaint(Color.WHITE);

        if (CACHE_CHART) {
            log.info("Put chart into cache.");
            purifyChart(chart, uuid);
        }

        return chart;
    }


    @Override
    protected Statistics getStatisticsGenerator() {
        return new VerticalCrossSectionStatistics();
    }

    /**
     * Nothing happens here. <b>This method should never be called</b> until
     * there is a wise implementation of a csv representation of the polygon
     * data.
     *
     * @param outputStream The output stream used to write the csv file to.
     * @param chartResult  The data used to be written to csv file.
     * @throws UnsupportedEncodingException if the encoding is not supported.
     * @throws IOException if an error occured while writing to output stream.
     * @throws StateException if an error occured while csv file creation.
     */
    @Override
    protected void createCSV(
        OutputStream       outputStream,
        Collection<Result> chartResult
    )
    throws UnsupportedEncodingException, IOException, StateException
    {
        // TODO: Implement a substitution which makes sense.
    }

    @Override
    protected void createODV(
        OutputStream outputStream,
        String       uuid,
        CallContext  callContext)
    throws IOException, StateException
    {
        // 'Profilschnitte' contain one parameter only
        Collection tmp            = getParameters(uuid);
        KeyValueDescibeData param = (KeyValueDescibeData) tmp.toArray()[0];

        String [] COLUMN_HEADER = {
            "Cruise",
            "Station",
            "Type",
            "yyyy-mm-dd hh:mm",
            "Lon (°E)",
            "Lat (°N)",
            "Bot. Depth [m]",
            "Depth [m]",
            "QF",
            param.getValue()
        };

        Export.Profile ODV_PROFILE = new DefaultProfile(
            COLUMN_HEADER,
            '\t',
            CSVWriter.NO_QUOTE_CHARACTER,
            CSVWriter.NO_ESCAPE_CHARACTER,
            "ODV",
            "ISO-8859-1");

        Object chartResult = getChartResult(uuid, callContext);

        if (chartResult == null) {
            log.error("No data for export found.");
            return;
        }

        List result = new ArrayList(1);
        result.add(chartResult);

        InputData data = inputData.get(dateValueName);
        String    date = data.getDescription()[0];

        Interpolation3D interpol = ((AttributedXYColumns)
            chartResult).getInterpolation();

        Coordinate[] coords = interpol.getCoordinates();
        double[]     depth  = interpol.getDepths();
        double[]     raster = interpol.getRaster();

        Export export = new VerticalCrossODVExport(
            interpol.getCoordinates(),
            interpol.getCellHeight(),
            interpol.getCellWidth(),
            interpol.getRaster(),
            date,
            interpol.getWidth(),
            interpol.getHeight());

        export.create(ODV_PROFILE, outputStream, null);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org