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

Removed bugs that existed in the caching mechanism (issue264, issue268). gnv-artifacts/trunk@1067 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Thu, 06 May 2010 08:32:56 +0000
parents c9996913ff4b
children 50a5ce7a47b7
line wrap: on
line source
package de.intevation.gnv.state.profile.horizontal;

import com.vividsolutions.jts.geom.Coordinate;

import de.intevation.artifactdatabase.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.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.utils.DistanceCalculator;
import de.intevation.gnv.utils.StringUtils;
import de.intevation.gnv.utils.WKTUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

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
{

    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;
    }

	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(uuid);
        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();

                for (int j = 0; j < columns; ++j) {
                    String columnName = rd.getColumnName(j);
                    if (!StringUtils.contains(COLUMN_BLACKLIST, 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