view artifacts/src/main/java/org/dive4elements/river/artifacts/states/WaterlevelFetcher.java @ 9425:3f49835a00c3

Extended CrossSectionFacet so it may fetch different data from within the artifact result. Also allows to have acces to the potentially already computed artifact result via its normal computation cache.
author gernotbelger
date Fri, 17 Aug 2018 15:31:02 +0200
parents 392745cccede
children 93389a05119e
line wrap: on
line source
/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
 * Software engineering by
 *  Björnsen Beratende Ingenieure GmbH
 *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
 *
 * 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.states;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.math.DoubleRange;
import org.apache.log4j.Logger;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.D4EArtifact;
import org.dive4elements.river.artifacts.FixationArtifact;
import org.dive4elements.river.artifacts.StaticWKmsArtifact;
import org.dive4elements.river.artifacts.StaticWQKmsArtifact;
import org.dive4elements.river.artifacts.WINFOArtifact;
import org.dive4elements.river.artifacts.access.FixRealizingAccess;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.Segment;
import org.dive4elements.river.artifacts.model.WKms;
import org.dive4elements.river.artifacts.model.WQKms;
import org.dive4elements.river.artifacts.model.WstColumnFactory;
import org.dive4elements.river.artifacts.model.fixings.FixRealizingCalculationExtended;
import org.dive4elements.river.artifacts.model.fixings.FixRealizingResult;
import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
import org.dive4elements.river.model.TimeInterval;
import org.dive4elements.river.model.WstColumn;
import org.dive4elements.river.utils.RiverUtils;

/**
 * Helper class that accesses existing waterlevels (in most cases) from the data-cage.
 * This is mainly a refaktoring of org.dive4elements.river.artifacts.states.WDifferencesState.getWKms(String,
 * CallContext, double, double), adding more infos to the fetched water levels.
 *
 * @author Gernot Belger
 */
public class WaterlevelFetcher {
    private static Logger log = Logger.getLogger(WaterlevelFetcher.class);

    /**
     * @param simulationRange
     *            This range is used to check if the found waterlevel covers it. It is NOT used to reduce the fetched data,
     *            because in case of waterlevels we always need to full set in order to determine the relevant gauge.
     */
    public WaterlevelData findWaterlevel(final CallContext context, final String mingle, final DoubleRange simulationRange, final Calculation problems) {

        final String[] def = mingle.split(";");
        final String uuid = def[0];
        // final String factory = def[1];
        final int idx = Integer.parseInt(def[2]);
        final String name = def[3];
        final D4EArtifact d4eArtifact = RiverUtils.getArtifact(uuid, context);
        if (d4eArtifact == null)
            return null;

        final WaterlevelData data = fetchWaterlevelFromArtifact(context, d4eArtifact, idx, Double.NaN, Double.NaN);
        if (data == null) {
            problems.addProblem("waterlevelfetcher.missing", mingle);
            return null;
        }

        if (!data.covers(simulationRange)) {
            problems.addProblem("waterlevelfetcher.empty", data.getName());
            return null;
        }

        return data.withName(name);
    }

    private WaterlevelData fetchWaterlevelFromArtifact(final CallContext context, final D4EArtifact d4eArtifact, final int idx, final double from,
            final double to) {

        if (d4eArtifact instanceof StaticWKmsArtifact)
            return fetchStaticWKmsArtifactWaterlevel((StaticWKmsArtifact) d4eArtifact, from, to);

        if (d4eArtifact instanceof StaticWQKmsArtifact)
            return fetchStaticWQKmsArtifactWaterlevel((StaticWQKmsArtifact) d4eArtifact, from, to);

        if (d4eArtifact instanceof WINFOArtifact)
            return fetchWINFOArtifactWaterlevel(context, (WINFOArtifact) d4eArtifact, idx, from, to);

        if (d4eArtifact instanceof FixationArtifact)
            return fetchFixationArtifactWaterlevel(context, (FixationArtifact) d4eArtifact, idx, from, to);

        log.warn(String.format("Get Waterlevel from %s not implemented!", d4eArtifact.getClass().getSimpleName()));
        return null;
    }

    // REMARK: instead of several helper methods here this would be a good place for abstraction, in order to push
    // this logic back to the corresponding artifacts. However this will most certainly break existing
    // artifact-serialization

