2 * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
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; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 package org.altusmetrum.altosuilib_13;
22 import java.awt.event.*;
23 import java.awt.image.*;
26 import java.lang.Math;
27 import java.awt.geom.*;
29 import java.util.concurrent.*;
30 import javax.imageio.*;
31 import org.altusmetrum.altoslib_13.*;
33 public class AltosUIMap extends JComponent implements AltosFlightDisplay, AltosMapInterface {
40 static Point2D.Double point2d(AltosPointDouble pt) {
41 return new Point2D.Double(pt.x, pt.y);
44 static final AltosPointDouble point_double(Point pt) {
45 return new AltosPointDouble(pt.x, pt.y);
48 class MapMark extends AltosMapMark {
49 public void paint(AltosMapTransform t) {
50 AltosPointDouble pt = t.screen(lat_lon);
52 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
53 RenderingHints.VALUE_ANTIALIAS_ON);
54 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
56 if (0 <= state && state < AltosUIMap.stateColors.length)
57 g.setColor(AltosUIMap.stateColors[state]);
59 g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
61 g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
62 g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
63 g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
66 MapMark(double lat, double lon, int state) {
67 super(lat, lon, state);
71 class MapView extends JComponent implements MouseMotionListener, MouseListener, ComponentListener, MouseWheelListener {
73 private VolatileImage create_back_buffer() {
74 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
77 private void do_paint(Graphics my_g) {
78 g = (Graphics2D) my_g;
83 public void paint(Graphics my_g) {
84 VolatileImage back_buffer = create_back_buffer();
86 Graphics2D top_g = (Graphics2D) my_g;
89 GraphicsConfiguration gc = getGraphicsConfiguration();
90 int code = back_buffer.validate(gc);
91 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
92 back_buffer = create_back_buffer();
94 Graphics g_back = back_buffer.getGraphics();
95 g_back.setClip(top_g.getClip());
99 top_g.drawImage(back_buffer, 0, 0, this);
100 } while (back_buffer.contentsLost());
104 public void repaint(AltosRectangle damage) {
105 repaint(damage.x, damage.y, damage.width, damage.height);
108 private boolean is_drag_event(MouseEvent e) {
109 return e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK;
112 /* MouseMotionListener methods */
114 public void mouseDragged(MouseEvent e) {
115 map.touch_continue(e.getPoint().x, e.getPoint().y, is_drag_event(e));
118 public void mouseMoved(MouseEvent e) {
121 /* MouseListener methods */
122 public void mouseClicked(MouseEvent e) {
125 public void mouseEntered(MouseEvent e) {
128 public void mouseExited(MouseEvent e) {
131 public void mousePressed(MouseEvent e) {
132 map.touch_start(e.getPoint().x, e.getPoint().y, is_drag_event(e));
135 public void mouseReleased(MouseEvent e) {
138 /* MouseWheelListener methods */
140 public void mouseWheelMoved(MouseWheelEvent e) {
141 int zoom_change = e.getWheelRotation();
143 map.set_zoom_centre(map.get_zoom() - zoom_change, new AltosPointInt(e.getPoint().x, e.getPoint().y));
146 /* ComponentListener methods */
148 public void componentHidden(ComponentEvent e) {
151 public void componentMoved(ComponentEvent e) {
154 public void componentResized(ComponentEvent e) {
158 public void componentShown(ComponentEvent e) {
163 addComponentListener(this);
164 addMouseMotionListener(this);
165 addMouseListener(this);
166 addMouseWheelListener(this);
170 class MapLine extends AltosMapLine {
172 public void paint(AltosMapTransform t) {
174 if (start == null || end == null)
177 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
179 Line2D.Double line = new Line2D.Double(point2d(t.screen(start)),
180 point2d(t.screen(end)));
182 g.setColor(Color.WHITE);
183 g.setStroke(new BasicStroke(stroke_width+4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
186 g.setColor(Color.BLUE);
187 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
190 String message = line_dist();
192 bounds = line_font.getStringBounds(message, g.getFontRenderContext());
194 float x = (float) line.x1;
195 float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
197 if (line.x1 < line.x2) {
198 x -= (float) bounds.getWidth() + 2.0f;
203 g.setFont(line_font);
204 g.setColor(Color.WHITE);
205 for (int dy = -2; dy <= 2; dy += 2)
206 for (int dx = -2; dx <= 2; dx += 2)
207 g.drawString(message, x + dx, y + dy);
208 g.setColor(Color.BLUE);
209 g.drawString(message, x, y);
216 class MapPath extends AltosMapPath {
217 public void paint(AltosMapTransform t) {
218 Point2D.Double prev = null;
220 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
221 RenderingHints.VALUE_ANTIALIAS_ON);
222 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
224 for (AltosMapPathPoint point : points) {
225 Point2D.Double cur = point2d(t.screen(point.lat_lon));
227 Line2D.Double line = new Line2D.Double (prev, cur);
228 Rectangle bounds = line.getBounds();
230 if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) {
231 if (0 <= point.state && point.state < AltosUIMap.stateColors.length)
232 g.setColor(AltosUIMap.stateColors[point.state]);
234 g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
244 class MapTile extends AltosMapTile {
245 public MapTile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
246 super(cache, upper_left, center, zoom, maptype, px_size, scale);
249 public void paint(AltosMapTransform t) {
251 AltosPointDouble point_double = t.screen(upper_left);
252 Point point = new Point((int) (point_double.x + 0.5),
253 (int) (point_double.y + 0.5));
255 if (!g.hitClip(point.x, point.y, px_size, px_size))
258 AltosImage altos_image = get_image();
259 AltosUIImage ui_image = (AltosUIImage) altos_image;
262 if (ui_image != null)
263 image = ui_image.image;
266 g.drawImage(image, point.x, point.y, null);
268 * Useful when debugging map fetching problems
270 String message = String.format("%.6f %.6f", center.lat, center.lon);
271 g.setFont(tile_font);
272 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
273 Rectangle2D bounds = tile_font.getStringBounds(message, g.getFontRenderContext());
275 float x = px_size / 2.0f;
276 float y = px_size / 2.0f;
277 x = x - (float) bounds.getWidth() / 2.0f;
278 y = y + (float) bounds.getHeight() / 2.0f;
279 g.setColor(Color.RED);
280 g.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
283 g.setColor(Color.GRAY);
284 g.fillRect(point.x, point.y, px_size, px_size);
286 if (t.has_location()) {
287 String message = null;
289 case AltosMapTile.fetching:
290 message = "Fetching...";
292 case AltosMapTile.bad_request:
293 message = "Internal error";
295 case AltosMapTile.failed:
296 message = "Network error";
298 case AltosMapTile.forbidden:
299 message = "Outside of known launch areas";
302 if (message != null && tile_font != null) {
303 g.setFont(tile_font);
304 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
305 Rectangle2D bounds = tile_font.getStringBounds(message, g.getFontRenderContext());
307 float x = px_size / 2.0f;
308 float y = px_size / 2.0f;
309 x = x - (float) bounds.getWidth() / 2.0f;
310 y = y + (float) bounds.getHeight() / 2.0f;
311 g.setColor(Color.BLACK);
312 g.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
319 public static final Color stateColors[] = {
320 Color.WHITE, // startup
325 Color.YELLOW, // coast
326 Color.CYAN, // drogue
328 Color.BLACK, // landed
329 Color.BLACK, // invalid
330 Color.CYAN, // stateless
333 /* AltosMapInterface functions */
335 public AltosMapPath new_path() {
336 return new MapPath();
339 public AltosMapLine new_line() {
340 return new MapLine();
343 public AltosImage load_image(File file) throws Exception {
344 return new AltosUIImage(ImageIO.read(file));
347 public AltosMapMark new_mark(double lat, double lon, int state) {
348 return new MapMark(lat, lon, state);
351 public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
352 return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale);
356 return view.getWidth();
359 public int height() {
360 return view.getHeight();
363 public void repaint() {
367 public void repaint(AltosRectangle damage) {
368 view.repaint(damage);
371 public void set_zoom_label(String label) {
372 zoom_label.setText(label);
375 public void select_object(AltosLatLon latlon) {
376 debug("select at %f,%f\n", latlon.lat, latlon.lon);
379 public void debug(String format, Object ... arguments) {
380 if (AltosUIPreferences.serial_debug())
381 System.out.printf(format, arguments);
385 /* AltosFlightDisplay interface */
387 public void set_font() {
388 tile_font = AltosUILib.value_font;
389 line_font = AltosUILib.status_font;
392 public void font_size_changed(int font_size) {
397 public void units_changed(boolean imperial_units) {
403 public void set_maptype(int type) {
405 map.set_maptype(type);
406 maptype_combo.setSelectedIndex(type);
410 /* AltosUIMapPreload functions */
412 public void set_zoom(int zoom) {
416 public void add_mark(double lat, double lon, int status) {
417 map.add_mark(lat, lon, status);
420 public void clear_marks() {
424 /* AltosFlightDisplay interface */
425 public void reset() {
429 public void show(AltosState state, AltosListenerState listener_state) {
430 map.show(state, listener_state);
433 public void show(AltosGPS gps, int state) {
434 map.show(gps, state);
437 public String getName() {
441 /* AltosGraphUI interface */
442 public void centre(AltosState state) {
446 public void centre(AltosGPS gps) {
450 /* internal layout bits */
451 private GridBagLayout layout = new GridBagLayout();
454 JComboBox<String> maptype_combo;
459 public AltosUIMap() {
463 view = new MapView();
465 view.setPreferredSize(new Dimension(500,500));
466 view.setVisible(true);
467 view.setEnabled(true);
469 GridBagLayout my_layout = new GridBagLayout();
471 setLayout(my_layout);
473 GridBagConstraints c = new GridBagConstraints();
474 c.anchor = GridBagConstraints.CENTER;
475 c.fill = GridBagConstraints.BOTH;
486 zoom_label = new JLabel("", JLabel.CENTER);
488 c = new GridBagConstraints();
489 c.anchor = GridBagConstraints.CENTER;
490 c.fill = GridBagConstraints.HORIZONTAL;
497 JButton zoom_reset = new JButton("0");
498 zoom_reset.addActionListener(new ActionListener() {
499 public void actionPerformed(ActionEvent e) {
500 map.set_zoom(map.default_zoom);
504 c = new GridBagConstraints();
505 c.anchor = GridBagConstraints.CENTER;
506 c.fill = GridBagConstraints.HORIZONTAL;
513 JButton zoom_in = new JButton("+");
514 zoom_in.addActionListener(new ActionListener() {
515 public void actionPerformed(ActionEvent e) {
516 map.set_zoom(map.get_zoom() + 1);
520 c = new GridBagConstraints();
521 c.anchor = GridBagConstraints.CENTER;
522 c.fill = GridBagConstraints.HORIZONTAL;
529 JButton zoom_out = new JButton("-");
530 zoom_out.addActionListener(new ActionListener() {
531 public void actionPerformed(ActionEvent e) {
532 map.set_zoom(map.get_zoom() - 1);
535 c = new GridBagConstraints();
536 c.anchor = GridBagConstraints.CENTER;
537 c.fill = GridBagConstraints.HORIZONTAL;
545 maptype_combo = new JComboBox<String>(map.maptype_labels);
547 maptype_combo.setEditable(false);
548 maptype_combo.setMaximumRowCount(maptype_combo.getItemCount());
549 maptype_combo.addItemListener(new ItemListener() {
550 public void itemStateChanged(ItemEvent e) {
551 map.set_maptype(maptype_combo.getSelectedIndex());
555 c = new GridBagConstraints();
556 c.anchor = GridBagConstraints.CENTER;
557 c.fill = GridBagConstraints.HORIZONTAL;
562 add(maptype_combo, c);
564 map = new AltosMap(this);