view artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadData.java @ 8047:c835f3cf098e

Sediment load: Added join to fetch measurement station via ranges which know the river. Added a visitor for values, too.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 17 Jul 2014 11:19:59 +0200
parents 86fa217c24d5
children cde6d2a9ec32
line wrap: on
line source
/* Copyright (C) 2014 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.model.minfo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;

import org.dive4elements.river.utils.EpsilonComparator;

public class SedimentLoadData implements Serializable
{
    public static final int GF_UNKNOWN        = -1;
    public static final int GF_COARSE         =  0;
    public static final int GF_FINE_MIDDLE    =  1;
    public static final int GF_SAND           =  2;
    public static final int GF_SUSP_SAND      =  3;
    public static final int GF_SUSP_SAND_BED  =  4;
    public static final int GF_SUSP_SEDIMENT  =  5;
    public static final int GF_TOTAL          =  6;
    public static final int GF_BED_LOAD       =  7;
    public static final int GF_SUSPENDED_LOAD =  8;
    public static final int GF_MAX            =  8;

    public static final int grainFractionIndex(String name) {
        if ("coarse".equals(name))             return GF_COARSE;
        if ("fine_middle".equals(name))        return GF_FINE_MIDDLE;
        if ("sand".equals(name))               return GF_SAND;
        if ("susp_sand".equals(name))          return GF_SUSP_SAND;
        if ("susp_sand_bed".equals(name))      return GF_SUSP_SAND_BED;
        if ("suspended_sediment".equals(name)) return GF_SUSP_SEDIMENT;
        if ("total".equals(name))              return GF_TOTAL;
        if ("bed_load".equals(name))           return GF_BED_LOAD;
        if ("suspended_load".equals(name))     return GF_SUSPENDED_LOAD;
        return GF_UNKNOWN;
    }

    public interface Visitor {
        void visit(Station station);
    }

    public static class Value implements Serializable {

        public interface Filter {
            boolean accept(Value value);
        }

        public interface Visitor {
            void visit(Value value);
        }

        private double value;

        private Load load;

        public Value() {
        }

        public Value(Load load, double value) {
            this.load = load;
            this.value = value;
        }

        public double getValue() {
            return value;
        }

        public Load getLoad() {
            return load;
        }
    } // class Value


    public static class Load implements Serializable {

        private int id;

        private String description;

        private Date startTime;
        private Date stopTime;

        public Load() {
        }

        public Load(int id, String description, Date startTime, Date stopTime) {
            this.id          = id;
            this.description = description;
            this.startTime   = startTime;
            this.stopTime    = stopTime;
        }

        public int getId() {
            return id;
        }

        public String getDescription() {
            return description;
        }

        public Date getStartTime() {
            return startTime;
        }

        public Date getStopTime() {
            return stopTime;
        }

        public boolean isEpoch() {
            return startTime != null && stopTime != null;
        }
    } // class SedimentLoad

    public static class Station implements Serializable {

        public static final int BED_LOAD  = 1;
        public static final int SUSPENDED = 2;

        private double station;

        private int type;

        private List<List<Value>> grainFractions;

        private Station next;
        private Station prev;

        public Station() {
            this(BED_LOAD, 0.0);
        }

        public Station(int type, double station) {
            grainFractions = new ArrayList<List<Value>>(GF_MAX+1);
            for (int i = 0; i < GF_MAX+1; ++i) {
                grainFractions.add(null);
            }
            this.type = type;
            this.station = station;
        }

        public double getStation() {
            return station;
        }

        public int getType() {
            return type;
        }

        public boolean isType(int type) {
            return (this.type & type) == type;
        }

        public void setNext(Station next) {
            this.next = next;
        }

        public Station getNext() {
            return next;
        }

        public void setPrev(Station prev) {
            this.prev = next;
        }

        public Station getPrev() {
            return prev;
        }

        public Station getNext(boolean isKMUp) {
            return isKMUp ? getNext() : getPrev();
        }

        public Station getPrev(boolean isKMUp) {
            return isKMUp ? getPrev() : getNext();
        }

        public void merge(Station other) {
            this.type |= other.type;
            for (int i = 0, N = grainFractions.size(); i < N; ++i) {
                grainFractions.set(i,
                    mergeValues(grainFractions.get(i), other.grainFractions.get(i)));
            }
        }

        private static final Comparator<Value> ID_CMP = new Comparator<Value>() {
            @Override
            public int compare(Value a, Value b) {
                return a.getLoad().getId() - b.getLoad().getId();
            }
        };

        private static List<Value> mergeValues(List<Value> a, List<Value> b) {
            if (a == null) return b;
            if (b == null) return a;
            a.addAll(b);
            // re-establish id order.
            Collections.sort(a, ID_CMP);
            return a;
        }

        public Station nextByType(int type, boolean isKMUp) {
            for (Station curr = this; curr != null; curr = curr.getNext(isKMUp)) {
                if (curr.isType(type)) {
                    return curr;
                }
            }
            return null;
        }

        public Station prevByType(int type, boolean isKMUp) {
            for (Station curr = this; curr != null; curr = curr.getPrev(isKMUp)) {
                if (curr.isType(type)) {
                    return curr;
                }
            }
            return null;
        }

        public void addValue(int grainFraction, Value value) {
            List<Value> values = grainFractions.get(grainFraction);
            if (values == null) {
                values = new ArrayList<Value>();
                grainFractions.set(grainFraction, values);
            }
            values.add(value);
        }

        public boolean hasGrainFraction(String grainFraction) {
            return hasGrainFraction(grainFractionIndex(grainFraction));
        }

        public boolean hasGrainFraction(int grainFraction) {
            List<Value> values = grainFractions.get(grainFraction);
            return values != null && !values.isEmpty();
        }

        public void filterGrainFraction(
            int           grainFraction,
            Value.Filter  filter,
            Value.Visitor visitor
        ) {
            List<Value> values = grainFractions.get(grainFraction);
            if (values != null && !values.isEmpty()) {
                for (Value value: values) {
                    if (filter.accept(value)) {
                        visitor.visit(value);
                    }
                }
            }
        }

        public List<Value> filterGrainFraction(int grainFraction, Value.Filter filter) {
            final List<Value> result = new ArrayList<Value>();
            filterGrainFraction(grainFraction, filter, new Value.Visitor() {
                @Override
                public void visit(Value value) {
                    result.add(value);
                }
            });
            return result;
        }

        public double findValueByLoadId(int id) {
            for (List<Value> values: grainFractions) {
                double value = findValueByLoadId(values, id);
                if (!Double.isNaN(value)) {
                    return value;
                }
            }
            return Double.NaN;
        }

        private static final double findValueByLoadId(
            List<Value> values,
            int         id
        ) {
            if (values == null) {
                return Double.NaN;
            }
            // List is ordered by station id -> binary search.
            int lo = 0, hi = values.size()-1;
            while (lo <= hi) {
                int mid = (lo + hi)/2;
                Value v = values.get(mid);
                int xid = v.getLoad().getId();
                if      (xid < id) hi = mid-1;
                else if (xid > id) lo = mid+1;
                else return v.getValue();
            }

            return Double.NaN;
        }
    } // class Station


    private Station [] stations;

    public SedimentLoadData() {
    }

    public SedimentLoadData(Collection<Station> stations) {
        setStations(stations);
    }

    public void setStations(Collection<Station> stations) {
        TreeMap<Double, Station> same =
            new TreeMap<Double, Station>(EpsilonComparator.CMP);

        for (Station station: stations) {
            Double key = station.getStation();
            Station st = same.get(key);
            if (st == null) {
                same.put(key, station);
            } else {
                st.merge(station);
            }
        }
        this.stations = new Station[same.size()];
        int i = 0;
        for (Station station: same.values()) {
            this.stations[i++] = station;
        }
        wireNeighbors();
    }

    private void wireNeighbors() {
        for (int i = 1; i < stations.length; ++i) {
            stations[i-1].setNext(stations[i]);
            stations[i].setPrev(stations[i-1]);
        }
    }

    private void recursiveFindStations(
        double a, double b,
        int lo, int hi,
        Visitor visitor
    ) {
        while (lo >= hi) {
            int mid = (lo+hi)/2;
            Station st = stations[mid];
            double station = st.getStation();
            if (station < a) {
                hi = mid-1;
            } else if (station > b) {
                lo = mid+1;
            } else {
                recursiveFindStations(a, b, lo, mid-1, visitor);
                visitor.visit(st);
                lo = mid+1;
            }
        }
    }

    public void findStations(double a, double b, Visitor visitor) {
        if (a > b) {
            double t = a; a = b; b = t;
        }
        recursiveFindStations(a, b, 0, stations.length-1, visitor);
    }

    public List<Station> findStations(double a, double b) {
        final List<Station> result = new ArrayList<Station>();
        findStations(a, b, new Visitor() {
            @Override
            public void visit(Station station) {
                result.add(station);
            }
        });
        return result;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org