1 package au.gov.amsa.geo.distance;
2
3 import static au.gov.amsa.geo.model.Util.formatDate;
4
5 import java.awt.Color;
6 import java.awt.Font;
7 import java.awt.Graphics2D;
8 import java.awt.Point;
9 import java.awt.image.BufferedImage;
10 import java.io.File;
11 import java.io.IOException;
12 import java.text.DecimalFormat;
13
14 import javax.imageio.ImageIO;
15
16 import org.apache.log4j.Logger;
17
18 import rx.Observable;
19 import rx.functions.Action1;
20 import rx.functions.Func1;
21 import rx.schedulers.Schedulers;
22 import au.gov.amsa.geo.distance.DistanceTravelledCalculator.CalculationResult;
23 import au.gov.amsa.geo.model.Bounds;
24 import au.gov.amsa.geo.model.CellValue;
25 import au.gov.amsa.geo.model.Options;
26 import au.gov.amsa.geo.model.Position;
27 import au.gov.amsa.geo.projection.FeatureUtil;
28 import au.gov.amsa.geo.projection.Projector;
29 import au.gov.amsa.geo.projection.ProjectorBounds;
30 import au.gov.amsa.geo.projection.ProjectorTarget;
31
32 import com.google.common.util.concurrent.AtomicDouble;
33
34
35
36
37
38 public class Renderer {
39
40 private static Logger log = Logger.getLogger(Renderer.class);
41 private final static boolean coloured = false;
42 private static final String UNICODE_GREATER_THAN_OR_EQUAL = "\u2265 ";
43
44 public static void paintAll(Graphics2D g, Options options, int numberStandardDeviations, int w,
45 int h, CalculationResult calculationResult, boolean addLegend, boolean addParameters) {
46 paintMap(g, options.getBounds(), options.getCellSizeDegreesAsDouble(),
47 numberStandardDeviations, w, h, calculationResult.getCells(), addLegend);
48 if (addParameters)
49 paintParameters(g, options, calculationResult, w, h);
50 }
51
52 private static Func1<Position, Point> epsg4326Locator(Bounds b, int w, int h) {
53
54 ProjectorBounds projectorBounds = new ProjectorBounds(FeatureUtil.EPSG_4326,
55 b.getTopLeftLon(), b.getBottomRightLat(), b.getBottomRightLon(), b.getTopLeftLat());
56 ProjectorTarget projectorTarget = new ProjectorTarget(w, h);
57 final Projector projector = new Projector(projectorBounds, projectorTarget);
58
59 return new Func1<Position, Point>() {
60
61 @Override
62 public Point call(Position p) {
63 return projector.toPoint(p.lat(), p.lon());
64 }
65 };
66 }
67
68 public static void paintMap(Graphics2D g, final Bounds b, double cellSizeDegrees,
69 double numberStandardDeviationsForHighValue, final int w, final int h,
70 Observable<CellValue> cells, boolean addLegend) {
71
72 paintMap(g, b, cellSizeDegrees, numberStandardDeviationsForHighValue, w, h, cells,
73 addLegend, epsg4326Locator(b, w, h));
74 }
75
76 public static void paintMap(final Graphics2D g, Bounds b, final double cellSizeDegrees,
77 double numberStandardDeviationsForHighValue, final int w, final int h,
78 Observable<CellValue> cells, final boolean addLegend,
79 final Func1<Position, Point> locator) {
80
81 final Statistics metrics = getStatistics(cells);
82 final double maxNmForColour = metrics.mean + numberStandardDeviationsForHighValue
83 * metrics.sd;
84
85 final double minSaturation = 0.05;
86 cells.observeOn(Schedulers.immediate()).subscribeOn(Schedulers.immediate())
87
88 .doOnNext(new Action1<CellValue>() {
89 @Override
90 public void call(CellValue cell) {
91 double topLeftLat = cell.getCentreLat() + cellSizeDegrees / 2;
92 double topLeftLon = cell.getCentreLon() - cellSizeDegrees / 2;
93 double bottomRightLat = cell.getCentreLat() - cellSizeDegrees / 2;
94 double bottomRightLon = cell.getCentreLon() + cellSizeDegrees / 2;
95 Point topLeft = locator.call(new Position(topLeftLat, topLeftLon));
96 Point bottomRight = locator.call(new Position(bottomRightLat,
97 bottomRightLon));
98 double d = cell.getValue();
99 double prop = Math.min(d, maxNmForColour) / maxNmForColour;
100 Color color = toColor(minSaturation, prop);
101 g.setColor(color);
102 g.fillRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y
103 - topLeft.y);
104 }
105 })
106
107 .count()
108
109 .toBlocking().single();
110
111 if (addLegend)
112 paintLegend(g, cellSizeDegrees, metrics, maxNmForColour, minSaturation, w, h);
113
114 }
115
116 public static void paintParameters(Graphics2D g, Options options,
117 CalculationResult calculationResult, int w, int h) {
118
119 g.setColor(Color.darkGray);
120 Font font = g.getFont();
121 g.setFont(font.deriveFont(10f));
122 {
123 String label = "startTime=" + formatDate(options.getStartTime()) + ", finishTime="
124 + formatDate(options.getFinishTime()) + ", "
125 + options.getSegmentOptions().toString();
126 int labelWidth = g.getFontMetrics().stringWidth(label);
127 g.drawString(label, (w - labelWidth) / 2, h - 50);
128 }
129 {
130 String label = metricsToString(calculationResult);
131 int labelWidth = g.getFontMetrics().stringWidth(label);
132 g.drawString(label, (w - labelWidth) / 2, h - 50 + g.getFontMetrics().getHeight());
133 }
134
135 g.setFont(font);
136 }
137
138 private static String metricsToString(CalculationResult r) {
139 StringBuilder s = new StringBuilder();
140
141 DistanceCalculationMetrics m = r.getMetrics();
142 s.append(", fixes=" + m.fixes.get());
143 s.append(", inTime=" + m.fixesInTimeRange.get());
144 s.append(", inRegion=" + m.fixesWithinRegion.get());
145 s.append(", effSpdOk=" + m.fixesPassedEffectiveSpeedCheck.get());
146 s.append(", segs=" + m.segments.get());
147 s.append(", segsTimeDiffOk=" + m.segmentsTimeDifferenceOk.get());
148 s.append(", segCells=" + m.segmentCells.get());
149 s.append(", totalNm=" + m.totalNauticalMiles);
150 return s.toString();
151 }
152
153 private static void paintLegend(Graphics2D g, double cellSizeDegrees, Statistics metrics,
154 double maxNmForColour, double minSaturation, int w, int h) {
155 int legendWidth = 120;
156 int rightMargin = 50;
157 int legendHeight = 200;
158 int topMargin = 50;
159 g.setColor(Color.darkGray);
160 int legendLeft = w - rightMargin - legendWidth;
161 int legendTop = topMargin;
162 g.clearRect(legendLeft, legendTop, legendWidth, legendHeight);
163 g.drawRect(legendLeft, legendTop, legendWidth, legendHeight);
164 int innerTopMargin = 15;
165 int innerBottomMargin = 15;
166 int innerRightMargin = 5;
167 int innerHeight = legendHeight - 2 - innerTopMargin - innerBottomMargin;
168 int innerWidth = (legendWidth - innerRightMargin) / 2;
169
170 for (int i = 1; i < innerHeight; i++) {
171 int y = legendTop + innerHeight + innerTopMargin - i;
172 double prop = (double) i / innerHeight;
173 Color color = toColor(minSaturation, prop);
174 g.setColor(color);
175 g.drawLine(legendLeft + legendWidth - innerWidth - innerRightMargin, y, legendLeft
176 + legendWidth - innerRightMargin, y);
177 }
178 int numMarkers = 5;
179 int markerLeftMargin = 5;
180 Font font = g.getFont();
181 g.setFont(new Font("Arial", Font.PLAIN, 10));
182 g.setColor(Color.black);
183 DecimalFormat df = new DecimalFormat("0.##E0");
184 int markerLineLength = 8;
185 for (int i = 0; i <= numMarkers; i++) {
186 int y = legendTop + innerHeight + innerTopMargin
187 - (int) Math.round((double) i * innerHeight / numMarkers);
188 String label = df.format(maxNmForColour * i / numMarkers);
189 if (i == numMarkers)
190 label = UNICODE_GREATER_THAN_OR_EQUAL + label;
191 g.drawString(label, legendLeft + markerLeftMargin, y);
192
193 int markerLineX = legendLeft + legendWidth - innerRightMargin - innerWidth
194 - markerLineLength / 2;
195 g.drawLine(markerLineX, y, markerLineX + markerLineLength, y);
196 }
197 g.drawString("nm/nm2", legendLeft + legendWidth - innerRightMargin - innerWidth + 5,
198 legendTop + innerTopMargin - 2);
199 g.drawString("cellSize=" + new DecimalFormat("0.###").format(cellSizeDegrees) + "degs",
200 legendLeft + markerLeftMargin, legendTop + legendHeight - 3);
201 g.setFont(font);
202
203 }
204
205 private static Color toColor(double minSaturation, double prop) {
206 if (coloured) {
207 return Color.getHSBColor((float) (1 - prop) * 0.5f, 1.0f, 1.0f);
208 } else {
209 return Color.getHSBColor(0.0f, (float) (prop * (1 - minSaturation) + minSaturation),
210 1.0f);
211 }
212 }
213
214 private static Statistics getStatistics(Observable<CellValue> cells) {
215 final AtomicDouble sum = new AtomicDouble(0);
216 final AtomicDouble sumSquares = new AtomicDouble(0);
217 log.info("calculating mean and sd");
218 long count = cells
219
220 .doOnNext(new Action1<CellValue>() {
221 @Override
222 public void call(CellValue cell) {
223 sum.addAndGet(cell.getValue());
224 sumSquares.addAndGet(cell.getValue() * cell.getValue());
225 }
226 })
227
228 .count()
229
230 .toBlocking().single();
231 double mean = sum.get() / count;
232 double variance = sumSquares.get() / count - mean * mean;
233 double sd = Math.sqrt(variance);
234 log.info("calculated");
235 Statistics stats = new Statistics(mean, sd, count);
236 log.info(stats);
237 return stats;
238 }
239
240 public static BufferedImage createImage(Options options, int numStandardDeviations, int width,
241 CalculationResult calculationResult) {
242 log.info("creating image");
243 int height = (int) Math.round(width / options.getBounds().getWidthDegrees()
244 * options.getBounds().getHeightDegrees());
245 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
246 Graphics2D g = image.createGraphics();
247 g.setBackground(Color.white);
248 g.clearRect(0, 0, width, height);
249 Renderer.paintAll(g, options, numStandardDeviations, width, height, calculationResult,
250 true, true);
251 log.info("created image");
252 return image;
253 }
254
255 public static void saveAsPng(BufferedImage image, File file) {
256 log.info("saving png to " + file);
257 try {
258 ImageIO.write(image, "png", file);
259 log.info("saved");
260 } catch (IOException e) {
261 throw new RuntimeException(e);
262 }
263 }
264
265 private static class Statistics {
266 final double mean;
267 final double sd;
268 private final long count;
269
270 Statistics(double mean, double sd, long count) {
271 this.mean = mean;
272 this.sd = sd;
273 this.count = count;
274 }
275
276 @Override
277 public String toString() {
278 StringBuilder builder = new StringBuilder();
279 builder.append("Statistics [mean=");
280 builder.append(mean);
281 builder.append(", sd=");
282 builder.append(sd);
283 builder.append(", count=");
284 builder.append(count);
285 builder.append("]");
286 return builder.toString();
287 }
288
289 }
290
291 }