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;
47 int pad_off_x, pad_off_y;
49 int rocket_off_x, rocket_off_y;
51 int here_off_x, here_off_y;
53 private boolean pad_set;
55 private TextView mDistanceView;
56 private TextView mBearingView;
57 private TextView mTargetLatitudeView;
58 private TextView mTargetLongitudeView;
59 private TextView mReceiverLatitudeView;
60 private TextView mReceiverLongitudeView;
62 private double mapAccuracy = -1;
64 int stroke_width = 20;
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));
70 canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
74 class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
76 ScaleGestureDetector scale_detector;
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);
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);
91 protected void onDraw(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);
102 public boolean onScale(ScaleGestureDetector detector) {
103 float f = detector.getScaleFactor();
104 AltosDebug.debug("onScale %f\n", f);
106 map.set_zoom(map.get_zoom() - 1);
110 map.set_zoom(map.get_zoom() + 1);
116 public boolean onScaleBegin(ScaleGestureDetector detector) {
117 AltosDebug.debug("onScaleBegin %f\n", detector.getScaleFactor());
121 public void onScaleEnd(ScaleGestureDetector detector) {
122 AltosDebug.debug("onScaleEnd %f\n", detector.getScaleFactor());
126 public boolean dispatchTouchEvent(MotionEvent event) {
127 scale_detector.onTouchEvent(event);
129 if (scale_detector.isInProgress()) {
134 if(AltosDebug.D) AltosDebug.debug("scale in progress\n");
135 if (event.getAction() == MotionEvent.ACTION_UP) {
136 AltosDebug.debug("scale finished\n");
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);
152 public MapView(Context context) {
154 scale_detector = new ScaleGestureDetector(this.getContext(), this);
158 class MapFragment extends Fragment {
161 public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) {
162 map_view = new MapView(container.getContext());
166 public MapFragment() {
170 MapFragment map_fragment;
172 /* AltosMapInterface */
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;
182 public static final int stateColors[] = {
196 class MapPath extends AltosMapPath {
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);
205 return left < bounds.right && bounds.left < right &&
206 top < bounds.bottom && bounds.top < bottom;
209 public void paint(AltosMapTransform t) {
210 AltosPointDouble prev = null;
211 int cur_color = paint.getColor();
213 for (AltosMapPathPoint point : points) {
214 AltosPointDouble cur = t.screen(point.lat_lon);
216 if (prev != null && line_in(prev, cur)) {
218 if (0 <= point.state && point.state < stateColors.length)
219 color = stateColors[point.state];
221 color = stateColors[AltosLib.ao_flight_invalid];
222 if (color != cur_color) {
223 paint.setColor(color);
226 canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
233 stroke_width = TabMapOffline.this.stroke_width;
237 public AltosMapPath new_path() {
241 class MapLine extends AltosMapLine {
242 public void paint(AltosMapTransform t) {
249 public AltosMapLine new_line() {
253 class MapImage implements AltosImage {
254 public Bitmap bitmap;
256 public void flush() {
257 if (bitmap != null) {
263 public MapImage(File file) {
264 bitmap = BitmapFactory.decodeFile(file.getPath());
268 public AltosImage load_image(File file) throws Exception {
269 return new MapImage(file);
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);
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);
477 if (receiver != null) {
480 here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
481 if (receiver.hasAccuracy())
482 accuracy = receiver.getAccuracy();
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);
492 public TabMapOffline() {