comparison artifacts/src/main/java/org/dive4elements/river/artifacts/services/FixingsKMChartService.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/services/FixingsKMChartService.java@bd047b71ab37
children 4897a58c8746
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.artifacts.services;
2
3 import org.dive4elements.artifactdatabase.DefaultService;
4
5 import org.dive4elements.artifacts.CallMeta;
6 import org.dive4elements.artifacts.GlobalContext;
7 import org.dive4elements.artifacts.Service;
8
9 import org.dive4elements.river.artifacts.model.FixingsColumn;
10 import org.dive4elements.river.artifacts.model.FixingsColumnFactory;
11 import org.dive4elements.river.artifacts.model.FixingsFilterBuilder;
12
13 import org.dive4elements.river.artifacts.model.FixingsOverview.Fixing;
14
15 import org.dive4elements.river.artifacts.model.FixingsOverview;
16 import org.dive4elements.river.artifacts.model.FixingsOverviewFactory;
17 import org.dive4elements.river.artifacts.model.GaugeFinder;
18 import org.dive4elements.river.artifacts.model.GaugeFinderFactory;
19 import org.dive4elements.river.artifacts.model.GaugeRange;
20
21 import org.dive4elements.river.artifacts.model.fixings.QWI;
22
23 import org.dive4elements.river.artifacts.resources.Resources;
24
25 import org.dive4elements.river.backend.SessionHolder;
26
27 import org.dive4elements.river.jfree.ShapeRenderer;
28
29 import org.dive4elements.river.utils.Formatter;
30 import org.dive4elements.river.utils.Pair;
31
32 import java.awt.BasicStroke;
33 import java.awt.Color;
34 import java.awt.Dimension;
35 import java.awt.Transparency;
36
37 import java.awt.geom.Rectangle2D;
38
39 import java.awt.image.BufferedImage;
40
41 import java.io.ByteArrayOutputStream;
42 import java.io.IOException;
43
44 import java.util.ArrayList;
45 import java.util.List;
46
47 import javax.imageio.ImageIO;
48
49 import org.apache.log4j.Logger;
50
51 import org.jfree.chart.ChartFactory;
52 import org.jfree.chart.JFreeChart;
53 import org.jfree.chart.LegendItemCollection;
54
55 import org.jfree.chart.axis.NumberAxis;
56
57 import org.jfree.chart.plot.Marker;
58 import org.jfree.chart.plot.PlotOrientation;
59 import org.jfree.chart.plot.ValueMarker;
60 import org.jfree.chart.plot.XYPlot;
61
62 import org.jfree.data.Range;
63
64 import org.jfree.ui.RectangleAnchor;
65 import org.jfree.ui.TextAnchor;
66
67 import org.w3c.dom.Document;
68 import org.w3c.dom.Element;
69 import org.w3c.dom.NodeList;
70
71
72 /** Serve chart of Fixings at certain km. */
73 public class FixingsKMChartService
74 extends DefaultService
75 {
76 private static final Logger log =
77 Logger.getLogger(FixingsKMChartService.class);
78
79 public static final int DEFAULT_WIDTH = 240;
80 public static final int DEFAULT_HEIGHT = 180;
81
82 public static final String [] I18N_Q_SECTOR_BOARDERS = {
83 "fix.km.chart.q.sector.border0",
84 "fix.km.chart.q.sector.border1",
85 "fix.km.chart.q.sector.border2"
86 };
87
88 public static final String [] DEFAULT_Q_SECTOR_BORDERS = {
89 "(MNQ + MQ)/2",
90 "(MQ + MHQ)/2",
91 "HQ5"
92 };
93
94 public static final String I18N_CHART_LABEL_DATE =
95 "fix.km.chart.label.date";
96
97 public static final String DEFAULT_CHART_LABEL_DATE =
98 "yyyy/MM/dd";
99
100 public static final String I18N_CHART_TITLE =
101 "fix.km.chart.title";
102
103 public static final String DEFAULT_CHART_TITLE =
104 "Fixings {0} km {1,number,#.###}";
105
106 public static final String I18N_Q_AXIS =
107 "fix.km.chart.q.axis";
108
109 public static final String DEFAULT_Q_AXIS =
110 "Q [m\u00b3/s]";
111
112 public static final String I18N_W_AXIS =
113 "fix.km.chart.w.axis";
114
115 public static final String DEFAULT_W_AXIS =
116 "W [NN + m]";
117
118 public static final String I18N_MEASURED =
119 "fix.km.chart.measured";
120
121 public static final String DEFAULT_MEASURED =
122 "measured";
123
124 public static final String I18N_INTERPOLATED =
125 "fix.km.chart.interpolated";
126
127 public static final String DEFAULT_INTERPOLATED =
128 "interpolated";
129
130 public static final String DEFAULT_FORMAT = "png";
131
132 // TODO: Load fancy image from resources.
133 public static final byte [] EMPTY = {
134 (byte)0x89, (byte)0x50, (byte)0x4e, (byte)0x47,
135 (byte)0x0d, (byte)0x0a, (byte)0x1a, (byte)0x0a,
136 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0d,
137 (byte)0x49, (byte)0x48, (byte)0x44, (byte)0x52,
138 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
139 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
140 (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00,
141 (byte)0x00, (byte)0x3a, (byte)0x7e, (byte)0x9b,
142 (byte)0x55, (byte)0x00, (byte)0x00, (byte)0x00,
143 (byte)0x01, (byte)0x73, (byte)0x52, (byte)0x47,
144 (byte)0x42, (byte)0x00, (byte)0xae, (byte)0xce,
145 (byte)0x1c, (byte)0xe9, (byte)0x00, (byte)0x00,
146 (byte)0x00, (byte)0x09, (byte)0x70, (byte)0x48,
147 (byte)0x59, (byte)0x73, (byte)0x00, (byte)0x00,
148 (byte)0x0b, (byte)0x13, (byte)0x00, (byte)0x00,
149 (byte)0x0b, (byte)0x13, (byte)0x01, (byte)0x00,
150 (byte)0x9a, (byte)0x9c, (byte)0x18, (byte)0x00,
151 (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x74,
152 (byte)0x49, (byte)0x4d, (byte)0x45, (byte)0x07,
153 (byte)0xdc, (byte)0x04, (byte)0x04, (byte)0x10,
154 (byte)0x30, (byte)0x15, (byte)0x7d, (byte)0x77,
155 (byte)0x36, (byte)0x0b, (byte)0x00, (byte)0x00,
156 (byte)0x00, (byte)0x08, (byte)0x74, (byte)0x45,
157 (byte)0x58, (byte)0x74, (byte)0x43, (byte)0x6f,
158 (byte)0x6d, (byte)0x6d, (byte)0x65, (byte)0x6e,
159 (byte)0x74, (byte)0x00, (byte)0xf6, (byte)0xcc,
160 (byte)0x96, (byte)0xbf, (byte)0x00, (byte)0x00,
161 (byte)0x00, (byte)0x0a, (byte)0x49, (byte)0x44,
162 (byte)0x41, (byte)0x54, (byte)0x08, (byte)0xd7,
163 (byte)0x63, (byte)0xf8, (byte)0x0f, (byte)0x00,
164 (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x00,
165 (byte)0x1b, (byte)0xb6, (byte)0xee, (byte)0x56,
166 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
167 (byte)0x49, (byte)0x45, (byte)0x4e, (byte)0x44,
168 (byte)0xae, (byte)0x42, (byte)0x60, (byte)0x82
169 };
170
171 private static final Output empty() {
172 return new Output(EMPTY, "image/png");
173 }
174
175 @Override
176 public Service.Output process(
177 Document data,
178 GlobalContext globalContext,
179 CallMeta callMeta
180 ) {
181 log.debug("FixingsKMChartService.process");
182
183 SessionHolder.acquire();
184 try {
185 return doProcess(data, globalContext, callMeta);
186 }
187 finally {
188 SessionHolder.HOLDER.get().close();
189 SessionHolder.release();
190 }
191 }
192
193 protected Service.Output doProcess(
194 Document input,
195 GlobalContext globalContext,
196 CallMeta callMeta
197 ) {
198 String river = getRiverName(input);
199 Double km = getKM(input);
200 Dimension extent = getExtent(input);
201 String format = getFormat(input);
202
203 if (river == null || km == null) {
204 log.warn("River and/or km invalid.");
205 return empty();
206 }
207
208 FixingsOverview overview = FixingsOverviewFactory.getOverview(river);
209
210 if (overview == null) {
211 log.warn("No overview found for river '" + river + "'");
212 return empty();
213 }
214
215 FixingsFilterBuilder ffb = new FixingsFilterBuilder(input);
216
217 List<Fixing.Column> columns = overview.filter(
218 ffb.getRange(),
219 ffb.getFilter());
220
221 List<Pair<Fixing.Column, FixingsColumn>> cols =
222 new ArrayList<Pair<Fixing.Column, FixingsColumn>>();
223
224 for (Fixing.Column col: columns) {
225 FixingsColumn data =
226 FixingsColumnFactory.INSTANCE.getColumnData(col);
227 if (data != null) {
228 cols.add(new Pair<Fixing.Column, FixingsColumn>(col, data));
229 }
230 }
231
232 JFreeChart chart = createChart(cols, river, km, callMeta);
233
234 return encode(chart, extent, format);
235 }
236
237 protected static Output encode(
238 JFreeChart chart,
239 Dimension extent,
240 String format
241 ) {
242 BufferedImage image = chart.createBufferedImage(
243 extent.width, extent.height,
244 Transparency.BITMASK,
245 null);
246
247 ByteArrayOutputStream out = new ByteArrayOutputStream();
248
249 try {
250 ImageIO.write(image, format, out);
251 }
252 catch (IOException ioe) {
253 log.warn("writing image failed", ioe);
254 return empty();
255 }
256
257 return new Output(out.toByteArray(), "image/" + format);
258 }
259
260 protected static JFreeChart createChart(
261 List<Pair<Fixing.Column, FixingsColumn>> cols,
262 String river,
263 double km,
264 CallMeta callMeta
265 ) {
266 String labelFormat = Resources.getMsg(
267 callMeta, I18N_CHART_LABEL_DATE, DEFAULT_CHART_LABEL_DATE);
268
269 QWSeriesCollection.LabelGenerator lg =
270 new QWSeriesCollection.DateFormatLabelGenerator(labelFormat);
271
272 QWSeriesCollection dataset = new QWSeriesCollection(lg);
273
274 double [] w = new double[1];
275 for (Pair<Fixing.Column, FixingsColumn> col: cols) {
276 boolean interpolated = !col.getB().getW(km, w);
277 double q = col.getB().getQ(km);
278 if (!Double.isNaN(w[0]) && !Double.isNaN(q)) {
279 QWI qw = new QWI(
280 q, w[0],
281 col.getA().getDescription(),
282 col.getA().getStartTime(),
283 interpolated, 0);
284 dataset.add(qw);
285 }
286 }
287
288 String title = Resources.format(
289 callMeta, I18N_CHART_TITLE, DEFAULT_CHART_TITLE, river, km);
290
291 String qAxis = Resources.getMsg(
292 callMeta, I18N_Q_AXIS, DEFAULT_Q_AXIS);
293
294 String wAxis = Resources.getMsg(
295 callMeta, I18N_W_AXIS, DEFAULT_W_AXIS);
296
297 JFreeChart chart = ChartFactory.createXYLineChart(
298 title,
299 qAxis,
300 wAxis,
301 null,
302 PlotOrientation.VERTICAL,
303 true,
304 true,
305 false);
306
307 XYPlot plot = (XYPlot)chart.getPlot();
308
309 NumberAxis qA = (NumberAxis)plot.getDomainAxis();
310 qA.setNumberFormatOverride(Formatter.getWaterlevelQ(callMeta));
311
312 NumberAxis wA = (NumberAxis)plot.getRangeAxis();
313 wA.setNumberFormatOverride(Formatter.getWaterlevelW(callMeta));
314
315 plot.setRenderer(0, dataset.createRenderer());
316 plot.setDataset(0, dataset);
317
318 Rectangle2D area = dataset.getArea();
319
320 if (area != null) {
321 double height = area.getHeight();
322 double wInset = Math.max(height, 0.01) * 0.25d;
323
324 wA.setAutoRangeIncludesZero(false);
325 wA.setRange(new Range(
326 area.getMinY() - wInset,
327 area.getMaxY() + wInset));
328 }
329
330 final String measuredS = Resources.getMsg(
331 callMeta, I18N_MEASURED, DEFAULT_MEASURED);
332
333 final String interpolatedS = Resources.getMsg(
334 callMeta, I18N_INTERPOLATED, DEFAULT_INTERPOLATED);
335
336 LegendItemCollection lic = plot.getLegendItems();
337 dataset.addLegendItems(lic, new ShapeRenderer.LabelGenerator() {
338 @Override
339 public String createLabel(ShapeRenderer.Entry entry) {
340 return entry.getFilled() ? measuredS : interpolatedS;
341 }
342 });
343 plot.setFixedLegendItems(lic);
344
345 applyQSectorMarkers(plot, river, km, callMeta);
346
347 chart.setBackgroundPaint(Color.white);
348 plot.setBackgroundPaint(Color.white);
349 plot.setDomainGridlinePaint(Color.gray);
350 plot.setRangeGridlinePaint(Color.gray);
351 plot.setDomainGridlinesVisible(true);
352 plot.setRangeGridlinesVisible(true);
353
354 return chart;
355 }
356
357 /** Add domain markers to plot that indicate Q-sectors. */
358 protected static void applyQSectorMarkers(
359 XYPlot plot,
360 String river,
361 double km,
362 CallMeta meta
363 ) {
364 GaugeFinderFactory ggf = GaugeFinderFactory.getInstance();
365 GaugeFinder gf = ggf.getGaugeFinder(river);
366
367 if (gf == null) {
368 log.warn("No gauge finder found for river '" + river + "'");
369 return;
370 }
371
372 GaugeRange gr = gf.find(km);
373 if (gr == null) {
374 log.debug("No gauge range found for km "
375 + km + " on river " + river + ".");
376 return;
377 }
378
379 if (log.isDebugEnabled()) {
380 log.debug(gr);
381 }
382
383 for (int i = 0; i < I18N_Q_SECTOR_BOARDERS.length; ++i) {
384 String key = I18N_Q_SECTOR_BOARDERS[i];
385 String def = DEFAULT_Q_SECTOR_BORDERS[i];
386 String label = Resources.getMsg(meta, key, def);
387
388 Marker m = createQSectorMarker(
389 gr.getSectorBorder(i),
390 label);
391
392 if (m != null) {
393 plot.addDomainMarker(m);
394 }
395 }
396 }
397
398 /** Create Marker at value with label. */
399 protected static Marker createQSectorMarker(
400 double value, String label
401 ) {
402 if (Double.isNaN(value)) {
403 return null;
404 }
405 Marker m = new ValueMarker(value);
406 m.setPaint(Color.black);
407 m.setStroke(new BasicStroke());
408 m.setLabel(label);
409 m.setLabelAnchor(RectangleAnchor.TOP_LEFT);
410 m.setLabelTextAnchor(TextAnchor.TOP_LEFT);
411 return m;
412 }
413
414 protected static String getRiverName(Document input) {
415 NodeList rivers = input.getElementsByTagName("river");
416
417 if (rivers.getLength() == 0) {
418 return null;
419 }
420
421 String river = ((Element)rivers.item(0)).getAttribute("name");
422
423 return river.length() > 0 ? river : null;
424 }
425
426 protected static Double getKM(Document input) {
427 NodeList kms = input.getElementsByTagName("km");
428
429 if (kms.getLength() == 0) {
430 return null;
431 }
432
433 String km = ((Element)kms.item(0)).getAttribute("value");
434
435 try {
436 return Double.valueOf(km);
437 }
438 catch (NumberFormatException nfe) {
439 log.warn("Km '" + km + " is not a valid number.");
440 return null;
441 }
442 }
443
444 protected static Dimension getExtent(Document input) {
445
446 int width = DEFAULT_WIDTH;
447 int height = DEFAULT_HEIGHT;
448
449 NodeList extents = input.getElementsByTagName("extent");
450
451 if (extents.getLength() > 0) {
452 Element element = (Element)extents.item(0);
453 String w = element.getAttribute("width");
454 String h = element.getAttribute("height");
455
456 try {
457 width = Math.max(1, Integer.parseInt(w));
458 }
459 catch (NumberFormatException nfe) {
460 log.warn("width '" + w + "' is not a valid.");
461 }
462
463 try {
464 height = Math.max(1, Integer.parseInt(h));
465 }
466 catch (NumberFormatException nfe) {
467 log.warn("height '" + h + "' is not a valid");
468 }
469 }
470
471 return new Dimension(width, height);
472 }
473
474 protected static String getFormat(Document input) {
475 String format = DEFAULT_FORMAT;
476
477 NodeList formats = input.getElementsByTagName("format");
478
479 if (formats.getLength() > 0) {
480 String type = ((Element)formats.item(0)).getAttribute("type");
481 if (type.length() > 0) {
482 format = type;
483 }
484 }
485
486 return format;
487 }
488 }
489 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org