Bump java lib versions in preparation for 1.9.2
[fw/altos] / altosdroid / app / src / main / java / 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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
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.
13  *
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.
17  */
18
19 package org.altusmetrum.AltosDroid;
20
21 import java.util.*;
22 import java.io.*;
23
24 import org.altusmetrum.altoslib_14.*;
25
26 import android.graphics.*;
27 import android.view.*;
28 import android.location.Location;
29 import android.content.*;
30 import android.util.*;
31
32 class Rocket implements Comparable {
33         AltosLatLon     position;
34         String          name;
35         int             serial;
36         long            last_packet;
37         boolean         active;
38         AltosMapOffline map_offline;
39
40         void paint() {
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);
43         }
44
45         void set_position(AltosLatLon position, long last_packet) {
46                 this.position = position;
47                 this.last_packet = last_packet;
48         }
49
50         void set_active(boolean active) {
51                 this.active = active;
52         }
53
54         public int compareTo(Object o) {
55                 Rocket other = (Rocket) o;
56
57                 if (active && !other.active)
58                         return 1;
59                 if (other.active && !active)
60                         return -1;
61
62                 long    diff = last_packet - other.last_packet;
63
64                 if (diff > 0)
65                         return 1;
66                 if (diff < 0)
67                         return -1;
68                 return 0;
69         }
70
71         Rocket(int serial, AltosMapOffline map_offline) {
72                 this.serial = serial;
73                 this.name = String.format("%d", serial);
74                 this.map_offline = map_offline;
75         }
76 }
77
78 public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface, AltosMapTypeListener {
79         ScaleGestureDetector    scale_detector;
80         boolean                 scaling;
81         AltosMap                map;
82         AltosDroid              altos_droid;
83
84         static int scale = 1;
85
86         AltosLatLon     here;
87         AltosLatLon     there;
88         AltosLatLon     pad;
89
90         Canvas  canvas;
91         Paint   paint;
92
93         Bitmap  pad_bitmap;
94         int     pad_off_x, pad_off_y;
95         Bitmap  rocket_bitmap;
96         int     rocket_off_x, rocket_off_y;
97         Bitmap  here_bitmap;
98         int     here_off_x, here_off_y;
99
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;
107
108         public static final int stateColors[] = {
109                 WHITE,  // startup
110                 WHITE,  // idle
111                 WHITE,  // pad
112                 RED,    // boost
113                 PINK,   // fast
114                 YELLOW, // coast
115                 CYAN,   // drogue
116                 BLUE,   // main
117                 BLACK,  // landed
118                 BLACK,  // invalid
119                 CYAN,   // stateless
120         };
121
122         /* AltosMapInterface */
123         public void debug(String format, Object ... arguments) {
124                 AltosDebug.debug(format, arguments);
125         }
126
127         class MapTile extends AltosMapTile {
128                 public void paint(AltosMapTransform t) {
129                         AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
130
131                         if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
132                                 return;
133
134                         AltosImage              altos_image = this.get_image();
135
136                         MapImage                map_image = (MapImage) altos_image;
137
138                         Bitmap                  bitmap = null;
139
140                         if (map_image != null)
141                                 bitmap = map_image.bitmap;
142
143                         if (bitmap != null) {
144                                 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
145                         } else {
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;
150                                         switch (status) {
151                                         case AltosMapTile.fetching:
152                                                 message = "Fetching...";
153                                                 break;
154                                         case AltosMapTile.bad_request:
155                                                 message = "Internal error";
156                                                 break;
157                                         case AltosMapTile.failed:
158                                                 message = "Network error";
159                                                 break;
160                                         case AltosMapTile.forbidden:
161                                                 message = "Outside of known launch areas";
162                                                 break;
163                                         }
164                                         if (message != null) {
165                                                 Rect    bounds = new Rect();
166                                                 paint.getTextBounds(message, 0, message.length(), bounds);
167
168                                                 int     width = bounds.right - bounds.left;
169                                                 int     height = bounds.bottom - bounds.top;
170
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);
177                                         }
178                                 }
179                         }
180                 }
181
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);
184                 }
185
186         }
187
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);
190         }
191
192         public AltosMapPath new_path() {
193                 return null;
194         }
195
196         public AltosMapLine new_line() {
197                 return null;
198         }
199
200         class MapImage implements AltosImage {
201                 public Bitmap   bitmap;
202
203                 public void flush() {
204                         if (bitmap != null) {
205                                 bitmap.recycle();
206                                 bitmap = null;
207                         }
208                 }
209
210                 public MapImage(File file) {
211                         bitmap = BitmapFactory.decodeFile(file.getPath());
212                 }
213         }
214
215         public AltosImage load_image(File file) throws Exception {
216                 return new MapImage(file);
217         }
218
219         class MapMark extends AltosMapMark {
220                 public void paint(AltosMapTransform t) {
221                 }
222
223                 MapMark(double lat, double lon, int state) {
224                         super(lat, lon, state);
225                 }
226         }
227
228         public AltosMapMark new_mark(double lat, double lon, int state) {
229                 return new MapMark(lat, lon, state);
230         }
231
232         public int width() {
233                 return getWidth();
234         }
235
236         public int height() {
237                 return getHeight();
238         }
239
240         public void repaint() {
241                 postInvalidate();
242         }
243
244         public void repaint(AltosRectangle damage) {
245                 postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
246         }
247
248         public void set_zoom_label(String label) {
249         }
250
251         public void select_object(AltosLatLon latlon) {
252                 if (map.transform == null)
253                         return;
254                 ArrayList<Integer>      near = new ArrayList<Integer>();
255
256                 for (Rocket rocket : sorted_rockets()) {
257                         if (rocket.position == null) {
258                                 debug("rocket %d has no position\n", rocket.serial);
259                                 continue;
260                         }
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);
266                         }
267                 }
268                 if (near.size() != 0)
269                         altos_droid.touch_trackers(near.toArray(new Integer[0]));
270         }
271
272         class Line {
273                 AltosLatLon     a, b;
274
275                 void paint() {
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,
282                                                     paint);
283                         }
284                 }
285
286                 void set_a(AltosLatLon a) {
287                         this.a = a;
288                 }
289
290                 void set_b(AltosLatLon b) {
291                         this.b = b;
292                 }
293
294                 Line() {
295                 }
296         }
297
298         Line line = new Line();
299
300         int     stroke_width = 20;
301
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));
305
306                         Rect    bounds = new Rect();
307                         paint.getTextBounds(text, 0, text.length(), bounds);
308
309                         int     width = bounds.right - bounds.left;
310                         int     height = bounds.bottom - bounds.top;
311
312                         float x = pt.x;
313                         float y = pt.y;
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);
318                 }
319         }
320
321         HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
322
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));
326
327                         canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
328                 }
329         }
330
331         private Rocket[] sorted_rockets() {
332                 Rocket[]        rocket_array = rockets.values().toArray(new Rocket[0]);
333
334                 Arrays.sort(rocket_array);
335                 return rocket_array;
336         }
337
338         private void draw_positions() {
339                 line.set_a(there);
340                 line.set_b(here);
341                 line.paint();
342                 draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
343
344                 for (Rocket rocket : sorted_rockets())
345                         rocket.paint();
346                 draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
347         }
348
349         @Override
350         protected void onDraw(Canvas view_canvas) {
351                 if (map == null) {
352                         debug("MapView draw without map\n");
353                         return;
354                 }
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);
361                 map.paint();
362                 draw_positions();
363                 canvas = null;
364         }
365
366         public boolean onScale(ScaleGestureDetector detector) {
367                 float   f = detector.getScaleFactor();
368
369                 if (f <= 0.8) {
370                         map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
371                         return true;
372                 }
373                 if (f >= 1.2) {
374                         map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
375                         return true;
376                 }
377                 return false;
378         }
379
380         public boolean onScaleBegin(ScaleGestureDetector detector) {
381                 return true;
382         }
383
384         public void onScaleEnd(ScaleGestureDetector detector) {
385         }
386
387         @Override
388         public boolean dispatchTouchEvent(MotionEvent event) {
389                 scale_detector.onTouchEvent(event);
390
391                 if (scale_detector.isInProgress()) {
392                         scaling = true;
393                 }
394
395                 if (scaling) {
396                         if (event.getAction() == MotionEvent.ACTION_UP) {
397                                 scaling = false;
398                         }
399                         return true;
400                 }
401
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);
408                 }
409                 return true;
410         }
411
412         double  mapAccuracy;
413
414         public void center(double lat, double lon, double accuracy) {
415                 if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) {
416                         if (map != null)
417                                 map.maybe_centre(lat, lon);
418                         mapAccuracy = accuracy;
419                 }
420         }
421
422         public void set_visible(boolean visible) {
423                 if (visible)
424                         setVisibility(VISIBLE);
425                 else
426                         setVisibility(GONE);
427         }
428
429         public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
430                 boolean changed = false;
431
432                 if (state != null) {
433                         map.show(state, null);
434                         if (state.pad_lat != AltosLib.MISSING && pad == null)
435                                 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
436                 }
437
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]);
441
442                         /* remove deleted keys */
443                         for (int serial : old_serial) {
444                                 if (!telem_state.containsKey(serial))
445                                         rockets.remove(serial);
446                         }
447
448                         /* set remaining keys */
449
450                         for (int serial : new_serial) {
451                                 Rocket          rocket;
452                                 AltosState      t_state = telem_state.get(serial);
453                                 if (rockets.containsKey(serial))
454                                         rocket = rockets.get(serial);
455                                 else {
456                                         rocket = new Rocket(serial, this);
457                                         rockets.put(serial, rocket);
458                                 }
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)
463                                                 there = latlon;
464                                 }
465                                 if (state != null)
466                                         rocket.set_active(state.cal_data().serial == serial);
467                         }
468                 }
469                 if (receiver != null) {
470                         AltosLatLon new_here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
471                         if (!new_here.equals(here)) {
472                                 here = new_here;
473                                 AltosDebug.debug("Location changed, redraw");
474                                 repaint();
475                         }
476                 }
477         }
478
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());
484
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();
489
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();
494
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;
499         }
500
501         public void onDestroyView() {
502                 AltosPreferences.unregister_map_type_listener(this);
503         }
504
505         public void map_type_changed(int map_type) {
506                 if (map != null)
507                         map.set_maptype(map_type);
508         }
509
510         public AltosMapOffline(Context context, AttributeSet attrs) {
511                 super(context, attrs);
512                 this.altos_droid = altos_droid;
513                 scale_detector = new ScaleGestureDetector(context, this);
514         }
515 }