论坛首页 移动开发技术论坛

自己做一个GPS卫星分布状态显示程序

浏览 25049 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-03-14   最后修改:2012-03-14
非常喜欢GPSTest这款Android上查看GPS状态及GPS卫星分布的软件,心想自己也仿照做一个,仔细研究了Android提供的接口,是完全可有可能的,目前只实现了固定罗盘上展示GPS卫星的分布,考虑到将来动态罗盘(自动指北,技术有限,没实现)。希望大家能帮我改进,以下是GPSTest的一个界面图


这是我的程序界面

首先建Activity,来监听GPS状态变化
public class GpsViewActivity extends Activity {
	private int minTime = 1000;
	private int minDistance = 0;
	private static final String TAG = "GpsView";

	private LocationManager locationManager;
	private SatellitesView satellitesView;
	private TextView lonlatText;
	private TextView gpsStatusText;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.gps_view_activity);

		gpsStatusText = (TextView) findViewById(R.id.gps_status_text);
		lonlatText = (TextView) findViewById(R.id.lonlat_text);
		satellitesView = (SatellitesView) findViewById(R.id.satellitesView);
		
		registerListener();

	}
    /**
     * 注册监听
     */
	private void registerListener() {
		if (locationManager == null) {
			locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		}
		//侦听位置信息(经纬度变化)
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
				minTime, minDistance, locationListener);
		// 侦听GPS状态,主要是捕获到的各个卫星的状态
		locationManager.addGpsStatusListener(gpsStatusListener);
		//TODO:考虑增加监听传感器中的方位数据,以使罗盘的北能自动指向真实的北向
	}
    /**
     * 移除监听
     */
	private void unregisterListener() {
		if (locationManager != null) {
			locationManager.removeGpsStatusListener(gpsStatusListener);
			locationManager.removeUpdates(locationListener);
		}
	}
    /**
     * 坐标位置监听
     */
	private LocationListener locationListener = new LocationListener() {

		@Override
		public void onLocationChanged(Location location) {
			StringBuffer sb = new StringBuffer();
			int fmt = Location.FORMAT_DEGREES;
			sb.append(Location.convert(location.getLongitude(), fmt));
			sb.append(" ");
			sb.append(Location.convert(location.getLatitude(), fmt));
			lonlatText.setText(sb.toString());

		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			gpsStatusText.setText("onStatusChanged");

		}

		@Override
		public void onProviderEnabled(String provider) {
			gpsStatusText.setText("onProviderEnabled");

		}

		@Override
		public void onProviderDisabled(String provider) {
			gpsStatusText.setText("onProviderDisabled");

		}

	};
	
    /**
     * Gps状态监听
     */
	private GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
		public void onGpsStatusChanged(int event) {
			GpsStatus gpsStatus = locationManager.getGpsStatus(null);
			switch (event) {
			case GpsStatus.GPS_EVENT_FIRST_FIX: {
				gpsStatusText.setText("GPS_EVENT_FIRST_FIX");
				// 第一次定位时间UTC gps可用
				// Log.v(TAG,"GPS is usable");
				int i = gpsStatus.getTimeToFirstFix();
				break;
			}

			case GpsStatus.GPS_EVENT_SATELLITE_STATUS: {// 周期的报告卫星状态
				// 得到所有收到的卫星的信息,包括 卫星的高度角、方位角、信噪比、和伪随机号(及卫星编号)
				Iterable<GpsSatellite> satellites = gpsStatus.getSatellites();

				List<GpsSatellite> satelliteList = new ArrayList<GpsSatellite>();

				for (GpsSatellite satellite : satellites) {
					// 包括 卫星的高度角、方位角、信噪比、和伪随机号(及卫星编号)
					/*
					 * satellite.getElevation(); //卫星仰角
					 * satellite.getAzimuth();   //卫星方位角 
					 * satellite.getSnr();       //信噪比
					 * satellite.getPrn();       //伪随机数,可以认为他就是卫星的编号
					 * satellite.hasAlmanac();   //卫星历书 
					 * satellite.hasEphemeris();
					 * satellite.usedInFix();
					 */
					satelliteList.add(satellite);
				}

				satellitesView.repaintSatellites(satelliteList);
				gpsStatusText.setText("GPS_EVENT_SATELLITE_STATUS:"
						+ satelliteList.size());
				break;
			}

			case GpsStatus.GPS_EVENT_STARTED: {
				gpsStatusText.setText("GPS_EVENT_STARTED");
				break;
			}

			case GpsStatus.GPS_EVENT_STOPPED: {
				gpsStatusText.setText("GPS_EVENT_STOPPED");
				break;
			}

			default:
				gpsStatusText.setText("GPS_EVENT:" + event);
				break;
			}
		}
	};

	@Override
	protected void onResume() {
		super.onResume();
		registerListener();
	}

	@Override
	protected void onDestroy() {
		unregisterListener();
		super.onDestroy();
	}

}


然后,是创建一个类来用于绘制GPS卫星的分布,这里我是用继承的SurfaceView的类来实现的,我这里模仿了SDK中提供的范例中的LunarLander游戏的绘制方法,把绘制工作交给一个线程完成,如下,各个绘制函数注释已经写得很清楚了,我不再重复了:
package ylybbs.study.mygpstest;

import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Paint.Align;
import android.location.GpsSatellite;
import android.view.SurfaceHolder;

public class DrawSatellitesThread extends Thread {
	// 卫星图
	private Bitmap satelliteBitmap;
	private Bitmap compassBitmap;

	private Paint paint;

	/** Handle to the surface manager object we interact with */
	private SurfaceHolder surfaceHolder;

	/** Indicate whether the surface has been created & is ready to draw */
	private boolean isRunning = false;
	private int cx=0;
	private int cy=0;
	private int compassRadius = 434 / 2;
	
	PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter(0,
			Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
	
	public static final LinkedBlockingQueue<List<GpsSatellite>> queue = 
			new LinkedBlockingQueue<List<GpsSatellite>>(60);

	public DrawSatellitesThread(SurfaceHolder surfaceHolder, Context context) {
		this.surfaceHolder = surfaceHolder;
		Resources res = context.getResources();
		// cache handles to our key sprites & other drawables
		compassBitmap = BitmapFactory.decodeResource(res, R.drawable.compass);
		compassRadius = compassBitmap.getWidth() / 2;

		satelliteBitmap = BitmapFactory.decodeResource(res,
				R.drawable.satellite_mark);

		paint = new Paint();
		paint.setSubpixelText(true);
		paint.setAntiAlias(true);
		paint.setFilterBitmap(true);
		paint.setColor(Color.RED);
		paint.setTextSize(24);
		paint.setTextAlign(Align.CENTER);
	}
	
    /* Callback invoked when the surface dimensions change. */
    public void setSurfaceSize(int width, int height) {
        synchronized (surfaceHolder) {
			cx = width / 2;
			cy = height  / 2;
        }
    }
	
	@Override
	public void run() {		
		List<GpsSatellite> list=null;
		Canvas c = null;
		
		try {
			c = surfaceHolder.lockCanvas(null);
			//初始化画板的中心坐标
			cx = c.getWidth() / 2;
			cy = c.getWidth()  / 2;
			synchronized (surfaceHolder) {
				doDraw(c,null);
			}
		} finally {
			if (c != null) {
				surfaceHolder.unlockCanvasAndPost(c);
			}
		}
		while (isRunning) {
			try{
				list = queue.take();				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			try {
				c = surfaceHolder.lockCanvas(null);
				synchronized (surfaceHolder) {
					doDraw(c,list);
				}
			} finally {
				if (c != null) {
					surfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}
	}


	public void setRunning(boolean b) {
		isRunning = b;
	}

	public void repaintSatellites(List<GpsSatellite> list) {
		synchronized (surfaceHolder) {
			try {
				queue.offer(list);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

    /**
     * 绘制背景罗盘
     * @param canvas
     * @param cx  罗盘中心点位于画布上的X坐标
     * @param cy  罗盘中心点位于画布上的Y坐标
     * @param r   罗盘的半径
     */
	private void drawBackground(Canvas canvas, int cx, int cy, int r) {
		int x = cx - r;
		int y = cy - r;
		canvas.drawBitmap(compassBitmap, x, y, paint);
	}

	/**
	 * 将角度转换为弧度,以用于三角函数的运算
	 * 
	 * @param degree
	 * @return
	 */
	private double degreeToRadian(double degree) {
		return (degree * Math.PI) / 180.0d;
	}

	/*
	 * 将SNR的值,转化为通用的信号强度级别,主要用于在绘制卫星时,通过颜色来表明它的信号强度,暂时没用到
	 * SNR is mapped to signal strength [0,1,4-9] COMMENT SNR: >500 >100 >50 >10
	 * >5 >0 bad n/a COMMENT sig: 9 8 7 6 5 4 1 0 COMMENT
	 */
	private int snrToSignalLevel(float snr) {
		int level = 0;
		if (snr >= 0 && snr < 5) {
			level = 4;
		} else if (snr >= 5 && snr < 10) {
			level = 5;
		} else if (snr >= 10 && snr < 50) {
			level = 6;
		} else if (snr >= 50 && snr < 100) {
			level = 7;
		} else if (snr >= 100 && snr < 500) {
			level = 8;
		} else if (snr >= 500) {
			level = 9;
		}
		return level;
	}
	
    /**
     * 在背景罗盘上绘制卫星
     * @param canvas
     * @param satellite
     * @param cx  中心圆点的X座标
     * @param cy  中心圆点的Y座标
     * @param r   罗盘背景的半径
     */
	private void drawSatellite(Canvas canvas,GpsSatellite satellite, int cx, int cy, int r) {

		/**
		 * GPS卫星导航仪通常选用仰角大于5º,小于85º。 因为当卫星仰角大于85º时,L1波段的电离层折射误差较大,故规定仰角大于85º时,
		 * 定位无效,不进行数据更新。而卫星仰角越小,则对流层折射误差越大,故一般选用仰角大于5º的卫星来定位。
		 */
		//得到仰角
		float elevation = satellite.getElevation();
        //通过仰角,计算出这个卫星应该绘制到离圆心多远的位置,这里用的是角度的比值
		double r2 = r * ((90.0f - elevation) / 90.0f);
        
		/*得到方位角(与正北向也就是Y轴顺时针方向的夹角,注意我们通常几何上的角度
         * 是与X轴正向的逆时针方向的夹角),在计算X,Y座标的三角函数时,要做转换
         */
		double azimuth = satellite.getAzimuth();
        
		/*
         * 转换成XY座标系中的夹角,方位角是与正北向也就是Y轴顺时针方向的夹角,
         * 注意我们通常几何上的角度是与X轴正向的逆时针方向的夹角),
         * 在计算X,Y座标的三角函数时,要做转换
         */
		double radian = degreeToRadian(360-azimuth + 90);
           
		double x = cx + Math.cos(radian) * r2;
		double y = cy + Math.sin(radian) * r2;
		
		//得到卫星图标的半径
		int sr = satelliteBitmap.getWidth() / 2;
        //以x,y为中心绘制卫星图标
		canvas.drawBitmap(satelliteBitmap, (float) (x - sr), (float) (y - sr),paint);
		//在卫星图标的位置上绘出文字(卫星编号及信号强度)
		int snr=(int)satellite.getSnr();
        int signLevel=snrToSignalLevel(snr);  //暂时不用
		String info = String.format("#%s_%s", satellite.getPrn(), snr);
		canvas.drawText(info, (float) (x), (float) (y), paint);

	}


	private void doDraw(Canvas canvas, List<GpsSatellite> satellites) {
		if (canvas != null) {
			// 绘制背景罗盘
			drawBackground(canvas, cx, cy, compassRadius);
			//绘制卫星分布
			if (satellites != null) {
				for (GpsSatellite satellite : satellites) {
					drawSatellite(canvas,satellite, cx, cy, compassRadius);
				}
			}
		}

	}

}


此代码编译发布到设备后,搜到星后,能运行十几秒钟正常后,就会挂掉,原因暂时没分析出来,看绘出的界面应该正确,可能是绘图过程中状态的过程可能会有死锁之类的,关键是解决不好状态更新与绘制过程之间的同步问题,有可能没绘完上一个画面,这时GPS状态又有了新的更新,如果此是绘图那边加了锁,我不知道这边的listener调用会出现什么情况。其实GPS的状态更新,其中的卫星状态中的卫星数目或是信号强弱的更新这种状态变化是可以漏掉中间的某些同类事件,因为用户只关注卫星最新的分布结果。有可手能帮我分析一下什么问题吗?或是有更好的解决方案?

附上我的项目源码:

  • 大小: 102.6 KB
  • 大小: 137.6 KB
   发表时间:2012-03-14  
呼呼  还没有研究过这方面的东西 不过看起来挺好玩的 支持个哦
0 请登录后投票
   发表时间:2012-03-15  
4.0.3 未响应。。
0 请登录后投票
   发表时间:2012-03-15  
这个界面,很熟悉啊
0 请登录后投票
   发表时间:2012-03-15  
确实很熟悉,GPSTest的UI,嘿嘿!
0 请登录后投票
   发表时间:2012-03-16  
是否和信号强度有关呢?
0 请登录后投票
   发表时间:2012-06-07   最后修改:2012-06-07
你的surfaceDestroyed里面的thread.join();有问题 一直阻塞在这, 导致没法退出surfaceDestroyed  换成thread.interrupt();就可以了   
0 请登录后投票
   发表时间:2012-06-07  
另外,关于compass的旋转,可以这么来做 ,下面的mValues是从方向传感器的来的x,y,z,三房向中的x方向值;
Matrix m = new Matrix();
            // 设置旋转角度
            m.postRotate(-mValues,
            compassBitmap.getWidth() / 2,
            compassBitmap.getHeight() / 2);
            // 设置左边距和上边距
            m.postTranslate(y/2-compassRadius,x/2-compassRadius);

// 绘制背景罗盘

            drawBackground(canvas, m);
谢谢你的这篇博客  ,共同进步吧 
0 请登录后投票
   发表时间:2012-06-07  
上面的x,y是
int x = canvas.getHeight();
int y = canvas.getWidth();
而drawBackground(canvas, m); 里面就一句
canvas.drawBitmap(compassBitmap, m, paint);
0 请登录后投票
   发表时间:2012-06-08   最后修改:2012-06-08
上面只是compass的旋转,要卫星分布一同旋转的话;可以这么干  
在你的drawSatellite()里面,将计算radian改为
double radian = degreeToRadian(360 + (azimuth - 90) - mValues);
mValues就是上面提到的  ,而改为azimuth - 90,才是将y轴夹角传化为x轴夹角;我是这么理解的  。
0 请登录后投票
论坛首页 移动开发技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics