2 * Copyright © 2013 Mike Beattie <mike@ethernal.org>
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.
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.
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.
18 package org.altusmetrum.AltosDroid;
20 import java.util.Arrays;
23 import org.altusmetrum.altoslib_7.*;
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.*;
35 public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
37 AltosDroid mAltosDroid;
48 int pad_off_x, pad_off_y;
50 int rocket_off_x, rocket_off_y;
52 int here_off_x, here_off_y;
54 private boolean pad_set;
56 private TextView mDistanceView;
57 private TextView mBearingView;
58 private TextView mTargetLatitudeView;
59 private TextView mTargetLongitudeView;
60 private TextView mReceiverLatitudeView;
61 private TextView mReceiverLongitudeView;
63 private double mapAccuracy = -1;
65 int stroke_width = 20;
67 private void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
68 if (lat_lon != null) {
69 AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
71 canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
75 class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
77 ScaleGestureDetector scale_detector;
80 private void draw_positions() {
81 if (map.last_position != null && here != null) {
82 AltosPointDouble rocket_screen = map.transform.screen(map.last_position);
83 AltosPointDouble here_screen = map.transform.screen(here);
84 paint.setColor(0xff8080ff);
85 canvas.drawLine((float) rocket_screen.x, (float) rocket_screen.y,
86 (float) here_screen.x, (float) here_screen.y, paint);
88 draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
89 draw_bitmap(map.last_position, rocket_bitmap, rocket_off_x, rocket_off_y);
90 draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
93 protected void onDraw(Canvas view_canvas) {
95 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
96 paint.setStrokeWidth(stroke_width);
97 paint.setStrokeCap(Paint.Cap.ROUND);
98 paint.setStrokeJoin(Paint.Join.ROUND);
104 public boolean onScale(ScaleGestureDetector detector) {
105 float f = detector.getScaleFactor();
106 AltosDebug.debug("onScale %f\n", f);
108 map.set_zoom(map.get_zoom() - 1);
112 map.set_zoom(map.get_zoom() + 1);
118 public boolean onScaleBegin(ScaleGestureDetector detector) {
119 AltosDebug.debug("onScaleBegin %f\n", detector.getScaleFactor());
123 public void onScaleEnd(ScaleGestureDetector detector) {
124 AltosDebug.debug("onScaleEnd %f\n", detector.getScaleFactor());
128 public boolean dispatchTouchEvent(MotionEvent event) {
129 scale_detector.onTouchEvent(event);
131 if (scale_detector.isInProgress()) {
136 if(AltosDebug.D) AltosDebug.debug("scale in progress\n");
137 if (event.getAction() == MotionEvent.ACTION_UP) {
138 AltosDebug.debug("scale finished\n");
144 if (event.getAction() == MotionEvent.ACTION_DOWN) {
145 AltosDebug.debug("down event %g %g\n", event.getX(), event.getY());
146 map.touch_start((int) event.getX(), (int) event.getY(), true);
147 } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
148 AltosDebug.debug("continue event %g %g\n", event.getX(), event.getY());
149 map.touch_continue((int) event.getX(), (int) event.getY(), true);
154 public MapView(Context context) {
156 scale_detector = new ScaleGestureDetector(this.getContext(), this);
160 class MapFragment extends Fragment {
163 public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) {
164 map_view = new MapView(container.getContext());
168 public MapFragment() {
172 MapFragment map_fragment;
174 /* AltosMapInterface */
176 static final int WHITE = 0xffffffff;
177 static final int RED = 0xffff0000;
178 static final int PINK = 0xffff8080;
179 static final int YELLOW= 0xffffff00;
180 static final int CYAN = 0xff00ffff;
181 static final int BLUE = 0xff0000ff;
182 static final int BLACK = 0xff000000;
184 public static final int stateColors[] = {
198 class MapPath extends AltosMapPath {
200 boolean line_in(AltosPointDouble a, AltosPointDouble b) {
201 final Rect bounds = canvas.getClipBounds();
202 int left = (int) Math.floor (Math.min((float) a.x, (float) b.x) - stroke_width / 2.0f);
203 int right = (int) Math.ceil(Math.max((float) a.x, (float) b.x) + stroke_width / 2.0f);
204 int top = (int) Math.floor(Math.min((float) a.y, (float) b.y) - stroke_width / 2.0f);
205 int bottom = (int) Math.ceil(Math.max((float) a.y, (float) b.y) + stroke_width / 2.0f);
207 return left < bounds.right && bounds.left < right &&
208 top < bounds.bottom && bounds.top < bottom;
211 public void paint(AltosMapTransform t) {
212 AltosPointDouble prev = null;
213 int cur_color = paint.getColor();
215 for (AltosMapPathPoint point : points) {
216 AltosPointDouble cur = t.screen(point.lat_lon);
218 if (prev != null && line_in(prev, cur)) {
220 if (0 <= point.state && point.state < stateColors.length)
221 color = stateColors[point.state];
223 color = stateColors[AltosLib.ao_flight_invalid];
224 if (color != cur_color) {
225 paint.setColor(color);
228 canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
235 stroke_width = TabMapOffline.this.stroke_width;
239 public AltosMapPath new_path() {
243 class MapLine extends AltosMapLine {
244 public void paint(AltosMapTransform t) {
251 public AltosMapLine new_line() {
255 class MapImage implements AltosImage {
256 public Bitmap bitmap;
258 public void flush() {
259 if (bitmap != null) {
265 public MapImage(File file) {
266 bitmap = BitmapFactory.decodeFile(file.getPath());
270 public AltosImage load_image(File file) throws Exception {
271 return new MapImage(file);
274 class MapMark extends AltosMapMark {
275 public void paint(AltosMapTransform t) {
278 MapMark(double lat, double lon, int state) {
279 super(lat, lon, state);
283 public AltosMapMark new_mark(double lat, double lon, int state) {
284 return new MapMark(lat, lon, state);
287 class MapTile extends AltosMapTile {
288 public void paint(AltosMapTransform t) {
289 AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
291 if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
294 AltosImage altos_image = cache.get(this, store, px_size, px_size);
296 MapImage map_image = (MapImage) altos_image;
298 Bitmap bitmap = null;
300 if (map_image != null)
301 bitmap = map_image.bitmap;
303 if (bitmap != null) {
304 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
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;
311 case AltosMapTile.loading:
312 message = "Loading...";
314 case AltosMapTile.bad_request:
315 message = "Internal error";
317 case AltosMapTile.failed:
318 message = "Network error, check connection";
320 case AltosMapTile.forbidden:
321 message = "Too many requests, try later";
324 if (message != null) {
325 Rect bounds = new Rect();
326 paint.getTextBounds(message, 0, message.length(), bounds);
328 int width = bounds.right - bounds.left;
329 int height = bounds.bottom - bounds.top;
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);
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);
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);
354 if (map_fragment != null && map_fragment.map_view != null)
355 return map_fragment.map_view.getWidth();
359 public int height() {
360 if (map_fragment != null && map_fragment.map_view != null)
361 return map_fragment.map_view.getHeight();
365 public void repaint() {
366 this.getActivity().runOnUiThread(new Runnable() {
368 if (map_fragment != null && map_fragment.map_view != null)
369 map_fragment.map_view.invalidate();
374 public void repaint(AltosRectangle t_damage) {
375 final AltosRectangle damage = t_damage;
376 this.getActivity().runOnUiThread(new Runnable() {
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);
384 public void set_zoom_label(String label) {
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();
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();
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;
409 public void onCreate(Bundle savedInstanceState) {
410 super.onCreate(savedInstanceState);
414 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
415 View v = inflater.inflate(R.layout.tab_map, container, false);
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);
429 public void onActivityCreated(Bundle savedInstanceState) {
430 super.onActivityCreated(savedInstanceState);
431 getChildFragmentManager().beginTransaction().add(R.id.map, map_fragment).commit();
435 public void onDestroyView() {
436 super.onDestroyView();
438 mAltosDroid.unregisterTab(this);
442 // Fragment fragment = (getFragmentManager().findFragmentById(R.id.map));
443 // FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
444 // ft.remove(fragment);
448 private void setupMap() {
451 private void center(double lat, double lon, double accuracy) {
452 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
454 map.maybe_centre(lat, lon);
455 mapAccuracy = accuracy;
459 public String tab_name() { return "offmap"; }
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);
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);
475 if (state.pad_lat != AltosLib.MISSING && pad == null)
476 pad = new AltosLatLon(state.pad_lat, state.pad_lon);
479 if (receiver != null) {
482 here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
483 if (receiver.hasAccuracy())
484 accuracy = receiver.getAccuracy();
487 mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S"));
488 mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
489 center (here.lat, here.lon, accuracy);
494 public void set_map_type(int map_type) {
496 map.set_maptype(map_type);
499 public TabMapOffline() {