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; version 2 of the License.
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.
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.
18 package org.altusmetrum.AltosDroid;
23 import org.altusmetrum.altoslib_7.*;
25 import android.app.Activity;
26 import android.graphics.*;
27 import android.os.Bundle;
28 import android.support.v4.app.Fragment;
29 import android.support.v4.app.FragmentTransaction;
30 import android.view.*;
31 import android.widget.*;
32 import android.location.Location;
33 import android.content.*;
34 import android.util.*;
36 class Rocket implements Comparable {
40 AltosMapOffline map_offline;
43 map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
44 map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
47 void set_position(AltosLatLon position, long last_packet) {
48 this.position = position;
49 this.last_packet = last_packet;
52 Rocket(String name, AltosMapOffline map_offline) {
54 this.map_offline = map_offline;
57 public int compareTo(Object o) {
58 Rocket other = (Rocket) o;
60 long diff = last_packet - other.last_packet;
70 public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface {
71 ScaleGestureDetector scale_detector;
82 int pad_off_x, pad_off_y;
84 int rocket_off_x, rocket_off_y;
86 int here_off_x, here_off_y;
88 static final int WHITE = 0xffffffff;
89 static final int RED = 0xffff0000;
90 static final int PINK = 0xffff8080;
91 static final int YELLOW= 0xffffff00;
92 static final int CYAN = 0xff00ffff;
93 static final int BLUE = 0xff0000ff;
94 static final int BLACK = 0xff000000;
96 public static final int stateColors[] = {
110 /* AltosMapInterface */
111 public void debug(String format, Object ... arguments) {
112 AltosDebug.debug(format, arguments);
115 class MapTile extends AltosMapTile {
116 public void paint(AltosMapTransform t) {
117 AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
119 if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
122 AltosImage altos_image = cache.get(this, store, px_size, px_size);
124 MapImage map_image = (MapImage) altos_image;
126 Bitmap bitmap = null;
128 if (map_image != null)
129 bitmap = map_image.bitmap;
131 if (bitmap != null) {
132 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
134 paint.setColor(0xff808080);
135 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
136 if (t.has_location()) {
137 String message = null;
139 case AltosMapTile.loading:
140 message = "Loading...";
142 case AltosMapTile.bad_request:
143 message = "Internal error";
145 case AltosMapTile.failed:
146 message = "Network error, check connection";
148 case AltosMapTile.forbidden:
149 message = "Too many requests, try later";
152 if (message != null) {
153 Rect bounds = new Rect();
154 paint.getTextBounds(message, 0, message.length(), bounds);
156 int width = bounds.right - bounds.left;
157 int height = bounds.bottom - bounds.top;
159 float x = pt.x + px_size / 2.0f;
160 float y = pt.y + px_size / 2.0f;
161 x = x - width / 2.0f;
162 y = y + height / 2.0f;
163 paint.setColor(0xff000000);
164 canvas.drawText(message, 0, message.length(), x, y, paint);
170 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
171 super(listener, upper_left, center, zoom, maptype, px_size, 2);
176 public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
177 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
180 public AltosMapPath new_path() {
184 public AltosMapLine new_line() {
188 class MapImage implements AltosImage {
189 public Bitmap bitmap;
191 public void flush() {
192 if (bitmap != null) {
198 public MapImage(File file) {
199 bitmap = BitmapFactory.decodeFile(file.getPath());
203 public AltosImage load_image(File file) throws Exception {
204 return new MapImage(file);
207 class MapMark extends AltosMapMark {
208 public void paint(AltosMapTransform t) {
211 MapMark(double lat, double lon, int state) {
212 super(lat, lon, state);
216 public AltosMapMark new_mark(double lat, double lon, int state) {
217 return new MapMark(lat, lon, state);
224 public int height() {
228 public void repaint() {
232 public void repaint(AltosRectangle damage) {
233 postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
236 public void set_zoom_label(String label) {
243 if (a != null && b != null) {
244 AltosPointDouble a_screen = map.transform.screen(a);
245 AltosPointDouble b_screen = map.transform.screen(b);
246 paint.setColor(0xff8080ff);
247 canvas.drawLine((float) a_screen.x, (float) a_screen.y,
248 (float) b_screen.x, (float) b_screen.y,
253 void set_a(AltosLatLon a) {
257 void set_b(AltosLatLon b) {
265 Line line = new Line();
267 int stroke_width = 20;
269 void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
270 if (lat_lon != null && map != null && map.transform != null) {
271 AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
273 Rect bounds = new Rect();
274 paint.getTextBounds(text, 0, text.length(), bounds);
276 int width = bounds.right - bounds.left;
277 int height = bounds.bottom - bounds.top;
281 x = x - width / 2.0f - off_x;
282 y = y + height / 2.0f - off_y;
283 paint.setColor(0xff000000);
284 canvas.drawText(text, 0, text.length(), x, y, paint);
288 HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
290 void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
291 if (lat_lon != null && map != null && map.transform != null) {
292 AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
294 canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
298 private void draw_positions() {
299 line.set_a(map.last_position);
302 draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
304 Rocket[] rocket_array = rockets.values().toArray(new Rocket[0]);
306 Arrays.sort(rocket_array);
307 for (Rocket rocket : rocket_array)
309 draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
312 @Override public void invalidate() {
318 @Override public void invalidate(int l, int t, int r, int b) {
319 Rect rect = new Rect();
320 getDrawingRect(rect);
325 protected void onDraw(Canvas view_canvas) {
328 debug("MapView draw without map\n");
331 canvas = view_canvas;
332 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
333 paint.setStrokeWidth(stroke_width);
334 paint.setStrokeCap(Paint.Cap.ROUND);
335 paint.setStrokeJoin(Paint.Join.ROUND);
336 paint.setTextSize(40);
342 public boolean onScale(ScaleGestureDetector detector) {
343 float f = detector.getScaleFactor();
346 map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
350 map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
356 public boolean onScaleBegin(ScaleGestureDetector detector) {
360 public void onScaleEnd(ScaleGestureDetector detector) {
364 public boolean dispatchTouchEvent(MotionEvent event) {
365 scale_detector.onTouchEvent(event);
367 if (scale_detector.isInProgress()) {
372 if (event.getAction() == MotionEvent.ACTION_UP) {
378 if (event.getAction() == MotionEvent.ACTION_DOWN) {
379 map.touch_start((int) event.getX(), (int) event.getY(), true);
380 } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
381 map.touch_continue((int) event.getX(), (int) event.getY(), true);
388 public void center(double lat, double lon, double accuracy) {
389 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
391 map.maybe_centre(lat, lon);
392 mapAccuracy = accuracy;
396 public void set_visible(boolean visible) {
398 setVisibility(VISIBLE);
403 public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
405 map.show(state, null);
406 if (state.pad_lat != AltosLib.MISSING && pad == null)
407 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
410 if (telem_state != null) {
411 Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
412 Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
414 /* remove deleted keys */
415 for (int serial : old_serial) {
416 if (!telem_state.states.containsKey(serial))
417 rockets.remove(serial);
420 /* set remaining keys */
422 for (int serial : new_serial) {
424 AltosState t_state = telem_state.states.get(serial);
425 if (rockets.containsKey(serial))
426 rocket = rockets.get(serial);
428 rocket = new Rocket(String.format("%d", serial), this);
429 rockets.put(serial, rocket);
431 if (t_state.gps != null)
432 rocket.set_position(new AltosLatLon(t_state.gps.lat, t_state.gps.lon), t_state.received_time);
435 if (receiver != null) {
436 here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
440 public void onCreateView(int map_type) {
441 map = new AltosMap(this);
442 map.set_maptype(map_type);
444 pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
445 /* arrow at the bottom of the launchpad image */
446 pad_off_x = pad_bitmap.getWidth() / 2;
447 pad_off_y = pad_bitmap.getHeight();
449 rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
450 /* arrow at the bottom of the rocket image */
451 rocket_off_x = rocket_bitmap.getWidth() / 2;
452 rocket_off_y = rocket_bitmap.getHeight();
454 here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
455 /* Center of the dot */
456 here_off_x = here_bitmap.getWidth() / 2;
457 here_off_y = here_bitmap.getHeight() / 2;
460 public void set_map_type(int map_type) {
462 map.set_maptype(map_type);
465 public AltosMapOffline(Context context, AttributeSet attrs) {
466 super(context, attrs);
467 scale_detector = new ScaleGestureDetector(context, this);