comparison artifacts/src/main/java/org/dive4elements/river/artifacts/charts/CrossSectionApp.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/charts/CrossSectionApp.java@bd047b71ab37
children 4897a58c8746
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.artifacts.charts;
2
3 import org.dive4elements.river.backend.SessionFactoryProvider;
4
5 import org.dive4elements.river.artifacts.geom.Lines;
6
7 import org.dive4elements.river.model.CrossSection;
8 import org.dive4elements.river.model.CrossSectionLine;
9 import org.dive4elements.river.model.CrossSectionPoint;
10
11 import org.dive4elements.river.utils.Pair;
12
13 import org.dive4elements.river.jfree.StableXYDifferenceRenderer;
14
15 import java.awt.BorderLayout;
16 import java.awt.Dimension;
17 import java.awt.FlowLayout;
18
19 import java.awt.event.ActionEvent;
20 import java.awt.event.ActionListener;
21 import java.awt.event.ItemEvent;
22 import java.awt.event.ItemListener;
23
24 import java.awt.geom.Point2D;
25 import java.awt.geom.Rectangle2D;
26
27 import java.io.File;
28 import java.io.FileWriter;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31
32 import java.math.BigDecimal;
33 import java.math.MathContext;
34
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.TreeMap;
40
41 import java.awt.Color;
42 import java.awt.Paint;
43 import java.awt.TexturePaint;
44
45 import java.awt.image.BufferedImage;
46
47 import javax.swing.DefaultComboBoxModel;
48 import javax.swing.JButton;
49 import javax.swing.JComboBox;
50 import javax.swing.JPanel;
51 import javax.swing.JScrollPane;
52 import javax.swing.JTable;
53 import javax.swing.JTextField;
54
55 import javax.swing.event.TableModelEvent;
56 import javax.swing.event.TableModelListener;
57
58 import javax.swing.table.AbstractTableModel;
59
60 import org.hibernate.Query;
61 import org.hibernate.Session;
62
63 import org.jfree.chart.ChartFactory;
64 import org.jfree.chart.ChartPanel;
65 import org.jfree.chart.ChartUtilities;
66 import org.jfree.chart.JFreeChart;
67
68 import org.jfree.chart.axis.NumberAxis;
69
70 import org.jfree.chart.plot.PlotOrientation;
71 import org.jfree.chart.plot.XYPlot;
72
73 import org.jfree.data.xy.DefaultXYDataset;
74 import org.jfree.data.xy.XYSeries;
75 import org.jfree.data.xy.XYDataset;
76 import org.jfree.data.xy.XYSeriesCollection;
77 import org.jfree.chart.renderer.xy.XYItemRenderer;
78
79 import org.jfree.ui.ApplicationFrame;
80 import org.jfree.ui.RefineryUtilities;
81
82
83 /**
84 * Standalone tech-demo.
85 */
86 public class CrossSectionApp
87 extends ApplicationFrame
88 {
89 public static final String RIVER = System.getProperty("river", "Saar");
90
91 public static final String WATER_LEVEL = System.getProperty("waterlevel");
92
93 public static final String KM = System.getProperty("km");
94
95 public static final double EPSILON = 1e-4;
96
97 protected Session session;
98
99 protected JComboBox crossSectionLinesCB;
100 protected JTextField waterlevelTF;
101
102 protected ChartPanel chartPanel;
103
104 protected Double lastWaterLevel;
105
106 protected List<CrossSection> crossSections;
107 protected boolean [] drawCrossSection;
108 protected boolean [] drawWaterLevel;
109 protected boolean [] drawGround;
110 protected boolean [] drawFill;
111
112 protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines;
113
114 protected static final Paint TRANSPARENT = createTransparentPaint();
115
116 public class CrossSectionTableModel extends AbstractTableModel {
117
118 @Override
119 public String getColumnName(int col) {
120 switch (col) {
121 case 0: return "Peilungsname";
122 case 1: return "Peilung";
123 case 2: return "Wasserstand";
124 case 3: return "Boden";
125 case 4: return "Wasser";
126 }
127 return "";
128 }
129
130 @Override
131 public int getColumnCount() {
132 return 5;
133 }
134
135 @Override
136 public int getRowCount() {
137 return crossSections != null ? crossSections.size() : 0;
138 }
139
140 @Override
141 public Object getValueAt(int row, int col) {
142 if (crossSections == null) return null;
143 switch (col) {
144 case 0: return crossSections.get(row).getDescription();
145 case 1: return drawCrossSection[row];
146 case 2: return drawWaterLevel[row];
147 case 3: return drawGround[row];
148 case 4: return drawFill[row];
149 }
150 return null;
151 }
152
153 @Override
154 public void setValueAt(Object value, int row, int col) {
155 switch (col) {
156 case 1:
157 if (change(drawCrossSection, row, (Boolean)value)) {
158 fireTableCellUpdated(row, col);
159 }
160 break;
161 case 2:
162 if (change(drawWaterLevel, row, (Boolean)value)) {
163 fireTableCellUpdated(row, col);
164 }
165 break;
166 case 3:
167 if (change(drawGround, row, (Boolean)value)) {
168 fireTableCellUpdated(row, col);
169 }
170 break;
171 case 4:
172 if (change(drawFill, row, (Boolean)value)) {
173 fireTableCellUpdated(row, col);
174 }
175 break;
176 }
177 }
178
179 @Override
180 public Class<?> getColumnClass(int columnIndex) {
181 switch (columnIndex) {
182 case 0: return String.class;
183 case 1:
184 case 2:
185 case 3:
186 case 4: return Boolean.class;
187 }
188 return null;
189 }
190
191 @Override
192 public boolean isCellEditable(
193 int rowIndex,
194 int columnIndex
195 ) {
196 return columnIndex >= 1 && columnIndex <= 4;
197 }
198 } // class CrossSectionTableModel
199
200 private static boolean change(
201 boolean [] values,
202 int index,
203 boolean value
204 ) {
205 if (values[index] != value) {
206 values[index] = value;
207 return true;
208 }
209 return false;
210 }
211
212 public static class CrossSectionLineItem {
213
214 Double km;
215 List<Pair<CrossSection, CrossSectionLine>> lines;
216
217 public CrossSectionLineItem(
218 Double km,
219 List<Pair<CrossSection, CrossSectionLine>> lines
220 ) {
221 this.km = km;
222 this.lines = lines;
223 }
224
225 public String toString() {
226 return String.valueOf(km);
227 }
228 } // CrossSectionLineItem
229
230 public CrossSectionApp(String title) {
231 super(title);
232
233 session = SessionFactoryProvider
234 .createSessionFactory()
235 .openSession();
236
237 JPanel content = createContent();
238 content.setPreferredSize(new Dimension(800, 480));
239 setContentPane(content);
240 }
241
242
243 /** Query and return CrossSections for named river. */
244 public List<CrossSection> crossSections(String river) {
245 Query query = session.createQuery(
246 "from CrossSection where river.name = :river");
247 query.setParameter("river", river);
248 return query.list();
249 }
250
251 protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>>
252 loadAllLines(List<CrossSection> crossSections) {
253 Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines =
254 new TreeMap<Double, List<Pair<CrossSection, CrossSectionLine>>>();
255 for (CrossSection cs: crossSections) {
256 List<CrossSectionLine> lines = cs.getLines();
257 for (CrossSectionLine csl: lines) {
258 Double km = Math.round(csl.getKm().doubleValue() * 1000d)/1000d;
259 List<Pair<CrossSection, CrossSectionLine>> ls
260 = km2lines.get(km);
261 if (ls == null) {
262 ls = new ArrayList<Pair<CrossSection, CrossSectionLine>>(2);
263 km2lines.put(km, ls);
264 }
265 ls.add(new Pair<CrossSection, CrossSectionLine>(cs, csl));
266 }
267 }
268 return km2lines;
269 }
270
271 public JPanel createContent() {
272 JPanel panel = new JPanel(new BorderLayout());
273
274 JPanel nav = new JPanel(new FlowLayout());
275
276 crossSections = crossSections(RIVER);
277 km2lines = loadAllLines(crossSections);
278
279 int CS = crossSections.size();
280 Arrays.fill(drawCrossSection = new boolean[CS], true);
281 drawWaterLevel = new boolean[CS];
282 drawGround = new boolean[CS];
283 drawFill = new boolean[CS];
284
285 Object [] clis = createCrossSectionLineItems(km2lines);
286
287 DefaultComboBoxModel dcbm = new DefaultComboBoxModel(clis);
288
289 crossSectionLinesCB = new JComboBox(dcbm);
290
291 if (KM != null) {
292 try {
293 double km = Double.parseDouble(KM);
294
295 CrossSectionLineItem found = null;
296
297 for (Object o: clis) {
298 CrossSectionLineItem csli = (CrossSectionLineItem)o;
299 if (Math.abs(csli.km - km) < EPSILON) {
300 found = csli;
301 break;
302 }
303 }
304
305 if (found != null) {
306 crossSectionLinesCB.setSelectedItem(found);
307 }
308 }
309 catch (NumberFormatException nfe) {
310 System.err.println("km is not a number: "
311 + nfe.getMessage());
312 }
313 }
314
315 nav.add(crossSectionLinesCB);
316
317 crossSectionLinesCB.addItemListener(new ItemListener() {
318 @Override
319 public void itemStateChanged(ItemEvent ie) {
320 if (ie.getStateChange() == ItemEvent.SELECTED) {
321 updateChart();
322 }
323 }
324 });
325
326 waterlevelTF = new JTextField(5);
327
328 if (WATER_LEVEL != null) {
329 try {
330 waterlevelTF.setText(
331 (lastWaterLevel = Double.valueOf(WATER_LEVEL)).toString());
332 }
333 catch (NumberFormatException nfe) {
334 System.err.println("Water level not a number: " +
335 nfe.getMessage());
336 }
337 }
338
339 waterlevelTF.addActionListener(new ActionListener() {
340 @Override
341 public void actionPerformed(ActionEvent ae) {
342 waterLevelChanged();
343 }
344 });
345
346 nav.add(waterlevelTF);
347
348 JButton dump = new JButton("dump");
349
350 dump.addActionListener(new ActionListener() {
351 @Override
352 public void actionPerformed(ActionEvent ae) {
353 dumpData();
354 }
355 });
356
357 nav.add(dump);
358
359
360 chartPanel = createChartPanel();
361
362 panel.add(chartPanel, BorderLayout.CENTER);
363
364
365 CrossSectionTableModel cstm = new CrossSectionTableModel();
366
367 cstm.addTableModelListener(new TableModelListener() {
368 @Override
369 public void tableChanged(TableModelEvent e) {
370 updateChart();
371 }
372 });
373
374 JTable crossTable = new JTable(cstm);
375
376 JPanel west = new JPanel(new BorderLayout());
377 JScrollPane scrollPane = new JScrollPane(crossTable);
378 west.add(scrollPane);
379
380 west.add(nav, BorderLayout.SOUTH);
381
382 panel.add(west, BorderLayout.WEST);
383
384 return panel;
385 }
386
387 protected void waterLevelChanged() {
388 String value = waterlevelTF.getText();
389 try {
390 lastWaterLevel = Double.valueOf(value);
391 }
392 catch (NumberFormatException nfe) {
393 waterlevelTF.setText(
394 lastWaterLevel != null ? lastWaterLevel.toString() : "");
395 return;
396 }
397 updateChart();
398 }
399
400 protected void updateChart() {
401
402 JFreeChart chart = createChart();
403
404 chartPanel.setChart(chart);
405 }
406
407 protected ChartPanel createChartPanel() {
408
409 JFreeChart chart = createChart();
410
411 return new ChartPanel(chart);
412 }
413
414 protected void dumpData() {
415
416 CrossSectionLineItem csli =
417 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
418
419 if (csli == null) {
420 return;
421 }
422
423 double km = Math.round(csli.km.doubleValue() * 1000d)/1000d;
424
425 String kmS = String.valueOf(km).replace(".", "-");
426
427 int i = 1;
428 File file = new File("cross-section-" + kmS + ".txt");
429 while (file.exists()) {
430 file = new File("cross-section-" + kmS + "[" + (i++) + "].txt");
431 }
432
433 System.err.println("dump points to file '" + file + "'");
434
435 PrintWriter out = null;
436
437 MathContext mc = new MathContext(3);
438
439 try {
440 out =
441 new PrintWriter(
442 new FileWriter(file));
443
444 for (Pair<CrossSection, CrossSectionLine> pair: csli.lines) {
445 out.println("# " + pair.getA().getDescription());
446 for (CrossSectionPoint point: pair.getB().getPoints()) {
447 out.println(
448 new BigDecimal(point.getX()).round(mc) + " " +
449 new BigDecimal(point.getY()).round(mc));
450 }
451 }
452
453 out.flush();
454 }
455 catch (IOException ioe) {
456 ioe.printStackTrace();
457 }
458 finally {
459 if (out != null) {
460 out.close();
461 }
462 }
463 }
464
465 public void generateWaterLevels(
466 List<Point2D> points,
467 List<Pair<XYDataset, XYItemRenderer>> datasets
468 ) {
469 if (points == null || points.isEmpty() || lastWaterLevel == null) {
470 return;
471 }
472
473 double [][] data = Lines.createWaterLines(points, lastWaterLevel).points;
474 XYSeries series =
475 new XYSeries(String.valueOf(lastWaterLevel), false);
476
477 double [] x = data[0];
478 double [] y = data[1];
479 for (int i = 0; i < x.length; ++i) {
480 series.add(x[i], y[i], false);
481 }
482
483 datasets.add(new Pair<XYDataset, XYItemRenderer>(
484 new XYSeriesCollection(series), null));
485 }
486
487 public void generateFill(
488 List<Point2D> points,
489 String legend,
490 List<Pair<XYDataset, XYItemRenderer>> datasets
491 ) {
492 if (points == null || points.isEmpty() || lastWaterLevel == null) {
493 return;
494 }
495
496 double [][] data = Lines.createWaterLines(points, lastWaterLevel).points;
497 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
498
499 DefaultXYDataset dataset = new DefaultXYDataset();
500
501 dataset.addSeries(legend + "-Linie", values);
502 dataset.addSeries(legend + "-Fl\u00e4che", data);
503
504 datasets.add(new Pair<XYDataset, XYItemRenderer>(
505 dataset,
506 new StableXYDifferenceRenderer(
507 TRANSPARENT, Color.blue, false)));
508 }
509
510 public void generateProfile(
511 List<Point2D> points,
512 String legend,
513 List<Pair<XYDataset, XYItemRenderer>> datasets
514 ) {
515 if (points == null || points.isEmpty()) {
516 return;
517 }
518
519 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
520
521 XYSeries series = new XYSeries(legend, false);
522
523 double [] x = values[0];
524 double [] y = values[1];
525 for (int i = 0; i < x.length; ++i) {
526 series.add(x[i], y[i], false);
527 }
528
529 datasets.add(new Pair<XYDataset, XYItemRenderer>(
530 new XYSeriesCollection(series), null));;
531 }
532
533
534 /**
535 * @param legend the legend entry.
536 */
537 public void generateGround(
538 List<Point2D> points,
539 String legend,
540 List<Pair<XYDataset, XYItemRenderer>> datasets
541 ) {
542 if (points == null || points.isEmpty()) {
543 return;
544 }
545
546 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);
547
548 DefaultXYDataset dataset = new DefaultXYDataset();
549
550 dataset.addSeries(legend, values);
551
552 StableXYDifferenceRenderer renderer =
553 new StableXYDifferenceRenderer();
554
555 datasets.add(new Pair<XYDataset, XYItemRenderer>(
556 dataset, renderer));
557 }
558
559 public List<Pair<XYDataset, XYItemRenderer>> generateDatasets() {
560
561 List<Pair<XYDataset, XYItemRenderer>> datasets =
562 new ArrayList<Pair<XYDataset, XYItemRenderer>>();
563
564 CrossSectionLineItem csli =
565 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();
566
567 for (int i = 0; i < drawCrossSection.length; ++i) {
568 List<Point2D> points = null;
569 CrossSection cs = crossSections.get(i);
570
571 if (drawGround[i]) {
572 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
573 if (csl.getA() == cs) {
574 if (points == null) {
575 points = csl.getB().fetchCrossSectionLinesPoints();
576 }
577 generateGround(
578 points,
579 cs.getDescription() + "/Boden",
580 datasets);
581 break;
582 }
583 }
584 }
585
586 if (drawFill[i]) {
587 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
588 if (csl.getA() == cs) {
589 if (points == null) {
590 points = csl.getB().fetchCrossSectionLinesPoints();
591 }
592
593 generateFill(
594 points, cs.getDescription(), datasets);
595 break;
596 }
597 }
598 }
599
600 if (drawCrossSection[i]) {
601 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
602 if (csl.getA() == cs) {
603 if (points == null) {
604 points = csl.getB().fetchCrossSectionLinesPoints();
605 }
606
607 generateProfile(
608 points, cs.getDescription(), datasets);
609 break;
610 }
611 }
612 }
613
614 if (drawWaterLevel[i]) {
615 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
616 if (csl.getA() == cs) {
617 if (points == null) {
618 points = csl.getB().fetchCrossSectionLinesPoints();
619 }
620 generateWaterLevels(points, datasets);
621 break;
622 }
623 }
624 }
625
626 }
627
628 return datasets;
629 }
630
631 protected Object [] createCrossSectionLineItems(
632 Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines
633 ) {
634 Object [] result = new Object[km2lines.size()];
635 int i = 0;
636 for (Map.Entry<Double, List<Pair<CrossSection, CrossSectionLine>>> entry:
637 km2lines.entrySet()) {
638 result[i++] = new CrossSectionLineItem(
639 entry.getKey(),
640 entry.getValue());
641 }
642 return result;
643 }
644
645
646 public JFreeChart createChart() {
647 JFreeChart chart = ChartFactory.createXYLineChart(
648 null,
649 "Abstand [m]",
650 "H\u00f6he [m]",
651 null,
652 PlotOrientation.VERTICAL,
653 true,
654 true,
655 false);
656
657 List<Pair<XYDataset, XYItemRenderer>> datasets =
658 generateDatasets();
659
660 XYPlot plot = chart.getXYPlot();
661
662 for (int i = 0, N = datasets.size(); i < N; ++i) {
663 Pair<XYDataset, XYItemRenderer> p = datasets.get(i);
664 plot.setDataset(i, p.getA());
665 plot.mapDatasetToRangeAxis(i, 0);
666 XYItemRenderer renderer = p.getB();
667 if (renderer != null) {
668 plot.setRenderer(i, renderer);
669 }
670 }
671
672 NumberAxis yAxis = (NumberAxis)plot.getRangeAxis();
673 yAxis.setAutoRangeIncludesZero(false);
674
675 ChartUtilities.applyCurrentTheme(chart);
676 return chart;
677 }
678
679 protected static Paint createTransparentPaint() {
680 BufferedImage texture = new BufferedImage(
681 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
682
683 return new TexturePaint(
684 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
685 }
686
687 public static void main(String [] args) {
688 CrossSectionApp csa = new CrossSectionApp("Querprofile");
689 csa.pack();
690 RefineryUtilities.centerFrameOnScreen(csa);
691 csa.setVisible(true);
692 }
693 }
694 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org