view artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFacet.java @ 8024:963ede7b32bb

Renamed SedimentLoad to SedimentLoadLSData to make place for SedimentLoad in backend.
author Sascha L. Teichmann <teichmann@intevation.de>
date Wed, 09 Jul 2014 17:33:57 +0200
parents 42076d94977e
children 5e3f4b4fcb28
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);
        this.metaData.put("X", "chart.longitudinal.section.xaxis.label");
        this.metaData.put("Y", "");
    }

    @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_TA_TOTAL)
           ||getName().equals(FacetTypes.SEDIMENT_LOAD_M3A_TOTAL)) {
            SedimentLoadLSData 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) {
        String name = getName();
        if (FacetTypes.IS.SEDIMENT_LOAD_SAND(name))
            return result.getSandData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_COARSE(name))
            return result.getCoarseData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_FINEMIDDLE(name))
            return result.getFineMiddleData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_SUSP_SAND(name))
            return result.getSuspSandData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_SUSP_SAND_BED(name))
            return result.getSuspSandBedData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_SUSP_SEDIMENT(name))
            return result.getSuspSedimentData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_TOTAL_LOAD(name))
            return result.getTotalLoadData();
        else if (FacetTypes.IS.SEDIMENT_LOAD_TOTAL(name))
            return result.getTotalData();
        else {
            logger.error("SedimentLoadFacet " + name + " 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