2011-11-02 8 views
32

Sono perplesso su come implementare una "bussola personale", ovvero una bussola che punta a un cuscinetto specifico anziché al "polo nord" standard ... sfortunatamente, il mio attuale tentativo è venuto fuori sbagliato (non indica il rilevamento dato). È anche collegato all'acceleratore per essere in grado di adattarsi dinamicamente in base al modo in cui l'utente sta girando.Rotazione di un ImageView come una bussola (con il "polo nord" impostato altrove)

Ecco il mio attuale tentativo di essa (la onSensorChanged() -Metodo che aggiorna la freccia):

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

     } 

Ed ecco il metodo che fa ruotare l'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); 
} 

Qualsiasi aiuto sarebbe essere molto apprezzato, in quanto non so come procedere. Le "letture" che sto ricevendo mentre lo provano sono alquanto imprecise e indicano la direzione sbagliata. Sto facendo qualcosa di veramente fuori, o ho appena fatto una brutta prova?

+0

Posso chiedere se questo metodo di aggiornamento dell'immagine è troppo CPU-costy? Come quando seguo il codice, lo schermo è estremamente lento e non riesco nemmeno a ingrandire o ridurre. –

+1

@ perfectionm1ng Non ho notato niente del genere, e questo era quasi due anni fa. Potrebbe essere la combinazione di cose? :-) – ninetwozero

risposta

51

La funzione rotateImageView dovrebbe funzionare bene, tuttavia ci sono alcune cose che devono essere cambiate nei calcoli di rotazione.

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

Il problema è che bearingTo fornirà un intervallo da -180 a 180, che confonderà un po 'le cose. Dovremo convertire questo valore in un intervallo da 0 a 360 per ottenere la rotazione corretta.

Questa è una tabella di ciò che vogliamo veramente, se paragonato a quello bearingTo ci dà

 
+-----------+--------------+ 
| bearingTo | Real bearing | 
+-----------+--------------+ 
| 0   | 0   | 
+-----------+--------------+ 
| 90  | 90   | 
+-----------+--------------+ 
| 180  | 180   | 
+-----------+--------------+ 
| -90  | 270   | 
+-----------+--------------+ 
| -135  | 225   | 
+-----------+--------------+ 
| -180  | 180   | 
+-----------+--------------+ 

Anche se la bearingTo è nel range -180 a 180, 0 è ancora vero nord, che ci lascerà a questo calcolo:

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

Se aggiungiamo alcuni valori fittizi per testare la nostra nuova formula:

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

Ora abbiamo risolto il problema, quindi andiamo all'azimut!

È necessario sottrarre la declinazione invece di aggiungerla, in quanto vogliamo che l'azimut sia 0 quando puntiamo il telefono direttamente al nord vero invece di avere la declinazione aggiunta all'azimut, che ci darà quindi il doppio della declinazione quando puntiamo il telefono verso il nord vero. Correggere questo sottraendo la declinazione invece di aggiungerla.

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

Quando ci rivolgiamo al telefono a nord vero oggi, azimuth sarà quindi uguale a 0

Il codice per la correzione del azimuth non è più necessario.

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

Ora continueremo al punto in cui calcoliamo la rotazione reale. Ma prima riassumerò che tipo di valori abbiamo ora e spiegando cosa sono realmente:

bearTo = L'angolo dal nord vero al luogo di destinazione dal punto in cui ci troviamo attualmente.

azimuth = L'angolo in cui è stato ruotato il telefono dal nord.

Dicendo questo, se si punta il telefono direttamente al nord vero, vogliamo davvero che la freccia ruoti l'angolo che bearto è impostato come. Se punti il ​​tuo telefono a 45 gradi dal nord vero, vogliamo che la freccia ruoti di 45 gradi in meno rispetto a ciò che è bearTo.Questo ci lascia per i seguenti calcoli:

float direction = bearTo - azimuth; 

Tuttavia, se mettiamo in alcuni valori fittizi: bearTo = 45; azimuth = 180;

direction = 45 - 180 = -135; 

