view artifacts/src/main/java/org/dive4elements/river/artifacts/MainValuesArtifact.java @ 7471:fff862f4ef76

Experimental caching of datacage recommendations. The respective hook is called a lot and running the datacage over and over again when loading data can be expensive. So the generated recommendations are cached for some time. Hopefully this improves the overall speed of loading data from the datacage.
author Sascha L. Teichmann <teichmann@intevation.de>
date Wed, 30 Oct 2013 15:26:21 +0100
parents 661b45d13b17
children a9eb777463e1
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;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import org.w3c.dom.Document;

import org.dive4elements.artifactdatabase.data.DefaultStateData;
import org.dive4elements.artifactdatabase.state.Facet;
import org.dive4elements.artifactdatabase.state.FacetActivity;
import org.dive4elements.artifactdatabase.state.DefaultOutput;
import org.dive4elements.artifactdatabase.state.State;

import org.dive4elements.artifacts.Artifact;
import org.dive4elements.artifacts.ArtifactFactory;
import org.dive4elements.artifacts.CallMeta;

import org.dive4elements.artifacts.common.ArtifactNamespaceContext;
import org.dive4elements.artifacts.common.utils.XMLUtils;

import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.MainValue;
import org.dive4elements.river.model.River;

import org.dive4elements.river.artifacts.access.RangeAccess;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.FacetTypes;
import org.dive4elements.river.artifacts.model.MainValuesQFacet;
import org.dive4elements.river.artifacts.model.MainValuesWFacet;
import org.dive4elements.river.artifacts.model.NamedDouble;
import org.dive4elements.river.artifacts.model.WstValueTable;
import org.dive4elements.river.artifacts.model.WstValueTableFactory;

import org.dive4elements.river.artifacts.states.StaticState;
import org.dive4elements.river.artifacts.resources.Resources;

import org.dive4elements.river.utils.RiverUtils;


/**
 * Artifact to access main and extreme values of a river.
 * This artifact neglects (Static)D4EArtifacts capabilities of interaction
 * with the StateEngine by overriding the getState*-methods.
 */
