2011-11-02 48 views
32

对于如何实现“个人指南针”(即指向特定方位而不是标准“北极”)的指南针, ......不幸的是,我目前的尝试出现了错误(不指向给定的方位)。它还与油门挂钩,以便根据用户转向的方式动态调整自己。像指南针一样旋转ImageView(其他地方设置“北极”)

这是我在它(onSensorChanged() - 方法是更新箭头)当前的尝试:

public void onSensorChanged(SensorEvent event) { 

      // If we don't have a Location, we break out 
      if (LocationObj == null) return; 

      float azimuth = event.values[0]; 
          float baseAzimuth = azimuth; 

      GeomagneticField geoField = new GeomagneticField(Double 
        .valueOf(LocationObj.getLatitude()).floatValue(), Double 
        .valueOf(LocationObj.getLongitude()).floatValue(), 
        Double.valueOf(LocationObj.getAltitude()).floatValue(), 
        System.currentTimeMillis()); 
      azimuth += geoField.getDeclination(); // converts magnetic north into true north 

      //Correct the azimuth 
      azimuth = azimuth % 360; 

      //This is where we choose to point it 
      float direction = azimuth + LocationObj.bearingTo(destinationObj); 
      rotateImageView(arrow, R.drawable.arrow, direction); 

      //Set the field 
      if(baseAzimuth > 0 && baseAzimuth < 45) fieldBearing.setText("S"); 
      else if(baseAzimuth >= 45 && baseAzimuth < 90) fieldBearing.setText("SW"); 
      else if(baseAzimuth > 0 && baseAzimuth < 135) fieldBearing.setText("W"); 
      else if(baseAzimuth > 0 && baseAzimuth < 180) fieldBearing.setText("NW"); 
      else if(baseAzimuth > 0 && baseAzimuth < 225) fieldBearing.setText("N"); 
      else if(baseAzimuth > 0 && baseAzimuth < 270) fieldBearing.setText("NE"); 
      else if(baseAzimuth > 0 && baseAzimuth < 315) fieldBearing.setText("E"); 
      else if(baseAzimuth > 0 && baseAzimuth < 360) fieldBearing.setText("SE"); 
      else fieldBearing.setText("?"); 

     } 

及这里的旋转ImageView的(rotateImageView())的方法:

private void rotateImageView(ImageView imageView, int drawable, float rotate) { 

    // Decode the drawable into a bitmap 
    Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(), 
      drawable); 

    // Get the width/height of the drawable 
    DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); 
    int width = bitmapOrg.getWidth(), height = bitmapOrg.getHeight(); 

    // Initialize a new Matrix 
    Matrix matrix = new Matrix(); 

    // Decide on how much to rotate 
    rotate = rotate % 360; 

    // Actually rotate the image 
    matrix.postRotate(rotate, width, height); 

    // recreate the new Bitmap via a couple conditions 
    Bitmap rotatedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, width, height, matrix, true); 
    //BitmapDrawable bmd = new BitmapDrawable(rotatedBitmap); 

    //imageView.setImageBitmap(rotatedBitmap); 
    imageView.setImageDrawable(new BitmapDrawable(getResources(), rotatedBitmap)); 
    imageView.setScaleType(ScaleType.CENTER); 
} 

任何帮助非常感谢,因为我不知道如何继续。我在试用时得到的“读数”有些不准确,并指向了错误的方向。我是否真的做了一些事情,还是我的测试运行非常糟糕?

+0

请问更新图像这种方法是否过CPU-costy?当我遵循代码时,屏幕非常慢,我甚至无法放大或缩小。 –

+1

@ perfectionm1ng我没有注意到那样的事情,那已经差不多两年了。可能是事物的组合? :-) – ninetwozero

回答

51

你rotateImageView功能应该工作得很好,但是有一些事情需要在你的循环计算改变。

//This is where we choose to point it 
float direction = azimuth + LocationObj.bearingTo(destinationObj); 
rotateImageView(arrow, R.drawable.arrow, direction); 

问题是,bearingTo会给你从-180到180的范围,这会使事情混淆一点。我们需要将此值转换为0到360的范围才能获得正确的旋转。

