view gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/horizontal/HorizontalProfileMeshCrossOutputState.java @ 1117:dec4257ad570

Changed imports to fit new positions of XMLUtils and Config gnv-artifacts/trunk@1478 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 15 Mar 2011 16:13:39 +0000
parents f953c9a559d8
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.horizontal;

import au.com.bytecode.opencsv.CSVWriter;

import com.vividsolutions.jts.geom.Coordinate;

import de.intevation.artifacts.common.utils.Config;

import de.intevation.artifacts.CallContext;

import de.intevation.gnv.artifacts.cache.CacheFactory;

import de.intevation.gnv.artifacts.context.GNVArtifactContext;

import de.intevation.gnv.chart.Chart;
import de.intevation.gnv.chart.ChartLabels;
import de.intevation.gnv.chart.HorizontalCrossProfileChart;

import de.intevation.gnv.exports.DefaultProfile;
import de.intevation.gnv.exports.Export;
import de.intevation.gnv.exports.StringArrayKey;

import de.intevation.gnv.geobackend.base.DefaultResult;
import de.intevation.gnv.geobackend.base.DefaultResultDescriptor;
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.math.Interpolation2D;
import de.intevation.gnv.math.LinearMetrics;
import de.intevation.gnv.math.Point2d;

import de.intevation.gnv.state.InputData;
import de.intevation.gnv.state.describedata.KeyValueDescibeData;
import de.intevation.gnv.state.exception.StateException;

import de.intevation.gnv.utils.DistanceCalculator;
import de.intevation.gnv.utils.StringUtils;
import de.intevation.gnv.utils.WKTUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.log4j.Logger;

import org.jfree.chart.ChartTheme;

import org.w3c.dom.Node;

/**
 * This <code>OutputState</code> is used for 'Horizontalschnitt' products.
 *
 * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a>
 * @author <a href="mailto:iweinzierl@intevation.de">Ingo Weinzierl</a>
 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
 */
