6742e6060ba1e569596b3b5fc19a6e9fa5c7c0b4
[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.Arrays;
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 public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
36
37         AltosDroid mAltosDroid;
38
39         AltosMap map;
40
41         AltosLatLon     here;
42
43         Canvas  canvas;
44         Paint   paint;
45
46         Bitmap  pad_bitmap;
47         int     pad_off_x, pad_off_y;
48         Bitmap  rocket_bitmap;
49         int     rocket_off_x, rocket_off_y;
50         Bitmap  here_bitmap;
51         int     here_off_x, here_off_y;
52
53         private boolean pad_set;
54
55         private TextView mDistanceView;
56         private TextView mBearingView;
57         private TextView mTargetLatitudeView;
58         private TextView mTargetLongitudeView;
59         private TextView mReceiverLatitudeView;
60         private TextView mReceiverLongitudeView;
61
62         private double mapAccuracy = -1;
63
64         int     stroke_width = 20;
65
66         private void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
67                 if (lat_lon != null) {
68                         AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
69
70                         canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
71                 }
72         }
73
74         class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
75
76                 ScaleGestureDetector    scale_detector;
77                 boolean                 scaling;
78
79                 private void draw_positions() {
80                         if (map.last_position != null && here != null) {
81                                 AltosPointDouble        rocket_screen = map.transform.screen(map.last_position);
82                                 AltosPointDouble        here_screen = map.transform.screen(here);
83                                 paint.setColor(0xff8080ff);
84                                 canvas.drawLine((float) rocket_screen.x, (float) rocket_screen.y,
85                                                 (float) here_screen.x, (float) here_screen.y, paint);
86                         }
87                         draw_bitmap(map.last_position, rocket_bitmap, rocket_off_x, rocket_off_y);
88                         draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
89                 }
90
91                 protected void onDraw(Canvas view_canvas) {
92                         canvas = view_canvas;
93                         paint = new Paint(Paint.ANTI_ALIAS_FLAG);
94                         paint.setStrokeWidth(stroke_width);
95                         paint.setStrokeCap(Paint.Cap.ROUND);
96                         paint.setStrokeJoin(Paint.Join.ROUND);
97                         map.paint();
98                         draw_positions();
99                         canvas = null;
100                 }
101
102                 public boolean onScale(ScaleGestureDetector detector) {
103                         float   f = detector.getScaleFactor();
104                         AltosDebug.debug("onScale %f\n", f);
105                         if (f <= 0.8) {
106                                 map.set_zoom(map.get_zoom() - 1);
107                                 return true;
108                         }
109                         if (f >= 1.2) {
110                                 map.set_zoom(map.get_zoom() + 1);
111                                 return true;
112                         }
113                         return false;
114                 }
115
116                 public boolean onScaleBegin(ScaleGestureDetector detector) {
117                         AltosDebug.debug("onScaleBegin %f\n", detector.getScaleFactor());
118                         return true;
119                 }
120
121                 public void onScaleEnd(ScaleGestureDetector detector) {
122                         AltosDebug.debug("onScaleEnd %f\n", detector.getScaleFactor());
123                 }
124
125                 @Override
126                 public boolean dispatchTouchEvent(MotionEvent event) {
127                         scale_detector.onTouchEvent(event);
128
129                         if (scale_detector.isInProgress()) {
130                                 scaling = true;
131                         }
132
133                         if (scaling) {
134                                 if(AltosDebug.D) AltosDebug.debug("scale in progress\n");
135                                 if (event.getAction() == MotionEvent.ACTION_UP) {
136                                         AltosDebug.debug("scale finished\n");
137                                         scaling = false;
138                                 }
139                                 return true;
140                         }
141
142                         if (event.getAction() == MotionEvent.ACTION_DOWN) {
143                                 AltosDebug.debug("down event %g %g\n", event.getX(), event.getY());
144                                 map.touch_start((int) event.getX(), (int) event.getY(), true);
145                         } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
146                                 AltosDebug.debug("continue event %g %g\n", event.getX(), event.getY());
147                                 map.touch_continue((int) event.getX(), (int) event.getY(), true);
148                         }
149                         return true;
150                 }
151
152                 public MapView(Context context) {
153                         super(context);
154                         scale_detector = new ScaleGestureDetector(this.getContext(), this);
155                 }
156         }
157
158         class MapFragment extends Fragment {
159                 MapView map_view;
160
161                 public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) {
162                         map_view = new MapView(container.getContext());
163                         return map_view;
164                 }
165
166                 public MapFragment() {
167                 }
168         }
169
170         MapFragment map_fragment;
171
172         /* AltosMapInterface */
173
174         static  final int       WHITE = 0xffffffff;
175         static  final int       RED   = 0xffff0000;
176         static  final int       PINK  = 0xffff8080;
177         static  final int       YELLOW= 0xffffff00;
178         static  final int       CYAN  = 0xff00ffff;
179         static  final int       BLUE  = 0xff0000ff;
180         static  final int       BLACK = 0xff000000;
181
182         public static final int stateColors[] = {
183                 WHITE,  // startup
184                 WHITE,  // idle
185                 WHITE,  // pad
186                 RED,    // boost
187                 PINK,   // fast
188                 YELLOW, // coast
189                 CYAN,   // drogue
190                 BLUE,   // main
191                 BLACK,  // landed
192                 BLACK,  // invalid
193                 CYAN,   // stateless
194         };
195
196         class MapPath extends AltosMapPath {
197
198                 boolean line_in(AltosPointDouble a, AltosPointDouble b) {
199                         final Rect bounds = canvas.getClipBounds();
200                         int left = (int) Math.floor (Math.min((float) a.x, (float) b.x) - stroke_width / 2.0f);
201                         int right = (int) Math.ceil(Math.max((float) a.x, (float) b.x) + stroke_width / 2.0f);
202                         int top = (int) Math.floor(Math.min((float) a.y, (float) b.y) - stroke_width / 2.0f);
203                         int bottom = (int) Math.ceil(Math.max((float) a.y, (float) b.y) + stroke_width / 2.0f);
204
205                         return left < bounds.right && bounds.left < right &&
206                                 top < bounds.bottom && bounds.top < bottom;
207                 }
208
209                 public void paint(AltosMapTransform t) {
210                         AltosPointDouble        prev = null;
211                         int                     cur_color = paint.getColor();
212
213                         for (AltosMapPathPoint point : points) {
214                                 AltosPointDouble        cur = t.screen(point.lat_lon);
215
216                                 if (prev != null && line_in(prev, cur)) {
217                                         int color;
218                                         if (0 <= point.state && point.state < stateColors.length)
219                                                 color = stateColors[point.state];
220                                         else
221                                                 color = stateColors[AltosLib.ao_flight_invalid];
222                                         if (color != cur_color) {
223                                                 paint.setColor(color);
224                                                 cur_color = color;
225                                         }
226                                         canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
227                                 }
228                                 prev = cur;
229                         }
230                 }
231
232                 public MapPath() {
233                         stroke_width = TabMapOffline.this.stroke_width;
234                 }
235         }
236
237         public AltosMapPath new_path() {
238                 return null;
239         }
240
241         class MapLine extends AltosMapLine {
242                 public void paint(AltosMapTransform t) {
243                 }
244
245                 public MapLine() {
246                 }
247         }
248
249         public AltosMapLine new_line() {
250                 return null;
251         }
252
253         class MapImage implements AltosImage {
254                 public Bitmap   bitmap;
255
256                 public void flush() {
257                         if (bitmap != null) {
258                                 bitmap.recycle();
259                                 bitmap = null;
260                         }
261                 }
262
263                 public MapImage(File file) {
264                         bitmap = BitmapFactory.decodeFile(file.getPath());
265                 }
266         }
267
268         public AltosImage load_image(File file) throws Exception {
269                 return new MapImage(file);
270         }
271
272         class MapMark extends AltosMapMark {
273                 public void paint(AltosMapTransform t) {
274                         if (state == AltosLib.ao_flight_boost)
275                                 draw_bitmap(lat_lon, pad_bitmap, pad_off_x, pad_off_y);
276                 }
277
278                 MapMark(double lat, double lon, int state) {
279                         super(lat, lon, state);
280                 }
281         }
282
283         public AltosMapMark new_mark(double lat, double lon, int state) {
284                 return new MapMark(lat, lon, state);
285         }
286
287         class MapTile extends AltosMapTile {
288                 public void paint(AltosMapTransform t) {
289                         AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
290
291                         if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
292                                 return;
293
294                         AltosImage              altos_image = cache.get(this, store, px_size, px_size);
295
296                         MapImage                map_image = (MapImage) altos_image;
297
298                         Bitmap                  bitmap = null;
299
300                         if (map_image != null)
301                                 bitmap = map_image.bitmap;
302
303                         if (bitmap != null) {
304                                 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
305                         } else {
306                                 paint.setColor(0xff808080);
307                                 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
308                                 if (t.has_location()) {
309                                         String  message = null;
310                                         switch (status) {
311                                         case AltosMapTile.loading:
312                                                 message = "Loading...";
313                                                 break;
314                                         case AltosMapTile.bad_request:
315                                                 message = "Internal error";
316                                                 break;
317                                         case AltosMapTile.failed:
318                                                 message = "Network error, check connection";
319                                                 break;
320                                         case AltosMapTile.forbidden:
321                                                 message = "Too many requests, try later";
322                                                 break;
323                                         }
324                                         if (message != null) {
325                                                 Rect    bounds = new Rect();
326                                                 paint.getTextBounds(message, 0, message.length(), bounds);
327
328                                                 int     width = bounds.right - bounds.left;
329                                                 int     height = bounds.bottom - bounds.top;
330
331                                                 float x = pt.x + px_size / 2.0f;
332                                                 float y = pt.y + px_size / 2.0f;
333                                                 x = x - width / 2.0f;
334                                                 y = y + height / 2.0f;
335                                                 paint.setColor(0xff000000);
336                                                 canvas.drawText(message, 0, message.length(), x, y, paint);
337                                         }
338                                 }
339                         }
340
341                 }
342
343                 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
344                         super(listener, upper_left, center, zoom, maptype, px_size, 2);
345                 }
346
347         }
348
349         public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
350                 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
351         }
352
353         public int width() {
354                 if (map_fragment != null && map_fragment.map_view != null)
355                         return map_fragment.map_view.getWidth();
356                 return 500;
357         }
358
359         public int height() {
360                 if (map_fragment != null && map_fragment.map_view != null)
361                         return map_fragment.map_view.getHeight();
362                 return 500;
363         }
364
365         public void repaint() {
366                 this.getActivity().runOnUiThread(new Runnable() {
367                                 public void run() {
368                                         if (map_fragment != null && map_fragment.map_view != null)
369                                                 map_fragment.map_view.invalidate();
370                                 }
371                         });
372         }
373
374         public void repaint(AltosRectangle t_damage) {
375                 final AltosRectangle damage = t_damage;
376                 this.getActivity().runOnUiThread(new Runnable() {
377                                 public void run() {
378                                         if (map_fragment != null && map_fragment.map_view != null)
379                                                 map_fragment.map_view.invalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
380                                 }
381                         });
382         }
383
384         public void set_zoom_label(String label) {
385         }
386
387         @Override
388         public void onAttach(Activity activity) {
389                 super.onAttach(activity);
390                 mAltosDroid = (AltosDroid) activity;
391                 mAltosDroid.registerTab(this);
392                 pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
393                 /* arrow at the bottom of the launchpad image */
394                 pad_off_x = pad_bitmap.getWidth() / 2;
395                 pad_off_y = pad_bitmap.getHeight();
396
397                 rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
398                 /* arrow at the bottom of the rocket image */
399                 rocket_off_x = rocket_bitmap.getWidth() / 2;
400                 rocket_off_y = rocket_bitmap.getHeight();
401
402                 here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
403                 /* Center of the dot */
404                 here_off_x = here_bitmap.getWidth() / 2;
405                 here_off_y = here_bitmap.getHeight() / 2;
406         }
407
408         @Override
409         public void onCreate(Bundle savedInstanceState) {
410                 super.onCreate(savedInstanceState);
411         }
412
413         @Override
414         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
415                 View v = inflater.inflate(R.layout.tab_map, container, false);
416
417                 map_fragment = new MapFragment();
418                 map = new AltosMap(this);
419                 mDistanceView  = (TextView)v.findViewById(R.id.distance_value);
420                 mBearingView   = (TextView)v.findViewById(R.id.bearing_value);
421                 mTargetLatitudeView  = (TextView)v.findViewById(R.id.target_lat_value);
422                 mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value);
423                 mReceiverLatitudeView  = (TextView)v.findViewById(R.id.receiver_lat_value);
424                 mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value);
425                 return v;
426         }
427
428         @Override
429         public void onActivityCreated(Bundle savedInstanceState) {
430                 super.onActivityCreated(savedInstanceState);
431                 getChildFragmentManager().beginTransaction().add(R.id.map, map_fragment).commit();
432         }
433  
434         @Override
435         public void onDestroyView() {
436                 super.onDestroyView();
437
438                 mAltosDroid.unregisterTab(this);
439                 mAltosDroid = null;
440                 map_fragment = null;
441
442 //              Fragment fragment = (getFragmentManager().findFragmentById(R.id.map));
443 //              FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
444 //              ft.remove(fragment);
445 //              ft.commit();
446         }
447
448         private void setupMap() {
449         }
450
451         private void center(double lat, double lon, double accuracy) {
452                 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
453                         if (map != null)
454                                 map.maybe_centre(lat, lon);
455                         mapAccuracy = accuracy;
456                 }
457         }
458
459         public String tab_name() { return "offmap"; }
460
461         public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
462                 if (from_receiver != null) {
463                         mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
464                         set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
465                 }
466
467                 if (state != null) {
468                         map.show(state, null);
469                         if (state.gps != null) {
470                                 mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
471                                 mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
472                                 if (state.gps.locked && state.gps.nsat >= 4)
473                                         center (state.gps.lat, state.gps.lon, 10);
474                         }
475                 }
476
477                 if (receiver != null) {
478                         double accuracy;
479
480                         here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
481                         if (receiver.hasAccuracy())
482                                 accuracy = receiver.getAccuracy();
483                         else
484                                 accuracy = 1000;
485                         mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S"));
486                         mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
487                         center (here.lat, here.lon, accuracy);
488                 }
489
490         }
491
492         public TabMapOffline() {
493         }
494 }