Android:获取旋转的OSM映射以填充整个屏幕

问题描述:

我正在研究一个应用程序,该应用程序使用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倍。然而,这意味着图像被有效地放大,导致地图瓦片不可接受的像素化。

有谁知道画布的大小被声明在哪里,如果改变画布的大小可能实际上解决了我的问题?

我结束了罗曼盖伊的建议(和我在我的问题中发布的建议一样)。也就是说,我通过扩展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的顶部始终显示直接位于用户面前的地标。希望是有道理的! –