    private WaterlevelData fetchStaticWKmsArtifactWaterlevel(final StaticWKmsArtifact staticWKms, final double from, final double to) {

        log.debug("WDifferencesState obtain data from StaticWKms");

        final WKms wkms = staticWKms.getWKms(from, to);

        if (wkms != null) {
            final int year = fetchStaticWKmsYear(staticWKms);
            return new WaterlevelData(wkms, year, false, false);
        }

        log.error("No WKms from Static artifact for this range.");
        return null;
    }

    private WaterlevelData fetchStaticWQKmsArtifactWaterlevel(final StaticWQKmsArtifact staticWKms, final double from, final double to) {

        log.debug("WDifferencesState obtain data from StaticWQKms");

        final WQKms wkms = staticWKms.getWQKms(from, to);

        if (wkms != null) {
            final int year = fetchStaticWKmsYear(staticWKms);
            return new WaterlevelData(wkms, year, false, false);
        }

        log.error("No WKms from Static artifact for this range.");
        return null;
    }

    private WaterlevelData fetchWINFOArtifactWaterlevel(final CallContext context, final WINFOArtifact flys, final int idx, final double from,
            final double to) {
        log.debug("Get WKms from WINFOArtifact");

        final WKms[] wkms = (WKms[]) flys.getWaterlevelData(context).getData();

        if (wkms == null || wkms.length == 0) {
            log.warn("no waterlevels in artifact");
            return null;
        }

        if (wkms.length < idx + 1) {
            log.warn("Not enough waterlevels in artifact.");
            return null;
        }

        // REAMRK: W_INFO results does not know any 'year'
        final int year = -1;
        return new WaterlevelData(wkms[idx], year, false, true).filterByRange(from, to);
    }

    private WaterlevelData fetchFixationArtifactWaterlevel(final CallContext context, final FixationArtifact fixation, final int idx, final double from,
            final double to) {

        log.debug("Get WKms from FixationArtifact.");

        final CalculationResult r = (CalculationResult) fixation.compute(context, ComputeType.ADVANCE, false);
        final FixRealizingResult frR = (FixRealizingResult) r.getData();

        // Get W/Q input per gauge for this case.
        final FixRealizingAccess fixAccess = new FixRealizingAccess(fixation);
        final List<Segment> segments = fixAccess.getSegments();
        final boolean isFixRealize = (segments != null && !segments.isEmpty());

        /* ugly but necessary to keep this logic at least a bit inside the FixRealizing stuff */
        final FixRealizingCalculationExtended calculation = new FixRealizingCalculationExtended(fixAccess);
        final int year = calculation.determineMeanYear();

        // REMARK: same logic as in WaterlevelExporter
        final boolean showAllGauges = isFixRealize;

        return new WaterlevelData(frR.getWQKms()[idx], year, showAllGauges, true).filterByRange(from, to);
    }

    /**
     * Fetches the 'year' for a staticXXX-artifact.
     * REMARK: actually this should happen inside the staticWKms artifact and eventually in the WKmsFactory, but the code
     * there is already awful and it will also break the old artifact-serialization...
     */
    private int fetchStaticWKmsYear(final D4EArtifact staticWKms) {

        final int colPos = Integer.parseInt(staticWKms.getDataAsString("col_pos"));
        final int wstId = Integer.parseInt(staticWKms.getDataAsString("wst_id"));

        final WstColumn wstColumn = WstColumnFactory.getWstColumn(wstId, colPos);
        final TimeInterval timeInterval = wstColumn.getTimeInterval();
        if (timeInterval == null)
            return -1;

        final Date startTime = timeInterval.getStartTime();
        if (startTime == null)
            return -1;

        // REMARK: the times are stored without timezone in the DB, so it is unclear what hibernate makes of it.
        // We simply use the default timezone here and hope we never get problems...
        // Actually we always have 12:00 as time in the db data, so a smal timeshift due to winter/sommertime or UTC/GMT+1 will
        // no change anything regarding the year.
        final Calendar cal = Calendar.getInstance();
        cal.setTime(startTime);
        return cal.get(Calendar.YEAR);
    }
}

http://dive4elements.wald.intevation.org