changeset 9565:4809e23ffd27

FastAnnotations (Locations, POIs, Streckenfavoriten) deterministisch
author gernotbelger
date Mon, 05 Nov 2018 10:18:07 +0100
parents 1f6fbbe88af8
children 9826b465b751
files backend/src/main/java/org/dive4elements/river/model/FastAnnotations.java gwt-client/src/main/java/org/dive4elements/river/client/server/ChartServiceHelper.java
diffstat 2 files changed, 150 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/backend/src/main/java/org/dive4elements/river/model/FastAnnotations.java	Tue Oct 30 17:46:52 2018 +0100
+++ b/backend/src/main/java/org/dive4elements/river/model/FastAnnotations.java	Mon Nov 05 10:18:07 2018 +0100
@@ -8,81 +8,65 @@
 
 package org.dive4elements.river.model;
 
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
-import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.regex.Pattern;
 
-import java.io.Serializable;
-
+import org.dive4elements.river.backend.SessionHolder;
+import org.dive4elements.river.backend.utils.StringUtil;
+import org.hibernate.SQLQuery;
 import org.hibernate.Session;
-import org.hibernate.SQLQuery;
-
 import org.hibernate.type.StandardBasicTypes;
 
-import org.dive4elements.river.backend.SessionHolder;
+public class FastAnnotations implements Serializable {
+    private static final long serialVersionUID = 1L;
 
-public class FastAnnotations
-implements   Serializable
-{
-    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";
+    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";
+    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(Annotation a, Annotation b) {
-                double diff = a.a - b.a;
-                if (diff < -EPSILON) return -1;
-                if (diff > +EPSILON) return +1;
-                return 0;
-            }
-        };
+    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;
 
-    public static final class Annotation
-    implements                Serializable
-    {
+            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;
@@ -94,62 +78,68 @@
         public Annotation() {
         }
 
-        public Annotation(double a) {
+        public Annotation(final double a) {
             this.a = a;
         }
 
-        public Annotation(
-            double a,
-            double b,
-            String position,
-            String attribute,
-            String name,
-            double top,
-            double bottom
-        ) {
-            this.a         = a;
-            this.b         = b;
-            this.position  = position;
+        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;
+            this.name = name;
+            this.top = top;
+            this.bottom = bottom;
         }
 
         public double getA() {
-            return a;
+            return this.a;
         }
 
         public double getB() {
-            return b;
+            return this.b;
         }
 
         public String getPosition() {
-            return position;
+            return this.position;
         }
 
         public String getAttribute() {
-            return attribute;
+            return this.attribute;
         }
 
         public String getName() {
-            return name;
+            return this.name;
         }
 
         public double getTop() {
-            return top;
+            return this.top;
         }
 
         public double getBottom() {
-            return bottom;
+            return this.bottom;
         }
 
         @Override
         public String toString() {
-            return "[a=" + a + ";b=" + b +
-                ";pos=" + position + ";attr=" + attribute +
-                ";name=" + name + ";top=" + top +
-                ";bot=" + bottom + "]";
+            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
 
@@ -161,62 +151,62 @@
 
     public static class NameFilter implements Filter {
 
-        private Pattern namePattern;
+        private final Pattern namePattern;
 
-        public NameFilter(String name) {
+        public NameFilter(final String name) {
             this.namePattern = Pattern.compile(name);
         }
 
         @Override
-        public boolean accept(Annotation annotation) {
-            return namePattern.matcher(annotation.getName()).matches();
+        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(Annotation annotation) {
+        public boolean accept(final Annotation annotation) {
             return true;
         }
     };
 
     public static final Filter IS_POINT = new Filter() {
         @Override
-        public boolean accept(Annotation annotation) {
+        public boolean accept(final Annotation annotation) {
             return Double.isNaN(annotation.getB());
         }
     };
 
     public static final Filter IS_RANGE = new Filter() {
         @Override
-        public boolean accept(Annotation annotation) {
+        public boolean accept(final Annotation annotation) {
             return !Double.isNaN(annotation.getB());
         }
     };
 
-    private Annotation [] annotations;
+    private Annotation[] annotations;
 
     public FastAnnotations() {
     }
 
-    public FastAnnotations(Annotation [] annotations) {
+    public FastAnnotations(final Annotation[] annotations) {
         this.annotations = annotations;
     }
 
-    public FastAnnotations(String riverName) {
+    public FastAnnotations(final String riverName) {
         this(loadByRiverName(riverName));
     }
 
-    public FastAnnotations(int riverId) {
+    public FastAnnotations(final int riverId) {
         this(loadByRiverId(riverId));
     }
 
-    public FastAnnotations(Iterator<Annotation> iter) {
+    public FastAnnotations(final Iterator<Annotation> iter) {
         this(toArray(iter));
     }
 
     public int size() {
-        return annotations.length;
+        return this.annotations.length;
     }
 
     public Iterator<Annotation> filter(final Filter filter) {
@@ -227,23 +217,23 @@
 
             @Override
             public boolean hasNext() {
-                return current != null;
+                return this.current != null;
             }
 
             @Override
             public Annotation next() {
-                if (current == null) {
+                if (this.current == null) {
                     throw new NoSuchElementException();
                 }
-                Annotation result = current;
-                current = findNext();
+                final Annotation result = this.current;
+                this.current = findNext();
                 return result;
             }
 
             private Annotation findNext() {
 
-                while (idx < annotations.length) {
-                    Annotation annotation = annotations[idx++];
+                while (this.idx < FastAnnotations.this.annotations.length) {
+                    final Annotation annotation = FastAnnotations.this.annotations[this.idx++];
                     if (filter.accept(annotation)) {
                         return annotation;
                     }
@@ -259,9 +249,9 @@
         };
     }
 
-    public static Annotation [] toArray(Iterator<Annotation> iter) {
+    public static Annotation[] toArray(final Iterator<Annotation> iter) {
 
-        ArrayList<Annotation> list = new ArrayList<Annotation>();
+        final ArrayList<Annotation> list = new ArrayList<>();
 
         while (iter.hasNext()) {
             list.add(iter.next());
@@ -270,75 +260,84 @@
         return list.toArray(new Annotation[list.size()]);
     }
 
-    public Annotation findByKm(double km) {
-        Annotation key = new Annotation(km);
-        int idx = Arrays.binarySearch(annotations, key, KM_CMP);
-        return idx < 0 ? null : annotations[idx];
+    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(String query) {
-        Session session = SessionHolder.HOLDER.get();
+    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);
+        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(List<Object []> list) {
-        Annotation [] anns = new Annotation[list.size()];
+    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'.
-        HashMap<String, String> names = new HashMap<String, String>();
+        // final HashMap<String, String> names = new HashMap<>();
 
         for (int i = 0; i < anns.length; ++i) {
-            Object [] data   = list.get(i);
-            double a         = ((Double)data[0]);
-            double b         = data[1] != null ? (Double)data[1] : Double.NaN;
-            String position  = (String)data[2];
-            String attribute = (String)data[3];
-            String name      = (String)data[4];
-            double top       = data[5] != null ? (Double)data[5] : Double.NaN;
-            double bottom    = data[6] != null ? (Double)data[6] : Double.NaN;
+            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) {
-                String old = names.get(name);
-                if (old != null) {
-                    name = old;
-                }
-                else {
-                    names.put(name, name);
-                }
-            }
+            // 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);
+            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(String riverName) {
+    public static Annotation[] loadByRiverName(final String riverName) {
 
-        SQLQuery query = createQuery(SQL_BY_RIVER_NAME);
+        final SQLQuery query = createQuery(SQL_BY_RIVER_NAME);
 
         query.setString("river_name", riverName);
 
         return buildAnnotations(query.list());
     }
 
-    public static Annotation [] loadByRiverId(int riverId) {
+    public static Annotation[] loadByRiverId(final int riverId) {
 
-        SQLQuery query = createQuery(SQL_BY_RIVER_ID);
+        final SQLQuery query = createQuery(SQL_BY_RIVER_ID);
 
         query.setInteger("river_id", riverId);
 
         return buildAnnotations(query.list());
     }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/ChartServiceHelper.java	Tue Oct 30 17:46:52 2018 +0100
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/ChartServiceHelper.java	Mon Nov 05 10:18:07 2018 +0100
@@ -230,7 +230,7 @@
         ElementCreator      ec)
     {
         log.debug("ChartServiceHelper.appendCurrentKm");
-
+ 
         Element currentKm = ec.create("currentKm");
 
         String km = req.get("km");

http://dive4elements.wald.intevation.org