这是我们真正需要的表格,比较一下bearingTo给我们

 
+-----------+--------------+ 
| bearingTo | Real bearing | 
+-----------+--------------+ 
| 0   | 0   | 
+-----------+--------------+ 
| 90  | 90   | 
+-----------+--------------+ 
| 180  | 180   | 
+-----------+--------------+ 
| -90  | 270   | 
+-----------+--------------+ 
| -135  | 225   | 
+-----------+--------------+ 
| -180  | 180   | 
+-----------+--------------+ 

即使bearingTo的范围是从-180到180,0仍是正北,这将让我们这样计算:

// Store the bearingTo in the bearTo variable 
float bearTo = LocationObj.bearingTo(destinationObj); 

// If the bearTo is smaller than 0, add 360 to get the rotation clockwise. 
if (bearTo < 0) { 
    bearTo = bearTo + 360; 
} 

如果我们增加一些虚拟值来测试我们的新配方:

float bearTo = -100; 
// This will now equal to true 
if (-100 < 0) { 
    bearTo = -100 + 360 = 360 - 100 = 260; 
} 

我们现在已经整理了轴承,让我们来看方位!

您需要减去偏角而不是增加偏角,因为当我们将手机直接指向真北时,我们希望方位角为0,而不是将方位角添加到方位角,这样会在我们将倾角增加一倍时我们将电话指向正北。通过减去偏角而不是增加它来纠正这个问题。

azimuth -= geoField.getDeclination(); // converts magnetic north into true north 

当我们现在打开手机的真北的方位也就那么等于0

您的对方位进行校正代码不再是必要的。

// Remove/uncomment this line 
azimuth = azimuth % 360; 

我们现在将继续到我们计算真实旋转的点。但首先我将总结一下我们现在有什么类型的值,并解释他们真的是什么:

bearTo =从我们目前站立的角度,从真北处到目的位置的角度。

azimuth =您将手机从真北向旋转的角度。

通过这样说,如果您将手机直接指向正北,我们确实需要箭头来旋转bearTo设置的角度。如果您将手机指向真北方45度,我们希望箭头的旋转角度比bearTo小45度。这让我们对以下计算:

float direction = bearTo - azimuth; 

但是,如果我们把一些虚拟值: bearTo = 45; 方位角= 180;

direction = 45 - 180 = -135; 

这意味着箭头应该逆时针旋转135度。我们需要像熊熊一样提出类似的条件!

// If the direction is smaller than 0, add 360 to get the rotation clockwise. 
if (direction < 0) { 
    direction = direction + 360; 
} 

你的轴承文本时,N,E,S和W是关闭的,所以我已经在低于最终方法来校正它们。

你onSensorChanged方法应该是这样的:

public void onSensorChanged(SensorEvent event) { 

    // If we don't have a Location, we break out 
    if (LocationObj == null) return; 

    float azimuth = event.values[0]; 
    float baseAzimuth = azimuth; 

    GeomagneticField geoField = new GeomagneticField(Double 
     .valueOf(LocationObj.getLatitude()).floatValue(), Double 
     .valueOf(LocationObj.getLongitude()).floatValue(), 
     Double.valueOf(LocationObj.getAltitude()).floatValue(), 
     System.currentTimeMillis()); 

    azimuth -= geoField.getDeclination(); // converts magnetic north into true north 

    // Store the bearingTo in the bearTo variable 
    float bearTo = LocationObj.bearingTo(destinationObj); 

    // If the bearTo is smaller than 0, add 360 to get the rotation clockwise. 
    if (bearTo < 0) { 
     bearTo = bearTo + 360; 
    } 

    //This is where we choose to point it 
    float direction = bearTo - azimuth; 

    // If the direction is smaller than 0, add 360 to get the rotation clockwise. 
    if (direction < 0) { 
     direction = direction + 360; 
    } 

    rotateImageView(arrow, R.drawable.arrow, direction); 

    //Set the field 
    String bearingText = "N"; 

    if ((360 >= baseAzimuth && baseAzimuth >= 337.5) || (0 <= baseAzimuth && baseAzimuth <= 22.5)) bearingText = "N"; 
    else if (baseAzimuth > 22.5 && baseAzimuth < 67.5) bearingText = "NE"; 
    else if (baseAzimuth >= 67.5 && baseAzimuth <= 112.5) bearingText = "E"; 
    else if (baseAzimuth > 112.5 && baseAzimuth < 157.5) bearingText = "SE"; 
    else if (baseAzimuth >= 157.5 && baseAzimuth <= 202.5) bearingText = "S"; 
    else if (baseAzimuth > 202.5 && baseAzimuth < 247.5) bearingText = "SW"; 
    else if (baseAzimuth >= 247.5 && baseAzimuth <= 292.5) bearingText = "W"; 
    else if (baseAzimuth > 292.5 && baseAzimuth < 337.5) bearingText = "NW"; 
    else bearingText = "?"; 

    fieldBearing.setText(bearingText); 

} 
+0

