comparison artifacts/src/main/java/org/dive4elements/river/artifacts/model/FixingsOverview.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-artifacts/src/main/java/org/dive4elements/river/artifacts/model/FixingsOverview.java@bd047b71ab37
children 4897a58c8746
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.artifacts.model;
2
3 import java.io.Serializable;
4
5 import java.text.SimpleDateFormat;
6
7 import java.util.ArrayList;
8 import java.util.Collections;
9 import java.util.Comparator;
10 import java.util.Date;
11 import java.util.List;
12
13 import org.apache.log4j.Logger;
14
15 import org.hibernate.SQLQuery;
16 import org.hibernate.Session;
17
18 import org.hibernate.type.StandardBasicTypes;
19
20 import org.w3c.dom.Document;
21 import org.w3c.dom.Element;
22
23
24 /** Generate Fixings Table chart. */
25 public class FixingsOverview
26 implements Serializable
27 {
28 private static Logger log = Logger.getLogger(FixingsOverview.class);
29
30 public static final double EPSILON = 1e-2;
31
32 public static final String DATE_FORMAT = "dd.MM.yyyy";
33
34 public static final String SQL_RIVER_ID =
35 "SELECT" +
36 " id AS river_id," +
37 " km_up " +
38 "FROM rivers " +
39 "WHERE" +
40 " name = :name";
41
42 /** All kind-2 wsts from given river. */
43 public static final String SQL_FIXINGS =
44 "SELECT" +
45 " id AS wst_id," +
46 " description " +
47 "FROM wsts " +
48 "WHERE" +
49 " river_id = :river_id AND kind = 2";
50
51 /** All columns from given wst. */
52 public static final String SQL_FIXING_COLUMNS =
53 "SELECT" +
54 " wc.id AS wst_column_id," +
55 " ti.start_time AS start_time," +
56 " wc.name AS name " +
57 "FROM wst_columns wc" +
58 " JOIN time_intervals ti ON wc.time_interval_id = ti.id " +
59 "WHERE" +
60 " wc.wst_id = :wst_id " +
61 "ORDER BY position";
62
63 public static final String SQL_FIXING_COLUMN_Q_RANGES =
64 "SELECT" +
65 " wqr.q AS q," +
66 " r.a AS start_km," +
67 " r.b AS stop_km " +
68 "FROM wst_column_q_ranges wcqr" +
69 " JOIN wst_q_ranges wqr ON wcqr.wst_q_range_id = wqr.id" +
70 " JOIN ranges r ON wqr.range_id = r.id " +
71 "WHERE" +
72 " wcqr.wst_column_id = :column_id " +
73 "ORDER BY r.a";
74
75 public static final String SQL_FIXING_COLUMN_KM_RANGE =
76 "SELECT" +
77 " MIN(position) AS start_km," +
78 " MAX(position) AS stop_km " +
79 "FROM" +
80 " wst_column_values " +
81 "WHERE" +
82 " wst_column_id = :column_id";
83
84
85 public static class QRange extends Range {
86
87 protected double q;
88
89 public QRange() {
90 }
91
92 public QRange(double start, double end, double q) {
93 super(start, end);
94 this.q = q;
95 }
96 } // class QRange
97
98 public static class SectorRange extends Range {
99
100 protected int sector;
101
102 public SectorRange() {
103 }
104
105 public SectorRange(SectorRange other) {
106 start = other.start;
107 end = other.end;
108 sector = other.sector;
109 }
110
111 public SectorRange(Range range) {
112 super(range);
113 }
114
115 public SectorRange(double start, double end, int sector) {
116 super(start, end);
117 this.sector = sector;
118 }
119
120 public int getSector() {
121 return sector;
122 }
123
124 public void setSector(int sector) {
125 this.sector = sector;
126 }
127
128 public boolean enlarge(SectorRange other) {
129 if (sector == other.sector
130 && Math.abs(end-other.start) < FixingsOverview.EPSILON) {
131 end = other.end;
132 return true;
133 }
134 return false;
135 }
136 } // class SectorRange
137
138 public static class Fixing implements Serializable {
139
140 public static final Comparator<Column> DATE_CMP =
141 new Comparator<Column>() {
142 @Override
143 public int compare(Column a, Column b) {
144 return a.startTime.compareTo(b.startTime);
145 }
146 };
147
148 public interface Filter {
149
150 boolean accept(Column column);
151
152 } // interface Filter
153
154 public class Column extends Range {
155
156 protected int columnId;
157 protected Date startTime;
158 protected String name;
159
160 protected List<SectorRange> sectors;
161
162 public Column() {
163 }
164
165 public Column(int columnId, Date startTime, String name) {
166 this.columnId = columnId;
167 this.startTime = startTime;
168 this.name = name;
169
170 sectors = new ArrayList<SectorRange>();
171 }
172
173 public int getId() {
174 return columnId;
175 }
176
177 public Fixing getFixing() {
178 return Fixing.this;
179 }
180
181 public Date getStartTime() {
182 return startTime;
183 }
184
185 public String getName() {
186 return name;
187 }
188
189 public String getDescription() {
190 return Fixing.this.description + "/" + name;
191 }
192
193 public List<SectorRange> getSectors() {
194 return sectors;
195 }
196
197 public boolean hasSectorsInRange(Range range) {
198 for (SectorRange sector: sectors) {
199 if (sector.intersects(range)) {
200 return true;
201 }
202 }
203 return false;
204 }
205
206 public List<SectorRange> getSectors(Range range) {
207
208 List<SectorRange> result =
209 new ArrayList<SectorRange>(sectors.size());
210
211 for (SectorRange src: sectors) {
212 SectorRange dst = new SectorRange(src);
213 if (range == null || dst.clip(range)) {
214 result.add(dst);
215 }
216 }
217
218 return result;
219 }
220
221 public int findQSector(double km) {
222 for (SectorRange sector: sectors) {
223 if (sector.inside(km)) {
224 return sector.getSector();
225 }
226 }
227 return -1;
228 }
229
230 public void buildSectors(
231 GaugeFinder gaugeFinder,
232 List<QRange> qRanges
233 ) {
234 for (QRange qRange: qRanges) {
235 for (GaugeRange gRange: gaugeFinder.getGauges()) {
236 SectorRange sector = new SectorRange(qRange);
237 if (!sector.clip(gRange)) {
238 continue;
239 }
240 sector.setSector(gRange.classify(qRange.q));
241
242 if (sectors.isEmpty()
243 || !sectors.get(sectors.size()-1).enlarge(sector)) {
244 sectors.add(sector);
245 }
246 } // for all gauges
247 } // for all Q ranges
248 }
249
250 public void loadKmRange(SQLQuery query) {
251 query.setInteger("column_id", columnId);
252
253 List<Object []> kms = query.list();
254
255 if (kms.isEmpty()) {
256 log.warn("No km range for column " + columnId + ".");
257 }
258 else {
259 Object [] obj = kms.get(0);
260 start = (Double)obj[0];
261 end = (Double)obj[1];
262 }
263 }
264
265 public void loadQRanges(
266 SQLQuery query,
267 GaugeFinder gaugeFinder
268 ) {
269 query.setInteger("column_id", columnId);
270 List<Object []> list = query.list();
271
272 List<QRange> qRanges = new ArrayList<QRange>(list.size());
273
274 for (Object [] row: list) {
275 double q = (Double)row[0];
276 double start = (Double)row[1];
277 double end = (Double)row[2];
278 QRange qRange = new QRange(start, end, q);
279 if (qRange.clip(this)) {
280 qRanges.add(qRange);
281 }
282 }
283
284 buildSectors(gaugeFinder, qRanges);
285 }
286 } // class Column
287
288 protected int wstId;
289 protected String description;
290 protected List<Column> columns;
291
292 public Fixing() {
293 }
294
295 public int getId() {
296 return wstId;
297 }
298
299 public String getDescription() {
300 return description;
301 }
302
303 public Fixing(int wstId, String description) {
304 this.wstId = wstId;
305 this.description = description;
306 columns = new ArrayList<Column>();
307 }
308
309 public void loadColumns(SQLQuery query) {
310 query.setInteger("wst_id", wstId);
311 List<Object []> list = query.list();
312 for (Object [] row: list) {
313 int columnId = (Integer)row[0];
314 Date startTime = (Date) row[1];
315 String name = (String) row[2];
316 columns.add(new Column(columnId, startTime, name));
317 }
318 }
319
320 public void loadColumnsKmRange(SQLQuery query) {
321 for (Column column: columns) {
322 column.loadKmRange(query);
323 }
324 }
325
326 public void adjustExtent(Range extent) {
327 for (Column column: columns) {
328 extent.extend(column);
329 }
330 }
331
332 public void loadColumnsQRanges(
333 SQLQuery query,
334 GaugeFinder gaugeFinder
335 ) {
336 for (Column column: columns) {
337 column.loadQRanges(query, gaugeFinder);
338 }
339 }
340
341 /**
342 * @param allColumns[out] Columns will be put here.
343 * @param range can be null.
344 * @param filter filter to apply.
345 */
346 public void addAllColumns(
347 List<Column> allColumns,
348 Range range,
349 Filter filter
350 ) {
351 for (Column column: columns) {
352 if ((range == null || column.hasSectorsInRange(range))
353 && (filter == null || filter.accept(column))) {
354 allColumns.add(column);
355 }
356 }
357 }
358 } // class Fixing
359
360
361 protected String riverName;
362 protected int riverId;
363 protected boolean isKmUp;
364 protected List<Fixing> fixings;
365 protected Range extent;
366
367 public FixingsOverview() {
368 fixings = new ArrayList<Fixing>();
369 extent = new Range(Double.MAX_VALUE, -Double.MAX_VALUE);
370 }
371
372 public FixingsOverview(String riverName) {
373 this();
374 this.riverName = riverName;
375 }
376
377 protected boolean loadRiver(Session session) {
378 SQLQuery query = session.createSQLQuery(SQL_RIVER_ID)
379 .addScalar("river_id", StandardBasicTypes.INTEGER)
380 .addScalar("km_up", StandardBasicTypes.BOOLEAN);
381
382 query.setString("name", riverName);
383
384 List<Object []> list = query.list();
385
386 if (list.isEmpty()) {
387 log.warn("No river '" + riverName + "' found.");
388 return false;
389 }
390
391 Object [] row = list.get(0);
392
393 riverId = (Integer)row[0];
394 isKmUp = (Boolean)row[1];
395
396 return true;
397 }
398
399 protected void loadFixings(Session session) {
400 SQLQuery query = session.createSQLQuery(SQL_FIXINGS)
401 .addScalar("wst_id", StandardBasicTypes.INTEGER)
402 .addScalar("description", StandardBasicTypes.STRING);
403
404 query.setInteger("river_id", riverId);
405
406 List<Object []> list = query.list();
407
408 if (list.isEmpty()) {
409 log.warn("River " + riverId + " has no fixings.");
410 // Its pretty fine to have no fixings.
411 }
412
413 for (Object [] row: list) {
414 int wstId = (Integer)row[0];
415 String description = (String) row[1];
416 Fixing fixing = new Fixing(wstId, description);
417 fixings.add(fixing);
418 }
419 }
420
421 protected void loadFixingsColumns(Session session) {
422 SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMNS)
423 .addScalar("wst_column_id", StandardBasicTypes.INTEGER)
424 .addScalar("start_time", StandardBasicTypes.DATE)
425 .addScalar("name", StandardBasicTypes.STRING);
426
427 for (Fixing fixing: fixings) {
428 fixing.loadColumns(query);
429 }
430 }
431
432 protected void loadFixingsColumnsKmRange(Session session) {
433 SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_KM_RANGE)
434 .addScalar("start_km", StandardBasicTypes.DOUBLE)
435 .addScalar("stop_km", StandardBasicTypes.DOUBLE);
436
437 for (Fixing fixing: fixings) {
438 fixing.loadColumnsKmRange(query);
439 }
440 }
441
442 protected void loadFixingsColumnsQRanges(
443 Session session,
444 GaugeFinder gaugeFinder
445 ) {
446 SQLQuery query = session.createSQLQuery(SQL_FIXING_COLUMN_Q_RANGES)
447 .addScalar("q", StandardBasicTypes.DOUBLE)
448 .addScalar("start_km", StandardBasicTypes.DOUBLE)
449 .addScalar("stop_km", StandardBasicTypes.DOUBLE);
450
451 for (Fixing fixing: fixings) {
452 fixing.loadColumnsQRanges(query, gaugeFinder);
453 }
454 }
455
456 protected void adjustExtent() {
457 for (Fixing fixing: fixings) {
458 fixing.adjustExtent(extent);
459 }
460 }
461
462 public boolean load(Session session) {
463
464 if (!loadRiver(session)) {
465 return false;
466 }
467
468 GaugeFinderFactory gff = GaugeFinderFactory.getInstance();
469
470 GaugeFinder gaugeFinder = gff.getGaugeFinder(riverId, isKmUp);
471
472 if (gaugeFinder == null) {
473 return false;
474 }
475
476 loadFixings(session);
477 loadFixingsColumns(session);
478 loadFixingsColumnsKmRange(session);
479
480 adjustExtent();
481
482 loadFixingsColumnsQRanges(session, gaugeFinder);
483
484 return true;
485 }
486
487 public static final Range FULL_EXTENT =
488 new Range(-Double.MAX_VALUE, Double.MAX_VALUE);
489
490 public static final Fixing.Filter ACCEPT = new Fixing.Filter() {
491 @Override
492 public boolean accept(Fixing.Column column) {
493 return true;
494 }
495 };
496
497 public static class NotFilter implements Fixing.Filter {
498 protected Fixing.Filter child;
499
500 public NotFilter(Fixing.Filter child) {
501 this.child = child;
502 }
503
504 @Override
505 public boolean accept(Fixing.Column column) {
506 return !child.accept(column);
507 }
508 } // class NotFilter
509
510 public static abstract class ComponentFilter implements Fixing.Filter {
511 protected List<Fixing.Filter> children;
512
513 public ComponentFilter() {
514 children = new ArrayList<Fixing.Filter>();
515 }
516
517 public ComponentFilter(List<Fixing.Filter> children) {
518 this.children = children;
519 }
520
521 public ComponentFilter add(Fixing.Filter filter) {
522 children.add(filter);
523 return this;
524 }
525 } // class ComponentFilter
526
527 public static class OrFilter extends ComponentFilter {
528
529 public OrFilter() {
530 }
531
532 public OrFilter(List<Fixing.Filter> children) {
533 super(children);
534 }
535
536 @Override
537 public boolean accept(Fixing.Column column) {
538 for (Fixing.Filter child: children) {
539 if (child.accept(column)) {
540 return true;
541 }
542 }
543 return false;
544 }
545 } // class OrFilter
546
547 public static class AndFilter extends ComponentFilter {
548
549 public AndFilter() {
550 }
551
552 public AndFilter(List<Fixing.Filter> children) {
553 super(children);
554 }
555
556 @Override
557 public boolean accept(Fixing.Column column) {
558 for (Fixing.Filter child: children) {
559 if (!child.accept(column)) {
560 return false;
561 }
562 }
563 return true;
564 }
565 } // class AndFilter
566
567 public static class IdFilter implements Fixing.Filter {
568
569 protected int columnId;
570
571 public IdFilter(int columnId) {
572 this.columnId = columnId;
573 }
574
575 @Override
576 public boolean accept(Fixing.Column column) {
577 return column.getId() == columnId;
578 }
579 } // class IdFilter
580
581 /** Accept Fixing columns whose id is in id list. */
582 public static class IdsFilter implements Fixing.Filter {
583
584 protected int [] columnIds;
585
586 public IdsFilter(int [] columnIds) {
587 this.columnIds = columnIds;
588 }
589
590 @Override
591 public boolean accept(Fixing.Column column) {
592 int cid = column.getId();
593 for (int i = columnIds.length-1; i >= 0; --i) {
594 if (columnIds[i] == cid) {
595 return true;
596 }
597 }
598 return false;
599 }
600 } // class IdFilter
601
602 public static class DateFilter implements Fixing.Filter {
603
604 protected Date date;
605
606 public DateFilter(Date date) {
607 this.date = date;
608 }
609
610 @Override
611 public boolean accept(Fixing.Column column) {
612 return date.equals(column.getStartTime());
613 }
614 } // class DateFilter
615
616 public static class DateRangeFilter implements Fixing.Filter {
617
618 protected Date start;
619 protected Date end;
620
621 public DateRangeFilter(Date start, Date end) {
622 this.start = start;
623 this.end = end;
624 }
625
626 @Override
627 public boolean accept(Fixing.Column column) {
628 Date date = column.getStartTime();
629 return start.compareTo(date) <= 0 && end.compareTo(date) >= 0;
630 }
631 } // class DateRangeFilter
632
633 public static class SectorFilter implements Fixing.Filter {
634
635 protected int sector;
636
637 public SectorFilter(int sector) {
638 this.sector = sector;
639 }
640
641 @Override
642 public boolean accept(Fixing.Column column) {
643 for (SectorRange s: column.getSectors()) {
644 if (s.getSector() == sector) {
645 return true;
646 }
647 }
648 return false;
649 }
650 } // class SectorFilter
651
652 public static class SectorRangeFilter implements Fixing.Filter {
653
654 protected int min;
655 protected int max;
656
657 public SectorRangeFilter(int min, int max) {
658 this.min = Math.min(min, max);
659 this.max = Math.max(min, max);
660 }
661
662 @Override
663 public boolean accept(Fixing.Column column) {
664 for (SectorRange s: column.getSectors()) {
665 int v = s.getSector();
666 if (v < min || v > max) {
667 return false;
668 }
669 }
670 return true;
671 }
672 } // class SectorRangeFilter
673
674 public static class KmFilter implements Fixing.Filter {
675
676 protected double km;
677
678 public KmFilter(double km) {
679 this.km = km;
680 }
681
682 @Override
683 public boolean accept(Fixing.Column column) {
684 for (SectorRange s: column.getSectors()) {
685 if (s.inside(km)) {
686 return true;
687 }
688 }
689 return false;
690 }
691 } // class KmFilter
692
693 public void generateOverview(Document document) {
694 generateOverview(document, FULL_EXTENT, ACCEPT);
695 }
696
697 public List<Fixing.Column> filter(Range range, Fixing.Filter filter) {
698 List<Fixing.Column> allColumns = new ArrayList<Fixing.Column>();
699
700 for (Fixing fixing: fixings) {
701 fixing.addAllColumns(allColumns, range, filter);
702 }
703
704 Collections.sort(allColumns, Fixing.DATE_CMP);
705
706 return allColumns;
707 }
708
709 protected static Range realRange(List<Fixing.Column> columns) {
710 Range range = null;
711 for (Fixing.Column column: columns) {
712 if (range == null) {
713 range = new Range(column);
714 }
715 else {
716 range.extend(column);
717 }
718 }
719 return range;
720 }
721
722 protected Element intersectingGauges(Document document, Range range) {
723 Element gauges = document.createElement("gauges");
724
725 if (range == null) {
726 return gauges;
727 }
728
729 GaugeFinderFactory gff = GaugeFinderFactory.getInstance();
730
731 GaugeFinder gf = gff.getGaugeFinder(riverId, isKmUp);
732
733 if (gf == null) {
734 return gauges;
735 }
736
737 for (GaugeRange gr: gf.getGauges()) {
738 if (gr.intersects(range)) {
739 Element gauge = document.createElement("gauge");
740 gauge.setAttribute("from", String.valueOf(gr.getStart()));
741 gauge.setAttribute("to", String.valueOf(gr.getEnd()));
742 gauge.setAttribute("name", gr.getName());
743 gauges.appendChild(gauge);
744 }
745 }
746
747 return gauges;
748 }
749
750 /** Populate document with fixings, filtered by range and filter. */
751 public void generateOverview(
752 Document document,
753 Range range,
754 Fixing.Filter filter
755 ) {
756 List<Fixing.Column> allColumns = filter(range, filter);
757
758 Element fixingsElement = document.createElement("fixings");
759
760 Element riverElement = document.createElement("river");
761
762 riverElement.setAttribute("from", String.valueOf(extent.start));
763 riverElement.setAttribute("to", String.valueOf(extent.end));
764 riverElement.setAttribute("rid", String.valueOf(riverId));
765 riverElement.setAttribute("name", riverName);
766
767 fixingsElement.appendChild(riverElement);
768
769 fixingsElement.appendChild(
770 intersectingGauges(
771 document,
772 realRange(allColumns)));
773
774 SimpleDateFormat df = new SimpleDateFormat(DATE_FORMAT);
775
776 Element esE = document.createElement("events");
777
778 for (Fixing.Column column: allColumns) {
779
780 List<SectorRange> sectors = column.getSectors(range);
781
782 if (!sectors.isEmpty()) {
783 Element eE = document.createElement("event");
784 eE.setAttribute("description",
785 String.valueOf(column.getDescription()));
786 eE.setAttribute("cid", String.valueOf(column.columnId));
787 eE.setAttribute("date", df.format(column.startTime));
788
789 for (SectorRange sector: sectors) {
790 Element sE = document.createElement("sector");
791
792 sE.setAttribute("from", String.valueOf(sector.start));
793 sE.setAttribute("to", String.valueOf(sector.end));
794 sE.setAttribute("class", String.valueOf(sector.sector));
795
796 eE.appendChild(sE);
797 }
798
799 esE.appendChild(eE);
800 }
801 }
802
803 fixingsElement.appendChild(esE);
804
805 document.appendChild(fixingsElement);
806 }
807 }
808 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org