Mercurial > dive4elements > river
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 : |