view artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFacet.java @ 6665:b7945db8a43b

issue1413: Only show unknown sediment loads of selected unit type. Therefore, adjusted the factory to take the units name. Unfortunately, names in db do not match values of data items. Thus do manual replacing. In Facet and Calculate, take the chosen unit via access and to the string replacement. In Facet, do not transform data (we assume it comes in unit as labeled in the db), and removed the possibility of m3/a-data of unknown yields in a t/a diagram and vice versa.
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Thu, 25 Jul 2013 15:08:13 +0200
parents c74261e05a62
children 0c593745bcd6
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.model.minfo;

import gnu.trove.TDoubleArrayList;

import org.dive4elements.artifactdatabase.state.Facet;

import org.dive4elements.artifacts.Artifact;
import org.dive4elements.artifacts.CallContext;

import org.dive4elements.river.artifacts.D4EArtifact;

import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.DataFacet;
import org.dive4elements.river.artifacts.model.FacetTypes;

import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;

import org.dive4elements.river.model.MeasurementStation;

import org.dive4elements.river.utils.RiverUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;


/** Facet to access various sediment loads. */
public class SedimentLoadFacet
extends DataFacet
{
    /** Very own logger. */
    private static Logger logger = Logger.getLogger(SedimentLoadFacet.class);

    /** Used as tolerance value when fetching measurement stations. */
    private static double EPSILON = 1e-5;


    public SedimentLoadFacet() {
    }

    public SedimentLoadFacet(int idx, String name, String description,
        ComputeType type, String stateId, String hash) {
        super(idx, name, description, type, hash, stateId);
    }

    public Object getData(Artifact artifact, CallContext context) {
        logger.debug("Get data for sediment load at index: " + index);

        D4EArtifact flys = (D4EArtifact) artifact;

        CalculationResult res = (CalculationResult) flys.compute(context, hash,
            stateId, type, false);

        Object[] data =
            (SedimentLoadResult[]) res.getData(); // TODO CAST TO SPECIFIC CLASS

        List<MeasurementStation> allStations = RiverUtils.getRiver(flys).getMeasurementStations();
        SedimentLoadResult result = data != null && data.length > index ? (SedimentLoadResult)data[index] : null;
        if (result == null) {
            return null;
        }

        // These complicated calculations were necessary because
        // SedimentLoad/Fraction did not contain the ranges of the given
        // values. Since this changed, the code is somewhat obsolete, but stable.
        // For an example of easier calculation, see the "total" part below.

        List<Double> sortedStarts = new ArrayList<Double>();
        // Filter stations according to type.
        List<MeasurementStation> stations = new ArrayList<MeasurementStation>();
        for (MeasurementStation station: allStations) {
            if (station.getRange() == null || station.getMeasurementType() == null) {
                continue;
            }
            if (this.getName().contains("susp_sediment")
                && station.getMeasurementType().equals("Schwebstoff")) {
                stations.add(station);
                sortedStarts.add(station.getStation());
            }
            else if (!this.getName().contains("susp_sediment")
                && station.getMeasurementType().equals("Geschiebe")) {
                stations.add(station);
                sortedStarts.add(station.getStation());
            }
        }
        Collections.sort(sortedStarts);

        // Handle sediment load differently, as it respects already
        // the ranges that were added to SedimentLoad/Fraction.
        if (getName().equals(FacetTypes.SEDIMENT_LOAD_TOTAL)) {
            SedimentLoad load = result.getLoad();
            TDoubleArrayList xPos = new TDoubleArrayList();
            TDoubleArrayList yPos = new TDoubleArrayList();
            double lastX = -1d;
            for (double km: load.getKms()) {
                SedimentLoadFraction fraction = load.getFraction(km);
                if (fraction.getTotal() != 0) {
                    if (Math.abs(lastX-km) >= EPSILON) {
                       xPos.add(Double.NaN);
                       yPos.add(Double.NaN);
                    }
                    xPos.add(km);
                    yPos.add(fraction.getTotal());
                    xPos.add(fraction.getTotalRange().getEnd());
                    yPos.add(fraction.getTotal());
                    lastX = fraction.getTotalRange().getEnd();
                }
            }
            return new double[][] {xPos.toNativeArray(), yPos.toNativeArray()};
        }

        // Access data according to type (except total - see above).
        double[][] sd = getLoadData(result);

        // Sort by km.
        TreeMap<Double, Double> sortedKmLoad = new TreeMap<Double,Double>();

        double[] km   = sd[0];
        double[] load = sd[1];

        // Build map of km->load, but directly exclude the ones which do
        // not match against a measurements station ranges start.
        for (int i = 0 ; i < km.length; i++) {
            for (MeasurementStation station: stations) {
                if (Math.abs(station.getStation() - km[i]) <= EPSILON) {
                    sortedKmLoad.put(km[i], load[i]);
                    continue;
                }
            }
        }

        // [0] -> x, [1] -> y
        double[][] values = new double[2][];
        values[0] = new double[sortedKmLoad.size()*3];
        values[1] = new double[sortedKmLoad.size()*3];

        // Find station via its station (km).
        // TODO use a binarySearch instead of linear absdiff approach
        int i = 0;
        for (Map.Entry<Double, Double> kmLoad: sortedKmLoad.entrySet()) {
            boolean matchFound = false;
            for (int k = 0; k < stations.size(); k++) {
                MeasurementStation station = stations.get(k);
                if (Math.abs(station.getStation() - kmLoad.getKey()) < EPSILON) {
                    // Value has been taken at measurement station.
                    values[0][i*3]   = station.getRange().getA().doubleValue() + EPSILON;
                    values[1][i*3]   = kmLoad.getValue();
                    double endValue = 0d;
                    // Valid until next measurements stations begin of range,
                    // or end of current range if last value.
                    if (k+2 <= stations.size()) {
                        endValue = stations.get(k+1).getRange().getA().doubleValue();
                    }
                    else {
                        endValue = station.getRange().getB().doubleValue();
                    }
                    values[0][i*3+1] = endValue;
                    values[1][i*3+1] = kmLoad.getValue();
                    values[0][i*3+2] = endValue;
                    values[1][i*3+2] = kmLoad.getValue();
                    matchFound = true;
                }
            }
            // Store points without match for later assessment.
            if (!matchFound) {
                logger.warn("measurement without station ("+kmLoad.getKey()+")!");
            }
            i++;
        }

        for (int x = 0; x < values[0].length-1; x++) {
            // Introduce gaps where no data in measurement station.
            if (Math.abs(values[0][x+1] - values[0][x]) > 3*EPSILON
                    && values[1][x+1] != values[1][x]) {
                values[0][x] = Double.NaN;
                values[1][x] = Double.NaN;
            }
        }

        return values;
    }


    /** Get data according to type of facet. */
    private double[][] getLoadData(SedimentLoadResult result) {
        if (getName().equals(FacetTypes.SEDIMENT_LOAD_SAND))
            return result.getSandData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_COARSE))
            return result.getCoarseData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_FINEMIDDLE))
            return result.getFineMiddleData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_SUSP_SAND))
            return result.getSuspSandData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_SUSP_SAND_BED))
            return result.getSuspSandBedData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_SUSP_SEDIMENT))
            return result.getSuspSedimentData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_TOTAL_LOAD))
            return result.getTotalLoadData();
        else if (getName().equals(FacetTypes.SEDIMENT_LOAD_TOTAL))
            return result.getTotalData();
        else {
            logger.error("SedimentLoadFacet " + getName() + " cannot determine data type.");
            return null;
        }
    }

    /** Copy deeply. */
    @Override
    public Facet deepCopy() {
        SedimentLoadFacet copy = new SedimentLoadFacet();
        copy.set(this);
        copy.type = type;
        copy.hash = hash;
        copy.stateId = stateId;
        return copy;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org