地図アプリ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に当たる画素は除外される)、 森林画素であれば、パターンデータの該当画素を書き込む。
これをタイルキャンバスに描画する。 これにより、タイルキャンバスに穴あき森林ポリゴンが描画されることになる。
画素単位の処理はなるべく少なくなるように工夫しているため、 描画時間はさほど問題ではないとしても、それなりのプログラムが必要となる。
そのため、マルチポリゴンやパターンによる塗りつぶしについては、 少し、時間をかけて再検討したい。
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); }
画素単位の処理は実行時間もさることながら、プログラムが嵩む。