谢谢Chris,标记为答案。 :-) – ninetwozero

+0

很好的回答!这样节省时间。 –

+0

经过一些测试后,我发现手机旋转到横向模式时可能会关闭。 –

3

您应该能够将矩阵设置为ImageView,而不必每次都重新创建位图,并且呃“标准化”(是这个单词?)读数。

float b = mLoc.getBearing(); 
if(b < 0) 
    b = 360 + b; 
float h = item.mHeading; 
if(h < 0) 
    h = 360 + h; 
float r = (h - b) - 360; 
matrix.reset(); 
matrix.postRotate(r, width/2, height/2); 

在上面的例子mLoc由GPS提供程序返回一个位置和getBearing东返回行程的电流方向的北的多少度。 item.mHeading已使用Location.bearingTo()函数使用mLoc和项目的位置进行计算。宽度和高度是图像视图的尺寸。

因此,请确保您的变量是以度为单位而不是弧度,并尝试“正常化”(将标题变为0-360而不是-180-180)。另外,如果结果偏离了180度,请确保你的目标是取决于你的目标,而不是从你的目标到你的目标。

上述矩阵然后可以在由于您围绕在所述的ImageView(宽度/ 2,高度/ 2的中心点旋转,其具有ScaleType.Matrix

imageView.setMatrix(matrix); 
imageview.setScaleType(ScaleType.Matrix); 

一个ImageView的设置postRotate),你的drawable应该向上指向,并且会在绘制时旋转,而不是每次都重新创建一个新的位图。

+0

谢谢你的回应FunkTheMonk,我不知道Matrix的东西;我会尝试一下!然而,我确实选择了克里斯的答案作为正确的答案(因此奖励他赏金)。再次感谢您的帮助。 :) – ninetwozero

1

我一个周末花了大约40个小时试图做到这一点。

痛苦的屁股,希望我可以免除你的痛苦。

好吧,我警告你,这是一些丑陋的代码。 我很困难完成它,它没有命名方案,但我试图尽可能为你评论它。

它被用来找到大堆坚果的领域奠定了存储

使用手机当前的纬度和经度,该纬度目的地,罗盘传感器,以及一些代数/ LON,我能计算到目的地的方向。

纬度/经度和传感器读数从MainApplication类

这是一些对arrow.class的代码,这在我以前向的方向上的画布上画一个箭头的拉动。

