2 * Copyright © 2015 Keith Packard <keithp@keithp.com>
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.AltosDroid;
24 import org.altusmetrum.altoslib_14.*;
26 import android.graphics.*;
27 import android.view.*;
28 import android.location.Location;
29 import android.content.*;
30 import android.util.*;
32 class Rocket implements Comparable {
38 AltosMapOffline map_offline;
41 map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
42 map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
45 void set_position(AltosLatLon position, long last_packet) {
46 this.position = position;
47 this.last_packet = last_packet;
50 void set_active(boolean active) {
54 public int compareTo(Object o) {
55 Rocket other = (Rocket) o;
57 if (active && !other.active)
59 if (other.active && !active)
62 long diff = last_packet - other.last_packet;
71 Rocket(int serial, AltosMapOffline map_offline) {
73 this.name = String.format("%d", serial);
74 this.map_offline = map_offline;
78 public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface, AltosMapTypeListener {
79 ScaleGestureDetector scale_detector;
82 AltosDroid altos_droid;
94 int pad_off_x, pad_off_y;
96 int rocket_off_x, rocket_off_y;
98 int here_off_x, here_off_y;
100 static final int WHITE = 0xffffffff;
101 static final int RED = 0xffff0000;
102 static final int PINK = 0xffff8080;
103 static final int YELLOW= 0xffffff00;
104 static final int CYAN = 0xff00ffff;
105 static final int BLUE = 0xff0000ff;
106 static final int BLACK = 0xff000000;
108 public static final int stateColors[] = {
122 /* AltosMapInterface */
123 public void debug(String format, Object ... arguments) {
124 AltosDebug.debug(format, arguments);
127 class MapTile extends AltosMapTile {
128 public void paint(AltosMapTransform t) {
129 AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
131 if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
134 AltosImage altos_image = this.get_image();
136 MapImage map_image = (MapImage) altos_image;
138 Bitmap bitmap = null;
140 if (map_image != null)
141 bitmap = map_image.bitmap;
143 if (bitmap != null) {
144 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
146 paint.setColor(0xff808080);
147 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
148 if (t.has_location()) {
149 String message = null;
151 case AltosMapTile.fetching:
152 message = "Fetching...";
154 case AltosMapTile.bad_request:
155 message = "Internal error";
157 case AltosMapTile.failed:
158 message = "Network error";
160 case AltosMapTile.forbidden:
161 message = "Outside of known launch areas";
164 if (message != null) {
165 Rect bounds = new Rect();
166 paint.getTextBounds(message, 0, message.length(), bounds);
168 int width = bounds.right - bounds.left;
169 int height = bounds.bottom - bounds.top;
171 float x = pt.x + px_size / 2.0f;
172 float y = pt.y + px_size / 2.0f;
173 x = x - width / 2.0f;
174 y = y + height / 2.0f;
175 paint.setColor(0xff000000);
176 canvas.drawText(message, 0, message.length(), x, y, paint);
182 public MapTile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
183 super(cache, upper_left, center, zoom, maptype, px_size, scale);
188 public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
189 return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale);
192 public AltosMapPath new_path() {
196 public AltosMapLine new_line() {
200 class MapImage implements AltosImage {
201 public Bitmap bitmap;
203 public void flush() {
204 if (bitmap != null) {
210 public MapImage(File file) {
211 bitmap = BitmapFactory.decodeFile(file.getPath());
215 public AltosImage load_image(File file) throws Exception {
216 return new MapImage(file);
219 class MapMark extends AltosMapMark {
220 public void paint(AltosMapTransform t) {
223 MapMark(double lat, double lon, int state) {
224 super(lat, lon, state);
228 public AltosMapMark new_mark(double lat, double lon, int state) {
229 return new MapMark(lat, lon, state);
236 public int height() {
240 public void repaint() {
244 public void repaint(AltosRectangle damage) {
245 postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
248 public void set_zoom_label(String label) {
251 public void select_object(AltosLatLon latlon) {
252 if (map.transform == null)
254 ArrayList<Integer> near = new ArrayList<Integer>();
256 for (Rocket rocket : sorted_rockets()) {
257 if (rocket.position == null) {
258 debug("rocket %d has no position\n", rocket.serial);
261 double distance = map.transform.hypot(latlon, rocket.position);
262 debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth());
263 if (distance < rocket_bitmap.getWidth() * 2.0) {
264 debug("selecting %d\n", rocket.serial);
265 near.add(rocket.serial);
268 if (near.size() != 0)
269 altos_droid.touch_trackers(near.toArray(new Integer[0]));
276 if (a != null && b != null) {
277 AltosPointDouble a_screen = map.transform.screen(a);
278 AltosPointDouble b_screen = map.transform.screen(b);
279 paint.setColor(0xff8080ff);
280 canvas.drawLine((float) a_screen.x, (float) a_screen.y,
281 (float) b_screen.x, (float) b_screen.y,
286 void set_a(AltosLatLon a) {
290 void set_b(AltosLatLon b) {
298 Line line = new Line();
300 int stroke_width = 20;
302 void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
303 if (lat_lon != null && map != null && map.transform != null) {
304 AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
306 Rect bounds = new Rect();
307 paint.getTextBounds(text, 0, text.length(), bounds);
309 int width = bounds.right - bounds.left;
310 int height = bounds.bottom - bounds.top;
314 x = x - width / 2.0f - off_x;
315 y = y + height / 2.0f - off_y;
316 paint.setColor(0xff000000);
317 canvas.drawText(text, 0, text.length(), x, y, paint);
321 HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
323 void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
324 if (lat_lon != null && map != null && map.transform != null) {
325 AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
327 canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
331 private Rocket[] sorted_rockets() {
332 Rocket[] rocket_array = rockets.values().toArray(new Rocket[0]);
334 Arrays.sort(rocket_array);
338 private void draw_positions() {
342 draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
344 for (Rocket rocket : sorted_rockets())
346 draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
350 protected void onDraw(Canvas view_canvas) {
352 debug("MapView draw without map\n");
355 canvas = view_canvas;
356 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
357 paint.setStrokeWidth(stroke_width);
358 paint.setStrokeCap(Paint.Cap.ROUND);
359 paint.setStrokeJoin(Paint.Join.ROUND);
360 paint.setTextSize(40);
366 public boolean onScale(ScaleGestureDetector detector) {
367 float f = detector.getScaleFactor();
370 map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
374 map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
380 public boolean onScaleBegin(ScaleGestureDetector detector) {
384 public void onScaleEnd(ScaleGestureDetector detector) {
388 public boolean dispatchTouchEvent(MotionEvent event) {
389 scale_detector.onTouchEvent(event);
391 if (scale_detector.isInProgress()) {
396 if (event.getAction() == MotionEvent.ACTION_UP) {
402 if (event.getAction() == MotionEvent.ACTION_DOWN) {
403 map.touch_start((int) event.getX(), (int) event.getY(), true);
404 } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
405 map.touch_continue((int) event.getX(), (int) event.getY(), true);
406 } else if (event.getAction() == MotionEvent.ACTION_UP) {
407 map.touch_stop((int) event.getX(), (int) event.getY(), true);
414 public void center(double lat, double lon, double accuracy) {
415 if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) {
417 map.maybe_centre(lat, lon);
418 mapAccuracy = accuracy;
422 public void set_visible(boolean visible) {
424 setVisibility(VISIBLE);
429 public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
430 boolean changed = false;
433 map.show(state, null);
434 if (state.pad_lat != AltosLib.MISSING && pad == null)
435 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
438 if (telem_state != null) {
439 Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
440 Integer[] new_serial = telem_state.keySet().toArray(new Integer[0]);
442 /* remove deleted keys */
443 for (int serial : old_serial) {
444 if (!telem_state.containsKey(serial))
445 rockets.remove(serial);
448 /* set remaining keys */
450 for (int serial : new_serial) {
452 AltosState t_state = telem_state.get(serial);
453 if (rockets.containsKey(serial))
454 rocket = rockets.get(serial);
456 rocket = new Rocket(serial, this);
457 rockets.put(serial, rocket);
459 if (t_state.gps != null) {
460 AltosLatLon latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon);
461 rocket.set_position(latlon, t_state.received_time);
462 if (state != null && state.cal_data().serial == serial)
466 rocket.set_active(state.cal_data().serial == serial);
469 if (receiver != null) {
470 AltosLatLon new_here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
471 if (!new_here.equals(here)) {
473 AltosDebug.debug("Location changed, redraw");
479 public void onCreateView(AltosDroid altos_droid) {
480 this.altos_droid = altos_droid;
481 map = new AltosMap(this, scale);
482 AltosPreferences.register_map_type_listener(this);
483 map.set_maptype(AltosPreferences.map_type());
485 pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
486 /* arrow at the bottom of the launchpad image */
487 pad_off_x = pad_bitmap.getWidth() / 2;
488 pad_off_y = pad_bitmap.getHeight();
490 rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
491 /* arrow at the bottom of the rocket image */
492 rocket_off_x = rocket_bitmap.getWidth() / 2;
493 rocket_off_y = rocket_bitmap.getHeight();
495 here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
496 /* Center of the dot */
497 here_off_x = here_bitmap.getWidth() / 2;
498 here_off_y = here_bitmap.getHeight() / 2;
501 public void onDestroyView() {
502 AltosPreferences.unregister_map_type_listener(this);
505 public void map_type_changed(int map_type) {
507 map.set_maptype(map_type);
510 public AltosMapOffline(Context context, AttributeSet attrs) {
511 super(context, attrs);
512 this.altos_droid = altos_droid;
513 scale_detector = new ScaleGestureDetector(context, this);