telegps: Add graph display
[fw/altos] / altosuilib / AltosSiteMapTile.java
1 /*
2  * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.altosuilib_2;
19
20 import java.awt.*;
21 import java.awt.image.*;
22 import javax.swing.*;
23 import javax.imageio.*;
24 import java.awt.geom.*;
25 import java.io.*;
26 import java.util.*;
27 import java.awt.RenderingHints.*;
28 import org.altusmetrum.altoslib_4.*;
29
30 class AltosPoint {
31         Point2D.Double  pt;
32         int             state;
33
34         AltosPoint(Point2D.Double pt, int state) {
35                 this.pt = pt;
36                 this.state = state;
37         }
38 }
39
40 public class AltosSiteMapTile extends JComponent {
41         int px_size;
42         File file;
43         int status;
44
45         Point2D.Double  boost;
46         Point2D.Double  landed;
47         Line2D.Double   line;
48         double          line_course;
49         double          line_dist;
50
51         LinkedList<AltosPoint>  points;
52
53         public synchronized void queue_repaint() {
54                 if (SwingUtilities.isEventDispatchThread())
55                         repaint();
56                 else {
57                         SwingUtilities.invokeLater(new Runnable() {
58                                         public void run() {
59                                                 repaint();
60                                         }
61                                 });
62                 }
63         }
64
65         public void load_map(File pngFile) {
66                 file = pngFile;
67                 queue_repaint();
68         }
69
70         private Font    font = null;
71
72         public void set_font(Font font) {
73                 this.font = font;
74                 this.status = AltosSiteMapCache.success;
75         }
76
77         public void set_status(int status) {
78                 if (status != this.status || file != null) {
79                         file = null;
80                         this.status = status;
81                         queue_repaint();
82                 }
83         }
84
85         public void clearMap() {
86                 boost = null;
87                 landed = null;
88                 points = null;
89                 file = null;
90                 status = AltosSiteMapCache.success;
91                 line = null;
92         }
93
94         static Color stateColors[] = {
95                 Color.WHITE,  // startup
96                 Color.WHITE,  // idle
97                 Color.WHITE,  // pad
98                 Color.RED,    // boost
99                 Color.PINK,   // fast
100                 Color.YELLOW, // coast
101                 Color.CYAN,   // drogue
102                 Color.BLUE,   // main
103                 Color.BLACK   // landed
104         };
105
106         private void draw_circle(Graphics g, Point2D.Double pt) {
107                 g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
108                 g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
109                 g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
110         }
111
112         public void set_boost(Point2D.Double boost) {
113                 this.boost = boost;
114                 queue_repaint();
115         }
116
117         public void set_line(Line2D.Double line, double distance) {
118                 this.line = line;
119                 line_dist = distance;
120                 queue_repaint();
121         }
122
123         private String line_dist() {
124                 String  format;
125                 double  distance = line_dist;
126
127                 if (AltosConvert.imperial_units) {
128                         distance = AltosConvert.meters_to_feet(distance);
129                         if (distance < 10000) {
130                                 format = "%4.0fft";
131                         } else {
132                                 distance /= 5280;
133                                 if (distance < 10)
134                                         format = "%5.3fmi";
135                                 else if (distance < 100)
136                                         format = "%5.2fmi";
137                                 else if (distance < 1000)
138                                         format = "%5.1fmi";
139                                 else
140                                         format = "%5.0fmi";
141                         }
142                 } else {
143                         if (distance < 10000) {
144                                 format = "%4.0fm";
145                         } else {
146                                 distance /= 1000;
147                                 if (distance < 100)
148                                         format = "%5.2fkm";
149                                 else if (distance < 1000)
150                                         format = "%5.1fkm";
151                                 else
152                                         format = "%5.0fkm";
153                         }
154                 }
155                 return String.format(format, distance);
156         }
157
158         int     painting_serial;
159         int     painted_serial;
160
161         public void paint_graphics(Graphics2D g2d, Image image, int serial) {
162
163                 if (serial < painted_serial)
164                         return;
165
166                 painted_serial = serial;
167
168                 if (image != null) {
169                         AltosSiteMap.debug_component(this, "paint_graphics");
170                         g2d.drawImage(image, 0, 0, null);
171                 } else {
172                         AltosSiteMap.debug_component(this, "erase_graphics");
173                         g2d.setColor(Color.GRAY);
174                         g2d.fillRect(0, 0, getWidth(), getHeight());
175                         String  message = null;
176                         switch (status) {
177                         case AltosSiteMapCache.loading:
178                                 message = "Loading...";
179                                 break;
180                         case AltosSiteMapCache.bad_request:
181                                 message = "Internal error";
182                                 break;
183                         case AltosSiteMapCache.failed:
184                                 message = "Network error, check connection";
185                                 break;
186                         case AltosSiteMapCache.forbidden:
187                                 message = "Too many requests, try later";
188                                 break;
189                         }
190                         if (message != null && font != null) {
191                                 g2d.setFont(font);
192                                 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
193                                 Rectangle2D     bounds;
194                                 bounds = font.getStringBounds(message, g2d.getFontRenderContext());
195
196                                 float x = getWidth() / 2.0f;
197                                 float y = getHeight() / 2.0f;
198                                 x = x - (float) bounds.getWidth() / 2.0f;
199                                 y = y + (float) bounds.getHeight() / 2.0f;
200                                 g2d.setColor(Color.BLACK);
201                                 g2d.drawString(message, x, y);
202                         }
203                 }
204
205                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
206                                    RenderingHints.VALUE_ANTIALIAS_ON);
207                 g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
208
209                 if (points != null) {
210                         AltosPoint              prev = null;
211                         for (AltosPoint point : points) {
212                                 if (prev != null) {
213                                         if (0 <= point.state && point.state < stateColors.length)
214                                                 g2d.setColor(stateColors[point.state]);
215                                         g2d.draw(new Line2D.Double(prev.pt, point.pt));
216                                 }
217                                 prev = point;
218                         }
219                 }
220                 if (boost != null) {
221                         g2d.setColor(Color.RED);
222                         draw_circle(g2d, boost);
223                 }
224                 if (landed != null) {
225                         g2d.setColor(Color.BLACK);
226                         draw_circle(g2d, landed);
227                 }
228
229                 if (line != null) {
230                         g2d.setColor(Color.BLUE);
231                         g2d.draw(line);
232
233                         String  message = line_dist();
234                         g2d.setFont(font);
235                         g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
236                         Rectangle2D     bounds;
237                         bounds = font.getStringBounds(message, g2d.getFontRenderContext());
238
239                         float x = (float) line.x1;
240                         float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
241
242                         if (line.x1 < line.x2) {
243                                 x -= (float) bounds.getWidth() + 2.0f;
244                         } else {
245                                 x += 2.0f;
246                         }
247                         g2d.drawString(message, x, y);
248                 }
249         }
250
251         public void paint(Graphics g) {
252                 Graphics2D              g2d = (Graphics2D) g;
253                 Image                   image = null;
254                 boolean                 queued = false;
255
256                 AltosSiteMap.debug_component(this, "paint");
257
258                 ++painting_serial;
259
260                 if (file != null) {
261                         AltosSiteMapImage       aimage;
262
263                         aimage = AltosSiteMapCache.get_image(this, file, px_size, px_size);
264                         if (aimage != null) {
265                                 if (aimage.validate(painting_serial))
266                                         image = aimage.image;
267                                 else
268                                         queued = true;
269                         }
270                 }
271                 if (!queued)
272                         paint_graphics(g2d, image, painting_serial);
273         }
274
275         public void show(int state, Point2D.Double last_pt, Point2D.Double pt)
276         {
277                 if (points == null)
278                         points = new LinkedList<AltosPoint>();
279
280                 points.add(new AltosPoint(pt, state));
281
282                 if (state == AltosLib.ao_flight_boost && boost == null)
283                         boost = pt;
284                 if (state == AltosLib.ao_flight_landed && landed == null)
285                         landed = pt;
286                 queue_repaint();
287         }
288
289         public AltosSiteMapTile(int in_px_size) {
290                 px_size = in_px_size;
291                 setPreferredSize(new Dimension(px_size, px_size));
292         }
293 }