ingo@105: package de.intevation.flys.artifacts;
ingo@105:
sascha@1055: import de.intevation.artifactdatabase.data.StateData;
ingo@105:
sascha@3556: import de.intevation.artifactdatabase.state.Facet;
sascha@3556: import de.intevation.artifactdatabase.state.FacetActivity;
sascha@3556:
sascha@3556: import de.intevation.artifacts.Artifact;
sascha@1055: import de.intevation.artifacts.CallContext;
sascha@1055:
sascha@3233: import de.intevation.artifacts.common.utils.StringUtils;
sascha@3233:
sascha@3233: import de.intevation.flys.artifacts.geom.Lines;
sascha@3233:
sascha@1055: import de.intevation.flys.artifacts.model.Calculation1;
sascha@1055: import de.intevation.flys.artifacts.model.Calculation2;
sascha@1055: import de.intevation.flys.artifacts.model.Calculation3;
sascha@1055: import de.intevation.flys.artifacts.model.Calculation4;
sascha@2194: import de.intevation.flys.artifacts.model.Calculation5;
ingo@2215: import de.intevation.flys.artifacts.model.Calculation6;
sascha@1055: import de.intevation.flys.artifacts.model.Calculation;
sascha@1055: import de.intevation.flys.artifacts.model.CalculationResult;
sascha@1055: import de.intevation.flys.artifacts.model.DischargeTables;
felix@1809: import de.intevation.flys.artifacts.model.FacetTypes;
sascha@1055: import de.intevation.flys.artifacts.model.Segment;
felix@3124: import de.intevation.flys.artifacts.model.WQCKms;
sascha@1055: import de.intevation.flys.artifacts.model.WQKms;
sascha@2194: import de.intevation.flys.artifacts.model.WW;
sascha@1055: import de.intevation.flys.artifacts.model.WstValueTable;
sascha@1055: import de.intevation.flys.artifacts.model.WstValueTableFactory;
felix@4479: import de.intevation.flys.artifacts.model.extreme.ExtremeResult;
sascha@2126:
felix@4479: import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
sascha@1055: import de.intevation.flys.artifacts.states.LocationDistanceSelect;
sascha@1055:
sascha@3193: import de.intevation.flys.model.DischargeTable;
sascha@2194: import de.intevation.flys.model.FastCrossSectionLine;
ingo@385: import de.intevation.flys.model.Gauge;
ingo@362: import de.intevation.flys.model.River;
ingo@362:
sascha@1055: import de.intevation.flys.utils.DoubleUtil;
ingo@1095: import de.intevation.flys.utils.FLYSUtils;
sascha@655:
sascha@1055: import gnu.trove.TDoubleArrayList;
sascha@451:
sascha@2194: import java.awt.geom.Point2D;
sascha@2194:
sascha@1055: import java.util.Arrays;
ingo@2228: import java.util.Calendar;
sascha@1055: import java.util.Collections;
ingo@2228: import java.util.GregorianCalendar;
sascha@1055: import java.util.List;
sascha@1055: import java.util.Map;
sascha@1055:
sascha@1055: import org.apache.log4j.Logger;
sascha@1055:
felix@2733:
ingo@105: /**
ingo@105: * The default WINFO artifact.
ingo@105: *
ingo@105: * @author Ingo Weinzierl
ingo@105: */
felix@1809: public class WINFOArtifact
felix@1809: extends FLYSArtifact
felix@1981: implements FacetTypes, WaterLineArtifact {
ingo@105:
felix@1029: /** The logger for this class. */
ingo@105: private static Logger logger = Logger.getLogger(WINFOArtifact.class);
ingo@105:
felix@1029: /** The name of the artifact. */
ingo@121: public static final String ARTIFACT_NAME = "winfo";
ingo@121:
ingo@124: /** XPath */
ingo@124: public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
ingo@124:
sascha@1055: /** The default number of steps between the start end end of a selected Q
felix@1115: * range. */
sascha@1055: public static final int DEFAULT_Q_STEPS = 30;
sascha@1055:
felix@1115: /** The default step width between the start end end kilometer. */
sascha@1055: public static final double DEFAULT_KM_STEPS = 0.1;
sascha@1055:
sascha@3556: private static final String [] INACTIVES = new String[] {
sascha@3556: LONGITUDINAL_Q,
sascha@3556: DURATION_Q
sascha@3556: };
sascha@3556:
sascha@3556: static {
sascha@3556: // TODO: Move to configuration.
sascha@3556: FacetActivity.Registry.getInstance().register(
sascha@3556: ARTIFACT_NAME,
sascha@3556: new FacetActivity() {
sascha@3556: @Override
sascha@3558: public Boolean isInitialActive(
sascha@3556: Artifact artifact,
sascha@3556: Facet facet,
sascha@3556: String outputName
sascha@3556: ) {
sascha@3556: String fname = facet.getName();
sascha@3556: if ((fname.equals(COMPUTED_DISCHARGE_MAINVALUES_Q)
felix@4181: || fname.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
felix@4181: || fname.equals(MAINVALUES_Q)
felix@4181: || fname.equals(MAINVALUES_W))
felix@4181: && outputName.equals("computed_discharge_curve"))
felix@4181: {
sascha@3558: return Boolean.FALSE;
sascha@3556: }
sascha@3556: return !StringUtils.contains(fname, INACTIVES);
sascha@3556: }
sascha@3556: });
sascha@3556: }
ingo@121:
ingo@105: /**
ingo@105: * The default constructor.
ingo@105: */
ingo@105: public WINFOArtifact() {
ingo@105: }
ingo@105:
ingo@105:
ingo@121:
ingo@121: /**
ingo@121: * Returns the name of the concrete artifact.
ingo@121: *
ingo@121: * @return the name of the concrete artifact.
ingo@121: */
sascha@3193: @Override
ingo@121: public String getName() {
ingo@121: return ARTIFACT_NAME;
ingo@121: }
ingo@124:
sascha@2624: protected static boolean reportGeneratedWs(
sascha@2624: Calculation report,
sascha@2624: double [] ws
sascha@2624: ) {
sascha@2624: if (ws == null || ws.length < 2) {
sascha@2624: return false;
sascha@2624: }
sascha@2624:
sascha@2624: double lastW = ws[0];
sascha@2624: boolean alreadyReported = false;
sascha@2624:
sascha@2624: for (int i = 1; i < ws.length; ++i) {
sascha@2624: if (Math.abs(lastW - ws[i]) < 1e-5) {
sascha@2624: if (!alreadyReported) {
sascha@2624: alreadyReported = true;
sascha@2624: report.addProblem("more.than.one.q.for.w", ws[i]);
sascha@2624: }
sascha@2624: }
sascha@2624: else {
sascha@2624: alreadyReported = false;
sascha@2624: }
sascha@2624: lastW = ws[i];
sascha@2624: }
sascha@2624:
sascha@2624: return true;
sascha@2624: }
sascha@2624:
ingo@362:
ingo@362: //
ingo@362: // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
ingo@362: //
felix@4479: //
felix@4479: /**
felix@4479: * Returns the data that is computed by a waterlevel computation.
felix@4479: *
felix@4479: * @return an array of data triples that consist of W, Q and Kms.
felix@4479: */
felix@4479: public CalculationResult getWaterlevelData() {
felix@4479: return this.getWaterlevelData(null);
felix@4479: }
ingo@362:
ingo@687: /**
ingo@362: * Returns the data that is computed by a waterlevel computation.
ingo@362: *
ingo@362: * @return an array of data triples that consist of W, Q and Kms.
ingo@362: */
felix@4478: public CalculationResult getWaterlevelData(CallContext context)
ingo@362: {
ingo@362: logger.debug("WINFOArtifact.getWaterlevelData");
ingo@362:
felix@4479: String calculationMode = getDataAsString("calculation_mode");
felix@4479:
felix@4479: if (calculationMode.equals("calc.discharge.longitudinal.section")
sascha@3449: ) {
felix@2765: return getDischargeLongitudinalSectionData();
felix@2765: }
felix@4479: else if (calculationMode.equals("calc.extreme.curve")) {
felix@4505: return (CalculationResult)
felix@4505: this.compute(context, ComputeType.ADVANCE, false);
felix@4479: }
felix@2765:
felix@1102: River river = FLYSUtils.getRiver(this);
ingo@362: if (river == null) {
sascha@2166: return error(new WQKms[0], "no.river.selected");
ingo@362: }
ingo@362:
ingo@362: double[] kms = getKms();
ingo@362: if (kms == null) {
sascha@2166: return error(new WQKms[0], "no.kms.selected");
ingo@362: }
ingo@362:
ingo@447: double[] qs = getQs();
ingo@447: double[] ws = null;
ingo@447: boolean qSel = true;
ingo@447:
sascha@2624: Calculation report = new Calculation();
sascha@2624:
ingo@362: if (qs == null) {
ingo@377: logger.debug("Determine Q values based on a set of W values.");
ingo@447: qSel = false;
ingo@447: ws = getWs();
sascha@2165: double [][] qws = getQsForWs(ws);
sascha@2415: if (qws == null || qws.length == 0) {
sascha@2166: return error(new WQKms[0], "converting.ws.to.qs.failed");
sascha@735: }
sascha@2165: qs = qws[0];
sascha@2165:
sascha@3076: if (reportGeneratedWs(report, qws[1])) {
sascha@2165: ws = qws[1];
sascha@2165: }
ingo@362: }
ingo@362:
sascha@443: WstValueTable wst = WstValueTableFactory.getTable(river);
ingo@362: if (wst == null) {
sascha@2166: return error(new WQKms[0], "no.wst.for.selected.river");
ingo@362: }
ingo@362:
sascha@708:
ingo@1095: double [] range = FLYSUtils.getKmRange(this);
sascha@738: if (range == null) {
sascha@2166: return error(new WQKms[0], "no.range.found");
sascha@738: }
sascha@736:
sascha@738: double refKm;
sascha@738:
sascha@2164: if (isFreeQ() || isFreeW()) {
sascha@738: refKm = range[0];
sascha@738: logger.debug("'free' calculation (km " + refKm + ")");
sascha@738: }
sascha@738: else {
sascha@736: Gauge gauge = river.determineGaugeByPosition(range[0]);
sascha@708: if (gauge == null) {
sascha@736: return error(
sascha@2166: new WQKms[0], "no.gauge.found.for.km");
sascha@708: }
sascha@736:
sascha@736: refKm = gauge.getStation().doubleValue();
sascha@736:
sascha@736: logger.debug(
sascha@736: "reference gauge: " + gauge.getName() + " (km " + refKm + ")");
sascha@708: }
sascha@708:
sascha@2624: return computeWaterlevelData(kms, qs, ws, wst, refKm, report);
ingo@447: }
ingo@447:
felix@1115:
ingo@362: /**
ingo@362: * Computes the data of a waterlevel computation based on the interpolation
ingo@362: * in WstValueTable.
ingo@362: *
ingo@362: * @param kms The kilometer values.
felix@3272: * @param qs The discharge values.
ingo@362: * @param wst The WstValueTable used for the interpolation.
ingo@362: *
ingo@362: * @return an array of data triples that consist of W, Q and Kms.
ingo@362: */
sascha@709: public static CalculationResult computeWaterlevelData(
ingo@686: double [] kms,
ingo@686: double [] qs,
ingo@686: double [] ws,
sascha@635: WstValueTable wst,
sascha@2624: double refKm,
sascha@2624: Calculation report
sascha@635: ) {
ingo@362: logger.info("WINFOArtifact.computeWaterlevelData");
ingo@362:
sascha@738: Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm);
sascha@636:
sascha@2624: if (report != null) {
sascha@2624: calc1.addProblems(report);
sascha@2624: }
sascha@2624:
sascha@709: return calc1.calculate(wst);
ingo@124: }
ingo@385:
ingo@385:
ingo@385: /**
ingo@385: * Returns the data that is computed by a duration curve computation.
ingo@385: *
ingo@385: * @return the data computed by a duration curve computation.
ingo@385: */
sascha@709: public CalculationResult getDurationCurveData() {
ingo@385: logger.debug("WINFOArtifact.getDurationCurveData");
ingo@385:
felix@1102: River r = FLYSUtils.getRiver(this);
ingo@385:
ingo@385: if (r == null) {
sascha@2166: return error(null, "no.river.selected");
ingo@385: }
ingo@385:
ingo@385: Gauge g = getGauge();
ingo@385:
ingo@385: if (g == null) {
sascha@2166: return error(null, "no.gauge.selected");
ingo@385: }
ingo@385:
ingo@1095: double[] locations = FLYSUtils.getLocations(this);
ingo@385:
ingo@385: if (locations == null) {
sascha@2166: return error(null, "no.locations.selected");
ingo@385: }
ingo@385:
sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r);
ingo@385: if (wst == null) {
sascha@2166: return error(null, "no.wst.for.river");
ingo@385: }
ingo@385:
ingo@385: return computeDurationCurveData(g, wst, locations[0]);
ingo@385: }
ingo@385:
ingo@385:
ingo@385: /**
ingo@385: * Computes the data used to create duration curves.
ingo@385: *
ingo@385: * @param gauge The selected gauge.
ingo@385: * @param location The selected location.
ingo@385: *
ingo@385: * @return the computed data.
ingo@385: */
sascha@709: public static CalculationResult computeDurationCurveData(
felix@1974: Gauge gauge,
felix@1974: WstValueTable wst,
felix@1974: double location)
ingo@385: {
ingo@385: logger.info("WINFOArtifact.computeDurationCurveData");
ingo@385:
sascha@3647: Object[] obj = gauge.fetchDurationCurveData();
ingo@385:
ingo@385: int[] days = (int[]) obj[0];
ingo@385: double[] qs = (double[]) obj[1];
ingo@385:
ingo@686: Calculation3 calculation = new Calculation3(location, days, qs);
ingo@385:
ingo@686: return calculation.calculate(wst);
ingo@385: }
ingo@393:
ingo@393:
ingo@393: /**
ingo@393: * Returns the data that is computed by a discharge curve computation.
ingo@393: *
ingo@393: * @return the data computed by a discharge curve computation.
ingo@393: */
sascha@709: public CalculationResult getComputedDischargeCurveData()
ingo@393: throws NullPointerException
ingo@393: {
ingo@393: logger.debug("WINFOArtifact.getComputedDischargeCurveData");
ingo@393:
felix@1102: River r = FLYSUtils.getRiver(this);
ingo@393:
ingo@393: if (r == null) {
sascha@2166: return error(new WQKms[0], "no.river.selected");
ingo@393: }
ingo@393:
ingo@1095: double[] locations = FLYSUtils.getLocations(this);
ingo@393:
ingo@393: if (locations == null) {
sascha@2166: return error(new WQKms[0], "no.locations.selected");
ingo@393: }
ingo@393:
sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r);
ingo@393: if (wst == null) {
sascha@2166: return error(new WQKms[0], "no.wst.for.river");
ingo@393: }
ingo@393:
sascha@709: return computeDischargeCurveData(wst, locations[0]);
ingo@456: }
ingo@456:
ingo@456:
ingo@456: /**
ingo@393: * Computes the data used to create computed discharge curves.
ingo@393: *
felix@3272: * @param wst The WstValueTable that is used for the interpolation (river-
felix@3272: * bound).
ingo@393: * @param location The location where the computation should be based on.
ingo@393: *
ingo@393: * @return an object that contains tuples of W/Q values at the specified
ingo@393: * location.
ingo@393: */
sascha@709: public static CalculationResult computeDischargeCurveData(
ingo@393: WstValueTable wst,
ingo@393: double location)
ingo@393: {
ingo@393: logger.info("WINFOArtifact.computeDischargeCurveData");
ingo@393:
ingo@686: Calculation2 calculation = new Calculation2(location);
ingo@393:
sascha@709: return calculation.calculate(wst);
sascha@709: }
ingo@393:
felix@3272:
felix@3272: /** Create CalculationResult with data and message. */
sascha@709: protected static final CalculationResult error(Object data, String msg) {
sascha@709: return new CalculationResult(data, new Calculation(msg));
ingo@393: }
ingo@402:
felix@1148:
ingo@402: /**
ingo@402: * Returns the data computed by the discharge longitudinal section
ingo@402: * computation.
ingo@402: *
ingo@402: * @return an array of WQKms object - one object for each given Q value.
ingo@402: */
sascha@709: public CalculationResult getDischargeLongitudinalSectionData() {
sascha@451:
ingo@402: logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
ingo@402:
felix@1102: River river = FLYSUtils.getRiver(this);
ingo@402: if (river == null) {
sascha@709: logger.debug("No river selected.");
sascha@2166: return error(new WQKms[0], "no.river.selected");
ingo@402: }
ingo@402:
sascha@655: WstValueTable table = WstValueTableFactory.getTable(river);
sascha@655: if (table == null) {
sascha@709: logger.debug("No wst found for selected river.");
sascha@2166: return error(new WQKms[0], "no.wst.for.river");
ingo@402: }
ingo@402:
sascha@655: List segments = getSegments();
sascha@637:
sascha@655: if (segments == null) {
sascha@709: logger.debug("Cannot create segments.");
sascha@2166: return error(new WQKms[0], "cannot.create.segments");
sascha@451: }
ingo@450:
sascha@655: double [] range = getFromToStep();
sascha@451:
sascha@655: if (range == null) {
sascha@709: logger.debug("Cannot figure out range.");
sascha@2166: return error(new WQKms[0], "no.range.found");
sascha@451: }
sascha@451:
sascha@655: Calculation4 calc4 = new Calculation4(segments, river, isQ());
sascha@455:
sascha@709: return calc4.calculate(table, range[0], range[1], range[2]);
ingo@450: }
sascha@1055:
felix@2229:
sascha@2194: /**
sascha@2194: * Returns the data that is computed by a reference curve computation.
sascha@2194: *
sascha@2194: * @return the data computed by a reference curve computation.
sascha@2194: */
sascha@2326: public CalculationResult getReferenceCurveData(CallContext context) {
sascha@2194:
sascha@2194: Double startKm = getReferenceStartKm();
sascha@2194:
sascha@2194: if (startKm == null) {
sascha@2194: return error(new WW[0], "no.reference.start.km");
sascha@2194: }
sascha@2194:
sascha@2194: double [] endKms = getReferenceEndKms();
sascha@2194:
sascha@2194: if (endKms == null || endKms.length == 0) {
sascha@2194: return error(new WW[0], "no.reference.end.kms");
sascha@2194: }
sascha@2194:
sascha@2194: Calculation5 calc5 = new Calculation5(startKm, endKms);
sascha@2194:
sascha@2194: River r = FLYSUtils.getRiver(this);
sascha@2194: if (r == null) {
sascha@2194: return error(new WW[0], "no.river.found");
sascha@2194: }
sascha@2194:
sascha@2194: WstValueTable wst = WstValueTableFactory.getTable(r);
sascha@2194: if (wst == null) {
sascha@2194: return error(new WW[0], "no.wst.for.river");
sascha@2194: }
sascha@2194:
felix@2257: Map kms2gaugeDatums = r.queryGaugeDatumsKMs();
sascha@2256:
sascha@2326: return calc5.calculate(wst, kms2gaugeDatums, context);
sascha@2194: }
sascha@2194:
felix@2229:
felix@2251: /** Get reference (start) km. */
felix@2754: public Double getReferenceStartKm() {
felix@2308: StateData sd = getData("reference_startpoint");
sascha@2194:
sascha@2194: if (sd == null) {
sascha@2194: logger.warn("no reference start given.");
sascha@2194: return null;
sascha@2194: }
sascha@2194:
felix@2251: logger.debug("Reference start km given: " + sd.getValue());
felix@2251:
felix@2251: String input = (String) sd.getValue();
sascha@2194:
sascha@2726: if (input == null || (input = input.trim()).length() == 0) {
sascha@2194: logger.warn("reference start string is empty.");
sascha@2194: return null;
sascha@2194: }
sascha@2194:
sascha@2194: try {
sascha@2194: return Double.valueOf(input);
sascha@2194: }
sascha@2194: catch (NumberFormatException nfe) {
sascha@2194: logger.warn("reference start string is not numeric.");
sascha@2194: }
sascha@2194:
sascha@2194: return null;
sascha@2194: }
sascha@2194:
felix@2251:
felix@2754: /**
felix@2754: * Get end kms for reference curve (null if none).
felix@2754: */
felix@2754: public double [] getReferenceEndKms() {
felix@2308: StateData sd = getData("reference_endpoint");
sascha@2194:
sascha@2194: if (sd == null) {
sascha@2194: logger.warn("no reference end given.");
sascha@2194: return null;
sascha@2194: }
felix@2251: else {
felix@2251: logger.debug("Reference end km : " + sd.getValue());
felix@2251: }
sascha@2194:
felix@2251: String input = (String) sd.getValue();
sascha@2194:
sascha@2415: if (input == null || (input = input.trim()).length() == 0) {
sascha@2194: logger.warn("reference end string is empty.");
sascha@2194: return null;
sascha@2194: }
sascha@2194:
sascha@2194: TDoubleArrayList endKms = new TDoubleArrayList();
sascha@2194:
sascha@2194: for (String part: input.split("\\s+")) {
sascha@2194: try {
sascha@2309: double km = Double.parseDouble(part);
sascha@2309: if (!endKms.contains(km)) {
sascha@2309: endKms.add(km);
sascha@2309: }
sascha@2194: }
sascha@2194: catch (NumberFormatException nfe) {
sascha@2194: logger.warn("reference end string is not numeric.");
sascha@2194: }
sascha@2194: }
sascha@2194:
sascha@2194: return endKms.toNativeArray();
sascha@2194: }
felix@1137:
ingo@2215:
ingo@2215: public CalculationResult getHistoricalDischargeData() {
ingo@2219: Gauge gauge = FLYSUtils.getReferenceGauge(this);
ingo@2219: String rawTimerange = getDataAsString("year_range");
ingo@2219: String rawValues = getDataAsString("historical_values");
ingo@2219: int mode = getDataAsInteger("historical_mode");
ingo@2219:
raimund@2600: long[] timerange = FLYSUtils.longArrayFromString(rawTimerange);
ingo@2219: double[] values = FLYSUtils.doubleArrayFromString(rawValues);
ingo@2219:
raimund@2600: Calendar start = new GregorianCalendar();
raimund@2600: start.setTimeInMillis(timerange[0]);
raimund@2600: Calendar end = new GregorianCalendar();
raimund@2600: end.setTimeInMillis(timerange[1]);
ingo@2228:
ingo@2228: Calculation6 calc = new Calculation6(
ingo@2228: mode,
ingo@2228: new long[] { start.getTimeInMillis(), end.getTimeInMillis() },
ingo@2228: values);
ingo@2228:
ingo@2228: return calc.calculate(gauge);
ingo@2215: }
ingo@2215:
ingo@2215:
sascha@1055: public List getSegments() {
sascha@1055: StateData wqValues = getData("wq_values");
sascha@1055: if (wqValues == null) {
sascha@1055: logger.warn("no wq_values given");
ingo@3785: return Collections.emptyList();
sascha@1055: }
felix@3444: String input = (String) wqValues.getValue();
sascha@1055: if (input == null || (input = input.trim()).length() == 0) {
sascha@1055: logger.warn("wq_values are empty");
ingo@3785: return Collections.emptyList();
sascha@1055: }
sascha@1055: return Segment.parseSegments(input);
sascha@1055: }
sascha@1055:
felix@1122:
felix@1122: /**
felix@3124: * Get corrected waterline against surface/profile.
felix@3124: */
felix@3124: public Lines.LineData waterLineC(int idx, FastCrossSectionLine csl) {
felix@3124: List points = csl.getPoints();
felix@3124:
felix@3124: WQKms[] wqckms = (WQKms[])
felix@3124: getDischargeLongitudinalSectionData().getData();
felix@3124:
felix@3124: // Find index of km.
felix@3124: double wishKM = csl.getKm();
felix@3124:
felix@3124: // Find W/C at km, linear naive approach.
felix@3124: WQCKms triple = (WQCKms) wqckms[idx-1];
felix@3124:
felix@3124: int old_idx = 0;
felix@3124:
felix@3124: if (triple.size() == 0) {
felix@3124: logger.warn("Calculation of c/waterline is empty.");
felix@3124: return Lines.createWaterLines(points, 0.0f);
felix@3124: }
felix@3124:
felix@3124: // Linear seach in WQKms for closest km.
felix@3124: double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
felix@3124: double last_c = triple.getC(0);
felix@3124:
felix@3124: for (int i = 0, T = triple.size(); i < T; i++) {
felix@3124: double diff = Math.abs(wishKM - triple.getKm(i));
felix@3124: if (diff > old_dist_wish) {
felix@3124: break;
felix@3124: }
felix@3124: last_c = triple.getC(i);
felix@3124: old_dist_wish = diff;
felix@3124: }
felix@3124:
felix@3124: return Lines.createWaterLines(points, last_c);
felix@3124: }
felix@3124:
felix@3124:
felix@3124: /**
felix@1122: * Get points of line describing the surface of water at cross section.
felix@1122: *
felix@3123: * @param idx Index for getWaterlevelData.
felix@3123: * @param csl The profile/surface to fill with water.
felix@3272: * @param nextIgnored Ignored in this implementation of WaterLineArtifact.
felix@3272: * @param prevIgnored Ignored in this implementation of WaterLineArtifact.
felix@3123: *
felix@1122: * @return an array holding coordinates of points of surface of water (
felix@1122: * in the form {{x1, x2} {y1, y2}} ).
felix@1122: */
felix@2652: @Override
felix@3272: public Lines.LineData getWaterLines(int idx, FastCrossSectionLine csl,
felix@4479: double nextIgnored, double prevIgnored, CallContext context) {
felix@1975: logger.debug("getWaterLines(" + idx + ")");
felix@1975:
sascha@2120: List points = csl.getPoints();
felix@1802:
felix@1139: // Need W at km
felix@4479: Object waterlevelResult = getWaterlevelData(context).getData();
felix@4479: WQKms [] wqkms;
felix@4479:
felix@4479: if (waterlevelResult instanceof ExtremeResult) {
felix@4479: wqkms = ((ExtremeResult) waterlevelResult).getWQKms();
felix@4479: }
felix@4479: else {
felix@4479: wqkms = (WQKms[]) waterlevelResult;
felix@4479: }
felix@4479:
felix@1139: if (wqkms.length == 0) {
felix@1139: logger.error("No WQKms found.");
sascha@1651: return Lines.createWaterLines(points, 0.0f);
felix@1139: }
felix@1139:
felix@3123: if (wqkms.length <= idx) {
felix@1802: logger.error("getWaterLines() requested index ("
felix@1802: + idx + " not found.");
felix@3124: return waterLineC(idx, csl);
sascha@1651: }
felix@1802:
felix@4479: // Find W at km, linear naive approach.
felix@4479: WQKms triple = wqkms[idx];
felix@4479:
felix@3124: // Find index of km.
felix@3124: double wishKM = csl.getKm();
felix@3124:
sascha@1651: if (triple.size() == 0) {
sascha@1651: logger.warn("Calculation of waterline is empty.");
sascha@1651: return Lines.createWaterLines(points, 0.0f);
sascha@1651: }
sascha@1651:
felix@3892: // Early abort if we would need to extrapolate.
felix@3892: int T = triple.size();
felix@3892: double max_km = triple.getKm(T-1), min_km = triple.getKm(0);
felix@3892: if (wishKM < min_km || wishKM > max_km) {
felix@3892: // TODO Does this have to be done in the other WaterlineArtifact
felix@3892: // implementations, too?
felix@3892: logger.warn("Will not extrapolate waterlevels.");
felix@3892: return Lines.createWaterLines(points, 0.0f);
felix@3892: }
felix@3892:
felix@3892: int old_idx = 0;
felix@3892:
sascha@1651: // Linear seach in WQKms for closest km.
sascha@1651: double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
sascha@1651: double last_w = triple.getW(0);
sascha@1651:
felix@3892: for (int i = 0; i < T; i++) {
sascha@1651: double diff = Math.abs(wishKM - triple.getKm(i));
sascha@1651: if (diff > old_dist_wish) {
sascha@1651: break;
felix@1139: }
sascha@1651: last_w = triple.getW(i);
sascha@1651: old_dist_wish = diff;
felix@1139: }
felix@3123:
sascha@1651: return Lines.createWaterLines(points, last_w);
felix@1122: }
felix@1122:
felix@1122:
felix@1122: /**
sascha@1055: * Returns the Qs for a number of Ws. This method makes use of
sascha@1055: * DischargeTables.getQForW().
sascha@1055: *
sascha@1055: * @param ws An array of W values.
sascha@1055: *
sascha@1055: * @return an array of Q values.
sascha@1055: */
sascha@2165: public double [][] getQsForWs(double[] ws) {
sascha@1055:
sascha@2415: if (ws == null) {
sascha@2415: logger.error("getQsForWs: ws == null");
sascha@2415: return null;
sascha@2415: }
sascha@2415:
sascha@1055: boolean debug = logger.isDebugEnabled();
sascha@1055:
sascha@1055: if (debug) {
sascha@1055: logger.debug("FLYSArtifact.getQsForWs");
sascha@1055: }
sascha@1055:
felix@1102: River r = FLYSUtils.getRiver(this);
sascha@1055: if (r == null) {
sascha@1055: logger.warn("no river found");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
ingo@1095: double [] range = FLYSUtils.getKmRange(this);
sascha@1055: if (range == null) {
sascha@1055: logger.warn("no ranges found");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@2164: if (isFreeW()) {
sascha@2415: logger.debug("Bezugslinienverfahren I: W auf freier Strecke");
sascha@2165: // The simple case of the "Bezugslinienverfahren"
sascha@2165: // "W auf freier Strecke".
sascha@2164: WstValueTable wst = WstValueTableFactory.getTable(r);
sascha@2164: if (wst == null) {
sascha@2164: logger.warn("no wst value table found");
sascha@2164: return null;
sascha@2164: }
sascha@2164: double km = range[0];
sascha@2164:
sascha@2165: TDoubleArrayList outQs = new TDoubleArrayList(ws.length);
sascha@2165: TDoubleArrayList outWs = new TDoubleArrayList(ws.length);
sascha@2165:
sascha@2165: boolean generatedWs = false;
sascha@2164:
sascha@2164: for (int i = 0; i < ws.length; ++i) {
sascha@2415: double w = ws[i];
sascha@2415: if (debug) {
sascha@2415: logger.debug("getQsForWs: lookup Q for W: " + w);
sascha@2415: }
sascha@2165: // There could bemore than one Q per W.
sascha@2415: double [] qs = wst.findQsForW(km, w);
sascha@2165: for (int j = 0; j < qs.length; ++j) {
sascha@2165: outWs.add(ws[i]);
sascha@2165: outQs.add(qs[j]);
sascha@2164: }
sascha@2165: generatedWs |= qs.length != 1;
sascha@2164: }
sascha@2164:
sascha@2415: if (debug) {
sascha@2415: logger.debug("getQsForWs: number of Qs: " + outQs.size());
sascha@2415: }
sascha@2415:
sascha@3076: return new double [][] {
sascha@3076: outQs.toNativeArray(),
sascha@2165: generatedWs ? outWs.toNativeArray() : null };
sascha@2164: }
sascha@2164:
sascha@1055: if (debug) {
sascha@1055: logger.debug("range: " + Arrays.toString(range));
sascha@1055: }
sascha@1055:
sascha@1055: Gauge g = r.determineGaugeByPosition(range[0]);
sascha@1055: if (g == null) {
sascha@1055: logger.warn("no gauge found for km: " + range[0]);
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: if (debug) {
sascha@1055: logger.debug("convert w->q with gauge '" + g.getName() + "'");
sascha@1055: }
sascha@1055:
sascha@2418: DischargeTable dt = g.fetchMasterDischargeTable();
sascha@1055:
sascha@2418: if (dt == null) {
sascha@2418: logger.warn("No master discharge table found for gauge '"
sascha@2418: + g.getName() + "'");
sascha@2418: return null;
sascha@2418: }
sascha@2418:
sascha@2418: double [][] values = DischargeTables.loadDischargeTableValues(dt, 1);
sascha@1055:
sascha@2415: TDoubleArrayList wsOut = new TDoubleArrayList(ws.length);
sascha@2415: TDoubleArrayList qsOut = new TDoubleArrayList(ws.length);
sascha@2415:
sascha@2418: boolean generatedWs = false;
sascha@2418:
sascha@1055: for (int i = 0; i < ws.length; i++) {
sascha@2415: if (Double.isNaN(ws[i])) {
sascha@2415: logger.warn("W is NaN: ignored");
sascha@2415: continue;
sascha@2415: }
sascha@2418: double w = ws[i] / 100d;
sascha@2418: double [] qs = DischargeTables.getQsForW(values, w);
sascha@2418:
sascha@2418: if (qs.length == 0) {
sascha@2418: logger.warn("No Qs found for W = " + ws[i]);
sascha@2415: }
sascha@2418: else {
sascha@2418: for (double q: qs) {
sascha@2418: wsOut.add(ws[i]);
sascha@2607: qsOut.add(q * 100d);
sascha@2418: }
sascha@1055: }
sascha@2418: generatedWs |= qs.length != 1;
sascha@1055: }
sascha@1055:
sascha@2415: return new double [][] {
sascha@2415: qsOut.toNativeArray(),
sascha@2418: generatedWs ? wsOut.toNativeArray() : null
sascha@2415: };
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Determines the selected mode of distance/range input.
sascha@1055: *
sascha@1055: * @return true, if the range mode is selected otherwise false.
sascha@1055: */
sascha@1055: public boolean isRange() {
sascha@1055: StateData mode = getData("ld_mode");
sascha@1055:
sascha@1055: if (mode == null) {
sascha@1055: logger.warn("No mode location/range chosen. Defaults to range.");
sascha@1055: return true;
sascha@1055: }
sascha@1055:
sascha@1055: String value = (String) mode.getValue();
sascha@1055:
sascha@1055: return value.equals("distance");
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the selected distance based on a given range (from, to).
sascha@1055: *
sascha@1055: * @param dFrom The StateData that contains the lower value.
sascha@1055: * @param dTo The StateData that contains the upper value.
sascha@1055: *
sascha@1055: * @return the selected distance.
sascha@1055: */
sascha@1055: protected double[] getDistanceByRange(StateData dFrom, StateData dTo) {
sascha@1055: double from = Double.parseDouble((String) dFrom.getValue());
sascha@1055: double to = Double.parseDouble((String) dTo.getValue());
sascha@1055:
sascha@1055: return new double[] { from, to };
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the selected Kms.
sascha@1055: *
sascha@1055: * @param distance An 2dim array with [lower, upper] values.
sascha@1055: *
sascha@1055: * @return the selected Kms.
sascha@1055: */
sascha@1055: public double[] getKms(double[] distance) {
sascha@1055: StateData dStep = getData("ld_step");
sascha@1055:
sascha@1055: if (dStep == null) {
sascha@1055: logger.warn("No step width given. Cannot compute Kms.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: double step = Double.parseDouble((String) dStep.getValue());
sascha@1055:
sascha@1055: // transform step from 'm' into 'km'
sascha@1055: step = step / 1000;
sascha@1055:
sascha@1055: if (step == 0d) {
sascha@1055: step = DEFAULT_KM_STEPS;
sascha@1055: }
sascha@1055:
sascha@1055: return DoubleUtil.explode(distance[0], distance[1], step);
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the selected Kms.
sascha@1055: *
sascha@1055: * @return the selected kms.
sascha@1055: */
sascha@1055: public double[] getKms() {
sascha@1055: if (isRange()) {
ingo@1095: double[] distance = FLYSUtils.getKmRange(this);
sascha@1055: return getKms(distance);
sascha@1055:
sascha@1055: }
sascha@1055: else {
sascha@1055: return LocationDistanceSelect.getLocations(this);
sascha@1055: }
sascha@1055: }
sascha@1055:
felix@1137:
sascha@1055: public double [] getFromToStep() {
sascha@1055: if (!isRange()) {
sascha@1055: return null;
sascha@1055: }
ingo@1095: double [] fromTo = FLYSUtils.getKmRange(this);
sascha@1055:
sascha@1055: if (fromTo == null) {
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: StateData dStep = getData("ld_step");
sascha@1055: if (dStep == null) {
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: double [] result = new double[3];
sascha@1055: result[0] = fromTo[0];
sascha@1055: result[1] = fromTo[1];
sascha@1055:
sascha@1055: try {
sascha@1055: String step = (String)dStep.getValue();
sascha@1055: result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d);
sascha@1055: }
sascha@1055: catch (NumberFormatException nfe) {
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: return result;
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the gauge based on the current distance and river.
sascha@1055: *
sascha@1055: * @return the gauge.
sascha@1055: */
sascha@1055: public Gauge getGauge() {
ingo@2045: return FLYSUtils.getGauge(this);
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * This method returns the Q values.
sascha@1055: *
sascha@1055: * @return the selected Q values or null, if no Q values are selected.
sascha@1055: */
sascha@1055: public double[] getQs() {
ingo@2423: StateData dMode = getData("wq_isq");
ingo@2423: StateData dSelection = getData("wq_isrange");
sascha@1055:
ingo@2422: boolean isRange = dSelection != null
ingo@2422: ? Boolean.valueOf((String)dSelection.getValue())
ingo@2422: : false;
sascha@1055:
ingo@2422: if (isQ()) {
ingo@2422: if (!isRange) {
sascha@1055: return getSingleWQValues();
sascha@1055: }
sascha@1055: else {
sascha@1055: return getWQTriple();
sascha@1055: }
sascha@1055: }
sascha@1055: else {
sascha@1055: logger.warn("You try to get Qs, but W has been inserted.");
sascha@1055: return null;
sascha@1055: }
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: public boolean isQ() {
ingo@2423: StateData mode = getData("wq_isq");
ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null;
ingo@2422: return value != null ? Boolean.valueOf(value) : false;
ingo@2422: }
ingo@2422:
ingo@2422: public boolean isW() {
ingo@2423: StateData mode = getData("wq_isq");
ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null;
ingo@2422: return value != null ? !Boolean.valueOf(value) : false;
sascha@1055: }
sascha@1055:
sascha@2164: public boolean isFreeW() {
ingo@2422: if(!isW()) {
ingo@2422: return false;
ingo@2422: }
ingo@2423: StateData mode = getData("wq_isfree");
ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null;
ingo@2422:
ingo@2422: return value != null ? Boolean.valueOf(value) : false;
sascha@2164: }
sascha@2164:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns true, if the parameter is set to compute data on a free range.
sascha@1055: * Otherwise it returns false, which tells the calculation that it is bound
sascha@1055: * to a gauge.
sascha@1055: *
sascha@1055: * @return true, if the calculation should compute on a free range otherwise
sascha@1055: * false and the calculation is bound to a gauge.
sascha@1055: */
sascha@1055: public boolean isFreeQ() {
ingo@2422: if(!isQ()) {
ingo@2422: return false;
ingo@2422: }
ingo@2423: StateData mode = getData("wq_isfree");
felix@1115: String value = (mode != null) ? (String) mode.getValue() : null;
sascha@1055:
sascha@1055: logger.debug("isFreeQ: " + value);
sascha@1055:
sascha@2164: return value != null && Boolean.valueOf(value);
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the Q values based on a specified kilometer range.
sascha@1055: *
sascha@1055: * @param range A 2dim array with lower and upper kilometer range.
sascha@1055: *
sascha@1055: * @return an array of Q values.
sascha@1055: */
sascha@1055: public double[] getQs(double[] range) {
ingo@2423: StateData dMode = getData("wq_isq");
sascha@1055:
ingo@2422: if (isQ()) {
sascha@1055: return getWQForDist(range);
sascha@1055: }
sascha@1055:
sascha@1055: logger.warn("You try to get Qs, but Ws has been inserted.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns the W values based on a specified kilometer range.
sascha@1055: *
sascha@1055: * @param range A 2dim array with lower and upper kilometer range.
sascha@1055: *
sascha@1055: * @return an array of W values.
sascha@1055: */
sascha@1055: public double[] getWs(double[] range) {
ingo@2422: if (isW()) {
sascha@1055: return getWQForDist(range);
sascha@1055: }
sascha@1055:
sascha@1055: logger.warn("You try to get Ws, but Qs has been inserted.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * This method returns the W values.
sascha@1055: *
sascha@1055: * @return the selected W values or null, if no W values are selected.
sascha@1055: */
sascha@1055: public double[] getWs() {
ingo@2422: if (isW()) {
felix@4652: StateData dSingle = getData("wq_single");
sascha@1055: if (dSingle != null) {
sascha@1055: return getSingleWQValues();
sascha@1055: }
sascha@1055: else {
sascha@1055: return getWQTriple();
sascha@1055: }
sascha@1055: }
sascha@1055: else {
ingo@2422: logger.warn("You try to get Ws, but Q has been inserted.");
sascha@1055: return null;
sascha@1055: }
sascha@1055: }
sascha@1055:
sascha@1055: /**
sascha@1055: * This method returns the given W or Q values for a specific range
sascha@1055: * (inserted in the WQ input panel for discharge longitudinal sections).
sascha@1055: *
sascha@1055: * @param dist A 2dim array with lower und upper kilometer values.
sascha@1055: *
sascha@1055: * @return an array of W or Q values.
sascha@1055: */
sascha@1055: protected double[] getWQForDist(double[] dist) {
sascha@1055: logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
sascha@1055: StateData data = getData("wq_values");
sascha@1055:
sascha@1055: if (data == null) {
sascha@1055: logger.warn("Missing wq values!");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: String dataString = (String) data.getValue();
sascha@1055: String[] ranges = dataString.split(":");
sascha@1055:
sascha@1055: for (String range: ranges) {
sascha@1055: String[] parts = range.split(";");
sascha@1055:
sascha@1055: double lower = Double.parseDouble(parts[0]);
sascha@1055: double upper = Double.parseDouble(parts[1]);
sascha@1055:
sascha@1055: if (lower <= dist[0] && upper >= dist[1]) {
sascha@1055: String[] values = parts[2].split(",");
sascha@1055:
sascha@1055: int num = values.length;
sascha@1055: double[] res = new double[num];
sascha@1055:
sascha@1055: for (int i = 0; i < num; i++) {
sascha@1055: try {
sascha@1055: res[i] = Double.parseDouble(values[i]);
sascha@1055: }
sascha@1055: catch (NumberFormatException nfe) {
sascha@1055: logger.warn(nfe, nfe);
sascha@1055: }
sascha@1055: }
sascha@1055:
sascha@1055: return res;
sascha@1055: }
sascha@1055: }
sascha@1055:
sascha@1055: logger.warn("Specified range for WQ not found!");
sascha@1055:
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * This method returns an array of inserted WQ triples that consist of from,
sascha@1055: * to and the step width.
sascha@1055: *
sascha@1055: * @return an array of from, to and step width.
sascha@1055: */
sascha@1055: protected double[] getWQTriple() {
sascha@1055: StateData dFrom = getData("wq_from");
sascha@1055: StateData dTo = getData("wq_to");
sascha@1055:
sascha@1055: if (dFrom == null || dTo == null) {
sascha@1055: logger.warn("Missing start or end value for range.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: double from = Double.parseDouble((String) dFrom.getValue());
sascha@1055: double to = Double.parseDouble((String) dTo.getValue());
sascha@1055:
sascha@1055: StateData dStep = getData("wq_step");
sascha@1055:
sascha@1055: if (dStep == null) {
sascha@1055: logger.warn("No step width given. Cannot compute Qs.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: double step = Double.parseDouble((String) dStep.getValue());
sascha@1055:
sascha@1055: // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
sascha@1055: // width. Maybe, we should round the value to a number of digits.
sascha@1055: if (step == 0d) {
sascha@1055: double diff = to - from;
sascha@1055: step = diff / DEFAULT_Q_STEPS;
sascha@1055: }
sascha@1055:
sascha@1055: return DoubleUtil.explode(from, to, step);
sascha@1055: }
sascha@1055:
sascha@1055:
sascha@1055: /**
sascha@1055: * Returns an array of inserted WQ double values stored as whitespace
sascha@1055: * separated list.
sascha@1055: *
sascha@1055: * @return an array of W or Q values.
sascha@1055: */
sascha@1055: protected double[] getSingleWQValues() {
sascha@1055: StateData dSingle = getData("wq_single");
sascha@1055:
sascha@1055: if (dSingle == null) {
sascha@1055: logger.warn("Cannot determine single WQ values. No data given.");
sascha@1055: return null;
sascha@1055: }
sascha@1055:
sascha@1055: String tmp = (String) dSingle.getValue();
sascha@1055: String[] strValues = tmp.split(" ");
sascha@1055:
sascha@1055: TDoubleArrayList values = new TDoubleArrayList();
sascha@1055:
sascha@1055: for (String strValue: strValues) {
sascha@1055: try {
sascha@1055: values.add(Double.parseDouble(strValue));
sascha@1055: }
sascha@1055: catch (NumberFormatException nfe) {
sascha@1055: logger.warn(nfe, nfe);
sascha@1055: }
sascha@1055: }
sascha@1055:
sascha@1055: values.sort();
sascha@1055:
sascha@1055: return values.toNativeArray();
sascha@1055: }
felix@1762:
raimund@2617: /**
felix@3272: * Returns the WstValueTable of current river.
raimund@2617: */
raimund@2617: public WstValueTable getWstValueTable() {
raimund@2617: River r = FLYSUtils.getRiver(this);
raimund@2617: return WstValueTableFactory.getTable(r);
raimund@2617: }
ingo@105: }
ingo@105: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :