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.*;
34 import android.util.Log;
36 public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
38 static final String TAG = "AltosDroid";
39 static final boolean D = true;
41 AltosDroid mAltosDroid;
48 private boolean pad_set;
50 private TextView mDistanceView;
51 private TextView mBearingView;
52 private TextView mTargetLatitudeView;
53 private TextView mTargetLongitudeView;
54 private TextView mReceiverLatitudeView;
55 private TextView mReceiverLongitudeView;
57 private double mapAccuracy = -1;
59 int stroke_width = 20;
61 class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
63 ScaleGestureDetector scale_detector;
66 protected void onDraw(Canvas view_canvas) {
68 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
69 paint.setStrokeWidth(stroke_width);
70 paint.setStrokeCap(Paint.Cap.ROUND);
71 paint.setStrokeJoin(Paint.Join.ROUND);
76 public boolean onScale(ScaleGestureDetector detector) {
77 float f = detector.getScaleFactor();
78 if (D) Log.d(TAG, String.format("onScale %f\n", f));
80 map.set_zoom(map.get_zoom() - 1);
84 map.set_zoom(map.get_zoom() + 1);
90 public boolean onScaleBegin(ScaleGestureDetector detector) {
91 if (D) Log.d(TAG, String.format("onScaleBegin %f\n", detector.getScaleFactor()));
95 public void onScaleEnd(ScaleGestureDetector detector) {
96 if (D) Log.d(TAG, String.format("onScaleEnd %f\n", detector.getScaleFactor()));
100 public boolean dispatchTouchEvent(MotionEvent event) {
101 scale_detector.onTouchEvent(event);
103 if (scale_detector.isInProgress()) {
108 if(D) Log.d(TAG, "scale in progress\n");
109 if (event.getAction() == MotionEvent.ACTION_UP) {
110 if (D) Log.d(TAG, "scale finished\n");
116 if (event.getAction() == MotionEvent.ACTION_DOWN) {
117 if(D) Log.d(TAG, String.format("down event %g %g\n", event.getX(), event.getY()));
118 map.touch_start((int) event.getX(), (int) event.getY(), true);
119 } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
120 if(D) Log.d(TAG, String.format("continue event %g %g\n", event.getX(), event.getY()));
121 map.touch_continue((int) event.getX(), (int) event.getY(), true);
126 public MapView(Context context) {
128 scale_detector = new ScaleGestureDetector(this.getContext(), this);
132 class MapFragment extends Fragment {
135 public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) {
136 map_view = new MapView(container.getContext());
140 public MapFragment() {
144 MapFragment map_fragment;
146 /* AltosMapInterface */
148 static final int WHITE = 0xffffffff;
149 static final int RED = 0xffff0000;
150 static final int PINK = 0xffff8080;
151 static final int YELLOW= 0xffffff00;
152 static final int CYAN = 0xff00ffff;
153 static final int BLUE = 0xff0000ff;
154 static final int BLACK = 0xff000000;
156 public static final int stateColors[] = {
170 class MapPath extends AltosMapPath {
172 boolean line_in(AltosPointDouble a, AltosPointDouble b) {
173 final Rect bounds = canvas.getClipBounds();
174 int left = (int) Math.floor (Math.min((float) a.x, (float) b.x) - stroke_width / 2.0f);
175 int right = (int) Math.ceil(Math.max((float) a.x, (float) b.x) + stroke_width / 2.0f);
176 int top = (int) Math.floor(Math.min((float) a.y, (float) b.y) - stroke_width / 2.0f);
177 int bottom = (int) Math.ceil(Math.max((float) a.y, (float) b.y) + stroke_width / 2.0f);
179 return left < bounds.right && bounds.left < right &&
180 top < bounds.bottom && bounds.top < bottom;
183 public void paint(AltosMapTransform t) {
184 AltosPointDouble prev = null;
185 int cur_color = paint.getColor();
187 for (AltosMapPathPoint point : points) {
188 AltosPointDouble cur = t.screen(point.lat_lon);
190 if (prev != null && line_in(prev, cur)) {
192 if (0 <= point.state && point.state < stateColors.length)
193 color = stateColors[point.state];
195 color = stateColors[AltosLib.ao_flight_invalid];
196 if (color != cur_color) {
197 paint.setColor(color);
200 canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
207 stroke_width = TabMapOffline.this.stroke_width;
211 public AltosMapPath new_path() {
212 return new MapPath();
215 class MapLine extends AltosMapLine {
216 public void paint(AltosMapTransform t) {
223 public AltosMapLine new_line() {
224 return new MapLine();
227 class MapImage implements AltosImage {
228 public Bitmap bitmap;
230 public void flush() {
231 if (bitmap != null) {
237 public MapImage(File file) {
238 bitmap = BitmapFactory.decodeFile(file.getPath());
242 public AltosImage load_image(File file) throws Exception {
243 return new MapImage(file);
246 class MapMark extends AltosMapMark {
247 public void paint(AltosMapTransform t) {
250 MapMark(double lat, double lon, int state) {
251 super(lat, lon, state);
255 public AltosMapMark new_mark(double lat, double lon, int state) {
256 return new MapMark(lat, lon, state);
259 class MapTile extends AltosMapTile {
260 public void paint(AltosMapTransform t) {
261 AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
263 if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
266 AltosImage altos_image = cache.get(this, store, px_size, px_size);
268 MapImage map_image = (MapImage) altos_image;
270 Bitmap bitmap = null;
272 if (map_image != null)
273 bitmap = map_image.bitmap;
275 if (bitmap != null) {
276 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
278 paint.setColor(0xff808080);
279 canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
280 if (t.has_location()) {
281 String message = null;
283 case AltosMapTile.loading:
284 message = "Loading...";
286 case AltosMapTile.bad_request:
287 message = "Internal error";
289 case AltosMapTile.failed:
290 message = "Network error, check connection";
292 case AltosMapTile.forbidden:
293 message = "Too many requests, try later";
296 if (message != null) {
297 Rect bounds = new Rect();
298 paint.getTextBounds(message, 0, message.length(), bounds);
300 int width = bounds.right - bounds.left;
301 int height = bounds.bottom - bounds.top;
303 float x = pt.x + px_size / 2.0f;
304 float y = pt.y + px_size / 2.0f;
305 x = x - width / 2.0f;
306 y = y + height / 2.0f;
307 paint.setColor(0xff000000);
308 canvas.drawText(message, 0, message.length(), x, y, paint);
315 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
316 super(listener, upper_left, center, zoom, maptype, px_size, 2);
321 public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
322 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
326 if (map_fragment != null && map_fragment.map_view != null)
327 return map_fragment.map_view.getWidth();
331 public int height() {
332 if (map_fragment != null && map_fragment.map_view != null)
333 return map_fragment.map_view.getHeight();
337 public void repaint() {
338 this.getActivity().runOnUiThread(new Runnable() {
340 if (map_fragment != null && map_fragment.map_view != null)
341 map_fragment.map_view.invalidate();
346 public void repaint(AltosRectangle t_damage) {
347 final AltosRectangle damage = t_damage;
348 this.getActivity().runOnUiThread(new Runnable() {
350 if (map_fragment != null && map_fragment.map_view != null)
351 map_fragment.map_view.invalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
356 public void set_zoom_label(String label) {
360 public void onAttach(Activity activity) {
361 super.onAttach(activity);
362 mAltosDroid = (AltosDroid) activity;
363 mAltosDroid.registerTab(this);
367 public void onCreate(Bundle savedInstanceState) {
368 super.onCreate(savedInstanceState);
372 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
373 View v = inflater.inflate(R.layout.tab_map, container, false);
375 map_fragment = new MapFragment();
376 map = new AltosMap(this);
377 mDistanceView = (TextView)v.findViewById(R.id.distance_value);
378 mBearingView = (TextView)v.findViewById(R.id.bearing_value);
379 mTargetLatitudeView = (TextView)v.findViewById(R.id.target_lat_value);
380 mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value);
381 mReceiverLatitudeView = (TextView)v.findViewById(R.id.receiver_lat_value);
382 mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value);
387 public void onActivityCreated(Bundle savedInstanceState) {
388 super.onActivityCreated(savedInstanceState);
389 getChildFragmentManager().beginTransaction().add(R.id.map, map_fragment).commit();
393 public void onDestroyView() {
394 super.onDestroyView();
396 mAltosDroid.unregisterTab(this);
400 // Fragment fragment = (getFragmentManager().findFragmentById(R.id.map));
401 // FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
402 // ft.remove(fragment);
406 private void setupMap() {
408 mMap = mMapFragment.getMap();
410 mMap.setMyLocationEnabled(true);
411 mMap.getUiSettings().setTiltGesturesEnabled(false);
412 mMap.getUiSettings().setZoomControlsEnabled(false);
414 mRocketMarker = mMap.addMarker(
415 // From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/
416 new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.rocket))
417 .position(new LatLng(0,0))
421 mPadMarker = mMap.addMarker(
422 new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
423 .position(new LatLng(0,0))
427 mPolyline = mMap.addPolyline(
428 new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
439 private void center(double lat, double lon, double accuracy) {
440 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
442 map.centre(lat, lon);
443 mapAccuracy = accuracy;
447 public String tab_name() { return "offmap"; }
449 public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
450 if (from_receiver != null) {
451 mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
452 set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
456 map.show(state, null);
457 if (state.gps != null) {
458 mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
459 mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
460 if (state.gps.locked && state.gps.nsat >= 4)
461 center (state.gps.lat, state.gps.lon, 10);
465 if (receiver != null) {
468 if (receiver.hasAccuracy())
469 accuracy = receiver.getAccuracy();
472 mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
473 mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
474 center (receiver.getLatitude(), receiver.getLongitude(), accuracy);
479 public TabMapOffline() {