Вращение 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 ответа

Решение

Ваша функция 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, чтобы получить правильное вращение.

Это таблица того, что мы действительно хотим, в сравнении с тем, что нам дает

+-----------+--------------+
| подшипник к | Реальный подшипник |
+-----------+--------------+
| 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 градусов от истинного севера, мы хотим, чтобы стрелка повернулась на 45 градусов меньше, чем BearTo. Это оставляет нас для следующих расчетов:

float direction = bearTo - azimuth;

Однако, если мы введем несколько фиктивных значений: bearTo = 45; азимут = 180;

direction = 45 - 180 = -135;

Это означает, что стрелка должна повернуться на 135 градусов против часовой стрелки. Нам нужно будет ввести похожее условие if, как мы это сделали с bearTo!

// 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 возвращает количество градусов к востоку от севера от текущего направления движения. Item.mHeading был рассчитан с использованием функции Location.bearingTo() с использованием mLoc и местоположением элемента. ширина и высота - размеры изображения.

Итак, убедитесь, что ваши переменные указаны в градусах, а не в радианах, и попробуйте "нормализовать" (получая заголовки в диапазоне 0-360, а не -180-180). Кроме того, если результаты отклоняются на 180 градусов, убедитесь, что вы приближаетесь к цели, а не к градусам от цели к вам.

Вышеуказанная матрица может быть затем установлена ​​в ImageView, который имеет ScaleType.Matrix

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

Поскольку вы вращаете вокруг центральной точки imageView (width/2, height/2 в postRotate), ваш чертеж должен быть направлен вверх и будет вращаться во время рисования, а не заново создавать каждый раз растровое изображение каждый раз,

Я провел около 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

Другие вопросы по тегам