osmdroid(OpenStreetMapのAndroid向けライブラリ)のMapViewを鳥瞰図に対応させる

# 概要
osmdroidのMapViewでは真上からの表示にしか対応してません。
そこで、MapViewを改造して鳥瞰図表示に対応しました。(マリオカートのような疑似3Dです)  
また地図の回転にも対応しています。
あくまで個人の備忘録としてまとめているので至らない点があったらすみません。

# 注意
計算負荷や各種リソースなどは考慮してないのでそこは注意！

# 完成イメージ
## 真上からの表示
![Screenshot_20240427-181419.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55941/e77f5ac8-fb87-5205-420d-e787df5ca853.png)
## 鳥瞰図としての表示
![Screenshot_20240427-181425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55941/3f076437-8294-ccad-d297-2d14b0fe5ae5.png)


# コード
```java:BirdEyeMapView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;

import org.osmdroid.api.IMapView;
import org.osmdroid.config.Configuration;
import org.osmdroid.tileprovider.MapTileProviderBase;
import org.osmdroid.views.MapView;

public class BirdEyeMapView extends MapView {

    private boolean birdView = true;

    public BirdEyeMapView(Context context, MapTileProviderBase tileProvider, Handler tileRequestCompleteHandler, AttributeSet attrs) {
        super(context, tileProvider, tileRequestCompleteHandler, attrs);
    }

    public BirdEyeMapView(Context context, MapTileProviderBase tileProvider, Handler tileRequestCompleteHandler, AttributeSet attrs, boolean hardwareAccelerated) {
        super(context, tileProvider, tileRequestCompleteHandler, attrs, hardwareAccelerated);
    }

    public BirdEyeMapView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BirdEyeMapView(Context context) {
        super(context);
    }

    public BirdEyeMapView(Context context, MapTileProviderBase aTileProvider) {
        super(context, aTileProvider);
    }

    public BirdEyeMapView(Context context, MapTileProviderBase aTileProvider, Handler tileRequestCompleteHandler) {
        super(context, aTileProvider, tileRequestCompleteHandler);
    }

    public void setBirdView(boolean birdView) {
        this.birdView = birdView;
    }


    @Override
    protected void dispatchDraw(Canvas c) {
        final long startMs = System.currentTimeMillis();

        this.setProjection(null);

        if (birdView) {
            Matrix perspectiveMatrix = new Matrix();
            perspectiveMatrix.setPolyToPoly(new float[]{
                            0, 0,   // Top-left
                            getWidth(), 0,   // Top-right
                            getWidth() * 0.7f, getHeight()*1.1f,  // Bottom-right
                            getWidth() * 0.3f, getHeight()*1.1f}, // Bottom-left
                    0, new float[]{
                            0, 0,   // Top-left
                            getWidth(), 0,   // Top-right
                            getWidth(), getHeight(),  // Bottom-right
                            0, getHeight()}, // Bottom-left
                    0, 4);
            c.concat(perspectiveMatrix);
        }



        /* Draw background */
        try {
            /* Draw all Overlays. */
            this.getOverlayManager().onDraw(c, this);
            if (this.getZoomController() != null) {
                this.getZoomController().draw(c);
            }
            super.dispatchDraw(c);
        } catch (Exception ex) {
            //for edit mode
            Log.e(IMapView.LOGTAG, "error dispatchDraw, probably in edit mode", ex);
        }
        if (Configuration.getInstance().isDebugMapView()) {
            final long endMs = System.currentTimeMillis();
            Log.d(IMapView.LOGTAG, "Rendering overall: " + (endMs - startMs) + "ms");
        }

    }

}
```

# 解説
dispatchDrawメソッドにてMatrixを使い台形に変換しています。
```java
            Matrix perspectiveMatrix = new Matrix();
            perspectiveMatrix.setPolyToPoly(new float[]{
                            0, 0,   // Top-left
                            getWidth(), 0,   // Top-right
                            getWidth() * 0.7f, getHeight()*1.1f,  // Bottom-right
                            getWidth() * 0.3f, getHeight()*1.1f}, // Bottom-left
                    0, new float[]{
                            0, 0,   // Top-left
                            getWidth(), 0,   // Top-right
                            getWidth(), getHeight(),  // Bottom-right
                            0, getHeight()}, // Bottom-left
                    0, 4);
            c.concat(perspectiveMatrix);
```
この部分になります。  
地図の回転にも対応しています。
```java
mapView.setMapOrientation(mLastDriveDirection, true);
```