web-dev-qa-db-ja.com

コンパスのように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 );
}
_

どうすればよいかわからないので、助けていただければ幸いです。試してみるときに得られる「読み」はやや不正確で間違った方向を示しています。私は本当に何かをやっているのですか、それとも本当に悪いテストランしかありませんでしたか?

35
karllindmark

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 | 
 + ----------- + -------------- + 

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;
}

これで、bearingToを整理したので、方位角を見てみましょう。

赤緯を追加するのではなく差し引く必要があります。方位角に赤緯を追加するのではなく、真北に電話を向けるときに方位角を0にしたいので、電話を向けると赤緯が2倍になります。真北へ。偏角を追加するのではなく差し引くことでこれを修正します。

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;

ただし、いくつかのダミー値を入力すると、次のようになります。方位角= 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);

}
55
chris

毎回ビットマップを再作成する必要なしに、マトリックスを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()関数を使用して計算されています。幅と高さは画像ビューの寸法です。

したがって、変数がラジアンではなく度数であることを確認し、「正規化」を試みます(見出しを-180-180ではなく0-360の範囲に入れます)。また、結果が180度ずれている場合は、ターゲットからの角度ではなく、ターゲットへのBearingToを取得していることを確認してください。

上記のマトリックスは、ScaleType.Matrixを持つImageViewで設定できます。

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

ImageViewの中心点(postRotateのwidth/2、height/2)を中心に回転しているため、ドローアブルは上向きであり、毎回新しいビットマップを再作成するのではなく、ドロー時に回転します。 。

3
FunkTheMonk

私はこれを行うために週末に約40時間を費やしました。

お尻の痛み、うまくいけば私はあなたにその痛みを救うことができます。

OK、私はあなたに警告しています、これはいくつかの醜いコードです。私はそれを完成するためにピンチでした、それは命名体系がありませんが、私はあなたのためにできる限りそれをコメントしようとしました。

保管のために畑に並べられた大きな山のナッツを見つけるために使用されました

携帯電話の現在の緯度と経度、目的地の緯度/経度、コンパスセンサー、およびいくつかの代数を使用して、目的地への方向を計算することができました。

緯度/経度とセンサーの読み取り値は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
MrZander