Android: получение повернутой карты OSM для заполнения всего экрана - PullRequest
9 голосов
/ 30 августа 2011

Я работаю над приложением, которое использует API OpenStreetMap (OSM) для отображения различных точек интереса на автономной карте, состоящей из статических плиток.

Одной из функций, которые я сейчас реализую, является поворот карты в соответствии с ориентацией, определенной телефоном (через GPS). Мне удалось реализовать фактическое вращение без особых усилий, но, поскольку мой код вращает весь холст - возможно, довольно наивный подход - у меня теперь есть пустые углы на экране, где не загружаются новые плитки, чтобы компенсировать факт что повернутые плитки больше не заполняют эти пиксели.

После небольшого поиска в Google я нашел несколько предложений относительно того, как я мог бы решить эту проблему, но пока не повезло.

В одном из Мистер. Посты Ромена Гая , он упоминает следующее:

Я делал это в прошлом, и это требует создания кастома ViewGroup, которая вращает Canvas в методе dispatchDraw (). Вы также необходимо увеличить размер MapView (так, чтобы он рисовал достаточно пикселей при повороте.) Вам также нужно будет повернуть сенсорные события в dispatchTouchEvent (). Или, если вы используете Android 3.0, вы можете просто позвонить theMapView.rotate ()

Следуя его советам относительно поворота карты, я реализовал dispatchDraw () и dispatchTouchEvent (), как и предлагалось, но у меня возникли проблемы с частью, где он упоминает, что мне нужно увеличить размер MapView?

Это то, что я делаю в XML-файле, как предложено в этой теме ?

Или я могу как-то переопределить функцию onMeasure () в моем подклассовом классе RelativeLayout, который обрабатывает вращение моей карты?

Предложения и советы приветствуются.

UPDATE:

В попытке найти приемлемое решение этой проблемы я попытался изменить размер холста. Мысль была о том, что с размером холста, который больше, чем фактический размер экрана, я мог бы полностью убрать пустые углы с экрана. К сожалению, здесь нет реальной опции canvas.size (); лучшее, что я нашел, было canvas.scale ().

Используя canvas.scale (), я смог увеличить масштаб холста в 2 раза как по горизонтали, так и по вертикали. Это означает, однако, что изображение эффективно увеличено, что приводит к недопустимому пикселизации элементов мозаики карты.

Кто-нибудь знает, где объявляется размер холста и может ли изменение размера холста действительно решить мою проблему?

1 Ответ

4 голосов
/ 27 сентября 2012

В итоге я последовал совету Ромена Гая (тот же самый, который я написал в своем вопросе).То есть я создал свой собственный ViewGroup, расширив RelativeLayout, а затем увеличил размер моего mapView, чтобы охватить весь экран.Расширение RelativeLayout ViewGroup было необходимо, чтобы я мог переопределить функции dispatchDraw(...), onMeasure(...), а также dispatchTouchEvent(...), чтобы включить нужные функции поворота карты для моего приложения.

dispatchDraw(...) функция по существу перехватывает вызовы функции onDraw(...), выполняет несколько конкретных манипуляций с входными данными этой функции и затем освобождает ее для обработки.В нашем случае мы хотим повернуть холст mapView, прежде чем он перейдет к действительной функции onDraw(...).Вот почему нам нужно переопределить эту функцию.

В частности, функция dispatchDraw(...) принимает в качестве входных данных объект холста, который (в данном случае) представляет объект OSM mapView (как определено в XML-файле ниже).Если к холсту должен быть применен поворот, нам нужно найти центр карты, перевести (т.е. переместить) карту так, чтобы центр карты находился в начале системы координат, повернул карту вокругначало системы координат, а затем, наконец, мы хотим отправить этот измененный холст на следующий этап конвейера рендеринга.

Мой код для этого ниже;обратите внимание, что Manager - это мое собственное одноэлементное создание, которое не будет существовать в вашей реализации, если вы сами не напишите его!

    /**
 * @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(...), потому что любое вращение OSMmapView холст заканчивается вращением не только графического представления карты, но и всего остального, связанного с этим действием (это происходит как побочный эффект моей конкретной реализации);то есть, координаты события касания остаются относительно холста 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 в качестве родителя для всехдругие элементы графического интерфейса в моем макете.Это позволило мне сделать размеры mapView значительно большими, чем размеры дисплея на моем Nexus One (480 на 800).Это решение также позволило мне вложить RelativeLayout в мой FrameLayout, сохраняя при этом фактические размеры дисплея устройства при использовании 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>

Я хотел бы отметить, что это решение ни в коем случае не является лучшим решением, но оно отлично работает для моего приложения для проверки концепции!

...