add beep when test completes
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / TabMapOffline.java
1 /*
2  * Copyright © 2013 Mike Beattie <mike@ethernal.org>
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_7.*;
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
35 class Rocket {
36         AltosLatLon     position;
37         String          name;
38         TabMapOffline   tab;
39
40         void paint() {
41                 tab.draw_bitmap(position, tab.rocket_bitmap, tab.rocket_off_x, tab.rocket_off_y);
42                 tab.draw_text(position, name, 0, 3*tab.rocket_bitmap.getHeight()/4);
43         }
44
45         void set_position(AltosLatLon position) {
46                 this.position = position;
47         }
48
49         Rocket(String name, TabMapOffline tab) {
50                 this.name = name;
51                 this.tab = tab;
52         }
53 }
54
55 public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
56
57         AltosDroid mAltosDroid;
58
59         AltosMap map;
60
61         AltosLatLon     here;
62         AltosLatLon     pad;
63
64         Canvas  canvas;
65         Paint   paint;
66
67         Bitmap  pad_bitmap;
68         int     pad_off_x, pad_off_y;
69         Bitmap  rocket_bitmap;
70         int     rocket_off_x, rocket_off_y;
71         Bitmap  here_bitmap;
72         int     here_off_x, here_off_y;
73
74         private boolean pad_set;
75
76         private TextView mDistanceView;
77         private TextView mBearingView;
78         private TextView mTargetLatitudeView;
79         private TextView mTargetLongitudeView;
80         private TextView mReceiverLatitudeView;
81         private TextView mReceiverLongitudeView;
82         private AltosMapView map_view;
83
84         private double mapAccuracy = -1;
85
86         int     stroke_width = 20;
87
88
89         void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
90                 if (lat_lon != null && map != null && map.transform != null) {
91                         AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
92
93                         Rect    bounds = new Rect();
94                         paint.getTextBounds(text, 0, text.length(), bounds);
95
96                         int     width = bounds.right - bounds.left;
97                         int     height = bounds.bottom - bounds.top;
98
99                         float x = pt.x;
100                         float y = pt.y;
101                         x = x - width / 2.0f - off_x;
102                         y = y + height / 2.0f - off_y;
103                         paint.setColor(0xff000000);
104                         canvas.drawText(text, 0, text.length(), x, y, paint);
105                 }
106         }
107
108         void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
109                 if (lat_lon != null && map != null && map.transform != null) {
110                         AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
111
112                         canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
113                 }
114         }
115
116         HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
117
118         /* AltosMapInterface */
119
120         static  final int       WHITE = 0xffffffff;
121         static  final int       RED   = 0xffff0000;
122         static  final int       PINK  = 0xffff8080;
123         static  final int       YELLOW= 0xffffff00;
124         static  final int       CYAN  = 0xff00ffff;
125         static  final int       BLUE  = 0xff0000ff;
126         static  final int       BLACK = 0xff000000;
127
128         public static final int stateColors[] = {
129                 WHITE,  // startup
130                 WHITE,  // idle
131                 WHITE,  // pad
132                 RED,    // boost
133                 PINK,   // fast
134                 YELLOW, // coast
135                 CYAN,   // drogue
136                 BLUE,   // main
137                 BLACK,  // landed
138                 BLACK,  // invalid
139                 CYAN,   // stateless
140         };
141
142         class MapPath extends AltosMapPath {
143
144                 boolean line_in(AltosPointDouble a, AltosPointDouble b) {
145                         final Rect bounds = canvas.getClipBounds();
146                         int left = (int) Math.floor (Math.min((float) a.x, (float) b.x) - stroke_width / 2.0f);
147                         int right = (int) Math.ceil(Math.max((float) a.x, (float) b.x) + stroke_width / 2.0f);
148                         int top = (int) Math.floor(Math.min((float) a.y, (float) b.y) - stroke_width / 2.0f);
149                         int bottom = (int) Math.ceil(Math.max((float) a.y, (float) b.y) + stroke_width / 2.0f);
150
151                         return left < bounds.right && bounds.left < right &&
152                                 top < bounds.bottom && bounds.top < bottom;
153                 }
154
155                 public void paint(AltosMapTransform t) {
156                         AltosPointDouble        prev = null;
157                         int                     cur_color = paint.getColor();
158
159                         for (AltosMapPathPoint point : points) {
160                                 AltosPointDouble        cur = t.screen(point.lat_lon);
161
162                                 if (prev != null && line_in(prev, cur)) {
163                                         int color;
164                                         if (0 <= point.state && point.state < stateColors.length)
165                                                 color = stateColors[point.state];
166                                         else
167                                                 color = stateColors[AltosLib.ao_flight_invalid];
168                                         if (color != cur_color) {
169                                                 paint.setColor(color);
170                                                 cur_color = color;
171                                         }
172                                         canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
173                                 }
174                                 prev = cur;
175                         }
176                 }
177
178                 public MapPath() {
179                         stroke_width = TabMapOffline.this.stroke_width;
180                 }
181         }
182
183         public AltosMapPath new_path() {
184                 return null;
185         }
186
187         class MapLine extends AltosMapLine {
188                 public void paint(AltosMapTransform t) {
189                 }
190
191                 public MapLine() {
192                 }
193         }
194
195         public AltosMapLine new_line() {
196                 return null;
197         }
198
199         class MapImage implements AltosImage {
200                 public Bitmap   bitmap;
201
202                 public void flush() {
203                         if (bitmap != null) {
204                                 bitmap.recycle();
205                                 bitmap = null;
206                         }
207                 }
208
209                 public MapImage(File file) {
210                         bitmap = BitmapFactory.decodeFile(file.getPath());
211                 }
212         }
213
214         public AltosImage load_image(File file) throws Exception {
215                 return new MapImage(file);
216         }
217
218         class MapMark extends AltosMapMark {
219                 public void paint(AltosMapTransform t) {
220                 }
221
222                 MapMark(double lat, double lon, int state) {
223                         super(lat, lon, state);
224                 }
225         }
226
227         public AltosMapMark new_mark(double lat, double lon, int state) {
228                 return new MapMark(lat, lon, state);
229         }
230
231         class MapTile extends AltosMapTile {
232                 public void paint(AltosMapTransform t) {
233                         AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
234
235                         if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
236                                 return;
237
238                         AltosImage              altos_image = cache.get(this, store, px_size, px_size);
239
240                         MapImage                map_image = (MapImage) altos_image;
241
242                         Bitmap                  bitmap = null;
243
244                         if (map_image != null)
245                                 bitmap = map_image.bitmap;
246
247                         if (bitmap != null) {
248                                 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
249                         } else {
250                                 paint.setColor(0xff808080);
251                                 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
252                                 if (t.has_location()) {
253                                         String  message = null;
254                                         switch (status) {
255                                         case AltosMapTile.loading:
256                                                 message = "Loading...";
257                                                 break;
258                                         case AltosMapTile.bad_request:
259                                                 message = "Internal error";
260                                                 break;
261                                         case AltosMapTile.failed:
262                                                 message = "Network error, check connection";
263                                                 break;
264                                         case AltosMapTile.forbidden:
265                                                 message = "Too many requests, try later";
266                                                 break;
267                                         }
268                                         if (message != null) {
269                                                 Rect    bounds = new Rect();
270                                                 paint.getTextBounds(message, 0, message.length(), bounds);
271
272                                                 int     width = bounds.right - bounds.left;
273                                                 int     height = bounds.bottom - bounds.top;
274
275                                                 float x = pt.x + px_size / 2.0f;
276                                                 float y = pt.y + px_size / 2.0f;
277                                                 x = x - width / 2.0f;
278                                                 y = y + height / 2.0f;
279                                                 paint.setColor(0xff000000);
280                                                 canvas.drawText(message, 0, message.length(), x, y, paint);
281                                         }
282                                 }
283                         }
284                 }
285
286                 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
287                         super(listener, upper_left, center, zoom, maptype, px_size, 2);
288                 }
289
290         }
291
292         public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
293                 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
294         }
295
296         public int width() {
297                 if (map_view != null)
298                         return map_view.getWidth();
299                 return 500;
300         }
301
302         public int height() {
303                 if (map_view != null)
304                         return map_view.getHeight();
305                 return 500;
306         }
307
308         public void repaint() {
309                 if (map_view != null)
310                         map_view.postInvalidate();
311         }
312
313         public void repaint(AltosRectangle damage) {
314                 if (map_view != null)
315                         map_view.postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
316         }
317
318         public void set_zoom_label(String label) {
319         }
320
321         public void debug(String format, Object ... arguments) {
322                 AltosDebug.debug(format, arguments);
323         }
324
325         @Override
326         public void onAttach(Activity activity) {
327                 super.onAttach(activity);
328                 mAltosDroid = (AltosDroid) activity;
329                 mAltosDroid.registerTab(this);
330
331                 map = new AltosMap(this);
332                 map.set_maptype(mAltosDroid.map_type);
333
334                 pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
335                 /* arrow at the bottom of the launchpad image */
336                 pad_off_x = pad_bitmap.getWidth() / 2;
337                 pad_off_y = pad_bitmap.getHeight();
338
339                 rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
340                 /* arrow at the bottom of the rocket image */
341                 rocket_off_x = rocket_bitmap.getWidth() / 2;
342                 rocket_off_y = rocket_bitmap.getHeight();
343
344                 here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
345                 /* Center of the dot */
346                 here_off_x = here_bitmap.getWidth() / 2;
347                 here_off_y = here_bitmap.getHeight() / 2;
348         }
349
350         @Override
351         public void onDetach() {
352                 super.onDetach();
353                 mAltosDroid = null;
354         }
355
356         @Override
357         public void onCreate(Bundle savedInstanceState) {
358                 super.onCreate(savedInstanceState);
359         }
360
361         @Override
362         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
363                 View v = inflater.inflate(R.layout.tab_map_offline, container, false);
364
365                 map_view = (AltosMapView)v.findViewById(R.id.map_view_offline);
366                 map_view.set_tab(this);
367                 mDistanceView  = (TextView)v.findViewById(R.id.distance_value_offline);
368                 mBearingView   = (TextView)v.findViewById(R.id.bearing_value_offline);
369                 mTargetLatitudeView  = (TextView)v.findViewById(R.id.target_lat_value_offline);
370                 mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value_offline);
371                 mReceiverLatitudeView  = (TextView)v.findViewById(R.id.receiver_lat_value_offline);
372                 mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value_offline);
373                 return v;
374         }
375
376         @Override
377         public void onActivityCreated(Bundle savedInstanceState) {
378                 super.onActivityCreated(savedInstanceState);
379         }
380
381         @Override
382         public void onDestroyView() {
383                 super.onDestroyView();
384
385                 mAltosDroid.unregisterTab(this);
386         }
387
388         private void center(double lat, double lon, double accuracy) {
389                 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
390                         if (map != null)
391                                 map.maybe_centre(lat, lon);
392                         mapAccuracy = accuracy;
393                 }
394         }
395
396         public String tab_name() { return "offmap"; }
397
398         public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
399                 if (from_receiver != null) {
400                         mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
401                         set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
402                 }
403
404                 if (state != null) {
405                         map.show(state, null);
406                         if (state.gps != null) {
407                                 mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
408                                 mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
409                                 if (state.gps.locked && state.gps.nsat >= 4)
410                                         center (state.gps.lat, state.gps.lon, 10);
411                         }
412                         if (state.pad_lat != AltosLib.MISSING && pad == null)
413                                 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
414
415                         int serial = state.serial;
416                         if (serial == AltosLib.MISSING)
417                                 serial = 0;
418
419                         Rocket  rocket = null;
420
421                         if (state.gps != null && state.gps.locked) {
422                                 if (!rockets.containsKey(serial)) {
423                                         rocket = new Rocket(String.format("%d", serial), this);
424                                         rockets.put(serial, rocket);
425                                 } else
426                                         rocket = rockets.get(serial);
427                                 rocket.set_position(new AltosLatLon(state.gps.lat, state.gps.lon));
428                         }
429                 }
430
431                 if (receiver != null) {
432                         double accuracy;
433
434                         here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
435                         if (receiver.hasAccuracy())
436                                 accuracy = receiver.getAccuracy();
437                         else
438                                 accuracy = 1000;
439                         mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S"));
440                         mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
441                         center (here.lat, here.lon, accuracy);
442                 }
443
444         }
445
446         @Override
447         public void set_map_type(int map_type) {
448                 if (map != null)
449                         map.set_maptype(map_type);
450         }
451
452         public TabMapOffline() {
453         }
454 }