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

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

Функция 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; } 

Теперь мы разобрались в подшипнике, идем вперед к азимуту!

Вам нужно вычесть склонение вместо добавления его, так как мы хотим, чтобы азимут был 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); } 

Вы должны иметь возможность установить матрицу в 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); 

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

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