comparison backend/src/main/java/org/dive4elements/river/model/FastAnnotations.java @ 9565:4809e23ffd27

FastAnnotations (Locations, POIs, Streckenfavoriten) deterministisch
author gernotbelger
date Mon, 05 Nov 2018 10:18:07 +0100
parents 6ae0c5116d58
children
comparison
equal deleted inserted replaced
9564:1f6fbbe88af8 9565:4809e23ffd27
6 * documentation coming with Dive4Elements River for details. 6 * documentation coming with Dive4Elements River for details.
7 */ 7 */
8 8
9 package org.dive4elements.river.model; 9 package org.dive4elements.river.model;
10 10
11 import java.io.Serializable;
12 import java.util.ArrayList;
13 import java.util.Arrays;
11 import java.util.Comparator; 14 import java.util.Comparator;
12 import java.util.ArrayList; 15 import java.util.Iterator;
13 import java.util.List; 16 import java.util.List;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.NoSuchElementException; 17 import java.util.NoSuchElementException;
18 import java.util.regex.Pattern; 18 import java.util.regex.Pattern;
19 19
20 import java.io.Serializable; 20 import org.dive4elements.river.backend.SessionHolder;
21 21 import org.dive4elements.river.backend.utils.StringUtil;
22 import org.hibernate.SQLQuery;
22 import org.hibernate.Session; 23 import org.hibernate.Session;
23 import org.hibernate.SQLQuery;
24
25 import org.hibernate.type.StandardBasicTypes; 24 import org.hibernate.type.StandardBasicTypes;
26 25
27 import org.dive4elements.river.backend.SessionHolder; 26 public class FastAnnotations implements Serializable {
28 27 private static final long serialVersionUID = 1L;
29 public class FastAnnotations 28
30 implements Serializable 29 public static final String SQL_BY_RIVER_NAME = //
31 { 30 "SELECT r.a AS a, r.b AS b, p.value AS position, " + //
32 public static final String SQL_BY_RIVER_NAME = 31 "at.value AS attribute, ant.name AS name, " + //
33 "SELECT r.a AS a, r.b AS b, p.value AS position, " + 32 "e.top AS top, e.bottom AS bottom " + //
34 "at.value AS attribute, ant.name AS name, " + 33 "FROM annotations an " + //
35 "e.top AS top, e.bottom AS bottom " + 34 "JOIN ranges r " + "ON an.range_id = r.id " + //
36 "FROM annotations an " + 35 "JOIN attributes at " + "ON an.attribute_id = at.id "//
37 "JOIN ranges r " + 36 + "JOIN positions p " + "ON an.position_id = p.id " + //
38 "ON an.range_id = r.id " + 37 "JOIN rivers riv " + "ON r.river_id = riv.id " + //
39 "JOIN attributes at " + 38 "LEFT JOIN annotation_types ant " + "ON an.type_id = ant.id " + //
40 "ON an.attribute_id = at.id " + 39 "LEFT JOIN edges e " + "ON an.edge_id = e.id " + //
41 "JOIN positions p " + 40 "WHERE riv.name = :river_name " + "ORDER BY r.a, position";
42 "ON an.position_id = p.id " + 41
43 "JOIN rivers riv " + 42 public static final String SQL_BY_RIVER_ID = "SELECT r.a AS a, r.b AS b, p.value AS position, "//
44 "ON r.river_id = riv.id " + 43 + "at.value AS attribute, ant.name AS name, "//
45 "LEFT JOIN annotation_types ant " + 44 + "e.top AS top, e.bottom AS bottom " + "FROM annotations an " + //
46 "ON an.type_id = ant.id " + 45 "JOIN ranges r " + "ON an.range_id = r.id " + //
47 "LEFT JOIN edges e " + 46 "JOIN attributes at " + "ON an.attribute_id = at.id "//
48 "ON an.edge_id = e.id " + 47 + "JOIN positions p " + "ON an.position_id = p.id " + //
49 "WHERE riv.name = :river_name " + 48 "LEFT JOIN annotation_types ant " + "ON an.type_id = ant.id "//
50 "ORDER BY r.a"; 49 + "LEFT JOIN edges e " + "ON an.edge_id = e.id " + //
51 50 "WHERE r.id = :river_id " + "ORDER BY r.a, position";
52 public static final String SQL_BY_RIVER_ID =
53 "SELECT r.a AS a, r.b AS b, p.value AS position, " +
54 "at.value AS attribute, ant.name AS name, " +
55 "e.top AS top, e.bottom AS bottom " +
56 "FROM annotations an " +
57 "JOIN ranges r " +
58 "ON an.range_id = r.id " +
59 "JOIN attributes at " +
60 "ON an.attribute_id = at.id " +
61 "JOIN positions p " +
62 "ON an.position_id = p.id " +
63 "LEFT JOIN annotation_types ant " +
64 "ON an.type_id = ant.id " +
65 "LEFT JOIN edges e " +
66 "ON an.edge_id = e.id " +
67 "WHERE r.id = :river_id " +
68 "ORDER BY r.a";
69 51
70 public static final double EPSILON = 1e-5; 52 public static final double EPSILON = 1e-5;
71 53
72 public static final Comparator<Annotation> KM_CMP = 54 public static final Comparator<Annotation> KM_CMP = new Comparator<Annotation>() {
73 new Comparator<Annotation>() { 55 @Override
74 @Override 56 public int compare(final Annotation a, final Annotation b) {
75 public int compare(Annotation a, Annotation b) { 57 final double diff = a.a - b.a;
76 double diff = a.a - b.a; 58
77 if (diff < -EPSILON) return -1; 59 if (diff < -EPSILON)
78 if (diff > +EPSILON) return +1; 60 return -1;
79 return 0; 61 if (diff > +EPSILON)
80 } 62 return +1;
81 }; 63
82 64 return 0;
83 public static final class Annotation 65 }
84 implements Serializable 66 };
85 { 67
68 public static final class Annotation implements Serializable, Comparable<Annotation> {
69 private static final long serialVersionUID = 1L;
86 private double a; 70 private double a;
87 private double b; 71 private double b;
88 private String position; 72 private String position;
89 private String attribute; 73 private String attribute;
90 private String name; 74 private String name;
92 private double bottom; 76 private double bottom;
93 77
94 public Annotation() { 78 public Annotation() {
95 } 79 }
96 80
97 public Annotation(double a) { 81 public Annotation(final double a) {
98 this.a = a; 82 this.a = a;
99 } 83 }
100 84
101 public Annotation( 85 public Annotation(final double a, final double b, final String position, final String attribute, final String name, final double top,
102 double a, 86 final double bottom) {
103 double b, 87 this.a = a;
104 String position, 88 this.b = b;
105 String attribute, 89 this.position = position;
106 String name,
107 double top,
108 double bottom
109 ) {
110 this.a = a;
111 this.b = b;
112 this.position = position;
113 this.attribute = attribute; 90 this.attribute = attribute;
114 this.name = name; 91 this.name = name;
115 this.top = top; 92 this.top = top;
116 this.bottom = bottom; 93 this.bottom = bottom;
117 } 94 }
118 95
119 public double getA() { 96 public double getA() {
120 return a; 97 return this.a;
121 } 98 }
122 99
123 public double getB() { 100 public double getB() {
124 return b; 101 return this.b;
125 } 102 }
126 103
127 public String getPosition() { 104 public String getPosition() {
128 return position; 105 return this.position;
129 } 106 }
130 107
131 public String getAttribute() { 108 public String getAttribute() {
132 return attribute; 109 return this.attribute;
133 } 110 }
134 111
135 public String getName() { 112 public String getName() {
136 return name; 113 return this.name;
137 } 114 }
138 115
139 public double getTop() { 116 public double getTop() {
140 return top; 117 return this.top;
141 } 118 }
142 119
143 public double getBottom() { 120 public double getBottom() {
144 return bottom; 121 return this.bottom;
145 } 122 }
146 123
147 @Override 124 @Override
148 public String toString() { 125 public String toString() {
149 return "[a=" + a + ";b=" + b + 126 return "[a=" + this.a + ";b=" + this.b + ";pos=" + this.position + ";attr=" + this.attribute + ";name=" + this.name + ";top=" + this.top + ";bot="
150 ";pos=" + position + ";attr=" + attribute + 127 + this.bottom + "]";
151 ";name=" + name + ";top=" + top + 128 }
152 ";bot=" + bottom + "]"; 129
130 @Override
131 public int compareTo(final Annotation o) {
132
133 // Comparable interface introduced to make annotations deterministic (for testing etc)
134 final int compareKmStart = Double.valueOf(this.a).compareTo(Double.valueOf(o.a));
135 if (compareKmStart != 0)
136 return compareKmStart;
137
138 // Although position must not be null by database definition, Null-Checks are provided for safety reasons
139 if (StringUtil.isEmpty(this.position))
140 return +1; // leere Strings ans Ende
141
142 return String.valueOf(this.position).compareTo(String.valueOf(o.position));
153 } 143 }
154 } // class Annotation 144 } // class Annotation
155 145
156 public interface Filter { 146 public interface Filter {
157 147
159 149
160 } // interface Filter 150 } // interface Filter
161 151
162 public static class NameFilter implements Filter { 152 public static class NameFilter implements Filter {
163 153
164 private Pattern namePattern; 154 private final Pattern namePattern;
165 155
166 public NameFilter(String name) { 156 public NameFilter(final String name) {
167 this.namePattern = Pattern.compile(name); 157 this.namePattern = Pattern.compile(name);
168 } 158 }
169 159
170 @Override 160 @Override
171 public boolean accept(Annotation annotation) { 161 public boolean accept(final Annotation annotation) {
172 return namePattern.matcher(annotation.getName()).matches(); 162 return this.namePattern.matcher(annotation.getName()).matches();
173 } 163 }
174 } // class NameFilter 164 } // class NameFilter
175 165
176 public static final Filter ALL = new Filter() { 166 public static final Filter ALL = new Filter() {
177 @Override 167 @Override
178 public boolean accept(Annotation annotation) { 168 public boolean accept(final Annotation annotation) {
179 return true; 169 return true;
180 } 170 }
181 }; 171 };
182 172
183 public static final Filter IS_POINT = new Filter() { 173 public static final Filter IS_POINT = new Filter() {
184 @Override 174 @Override
185 public boolean accept(Annotation annotation) { 175 public boolean accept(final Annotation annotation) {
186 return Double.isNaN(annotation.getB()); 176 return Double.isNaN(annotation.getB());
187 } 177 }
188 }; 178 };
189 179
190 public static final Filter IS_RANGE = new Filter() { 180 public static final Filter IS_RANGE = new Filter() {
191 @Override 181 @Override
192 public boolean accept(Annotation annotation) { 182 public boolean accept(final Annotation annotation) {
193 return !Double.isNaN(annotation.getB()); 183 return !Double.isNaN(annotation.getB());
194 } 184 }
195 }; 185 };
196 186
197 private Annotation [] annotations; 187 private Annotation[] annotations;
198 188
199 public FastAnnotations() { 189 public FastAnnotations() {
200 } 190 }
201 191
202 public FastAnnotations(Annotation [] annotations) { 192 public FastAnnotations(final Annotation[] annotations) {
203 this.annotations = annotations; 193 this.annotations = annotations;
204 } 194 }
205 195
206 public FastAnnotations(String riverName) { 196 public FastAnnotations(final String riverName) {
207 this(loadByRiverName(riverName)); 197 this(loadByRiverName(riverName));
208 } 198 }
209 199
210 public FastAnnotations(int riverId) { 200 public FastAnnotations(final int riverId) {
211 this(loadByRiverId(riverId)); 201 this(loadByRiverId(riverId));
212 } 202 }
213 203
214 public FastAnnotations(Iterator<Annotation> iter) { 204 public FastAnnotations(final Iterator<Annotation> iter) {
215 this(toArray(iter)); 205 this(toArray(iter));
216 } 206 }
217 207
218 public int size() { 208 public int size() {
219 return annotations.length; 209 return this.annotations.length;
220 } 210 }
221 211
222 public Iterator<Annotation> filter(final Filter filter) { 212 public Iterator<Annotation> filter(final Filter filter) {
223 return new Iterator<Annotation>() { 213 return new Iterator<Annotation>() {
224 214
225 private int idx; 215 private int idx;
226 private Annotation current = findNext(); 216 private Annotation current = findNext();
227 217
228 @Override 218 @Override
229 public boolean hasNext() { 219 public boolean hasNext() {
230 return current != null; 220 return this.current != null;
231 } 221 }
232 222
233 @Override 223 @Override
234 public Annotation next() { 224 public Annotation next() {
235 if (current == null) { 225 if (this.current == null) {
236 throw new NoSuchElementException(); 226 throw new NoSuchElementException();
237 } 227 }
238 Annotation result = current; 228 final Annotation result = this.current;
239 current = findNext(); 229 this.current = findNext();
240 return result; 230 return result;
241 } 231 }
242 232
243 private Annotation findNext() { 233 private Annotation findNext() {
244 234
245 while (idx < annotations.length) { 235 while (this.idx < FastAnnotations.this.annotations.length) {
246 Annotation annotation = annotations[idx++]; 236 final Annotation annotation = FastAnnotations.this.annotations[this.idx++];
247 if (filter.accept(annotation)) { 237 if (filter.accept(annotation)) {
248 return annotation; 238 return annotation;
249 } 239 }
250 } 240 }
251 241
257 throw new UnsupportedOperationException(); 247 throw new UnsupportedOperationException();
258 } 248 }
259 }; 249 };
260 } 250 }
261 251
262 public static Annotation [] toArray(Iterator<Annotation> iter) { 252 public static Annotation[] toArray(final Iterator<Annotation> iter) {
263 253
264 ArrayList<Annotation> list = new ArrayList<Annotation>(); 254 final ArrayList<Annotation> list = new ArrayList<>();
265 255
266 while (iter.hasNext()) { 256 while (iter.hasNext()) {
267 list.add(iter.next()); 257 list.add(iter.next());
268 } 258 }
269 259
270 return list.toArray(new Annotation[list.size()]); 260 return list.toArray(new Annotation[list.size()]);
271 } 261 }
272 262
273 public Annotation findByKm(double km) { 263 public Annotation findByKm(final double km) {
274 Annotation key = new Annotation(km); 264 final Annotation key = new Annotation(km);
275 int idx = Arrays.binarySearch(annotations, key, KM_CMP); 265 final int idx = Arrays.binarySearch(this.annotations, key, KM_CMP);
276 return idx < 0 ? null : annotations[idx]; 266
277 } 267 if ((idx < 0))
278 268 return null;
279 private static SQLQuery createQuery(String query) { 269
280 Session session = SessionHolder.HOLDER.get(); 270 if (idx == 0)
281 271 return this.annotations[idx]; // lowest possible index
282 return session.createSQLQuery(query) 272
283 .addScalar("a", StandardBasicTypes.DOUBLE) 273 // REMARK: binary search my find any annotation at kmTest, but we want the first entry (because the list of annotations
284 .addScalar("b", StandardBasicTypes.DOUBLE) 274 // is ordered by name)
285 .addScalar("position", StandardBasicTypes.STRING) 275 for (int lowestIndex = idx; lowestIndex > 0; lowestIndex--) {
286 .addScalar("attribute", StandardBasicTypes.STRING) 276 final double kmTest = this.annotations[lowestIndex].a;
287 .addScalar("name", StandardBasicTypes.STRING) 277 if (Math.abs(kmTest - this.annotations[idx].a) > EPSILON)
288 .addScalar("top", StandardBasicTypes.DOUBLE) 278 return this.annotations[lowestIndex + 1];
289 .addScalar("bottom", StandardBasicTypes.DOUBLE); 279 }
290 } 280
291 281 return this.annotations[0];
292 private static Annotation [] buildAnnotations(List<Object []> list) { 282 }
293 Annotation [] anns = new Annotation[list.size()]; 283
284 private static SQLQuery createQuery(final String query) {
285 final Session session = SessionHolder.HOLDER.get();
286
287 return session.createSQLQuery(query).addScalar("a", StandardBasicTypes.DOUBLE).addScalar("b", StandardBasicTypes.DOUBLE)
288 .addScalar("position", StandardBasicTypes.STRING).addScalar("attribute", StandardBasicTypes.STRING).addScalar("name", StandardBasicTypes.STRING)
289 .addScalar("top", StandardBasicTypes.DOUBLE).addScalar("bottom", StandardBasicTypes.DOUBLE);
290 }
291
292 private static Annotation[] buildAnnotations(final List<Object[]> list) {
293 final Annotation[] anns = new Annotation[list.size()];
294 294
295 // Names are likely the same because they are a type 295 // Names are likely the same because they are a type
296 // like 'Pegel' or 'Hafen'. 296 // like 'Pegel' or 'Hafen'.
297 HashMap<String, String> names = new HashMap<String, String>(); 297 // final HashMap<String, String> names = new HashMap<>();
298 298
299 for (int i = 0; i < anns.length; ++i) { 299 for (int i = 0; i < anns.length; ++i) {
300 Object [] data = list.get(i); 300 final Object[] data = list.get(i);
301 double a = ((Double)data[0]); 301 final double a = ((Double) data[0]);
302 double b = data[1] != null ? (Double)data[1] : Double.NaN; 302 final double b = data[1] != null ? (Double) data[1] : Double.NaN;
303 String position = (String)data[2]; 303 final String position = (String) data[2];
304 String attribute = (String)data[3]; 304 final String attribute = (String) data[3];
305 String name = (String)data[4]; 305 final String name = (String) data[4];
306 double top = data[5] != null ? (Double)data[5] : Double.NaN; 306 final double top = data[5] != null ? (Double) data[5] : Double.NaN;
307 double bottom = data[6] != null ? (Double)data[6] : Double.NaN; 307 final double bottom = data[6] != null ? (Double) data[6] : Double.NaN;
308 308
309 if (name != null) { 309 // if (name != null) {
310 String old = names.get(name); 310 // final String old = names.get(name);
311 if (old != null) { 311 // if (old != null) {
312 name = old; 312 // name = old;
313 } 313 // } else {
314 else { 314 // names.put(name, name);
315 names.put(name, name); 315 // }
316 } 316 // }
317 } 317
318 318 anns[i] = new Annotation(a, b, position, attribute, name, top, bottom);
319 anns[i] = new Annotation( 319
320 a, b, position, attribute, name, top, bottom); 320 }
321 } 321
322 322 Arrays.sort(anns); // Comparable interface introduced to make annotations deterministic (for testing etc)
323 return anns; 323 return anns;
324 } 324 }
325 325
326 public static Annotation [] loadByRiverName(String riverName) { 326 public static Annotation[] loadByRiverName(final String riverName) {
327 327
328 SQLQuery query = createQuery(SQL_BY_RIVER_NAME); 328 final SQLQuery query = createQuery(SQL_BY_RIVER_NAME);
329 329
330 query.setString("river_name", riverName); 330 query.setString("river_name", riverName);
331 331
332 return buildAnnotations(query.list()); 332 return buildAnnotations(query.list());
333 } 333 }
334 334
335 public static Annotation [] loadByRiverId(int riverId) { 335 public static Annotation[] loadByRiverId(final int riverId) {
336 336
337 SQLQuery query = createQuery(SQL_BY_RIVER_ID); 337 final SQLQuery query = createQuery(SQL_BY_RIVER_ID);
338 338
339 query.setInteger("river_id", riverId); 339 query.setInteger("river_id", riverId);
340 340
341 return buildAnnotations(query.list()); 341 return buildAnnotations(query.list());
342 } 342 }
343 } 343 }
344 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org