gernotbelger@8915: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@8915: * Software engineering by gernotbelger@8915: * Björnsen Beratende Ingenieure GmbH gernotbelger@8915: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@8915: * gernotbelger@8915: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@8915: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@8915: * documentation coming with Dive4Elements River for details. gernotbelger@8915: */ gernotbelger@8915: package org.dive4elements.river.artifacts.sinfo.tkhstate; gernotbelger@8915: gernotbelger@8915: import java.util.ArrayList; gernotbelger@8915: import java.util.Collection; gernotbelger@8915: import java.util.List; gernotbelger@8915: import java.util.Map.Entry; gernotbelger@8915: import java.util.NavigableMap; gernotbelger@8915: import java.util.TreeMap; gernotbelger@8915: gernotbelger@8915: import org.apache.commons.lang.math.DoubleRange; gernotbelger@8946: import org.dive4elements.artifacts.CallContext; gernotbelger@8946: import org.dive4elements.river.artifacts.BedHeightsArtifact; gernotbelger@8915: import org.dive4elements.river.artifacts.math.Linear; gernotbelger@8946: import org.dive4elements.river.artifacts.model.Calculation; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo; gernotbelger@8915: import org.dive4elements.river.model.BedHeight; gernotbelger@8915: import org.dive4elements.river.model.BedHeightValue; gernotbelger@8964: import org.dive4elements.river.model.BedHeightValueType; gernotbelger@8946: import org.dive4elements.river.utils.RiverUtils; gernotbelger@8915: gernotbelger@8915: /** gernotbelger@9573: * Provides bed levels for various calculations. gernotbelger@8915: * gernotbelger@8915: * @author Gernot Belger gernotbelger@8915: */ gernotbelger@8915: public final class BedHeightsFinder { gernotbelger@8915: gernotbelger@8964: private static double MAX_DISTANCE_KM = 1; gernotbelger@8964: gernotbelger@8915: private final BedHeightInfo info; gernotbelger@8915: gernotbelger@8915: private final NavigableMap values; gernotbelger@8915: gernotbelger@8964: private Calculation problems; mschaefer@8955: mschaefer@9480: private final boolean isNull; mschaefer@9480: gernotbelger@8915: /** gernotbelger@9573: * Create bed level finders from a collection of bed levels. gernotbelger@8915: */ gernotbelger@8964: public static Collection createTkhBedHeights(final Calculation problems, final DoubleRange range, gernotbelger@8964: final Collection bedHeights) { gernotbelger@8915: final List result = new ArrayList<>(bedHeights.size()); gernotbelger@8915: gernotbelger@8915: for (final BedHeight bedHeight : bedHeights) { gernotbelger@8964: final BedHeightsFinder finder = createBedHeights(problems, bedHeight, range); gernotbelger@8942: result.add(finder); gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: return result; gernotbelger@8915: } gernotbelger@8915: gernotbelger@8946: public static BedHeightsFinder forId(final CallContext context, final String soundingId, final DoubleRange calcRange, final Calculation problems) { gernotbelger@8946: gernotbelger@8946: // REMARK: absolutely unbelievable.... gernotbelger@8946: // The way how bed-heights (and other data too) is accessed is different for nearly every calculation-type gernotbelger@8946: // throughout flys. gernotbelger@8946: // The knowledge on how to parse the datacage-ids is spread through the complete code-base... gernotbelger@8946: gernotbelger@8946: // We use here the way on how bed-heights are accessed by the BedDifferenceAccess/BedDifferenceCalculation, but gernotbelger@8946: // this is plain random gernotbelger@8946: final String[] parts = soundingId.split(";"); gernotbelger@8946: gernotbelger@8946: final BedHeightsArtifact artifact = (BedHeightsArtifact) RiverUtils.getArtifact(parts[0], context); gernotbelger@8946: gernotbelger@8946: final Integer bedheightId = artifact.getDataAsInteger("height_id"); gernotbelger@8946: gernotbelger@8946: // REMARK: this only works with type 'single'; unclear on how to distinguish from epoch data (or whatever the gernotbelger@8946: // other type means) gernotbelger@8946: // Luckily, the requirement is to only access 'single' data here. gernotbelger@8946: // final String bedheightType = artifact.getDataAsString("type"); gernotbelger@8946: gernotbelger@8946: // REMARK: BedDifferences uses this, but we also need the metadata of the BedHeight gernotbelger@8946: // REMARK: second absolutely awful thing: BedHeight is a hibernate binding class, accessing the database via gernotbelger@8946: // hibernate stuff gernotbelger@8946: // BedHeightFactory uses its own (direct) way of accessing the data, with its own implemented data classes. gernotbelger@8946: // return BedHeightFactory.getHeight(bedheightType, bedheightId, from, to); gernotbelger@8946: gernotbelger@8964: final BedHeightsFinder bedHeight = bedheightId == null ? null : BedHeightsFinder.forId(problems, bedheightId, calcRange); gernotbelger@8964: if (bedHeight == null) { gernotbelger@8964: problems.addProblem("sinfo.bedheightsfinder.notfound", soundingId); gernotbelger@8964: return null; gernotbelger@8964: } gernotbelger@8946: gernotbelger@8964: if (bedHeight.isEmpty()) { gernotbelger@8964: problems.addProblem("sinfo.bedheightsfinder.empty"); gernotbelger@8964: return null; gernotbelger@8964: } gernotbelger@8964: gernotbelger@8964: return bedHeight; gernotbelger@8946: } gernotbelger@8946: gernotbelger@8915: /** gernotbelger@8915: * Creates a {@link BedHeightsFinder} for a dataset from the database, specified by its id. gernotbelger@8915: * gernotbelger@9573: * @return null if no bed level with the given id exists. gernotbelger@8915: */ mschaefer@9394: public static BedHeightsFinder forId(final Calculation problems, final int id, final DoubleRange range) { gernotbelger@8915: gernotbelger@8915: final BedHeight bedHeight = BedHeight.getBedHeightById(id); gernotbelger@8915: if (bedHeight == null) gernotbelger@8915: return null; gernotbelger@8915: gernotbelger@8964: return BedHeightsFinder.createBedHeights(problems, bedHeight, range); gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: /** mschaefer@9480: * Creates a {@link BedHeightsFinder} that returns always NaN heights mschaefer@9480: */ mschaefer@9480: public static BedHeightsFinder NullFinder() { mschaefer@9480: final NavigableMap values = new TreeMap<>(); mschaefer@9480: return new BedHeightsFinder(null, null, values, true); mschaefer@9480: } mschaefer@9480: mschaefer@9480: /** gernotbelger@9573: * Create a finder for a given bed level. gernotbelger@8915: * gernotbelger@8915: */ gernotbelger@8964: private static BedHeightsFinder createBedHeights(final Calculation problems, final BedHeight bedHeight, final DoubleRange range) { gernotbelger@8915: gernotbelger@8915: // FIXME: sort by station, but in what direction? gernotbelger@8915: // FIXME: using river.getKmUp()? gernotbelger@8915: final NavigableMap values = new TreeMap<>(); gernotbelger@8915: gernotbelger@8915: for (final BedHeightValue bedHeightValue : bedHeight.getValues()) { gernotbelger@8915: final Double station = bedHeightValue.getStation(); gernotbelger@8915: if (station != null && range.containsDouble(station)) { gernotbelger@8915: gernotbelger@8915: if (bedHeightValue.getHeight() != null) gernotbelger@8915: values.put(station, bedHeightValue); gernotbelger@8915: } gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: final BedHeightInfo info = BedHeightInfo.from(bedHeight); gernotbelger@8915: mschaefer@9480: return new BedHeightsFinder(problems, info, values, false); gernotbelger@8915: } gernotbelger@8915: mschaefer@9480: private BedHeightsFinder(final Calculation problems, final BedHeightInfo info, final NavigableMap values, final boolean isNull) { gernotbelger@8915: this.info = info; gernotbelger@8915: this.values = values; gernotbelger@8964: this.problems = problems; mschaefer@9480: this.isNull = isNull; mschaefer@9480: } mschaefer@9480: mschaefer@9480: /** mschaefer@9480: * Whether this is a null (always NaN) finder. gernotbelger@9573: * mschaefer@9480: * @return mschaefer@9480: */ mschaefer@9480: public boolean isNull() { mschaefer@9480: return this.isNull; gernotbelger@8964: } gernotbelger@8964: gernotbelger@8964: public boolean isEmpty() { gernotbelger@8964: return this.values.isEmpty(); gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: public BedHeightInfo getInfo() { gernotbelger@8915: return this.info; gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: public Collection getStations() { gernotbelger@8915: return this.values.keySet(); gernotbelger@8915: } gernotbelger@8915: mschaefer@9394: public DoubleRange getKmRange() { mschaefer@9394: if (this.values.isEmpty()) mschaefer@9394: return null; mschaefer@9394: return new DoubleRange(this.values.firstKey().doubleValue(), this.values.lastKey().doubleValue()); mschaefer@9394: } mschaefer@9394: gernotbelger@8915: public double getMeanBedHeight(final double km) { gernotbelger@8964: return interpolateBedHeights(km, BedHeightValueType.value); mschaefer@8955: } gernotbelger@8915: mschaefer@8955: public double getMinBedHeight(final double km) { gernotbelger@8964: return interpolateBedHeights(km, BedHeightValueType.min); mschaefer@8955: } mschaefer@8955: mschaefer@8955: public double getMaxBedHeight(final double km) { gernotbelger@8964: return interpolateBedHeights(km, BedHeightValueType.max); mschaefer@8955: } mschaefer@8955: mschaefer@9444: public double getFieldHeight(final double km, final int index) { mschaefer@9444: return interpolateBedHeights(km, BedHeightValueType.field(index)); mschaefer@9444: } mschaefer@9444: gernotbelger@8964: private double interpolateBedHeights(final double km, final BedHeightValueType type) { gernotbelger@9573: if (this.values.containsKey(km)) { gernotbelger@8964: final Double value = type.getValue(this.values.get(km)); gernotbelger@8964: return value == null ? Double.NaN : value.doubleValue(); mschaefer@8955: } gernotbelger@8915: gernotbelger@8915: final Entry floorEntry = this.values.floorEntry(km); gernotbelger@8915: final Entry ceilingEntry = this.values.ceilingEntry(km); gernotbelger@8915: gernotbelger@8964: if (floorEntry == null || ceilingEntry == null) gernotbelger@8964: return Double.NaN; gernotbelger@8915: mschaefer@8955: final double floorKm = floorEntry.getKey().doubleValue(); mschaefer@8955: final double ceilKm = ceilingEntry.getKey().doubleValue(); gernotbelger@8915: gernotbelger@8964: /* report once if the interpolation distance exceeds 1000m */ gernotbelger@8964: if (Math.abs(floorKm - ceilKm) > MAX_DISTANCE_KM && this.problems != null) { gernotbelger@8964: this.problems.addProblem(km, "linearInterpolator.maxdistance", MAX_DISTANCE_KM * 1000); gernotbelger@8964: this.problems = null; gernotbelger@8964: return Double.NaN; gernotbelger@8964: } gernotbelger@8915: gernotbelger@8964: final Double floorHeight = type.getValue(floorEntry.getValue()); gernotbelger@8964: final Double ceilingHeight = type.getValue(ceilingEntry.getValue()); mschaefer@8955: gernotbelger@8964: if (floorHeight == null || ceilingHeight == null) mschaefer@8955: return Double.NaN; gernotbelger@8964: gernotbelger@8964: return Linear.linear(km, floorKm, ceilKm, floorHeight, ceilingHeight); gernotbelger@8915: } gernotbelger@8915: }