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