public class MainValuesArtifact
extends      StaticD4EArtifact
implements   FacetTypes
{
    /** The logger for this class. */
    private static Logger logger = Logger.getLogger(MainValuesArtifact.class);

    /** The name of the artifact. */
    public static final String ARTIFACT_NAME = "mainvalue";

    /** The name of the static state for this artifact. */
    public static final String STATIC_STATE_NAME = "state.mainvalue.static";

    /** One and only state to be in. */
    protected transient State state = null;


    static {
        // TODO: Move to configuration.
        FacetActivity.Registry.getInstance().register(
            ARTIFACT_NAME,
            new FacetActivity() {
                @Override
                public Boolean isInitialActive(
                    Artifact artifact,
                    Facet    facet,
                    String   outputName
                ) {
                    return outputName.equals("computed_discharge_curve")
                        || outputName.equals("duration_curve")
                        || outputName.equals("discharge_curve")
                        || outputName.equals("fix_wq_curve")
                        || outputName.equals("historical_discharge_wq");
                }
            });
    }


    /**
     * Trivial Constructor.
     */
    public MainValuesArtifact() {
        logger.debug("MainValuesArtifact.MainValuesartifact()");
    }


    /**
     * Gets called from factory, to set things up.
     */
    @Override
    public void setup(
        String          identifier,
        ArtifactFactory factory,
        Object          context,
        CallMeta        callMeta,
        Document        data)
    {
        logger.debug("MainValuesArtifact.setup");
        state = new StaticState(STATIC_STATE_NAME);

        initFromGaugeDoc(data, callMeta);

        List<Facet> fs = new ArrayList<Facet>();
        addFacets(state.getID(), fs);
        spawnState();
        super.setup(identifier, factory, context, callMeta, data);
        String restriction = getDatacageIDValue(data);
        logger.debug("mainvalue restriction " + restriction);
        boolean restricted = restriction.endsWith("q") || restriction.endsWith("w");
        if (!restricted || restriction.endsWith("q")) {
            Facet qfacet0 = new MainValuesQFacet(
                DURATION_MAINVALUES_Q,
                Resources.getMsg(
                    callMeta,
                    "facet.discharge_curves.mainvalues.q",
                    "facet.discharge_curves.mainvalues.q"),
                false);
            Facet qfacet1 = new MainValuesQFacet(
                COMPUTED_DISCHARGE_MAINVALUES_Q,
                Resources.getMsg(
                    callMeta,
                    "facet.discharge_curves.mainvalues.q",
                    "facet.discharge_curves.mainvalues.q"),
                false);
            Facet qfacet2 = new MainValuesQFacet(
                MAINVALUES_Q,
                Resources.getMsg(
                    callMeta,
                    "facet.discharge_curves.mainvalues.q",
                    "facet.discharge_curves.mainvalues.q"),
                true);
            Facet qfacet3 = new MainValuesQFacet(
                HISTORICAL_DISCHARGE_MAINVALUES_Q,
                Resources.getMsg(
                    callMeta,
                    "historical_discharge.mainvalues.q",
                    "historical_discharge.mainvalues.q"),
                false);

            fs.add(qfacet0);
            fs.add(qfacet1);
            fs.add(qfacet2);
            fs.add(qfacet3);
        }
        if (!restricted || restriction.endsWith("w")) {
            Facet wfacet1 = new MainValuesWFacet(
                COMPUTED_DISCHARGE_MAINVALUES_W,
                Resources.getMsg(
                    callMeta,
                    "facet.discharge_curves.mainvalues.w",
                    "facet.discharge_curves.mainvalues.w"),
                false);
            Facet wfacet2 = new MainValuesWFacet(
                MAINVALUES_W,
                Resources.getMsg(
                    callMeta,
                    "facet.discharge_curves.mainvalues.w",
                    "facet.discharge_curves.mainvalues.w"),
                true);
            Facet wfacet3 = new MainValuesWFacet(
                HISTORICAL_DISCHARGE_MAINVALUES_W,
                Resources.getMsg(
                    callMeta,
                    "historical_discharge.mainvalues.w",
                    "historical_discharge.mainvalues.w"),
                true);
            fs.add(wfacet1);
            fs.add(wfacet2);
            fs.add(wfacet3);
        }
    }

    /**
     * The MainValueArtifact can be set up with a document giving the
     * river and gauge.  This happens in context of GaugeDischargeArtifact.
     * In that case, initalize() is not called.
     */
    private void initFromGaugeDoc(Document data, CallMeta callMeta) {
        String gaugeref = XMLUtils.xpathString(
                data, GaugeDischargeCurveArtifact.XPATH_GAUGE,
                ArtifactNamespaceContext.INSTANCE);
        String rivername = XMLUtils.xpathString(
                data, GaugeDischargeCurveArtifact.XPATH_RIVER,
                ArtifactNamespaceContext.INSTANCE);

        if (rivername == null || gaugeref == null || rivername.equals("")
            || gaugeref.equals("")) {
            logger.debug("Not setting MainValuesArtifact up from gauge doc.");
            return;
        }

        addData("river", new DefaultStateData("river",
                    Resources.getMsg(callMeta,
                        "facet.gauge_discharge_curve.river",
                        "Name of the river"),
                    "String", rivername));

        try {
            Long officialNumber = Long.valueOf(gaugeref);
            Gauge gauge = Gauge.getGaugeByOfficialNumber(officialNumber);
            addData("ld_locations", new DefaultStateData("ld_locations", null, null,
                    String.valueOf(gauge.getStation())));
        } catch (NumberFormatException nfe) {
            logger.debug("MainValuesArtifact could not parse gaugeref from doc.");
        }
    }


    /**
     * Create "the" state.
     */
    protected State spawnState() {
        state = new StaticState(STATIC_STATE_NAME);
        List<Facet> fs = (List<Facet>) getFacets(STATIC_STATE_NAME);

        DefaultOutput mainValuesOutput = new DefaultOutput(
            "computed_discharge_curve",
            "output.computed_discharge_curve", "image/png",
            fs,
            "chart");

        state.getOutputs().add(mainValuesOutput);
        return state;
    }


    /** Get important data from the 'calling' artifact. */
    @Override
    protected void initialize(Artifact artifact, Object context, CallMeta meta) {
        logger.debug("MainValuesArtifact.initialize");
        D4EArtifact winfo = (D4EArtifact) artifact;
        RangeAccess rangeAccess = new RangeAccess(winfo);
        double [] locations = rangeAccess.getKmRange();

        if (locations != null) {
            double location = locations[0];
            addData("ld_locations", new DefaultStateData("ld_locations", null, null,
                    String.valueOf(location)));
        }
        else {
            logger.error("No location for mainvalues given.");
        }
        importData(winfo, "river");
        // In the case of DischargeWQCurves, there are no locations, but a gauge.
        if (getDataAsString("ld_locations") == null) {
            // TODO its a tad difficult to remodel Range/Gauge-Access to
            // do this.
            String refGaugeID = winfo.getDataAsString("reference_gauge");
            if (refGaugeID != null) {
                Gauge g = Gauge.getGaugeByOfficialNumber(Integer.parseInt(refGaugeID));
                addData("ld_locations", new DefaultStateData("ld_locations", null, null,
                    String.valueOf(g.getStation())));
            }
            else {
                logger.error("MainValuesArtifact: No location/gauge.");
            }
        }
    }


    /**
     * Get a list containing the one and only State.
     * @param  context ignored.
     * @return list with one and only state.
     */
    @Override
    protected List<State> getStates(Object context) {
        ArrayList<State> states = new ArrayList<State>();
        states.add(getState());
        return states;
    }


    /**
     * Get the "current" state.
     * @param cc ignored.
     * @return the "current" state.
     */
    @Override
    public State getCurrentState(Object cc) {
        return getState();
    }


    /**
     * Get the only possible state.
     * @return the state.
     */
    protected State getState() {
        return getState(null, null);
    }


    /**
     * Get the state.
     * @param context ignored.
     * @param stateID ignored.
     * @return the state.
     */
    @Override
    protected State getState(Object context, String stateID) {
        if (state != null)
            return state;
        else
            return spawnState();
    }

    /**
     * Access the Gauge that the mainvalues are taken from.
     * @return Gauge that main values are taken from or null in case of
     *         invalid parameterization.
     */
    protected Gauge getGauge(double km) {
        River river = RiverUtils.getRiver(this);

        if (river == null) {
            logger.error("River is null");
            return null;
        }

        return river.determineGaugeByPosition(km);
    }

    /**
     * Access the Gauge that the mainvalues are taken from.
     * @return Gauge that main values are taken from or null in case of
     *         invalid parameterization.
     */
    protected Gauge getGauge() {
        River river = RiverUtils.getRiver(this);

        // TODO use helper to get location as double
        String locationStr = getDataAsString("ld_locations");

        if (river == null) {
            logger.error("River is null");
            return null;
        }

        if (locationStr == null) {
            logger.error("Locationstr is null");
            return null;
        }

        double location = Double.parseDouble(locationStr);

        return river.determineGaugeByPosition(location);
    }


    /**
     * Get current location.
     * @return the location.
     */
    public double getLocation() {
        double location = Double.parseDouble(getDataAsString("ld_locations"));
        return location;
    }


    /**
     * Get a list of "Q" main values.
     * @return list of Q main values.
     */
    public List<NamedDouble> getMainValuesQ(double[] kms) {
        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
        boolean atGauge = false;
        Gauge gauge = getGauge(kms[0]);
        WstValueTable interpolator = WstValueTableFactory.getTable(RiverUtils.getRiver(this));
        Calculation c = new Calculation();
        double w_out[] = {0.0f};
        double q_out[] = {0.0f};
        if (gauge != null) {
            double gaugeStation = gauge.getStation().doubleValue();
            atGauge = Math.abs(kms[0] - gaugeStation) < 1e-4;
            List<MainValue> orig = gauge.getMainValues();
            for (MainValue mv : orig) {
                if (mv.getMainValue().getType().getName().equals("Q")) {
                    if (atGauge) {
                        q_out[0] = mv.getValue().doubleValue();
                    }
                    else {
                        interpolator.interpolate(mv.getValue().doubleValue(),
                            gaugeStation, kms, w_out, q_out, c);
                    }
                    filteredList.add(new NamedDouble(
                                mv.getMainValue().getName(),
                                q_out[0]
                                ));
                }
            }
        }
        return filteredList;
    }

    /**
     * Get a list of "Q" main values.
     * @return list of Q main values.
     */
    public List<NamedDouble> getMainValuesQ(boolean atGauge) {
        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
        Gauge gauge = getGauge();
        WstValueTable interpolator = WstValueTableFactory.getTable(RiverUtils.getRiver(this));
        Calculation c = new Calculation();
        double w_out[] = {0.0f};
        double q_out[] = {0.0f};
        double kms[] = {getLocation()};
        if (gauge != null) {
            double gaugeStation = gauge.getStation().doubleValue();
            List<MainValue> orig = gauge.getMainValues();
            for (MainValue mv : orig) {
                if (mv.getMainValue().getType().getName().equals("Q")) {
                    if (atGauge) {
                        q_out[0] = mv.getValue().doubleValue();
                    }
                    else {
                        interpolator.interpolate(mv.getValue().doubleValue(),
                            gaugeStation, kms, w_out, q_out, c);
                    }
                    filteredList.add(new NamedDouble(
                                mv.getMainValue().getName(),
                                q_out[0]
                                ));
                }
            }
        }
        return filteredList;
    }


    /** Get main values of km. */
    public List<NamedDouble> getMainValuesW(double[] kms) {
        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
        boolean atGauge = false;
        double gaugeDatum = 0d;
        Gauge gauge = getGauge(kms[0]);
        if (gauge == null) {
            return filteredList;
        }
        else if (Math.abs(kms[0] - gauge.getStation().doubleValue()) < 1e-4) {
            atGauge = true;
            gaugeDatum = gauge.getDatum().doubleValue();
        }

        WstValueTable interpolator = WstValueTableFactory.getTable(RiverUtils.getRiver(this));
        Calculation c = new Calculation();

        double gaugeStation = gauge.getStation().doubleValue();
        double w_out[] = {0.0f};
        double q_out[] = {0.0f};
        if (gauge != null) {
            List<MainValue> orig = gauge.getMainValues();
            for (MainValue mv : orig) {
                if (atGauge) {
                    if (mv.getMainValue().getType().getName().equals("W")) {
                        filteredList.add(new NamedDouble(mv.getMainValue().getName(),
                                mv.getValue().doubleValue()));
                    }
                } else
                // We cannot interpolate the W values, so derive them
                // from given Q values.
                if (mv.getMainValue().getType().getName().equals("Q")) {
                    interpolator.interpolate(mv.getValue().doubleValue(),
                            gaugeStation, kms, w_out, q_out, c);

                    filteredList.add(new NamedDouble(
                                "W(" + mv.getMainValue().getName() +")",
                                w_out[0]
                                ));
                }
            }
        }
        return filteredList;
    }


    public List<NamedDouble> getMainValuesW(boolean atGauge, double[] kms) {
        List<NamedDouble> filteredList = new ArrayList<NamedDouble>();
        Gauge gauge = getGauge();
        WstValueTable interpolator = WstValueTableFactory.getTable(RiverUtils.getRiver(this));
        Calculation c = new Calculation();

        double w_out[] = {0.0f};
        double q_out[] = {0.0f};
        if (gauge != null) {
            double gaugeStation = gauge.getStation().doubleValue();
            List<MainValue> orig = gauge.getMainValues();
            for (MainValue mv : orig) {
                if (atGauge) {
                    if (mv.getMainValue().getType().getName().equals("W")) {
                        filteredList.add(new NamedDouble(mv.getMainValue().getName(),
                                mv.getValue().doubleValue()));

                    }
                } else
                // We cannot interpolate the W values, so derive them
                // from given Q values.
                if (mv.getMainValue().getType().getName().equals("Q")) {
                    interpolator.interpolate(mv.getValue().doubleValue(),
                            gaugeStation, kms, w_out, q_out, c);
                    filteredList.add(new NamedDouble(
                                "W(" + mv.getMainValue().getName() +")",
                                w_out[0]
                                ));
                }
            }
        }
        return filteredList;
    }


    /**
     * Get a list of "W" main values.
     * @param atGauge if true, do not interpolate
     * @return list of W main values.
     */
    public List<NamedDouble> getMainValuesW(boolean atGauge) {
        return getMainValuesW(atGauge, new double[] {getLocation()});
    }


    /**
     * Returns the name of this artifact ('mainvalue').
     *
     * @return 'mainvalue'
     */
    public String getName() {
        return ARTIFACT_NAME;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org