view backend/src/main/java/org/dive4elements/river/model/FastAnnotations.java @ 9709:b74f817435fe

comment removed
author dnt_bjoernsen <d.tironi@bjoernsen.de>
date Wed, 27 Jan 2021 11:47:38 +0100
parents 4809e23ffd27
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 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.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;

import org.dive4elements.river.backend.SessionHolder;
import org.dive4elements.river.backend.utils.StringUtil;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.type.StandardBasicTypes;

public class FastAnnotations implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final String SQL_BY_RIVER_NAME = //
            "SELECT r.a AS a, r.b AS b, p.value AS position, " + //
                    "at.value AS attribute, ant.name AS name, " + //
                    "e.top AS top, e.bottom AS bottom " + //
                    "FROM annotations an " + //
                    "JOIN ranges r " + "ON an.range_id = r.id " + //
                    "JOIN attributes at " + "ON an.attribute_id = at.id "//
                    + "JOIN positions p " + "ON an.position_id = p.id " + //
                    "JOIN rivers riv " + "ON r.river_id = riv.id " + //
                    "LEFT JOIN annotation_types ant " + "ON an.type_id = ant.id " + //
                    "LEFT JOIN edges e " + "ON an.edge_id = e.id " + //
                    "WHERE riv.name = :river_name " + "ORDER BY r.a, position";

    public static final String SQL_BY_RIVER_ID = "SELECT r.a AS a, r.b AS b, p.value AS position, "//
            + "at.value AS attribute, ant.name AS name, "//
            + "e.top AS top, e.bottom AS bottom " + "FROM annotations an " + //
            "JOIN ranges r " + "ON an.range_id = r.id " + //
            "JOIN attributes at " + "ON an.attribute_id = at.id "//
            + "JOIN positions p " + "ON an.position_id = p.id " + //
            "LEFT JOIN annotation_types ant " + "ON an.type_id = ant.id "//
            + "LEFT JOIN edges e " + "ON an.edge_id = e.id " + //
            "WHERE r.id = :river_id " + "ORDER BY r.a, position";

    public static final double EPSILON = 1e-5;

    public static final Comparator<Annotation> KM_CMP = new Comparator<Annotation>() {
        @Override
        public int compare(final Annotation a, final Annotation b) {
            final double diff = a.a - b.a;

            if (diff < -EPSILON)
                return -1;
            if (diff > +EPSILON)
                return +1;

            return 0;
        }
    };

    public static final class Annotation implements Serializable, Comparable<Annotation> {
        private static final long serialVersionUID = 1L;
        private double a;
        private double b;
        private String position;
        private String attribute;
        private String name;
        private double top;
        private double bottom;

        public Annotation() {
        }

        public Annotation(final double a) {
            this.a = a;
        }

        public Annotation(final double a, final double b, final String position, final String attribute, final String name, final double top,
                final double bottom) {
            this.a = a;
            this.b = b;
            this.position = position;
            this.attribute = attribute;
            this.name = name;
            this.top = top;
            this.bottom = bottom;
        }

        public double getA() {
            return this.a;
        }

        public double getB() {
            return this.b;
        }

        public String getPosition() {
            return this.position;
        }

        public String getAttribute() {
            return this.attribute;
        }

        public String getName() {
            return this.name;
        }

        public double getTop() {
            return this.top;
        }

        public double getBottom() {
            return this.bottom;
        }

        @Override
        public String toString() {
            return "[a=" + this.a + ";b=" + this.b + ";pos=" + this.position + ";attr=" + this.attribute + ";name=" + this.name + ";top=" + this.top + ";bot="
                    + this.bottom + "]";
        }

        @Override
        public int compareTo(final Annotation o) {

            // Comparable interface introduced to make annotations deterministic (for testing etc)
            final int compareKmStart = Double.valueOf(this.a).compareTo(Double.valueOf(o.a));
            if (compareKmStart != 0)
                return compareKmStart;

            // Although position must not be null by database definition, Null-Checks are provided for safety reasons
            if (StringUtil.isEmpty(this.position))
                return +1; // leere Strings ans Ende

            return String.valueOf(this.position).compareTo(String.valueOf(o.position));
        }
    } // class Annotation

    public interface Filter {

        boolean accept(Annotation annotation);

    } // interface Filter

    public static class NameFilter implements Filter {

        private final Pattern namePattern;

        public NameFilter(final String name) {
            this.namePattern = Pattern.compile(name);
        }

        @Override
        public boolean accept(final Annotation annotation) {
            return this.namePattern.matcher(annotation.getName()).matches();
        }
    } // class NameFilter

    public static final Filter ALL = new Filter() {
        @Override
        public boolean accept(final Annotation annotation) {
            return true;
        }
    };

    public static final Filter IS_POINT = new Filter() {
        @Override
        public boolean accept(final Annotation annotation) {
            return Double.isNaN(annotation.getB());
        }
    };

    public static final Filter IS_RANGE = new Filter() {
        @Override
        public boolean accept(final Annotation annotation) {
            return !Double.isNaN(annotation.getB());
        }
    };

    private Annotation[] annotations;

    public FastAnnotations() {
    }

    public FastAnnotations(final Annotation[] annotations) {
        this.annotations = annotations;
    }

    public FastAnnotations(final String riverName) {
        this(loadByRiverName(riverName));
    }

    public FastAnnotations(final int riverId) {
        this(loadByRiverId(riverId));
    }

    public FastAnnotations(final Iterator<Annotation> iter) {
        this(toArray(iter));
    }

    public int size() {
        return this.annotations.length;
    }

    public Iterator<Annotation> filter(final Filter filter) {
        return new Iterator<Annotation>() {

            private int idx;
            private Annotation current = findNext();

            @Override
            public boolean hasNext() {
                return this.current != null;
            }

            @Override
            public Annotation next() {
                if (this.current == null) {
                    throw new NoSuchElementException();
                }
                final Annotation result = this.current;
                this.current = findNext();
                return result;
            }

            private Annotation findNext() {

                while (this.idx < FastAnnotations.this.annotations.length) {
                    final Annotation annotation = FastAnnotations.this.annotations[this.idx++];
                    if (filter.accept(annotation)) {
                        return annotation;
                    }
                }

                return null;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public static Annotation[] toArray(final Iterator<Annotation> iter) {

        final ArrayList<Annotation> list = new ArrayList<>();

        while (iter.hasNext()) {
            list.add(iter.next());
        }

        return list.toArray(new Annotation[list.size()]);
    }

    public Annotation findByKm(final double km) {
        final Annotation key = new Annotation(km);
        final int idx = Arrays.binarySearch(this.annotations, key, KM_CMP);

        if ((idx < 0))
            return null;

        if (idx == 0)
            return this.annotations[idx]; // lowest possible index

        // REMARK: binary search my find any annotation at kmTest, but we want the first entry (because the list of annotations
        // is ordered by name)
        for (int lowestIndex = idx; lowestIndex > 0; lowestIndex--) {
            final double kmTest = this.annotations[lowestIndex].a;
            if (Math.abs(kmTest - this.annotations[idx].a) > EPSILON)
                return this.annotations[lowestIndex + 1];
        }

        return this.annotations[0];
    }

    private static SQLQuery createQuery(final String query) {
        final Session session = SessionHolder.HOLDER.get();

        return session.createSQLQuery(query).addScalar("a", StandardBasicTypes.DOUBLE).addScalar("b", StandardBasicTypes.DOUBLE)
                .addScalar("position", StandardBasicTypes.STRING).addScalar("attribute", StandardBasicTypes.STRING).addScalar("name", StandardBasicTypes.STRING)
                .addScalar("top", StandardBasicTypes.DOUBLE).addScalar("bottom", StandardBasicTypes.DOUBLE);
    }

    private static Annotation[] buildAnnotations(final List<Object[]> list) {
        final Annotation[] anns = new Annotation[list.size()];

        // Names are likely the same because they are a type
        // like 'Pegel' or 'Hafen'.
        // final HashMap<String, String> names = new HashMap<>();

        for (int i = 0; i < anns.length; ++i) {
            final Object[] data = list.get(i);
            final double a = ((Double) data[0]);
            final double b = data[1] != null ? (Double) data[1] : Double.NaN;
            final String position = (String) data[2];
            final String attribute = (String) data[3];
            final String name = (String) data[4];
            final double top = data[5] != null ? (Double) data[5] : Double.NaN;
            final double bottom = data[6] != null ? (Double) data[6] : Double.NaN;

            // if (name != null) {
            // final String old = names.get(name);
            // if (old != null) {
            // name = old;
            // } else {
            // names.put(name, name);
            // }
            // }

            anns[i] = new Annotation(a, b, position, attribute, name, top, bottom);

        }

        Arrays.sort(anns); // Comparable interface introduced to make annotations deterministic (for testing etc)
        return anns;
    }

    public static Annotation[] loadByRiverName(final String riverName) {

        final SQLQuery query = createQuery(SQL_BY_RIVER_NAME);

        query.setString("river_name", riverName);

        return buildAnnotations(query.list());
    }

    public static Annotation[] loadByRiverId(final int riverId) {

        final SQLQuery query = createQuery(SQL_BY_RIVER_ID);

        query.setInteger("river_id", riverId);

        return buildAnnotations(query.list());
    }
}

http://dive4elements.wald.intevation.org