トップMy OpenStreetMap > 森林のレンダリング(高ズーム)

森林のレンダリング(高ズーム)

森林の高ズームレンダリング

地図アプリGISでのzoom 16でのレンダリング結果を下に示す。

ほぼ画面中央の森林を見ると、三つの穴が空いている。 いわゆる穴あきポリゴン(Polygon with holes)である。OSMではマルチポリゴンと呼んでいる。 外側のポリゴンを outer polygon 、内側のポリゴンを inner polygon と呼ぶ。

OSMのデータには、outer polygon が二う以上の relationオブジェクトがあるが、 レンダリングでは、outer polygon 毎に分離する。 このため、レンダリング上のマルチポリゴンは outer polygon が一つで、inner polygon は 二つ以上である。

relation オブジェクトとしては、outer polygon が一つで、inner polygon がないものも あるが、これは単純なポリゴンであり、マルチポリゴンとは呼ばない。

Windows C# の場合、 GDI+ のおかげで、穴あきポリゴンの描画やアイコンパターンによる塗りつぶしなど 多彩な描画機能をサポートしている。地図専用の Mapnik に近い地図が比較的簡単に得られる。

それに比べると、Android Java にはそのような機能は殆どない。

PorterDuff.Mode演算を使えば、ある程度のことはできるかも知れないが、 C# に比べると敷居が高く、自作地図アプリではまだ使用経験がない。

地図アプリGISでは、ワーク用キャンバス(Bitmap) に、まず、 outer polygon を描画する。 その後、別の色で全ての inner polygon を描画する。 それが終わってから、画素単位の処理となる。inner polygon の内部に当たる画素は透明に変える。 これによって、ワーク用ビットマップには穴あきポリゴンが完成する。

森林の場合、葉っぱアイコンで塗りつぶすが、これも画素単位の処理となる。 予め、タイル一杯に葉っぱアイコンで塗りつぶしたデータを用意している。 1画素ずつ森林ポリゴンかどうか判定して(inner polygonに当たる画素は除外される)、 森林画素であれば、パターンデータの該当画素を書き込む。

これをタイルキャンバスに描画する。 これにより、タイルキャンバスに穴あき森林ポリゴンが描画されることになる。

画素単位の処理はなるべく少なくなるように工夫しているため、 描画時間はさほど問題ではないとしても、それなりのプログラムが必要となる。

そのため、マルチポリゴンやパターンによる塗りつぶしについては、 少し、時間をかけて再検討したい。

地図アプリGISでのマルチポリゴンおよびパターン塗りつぶしプログラム

    private static Paint paintWhite = getPaintFill(0xffffffff);

    private void fillMultiPolygon(Canvas canvas, TileToRender tile, Paint paint) {
        if (type != 3) return;    // multipolygonではない
        int[] multi = getMulti();
        boolean no_inner = true;
        for (int n = 1; n < multi.length; n++) {
            if (multi[n] > 0) { no_inner = false; break; }
        }
        if (no_inner) {
            fillPolygon(canvas, tile, paint, bgnData, nodes); // outer polygon
            return;
        }
        Bitmap bmpWork = tile.bmpWork;
        bmpWork.eraseColor(Color.TRANSPARENT);
        Canvas cvWork = tile.cvWork;
        fillPolygon(cvWork, tile, paint, bgnData, multi[0]); // outer polygon

        int offset = bgnData + multi[0] * (slim ? 1 : 2);  // outer polygon のノード数
        for (int n = 1; n < multi.length; n++) {
            fillPolygon(cvWork, tile, paintWhite, offset, multi[n]);
            offset += multi[n] * (slim ? 1 : 2);
        }

        // Pixel 操作部分
        int color = paint.getColor();
        tile.bmpWork.getPixels(tile.pxWork, 0, PX, px0, py0, px1-px0, py1-py0);
        tile.bmpMain.getPixels(tile.pxMain, 0, PX, px0, py0, px1-px0, py1-py0);
        int[] mainPixels = tile.pxMain;
        int[] workPixels = tile.pxWork;

        for (int i = 0; i < py1-py0; i++) {
            int ipy = i * PX;
            int end = ipy + px1-px0;
            for (int j = ipy; j < end; j++) {
                int wp = workPixels[j];
                if (wp < 0 && wp != 0xffffffff) {
                    mainPixels[j] = color;
                }   // inner polygon の画素については無視する
            }
        }
        tile.bmpMain.setPixels(tile.pxMain, 0, PX, px0, py0,px1-px0, py1-py0);

    }


    final void fillPolygon(Canvas canvas, TileToRender tile, Paint paint, int[] patterns) {
        if (patterns == null || patterns.length == 0) {
            fillPolygon(canvas, tile, paint);
            return;
        }
        //tile.bmpWork.setPixels(pxZeros, 0, PX, 0, 0, PX, PX);
        tile.cvWork.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        fillPolygon(tile.cvWork, tile, paint);//bmpWork上にpolygonをcolorで塗りつぶし

        tile.bmpWork.getPixels(tile.pxWork, 0, PX, px0, py0, px1-px0, py1-py0);
        tile.bmpMain.getPixels(tile.pxMain, 0, PX, px0, py0, px1-px0, py1-py0);
        int[] mainPixels = tile.pxMain;
        int[] workPixels = tile.pxWork;

        int color = paint.getColor();
        float ac = (((color >> 24) & 0xff) / 255.0f);  // alpha
        int rc = (color & 0x00ff0000) >> 16;
        int gc = (color & 0x0000ff00) >> 8;
        int bc = color & 0x000000ff;
        for (int i = 0; i < py1-py0; i++) {
            int ipy = i * PX;
            int off = (py0+i)*PX + px0;
            for (int j = 0; j < px1-px0; j++) {
                if (workPixels[ipy + j] == color) {
                    int rp, gp, bp;
                    int pattern = patterns[off + j];
                    float ap = (((pattern >> 24) & 0xff) / 255.0f);  // alpha
                    int cm = mainPixels[ipy + j];
                    float am = (((cm >> 24) & 0xff) / 255.0f);  // alpha
                    int rm = (cm & 0x00ff0000) >> 16;
                    int gm = (cm & 0x0000ff00) >> 8;
                    int bm = cm & 0x000000ff;
                    if (ap == 0) {
                        ap = ac; rp = rc; gp = gc; bp = bc;
                    } else {
                        rp = (pattern & 0x00ff0000) >> 16;
                        gp = (pattern & 0x0000ff00) >> 8;
                        bp =  pattern & 0x000000ff;
                    }
                    int r = (int) (rm * (1-ap) + rp * ap);
                    int g = (int) (gm * (1-ap) + gp * ap);
                    int b = (int) (bm * (1-ap) + bp * ap);
                    int a = (int)((ap + (1 - ap) * am) * 255);
                    mainPixels[ipy + j] = (a << 24) | (r << 16) | (g << 8) | b;
                }
            }
        }
        tile.bmpMain.setPixels(tile.pxMain, 0, PX, px0, py0,px1-px0, py1-py0);
    }

画素単位の処理は実行時間もさることながら、プログラムが嵩む。