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