Rotar un ImageView como una brújula (con el "polo norte" puesto en otro lugar)

Estoy atónito con respecto a cómo implementar una "brújula personal", es decir, una brújula que apunta a un rumbo específico en lugar del estándar "polo norte" … desafortunadamente, mi intento actual ha salido mal (no apunta a la Dado cojinete). También está conectado con el acelerador para poder ajustarse dinámicamente en función de la forma en que el usuario está girando.

Aquí está mi intento actual (el método onSensorChanged() que actualiza la flecha):

 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("?"); } 

Y aquí está el método que rota el 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 ); } 

Cualquier ayuda sería muy apreciada, ya que no sé cómo proceder. Las "lecturas" que estoy recibiendo al intentarlo son algo imprecisas y apuntan en la dirección equivocada. ¿Estoy haciendo algo realmente malo, o acabo de tener una mala prueba?

Su función rotateImageView debería funcionar bien, sin embargo, hay algunas cosas que deben cambiarse en los cálculos de rotación.

 //This is where we choose to point it float direction = azimuth + LocationObj.bearingTo( destinationObj ); rotateImageView( arrow, R.drawable.arrow, direction ); 

El problema es que bearingTo te dará un rango de -180 a 180, lo que confunde un poco las cosas. Necesitaremos convertir este valor en un rango de 0 a 360 para obtener la rotación correcta.

Esta es una tabla de lo que realmente queremos, comparando con lo que nos da

 Unesdoc.unesco.org unesdoc.unesco.org
 |  TeniendoTo |  Rodamiento real |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  0 |  0 |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  90 |  90 |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  180 |  180 |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  -90 |  270 |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  -135 |  225 |
 Unesdoc.unesco.org unesdoc.unesco.org
 |  -180 |  180 |
 Unesdoc.unesco.org unesdoc.unesco.org

Aunque el bearingTo está en el rango de -180 a 180, 0 sigue siendo verdadero al norte que nos dejará a este cálculo:

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

Si añadimos algunos valores ficticios para probar nuestra nueva fórmula:

 float bearTo = -100; // This will now equal to true if (-100 < 0) { bearTo = -100 + 360 = 360 - 100 = 260; } 

¡Ahora hemos resuelto el rodamiento, para dirigirnos hacia el acimut!

Necesitas suprimir la declinación en lugar de añadirla, ya que queremos que el azimut sea 0 cuando apuntamos el teléfono directamente al norte verdadero en lugar de tener la declinación añadida al azimut, lo que nos dará el doble de la declinación cuando apuntamos el teléfono. Al norte verdadero. Corrija esto restando la declinación en lugar de añadirla.

 azimuth -= geoField.getDeclination(); // converts magnetic north into true north 

Cuando giramos el teléfono hacia el norte verdadero ahora, el azimut será igual a 0

Su código para corregir el azimut ya no es necesario.

 // Remove / uncomment this line azimuth = azimuth % 360; 

Ahora continuaremos hasta el punto en que calculamos la rotación real. Pero primero voy a resumir qué tipo de valores que tenemos ahora y explicar lo que realmente son:

BearTo = El ángulo desde el norte verdadero hasta la ubicación de destino desde el punto en el que estamos actualmente.

Azimut = El ángulo que ha girado su teléfono desde el norte verdadero.

Diciendo esto, si apuntas tu teléfono directamente al norte verdadero, realmente queremos que la flecha gire el ángulo que bearTo se establece como. Si apuntas tu teléfono a 45 grados del norte verdadero, queremos que la flecha gire 45 grados menos de lo que es bearTo. Esto nos deja a los siguientes cálculos:

 float direction = bearTo - azimuth; 

Sin embargo, si ponemos algunos valores ficticios: bearTo = 45; Azimut = 180;

 direction = 45 - 180 = -135; 

Esto significa que la flecha debe girar 135 grados hacia la izquierda. Tendremos que poner en un similar si-condición como lo hicimos con el bearTo!

 // If the direction is smaller than 0, add 360 to get the rotation clockwise. if (direction < 0) { direction = direction + 360; } 

Su texto del cojinete, el N, E, S y W está apagado, así que los he corregido en el método final abajo.

Su método onSensorChanged debería tener este aspecto:

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

Usted debe ser capaz de establecer la matriz a la ImageView sin tener que recrear el mapa de bits cada vez, y er .. "normalizar" (es que la palabra?) Las lecturas.

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

En el ejemplo anterior mLoc es un Location devuelto por un proveedor de gps y getBearing devuelve el número de grados al este del norte de la dirección actual de viaje. Item.mHeading se ha calculado utilizando la función Location.bearingTo () utilizando mLoc y la ubicación del elemento. Anchura y altura son las dimensiones de la vista de imagen.

Por lo tanto, asegúrese de que sus variables están en grados y no radianes, y tratar de normalizar (obtener títulos en el rango de 0-360 y no -180-180). Además, si los resultados están apagados en 180 grados, asegúrese de que está recibiendo el apoyo a su objetivo, en lugar de los grados de su objetivo a usted.

La matriz anterior se puede establecer en un ImageView que tiene un ScaleType.Matrix

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

Puesto que estás girando alrededor del punto central de la imageView (el ancho / 2, altura / 2 en el postRotate), tu dibujable debe estar apuntando hacia arriba y se girará en tiempo de dibujo, en lugar de volver a crear un nuevo mapa de bits cada vez .

Pasé unas 40 horas un fin de semana tratando de hacer esto.

Dolor en el culo, espero que pueda ahorrarte ese dolor.

Ok, te estoy advirtiendo, este es un código feo. Estaba en un apuro para terminarlo, no tiene esquemas de nombramiento, pero traté de comentarlo lo mejor que pude por usted.

Se utilizó para localizar grandes montones de nueces colocando en los campos para el almacenamiento

Usando los teléfonos latitud y longitud actuales, el lat / lon del destino, el sensor de la brújula, y un cierto álgebra, pude calcular la dirección al destino.

Las lecturas Lat / lon y sensor se extraen de la clase MainApplication

Esto es parte del código de arrow.class, que usé para dibujar una flecha en un lienzo hacia una dirección.

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

Espero que puedas leer mi código … Si tengo tiempo, lo haré un poco más bonito.

Si necesita alguna explicación, avíseme.

Marrón

  • Rotar una matriz de bytes YUV en Android
  • Cómo rotar un rectángulo dibujado en lienzo en Android?
  • ImageView volverá al estado original después de girar?
  • Actividad de reinicio en la rotación de Android
  • Android - ¿Cómo diseñar un tipo de diseño de Rotary Dialer / Rotary Dialer?
  • Traducción después de la rotación
  • Cuando giro la imagen a 45 grados de superposición que sale de los límites en android
  • Rotar la imagen en el centro no va lisa (Monodroid)
  • Rotar una superficie de la cámaraVer a retrato
  • SetRotationY () width API10 en Android
  • Después de la rotación de la pantalla, se cambiará el idioma de mi aplicación
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.