2011-08-29 17 views
8

Estoy trabajando en una aplicación que usa la API OpenStreetMap (OSM) para mostrar varios puntos de interés en un mapa sin conexión, compuesto por mosaicos estáticos.Android: Obtener un mapa OSM girado para llenar toda la pantalla

Una de las características que actualmente estoy implementando es que el mapa gire de acuerdo con el rumbo determinado por el teléfono (a través del GPS). Pude implementar la rotación real sin demasiado esfuerzo, pero como mi código rota todo el lienzo, quizás un enfoque bastante ingenuo, ahora tengo esquinas en blanco en la pantalla donde no se cargan nuevos mosaicos para compensar el hecho que las fichas giradas ya no llenan estos píxeles.

Después de googlear un poco, encontré algunas sugerencias sobre cómo podría resolver este problema, pero hasta ahora no tuve suerte.

En uno de Mr. Romain Guy's posts, menciona lo siguiente:

he hecho esto en el pasado y que se requiere para crear un ViewGroup a medida que gira la lona en el dispatchDraw método(). Usted también necesita aumentar el tamaño de MapView (para que dibuje suficientes píxeles al girar). También deberá girar los eventos táctiles en dispatchTouchEvent(). O si utiliza Android 3.0 sólo tiene que llamar theMapView.rotate()

Tomando su consejo en el mapa rotación, he implementado dispatchDraw() y dispatchTouchEvent() como sugirieron, pero estoy teniendo problemas con la parte donde él menciona que necesito aumentar el tamaño de MapView?

¿Esto es algo que hago en el archivo XML, como se sugiere en this thread?

O, ¿puedo anular de alguna manera la función onMeasure() en mi clase RelativeLayout subclasificada que maneja la rotación de mi mapa?

Sugerencias y consejos son bienvenidos.

ACTUALIZACIÓN:

En un intento de encontrar una solución aceptable a este problema, he intentado cambiar el tamaño del lienzo. El pensamiento era que con un tamaño de lienzo que es más grande que el tamaño de la pantalla real, podría mover las esquinas en blanco de la pantalla por completo. Lamentablemente, no parece haber una opción canvas.size() real; lo mejor que encontré fue canvas.scale().

Usando canvas.scale(), pude aumentar la escala del lienzo por un factor de 2 en las dimensiones horizontal y vertical. Esto significa, sin embargo, que la imagen se acerca de manera efectiva, lo que causa una pixelación inaceptable en los mosaicos del mapa.

¿Alguien sabe dónde se declara el tamaño del lienzo, y si cambiar el tamaño del lienzo en realidad podría resolver mi problema?

Respuesta

3

Terminé siguiendo los consejos de Romain Guy (el mismo consejo que publiqué en mi pregunta). Es decir, creé mi propio ViewGroup personalizado extendiendo el RelativeLayout, y luego aumenté el tamaño de mi mapView para brindar cobertura de toda la pantalla.Extendiendo el RelativeLayout ViewGroup fue necesario para poder anular el dispatchDraw(...), el onMeasure(...), así como las funciones dispatchTouchEvent(...) para habilitar las funciones de rotación del mapa para mi aplicación.

La función dispatchDraw(...) intercepta esencialmente las llamadas a la función onDraw(...), realiza algunas manipulaciones específicas en la entrada de esa función y luego la libera para su procesamiento. En nuestro caso, querremos rotar el lienzo mapView antes de que llegue a la función real onDraw(...). Es por eso que necesitaremos anular esta función.

Específicamente, la función dispatchDraw(...) toma como entrada un objeto canvas, que (en este caso) representa el objeto OSM mapView (como se define en el archivo XML a continuación). Si se va a aplicar una rotación al lienzo, querremos ubicar el centro del mapa, traducir (es decir, mover) el mapa de modo que el centro del mapa se encuentre sobre el origen del sistema de coordenadas, girar el mapa sobre el origen del sistema de coordenadas, y luego, finalmente, querremos enviar este lienzo modificado a la siguiente etapa en la tubería de renderizado.

Mi código para esto está a continuación; tenga en cuenta que Manager es mi propia creación de singleton que no existirá en su implementación a menos que escriba uno usted mismo.

/** 
* @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() 

A continuación hay que anular dispatchTouchEvent(...), ya que cualquier rotación del lienzo OSM mapView termina la rotación no sólo la representación gráfica del mapa, sino también todo lo relacionado con esa actividad (esto ocurre como un lado efecto de mi implementación específica); es decir, las coordenadas del evento táctil permanecen relativas al lienzo mapView después de girarse y no en relación con el teléfono real. Por ejemplo, si imaginamos que el lienzo se gira 180 grados, entonces si el usuario intenta desplazar el mapa hacia la izquierda, se moverá hacia el mapa a la derecha, ¡ya que todo está al revés!

En el código, es posible corregir este problema de la siguiente manera:

/** 
* @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() 

Por último, el truco para conseguir el XML correspondiente para la actividad mapa para que funcione correctamente es utilizar un FrameLayout como el padre de todos los demás Elementos GUI en mi diseño. Esto me permitió hacer las dimensiones mapView considerablemente más grandes que las dimensiones de la pantalla en mi Nexus One (480 por 800). Esta solución también me permitió anidar un RelativeLayout dentro de mi FrameLayout respetando las dimensiones de visualización reales del dispositivo cuando usas match_parent y parámetros similares.

La parte pertinente de mi diseño XML tiene el siguiente aspecto:

<?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> 

me gustaría remarcar que esta solución no es en absoluto la mejor solución, pero que funcionó bien para mi prueba de ¡aplicación de concepto!

+0

¿Pudo despachar eventos táctiles de forma relativa, si el lienzo se rota en un grado distinto a múltiplos de 180? Por ejemplo: 50, 90, 270, 300? – Mani

+0

Sí, cualquier ángulo de rotación arbitrario funciona. Pude obtener lecturas del magnetómetro del teléfono y luego usar esas lecturas para rotar el lienzo de modo que la parte superior del mapa Vista siempre mostrara el hito que estaba directamente frente al usuario. Espero que tenga sentido! –

Cuestiones relacionadas