public class HorizontalProfileMeshCrossOutputState
extends      HorizontalProfileMeshOutputState
{

    /**
     * Constant field which defines the source format of a given datetime.
     */
    public static final String SRC_FORMAT  = "yyyy.MM.dd HH:mm:ss";

    /**
     * Constant field which defines the target format of a given datetime.
     */
    public static final String DEST_FORMAT = "yyyy-MM-dd HH:mm";

    /**
     * Source format.
     */
    public static DateFormat srcFormat  = new SimpleDateFormat(SRC_FORMAT);

    /**
     * Target format.
     */
    public static DateFormat destFormat = new SimpleDateFormat(DEST_FORMAT);


    /**
     * This class represents an exporter used for exporting 'Horizontale
     * Schnittprofile' as odv.
     *
     * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
     */
    public class HorizontalProfileMeshCrossODVExporter
    implements   Export
    {
        private ResultDescriptor rd;

        private Collection parameters;

        private int dateIdx;
        private int paramIdx;
        private int depthIdx;
        private int valueIdx;
        private int shpIdx;

        public HorizontalProfileMeshCrossODVExporter(Collection parameters) {
            this.parameters = parameters;
        }

        public void create(
            Profile      profile,
            OutputStream outputStream,
            Collection   result)
        throws
            IOException,
            UnsupportedEncodingException,
            StateException
        {
            CSVWriter writer = new CSVWriter(
                new OutputStreamWriter(outputStream, profile.getEncoding()),
                profile.getSeparator(),
                profile.getQuoteCharacter(),
                profile.getEscapeCharacter());

            writeData(profile, writer, result);

            writer.close();
        }

        protected void writeData(
            Profile            profile,
            CSVWriter          writer,
            Collection<Result> results)
        {
            log.debug("Put " + results.size() + " elements into odv export.");

            // just write header into odv export
            String[] header = profile.getHeader();
            ArrayList<String> headerList = new ArrayList<String>();
            for (int i= 0; i < header.length; i++){
                headerList.add(header[i]);
            }

            ArrayList<String> paramids = new ArrayList<String>();

            Map<StringArrayKey, Map<String,String>> aggregatedRows =
                new HashMap<StringArrayKey, Map<String,String>>();

            for (Result res: results) {
                if (rd == null) {
                    rd       = res.getResultDescriptor();
                    paramIdx = rd.getColumnIndex("GROUP1");
                    depthIdx = rd.getColumnIndex("GROUP2");
                    dateIdx  = rd.getColumnIndex("GROUP3");
                    shpIdx   = rd.getColumnIndex("SHAPE");
                    valueIdx = rd.getColumnIndex("YORDINATE");
                }

                String[] row          = generateRow(res);
                StringArrayKey key    = new StringArrayKey(row);
                String parameterValue = res.getString(valueIdx);
                String parameterID    = res.getString(paramIdx);

                if (!paramids.contains(parameterID)) {
                    paramids.add(parameterID);
                    headerList.add(findParamTitle(parameters, parameterID));
                    headerList.add("QF");
                }

                Map<String,String> aggregatedRow = aggregatedRows.get(key);
                if (aggregatedRow != null) {
                    aggregatedRow.put(parameterID, parameterValue);
                }
                else{
                    Map<String,String> params = new HashMap<String, String>();
                    params.put(parameterID, parameterValue);
                    aggregatedRows.put(key, params);
                }
            }

            if (header != null){
                writer.writeNext(headerList.toArray(header));
            }

            Iterator<StringArrayKey> rows = aggregatedRows.keySet().iterator();
            int idx = 1;
            while (rows.hasNext()){
                StringArrayKey row = rows.next();
                Map<String,String> params = aggregatedRows.get(row);

                ArrayList<String> rowList = new ArrayList<String>();
                String[] rowArray = row.getValue();
                for (int i= 0; i < rowArray.length; i++){
                    String value = rowArray[i];
                    if (value != null && value.equals("GNV_STATION")) {
                        value += "_" + idx++;
                    }
                    rowList.add(value);
                }
                for (int i = 0; i < paramids.size();i++){
                    String key = paramids.get(i);
                    String value = params.get(key);
                    if (value == null){
                        value = "";
                    }
                    rowList.add(value);
                    rowList.add("1");
                }
                log.debug("Write new line into odv export.");
                writer.writeNext(rowList.toArray(rowArray));
            }
        }

        protected String[] generateRow(Result res) {
            Date   tmpDate = null;
            String dateTmp = res.getString(dateIdx);
            try {
                tmpDate = srcFormat.parse(dateTmp);
            }
            catch (ParseException pe) {
                log.warn(pe,pe);
            }

            String shapeTmp = res.getString(shpIdx);
            Coordinate  p   = WKTUtils.toCoordinate(shapeTmp);

            String cruise   = "GNV_EXPORT";
            String station  = "GNV_STATION";
            String type     = "*";
            String date     = tmpDate != null
                ? destFormat.format(tmpDate)
                : dateTmp;
            String lon      = "" + p.x;
            String lat      = "" + p.y;
            String botDepth = "0";
            String depth    = res.getString(depthIdx);

            return new String[] {
                cruise, station, type, date, lon, lat, botDepth, depth
            };
        }

        /**
         * This method is used to search specific value coresponding to its key
         * <code>id</code> and return its description.
         *
         * @param values Collection of parameters.
         * @param id Key used to find the value.
         *
         * @return Description of searched value.
         */
        protected String findParamTitle(Collection values, String id) {
            if (values != null){
                Iterator it = values.iterator();
                while (it.hasNext()) {
                    KeyValueDescibeData data = (KeyValueDescibeData) it.next();

                    if (id.equals(data.getKey()))
                        return data.getValue();
                }
            }
            return "";
        }
    } // HorizontalProfileMeshCrossODVExporter


    public static final boolean USE_INDEX_BUFFER =
        Boolean.getBoolean("gnv.horizontal.profile.mesh.cross.index.buffer");

    private static final long serialVersionUID = 2205958041745637263L;

    /**
     * the logger, used to log exceptions and additonaly information
     */
    private static Logger log = Logger.getLogger(
        HorizontalProfileMeshCrossOutputState.class);

    private String ijkQueryID = null;

    /**
     * Constructor
     */
    public HorizontalProfileMeshCrossOutputState() {
        super();
    }


    @Override
    public void setup(Node configuration) {
        super.setup(configuration);
        this.ijkQueryID = Config.getStringXPath(configuration,"queryID-ijk");

    }


    /**
     * This method creates a chart and returns it.
     *
     * @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 <code>HorizontalCrossProfileChart</code>.
     */
    @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 horizontalprofilemeshcross chart from cache.");
            chart = (Chart) getChartFromCache(uuid, callContext);
        }

        if (chart != null)
            return chart;

        log.info("Chart not in cache yet.");
        chart = new HorizontalCrossProfileChart(
            chartLables,
            theme,
            parameters,
            measurements,
            dates,
            (Collection)result,
            null,
            locale,
            linesVisible,
            shapesVisible
        );
        chart.generateChart();

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

        return chart;
    }


    @Override
    protected void createODV(
        OutputStream outputStream, String uuid, CallContext callContext)
    throws IOException, StateException {

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

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

        Collection<Result> result = (Collection<Result>) getChartResult(
            uuid, callContext);

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

        Export export = new HorizontalProfileMeshCrossODVExporter(
            getParameters(uuid));

        export.create(ODV_PROFILE, outputStream, result);
    }


	private static int numSamples(CallContext callContext) {
		GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
		Integer samples = (Integer)context.get(
			GNVArtifactContext.HORIZONTAL_CROSS_SECTION_PROFILE_SAMPLES_KEY);
		return samples != null
			? samples.intValue()
			: GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_PROFILE_SAMPLES;
	}

    @Override
    protected Object getChartResult(String uuid, CallContext callContext) {
        log.debug("HorizontalProfileMeshCrossOutputState.getChartResult");

        String key = getHash();
        if (CacheFactory.getInstance().isInitialized()) {
            log.debug("Using cache - key: " + key);
            net.sf.ehcache.Element value = CacheFactory.getInstance().getCache().get(key);

            if (value != null) {
                log.debug("Found element in cache.");
                return (Collection<Result>) (value.getObjectValue());
            }
        }

        log.debug("Not using cache or element not found.");
        Collection<Result> result = null;

        InputData meshLine = inputData.get("mesh_linestring");
        InputData meshId   = inputData.get("meshid");

        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.getValue());

        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 queryExecutor = QueryExecutorFactory
                .getInstance()
                .getQueryExecutor();

            result = process(
                Arrays.asList(coords),
                numSamples(callContext),
                queryExecutor.executeQuery(
                queryID,
                addedFilterValues));
        }
        catch (QueryException e) {
            log.error(e,e);
        }

        if (CacheFactory.getInstance().isInitialized()) {
            CacheFactory.getInstance().getCache().put(new net.sf.ehcache.Element(key, result));
        }

        return result;
    }


    /**
     * Prepares the input data for chart creation.
     *
     * @param path The coordinates describing the path the data is processed
     * for.
     * @param numSamples Number of samples.
     * @param input The input data.
     * @return finalized data ready for chart creation.
     */
    public static Collection<Result> process(
        List<Coordinate>   path,
		int                numSamples,
        Collection<Result> input
    ) {
		boolean debug = log.isDebugEnabled();

		if (debug) {
			log.debug("--- number of points before processing: " + input.size());
			log.debug("    number samples: " + numSamples);
		}

        ArrayList<Result> output = new ArrayList<Result>();

        Result last = null;

        int [] diffColums = null;

        SectionHandler sectionHandler = null;

        for (Result result: input) {

            if (sectionHandler == null) {

                ResultDescriptor rd = result.getResultDescriptor();
                diffColums = rd.getColumnIndices(DIFF_COLUMS);
                int columns = rd.getColumnCount();

                DefaultResultDescriptor resultDescriptor =
                    new DefaultResultDescriptor();

                log.debug("------------------------------------------------------");
                for (int j = 0; j < columns; ++j) {
                    String columnName = rd.getColumnName(j);
                    if (!StringUtils.contains(COLUMN_BLACKLIST, columnName)) {
                        log.debug("!!! COLUMN NAME: " + columnName);
                        resultDescriptor.addColumn(
                            columnName,
                            rd.getColumnClassName(j));
                    }
                }

                sectionHandler = new SectionHandler(
                    path,
					numSamples,
                    output,
                    resultDescriptor);

                sectionHandler.setPrototyp(result);
            }

            if (last != null && WKTUtils.different(last, result, diffColums)) {
                sectionHandler.finish();
                sectionHandler.setPrototyp(result);
            }

            sectionHandler.handle(result);

            last = result;
        }

        if (sectionHandler != null) {
            sectionHandler.finish();
        }

		if (debug) {
			log.debug("--- number of points after processing: " + output.size());
		}

        return output;
    }


    private static final String [] DIFF_COLUMS = {
        "GROUP1",
        "GROUP2",
        "GROUP3"
    };

    private static final String [] COLUMN_BLACKLIST = {
        "MEDIAN.MESHPOINT.JPOSITION",
        "MEDIAN.MESHPOINT.IPOSITION"
    };

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

    public static final class SectionHandler
    implements                Interpolation2D.Consumer
    {
        private ArrayList<Point2d> points;
        private List<Coordinate>   path;
        private Collection<Result> output;
        private Result             prototyp;
        private ResultDescriptor   descriptor;
        private boolean            lastWasSuccess;
		private int                numSamples;

        public SectionHandler() {
        }

        public SectionHandler(
            List<Coordinate>   path,
			int                numSamples,
            Collection<Result> output,
            ResultDescriptor   descriptor
        ) {
            this.path       = path;
			this.numSamples = numSamples;
            this.output     = output;
            this.descriptor = descriptor;
            points          = new ArrayList<Point2d>();
            lastWasSuccess  = true;
        }

        public void finish() {
            if (!points.isEmpty()) {
                double distance = WKTUtils.toKM(
                    DistanceCalculator.calculateDistance(path));

                if (distance > EPSILON) {

                    Interpolation2D.interpolate(
                        path,
                        points,
                        0d,
                        distance,
                        numSamples,
                        LinearMetrics.INSTANCE,
                        this);
                }

                points.clear();
            }
            lastWasSuccess = true;
        }

        public void setPrototyp(Result prototyp) {
            this.prototyp = prototyp;
        }

        public void handle(Result result) {
            Coordinate coordinate =
                WKTUtils.toCoordinate(result.getString("SHAPE"));
            double value = result.getDouble("YORDINATE");
            int iPos     = result.getInteger("IPOSITION");
            int jPos     = result.getInteger("JPOSITION");
            Point2d p = new Point2d(
                coordinate.x,
                coordinate.y,
                value,
                iPos, jPos);
            points.add(p);
        }

        public void interpolated(Coordinate coordinate, boolean success) {

            if (!success && !lastWasSuccess) {
                // only insert null if last was valid.
                // This prevents flooding the result set with nulls
                // if interpolating over a large gap.
                return;
            }

            DefaultResult result = new DefaultResult(descriptor);
            ResultDescriptor pd = prototyp.getResultDescriptor();

            int pcolums = pd.getColumnCount();
            for (int i = 0, j = 0; i < pcolums; ++i) {
                String colname = pd.getColumnName(i);
                if (StringUtils.contains(COLUMN_BLACKLIST, colname)) {
                    continue;
                }
                if (colname.equals("SHAPE")) {
                    result.addColumnValue(j, WKTUtils.toWKT(coordinate));
                }
                else if (colname.equals("YORDINATE")) {
                    result.addColumnValue(j, success
                        ? Double.valueOf(coordinate.z)
                        : null);
                }
                else {
                    result.addColumnValue(j, prototyp.getObject(i));
                }
                ++j;
            }
            output.add(result);
            lastWasSuccess = success;
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org