Подтвердить что ты не робот

Вращение 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 );
}

Любая помощь будет высоко оценена, поскольку я не совсем знаю, как действовать. "Чтения", которые я получаю, когда вы пытаетесь это сделать, несколько неточно и указывают в неправильном направлении. Я что-то действительно делаю, или у меня просто плохой тест?

4b9b3361

Ответ 1

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

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

}

Ответ 2

Вы должны иметь возможность установить матрицу в ImageView без необходимости воссоздавать растровое изображение каждый раз, и er.. 'normalize' (это слово?) показания.

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

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

Ответ 3

Я провел около 40 часов в один уикенд, пытаясь это сделать.

Боль в прикладе, надеюсь, я могу избавить вас от этой боли.

Хорошо, я предупреждаю вас, это какой-то уродливый код. Я был в крайнем случае, чтобы закончить его, у него нет схем именования, но я попытался прокомментировать его как можно лучше для вас.

Он использовался для размещения больших кучек орехов, выложенных в полях для хранения

Используя телефонные диапазоны широты и долготы, lat/lon адресата, датчик компаса и некоторую алгебру, я смог рассчитать направление к месту назначения.

Показания Lat/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