view gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/horizontal/HorizontalProfileMeshCrossOutputState.java @ 418:6eae1efb5fc3

Horizontales Schnittprofil: Added some code to prevent needless null inserts if interpolating over large gaps. gnv-artifacts/trunk@466 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 21 Dec 2009 14:41:18 +0000
parents 04a242c67fe6
children c6a287398379
line wrap: on
line source
/**
 *
 */
package de.intevation.gnv.state.profile.horizontal;

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.w3c.dom.Node;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.LineString;

import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;

import de.intevation.artifactdatabase.Config;

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

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

import de.intevation.gnv.geobackend.base.DefaultResultDescriptor;
import de.intevation.gnv.geobackend.base.ResultDescriptor;
import de.intevation.gnv.geobackend.base.DefaultResult;
import de.intevation.gnv.geobackend.base.Result;

import de.intevation.gnv.math.Point2d;
import de.intevation.gnv.math.Interpolation2D;

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.ResultSet;


import de.intevation.gnv.utils.IndexBuffer;
import de.intevation.gnv.utils.DistanceCalculator;

import de.intevation.gnv.math.LinearFunction;
import de.intevation.gnv.math.LinearMetrics;

import org.apache.commons.math.optimization.fitting.CurveFitter;

import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
import org.apache.commons.math.optimization.OptimizationException;

import org.apache.commons.math.FunctionEvaluationException;

import org.jfree.chart.ChartTheme;

/**
 * @author Tim Englich <tim.englich@intevation.de>
 *
 */
