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

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

Landcoverレイヤの低ズームレンダリング

陸地のレンダリングに目途が付いたので、本格的な OSM地図のレンダリングに着手する。

まずは、地図アプリGISの renderLowメソッドをスタート点とする。 森林(natural=wood, landuse=forest)だけではなく、低木林(natural=scrub)、砂漠・砂浜(natural=sand)、 湿地(natural=wetland)、水域(natural=water, waterway=riverbank)、遊水池(landuse=basin) がレンダリング対象である。

void renderLow(Canvas cv, TileToRender tile, OSM[] osms, int fromIndex, int toIndex) {
    for (int n = fromIndex; n < toIndex; n++) {
        OSM osm = osms[n];
        if (osm.type < 2 || (osm.tag_flags&bitLandcoverLow) == 0) continue;

        if ((osm.tag_flags & bitNatural) != 0) {
            Tag.Val natural = osm.getVal(Tag.Key.natural);
            switch (natural) {
                case wood:  osm.fillPolygon(cv, tile, paintWood); break;
                case scrub: osm.fillPolygon(cv, tile, paintScrub); break;
                case sand:  osm.fillPolygon(cv, tile, paintSand); break;
                case wetland: osm.fillPolygon(cv, tile, paintWetland); break;
                case water: osm.fillPolygon(cv, tile, paintWater); break;
            }
        } else if ((osm.tag_flags & bitLanduse) != 0) {
            Tag.Val landuse = osm.getVal(Tag.Key.landuse);
            if (landuse == Tag.Val.forest) {
                osm.fillPolygon(cv, tile, paintWood);
            } else if (landuse == Tag.Val.basin) {
                osm.fillPolygon(cv, tile, paintWater);
            }
        } else if (osm.getVal(Tag.Key.waterway) == Tag.Val.riverbank) {
            osm.fillPolygon(cv, tile, paintWater);
        }
    }
}

実行結果

全体としては、背景色(海)塗りつぶし、陸地のレンダリング、森林・水域等のレンダリングとなる。 zoom によって、レンダリングに使うファイルを切り替えるが、zoom の範囲は評価後に確定する。 下の数値はテスト段階の値であり、最終的には変更する可能性が高い。

上のプログラムではハイズームも renderLow メソッドでレンダリング(描画)しているが、 いずれハイズームは別のメソッドを使う。

Canvas cv = tile.canvas;
TileToRender ttr = tileToRender;
ttr.set(zoom, x, y);
if (false && isLandTile(zoom, x, y)) {
    cv.drawRect(0, 0, PX, PX, paintLand);
} else {
    cv.drawRect(0, 0, PX, PX, paintWater);
    ttr.ixRecord = 0; 
    if (zoom <= 9) {
        Block.getOSMs(1, (DIR + "lands_low"), ttr, osms);
        blockLandL = Block.blocks[ttr.ixBlock];
    } else {
        Block.getOSMs(1, (DIR + "lands_split"), ttr, osms);
        blockLandL = Block.blocks[ttr.ixBlock];
        Block.getOSMs(5, DIR + "lands_split", ttr, osms);
        blockLandH = Block.blocks[ttr.ixBlock];
    }
    for (int n = 0; n < ttr.ixRecord; n++) {
        osms[n].fillLandPolygon(cv, ttr, paintLand);
    }
}
ttr.ixRecord = 0;
if (zoom <= 7) {
    Block.getOSMs(3, DIR + src + "_low", ttr, osms);
    blockOsmL = Block.blocks[ttr.ixBlock];
} else if (zoom <= 12) {
    Block.getOSMs(7, DIR + src + "_mid", ttr, osms);
    blockOsmL = Block.blocks[ttr.ixBlock];
} else {
    Block.getOSMs(6, DIR + src + "_norm", ttr, osms);
    blockOsmL = Block.blocks[ttr.ixBlock];
    Block.getOSMs(12, DIR + src + "_norm", ttr, osms);
    blockOsmH = Block.blocks[ttr.ixBlock];
}

renderLandcover.renderLow(cv, ttr, osms, 0, ttr.ixRecord);

zoom 8での実行結果を下に示す。

レンダリングメソッドの引数

レンダリングメソッドの引数は分かりやすさを重視して決めている。 極端な場合、TileToRender に他の引数も含ませて、引数を一つにすることもできる。 しかし、それでは、レンダリングメソッドの働きが引数から想像できない。

現地図アプリGISでは、fromIndex は常に 0 である。 恒久的に 0 であるならば、fromIndex、toIndex を廃止して、length に変えた方がよい。 osms配列には当然長さがあるが、共用しているため、 配列全体のデータが有効というわけではないので、有効範囲を示す引数が必要である。

レンダリングは多くのレイヤに分かれている。 レコードは ポイント、ライン、ポリゴンに分かれている。 Mapnik によるレンダリングでは、レコードタイプ毎にテーブルが分かれている。

一つのレイヤでポイントレコードとポリゴンレコードがレンダリングの対象となることがあるため、 マイOSM地図では、データを一まとめにしている。しかし、データタイプでソートしておけば、 レイヤによっては、全レコードを対象としなくても済むことがあり、 fromIndex、toIndex による指定が有効に働く。

結論としては、現状の引数を維持する。

検討課題

冴えないのは blockOsmL = Block.blocks[ttr.ixBlock]; である。 OSMバイナリレコードブロックはキャッシュしており、適宜、スワップする。 マルチスレッドでレンダリングしているため、使用中のスワップを禁止する手段が必要である。 現在は、参照カウンタを用いている。getOSMの実行で参照が決まり、参照カウンタをインクリメントする。 全てのレイヤのレンダリングが終わったとき、この参照カウンタをデクリメントする。

このため、現在のタイルのレンダリングに使用しているブロックを blockOsmL、blockOsmH などに記憶している。

タイル座標 zoom、x、y の値から使用していたブロックは分かるので、 blockOsmL、blockOsmH などに記憶しなくても、 このブロックの参照カウンタをデクリメントできる。