diff options
| author | Keith Packard <keithp@keithp.com> | 2015-05-26 19:47:04 -0700 | 
|---|---|---|
| committer | Keith Packard <keithp@keithp.com> | 2015-05-26 19:47:04 -0700 | 
| commit | 7975d088a4ac44c0943134fa41d0e3b88f50b98f (patch) | |
| tree | a4213f36420604ee6ff11ff348d53cc28d81ec07 | |
| parent | f822b84d8c25159ff113fef6a419b6e18e87a87a (diff) | |
altosdroid: Add offline map tab
It's not very fancy yet, but it does zoom and pan, and show the path
of the rocket with a line.
Signed-off-by: Keith Packard <keithp@keithp.com>
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java | 1 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java | 3 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/TabMapOffline.java | 481 | ||||
| -rw-r--r-- | altoslib/AltosMap.java | 2 | ||||
| -rw-r--r-- | altoslib/AltosMapTile.java | 25 | ||||
| -rw-r--r-- | altoslib/AltosPointInt.java | 10 | 
6 files changed, 514 insertions, 8 deletions
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index eff24b10..dd87614b 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -444,6 +444,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {  		mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator(create_tab_view("Descent")), TabDescent.class, null);  		mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator(create_tab_view("Landed")), TabLanded.class, null);  		mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator(create_tab_view("Map")), TabMap.class, null); +		mTabsAdapter.addTab(mTabHost.newTabSpec("offmap").setIndicator(create_tab_view("OffMap")), TabMapOffline.class, null);  		// Set up the custom title  		mTitle = (TextView) findViewById(R.id.title_left_text); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java index 223ae75a..e8299b09 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java @@ -34,6 +34,9 @@ public class AltosViewPager extends ViewPager {      @Override      protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { +	if (v.getClass().getName().endsWith("MapView")) +	    return true; +  	if(v.getClass() != null &&  	   v.getClass().getPackage() != null &&  	   v.getClass().getPackage().getName() != null && diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabMapOffline.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabMapOffline.java new file mode 100644 index 00000000..42d80ad5 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabMapOffline.java @@ -0,0 +1,481 @@ +/* + * Copyright © 2013 Mike Beattie <mike@ethernal.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.util.Arrays; +import java.io.*; + +import org.altusmetrum.altoslib_7.*; + +import android.app.Activity; +import android.graphics.*; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.*; +import android.widget.*; +import android.location.Location; +import android.content.*; +import android.util.Log; + +public class TabMapOffline extends AltosDroidTab implements AltosMapInterface { +	// Debugging +	static final String TAG = "AltosDroid"; +	static final boolean D = true; + +	AltosDroid mAltosDroid; + +	AltosMap map; + +	Canvas	canvas; +	Paint	paint; + +	private boolean pad_set; + +	private TextView mDistanceView; +	private TextView mBearingView; +	private TextView mTargetLatitudeView; +	private TextView mTargetLongitudeView; +	private TextView mReceiverLatitudeView; +	private TextView mReceiverLongitudeView; + +	private double mapAccuracy = -1; + +	int	stroke_width = 20; + +	class MapView extends View implements ScaleGestureDetector.OnScaleGestureListener { + +		ScaleGestureDetector	scale_detector; +		boolean			scaling; + +		protected void onDraw(Canvas view_canvas) { +			canvas = view_canvas; +			paint = new Paint(Paint.ANTI_ALIAS_FLAG); +			paint.setStrokeWidth(stroke_width); +			paint.setStrokeCap(Paint.Cap.ROUND); +			paint.setStrokeJoin(Paint.Join.ROUND); +			map.paint(); +			canvas = null; +		} + +		public boolean onScale(ScaleGestureDetector detector) { +			float	f = detector.getScaleFactor(); +			if (D) Log.d(TAG, String.format("onScale %f\n", f)); +			if (f <= 0.8) { +				map.set_zoom(map.get_zoom() - 1); +				return true; +			} +			if (f >= 1.2) { +				map.set_zoom(map.get_zoom() + 1); +				return true; +			} +			return false; +		} + +		public boolean onScaleBegin(ScaleGestureDetector detector) { +			if (D) Log.d(TAG, String.format("onScaleBegin %f\n", detector.getScaleFactor())); +			return true; +		} + +		public void onScaleEnd(ScaleGestureDetector detector) { +			if (D) Log.d(TAG, String.format("onScaleEnd %f\n", detector.getScaleFactor())); +		} + +		@Override +		public boolean dispatchTouchEvent(MotionEvent event) { +			scale_detector.onTouchEvent(event); + +			if (scale_detector.isInProgress()) { +				scaling = true; +			} + +			if (scaling) { +				if(D) Log.d(TAG, "scale in progress\n"); +				if (event.getAction() == MotionEvent.ACTION_UP) { +					if (D) Log.d(TAG, "scale finished\n"); +					scaling = false; +				} +				return true; +			} + +			if (event.getAction() == MotionEvent.ACTION_DOWN) { +				if(D) Log.d(TAG, String.format("down event %g %g\n", event.getX(), event.getY())); +				map.touch_start((int) event.getX(), (int) event.getY(), true); +			} else if (event.getAction() == MotionEvent.ACTION_MOVE) { +				if(D) Log.d(TAG, String.format("continue event %g %g\n", event.getX(), event.getY())); +				map.touch_continue((int) event.getX(), (int) event.getY(), true); +			} +			return true; +		} + +		public MapView(Context context) { +			super(context); +			scale_detector = new ScaleGestureDetector(this.getContext(), this); +		} +	} + +	class MapFragment extends Fragment { +		MapView	map_view; + +		public View onCreateView(LayoutInflater inflator, ViewGroup container, Bundle savedInstanceState) { +			map_view = new MapView(container.getContext()); +			return map_view; +		} + +		public MapFragment() { +		} +	} + +	MapFragment map_fragment; + +	/* AltosMapInterface */ + +	static  final int	WHITE = 0xffffffff; +	static  final int	RED   = 0xffff0000; +	static  final int	PINK  = 0xffff8080; +	static  final int	YELLOW= 0xffffff00; +	static  final int	CYAN  = 0xff00ffff; +	static  final int	BLUE  = 0xff0000ff; +	static  final int	BLACK = 0xff000000; + +	public static final int stateColors[] = { +		WHITE,  // startup +		WHITE,  // idle +		WHITE,  // pad +		RED,    // boost +		PINK,   // fast +		YELLOW, // coast +		CYAN,   // drogue +		BLUE,   // main +		BLACK,  // landed +		BLACK,  // invalid +		CYAN,   // stateless +	}; + +	class MapPath extends AltosMapPath { + +		boolean line_in(AltosPointDouble a, AltosPointDouble b) { +			final Rect bounds = canvas.getClipBounds(); +			int left = (int) Math.floor (Math.min((float) a.x, (float) b.x) - stroke_width / 2.0f); +			int right = (int) Math.ceil(Math.max((float) a.x, (float) b.x) + stroke_width / 2.0f); +			int top = (int) Math.floor(Math.min((float) a.y, (float) b.y) - stroke_width / 2.0f); +			int bottom = (int) Math.ceil(Math.max((float) a.y, (float) b.y) + stroke_width / 2.0f); + +			return left < bounds.right && bounds.left < right && +				top < bounds.bottom && bounds.top < bottom; +		} + +		public void paint(AltosMapTransform t) { +			AltosPointDouble	prev = null; +			int			cur_color = paint.getColor(); + +			for (AltosMapPathPoint point : points) { +				AltosPointDouble	cur = t.screen(point.lat_lon); + +				if (prev != null && line_in(prev, cur)) { +					int color; +					if (0 <= point.state && point.state < stateColors.length) +						color = stateColors[point.state]; +					else +						color = stateColors[AltosLib.ao_flight_invalid]; +					if (color != cur_color) { +						paint.setColor(color); +						cur_color = color; +					} +					canvas.drawLine((float) prev.x, (float) prev.y, (float) cur.x, (float) cur.y, paint); +				} +				prev = cur; +			} +		} + +		public MapPath() { +			stroke_width = TabMapOffline.this.stroke_width; +		} +	} + +	public AltosMapPath new_path() { +		return new MapPath(); +	} + +	class MapLine extends AltosMapLine { +		public void paint(AltosMapTransform t) { +		} + +		public MapLine() { +		} +	} + +	public AltosMapLine new_line() { +		return new MapLine(); +	} + +	class MapImage implements AltosImage { +		public Bitmap	bitmap; + +		public void flush() { +			if (bitmap != null) { +				bitmap.recycle(); +				bitmap = null; +			} +		} + +		public MapImage(File file) { +			bitmap = BitmapFactory.decodeFile(file.getPath()); +		} +	} + +	public AltosImage load_image(File file) throws Exception { +		return new MapImage(file); +	} + +	class MapMark extends AltosMapMark { +		public void paint(AltosMapTransform t) { +		} + +		MapMark(double lat, double lon, int state) { +			super(lat, lon, state); +		} +	} + +	public AltosMapMark new_mark(double lat, double lon, int state) { +		return new MapMark(lat, lon, state); +	} + +	class MapTile extends AltosMapTile { +		public void paint(AltosMapTransform t) { +			AltosPointInt		pt = new AltosPointInt(t.screen(upper_left)); + +			if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA)) +				return; + +			AltosImage		altos_image = cache.get(this, store, px_size, px_size); + +			MapImage 		map_image = (MapImage) altos_image; + +			Bitmap			bitmap = null; + +			if (map_image != null) +				bitmap = map_image.bitmap; + +			if (bitmap != null) { +				canvas.drawBitmap(bitmap, pt.x, pt.y, paint); +			} else { +				paint.setColor(0xff808080); +				canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint); +				if (t.has_location()) { +					String	message = null; +					switch (status) { +					case AltosMapTile.loading: +						message = "Loading..."; +						break; +					case AltosMapTile.bad_request: +						message = "Internal error"; +						break; +					case AltosMapTile.failed: +						message = "Network error, check connection"; +						break; +					case AltosMapTile.forbidden: +						message = "Too many requests, try later"; +						break; +					} +					if (message != null) { +						Rect	bounds = new Rect(); +						paint.getTextBounds(message, 0, message.length(), bounds); + +						int	width = bounds.right - bounds.left; +						int	height = bounds.bottom - bounds.top; + +						float x = pt.x + px_size / 2.0f; +						float y = pt.y + px_size / 2.0f; +						x = x - width / 2.0f; +						y = y + height / 2.0f; +						paint.setColor(0xff000000); +						canvas.drawText(message, 0, message.length(), x, y, paint); +					} +				} +			} + +		} + +		public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +			super(listener, upper_left, center, zoom, maptype, px_size, 2); +		} + +	} + +	public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +		return new MapTile(listener, upper_left, center, zoom, maptype, px_size); +	} + +	public int width() { +		if (map_fragment != null && map_fragment.map_view != null) +			return map_fragment.map_view.getWidth(); +		return 500; +	} + +	public int height() { +		if (map_fragment != null && map_fragment.map_view != null) +			return map_fragment.map_view.getHeight(); +		return 500; +	} + +	public void repaint() { +		this.getActivity().runOnUiThread(new Runnable() { +				public void run() { +					if (map_fragment != null && map_fragment.map_view != null) +						map_fragment.map_view.invalidate(); +				} +			}); +	} + +	public void repaint(AltosRectangle t_damage) { +		final AltosRectangle damage = t_damage; +		this.getActivity().runOnUiThread(new Runnable() { +				public void run() { +					if (map_fragment != null && map_fragment.map_view != null) +						map_fragment.map_view.invalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height); +				} +			}); +	} + +	public void set_zoom_label(String label) { +	} + +	@Override +	public void onAttach(Activity activity) { +		super.onAttach(activity); +		mAltosDroid = (AltosDroid) activity; +		mAltosDroid.registerTab(this); +	} + +	@Override +	public void onCreate(Bundle savedInstanceState) { +		super.onCreate(savedInstanceState); +	} + +	@Override +	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +		View v = inflater.inflate(R.layout.tab_map, container, false); + +		map_fragment = new MapFragment(); +		map = new AltosMap(this); +		mDistanceView  = (TextView)v.findViewById(R.id.distance_value); +		mBearingView   = (TextView)v.findViewById(R.id.bearing_value); +		mTargetLatitudeView  = (TextView)v.findViewById(R.id.target_lat_value); +		mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value); +		mReceiverLatitudeView  = (TextView)v.findViewById(R.id.receiver_lat_value); +		mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value); +		return v; +	} + +	@Override +	public void onActivityCreated(Bundle savedInstanceState) { +		super.onActivityCreated(savedInstanceState); +		getChildFragmentManager().beginTransaction().add(R.id.map, map_fragment).commit(); +	} +  +	@Override +	public void onDestroyView() { +		super.onDestroyView(); + +		mAltosDroid.unregisterTab(this); +		mAltosDroid = null; +		map_fragment = null; + +//		Fragment fragment = (getFragmentManager().findFragmentById(R.id.map)); +//		FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction(); +//		ft.remove(fragment); +//		ft.commit(); +	} + +	private void setupMap() { +/* +		mMap = mMapFragment.getMap(); +		if (mMap != null) { +			mMap.setMyLocationEnabled(true); +			mMap.getUiSettings().setTiltGesturesEnabled(false); +			mMap.getUiSettings().setZoomControlsEnabled(false); + +			mRocketMarker = mMap.addMarker( +					// From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/ +					new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.rocket)) +					                   .position(new LatLng(0,0)) +					                   .visible(false) +					); + +			mPadMarker = mMap.addMarker( +					new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad)) +					                   .position(new LatLng(0,0)) +					                   .visible(false) +					); + +			mPolyline = mMap.addPolyline( +					new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0)) +					                     .width(3) +					                     .color(Color.BLUE) +					                     .visible(false) +					); + +			mapLoaded = true; +		} +*/ +	} + +	private void center(double lat, double lon, double accuracy) { +		if (mapAccuracy < 0 || accuracy < mapAccuracy/10) { +			if (map != null) +				map.centre(lat, lon); +			mapAccuracy = accuracy; +		} +	} + +	public String tab_name() { return "offmap"; } + +	public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { +		if (from_receiver != null) { +			mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); +			set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); +		} + +		if (state != null) { +			map.show(state, null); +			if (state.gps != null) { +				mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); +				mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W")); +				if (state.gps.locked && state.gps.nsat >= 4) +					center (state.gps.lat, state.gps.lon, 10); +			} +		} + +		if (receiver != null) { +			double accuracy; + +			if (receiver.hasAccuracy()) +				accuracy = receiver.getAccuracy(); +			else +				accuracy = 1000; +			mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); +			mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W")); +			center (receiver.getLatitude(), receiver.getLongitude(), accuracy); +		} + +	} + +	public TabMapOffline() { +	} +} diff --git a/altoslib/AltosMap.java b/altoslib/AltosMap.java index b54c66cf..bdb60f0c 100644 --- a/altoslib/AltosMap.java +++ b/altoslib/AltosMap.java @@ -287,7 +287,7 @@ public class AltosMap implements AltosMapTileListener, AltosMapStoreListener {  					AltosLatLon	ul = transform.lat_lon(new AltosPointDouble(x, y));  					AltosLatLon	center = transform.lat_lon(new AltosPointDouble(x + AltosMap.px_size/2, y + AltosMap.px_size/2));  					AltosMapTile tile = map_interface.new_tile(this, ul, center, zoom, maptype, -										   AltosMap.px_size); +										   px_size);  					tiles.put(point, tile);  				}  			} diff --git a/altoslib/AltosMapTile.java b/altoslib/AltosMapTile.java index 165f9e6f..ee9206ee 100644 --- a/altoslib/AltosMapTile.java +++ b/altoslib/AltosMapTile.java @@ -26,6 +26,7 @@ public abstract class AltosMapTile implements AltosFontListener {  	public int		px_size;  	int		zoom;  	int		maptype; +	int		scale;  	public AltosMapStore	store;  	public AltosMapCache	cache;  	public int	status; @@ -51,23 +52,28 @@ public abstract class AltosMapTile implements AltosFontListener {  		else  			format_string = "png";  		return new File(AltosPreferences.mapdir(), -				String.format("map-%c%.6f,%c%.6f-%s%d.%s", -					      chlat, lat, chlon, lon, maptype_string, zoom, format_string)); +				String.format("map-%c%.6f,%c%.6f-%s%d%s.%s", +					      chlat, lat, chlon, lon, maptype_string, zoom, scale == 1 ? "" : String.format("-%d", scale), format_string));  	}  	private String map_url() {  		String format_string; +		int z = zoom; +  		if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)  			format_string = "jpg";  		else  			format_string = "png32"; +		for (int s = 1; s < scale; s <<= 1) +			z--; +  		if (AltosVersion.has_google_maps_api_key()) -			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s&key=%s", -					     center.lat, center.lon, zoom, px_size, px_size, AltosMap.maptype_names[maptype], format_string, AltosVersion.google_maps_api_key); +			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&scale=%d&sensor=false&maptype=%s&format=%s&key=%s", +					     center.lat, center.lon, z, px_size/scale, px_size/scale, scale, AltosMap.maptype_names[maptype], format_string, AltosVersion.google_maps_api_key);  		else -			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s", -					     center.lat, center.lon, zoom, px_size, px_size, AltosMap.maptype_names[maptype], format_string); +			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&scale=%d&sensor=false&maptype=%s&format=%s", +					     center.lat, center.lon, z, px_size/scale, px_size/scale, AltosMap.maptype_names[maptype], format_string);  	}  	public void font_size_changed(int font_size) { @@ -96,7 +102,7 @@ public abstract class AltosMapTile implements AltosFontListener {  	public abstract void paint(AltosMapTransform t); -	public AltosMapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +	public AltosMapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {  		this.listener = listener;  		this.upper_left = upper_left;  		this.cache = listener.cache(); @@ -110,8 +116,13 @@ public abstract class AltosMapTile implements AltosFontListener {  		this.zoom = zoom;  		this.maptype = maptype;  		this.px_size = px_size; +		this.scale = scale;  		status = AltosMapTile.loading;  		store = AltosMapStore.get(map_url(), map_file());  	} + +	public AltosMapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) { +		this(listener, upper_left, center, zoom, maptype, px_size, 1); +	}  } diff --git a/altoslib/AltosPointInt.java b/altoslib/AltosPointInt.java index e133ae9c..5d884391 100644 --- a/altoslib/AltosPointInt.java +++ b/altoslib/AltosPointInt.java @@ -28,4 +28,14 @@ public class AltosPointInt {  		this.x = x;  		this.y = y;  	} + +	public AltosPointInt(double x, double y) { +		this.x = (int) (x + 0.5); +		this.y = (int) (y + 0.5); +	} + +	public AltosPointInt(AltosPointDouble pt_d) { +		this.x = (int) (pt_d.x + 0.5); +		this.y = (int) (pt_d.y + 0.5); +	}  }  | 
