2011-08-29 12 views
8

Sto lavorando a un'applicazione che utilizza l'API OpenStreetMap (OSM) per visualizzare vari punti di interesse su una mappa offline, composta da riquadri statici.Android: ottenere una mappa OSM ruotata per riempire l'intera schermata

Una delle funzionalità che sto attualmente implementando è far ruotare la mappa in base al rilevamento determinato dal telefono (tramite GPS). Sono stato in grado di implementare la rotazione effettiva senza troppi sforzi, ma dal momento che il mio codice ruota l'intera tela - forse un approccio piuttosto ingenuo - ora ho angoli vuoti sullo schermo in cui non vengono caricate nuove tessere per compensare il fatto che le tessere ruotate non riempiono più questi pixel.

Dopo un po 'di ricerca su Google, ho trovato alcuni suggerimenti su come potrei essere in grado di risolvere questo problema, ma finora non ho avuto fortuna.

In uno dei Mr. Romain Guy's posts, cita quanto segue:

ho fatto in passato e richiede di creare un ViewGroup personalizzato che ruota() metodo tela nel dispatchDraw. È inoltre necessario aumentare la dimensione di MapView (in modo che assorba un numero sufficiente di pixel quando ruotati). È inoltre necessario ruotare gli eventi di tocco in dispatchTouchEvent(). O se si utilizza Android 3.0 si può semplicemente chiamare theMapView.rotate()

Prendendo il suo consiglio sulla rotazione della mappa, ho implementato dispatchDraw() e dispatchTouchEvent() come suggerito, ma sto avendo problemi con il parte dove accenna che ho bisogno di aumentare le dimensioni del MapView?

È qualcosa che faccio nel file XML, come suggerito in this thread?

Oppure, posso in qualche modo sovrascrivere la funzione onMeasure() nella classe RelativeLayout sottoclasse che gestisce la rotazione della mia mappa?

Suggerimenti e suggerimenti sono i benvenuti.

UPDATE:

Nel tentativo di trovare una soluzione accettabile a questo problema, ho cercato di cambiare la dimensione della tela. Il pensiero era che con una dimensione della tela che è più grande della dimensione effettiva dello schermo, potrei essere in grado di spostare completamente gli angoli vuoti dallo schermo. Sfortunatamente, non sembra essere un'opzione effettiva canvas.size(); il meglio che ho trovato era canvas.scale().

Utilizzando canvas.scale(), sono stato in grado di aumentare la scala della tela di un fattore 2 in entrambe le dimensioni orizzontale e verticale. Ciò significa, tuttavia, che l'immagine viene effettivamente ingrandita, causando una pixelizzazione inaccettabile per i riquadri della mappa.

Qualcuno sa dove viene dichiarata la dimensione della tela e se la modifica della dimensione della tela potrebbe effettivamente risolvere il mio problema?

risposta

3

Ho finito per seguire il consiglio di Romain Guy (lo stesso consiglio che ho postato nella mia domanda). Cioè, ho creato la mia personalizzata ViewGroup estendendo il RelativeLayout e ho quindi aumentato la dimensione del mio mapView per fornire copertura dell'intero schermo.L'estensione di RelativeLayout ViewGroup era necessaria per sostituire le funzioni dispatchDraw(...), onMeasure(...) e dispatchTouchEvent(...) per abilitare le funzioni di rotazione delle mappe desiderate per la mia applicazione.

La funzione dispatchDraw(...) essenzialmente intercetta le chiamate alla funzione onDraw(...), esegue alcune manipolazioni specifiche sull'input a tale funzione e quindi le rilascia per l'elaborazione. Nel nostro caso, vorremmo ruotare la tela mapView prima che arrivi alla funzione effettiva onDraw(...). Questo è il motivo per cui dovremo sostituire questa funzione.

In particolare, la funzione dispatchDraw(...) accetta come input un oggetto canvas, che (in questo caso) rappresenta l'oggetto OSM mapView (come definito nel file XML di seguito). Se si deve applicare una rotazione alla tela, vorremmo localizzare il centro della mappa, tradurre (cioè spostare) la mappa in modo che il centro della mappa si trovi sull'origine del sistema di coordinate, ruotato la mappa su l'origine del sistema di coordinate e, infine, vorremmo inviare questa tela modificata alla fase successiva nella pipeline di rendering.

Il mio codice per questo è di seguito; nota che Manager è la mia creazione singleton che non esisterà nella tua implementazione a meno che tu non ne scriva uno tu stesso!

