teichmann@8033: /* Copyright (C) 2014 by Bundesanstalt für Gewässerkunde teichmann@8033: * Software engineering by Intevation GmbH teichmann@8033: * teichmann@8033: * This file is Free Software under the GNU AGPL (>=v3) teichmann@8033: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@8033: * documentation coming with Dive4Elements River for details. teichmann@8033: */ teichmann@8033: package org.dive4elements.river.artifacts.model.minfo; teichmann@8033: teichmann@8033: import java.io.Serializable; teichmann@8033: import java.util.ArrayList; teichmann@8034: import java.util.Collection; teichmann@8036: import java.util.Collections; teichmann@8044: import java.util.Comparator; teichmann@8033: import java.util.Date; teichmann@8033: import java.util.List; teichmann@8033: import java.util.TreeMap; teichmann@8123: import java.util.TreeSet; teichmann@8033: andre@8199: import org.apache.log4j.Logger; andre@8199: teichmann@8201: import org.dive4elements.river.backend.utils.EpsilonComparator; teichmann@8033: teichmann@8033: public class SedimentLoadData implements Serializable teichmann@8033: { teichmann@8202: private static Logger log = Logger.getLogger(SedimentLoadData.class); andre@8199: tom@8177: public static final int GF_UNKNOWN = -1; tom@8177: public static final int GF_COARSE = 0; tom@8177: public static final int GF_FINE_MIDDLE = 1; tom@8177: public static final int GF_SAND = 2; tom@8177: public static final int GF_SUSP_SAND = 3; tom@8177: public static final int GF_SUSP_SAND_BED = 4; tom@8177: public static final int GF_SUSP_SEDIMENT = 5; tom@8177: public static final int GF_TOTAL = 6; tom@8177: public static final int GF_BED_LOAD = 7; tom@8177: public static final int GF_BED_LOAD_SUSP_SAND = 8; tom@8293: public static final int GF_SUSP_LOAD = 9; tom@8293: public static final int GF_MAX = 9; teichmann@8033: teichmann@8208: public static final String [] GF_NAMES = { teichmann@8208: "coarse", teichmann@8208: "fine_middle", teichmann@8208: "sand", teichmann@8208: "susp_sand", teichmann@8208: "susp_sand_bed", teichmann@8208: "suspended_sediment", teichmann@8208: "total", teichmann@8208: "bed_load", tom@8293: "bed_load_susp_sand", tom@8293: "suspended_load" teichmann@8208: }; teichmann@8208: teichmann@8048: public static final int [] MEASUREMENT_STATION_GF = { tom@8177: /* GF_COARSE */ Station.BED_LOAD, tom@8177: /* GF_FINE_MIDDLE */ Station.BED_LOAD, tom@8177: /* GF_SAND */ Station.BED_LOAD, tom@8177: /* GF_SUSP_SAND */ Station.BED_LOAD, tom@8177: /* GF_SUSP_SAND_BED */ Station.BED_LOAD, tom@8177: /* GF_SUSP_SEDIMENT */ Station.SUSPENDED, tom@8177: /* GF_TOTAL */ Station.BED_LOAD|Station.SUSPENDED, tom@8177: /* GF_BED_LOAD */ Station.BED_LOAD, tom@8293: /* GF_BED_LOAD_SUSP_SAND */ Station.BED_LOAD, tom@8293: /* GF_SUSP_LOAD */ Station.SUSPENDED teichmann@8048: }; teichmann@8048: teichmann@8209: public static final int measurementStationType(int grainFraction) { teichmann@8209: return grainFraction < 0 || grainFraction >= MEASUREMENT_STATION_GF.length teichmann@8209: ? Station.UNKNOWN teichmann@8209: : MEASUREMENT_STATION_GF[grainFraction]; teichmann@8209: } teichmann@8209: teichmann@8208: public static final int grainFractionIndex(String name) { teichmann@8208: for (int i = 0; i < GF_NAMES.length; ++i) { teichmann@8208: if (GF_NAMES[i].equals(name)) { teichmann@8208: return i; teichmann@8208: } teichmann@8208: } teichmann@8208: return GF_UNKNOWN; teichmann@8048: } teichmann@8048: teichmann@8208: public static final String grainFractionName(int index) { teichmann@8208: return index >= 0 && index < GF_NAMES.length teichmann@8208: ? GF_NAMES[index] teichmann@8208: : "unknown"; teichmann@8033: } teichmann@8033: teichmann@8208: teichmann@8036: public interface Visitor { teichmann@8038: void visit(Station station); teichmann@8036: } teichmann@8036: teichmann@8033: public static class Value implements Serializable { teichmann@8033: teichmann@8036: public interface Filter { teichmann@8036: boolean accept(Value value); teichmann@8036: } teichmann@8036: teichmann@8047: public interface Visitor { teichmann@8047: void visit(Value value); teichmann@8047: } teichmann@8047: teichmann@8033: private double value; teichmann@8033: teichmann@8033: private Load load; teichmann@8033: teichmann@8033: public Value() { teichmann@8033: } teichmann@8033: teichmann@8033: public Value(Load load, double value) { teichmann@8033: this.load = load; teichmann@8033: this.value = value; teichmann@8033: } teichmann@8033: teichmann@8033: public double getValue() { teichmann@8033: return value; teichmann@8033: } teichmann@8033: teichmann@8033: public Load getLoad() { teichmann@8033: return load; teichmann@8033: } teichmann@8033: } // class Value teichmann@8033: teichmann@8033: teichmann@8033: public static class Load implements Serializable { teichmann@8033: teichmann@8033: private int id; teichmann@8058: private int kind; andre@8239: private Integer sqTiId; teichmann@8033: teichmann@8033: private String description; teichmann@8033: teichmann@8033: private Date startTime; teichmann@8033: private Date stopTime; andre@8199: private Date sqStartTime; andre@8199: private Date sqStopTime; teichmann@8033: teichmann@8033: public Load() { teichmann@8033: } teichmann@8033: teichmann@8058: public Load( andre@8237: int id, andre@8237: int kind, andre@8237: String description, andre@8237: Date startTime, andre@8237: Date stopTime, andre@8237: Integer sqTiId, andre@8237: Date sqStartTime, andre@8237: Date sqStopTime teichmann@8058: ) { teichmann@8033: this.id = id; teichmann@8058: this.kind = kind; teichmann@8033: this.description = description; teichmann@8033: this.startTime = startTime; teichmann@8033: this.stopTime = stopTime; andre@8199: this.sqStartTime = sqStartTime; andre@8199: this.sqStopTime = sqStopTime; andre@8239: this.sqTiId = sqTiId; teichmann@8033: } teichmann@8033: teichmann@8033: public int getId() { teichmann@8033: return id; teichmann@8033: } teichmann@8033: andre@8239: public Integer getSQRelationTimeIntervalId() { andre@8237: return sqTiId; andre@8199: } andre@8199: teichmann@8058: public int getKind() { teichmann@8058: return kind; teichmann@8058: } teichmann@8058: teichmann@8033: public String getDescription() { teichmann@8033: return description; teichmann@8033: } teichmann@8033: teichmann@8033: public Date getStartTime() { teichmann@8033: return startTime; teichmann@8033: } teichmann@8033: teichmann@8033: public Date getStopTime() { teichmann@8033: return stopTime; teichmann@8033: } teichmann@8033: andre@8199: public Date getSQStartTime() { andre@8228: return sqStartTime; andre@8199: } andre@8199: andre@8199: public Date getSQStopTime() { andre@8228: return sqStopTime; andre@8199: } andre@8199: teichmann@8033: public boolean isEpoch() { teichmann@8033: return startTime != null && stopTime != null; teichmann@8033: } tom@8185: } // class Load teichmann@8033: teichmann@8033: public static class Station implements Serializable { teichmann@8033: teichmann@8048: public static final int UNKNOWN = 0; teichmann@8044: public static final int BED_LOAD = 1; teichmann@8044: public static final int SUSPENDED = 2; teichmann@8033: teichmann@8033: private double station; teichmann@8033: teichmann@8033: private int type; teichmann@8033: teichmann@8033: private List> grainFractions; teichmann@8033: teichmann@8034: private Station prev; teichmann@8034: teichmann@8033: public Station() { teichmann@8033: this(BED_LOAD, 0.0); teichmann@8033: } teichmann@8033: teichmann@8033: public Station(int type, double station) { teichmann@8033: grainFractions = new ArrayList>(GF_MAX+1); teichmann@8033: for (int i = 0; i < GF_MAX+1; ++i) { teichmann@8036: grainFractions.add(null); teichmann@8033: } teichmann@8033: this.type = type; teichmann@8033: this.station = station; teichmann@8033: } teichmann@8033: andre@8768: public void allLoadsWithValue(Collection loads) { teichmann@8123: for (List values: grainFractions) { teichmann@8123: if (values != null) { teichmann@8123: for (Value value: values) { andre@8768: if (!Double.isNaN(value.getValue())) { andre@8768: loads.add(value.getLoad()); andre@8768: } teichmann@8123: } teichmann@8123: } teichmann@8123: } teichmann@8123: } teichmann@8123: andre@8768: public void allNonEpochLoadsWithValue(Collection loads) { andre@8750: for (List values: grainFractions) { andre@8750: if (values != null) { andre@8750: for (Value value: values) { andre@8750: Load load = value.getLoad(); andre@8768: if (load.isEpoch() || Double.isNaN(value.getValue())) { andre@8750: continue; andre@8750: } andre@8750: loads.add(value.getLoad()); andre@8750: } andre@8750: } andre@8750: } andre@8750: } andre@8750: andre@8768: public void allLoadsWithValue(Collection loads, Integer sqRelationTimeInterval) { andre@8242: andre@8242: for (List values: grainFractions) { andre@8242: if (values == null) { andre@8242: continue; andre@8242: } andre@8242: for (Value value: values) { andre@8768: if (Double.isNaN(value.getValue())) { andre@8768: continue; andre@8768: } andre@8242: Load load = value.getLoad(); andre@8242: Integer sqId = load.getSQRelationTimeIntervalId(); andre@8242: if ((sqRelationTimeInterval == null) andre@8242: || sqId != null && sqId.equals(sqRelationTimeInterval)) { andre@8242: loads.add(load); andre@8242: } andre@8242: } andre@8242: } andre@8242: } andre@8242: teichmann@8033: public double getStation() { teichmann@8033: return station; teichmann@8033: } teichmann@8033: teichmann@8033: public int getType() { teichmann@8033: return type; teichmann@8033: } teichmann@8033: teichmann@8044: public boolean isType(int type) { teichmann@8050: return (this.type & type) != 0; teichmann@8044: } teichmann@8044: teichmann@8034: public void setPrev(Station prev) { tom@8193: this.prev = prev; teichmann@8034: } teichmann@8034: teichmann@8034: public Station getPrev() { teichmann@8034: return prev; teichmann@8034: } teichmann@8034: teichmann@8044: public void merge(Station other) { teichmann@8044: this.type |= other.type; teichmann@8044: for (int i = 0, N = grainFractions.size(); i < N; ++i) { teichmann@8044: grainFractions.set(i, teichmann@8044: mergeValues(grainFractions.get(i), other.grainFractions.get(i))); teichmann@8044: } teichmann@8038: } teichmann@8038: teichmann@8044: private static final Comparator ID_CMP = new Comparator() { teichmann@8044: @Override teichmann@8044: public int compare(Value a, Value b) { teichmann@8044: return a.getLoad().getId() - b.getLoad().getId(); teichmann@8044: } teichmann@8044: }; teichmann@8044: teichmann@8044: private static List mergeValues(List a, List b) { teichmann@8044: if (a == null) return b; teichmann@8044: if (b == null) return a; teichmann@8044: a.addAll(b); teichmann@8044: // re-establish id order. teichmann@8044: Collections.sort(a, ID_CMP); teichmann@8044: return a; teichmann@8044: } teichmann@8044: tom@8193: public Station prevByType(int type) { tom@8193: for (Station curr = this; curr != null; curr = curr.getPrev()) { teichmann@8044: if (curr.isType(type)) { teichmann@8044: return curr; teichmann@8044: } teichmann@8044: } teichmann@8044: return null; teichmann@8038: } teichmann@8038: teichmann@8033: public void addValue(int grainFraction, Value value) { teichmann@8036: List values = grainFractions.get(grainFraction); teichmann@8036: if (values == null) { teichmann@8036: values = new ArrayList(); teichmann@8036: grainFractions.set(grainFraction, values); teichmann@8036: } teichmann@8036: values.add(value); teichmann@8033: } teichmann@8033: teichmann@8033: public boolean hasGrainFraction(String grainFraction) { teichmann@8033: return hasGrainFraction(grainFractionIndex(grainFraction)); teichmann@8033: } teichmann@8033: teichmann@8033: public boolean hasGrainFraction(int grainFraction) { teichmann@8033: List values = grainFractions.get(grainFraction); teichmann@8036: return values != null && !values.isEmpty(); teichmann@8033: } teichmann@8033: teichmann@8047: public void filterGrainFraction( teichmann@8047: int grainFraction, teichmann@8047: Value.Filter filter, teichmann@8047: Value.Visitor visitor teichmann@8047: ) { teichmann@8036: List values = grainFractions.get(grainFraction); teichmann@8047: if (values != null && !values.isEmpty()) { teichmann@8047: for (Value value: values) { teichmann@8047: if (filter.accept(value)) { teichmann@8047: visitor.visit(value); teichmann@8047: } teichmann@8047: } teichmann@8033: } teichmann@8047: } teichmann@8047: teichmann@8047: public List filterGrainFraction(int grainFraction, Value.Filter filter) { teichmann@8047: final List result = new ArrayList(); teichmann@8047: filterGrainFraction(grainFraction, filter, new Value.Visitor() { teichmann@8047: @Override teichmann@8047: public void visit(Value value) { teichmann@8036: result.add(value); teichmann@8033: } teichmann@8047: }); teichmann@8036: return result; teichmann@8033: } teichmann@8034: teichmann@8034: public double findValueByLoadId(int id) { teichmann@8034: for (List values: grainFractions) { teichmann@8034: double value = findValueByLoadId(values, id); teichmann@8034: if (!Double.isNaN(value)) { teichmann@8034: return value; teichmann@8034: } teichmann@8034: } teichmann@8034: return Double.NaN; teichmann@8034: } teichmann@8034: teichmann@8034: private static final double findValueByLoadId( teichmann@8034: List values, teichmann@8034: int id teichmann@8034: ) { teichmann@8036: if (values == null) { teichmann@8036: return Double.NaN; teichmann@8036: } teichmann@8034: // List is ordered by station id -> binary search. teichmann@8034: int lo = 0, hi = values.size()-1; teichmann@8034: while (lo <= hi) { teichmann@8034: int mid = (lo + hi)/2; teichmann@8034: Value v = values.get(mid); teichmann@8034: int xid = v.getLoad().getId(); andre@8100: if (xid > id) hi = mid-1; andre@8100: else if (xid < id) lo = mid+1; teichmann@8034: else return v.getValue(); teichmann@8034: } teichmann@8034: teichmann@8034: return Double.NaN; teichmann@8034: } teichmann@8033: } // class Station teichmann@8033: teichmann@8033: teichmann@8040: private Station [] stations; teichmann@8033: teichmann@8033: public SedimentLoadData() { teichmann@8033: } teichmann@8033: tom@8193: public SedimentLoadData(Collection stations, boolean kmUp) { tom@8193: setStations(stations, kmUp); teichmann@8033: } teichmann@8033: andre@8092: public Station[] getStations() { andre@8092: return stations; andre@8092: } andre@8092: tom@8193: public void setStations(Collection stations, boolean kmUp) { teichmann@8038: TreeMap same = teichmann@8038: new TreeMap(EpsilonComparator.CMP); teichmann@8038: teichmann@8034: for (Station station: stations) { teichmann@8034: Double key = station.getStation(); teichmann@8038: Station st = same.get(key); teichmann@8038: if (st == null) { teichmann@8038: same.put(key, station); teichmann@8038: } else { teichmann@8044: st.merge(station); teichmann@8034: } teichmann@8034: } teichmann@8040: this.stations = new Station[same.size()]; teichmann@8040: int i = 0; teichmann@8040: for (Station station: same.values()) { teichmann@8040: this.stations[i++] = station; teichmann@8040: } tom@8193: wireNeighbors(kmUp); teichmann@8034: } teichmann@8033: tom@8193: private void wireNeighbors(boolean kmUp) { tom@8193: if (kmUp) { tom@8193: for (int i = stations.length - 1; i > 0; --i) { tom@8193: stations[i-1].setPrev(stations[i]); tom@8193: } tom@8193: } tom@8193: else { tom@8193: for (int i = 1; i < stations.length; ++i) { tom@8193: stations[i].setPrev(stations[i-1]); tom@8193: } teichmann@8033: } teichmann@8033: } teichmann@8035: teichmann@8035: private void recursiveFindStations( teichmann@8035: double a, double b, teichmann@8035: int lo, int hi, teichmann@8035: Visitor visitor teichmann@8035: ) { teichmann@8139: while (lo <= hi) { teichmann@8139: int mid = (lo+hi)/2; teichmann@8139: Station st = stations[mid]; teichmann@8139: double station = st.getStation(); teichmann@8139: if (station < a) { teichmann@8139: lo = mid+1; teichmann@8139: } else if (station > b) { teichmann@8139: hi = mid-1; teichmann@8139: } else { teichmann@8139: recursiveFindStations(a, b, lo, mid-1, visitor); teichmann@8139: visitor.visit(st); teichmann@8139: lo = mid+1; teichmann@8139: } teichmann@8035: } teichmann@8035: } teichmann@8035: teichmann@8123: public static final Comparator LOAD_ID_CMP = new Comparator() { teichmann@8123: @Override teichmann@8123: public int compare(Load a, Load b) { teichmann@8123: return a.getId() - b.getId(); teichmann@8123: } teichmann@8123: }; teichmann@8123: andre@8242: public static final Comparator LOAD_TI_CMP = new Comparator() { andre@8242: @Override andre@8242: public int compare(Load a, Load b) { andre@8242: Date a_start = a.getStartTime(); andre@8242: Date a_stop = a.getStopTime(); andre@8242: Date b_start = b.getStartTime(); andre@8242: Date b_stop = b.getStopTime(); andre@8242: if (a_start == null && b_start == null) { andre@8242: return 0; andre@8242: } else if (a_start != null) { andre@8242: return a_start.compareTo(b_start); andre@8242: } else if (a_stop != null) { andre@8242: return a_stop.compareTo(b_stop); andre@8242: } else { andre@8242: return 1; andre@8242: } andre@8242: } andre@8242: }; andre@8242: andre@8199: public static final Comparator LOAD_SQ_TI_CMP = new Comparator() { andre@8199: @Override andre@8199: public int compare(Load a, Load b) { andre@8239: Integer a_id = a.getSQRelationTimeIntervalId(); andre@8239: Integer b_id = b.getSQRelationTimeIntervalId(); andre@8239: if (a_id == null && b_id == null) { andre@8239: return 0; andre@8239: } andre@8239: if (a_id == null) { andre@8239: return -1; andre@8239: } andre@8239: if (b_id == null) { andre@8239: return 1; andre@8239: } andre@8239: return a_id - b_id; andre@8199: } andre@8199: }; andre@8199: andre@8199: /** Find all loads in the range a/b with the according sq_time_interval */ andre@8768: public Collection findLoadsWithValue(double a, double b, final Integer sqRelationTimeInterval) { andre@8239: final TreeSet loads = new TreeSet(LOAD_ID_CMP); andre@8199: andre@8199: findStations(a, b, new Visitor() { andre@8199: @Override andre@8199: public void visit(Station station) { andre@8768: station.allLoadsWithValue(loads, sqRelationTimeInterval); andre@8199: } andre@8199: }); andre@8199: andre@8242: return loads; andre@8242: } andre@8242: andre@8199: /** Get a list of loads with unique sq_time_intervals. andre@8199: * andre@8199: * This is mainly a convenience function for the SedimentLoadInfoService. andre@8199: */ andre@8768: public Collection findDistinctSQTimeIntervalNonEpochLoadsWithValue(double a, double b) { andre@8199: final TreeSet loads = new TreeSet(LOAD_SQ_TI_CMP); andre@8199: andre@8199: findStations(a, b, new Visitor() { andre@8199: @Override andre@8199: public void visit(Station station) { andre@8768: station.allNonEpochLoadsWithValue(loads); andre@8199: } andre@8199: }); andre@8199: andre@8199: return loads; andre@8199: } andre@8199: andre@8768: public Collection findLoadsWithValue(double a, double b) { teichmann@8140: final TreeSet loads = new TreeSet(LOAD_ID_CMP); teichmann@8123: teichmann@8123: findStations(a, b, new Visitor() { teichmann@8123: @Override teichmann@8123: public void visit(Station station) { andre@8768: station.allLoadsWithValue(loads); teichmann@8123: } teichmann@8123: }); teichmann@8123: teichmann@8123: return loads; teichmann@8123: } teichmann@8123: teichmann@8035: public void findStations(double a, double b, Visitor visitor) { teichmann@8035: if (a > b) { teichmann@8035: double t = a; a = b; b = t; teichmann@8035: } teichmann@8040: recursiveFindStations(a, b, 0, stations.length-1, visitor); teichmann@8035: } teichmann@8035: teichmann@8038: public List findStations(double a, double b) { teichmann@8038: final List result = new ArrayList(); teichmann@8035: findStations(a, b, new Visitor() { teichmann@8035: @Override teichmann@8038: public void visit(Station station) { teichmann@8038: result.add(station); teichmann@8035: } teichmann@8035: }); teichmann@8035: return result; teichmann@8035: } teichmann@8033: } teichmann@8033: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :