老样子,先看看视频,到底小车是怎么绘路线图的:基于OpenCV和Android的语音物标识别车
如何在Android显示小车的行进路线并且当遇到重要事物时在地图上标记?前面一篇文章已经讲了两台手机的socket双向通信,这里就以此进行数据信息的传输。
我以小车上的手机作为server,以手上的手机作为client,server用于陀螺仪和磁力计信息和图像信息(摄像头获取QR码和是否找到小球的信息)发送给client,client根据获得的信息绘制出小车的行进路线图和标记图标。
首先说说server发给client的数据格式,是四位数组 String[] a ={"S","0","0","0"},至于如何发送请看上一篇,我就不累犊了……
a[0]中S表示停止,F表示前进,B表示后退
a[1]存的是陀螺仪角度,和小车初始方向角处理得到当前小车朝向
a[2]存的是磁力角角度,确定小车初始方向角
a[3]记录发现的物体,比如 "ball" 和QR码
client部分绘制路线图,具体思路是:借用google map为小车路线图,server获得磁力计的角度,确定小车初始位置。然后根据陀螺仪角度实时获得小车方位,从而在地图上画出具有方向性的路线图。当小车获取QR码并发送给client,地图上将标记出QR码,点击就可以获取QR信息。同理,当找到小球时,地图上也会出现图标告诉你小车是在哪找到小球的。下面是client的绘图部分:
appState = ((RosminState)getApplicationContext());
mapView =(MapView)findViewById(R.id.map);
mapcontroller = mapView.getController();
originMaker = getResources().getDrawable(R.drawable.origin);//起点图标
currMaker = getResources().getDrawable(R.drawable.sign); //当前位置图标
ballMaker = getResources().getDrawable(R.drawable.goal); //发现小球图标
lat1 = 0;
lng1 = 0;
//画出起点,小车路线原理就是画出两点并连线,周而复始
points.add(new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000)));
//添加自定义的图层
PathOverLay polyline = new PathOverLay(points); //添加polyline图层,负责画出路线
mapView.getOverlays().add(polyline);
mapView.invalidate();
mapView.setBuiltInZoomControls(true);
//设置中心和放大倍数
GeoPoint point=new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000));
mapcontroller.setCenter(point);
mapcontroller.setZoom(15);
//这个图层负责添加图标,这里添加“起点”图标
myOverlay = new MapOverlay(originMaker);
myOverlay.setItem(point);
myOverlay.setQR("起点");
mapView.getOverlays().add(myOverlay);
mapcontroller.animateTo(point);
小车路线原理就是画出两点并连线,周而复始……这里布置两个图层,一个负责画出路线,另一个负责标记图标。
下面具体方法,放入thread实时进行:
//在地图上画出路线以及做出标记
Thread time = new Thread(new Runnable()
{
public void run() {
long t1 = 0,t2 = 0 ;
while(true){
while(!Control.a[0].equals("F") && !Control.a[0].equals("B"))//未获得行动命令就等待
{
if(!Control.a[3].equals("0") && !Control.a[3].equals("ball"))
{
break;
}
if(Control.a[3].equals("ball"))
{
break;
}
}
t1 = System.currentTimeMillis();
if(!Control.a[3].equals("0") && !Control.a[3].equals("ball"))
//发现QR码并保存,在地图上标记
{
QRmsg = Control.a[3];
getQR =true;
addMarker();
}
if(Control.a[3].equals("ball"))
//发现小球,在地图上标记
{
findBALL=true;
addMarker();
}
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
t2 = System.currentTimeMillis();
moveTimeGap = t2 -t1 ;
Handler01.post(rDraw);
}
}//run
});
time.start();
当检测QR码时,把QR的信息保存到QRmsg中,并addMarker()在地图上添加图标。当检测到小球时,同样在地图上添加小球图标。用handler传递绘制路线。
/**画图部分,这里使用经纬度来表示小车朝向与行进路线
* 最初用磁力计得到方向角,然后与陀螺仪得到的角度进行处理得到当前小车朝向
* */
private Runnable rDraw = new Runnable()
{
public void run()
{
if(ori)
{
oriAngle = Integer.parseInt(Control.a[2]);
ori = false;
}
gyroAngle = Integer.parseInt( Control.a[1] );
if(Control.a[0].equals("F"))
{
lat1 += (moveTimeGap*0.000001)*Math.cos((oriAngle-gyroAngle)*0.017452);
lng1 += (moveTimeGap*0.000001)*Math.sin((oriAngle-gyroAngle)*0.017452);
points.add(new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000)));
}
if(Control.a[0].equals("B"))
{
lat1 -= (moveTimeGap*0.000001)*Math.cos((oriAngle-gyroAngle)*0.017452);
lng1 -= (moveTimeGap*0.000001)*Math.sin((oriAngle-gyroAngle)*0.017452);
points.add(new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000)));
}
}
};
当得到磁力计角度确定小车当前初始角度后就不再处理磁力计获得的角度,也就是说它只用来定位初始位置,后面的姿态都是由陀螺仪获得。因为陀螺仪的精度远远大于磁力计获得的方位角,磁力计的方位角老飘……这里通过对坐标两个值的增减来近似定出小车当前的坐标。
private void addMarker() {
if(getQR) //发现QR码后,存储坐标,显示图标,记录QR信息
{
GeoPoint myGeoPoint = new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000));
myOverlay = new MapOverlay(currMaker);
myOverlay.setItem(myGeoPoint);
myOverlay.setQR(QRmsg);
mapView.getOverlays().add(myOverlay);
getQR = false;
}
if(findBALL) //发现球后,存储坐标,显示图标,记录球信息
{
GeoPoint myGeoPoint = new GeoPoint((int) (lat1 * 1000000),(int) (lng1 * 1000000));
myOverlay = new MapOverlay(ballMaker);
myOverlay.setItem(myGeoPoint);
myOverlay.setQR("发现小球");
mapView.getOverlays().add(myOverlay);
findBALL = false;
}
}
如代码注解所示,不过MapOverlay具体是怎么工作的,我们继续……
public class MapOverlay extends ItemizedOverlay<OverlayItem> {
private List<GeoPoint> mItems = new ArrayList<GeoPoint>();
public List<String> QRmessage = new ArrayList<String>();
public MapOverlay(Drawable marker) {
super(boundCenterBottom(marker));
}
public void setItems(ArrayList<GeoPoint> items) {
mItems = items;
populate();
}
public void setItem(GeoPoint item) {
mItems.add(item);
populate();
}
public void setQR(String s) {
QRmessage.add(s);
populate();
}
@Override
protected OverlayItem createItem(int i) {
return new OverlayItem(mItems.get(i), null, null);
}
@Override
public int size() {
return mItems.size();
}
@Override
protected boolean onTap(int i) {
Toast.makeText(Map.this, "获得信息\n "+ QRmessage, Toast.LENGTH_SHORT).show();
return true;
}
}
这里可以看出,我们可以记录单独点坐标和单独的信息,这些数据以ArrayList的方式存储。当小车发现了小球,这时地图上就标记出了一个图标,点击就会显示“发现小球”……上面就是对专门负责画图标的MapOverlay图层进行的说明,那我再回头看看专门负责画路线的PathOverLay图层是怎么实现路线绘制功能的……
public class PathOverLay extends Overlay {
List<GeoPoint> points;
Paint paint;
/**
* 构造函数,使用GeoPoint List构造Polyline
*
* @param points
* GeoPoint点List
*/
public PathOverLay(List<GeoPoint> points) {
this.points = points;
paint = new Paint();
paint.setColor(Color.rgb(59,88,235));
paint.setAlpha(150);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(4);
}
/**
* 使用GeoPoint点List和Paint对象来构造Polyline
*
* @param points
* GeoPoint点List,所有的拐点
* @param paint
* Paint对象,用来控制划线样式
*/
public PathOverLay(List<GeoPoint> points, Paint paint) {
this.points = points;
this.paint = paint;
}
/**
* 真正将线绘制出来 只需将线绘制到canvas上即可,主要是要转换经纬度到屏幕坐标
*/
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
int i;
if (!shadow) {// 不是绘制shadow层
Projection projection = mapView.getProjection();
if (points != null) {
if (points.size() >= 2) {
Point start = projection.toPixels(points.get(0), null);// 需要转换坐标
for ( i = 1; i < points.size(); i++) {
Point end = projection.toPixels(points.get(i), null);
canvas.drawLine(start.x, start.y, end.x, end.y, paint);// 绘制到canvas上即可
start = end;
if(i == points.size()-1)
{
paint.setColor(Color.rgb(250,20,85));
end = projection.toPixels(points.get(i), null);
canvas.drawCircle(end.x, end.y, 6, paint);
paint.setColor(Color.rgb(59,88,235));
}
}
}
}
}
}
}
点坐标是以List的方式存储,最先是对它的粗细、颜色等进行设置。这里绘图的方法具体是:确定当前点,和前面一个点进行连线,同时将当前点设置成不同于前面的蓝色,从而明显显示当前小车位置。
PS:google map因为是google的核心技术,所以看源代码就别想了,接口的调用需要慢慢熟悉。原先google map有地图解析什么的,这是真实地图用的,我这里只是借用google map来绘制小车路线而已,所以只用了2个图层和点点连线的绘图方式,然后借用经度纬度反应出小车的具体方向。
by:season