像指南针一样旋转ImageView(在其他地方设置“北极”)

时间:2011-11-02 10:10:44

标签: android gps rotation android-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 );
}

任何帮助都会非常感激,因为我不知道如何继续。我在尝试时得到的“读数”有点不准确,指向错误的方向。我真的做了什么,或者我的测试运行真的很糟糕吗?

3 个答案:

答案 0 :(得分:53)

您的rotateImageView函数应该可以正常工作,但是在旋转计算中需要更改一些内容。

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

问题是,轴承会给你-180到180的范围,这会让事情有点混乱。我们需要将此值转换为0到360的范围才能获得正确的旋转。

这是我们真正想要的表格,与轴承给我们的内容相比

+-----------+--------------+
| 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 =从我们目前所处的位置开始,从真北到目的地的角度。

方位角=您将手机从正北方向旋转的角度。

通过这样说,如果你将手机直接指向真正的北方,我们真的希望箭头旋转bearTo设置为的角度。如果你将手机指向真北45度,我们希望箭头比熊的旋转度低45度。这使我们进行以下计算:

float direction = bearTo - azimuth;

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

direction = 45 - 180 = -135;

这意味着箭头应逆时针旋转135度。我们需要像对待BearTo一样使用类似的if条件!

// 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);

}

答案 1 :(得分: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是使用mLoc和项目的位置使用Location.bearingTo()函数计算的。宽度和高度是图像视图的尺寸。

因此,请确保您的变量以度为单位而不是弧度,并尝试'标准化'(将标题设置为0-360而不是-180-180)。此外,如果结果偏离了180度,请确保你的目标是承受目标,而不是从目标到你的角度。

然后可以在具有ScaleType.Matrix

的ImageView中设置上述矩阵
imageView.setMatrix(matrix);
imageview.setScaleType(ScaleType.Matrix);

由于你围绕imageView的中心点(postRotate中的宽度/ 2,高度/ 2)旋转,你的drawable应该指向上方并且将在绘制时旋转,而不是重新创建一个新的每次都是位图。

答案 2 :(得分:1)

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

屁股疼痛,希望我可以免除那种痛苦。

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

它被用来找到在田间存放的大堆坚果

使用电话当前纬度和经度,目的地的纬度/经度,罗盘传感器和一些代数,我能够计算到目的地的方向。

从MainApplication类中拉出Lat / lon和传感器读数

这是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