//The location you want to go to// 
    //"Given North" 
    double lat=0; 
    double lon=0; 
    ////////////////////////////////// 
    protected void onDraw(Canvas canvas) { 

    //Sensor values from another class managing Sensor 
    float[] v = MainApplication.getValues(); 

    //The current location of the device, retrieved from another class managing GPS 
    double ourlat= MainApplication.getLatitudeD(); 
    double ourlon= MainApplication.getLongitudeD(); 

    //Manually calculate the direction of the pile from the device 
    double a= Math.abs((lon-ourlon)); 
    double b= Math.abs((lat-ourlat)); 
    //archtangent of a/b is equal to the angle of the device from 0-degrees in the first quadrant. (Think of a unit circle) 
    double thetaprime= Math.atan(a/b); 
    double theta= 0; 

    //Determine the 'quadrant' that the desired location is in 
    //ASTC (All, Sin, Tan, Cos) Determines which value is positive 
    //Gotta love Highschool algebra 

    if((lat<ourlat)&&(lon>ourlon)){//-+ 
     //theta is 180-thetaprime because it is in the 2nd quadrant 
     theta= ((Math.PI)-thetaprime); 

     //subtract theta from the compass value retrieved from the sensor to get our final direction 
     theta=theta - Math.toRadians(v[0]); 

    }else if((lat<ourlat)&&(lon<ourlon)){//-- 
     //Add 180 degrees because it is in the third quadrant 
     theta= ((Math.PI)+thetaprime); 

     //subtract theta from the compass value retreived from the sensor to get our final direction 
     theta=theta - Math.toRadians(v[0]); 

    }else if((lat>ourlat)&&(lon>ourlon)){ //++ 
     //No change is needed in the first quadrant 
     theta= thetaprime; 

     //subtract theta from the compass value retreived from the sensor to get our final direction 
     theta=theta - Math.toRadians(v[0]); 

    }else if((lat>ourlat)&&(lon<ourlon)){ //+- 
     //Subtract thetaprime from 360 in the fourth quadrant 
     theta= ((Math.PI*2)-thetaprime); 

     //subtract theta from the compass value retreived from the sensor to get our final direction 
     theta=theta - Math.toRadians(v[0]); 

    } 

    canvas.drawBitmap(_bitmap, 0, 0, paint); 
    float[] results = {0}; //Store data 
    Location.distanceBetween(ourlat, ourlon, lat, lon, results); 
    try{ 

     //Note, pileboundary is a value retreived from a database 
     //This changes the color of the canvas based upon how close you are to the destination 
     //Green < 100 (or database value), Yellow < (100)*2, Otherwise red 
     if((results[0])<(pileboundary==0?100:pileboundary)){ 
      _canvas.drawColor(Color.GREEN); 
     }else if((results[0])<(pileboundary==0?100:pileboundary)*2){ 
      _canvas.drawColor(Color.YELLOW); 
     }else{ 
      _canvas.drawColor(Color.rgb(0xff, 113, 116)); //RED-ish 
     } 
     //Draw the distance(in feet) from the destination 
     canvas.drawText("Distance: "+Integer.toString((int) (results[0]*3.2808399))+ " Feet", 3, height-3, textpaint); 
    }catch(IllegalArgumentException ex){ 
     //im a sloppy coder 
    } 
    int w = canvas.getWidth(); 
    int h = height; 
    int x = w/2; //put arrow in center 
    int y = h/2; 
    canvas.translate(x, y); 
    if (v != null) { 

     // Finally, we rotate the canvas to the desired direction 
     canvas.rotate((float)Math.toDegrees(theta)); 


    } 
    //Draw the arrow! 
    canvas.drawPath(thearrow, paint); 
} 


//Some of my declarations, once again sorry :P 
GeomagneticField gf; 
Bitmap _bitmap; 
Canvas _canvas; 
int _height; 
int _width; 
Bitmap b; 
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    //Get the current GeomagneticField (Should be valid until 2016, according to android docs) 
    gf = new GeomagneticField((float)lat,(float)lon,(float)MainApplication.getAltitude(),System.currentTimeMillis()); 
    _height = View.MeasureSpec.getSize(heightMeasureSpec); 
    _width = View.MeasureSpec.getSize(widthMeasureSpec); 
    setMeasuredDimension(_width, _height); 
    _bitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888); 
    _canvas = new Canvas(_bitmap); 
    b=Bitmap.createBitmap(_bitmap); 
    drawBoard(); 
    invalidate(); 
} 


//Here is the code to draw the arrow 
    thearrow.moveTo(0, -50); 
    thearrow.lineTo(-20, 50); 
    thearrow.lineTo(0, 50); 
    thearrow.lineTo(20, 50); 
    thearrow.close(); 
    thearrow.setFillType(FillType.EVEN_ODD); 

希望你可以设法阅读我的代码......如果我有时间,我会让它更漂亮。

如果您需要任何解释,请告诉我。

-MrZander

+1

你知道吗......我只是看着你的名字......你制作了BF3 Battlelog应用程序。我制作了BF3统计检索器。我刚刚帮助我的竞争对手:P – MrZander

+0

嗨,亚历山大,谢谢分享代码。但是,我选择了Chris的答案,因为他实际上帮助我脱离了Stackoverflow,但我告诉他在网站上发布解决方案,以便我可以将他的答案标记为“答案”以供进一步参考。我很感激你花时间,因为我会从你的帖子中学到一两件事。:-) – ninetwozero

+1

是的,我们在这里也会见到所有的地方,这很有趣。 :-) – ninetwozero