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;
44 private boolean pad_set;
46 private TextView mDistanceView;
47 private TextView mBearingView;
48 private TextView mTargetLatitudeView;
49 private TextView mTargetLongitudeView;
50 private TextView mReceiverLatitudeView;
51 private TextView mReceiverLongitudeView;
53 private double mapAccuracy = -1;
55 int stroke_width = 20;
57 class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
59 ScaleGestureDetector scale_detector;
62 protected void onDraw(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);
72 public boolean onScale(ScaleGestureDetector detector) {
73 float f = detector.getScaleFactor();
74 AltosDebug.debug("onScale %f\n", f);
76 map.set_zoom(map.get_zoom() - 1);
80 map.set_zoom(map.get_zoom() + 1);
86 public boolean onScaleBegin(ScaleGestureDetector detector) {
87 AltosDebug.debug("onScaleBegin %f\n", detector.getScaleFactor());
91 public void onScaleEnd(ScaleGestureDetector detector) {
92 AltosDebug.debug("onScaleEnd %f\n", detector.getScaleFactor());
96 public boolean dispatchTouchEvent(MotionEvent event) {
97 scale_detector.onTouchEvent(event);
99 if (scale_detector.isInProgress()) {
104 if(AltosDebug.D) AltosDebug.debug("scale in progress\n");
105 if (event.getAction() == MotionEvent.ACTION_UP) {
106 AltosDebug.debug("scale finished\n");
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);
122 public MapView(Context context) {
124 scale_detector = new ScaleGestureDetector(this.getContext(), this);
128 class MapFragment extends Fragment {
131 public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) {
132 map_view = new MapView(container.getContext());
136 public MapFragment() {
140 MapFragment map_fragment;
142 /* AltosMapInterface */
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;
152 public static final int stateColors[] = {
166 class MapPath extends AltosMapPath {
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);
175 return left < bounds.right && bounds.left < right &&
176 top < bounds.bottom && bounds.top < bottom;
179 public void paint(AltosMapTransform t) {
180 AltosPointDouble prev = null;
181 int cur_color = paint.getColor();
183 for (AltosMapPathPoint point : points) {
184 AltosPointDouble cur = t.screen(point.lat_lon);
186 if (prev != null && line_in(prev, cur)) {
188 if (0 <= point.state && point.state < stateColors.length)
189 color = stateColors[point.state];
191 color = stateColors[AltosLib.ao_flight_invalid];
192 if (color != cur_color) {
193 paint.setColor(color);
196 canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint);
203 stroke_width = TabMapOffline.this.stroke_width;
207 public AltosMapPath new_path() {
208 return new MapPath();
211 class MapLine extends AltosMapLine {
212 public void paint(AltosMapTransform t) {
219 public AltosMapLine new_line() {
220 return new MapLine();
223 class MapImage implements AltosImage {
224 public Bitmap bitmap;
226 public void flush() {
227 if (bitmap != null) {
233 public MapImage(File file) {
234 bitmap = BitmapFactory.decodeFile(file.getPath());
238 public AltosImage load_image(File file) throws Exception {
239 return new MapImage(file);
242 class MapMark extends AltosMapMark {
243 public void paint(AltosMapTransform t) {
246 MapMark(double lat, double lon, int state) {
247 super(lat, lon, state);
251 public AltosMapMark new_mark(double lat, double lon, int state) {
252 return new MapMark(lat, lon, state);
255 class MapTile extends AltosMapTile {
256 public void paint(AltosMapTransform t) {
257 AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
259 if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
262 AltosImage altos_image = cache.get(this, store, px_size, px_size);
264 MapImage map_image = (MapImage) altos_image;
266 Bitmap bitmap = null;
268 if (map_image != null)
269 bitmap = map_image.bitmap;
271 if (bitmap != null) {
272 canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
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;
279 case AltosMapTile.loading:
280 message = "Loading...";
282 case AltosMapTile.bad_request:
283 message = "Internal error";
285 case AltosMapTile.failed:
286 message = "Network error, check connection";
288 case AltosMapTile.forbidden:
289 message = "Too many requests, try later";
292 if (message != null) {
293 Rect bounds = new Rect();
294 paint.getTextBounds(message, 0, message.length(), bounds);
296 int width = bounds.right - bounds.left;
297 int height = bounds.bottom - bounds.top;
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);
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);
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);
322 if (map_fragment != null && map_fragment.map_view != null)
323 return map_fragment.map_view.getWidth();
327 public int height() {
328 if (map_fragment != null && map_fragment.map_view != null)
329 return map_fragment.map_view.getHeight();
333 public void repaint() {
334 this.getActivity().runOnUiThread(new Runnable() {
336 if (map_fragment != null && map_fragment.map_view != null)
337 map_fragment.map_view.invalidate();
342 public void repaint(AltosRectangle t_damage) {
343 final AltosRectangle damage = t_damage;
344 this.getActivity().runOnUiThread(new Runnable() {
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);
352 public void set_zoom_label(String label) {
356 public void onAttach(Activity activity) {
357 super.onAttach(activity);
358 mAltosDroid = (AltosDroid) activity;
359 mAltosDroid.registerTab(this);
363 public void onCreate(Bundle savedInstanceState) {
364 super.onCreate(savedInstanceState);
368 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
369 View v = inflater.inflate(R.layout.tab_map, container, false);
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);
383 public void onActivityCreated(Bundle savedInstanceState) {
384 super.onActivityCreated(savedInstanceState);
385 getChildFragmentManager().beginTransaction().add(R.id.map, map_fragment).commit();
389 public void onDestroyView() {
390 super.onDestroyView();
392 mAltosDroid.unregisterTab(this);
396 // Fragment fragment = (getFragmentManager().findFragmentById(R.id.map));
397 // FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
398 // ft.remove(fragment);
402 private void setupMap() {
404 mMap = mMapFragment.getMap();
406 mMap.setMyLocationEnabled(true);
407 mMap.getUiSettings().setTiltGesturesEnabled(false);
408 mMap.getUiSettings().setZoomControlsEnabled(false);
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))
417 mPadMarker = mMap.addMarker(
418 new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
419 .position(new LatLng(0,0))
423 mPolyline = mMap.addPolyline(
424 new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
435 private void center(double lat, double lon, double accuracy) {
436 if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
438 map.centre(lat, lon);
439 mapAccuracy = accuracy;
443 public String tab_name() { return "offmap"; }
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);
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);
461 if (receiver != null) {
464 if (receiver.hasAccuracy())
465 accuracy = receiver.getAccuracy();
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);
475 public TabMapOffline() {