view artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFacet.java @ 7357:9d3e44ab25f2

Refactoring: Move functionality of BedHeightAccess into BedHeightFacet for now. Idea is that Artifact and Access are lightweight. Access access the 'data' ('parameterization') attached to artifact, not the data delivered by means of artifact and its parameterization.
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Wed, 16 Oct 2013 10:42:45 +0200
parents fe32a7f9655e
children aab63784a80f
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.TreeSet;
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);
    }

    @Override
    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: new TreeSet<Double>(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, S = stations.size(); k < S; 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 <= S) {
                        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