2011-08-29 103 views
8

我正在研究一個應用程序,該應用程序使用OpenStreetMap(OSM)API顯示由靜態拼貼組成的離線地圖上的各種興趣點。Android:獲取旋轉的OSM映射以填充整個屏幕

我目前正在執行的功能之一是根據手機確定的方位(通過GPS)旋轉地圖。我能夠毫不費力地實現實際的旋轉,但是由於我的代碼旋轉了整個畫布 - 也許是一種相當天真的方法 - 我現在在屏幕上沒有加載新的瓷磚來補償事實的空白邊角旋轉的瓷磚不再填充這些像素。

經過一番Google搜索後,我發現了一些關於如何解決這個問題的建議,但到目前爲止沒有運氣。

Mr. Romain Guy's posts之一,他提到了以下幾點:

我在過去做到了這一點,它需要創建一個自定義的ViewGroup 是旋轉畫布在dispatchDraw()方法。您還需要增加MapView的大小(以便在旋轉時繪製足夠的 像素)。您還需要旋轉dispatchTouchEvent()中的觸摸事件 。或者,如果您使用Android 3.0,你可以簡單地調用 theMapView.rotate()

在地圖上旋轉以他的意見,我已經實現dispatchDraw()和dispatchTouchEvent()的建議,但我在使用的麻煩部分他提到我需要增加MapView的大小?

這是我在XML文件中做的事情,就像在this thread中建議的那樣?

或者,我可以以某種方式覆蓋我的subclassed RelativeLayout類中處理我的地圖旋轉的onMeasure()函數嗎?

建議和提示是最受歡迎的。

UPDATE:

在試圖找到這個問題的可接受的解決方案,我試圖改變畫布的大小。我的想法是,在比實際屏幕尺寸大的畫布尺寸下,我可能會將空白的角落完全移出屏幕。不幸的是,似乎沒有實際的canvas.size()選項;我發現最好的是canvas.scale()。

使用canvas.scale(),我能夠在水平和垂直維度上將畫布的比例增加2倍。然而,這意味着圖像被有效地放大,導致地圖瓦片不可接受的像素化。

有誰知道畫布的大小被聲明在哪裏,如果改變畫布的大小可能實際上解決了我的問題?

回答

3

我結束了羅曼蓋伊的建議(和我在我的問題中發佈的建議一樣)。也就是說,我通過擴展RelativeLayout創建了我自己的自定義ViewGroup,然後我增加了我的mapView的大小以提供整個屏幕的覆蓋範圍。擴展RelativeLayout ViewGroup是必要的,以便我可以覆蓋dispatchDraw(...),onMeasure(...)以及dispatchTouchEvent(...)函數,以便爲我的應用程序啓用所需的地圖旋轉功能。

dispatchDraw(...)函數實質上截獲對onDraw(...)函數的調用,對該函數的輸入執行一些特定的操作,然後釋放它以進行處理。在我們的例子中,我們希望旋轉mapView畫布,然後才能轉到實際的onDraw(...)函數。這就是爲什麼我們需要重寫這個函數。

具體而言,dispatchDraw(...)函數將用作輸入的畫布對象(在本例中)表示OSM mapView對象(如下面的XML文件中所定義的)。如果要在畫布上應用旋轉,我們需要定位地圖的中心,平移(即移動)地圖,以便地圖的中心位於座標系的原點上,旋轉地圖座標系的原點,然後,最後,我們希望將這個修改後的畫布分配到渲染管線中的下一個階段。

我的代碼如下;請注意0​​是我自己的單例創建,除非您自己寫一個,否則在您的實現中不存在!

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

下一步,我們將需要重寫dispatchTouchEvent(...),因爲OSM mapView畫布的任何旋轉結束了轉動,不僅地圖的圖形表示,而且一切與此活動相關(發生這種情況的側我的具體實施效果);也就是說,觸摸事件座標在旋轉後保持相對於mapView畫布而不是相對於實際的手機。例如,如果我們想象畫布旋轉了180度,那麼如果用戶嘗試將地圖平移到左側,它將移動到右側的地圖,因爲所有內容都是顛倒的!

在代碼中,你可以使用下列糾正這個問題:

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

最後的伎倆來獲得相應的XML的地圖活動的正常工作是利用FrameLayout作爲父所有其他我的佈局中的GUI元素。這使我可以使mapView的尺寸遠遠大於我的Nexus One上的顯示器尺寸(480 x 800)。該解決方案還允許我在我的FrameLayout中嵌套RelativeLayout,同時在使用match_parent和類似參數時仍遵守設備的實際顯示尺寸。

我的XML佈局的相關部分看起來是這樣的:

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

我想此話,這種解決方案絕不是最佳的解決方案,但它制定了罰款爲我證明的 - 概念應用程序!

+0

如果畫布旋轉到180度的倍數以外,您是否可以相對調度觸摸事件? 例如:50,90,270,300? – Mani

+0

是的,任何任意旋轉角度都適用。我能夠從手機的磁力計讀取數據,然後使用這些讀數旋轉畫布,以便mapView的頂部始終顯示直接位於用戶面前的地標。希望是有道理的! –