Bump Java library versions
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / AltosMapOffline.java
1 /*
2  * Copyright © 2015 Keith Packard <keithp@keithp.com>
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.AltosDroid;
19
20 import java.util.*;
21 import java.io.*;
22
23 import org.altusmetrum.altoslib_8.*;
24
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.*;
35
36 class Rocket implements Comparable {
37         AltosLatLon     position;
38         String          name;
39         int             serial;
40         long            last_packet;
41         boolean         active;
42         AltosMapOffline map_offline;
43
44         void paint() {
45                 map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
46                 map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
47         }
48
49         void set_position(AltosLatLon position, long last_packet) {
50                 this.position = position;
51                 this.last_packet = last_packet;
52         }
53
54         void set_active(boolean active) {
55                 this.active = active;
56         }
57
58         public int compareTo(Object o) {
59                 Rocket other = (Rocket) o;
60
61                 if (active && !other.active)
62                         return 1;
63                 if (other.active && !active)
64                         return -1;
65
66                 long    diff = last_packet - other.last_packet;
67
68                 if (diff > 0)
69                         return 1;
70                 if (diff < 0)
71                         return -1;
72                 return 0;
73         }
74
75         Rocket(int serial, AltosMapOffline map_offline) {
76                 this.serial = serial;
77                 this.name = String.format("%d", serial);
78                 this.map_offline = map_offline;
79         }
80 }
81
82 public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface {
83         ScaleGestureDetector    scale_detector;
84         boolean                 scaling;
85         AltosMap                map;
86         AltosDroid              altos_droid;
87
88         AltosLatLon     here;
89         AltosLatLon     there;
90         AltosLatLon     pad;
91
92         Canvas  canvas;
93         Paint   paint;
94
95         Bitmap  pad_bitmap;
96         int     pad_off_x, pad_off_y;
97         Bitmap  rocket_bitmap;
98         int     rocket_off_x, rocket_off_y;
99         Bitmap  here_bitmap;
100         int     here_off_x, here_off_y;
101
102         static  final int       WHITE = 0xffffffff;
103         static  final int       RED   = 0xffff0000;
104         static  final int       PINK  = 0xffff8080;
105         static  final int       YELLOW= 0xffffff00;
106         static  final int       CYAN  = 0xff00ffff;
107         static  final int       BLUE  = 0xff0000ff;
108         static  final int       BLACK = 0xff000000;
109
110         public static final int stateColors[] = {
111                 WHITE,  // startup
112                 WHITE,  // idle
113                 WHITE,  // pad
114                 RED,    // boost
115                 PINK,   // fast
116                 YELLOW, // coast
117                 CYAN,   // drogue
118                 BLUE,   // main
119                 BLACK,  // landed
120                 BLACK,  // invalid
121                 CYAN,   // stateless
122         };
123
124         /* AltosMapInterface */
125         public void debug(String format, Object ... arguments) {
126                 AltosDebug.debug(format, arguments);
127         }
128
129         class MapTile extends AltosMapTile {
130                 public void paint(AltosMapTransform t) {
131                         AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
132
133                         if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
134                                 return;
135
136                         AltosImage              altos_image = cache.get(this, store, px_size, px_size);
137
138                         MapImage                map_image = (MapImage) altos_image;
139
140                         Bitmap                  bitmap = null;
141
142                         if (map_image != null)
143                                 bitmap = map_image.bitmap;
144
145                         if (bitmap != null) {
146                                 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
147                         } else {
148                                 paint.setColor(0xff808080);
149                                 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
150                                 if (t.has_location()) {
151                                         String  message = null;
152                                         switch (status) {
153                                         case AltosMapTile.loading:
154                                                 message = "Loading...";
155                                                 break;
156                                         case AltosMapTile.bad_request:
157                                                 message = "Internal error";
158                                                 break;
159                                         case AltosMapTile.failed:
160                                                 message = "Network error, check connection";
161                                                 break;
162                                         case AltosMapTile.forbidden:
163                                                 message = "Too many requests, try later";
164                                                 break;
165                                         }
166                                         if (message != null) {
167                                                 Rect    bounds = new Rect();
168                                                 paint.getTextBounds(message, 0, message.length(), bounds);
169
170                                                 int     width = bounds.right - bounds.left;
171                                                 int     height = bounds.bottom - bounds.top;
172
173                                                 float x = pt.x + px_size / 2.0f;
174                                                 float y = pt.y + px_size / 2.0f;
175                                                 x = x - width / 2.0f;
176                                                 y = y + height / 2.0f;
177                                                 paint.setColor(0xff000000);
178                                                 canvas.drawText(message, 0, message.length(), x, y, paint);
179                                         }
180                                 }
181                         }
182                 }
183
184                 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
185                         super(listener, upper_left, center, zoom, maptype, px_size, 2);
186                 }
187
188         }
189
190         public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
191                 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
192         }
193
194         public AltosMapPath new_path() {
195                 return null;
196         }
197
198         public AltosMapLine new_line() {
199                 return null;
200         }
201
202         class MapImage implements AltosImage {
203                 public Bitmap   bitmap;
204
205                 public void flush() {
206                         if (bitmap != null) {
207                                 bitmap.recycle();
208                                 bitmap = null;
209                         }
210                 }
211
212                 public MapImage(File file) {
213                         bitmap = BitmapFactory.decodeFile(file.getPath());
214                 }
215         }
216
217         public AltosImage load_image(File file) throws Exception {
218                 return new MapImage(file);
219         }
220
221         class MapMark extends AltosMapMark {
222                 public void paint(AltosMapTransform t) {
223                 }
224
225                 MapMark(double lat, double lon, int state) {
226                         super(lat, lon, state);
227                 }
228         }
229
230         public AltosMapMark new_mark(double lat, double lon, int state) {
231                 return new MapMark(lat, lon, state);
232         }
233
234         public int width() {
235                 return getWidth();
236         }
237
238         public int height() {
239                 return getHeight();
240         }
241
242         public void repaint() {
243                 postInvalidate();
244         }
245
246         public void repaint(AltosRectangle damage) {
247                 postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
248         }
249
250         public void set_zoom_label(String label) {
251         }
252
253         public void select_object(AltosLatLon latlon) {
254                 if (map.transform == null)
255                         return;
256                 ArrayList<Integer>      near = new ArrayList<Integer>();
257
258                 for (Rocket rocket : sorted_rockets()) {
259                         if (rocket.position == null) {
260                                 debug("rocket %d has no position\n", rocket.serial);
261                                 continue;
262                         }
263                         double distance = map.transform.hypot(latlon, rocket.position);
264                         debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth());
265                         if (distance < rocket_bitmap.getWidth() * 2.0) {
266                                 debug("selecting %d\n", rocket.serial);
267                                 near.add(rocket.serial);
268                         }
269                 }
270                 if (near.size() != 0)
271                         altos_droid.touch_trackers(near.toArray(new Integer[0]));
272         }
273
274         class Line {
275                 AltosLatLon     a, b;
276
277                 void paint() {
278                         if (a != null && b != null) {
279                                 AltosPointDouble        a_screen = map.transform.screen(a);
280                                 AltosPointDouble        b_screen = map.transform.screen(b);
281                                 paint.setColor(0xff8080ff);
282                                 canvas.drawLine((float) a_screen.x, (float) a_screen.y,
283                                                     (float) b_screen.x, (float) b_screen.y,
284                                                     paint);
285                         }
286                 }
287
288                 void set_a(AltosLatLon a) {
289                         this.a = a;
290                 }
291
292                 void set_b(AltosLatLon b) {
293                         this.b = b;
294                 }
295
296                 Line() {
297                 }
298         }
299
300         Line line = new Line();
301
302         int     stroke_width = 20;
303
304         void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
305                 if (lat_lon != null && map != null && map.transform != null) {
306                         AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
307
308                         Rect    bounds = new Rect();
309                         paint.getTextBounds(text, 0, text.length(), bounds);
310
311                         int     width = bounds.right - bounds.left;
312                         int     height = bounds.bottom - bounds.top;
313
314                         float x = pt.x;
315                         float y = pt.y;
316                         x = x - width / 2.0f - off_x;
317                         y = y + height / 2.0f - off_y;
318                         paint.setColor(0xff000000);
319                         canvas.drawText(text, 0, text.length(), x, y, paint);
320                 }
321         }
322
323         HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
324
325         void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
326                 if (lat_lon != null && map != null && map.transform != null) {
327                         AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
328
329                         canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
330                 }
331         }
332
333         private Rocket[] sorted_rockets() {
334                 Rocket[]        rocket_array = rockets.values().toArray(new Rocket[0]);
335
336                 Arrays.sort(rocket_array);
337                 return rocket_array;
338         }
339
340         private void draw_positions() {
341                 line.set_a(there);
342                 line.set_b(here);
343                 line.paint();
344                 draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
345
346                 for (Rocket rocket : sorted_rockets())
347                         rocket.paint();
348                 draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
349         }
350
351         @Override public void invalidate() {
352                 Rect r = new Rect();
353                 getDrawingRect(r);
354                 super.invalidate();
355         }
356
357         @Override public void invalidate(int l, int t, int r, int b) {
358                 Rect rect = new Rect();
359                 getDrawingRect(rect);
360                 super.invalidate();
361         }
362
363         @Override
364         protected void onDraw(Canvas view_canvas) {
365                 if (map == null) {
366                         debug("MapView draw without map\n");
367                         return;
368                 }
369                 canvas = view_canvas;
370                 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
371                 paint.setStrokeWidth(stroke_width);
372                 paint.setStrokeCap(Paint.Cap.ROUND);
373                 paint.setStrokeJoin(Paint.Join.ROUND);
374                 paint.setTextSize(40);
375                 map.paint();
376                 draw_positions();
377                 canvas = null;
378         }
379
380         public boolean onScale(ScaleGestureDetector detector) {
381                 float   f = detector.getScaleFactor();
382
383                 if (f <= 0.8) {
384                         map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
385                         return true;
386                 }
387                 if (f >= 1.2) {
388                         map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
389                         return true;
390                 }
391                 return false;
392         }
393
394         public boolean onScaleBegin(ScaleGestureDetector detector) {
395                 return true;
396         }
397
398         public void onScaleEnd(ScaleGestureDetector detector) {
399         }
400
401         @Override
402         public boolean dispatchTouchEvent(MotionEvent event) {
403                 scale_detector.onTouchEvent(event);
404
405                 if (scale_detector.isInProgress()) {
406                         scaling = true;
407                 }
408
409                 if (scaling) {
410                         if (event.getAction() == MotionEvent.ACTION_UP) {
411                                 scaling = false;
412                         }
413                         return true;
414                 }
415
416                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
417                         map.touch_start((int) event.getX(), (int) event.getY(), true);
418                 } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
419                         map.touch_continue((int) event.getX(), (int) event.getY(), true);
420                 } else if (event.getAction() == MotionEvent.ACTION_UP) {
421                         map.touch_stop((int) event.getX(), (int) event.getY(), true);
422                 }
423                 return true;
424         }
425
426         double  mapAccuracy;
427
428         public void center(double lat, double lon, double accuracy) {
429                 if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) {
430                         if (map != null)
431                                 map.maybe_centre(lat, lon);
432                         mapAccuracy = accuracy;
433                 }
434         }
435
436         public void set_visible(boolean visible) {
437                 if (visible)
438                         setVisibility(VISIBLE);
439                 else
440                         setVisibility(GONE);
441         }
442
443         public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
444                 if (state != null) {
445                         map.show(state, null);
446                         if (state.pad_lat != AltosLib.MISSING && pad == null)
447                                 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
448                 }
449
450                 if (telem_state != null) {
451                         Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
452                         Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
453
454                         /* remove deleted keys */
455                         for (int serial : old_serial) {
456                                 if (!telem_state.states.containsKey(serial))
457                                         rockets.remove(serial);
458                         }
459
460                         /* set remaining keys */
461
462                         for (int serial : new_serial) {
463                                 Rocket          rocket;
464                                 AltosState      t_state = telem_state.states.get(serial);
465                                 if (rockets.containsKey(serial))
466                                         rocket = rockets.get(serial);
467                                 else {
468                                         rocket = new Rocket(serial, this);
469                                         rockets.put(serial, rocket);
470                                 }
471                                 if (t_state.gps != null) {
472                                         AltosLatLon     latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon);
473                                         rocket.set_position(latlon, t_state.received_time);
474                                         if (state.serial == serial)
475                                                 there = latlon;
476                                 }
477                                 if (state != null)
478                                         rocket.set_active(state.serial == serial);
479                         }
480                 }
481                 if (receiver != null) {
482                         here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
483                 }
484         }
485
486         public void onCreateView(AltosDroid altos_droid) {
487                 this.altos_droid = altos_droid;
488                 map = new AltosMap(this);
489                 map.set_maptype(altos_droid.map_type);
490
491                 pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
492                 /* arrow at the bottom of the launchpad image */
493                 pad_off_x = pad_bitmap.getWidth() / 2;
494                 pad_off_y = pad_bitmap.getHeight();
495
496                 rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
497                 /* arrow at the bottom of the rocket image */
498                 rocket_off_x = rocket_bitmap.getWidth() / 2;
499                 rocket_off_y = rocket_bitmap.getHeight();
500
501                 here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
502                 /* Center of the dot */
503                 here_off_x = here_bitmap.getWidth() / 2;
504                 here_off_y = here_bitmap.getHeight() / 2;
505         }
506
507         public void set_map_type(int map_type) {
508                 if (map != null)
509                         map.set_maptype(map_type);
510         }
511
512         public AltosMapOffline(Context context, AttributeSet attrs) {
513                 super(context, attrs);
514                 this.altos_droid = altos_droid;
515                 scale_detector = new ScaleGestureDetector(context, this);
516         }
517 }