/** 
* @param pCanvas 
* @return void 
* 
* This function intercepts all dispatches to the onDraw function of the 
* super, and it rotates the canvas in accordance with the user's wishes 
* using the phone bearing as determined either through the magnetometer 
* or GPS fixes. 
*/ 
@Override 
protected void dispatchDraw(final Canvas pCanvas) { 
    final long startMs = System.currentTimeMillis(); 

    // If automatic map rotation has been enabled, get bearing from phone: 
    if (Manager.getInstance().getMapRotationMode() != Constants.DISABLED) { 
     mBearing = Manager.getInstance().getPhoneBearing(); 

     // Save the state of the transformation matrix: 
     pCanvas.save(Canvas.MATRIX_SAVE_FLAG); 

     // getWidth() and getHeight() return the size of the canvas as 
     // defined in the XML file, and not the size of the screen! 
     int canvasOffsetX = -(getWidth()/2) + (screenWidth/2); 
     int canvasOffsetY = -(getHeight()/2) + (screenHeight/2); 

     // Set origin of canvas to center of map: 
     pCanvas.translate(canvasOffsetX, canvasOffsetY); 

     // Rotate the canvas to the correct bearing: 
     pCanvas.rotate(-mBearing, getWidth()/2, getHeight()/2); 

     // Pass on the rotated canvas, and restore after that: 
     super.dispatchDraw(pCanvas); 

     // Balance out the call to save, and restore the matrix to 
     // saved state: 
     pCanvas.restore();   
    } // end if 

    else { // If map rotation has not been enabled: 
     super.dispatchDraw(pCanvas); 
    } // end else 

    final long endMs = System.currentTimeMillis(); 
    if (LOG_ENABLED) { 
     Log.i(TAG, "mapView Dispatch Time: " + (endMs - startMs) + "ms"); 
    } // end if 
} // end dispatchDraw() 

successivo avremo bisogno di ignorare dispatchTouchEvent(...), perché la rotazione della tela OSM mapView finisce rotazione non solo la rappresentazione grafica della mappa, ma anche tutto il resto connessi a tale attività (ciò si verifica come un lato effetto della mia specifica implementazione); cioè, le coordinate dell'evento di tocco rimangono relative alla tela mapView dopo essere state ruotate e non relative al telefono effettivo. Ad esempio, se immaginiamo di ruotare la tela di 180 gradi, se l'utente tenta di spostare la mappa verso sinistra, si sposta invece sulla mappa a destra, poiché tutto è capovolto!

Nel codice, si potrebbe correggere per questo problema come segue:

/** 
* @param event 
* @return boolean 
* 
* This function intercepts all interactions with the touch display (that is, 
* all touchEvents), and for each finger on the screen, or pointer, the 
* function applies the necessary rotation to counter the rotation of the 
* map. The coordinate of each pointer is also modified so that it returns 
* the correct location on the enlarged canvas. This was necessary to return 
* the correct coordinate for actions such as double-tap, and proper icon 
* identification upon clicking an icon. 
*/ 
@Override 
public boolean dispatchTouchEvent(MotionEvent event) { 
    // Get the number of pointers (i.e. fingers on screen) from the passed 
    // in MotionEvent: 
    float degrees = Manager.getInstance().getPhoneBearing(); 
    int numPointers = event.getPointerCount(); 
    int[] pointerIDs = new int[numPointers]; 
    PointerCoords[] pointerCoords = new PointerCoords[numPointers]; 

    // Extract all pointers from the touch event: 
    for (int i = 0; i < numPointers; i++) { 
     pointerIDs[i] = event.getPointerId(i); 
     pointerCoords[i] = new PointerCoords(); 

     event.getPointerCoords(i, pointerCoords[i]); 
    } // end for 

    // Correct each pointer coordinate set for map rotation: 
    for (int i = 0; i < numPointers; i++) { 
     // x and y end up representing points on the canvas, although they 
     // are derived from points on the screen: 
     float x = pointerCoords[i].x; 
     float y = pointerCoords[i].y; 

     // Get the center of the MapView: 
     int centerX = getWidth()/2; 
     int centerY = getHeight()/2; 

     // Convert to radians 
     float rad = (float) ((degrees * Math.PI)/180f); 
     float s = (float) Math.sin(rad); 
     float c = (float) Math.cos(rad); 

     // Translate point to origin: 
     x -= centerX; 
     y -= centerY; 

     // Apply rotation 
     float tmpX = x * c - y * s; 
     float tmpY = x * s + y * c; 
     x = tmpX; 
     y = tmpY;   

     // Offset the coordinates to compensate for the fact that the 
     // canvas is 1200 by 1200, the phone screen is smaller, and 
     // they don't overlap nicely: 
     x += (600 - (screenWidth/2)) * c - (600 - (screenHeight/2)) * s; 
     y += (600 - (screenWidth/2)) * s + (600 - (screenHeight/2)) * c; 

     // Translate point back: 
     x += centerX; 
     y += centerY; 

     pointerCoords[i].x = x; 
     pointerCoords[i].y = y; 

     // Catlog: 
     if (LOG_ENABLED) Log.i(TAG, "(" + x + ", " + y + ")"); 
    } // end for 

    // Create new event to pass along the modified pointers. 
    // Need API level 9 or higher to make this work! 
    MotionEvent newEvent = MotionEvent.obtain(event.getDownTime(), event 
      .getEventTime(), event.getAction(), event.getPointerCount(), 
      pointerIDs, pointerCoords, event.getMetaState(), event 
        .getXPrecision(), event.getYPrecision(), event 
        .getDeviceId(), event.getEdgeFlags(), 
      event.getSource(), event.getFlags()); 

    // Dispatch the newly modified touch event: 
    return super.dispatchTouchEvent(newEvent); 
} // end dispatchTouchEvent() 

