comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/services/FixingsKMChartService.java @ 3318:dbe2f85bf160

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

http://dive4elements.wald.intevation.org