public class HorizontalProfileMeshCrossOutputState extends
                                                  HorizontalProfileOutputState {

    /**
     *
     */
    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();
    }
    
    /**
     * @see de.intevation.gnv.state.timeseries.TimeSeriesOutputState#setup(org.w3c.dom.Node)
     */
    @Override
    public void setup(Node configuration) {
        super.setup(configuration);
        this.ijkQueryID = Config.getStringXPath(configuration,"queryID-ijk");
        
    }


    @Override
    protected Chart getChart(
        ChartLabels  chartLables,
        ChartTheme   theme,
        Collection   parameters,
        Collection   measurements,
        Collection   dates,
        Collection   result,
        Locale       locale,
        String       uuid,
        boolean      linesVisible,
        boolean      shapesVisible
    ) {
        Chart chart = null;
        if (CACHE_CHART) {
            log.info("Try to get horizontalprofilemeshcross chart from cache.");
            chart = (Chart) getChartFromCache(uuid);
        }

        if (chart != null)
            return chart;

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

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

        return chart;
    }

    @Override
    protected Collection<Result> getChartResult(String uuid) {
        log.debug("HorizontalProfileMeshCrossOutputState.getChartResult");
        Collection<Result> result = null;
        if (CacheFactory.getInstance().isInitialized()) {
            String key = uuid + super.getID();
            log.debug("Hash for Queryelements: " + key);
            net.sf.ehcache.Element value = CacheFactory.getInstance().getCache().get(key);
            if (value != null) {
                result = (Collection<Result>) (value.getObjectValue());
            }else{
                
                if (this.inputData.containsKey("mesh_linestring")){
                    
                    try {
                        // 1. IJK Anfragen f�r Stuetzpunkte im Netz ausf�hren.
                        LineString ls = (LineString)new WKTReader().read(this.inputData
                                                                         .get("mesh_linestring")
                                                                         .getValue());
                        Coordinate[] coords = ls.getCoordinates();
                        
                        List<java.awt.Point> points = new ArrayList<java.awt.Point>(coords.length);
                        
                        String meshid = this.inputData.get("meshid").getValue();
                        QueryExecutor queryExecutor = QueryExecutorFactory
                                                        .getInstance()
                                                        .getQueryExecutor();

                        ArrayList missingPoints = new ArrayList();

                        String additionWhere = "FEATUREID=FEATUREID";

                        for (int i = 0; i < coords.length; i++) {

                            String wkt = toWKT(coords[i]);

                            result = queryExecutor.executeQuery(this.ijkQueryID,
                                                               new String[]{meshid,wkt});
                            if (!result.isEmpty()){
                                Result resultValue = result.iterator().next();
                                int iPos = resultValue.getInteger(1);
                                int jPos = resultValue.getInteger(0);
                                log.debug("Found Pos "+iPos+"/"+jPos +" for "+wkt);
                                points.add(i, new java.awt.Point(iPos,jPos));
                            }else{
                                log.debug("No i/j Pos found for "+wkt);
                                missingPoints.add(new Object [] { Integer.valueOf(i), coords[i] });
                                points.add(i, null);
                                // Special Case no i,j found for Coordinate
                            }
                        }

                        if (missingPoints.size() == coords.length) {
                            log.debug("cannot create index buffer");
                        }
                        else { // generate index filter
                            boolean remainsMissingPoints = !missingPoints.isEmpty();

                            if (remainsMissingPoints) {
                                // try to guess the missing (i, j)
                                CurveFitter iFitter = new CurveFitter(new GaussNewtonOptimizer(true));
                                CurveFitter jFitter = new CurveFitter(new GaussNewtonOptimizer(true));

                                for (int i = 0, N = points.size(); i < N; ++i) {
                                    java.awt.Point p = (java.awt.Point)points.get(i);
                                    if (p != null) {
                                        Coordinate coord = coords[i];
                                        iFitter.addObservedPoint(coord.x, p.x);
                                        jFitter.addObservedPoint(coord.y, p.y);
                                    }
                                }
                                try {
                                    // XXX: Assumption: (i, j) are created by componentwise linear function.
                                    // This is surely not correct because (x, y) are in a ellipsoid projection.
                                    // TODO: use ellipsoid functions and fit with Levenberg Marquardt.
                                    double [] iParams = iFitter.fit(
                                        LinearFunction.INSTANCE, new double [] { 1d, 1d });

                                    double [] jParams = jFitter.fit(
                                        LinearFunction.INSTANCE, new double [] { 1d, 1d });

                                    for (int i = missingPoints.size()-1; i >= 0; --i) {
                                        Object [] a = (Object [])missingPoints.get(i);
                                        Coordinate coord = (Coordinate)a[1];
                                        int pi = (int)Math.round(iParams[0]*coord.x + iParams[1]);
                                        int pj = (int)Math.round(jParams[0]*coord.y + jParams[1]);
                                        points.set(
                                            ((Integer)a[0]).intValue(),
                                            new java.awt.Point(pi, pj));
                                    }

                                    remainsMissingPoints = false; // we filled the gaps
                                }
                                catch (FunctionEvaluationException fee) {
                                    log.error(fee);
                                }
                                catch (OptimizationException oe) {
                                    log.error(oe);
                                }
                            }

                            if (!remainsMissingPoints) {
                                // TODO: Make Tablenames and Columns Configurable
                                IndexBuffer ib = new IndexBuffer(
                                    points, 
                                    "MEDIAN.MESHPOINT.IPOSITION", 
                                    "MEDIAN.MESHPOINT.JPOSITION" );
                                additionWhere = ib.toWhereClause();
                                log.debug("Additional Where Clause = "+additionWhere);
                                // 2. Aus diesen Stuetzpunkten den Resultset generieren.
                            }
                        } // if generate index filter
                        
                        String[] filterValues = this.generateFilterValuesFromInputData();
                        String[] addedFilterValues = new String[filterValues.length+1];
                        System.arraycopy(filterValues, 0, addedFilterValues, 0, filterValues.length);
                        addedFilterValues[filterValues.length] = additionWhere;
                        
                        result = process(
                            Arrays.asList(coords),
                            queryExecutor.executeQuery(
                                this.queryID,
                                addedFilterValues));
                        
                    } catch (ParseException e) {
                        log.error(e,e);
                    }catch (QueryException e) {
                        log.error(e,e);
                    }
                }else{
                    // TODO: definieren was passiert wenn kein linestring vorhanden ist.
                }
                
                if (CacheFactory.getInstance().isInitialized()) {
                    CacheFactory.getInstance().getCache().put(new net.sf.ehcache.Element(key, result));
                }
                
            }
        }
        return result;
    }


    @Override
    protected String createChartSubtitle(Locale locale, String uuid) {
        log.debug("create chart subtitle for horizontal crossprofile charts.");
        String subtitle = createTimePeriod(locale, uuid);

        return subtitle;
    }

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

    private static final boolean blacklisted(String column) {
        for (int i = 0; i < COLUMN_BLACKLIST.length; ++i) {
            if (COLUMN_BLACKLIST.equals(column)) {
                return true;
            }
        }
        return false;
    }

    private static boolean different(Result a, Result b, int [] indices) {
        for (int i = 0; i < indices.length; ++i) {
            String oa = a.getString(indices[i]);
            String ob = b.getString(indices[i]);

            if (oa == null && ob == null)  {
                continue;
            }

            if (oa == null || ob == null) {
                return true;
            }

            if (!oa.equals(ob)) {
                if (log.isDebugEnabled()) {
                    log.debug("+++++++++++++++ differs ++++++++++++++");
                    log.debug("   " + oa + " != " + ob);
                }
                return true;
            }
        }
        return false;
    }

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

    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;

        public SectionHandler() {
        }

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

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

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

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

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

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

        public void interpolated(Coordinate coordinate, boolean success) {
            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 (blacklisted(colname)) {
                    continue;
                }
                if (colname.equals("SHAPE")) {
                    result.addColumnValue(j, toWKT(coordinate));
                }
                else if (colname.equals("YORDINATE")) {
                    if (success) {
                        result.addColumnValue(j, Double.valueOf(coordinate.z));
                    }
                    else if (lastWasSuccess) {
                        // only insert null if last was valid.
                        // This prevents flooding the result set with nulls
                        // if interpolating over a large gap.
                        result.addColumnValue(j, null);
                    }
                }
                else {
                    result.addColumnValue(j, prototyp.getObject(i));
                }
                ++j;
            }
            output.add(result);
            lastWasSuccess = success;
        }
    } // class SectionHandler

    public static final double NAUTICAL_MILE = 1852.216d;
    public static final double KILOMETER     = 1000d;

    public static final double toKM(double distance) {
        return (distance * NAUTICAL_MILE) / KILOMETER;
    }

    public static final double EPSILON = 1e-5d;
    public static final int    INTERPOLATION_STEPS =
        Integer.getInteger("interpolation.steps", 500).intValue();

    public static Coordinate toCoordinate(String shape) {
        try {
            return ((Point)(new WKTReader().read(shape))).getCoordinate();
        }
        catch (ParseException pe) {
            log.error(pe);
        }
        return null;
    }

    public static String toWKT(Coordinate coordinate) {
        StringBuilder sb = new StringBuilder("POINT(");
        sb.append(coordinate.x)
          .append(' ')
          .append(coordinate.y)
          .append(')');
        return sb.toString();
    }

    protected Collection<Result> process(
        List<Coordinate>   path,
        Collection<Result> input
    ) {
        log.debug("------  number of points before processing: " + input.size());
        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 (!blacklisted(columnName)) {
                        resultDescriptor.addColumn(
                            columnName,
                            rd.getColumnClassName(j));
                    }
                }

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

                sectionHandler.setPrototyp(result);
            }

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

            sectionHandler.handle(result);

            last = result;
        }

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

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

        return output;
    }
}

http://dive4elements.wald.intevation.org