Infine, il trucco per ottenere il corrispondente XML per l'attività mappa per funzionare correttamente è quello di utilizzare un FrameLayout come il genitore a tutti gli altri Elementi della GUI nel mio layout. Questo mi ha permesso di rendere le dimensioni dello mapView notevolmente più grandi delle dimensioni del display sul mio Nexus One (480 per 800). Questa soluzione mi ha permesso anche di nidificare uno RelativeLayout all'interno del mio FrameLayout mentre ancora rispettavo le dimensioni effettive del display del dispositivo quando si utilizza match_parent e parametri simili.

La porzione rilevante del mio layout XML assomiglia a questo:

<?xml version="1.0" encoding="utf-8"?> 

<!--Note that the layout width and height is defined in px and not dip!--> 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:id="@+id/MapViewLayout"> 

<a.relevant.path.RotatingRelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="1200px" 
    android:layout_height="1200px"> 

    <org.osmdroid.views.MapView 
     android:id="@+id/mapview" 
     android:layout_width="1200px" 
     android:layout_height="1200px" 
     android:enabled="true"  
     android:clickable="true"/>   
</a.relevant.path.RotatingRelativeLayout> 

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

    <RelativeLayout 
     android:id="@+id/leftSlideHandleButton" 
     android:layout_width="match_parent" 
     android:layout_height="60dip" 
     android:layout_centerHorizontal="true" 
     android:background="#D0000000"> 

     <Button 
      android:id="@+id/mapZoomOutButton" 
      android:layout_height="wrap_content" 
      android:layout_width="wrap_content" 
      android:background="@drawable/zoom_out_button" 
      android:layout_alignParentLeft="true" 
      android:onClick="zoomOutButton"/> 

     <Button 
      android:id="@+id/mapZoomInButton" 
      android:layout_height="wrap_content" 
      android:layout_width="wrap_content" 
      android:background="@drawable/zoom_in_button" 
      android:layout_alignParentRight="true" 
      android:onClick="zoomInButton"/> 

     <TextView 
      android:id="@+id/headerSpeedText" 
      android:layout_height="wrap_content" 
      android:layout_width="wrap_content" 
      android:textColor="#33B5E5" 
      android:text="Speed: " 
      android:textSize="12sp" 
      android:paddingLeft="15dip" 
      android:layout_toRightOf="@id/mapZoomOutButton"/> 

     <TextView 
      android:id="@+id/headerSpeedReading" 
      android:layout_height="wrap_content" 
      android:layout_width="wrap_content" 
      android:textColor="#33B5E5" 
      android:text="N/A" 
      android:textSize="12sp" 
      android:paddingLeft="27dip" 
      android:layout_toRightOf="@id/headerSpeedText"/> 

     <TextView 
      android:id="@+id/headerBearingText" 
      android:layout_height="wrap_content" 
      android:layout_width="wrap_content" 
      android:textColor="#33B5E5" 
      android:text="Bearing: " 
      android:paddingLeft="15dip" 
      android:textSize="12sp" 
      android:layout_toRightOf="@id/mapZoomOutButton" 
      android:layout_below="@id/headerSpeedText"/> 

     <!-- Et Cetera... --> 

    </RelativeLayout> 
</FrameLayout> 

vorrei sottolineare che questa soluzione non è affatto la soluzione migliore, ma che andava bene per la mia prova di -concept application!

+0

È possibile inviare relativamente eventi di tocco, se la tela viene ruotata in gradi diversi da multipli di 180? Per esempio: 50, 90, 270, 300? – Mani

+0

Sì, qualsiasi angolo di rotazione arbitrario funziona. Sono stato in grado di ottenere letture dal magnetometro del telefono, e quindi utilizzare quelle letture per ruotare la tela in modo che la parte superiore di mapView mostrasse sempre il punto di riferimento che si trovava direttamente di fronte all'utente. Spero che abbia senso! –

Problemi correlati