像指南针一样旋转图像视图(将"north pole"设置在其他地方)



我对如何实现"个人指南针"感到困惑,即指向特定方位而不是标准的"北极"的指南针…不幸的是,我目前的尝试是错误的(没有指向给定的方位)。它还与加速器连接在一起,能够根据用户转向的方向动态调整自己。

这是我目前的尝试(更新箭头的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 );
}

任何帮助将是非常感激的,因为我不太知道如何进行。我在尝试时得到的"读数"有些不准确,而且指向错误的方向。是我做错了什么,还是我的试运行太糟糕了?

你的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给我们的比较

<>之前+-----------+--------------+→轴承→真正的轴承+-----------+--------------+| 0 | 0 |+-----------+--------------+| 90 | 90 |+-----------+--------------+| 180 | 180+-----------+--------------+| -90 | 270 |+-----------+--------------+| -135 | 225 |+-----------+--------------+| -180 | 180 |+-----------+--------------+之前

即使方位在-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度,我们希望箭头的旋转角度比bearTo小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);
}

您应该能够将矩阵设置为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返回当前行进方向偏东偏北的度数。项。mHeading是使用location. bearingto()函数使用mLoc和项目的位置计算出来的。宽度和高度是图像视图的尺寸。

所以,确保你的变量是度而不是弧度,并尝试"规范化"(让标题进入0-360的范围,而不是-180-180)。此外,如果结果偏离了180度,请确保你得到的是到目标的方位,而不是从目标到你的角度。

上面的矩阵可以在具有ScaleType的ImageView中设置。矩阵

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

因为你是围绕着imageView的中心点旋转(postRotate中的宽度/2,高度/2),你的可绘制对象应该指向上方,并在绘制时旋转,而不是每次重新创建一个新的位图

一个周末我花了大约40个小时来做这件事。

屁股疼,希望我能让你不疼。

好的,我警告你,这是一些丑陋的代码。我在紧要关头完成它,它没有命名方案,但我尽力为你评论它。

它被用来定位在田野里堆放的成堆的坚果。

使用手机当前的纬度和经度,目的地的经纬度,指南针传感器,以及一些代数运算,我能够计算到目的地的方向。

纬度和传感器读数是从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

最新更新