Ciò significa che la freccia deve ruotare di 135 gradi in senso antiorario. Avremo bisogno di inserire una condizione if simile come abbiamo fatto con l'orso!

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

Il testo del cuscinetto, la N, E, S e W è spento, così li ho corretti nel metodo finale inferiore.

Il tuo metodo onSensorChanged dovrebbe assomigliare a questa:

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

} 
+0

Grazie Chris, contrassegnato come la risposta. :-) – ninetwozero

+0

Ottima risposta! Un tale risparmio di tempo. –

+0

Dopo alcuni test, ho scoperto che può essere disattivato quando il telefono ruota in modalità orizzontale. –

3

Dovresti essere in grado di impostare la matrice su ImageView senza dover ricreare ogni volta la bitmap e ... normalizzare (è la parola?) Le letture.

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

Nell'esempio di cui sopra MLOC è un luogo restituito da un fornitore di GPS e getBearing restituisce il numero di gradi est a nord della attuale direzione di marcia. item.mHeading è stato calcolato utilizzando la funzione Location.bearingTo() utilizzando mLoc e la posizione dell'elemento. larghezza e altezza sono le dimensioni della vista dell'immagine.

Quindi, assicurarsi che le variabili siano in gradi e non in radianti, e provare a 'normalizzare' (ottenere le intestazioni nell'intervallo 0-360 e non -180-180). Inoltre, se i risultati sono spenti di 180 gradi, assicurati di ottenere il rilevamento al tuo bersaglio, piuttosto che i gradi dal tuo obiettivo a te.

La matrice di cui sopra può essere impostato in un ImageView che ha uno ScaleType.Matrix

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

Poiché stai ruotare attorno al punto centrale del ImageView (larghezza/2, altezza/2 nel postRotate), il tuo drawable dovrebbe essere rivolto verso l'alto e verrà ruotato al momento del disegno, invece di ricreare ogni volta una nuova bitmap.

+0

Grazie per la risposta FunkTheMonk, non sapevo nulla di Matrix; Lo proverò! Ho comunque scelto la risposta di Chris come quella corretta (assegnandogli così la taglia). Grazie ancora per il tuo aiuto. :) – ninetwozero

1

Ho trascorso circa 40 ore un fine settimana cercando di farlo.

Dolore nel culo, spero di poterti risparmiare quel dolore.

Ok, ti ​​avverto, questo è un brutto codice. Ero in un guaio per finirlo, non ha schemi di denominazione, ma ho provato a commentarlo nel miglior modo possibile per te.

È stata usata per individuare grandi mucchi di noci posa nei campi di stoccaggio

Uso del telefono latitudine e longitudine, la latitudine/longitudine della destinazione, il sensore della bussola, e alcuni algebra, ho potuto per calcolare la direzione verso la destinazione. letture

latitudine/longitudine e sensori sono tirati dalla classe principale dell'applicazione

Questa è una parte del codice per arrow.class, che ho usato per disegnare una freccia su una tela verso una direzione.

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

Speriamo che si può riuscire a leggere il mio codice ... Se avrò tempo, farò un po 'più bello.

Se hai bisogno di spiegazioni, fammi sapere.

-MrZander

+1

Sai cosa ... ho appena visto il tuo nome ... hai creato l'app BF3 Battlelog. Ho fatto il document retriever di BF3. Ho appena aiutato il mio concorrente: P – MrZander

+0

Hey Alexander, grazie per aver condiviso il codice. Tuttavia, ho selezionato la risposta di Chris come mi ha effettivamente aiutato fuori Stackoverflow, ma gli ho detto di pubblicare la soluzione sul sito Web in modo da poter contrassegnare la sua risposta come "la risposta" per ulteriori riferimenti. Apprezzo che ti stia prendendo il tuo tempo, visto che imparerò una o due cose dal tuo post. :-) – ninetwozero

+1

E sì, è piuttosto divertente che ci incontrassimo anche qui di tutti i posti. :-) – ninetwozero

Problemi correlati