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