トップ地図アプリMap > 地図アプリMap4開発記録

地図アプリMap4開発記録

2024.3.28 Paint#getTextBoundsの返す値

getTextBoundsの返す幅と高さを下に示す。「一」の高さはわずか2である。 回転、移動を考えると、横幅は文字により変動させるとしても、縦幅はほぼ同じにすべきであろう。

数十文字調べた範囲では幅、高さ 19, 19 が最大であった。

15, 2 一
15, 14 丁
11, 15 目
19, 19 横

とりあえず、「一」は上下のマージンにより描画されるようにした。 根本的には、Rect の幅と高さだけではなく、left、top を考量して、それにマージンを加えるべきであろう。

2024.3.27 ポリゴン描画で paint が null である

次のようにしているので null はありうる。

bmpIntermittentWaterが null だったのが引き金になっている。

  osm.fillPolygon(canvas, r, fIntermittent ? null : paintWater,
        fIntermittent ? bmpIntermittentWater : null);

画素数が3倍のときに下のようなケアレスミスがあった。「!」は要らない。

    if (Files.exists(Paths.get(path3))) {
    //if (!Files.exists(Paths.get(path3))) {

スマホでも境界線の「一」は消えていることが分かった。 水平方向の地名では「一丁目」はきちんと描画されるので、何か対策はあるだろう。

2024.3.27 Androidタブレットでは境界線に「一丁目」の「一」が描画されない

「二」、「三」は描画される。「一」だけが全く描画されない。

高精細なスマホでも同じかどうか確かめる。 USBケーブル接続でファイルを転送した。

2024.3.27 選択したバス路線は赤色で表示されない

仕様変更(簡略化)後の処理が未完成であった。Map3とは抜本的に仕組みを変えている。 道路などラインレコードおよびバス停ポイントレコードには該当する路線番号配列を持たせており、 空間検索ではその値を OSMインスタンスの bus_route_ids配列にセットしている。

バスが走るかどうかは bus_route_ids配列の有無だけで決める。バス路線の場合、細い青線を描画する。

バス路線表示モードにすると、その時点の画面内に含まれるバス路線だけが選択され mapBusRoutes に登録される。同時に路線選択ボタンが画面下に表示される。 mpaBusRoutesに登録された路線は太い青線になる。選択ボタンにより路線を選択すると、 その路線は赤い太線描画となる。

バス路線一覧にある場合 BusRoute br = mapBusRoutes.get(id) で BusRouteインスタンス br が 取り出せる。選択されている場合 br.selected が true である。

修正した。しかし、いずれ、地の地図の描画とアイコン、店名、バス路線などのレンダリングは分離する。 地の地図のタイル画像はストレージに保存して、再利用する。 アイコン、店名、バス路線などは選択でレンダリングが変わる。このとき、バス路線のレンダリングも再考する。

2024.3.27 ディレクトリ名、ファイル名変更

OSMバイナリレコードファイルについては既に japan と kanto では区別しているが、 最近導入したタイル画像の保存ディレクトリ名は区別していなかった。 osm384 とか osm768 など(数値はタイルの画素数)は japan384、japan768 あるいは kanto384、kanto768 などに変更する。

プログラム上の修正は "osm" としていたところを tile.src に変更するだけである。

バス路線ID(バス路線relationのosm_id)、バス路線番号、バス路線名レコードファイル bus_route.tsv は bus_route_japan.tsv、bus_route_kanto.tsv に変更する。

現在のプログラムは初期化処理で bus_route.tsv を読み込み、それを使用している。 アプリ起動後の kanto から japan への切り替えなどに対応していない。 ファイルサイズは japan でも 850KB に過ぎないので、バス路線図を表示するときに読込むように変更した。

読み込んだ時、japan とか kanto は記録しておき、連続的に同じデータを使うときは、 ファイル読み込みは初回だけとした。

2024.3.26 japan.osmデータを更新する

Map4に一区切りがついた。ここで japan.osm、kanto.osm を久しぶりに更新した。

ファイルサイズは前回より1割ほど増加した。隣の市では建物のマッピングが進んだ。 我が家の近辺では区の下の字の境界線も新たに登録されていた。 地方はチェックした範囲では大きな変化は感じられなかった。

2024.3.26 バス路線が描画されていない[解決]

バス路線が描画されていないことに気づいた。多分、何かの修正で消えたのであろう。

Paintインスタンスの取得メソッドを変更したときに描画処理をコメントアウトしたままになっていた。

2024.3.26 タイルをまたがる描画について

道路名などに限らず、店名、地名、アイコンなどあらゆる描画がタイルをまたがることがある。 タイル単位のレンダリング(描画)であるから、一方のタイルでは描画され、 他方のタイルでは描画されないことが必ず存在する。 タイル領域にマージンを持たせることにより、不完全な描画は大幅に軽減するが、皆無にはならない。

例えば地名描画の場合、事前にタイル境界のない地図で仮想的に描画して、 描画する地名を確定しておけば、完璧な描画が可能である。

道路名などは何度も繰り返し描画するため、タイルをまたがる描画を止めてもよい。 しかし、Matrix操作で描画する場合、描画領域を正確に把握することは簡単ではない。 時間がかかりすぎるのも問題である。

現時点では、アイコン、文字列のタイルをまたがる描画はまだ完璧なものではない。

地名についても、一時は完璧を期したが、今はプログラムの簡単さから マージンだけで対応しているため、ごくわずかであるが、地名の描画が不完全なこともありえるであろう。

無理はせず、簡単な方法で少しずつ、不完全な描画を減らしていきたい。

2024.3.25 線分と線分の角度が30度を超えるときは描画を避けた[Map420]

2024.3.25 橋など2ノードで短い場合中央に描画する

赤字部分の追加で大半の橋名は中央に描画されることを確認した。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, float dy) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        if (num_nodes == 2) {
            float width = getWidth(chrs);
            float dist = (float)distance(pnts[0], pnts[1], pnts[2], pnts[3]);
            if (dist > width && dist < width*5) {
                r.offset = (dist - width) / 2;
            }
        }
        Bitmap[] reversed = reverse(chrs);
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, dy, 0, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了(これ以上描画できない)
            } else if (rtn == 1) {
                r.offset += Map.PX * 0.05f; // 再試行 スペース(狭い)を空ける
                // r.ixNodeは更新されていない。
            } else {
                Val boundary = getVal(Key.boundary);
                boolean fAdmin = (boundary == Val.administrative);
                int addDegree = fAdmin ? 0 : rtn;
                drawBitmapMulti(canvas, r, addDegree==0 ? chrs : reversed,
                                dy, addDegree, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
                // r.ixNodeが更新されている。offset は ixNode の先頭から。
            }
        }
    }

2024.3.25 文字を読みやすいように必要に応じて 180度回転する

道路名等の描画に関して、恐らく最後と思われる課題に取り組む。

その他の課題として、細かい調整は残っている。(1)線分の角度が大きく変わる所には描画しない。 (2)文字列レコードが長い場合、文字列は繰り返し描画するので、タイルをまたがる所は避ける。 (3)橋など、一度しか描けない場合、なるべく、中間に描くなど。

ほほ水平の文字が上下が逆に表示されることがある。このような場合、 個々の文字を180度回転させる。同時に文字列の後ろから順に描くように変更する。

一方通行を表す矢印(→)も文字である。この矢印に限り、180度回転させてはならない。

これまでの地図アプリでは次のようにしている。

チェック段階では複数の文字の角度の平均値を算出して、この平均値により、0、-180、+180 を決める。 したがって、チェックでの戻り値は現在の 0, 1, 2 を変更する必要がある (とりあえず 0, -180, 180, 1, 2 でいいだろう)。 また、この角度はメソッドの引数(addDegree)として追加する必要がある。 一方通行の矢印の場合はこの回転度数は無条件に 0 とする。

-180 と +180 よって、何か変える必要あるかどうかはやってみないと分からない。

  if (!text.equals("→") && !text.equals("←")) {
      if (deg > 90 || deg < -90) {
          if (deg > 90) deg -= 180;
          else deg += 180;
      }   // deg は -90 ~ 90
  }

次のようにすることにより、道路名などが見やすくなった。 境界線の内側に描く都道府県名についてはこのまま回転させると、境界線の内側でなくなってしまう。 このため、当面は境界線に文字の下が接する形での描画とする。 Matrixが自由自在に使うこなせるようになったとき再トライしたい。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, float dy) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        Bitmap[] reversed = reverse(chrs);
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, dy, 0, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了
            } else if (rtn == 1) {
                r.offset += Map.PX * 0.05f; // 再試行 スペース(狭い)を空ける
            } else {
                Val boundary = getVal(Key.boundary);
                boolean fAdmin = (boundary == Val.administrative);
                int addDegree = fAdmin ? 0 : rtn;
                drawBitmapMulti(canvas, r, addDegree==0 ? chrs : reversed, dy, addDegree, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
            }
        }
    }

    static Bitmap[] reverse(Bitmap[] v) {
        Bitmap[] reversed = new Bitmap[v.length];
        for (int i = 0; i < v.length; i++) {
            reversed[v.length - i - 1] = v[i];
        }
        return reversed;
    }

2024.3.25 都道府県境界線の描画

通常の車道や川名はラインの中心に描画するが、都道府県境界線の場合、境界線の内側に都道府県名を描画する。

Matrix操作でラインとは垂直方向にずらせるはずであるが、慣れていないので試行錯誤となる。

プログラム上は dx = (xe - xb)/num、dy = (ye - yb)/num が線分方向で、 垂直方向は dx と dy が入れ替わるはず、どちらかの符号が変わったかも知れない。

線分単位の処理で、線分(xb,yb)-(xe,ye) を垂直方向の平行移動させてから、 現在のプログラムを実行すればよいが、現在の平行移動プログラムでは三角関数を使っているので時間がかかる。

都道府県境界線レコードのノード数は膨大なため、Matrixを使う方が効率的であろう。

現在のMatrix操作は次の2行である。これは水平方向の w * h画素のビットマップを deg だけ回転させた後、 平行移動させたものである。 (xb, yb)は線分の始点である。

  // ラインの中心に文字を描く
  matrix.preRotate(deg, w/2, h/2);
  matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);

道路のようにラインの中心に描くか、境界線の内側に描く二つとする。 線から離す場合には、ビットマップの上下にマージンを置くことにする。

試行錯誤の結果、境界線の内側に描くには例えば、次のようにすればよいことが分かった。 これで十分というより、これがベストと言える。

  // 文字はラインと交差しないように少し離して描く
  matrix.postTranslate(xb, yb);
  matrix.postRotate(deg, xb, yb);

上記二つのタイプのいずれかは float dy によって決める。dy = 0 のときは、車道名のようにラインの中心に描く。 都道府県境界線などでは、実質上は0となるような、十分に小さい値を引数とする。 中心線から規定値より少し離したい場合には、その値を設定する。中心線の上(左)か下(右)かは符号による。

  void drawTextMulti(Canvas canvas, Renderer r, String text, int color, float size, float dy, float halo)

ラインの中心に文字を描く場合、その領域は得ることができた。 ライン中心が離した場合、その領域がまだ掴めていない。

しかし、中心線上に描く場合と中心線と交わらないように描く差は文字の高さ半分の違いに過ぎない。 重なり回避に関しては、中心線上に描くと仮定して決定した結果を使っても、実際上の差は殆ど起きない。 従って、当面はその方法をとる。

2024.3.25 線分のつなぎ目の調整

線分の末尾が半文字未満のときは描画を止める。 従って、末尾には半文字分未満の空きがあるケースとはみ出しがあるケースがある。 次の線分の描画ではこれを考慮して、先頭より前から描きだすケースと 先頭から少しずらして描き始めるケースがある。

次の線分を基準として、前の線分の文字がはみ出した分を正の値で表す。前の線分の末尾に空きがある場合、 その幅をマイナスで表す。

まず、プログラムを簡単にするため、少しでもはみ出そうなときは描画をやめ、次の線分とした。 従って、次の線分は offset が正になることはない。

以下(赤字)の追加修正で道路名、川名の線分継ぎ目での不自然さはほぼなくなった。

市区町村境界線の場合、入り組んでいるため、線分と線分の角度の角度が大きい場所への描画を避けたい。

            if (dist < w) {     // w/2
                // 次の文字(幅は同じと仮定した)はこの線分には描けない
                if (n >= num_nodes * 2) {
                    return 2;   // このレコードには全文字は描けない
                }
                float rest = dist;  // 前の線分の末尾の空き
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = (float) distance(xb, yb, xe, ye);
                deg = (float) degree(xb, yb, xe, ye);
                if (rest != 0) {
                    num = dist / rest;    // 空きの何倍のおおきさか
                    xb -= ((xe - xb) / num);   // 空き描画で進める大きさ
                    yb -= ((ye - yb) / num);
                    dist += rest;
                }
            }

2024.3.24 橋名が描画されていない[解決]

1、2日前には描画されていたが、また、描画されなくなった。

1線分だけのレコードが除外されていた。

2024.3.24 他の地物のアイコンや文字との重なり回避機能を入れた[Map419]

まだ、線分のつなぎ目で改善の余地はあるが、アイコンや他の文字列との重なり回避機能を入れた。

重なり判定では、文字の回転を考慮していないが、矩形領域を描画してみて、 概ね文字が矩形領域に包含されていることを確認した。重なり判定はこれで十分とみられる。 必要に応じて、矩形にマージンを加えればよい。

    /* Renderer のメンバー変数
    int ixNode;         // 先頭の線分は #ixNode - #ixNode+1 である
    float offset;       // 文字列描画のスタート位置
     */
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了
            } else if (rtn == 0) {
                drawBitmapMulti(canvas, r, chrs, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
            } else {   // rtn == 1: 再試行、重なりで描画できない
                r.offset += Map.PX * 0.1f; // 再試行 スペース(狭い)を空ける
            }
        }
    }

    // chrs[] をラインに沿って描画する
    int drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, boolean check) {
        float dist = 0, deg = 0, num;
        float xb = 0, yb = 0, xe = 0, ye = 0;

        int n = r.ixNode * 2;  // 線分を指す
        xe = pnts[n++];   // 最初の始点座標にセットされる
        ye = pnts[n++];   // 同上
        float offset = r.offset;
        for ( ; offset >= 0 && n < num_nodes * 2; offset -= dist) {
            xb = xe;            // 線分の始点x座標
            yb = ye;            // 線分の始点y座標
            xe = pnts[n++];     // 線分の終点x座標
            ye = pnts[n++];     // 線分の終点y座標
            dist = (float) distance(xb, yb, xe, ye);
            deg = (float) degree(xb, yb, xe, ye);
            if (dist > offset) break;      // この線分から描画できる
        }
        //if (n >= num_nodes * 2) {
        //    return 2; // 1文字も描けない
        //}  これは間違い。1線分だけでも文字列が描けることが多い。橋は1線分が多い。
        if (offset > 0) {
            num = dist / offset;
            xb += (xe - xb) / num;
            yb += (ye - yb) / num;
            dist -= offset;
        }   // 残りのoffsetなし。(xb, yb)から文字を描く

        Matrix matrix = r.matrixMultiIcon;
        for (Bitmap chr : chrs) {
            float w = chr.getWidth();
            float h = chr.getHeight();
            num = dist / w;    // 何文字描けるか
            float dx = ((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = ((ye - yb) / num);
            float x1 = xb-w/2+dx/2;
            float y1 = yb-h/2+dy/2;
            float x2 = xb+w/2+dx/2;
            float y2 = yb+h/2+dy/2;
            if (check) {
                //canvas.drawRect(x1, y1, x2, y2, r.paintRedPen);
                if (r.intersects(x1, y1, x2, y2)) {
                    return 1;   // 交差有り
                }
            } else {
                //==== 一文字描画する ====
                matrix.reset();
                matrix.preRotate((float) deg, w / 2, h / 2);
                matrix.postTranslate(x1, y1);
                canvas.drawBitmap(chr, matrix, null);

                if (rectTile120.intersects(x1, y1, x2, y2)) {
                    RectF rect = r.rects[r.num_rects++];
                    rect.set(x1, y1, x2, y2);   // 追加登録
                }
            }
            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 yeに向かって進む
            dist -= w;
            if (dist < w/2) {
                // 次の文字はこの線分には描けない
                if (n >= num_nodes * 2) {
                    return 2;   // このレコードには全文字は描けない
                }
                float rest = dist - w;
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = (float) distance(xb, yb, xe, ye);
                deg = (float) degree(xb, yb, xe, ye);
            }
        }
        n -= 2;
        if (!check) {
            r.ixNode = n/2;
            //r.offset
        }
        // 最後の線分の後ろに dist 分のスペースがある
        return 0;   // 全文字の処理を終わった
    }

2024.3.23 つなぎ目での重なりは殆どなくなった。

線分のつなぎ目で文字間隔が空くことがある。重なりも皆無ではない。

元に戻れるように細かく記録を残しておく。

文字単位の描画は自作地図アプリでは初めての試みのため、急がず、 十二分に時間をかけて、より分かりやすいプログラムを目指す。

できれば、プログラムをもっとシンプルにしたい。また、継ぎ目の処理を分かりやすくしたい。 継ぎ目で角度差が大きい時は、描画を止めた方がいいだろう。角度差が小さい時は継ぎ目でのスペースを もっと小さくしたい。

drawBitmapMultiのチェック時の戻り値は成功、再試行、失敗の三つとしたい。線分の状態を引数とする。 状態としては、線分番号、線分の長さ、線分オフセットとする。チェックモードでは変更しないが、 描画モードではこれらの値を先に進める。再試行は、現在の線分番号、オフセットでは重なりが発生するが、 まだ、残りの線分に可能性があるときである。失敗は一部の文字が描けるとしても全部は描けない場合である。

dx、dy については当初、三角関数がいるかと思ったが、簡単に算出できた。

Matrix#postTranslateメソッドの引数は分かりにくいが、試行錯誤で、正解らしきものにたどり着いた。 記録を取りながら、試行錯誤で、少しずつ、ゴールに近づけばよい。

現在のプログラムは連続的に複数回文字列を描画するようになっているが、これを1回限りに変更する。 別途、親プログラムを作り、戻り値が失敗となるまで、これを繰り返し実行する。

線分番号、線分の長さ、線分オフセットは新たなclassの導入はひとまず見送り、 Renderer のメンバー変数とする。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        // 最初の線分は先頭から描画を始める
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);

            //==== 一文字描画する ====
            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);
            //canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);
            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 xeに向かって進む
            dist -= w;
            if (dist < w) {
                // 次の文字は描けない
                if (n >= num_nodes * 2) {
                    break;  // 線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }

            if (++k >= chrs.length) {
                // 全文字描き終わった
                k = 0;
                float space = Map.PX * 0.3f;
                if (dist > space + w) {
                    // まだ、この線分に描ける可能性がある。
                    num = dist / space;
                    dx = (float)((xe - xb) / num);  // space分の移動量
                    dy = (float)((ye - yb) / num);
                    dist -= space;
                    xb += dx;
                    yb += dy;
                } else if (n < num_nodes * 2) {
                    // スペースの確保を次の線分に引き継ぐ
                    space -= dist;  // 残りのスペース
                    while (n < num_nodes * 2) {
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                        if (dist < space) {
                            space -= dist;  // まだ、空きが足りない
                        } else {
                            num = dist / space;
                            dx = (float)((xe - xb) / num);  // space分の移動量
                            dy = (float)((ye - yb) / num);
                            xb += dx;
                            yb += dy;
                            dist -= space;
                            break;  // 文字描画を始める
                        }
                    }
                } else {
                    break;  // 全線分終了
                }
            }
        }
    }

2024.3.23 プログラム修正

線分のつなぎ目で文字が重なることがある。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        // 最初の線分は先頭から描画を始める
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);
            // 一文字描画する
            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);
            //canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);

            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 xeに向かって進む
            dist -= w;
            if (dist < 0) {
                // 次の文字は描けない
                if (n >= num_nodes * 2) {
                    break;  // 線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }

            if (++k >= chrs.length) {
                // 全文字描き終わった
                k = 0;
                float space = Map.PX * 0.3f;
                if (dist >= space) {
                    // まだ、この線分に描ける可能性がある。
                    num = dist / space;
                    dx = (float)((xe - xb) / num);  // space分の移動量
                    dy = (float)((ye - yb) / num);
                    dist -= space;
                    xb += dx;
                    yb += dy;
                } else if (n < num_nodes * 2) {
                    // スペースの確保を次の線分に引き継ぐ
                    space -= dist;  // 残りのスペース
                    while (n < num_nodes * 2) {
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                        if (dist < space) {
                            space -= dist;  // まだ、空きが足りない
                        } else {
                            num = dist / space;
                            dx = (float)((xe - xb) / num);  // space分の移動量
                            dy = (float)((ye - yb) / num);
                            xb += dx;
                            yb += dy;
                            dist -= space;
                            break;  // 文字描画を始める
                        }
                    }
                } else {
                    break;  // 全線分終了
                }
            }
        }
    }

2024.3.23 一方通行の矢印がスペースなく描画されている

2024.3.23 マルチポリゴンによるペデストリアンデッキの枠線が太すぎる。

multipolygonの除外をとった。

2024.3.23 highway=service, area=yesエラー

一度修正したエラーが元に戻っている。

なぜか、コメントアウトしていた。解除すると、正常に描画された。

2024.3.23 ゴールに近づいた

線分が変わったところで更新するのが distance(線分の長さ)と degree(線分の角度)であり、 文字が変わったところで更新するのが w、h、dx、dy である。

Matrixで難しいのは postTranslate の引数である。多分、回転前の文字ビットマップの中心座標であろう。 (xb-w/2、yb-h/2)とした場合、先頭文字の半分が線分の先頭より前に出てしまう。

先頭をピッタリ合わせるには (xb-w/2+dx/2、yb-h/2+dy/2) となる。 (xb-w/2+dx、yb-h/2+dy) とすれば、先頭一文字の半分が前に空く。

ここはどちらでもいいが、先頭に文字半分のスペースを空ける場合、distance から w/2 を引いておく必要がある。

分かりにくいのは回転前の w/2 と h/2 と回転後の dx、dy は同居している点である。 しかし、描画結果ではこれが一番良好な結果となっている。

下のプログラムでは線分の末尾に1文字未満のスペースが生まれる。 次の線分はこれを考慮しないため、このスペースが描画結果に現れる。

文字列間のスペースについても、前の線分で十分なスペースが確保できなかった場合、 次の線分の先頭で残りのスペースを確保してから描画を始める必要がある。この処理は未完成である。

「○○通り」で「通」と「り」の間にスペースが入ることはなくなった。 しかし、「り」の後ろが少し切れる。これは自分のプログラムのせいではない可能性が高い。 とりあえず、例外処理で目立たなくした。

頻度は少ないが、線分のつなぎ目で文字が中心からずれることがあるが、まずはこれでよしとして、 次のフェーズに移る。

大きくは似た処理を二度行う。一度目は実際の描画は行わず、 文字列中の全文字が重なりなく描けるかどうかを調べる。描けることが分かれば、二度目の処理で描画を行う。

全線分が終わって描けなかった場合はそれで終わり。 そうでないときは、適当なスペースを空けて、同じことを行う。

メソッドにチェックフェーズか描画フェーズかを区別するフラグ引数 boolean check を追加する。 check が true のときは、重なり判定のみ行い、描画は行わない。check が false のときは 描画を行い、 文字Bitmapの矩形を登録する。回転した矩形は登録できないため、とりあえず、対角線を1辺とする正方形を登録する。 中心座標は (xb-w/2+dx/2, yb-h/2+dy/2) である。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        //float rest = 0;
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);
            if (dist < w) {
                // この線分には描けない
                if (n >= num_nodes * 2) {
                    break;  // 全線分終了
                }
                // 新たな線分を取り出す
                //rest = w - dist;    // 前の線分の末尾のスペース
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }
            // 一文字描画する

            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);

            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 yeに向かって進む
            dist -= w;
            if (++k >= chrs.length) {
                k = 0;
                float space = Map.PX * 0.3f;
                while (n < num_nodes * 2) {
                    if (dist >= space) {
                        num = dist / space;
                        dx = (float)((xe - xb) / num);  // space分の移動量
                        dy = (float)((ye - yb) / num);
                        dist -= space;
                        xb += dx;
                        yb += dy;
                        break;      // スペースが現在の線分で確保できた
                    } else {
                        // スペースの確保を次の線分に引き継ぐ
                        space -= dist;  // 残りのスペース
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                    }
                }
            }
        }
    }

2024.3.23 文字の先頭が線分の始まりより前にある

文字の中心が先頭に一致している。最初に xb += dx; yb += dy; を実行してから文字を描くことにしたい。

長い橋には橋名が二度以上かけることもあるが、通常は1回である。1回の場合、中間に描くのが見栄えがよい。

2024.3.22 現アプリとの折衷案はどうか

文字列全体が一つの線分内に描画できるときは文字毎の描画ではなく、一括して描くことにしてはどうか。 文字間隔はバランスが良い。

二つの線分に分かれるときは、二組または両端と折れ曲がる所の1文字に分ける案もある。

一文字ずつでもこれと同等の結果が簡単に得られるならばそれでよいが。

2024.3.22 個々の文字間隔が不適切

道路の中心から文字がずれることはなくなった。文字列が一つの線分に描ける場合、ほぼ問題はなくなった。 ただし、幅の狭い開かな交じりのとき、文字間隔が空きすぎる。

文字の Bitmap を作るとき日本語の文字幅は固定長の方がいいかも知れない。縁取りを考慮してマージンを加えたが、 これが大きすぎるかも知れない。 しかし、文字間隔が不揃いである。dx、dy に問題がある。一文字ずれているかも知れない。 postTranslateの引数に問題がある可能性が高い。

Paint#getTextBoundsで得られる矩形を文字と共に描画してみたが、「こ」とか「り」のとき良くないようだ。 他に適切なメソッドが見つからない場合、個別に補正するのがいいだろう。 漢字に対しては適切な結果が得られている。

また「通り」の場合、「通」と「り」の間に他より大きいスペースが空くことが多い。これは自作プログラムの不具合であろう。

2024.3.22 道路名の描画(2)

今回、初めての試みのため、そう簡単にはいかない。String から Bitmap配列 を生成する方は単純であり、 概ね問題はないようだ。ただ、縁取りがうまく働いていないようだ。

道路上に、道路名が概ねうまく描けているところと大きく外れているところが半々くらいである。 プログラムにちょっとしたバグがあるのであろう。

Matrix演算を次のようにしているが、実のところこれが良く分かっていない。 postRotateはBitmapの中心で回転させるのでこれでいいのであろう。問題は postTranslate の引数である。 X, Y軸の移動量はこれでいいのだろうか? -dx/2、-dy/2 はいるかいらないか?

   matrix.postRotate(deg, w/2, h/2);
   matrix.postTranslate(xb-dx/2-w/2, yb-dy/2-h/2);

単純に matrix.postTranslate(xb, yb); としても、所々、道路から大きくずれる文字がある。 xb、yb の算出に問題が残っているのだろう。

一文字ごとに、xb、yb を次式で変更している。線分には傾きがあるため、一文字の移動量 dx、dy を次式で決めている。 dist は線分の長さである。w は文字ビットマップの幅である。xb、yb はだんだん xe、ye に近づいていくので、 これでいいように思う。

  float num = dist / w;    // 何文字描けるか
  float dx = (xe - xb) / num;   // 一文字描画で進める大きさ
  float dy = (ye - yb) / num;
  xb += dx;
  yb += dy;

Matrixの動作が予想とは違っている可能性もある。 回転はさせなくてもいいので、Matrixを使わずに、Bitmap を (xb, yb) の位置にコピーしてみょう。 要するに、Matrix操作がおかしいのか、(dx、dy) がおかしいのか切り分けたい。 うまく行けば、Matrix操作は回転だけに限定してもよい。

あるいは上のプログラムで xb、yb を変化させ、小さな点か十字線だけを描いてみる。 要するには、上の計算が正しいかどうかを確かめたい。


xb、yb の位置確認は次の一行で行えた。Matrix演算の前に、この座標計算に問題があることが判明した。

文字列と文字列間のスペースをやめると、この赤点がライン中心を外れることは殆どなくなった。 極まれに少しずれているのは、線分と線分のつなぎ目近辺と思われる。 従って、バグは文字列と文字列間のスペースにある。また、一つの文字が二つの線分をまたぐことがあるので、その処理に注意がいる。

文字列と文字列間のスペースは線分をまたぐこともあるため、思ったより厄介である。 スペース文字を描く形式の方がプログラムは楽かも知れない。

一つの文字が線分をまたぐケースが厄介である。傾きは前の線分のものでいいとしても、(dx、dy)が二つにわかれることになる。 前の線分で何%分か終わったとして、残りの分は後の線分の角度は前とは異なるために、(dx、dy)の算出をやり直す必要がある。 文字が線分をまたぐと計算が面倒である。

線分の残りが文字幅の半分以上であれば描く、半分未満であれは書かない。描いた場合、はみ出した幅(ドット単位)、 描かなかった場合、残りの幅を次の線分に引き継ぐ。

次の線分では、線分の先頭から文字を描くのではなく、はみ出し幅を空けて描き始める。 残り幅があれば、その分先頭より前から、文字を描き始める。

このような細かい調整を行わなかった場合、線分をまたぐところで、文字間隔が広がったり、少し文字が重なったりするであろう。

 canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);

スペースで dy とすべきを dx としていたミスがあった。道路中心からのはみ出しはこれが原因だったようだ。

2024.3.21 道路名の描画(1)

カーブに沿って描く道路名などの描画はアイコンをカーブ沿って並べるのに似ている。 特に、一方通行を表す矢印アイコンの場合、それに近い。

一般に道路などは複数の線分で構成される。カーブしている道路に道路名を描く場合、 全ての文字を同じ線分上に描くとは限らず、途中で文字の傾きが変わることもある。

しかし、名前の場合、これよりは色々と面倒である。複数の文字をセットとする必要がある。 矢印のように道路の進行方向に合わせて文字列を並べるわけではない。 例えば、東西に走る上下線が分かれた道路の場合、上りと下りは向きが逆であるが、 描画は同じで、左から右に文字列を並べる。

アイコンの場合、下地が何であっても無条件に描きならべるが、道路名の場合、 すでに別の文字やアイコンが描画されている場所は避ける。 全ての文字が重なることなく描画できる場合にのみ描画する。

まずは、重なりは気にせず、下のアイコン描画プログラムを参考にして 文字を描きならべてみよう。

個々の文字は Bitmap で表せるので、とりあえず drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) メソッドとしてみる。文字は反転(180度回転)させることがある。 そのときは、文字は後ろから順に描くことになるだろう。 これらについては追って考える。

下のプログラムは線分毎の処理で、一つの線分には 複数のアイコンが並べているため、二つの for 文になっている。

    // iconをラインに沿って繰り返し描画する
    void drawIconMulti(Canvas canvas, Renderer r, Bitmap bmp, float space) {
        if (bmp == null) return;
        float width = bmp.getWidth();
        float height = bmp.getHeight();
        Matrix matrix = r.matrixMultiIcon;
        float prv_x = pnts[0], prv_y = pnts[1];
        for (int k = 1; k < num_nodes; k++) {
            float x = pnts[k*2];
            float y = pnts[k*2+1];
            float dist = distance(x, y, prv_x, prv_y);
            int num_icons = (int)((dist + 0.9f)/(width+space));
            float dx = (x - prv_x) / num_icons;
            float dy = (y - prv_y) / num_icons;
            float deg = degree(prv_x, prv_y, x, y);
            float x1 = x, y1 = y;
            for (int n = 0; n < num_icons; n++, x1 -= dx, y1 -= dy) {
                matrix.reset();
                matrix.postRotate(deg, width/2, height/2);
                matrix.postTranslate(x1-dx/2-width/2, y1-dy/2-height/2);
                canvas.drawBitmap(bmp, matrix, null);
            }
            prv_x = x;
            prv_y = y;
        }
    }

一般に一つの道路等のレコードに複数回(0回、1回を含む)道路名等を描画することから、 全体が while文(またはfor文)とする。

必ずしも線分を区切りとはしないため、線分での for 文はない。 文字列が線分をまたがることがあるため、文字ごとの for文もやめる。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
	int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        float dist = distance(xb, yb, xe, ye);
        float deg = degree(xb, yb, xe, ye);
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
      if (dist < w) {
                if (n >= num_nodes*2) {
                    break;  // 全線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
        	dist = distance(xb, yb, xe, ye);
        	deg = degree(xb, yb, xe, ye);
            } else {
                // 一文字描画する
                matrix.reset();
                matrix.postRotate(deg, w/2, h/2);
                matrix.postTranslate(x-w/2, y-h/2);
                canvas.drawBitmap(bmp, matrix, null);
        	float num = dist/chrs[k].getWidth();	// 何文字描けるか
        	float dx = (xe - xb)/num;   // 一文字描画で進める大きさ
        	float dy = (ye - yb)/num;
                xb += dx;	// 一文字分 xeに向かって進む
                yb += dy;	// 一文字分 xeに向かって進む
                dist -= w;
                if (++k >= chrs.length) {
                    k = 0;
		    float space = Map.PX * 0.3f;
		    if (dist >= space) {
			dist -= space;
		    } else {
	            }
                    
                }  
            }
        }
    }

まだ、修正点は多く残っているが、道路名が道路に沿って描画されるようになった。

2024.3.20 アイコン、文字列を重なりや欠けることなく描画するのは難しい[Map419]

タイル境界の存在や文字列の傾きから適切な描画が難しい。 重なりだけを避けるはできるが、描画が減ってしまう。 完璧は難しいが、なるべく、標準OSM並みの描画を実現したい。 一から考え直す必要があるようだ。

カーブに沿った道路名などはこれまでずっと文字列ごとの描画であった。 Matrixを使って描画すると、実際の描画位置が良く分からない。 また、文字やアイコンの重なり判定を矩形で行っているため、傾いた矩形が扱えない。 したがって、実際の描画領域よりもはるかに大きい矩形領域を登録していた。

精度を上げるため、道路や都道府県境界線などに沿って描く文字列は、文字ごとの描画に変更する。 傾きのある矩形は扱いにくいため、重なり判定用としては、 傾きのある矩形を概ね包含するような水平方向の矩形を描画した領域とするが、 文字列毎に比べれば、格段に精度が上がる。


水平に文字を描いてそれを回転させる。水平線の上に文字を描く場合でも、それを180度回転させる場合には、 文字列上の文字は前から書くのではなく、後ろから順に書く必要がある。 多分、最初の線分の傾きで前か後ろかが決まるであろう。

2024.3.19 広域森林などマルチポリゴンの描画が消えた[解決]

ここ1、2日の変更が関係していると思われる。

マルチポリゴン描画の最後の処理をコメントアウトしていた。なぜかは、覚えていない。

2024.3.19 高尾山のエコーリフトの名前が描画されない

wayデータは始点、中間点、終点の3点だけであった。ノード数が少ないために、描画されなかったのであろう。 登山鉄道の場合、始点、終点近くを除くと、ノードが両端だけの way である。

ケーブルカーやロープウェイは通常の道路や鉄道と違う処理が必要なようである。

タイル境界内に限定していた。これを解除すると描画された。

ひょっとすると、道路や鉄道ではタイル内に限定した方が良いかもしれない。

2024.3.19 Map.Scaleはレンダリングメソッドで対応する

2024.3.19 drawLine簡素化

Paint引数をColor(int型)引数に変更。修正箇所は膨大であるが、簡素化を果たす。

2024.3.19 drawLine修正

Pathを使わず、線分毎に描画する方が高速であるが、鉄道の場合、カーブでは一つの線分の長さが短いため、 破線描画がうまく行かない。Pathを使うと見栄えががぜん良くなった。

低中ズームは高速描画になった。高ズームも近々この方法に変えたいが、レンダリングを修正した場合、 保存したタイル画像が無効になる。

しかし、レンダリングの修正は急がず、気が付いた時点で、描画を確認しながら行っているので、 こちらは長期戦である。

レストランとかコンビニだけなど、いずれ、特定のものだけ描画する機能を追加したいので、 このレンダリングとバス路線のレンダリングなど一部のレンダリングだけを切り離して、 地図のベースとなるタイル画像は一度作成したものをストレージに保存して、再利用する。 総合的な描画時間は 1/10~1/100 に短縮する。

レンダリング時間の短縮に拘ってプログラムを分かりにくくしているところは、少しずつシンプルにしていく。

2024.3.18 国立公園がレンダリングされていない

Relation処理で抽出されていなかった。

いくつかの修正を経て、所望のレンダリングが行われるようになった。

2024.3.18 低中ズームのタイル画像のストレージ保存開始[Map418]

やはり、タイル画像のストレージ保存が圧倒的に高パフォーマンスである。

動的変更がない低中ズームのタイル保存を開始した。

低中ズームはレンダリング時間を気にせず、見栄えを追求できる。

バス路線やアイコンなどをダイナミックに変更する場合、ベースとなるレンダリングと動的に変わる 部分のレンダリングを分離して、ベースの部分をタイル保存すれば大幅なパフォーマンス向上が図れる。 しばらくしてからこれを考えたい。

2024.3.18 低ズームのレンダリング時間

zoom 6では、時間のかかるタイルで、レコード数は6~10万、レンダリング時間は5~7秒である。 ラインレコードを空間検索で全て除外した場合、レコード数は 4,000~6,000 で レンダリング時間は4、5秒となる。レコード数は1/10 になるが、描画時間は2割ほど速くなるだけである。

道路等のライン描画にかかる時間はわずかで、大半が、巨大なポリゴン、マルチポリゴンの描画時間である。 都道府県境界線は現在は type = 1 レコードではなく、 Relation処理による ポリゴン/マルチポリゴンレコードで描画している。境界線にそって描画する都道府県名は Relation処理が必要であるが、境界線自体は way レコードでもいいはずなので、そうすれば少しは速くなる。

低ズームのレンダリング時間はレコード数に依存するのではなく、巨大なポリゴン/マルチポリゴンによる。 陸地ポリゴンも巨大であるが、数が少ない。やはり、一度レンダリングしたタイル画像をストレージに保存して おき、再利用するのは最も簡単で高速であろう。タイルのレンダリング時間を1秒未満にするのは簡単ではない。

バス路線など、タイルのレンダリングを動的に変更するのは、高ズーム(zoom 12または13以上)でよい。 高ズームではタイル画像のストレージ保存はしない。

2024.3.17 中低ズームのラインレコードを接続してレコード数を減らす[中止]

これは、即座に実行するのではなく、できるだけ簡単な方法が見つかるまで、熟慮する。

可変長バイトコード化も当初は実行時間がかかることから実施を見送っていたが、 熟慮の末、復号化にあまり時間がかからない方法を思いつくに至った。

中低ズームはレコード数が非常に多い。間引きによりレコード当たりのノード数は少ない。

リレーション route=train;subway;road を使って接続する案と、 ByteCoderの前処理で同じファイル内の鉄道、地下鉄、道路の終点座標と始点座標の比較で接続する案がある。 プログラム的には後者の方が簡単であろう。鉄道、地下鉄、道路毎に、タグの値が一致し、終点と始点が一致する場合、 つなぎ合わせる。しかし、交差点や鉄道のポイントである終点と始点が一致するものが二つ以上のケースがあるかも 知れない。最初に見つかったものをつなぐことにする。どれをつなぐかにより最終レコード数が変わることがあっても 問題ではない。

現在は osm_id をレコードに含めていないが、含める場合、先頭の osm_id をつなぎ合わせたレコードの osm_id とする。

中低ズームではバス路線番号リストを持っていないが、これを持つ場合には、バス路線番号リストもタグと同じように、 完全に一致する場合、繋ぎ合わせる。一部でも違いがあるものは繋ぎ合わせない。

境界ボックスは当然繋ぎ合わせた結果について求める。type = 1 だけを対象とするため、way_area は持たない。


レコードをつなぎ合わせる目途はついた。しかし、効果があまり期待できない。

ファイルサイズ上は中ズームでは type=1 レコードは少ない。(低ズームではtype=1が多い。)

また、道路等をつなぎ合わせると、zoom によっては実際には描画されない範囲についても 可変長バイトコードの復号化や座標変換を行う無駄が生じる。

レコード数が減っても余分な処理が増え、逆効果になる恐れもある。実際、どこに時間がかかっているか、 状況を掴んでからにする。

2024.3.17 マルチポリゴンで inner polygon のプールが塗りつぶされていない

アイコンは描画されている。inner (プール) が二つあり、一つは描画されている。

どうやら num_polysの値が 2 になっているようだ。

別のビルでは、inner polygon が6つのところが、3つしか穴あきになっていない。 1、3、5 が inner polygon になり、2 と 4 が無視されているようだ。 データ作成の Relation処理の誤りの可能性がある。

多分、listInnerPolygon.remove(i);によって、偶数番号の inner polygon が捨てられてしまうのであろう。

ループ内で要素を削除するのは良くない。outer polygon同士の重なりはないので、 一つの inner が二つ以上の outer polygon に含まれることはない。削除しなくても問題はない。

この結果、マルチポリゴンの inner polygon が全て描画されるようになった。

// inner polygon を outer polygon に所属させる
for (Polygon outer : listOuterPolygon) {
  for (int i = 0; i < listInnerPolygon.size(); i++) {
    Polygon inner = listInnerPolygon.get(i);
    if (outer.bbox.contains(inner.bbox)) {// 暫定 いずれポリゴン内点判定に変更
      listInnerPolygon.remove(i);
      outer.listInner.add(inner);
    }
  }
  if (outer.listInner.size() == 0) {
    //System.out.printf("id=%d no inner\n", rel.id);
  }
}

2024.3.16 車道がタイル境界線付近で描画されていない

空間検索ではマージンをいれている。ブロック分割にも以前と同じマージンを加えた。 これで描画された。

2024.3.16 ポリゴン/マルチポリゴンの先頭ノードと最終ノードの座標が一致しない[解決]

差が小さい場合、レンダリングでは気がつかないが、殆どの場合、わずかな差がある。 符号化か復号化のどちらに誤りがあるようだ。境界線の場合、ノード数が多いため、目立つようだ。 ノード数が少ない場合、差は小さいようである。下の例では、+2 ~ +4 である。

type=2 (1394989335, 355543050) (1394989337, 355543052)     (+2, +2)
type=2 (1395069981, 355530949) (1395069984, 355530953)     (+3, +4)

符号化の -1 が関係しているのであろう。復号化ではこれに相当するものを入れていない。 これに合わせた結果エラーがなくなった。

    static void ConvertSLong(ByteBuffer buf, long v) {
        long u = v<0 ? ((-v)<<1) - 1 : v<<1;
        ConvertULong(buf, u);
    }

2024.3.16 タイル4-8-3の陸地が描画されない[解決]

陸地ポリゴンの低ズーム分割を zoom 1 から zoom 0 に変更したが、 zoom 0 の分割ファイルをパソコン側で作っただけで、タブレットにコピーしていなかった。

2024.3.16 境界線に沿って描く県名や市名の位置が大きく(数十メートル)ずれている[解決]

可変長バイトコード化前はおかしくなかった。

町田市と横浜市の境界線は同時に東京都と神奈川県の境界線であるが、 リレーションで作られるレコードは異なる。

描画では県境と市境がずれている。経度、緯度差を可変長バイトコード化している。整数のため、 誤差の累積は起きない。どこかの1点で誤りがあり、それが全体のずれを生んだものと思われる。

大抵は1,2バイトコードであるが、稀に、3、4バイトになるであろう。1、2バイトの復号化にはバグはなく、 3バイトか4バイトの復号化に誤差があれば、このような現象が起こりうる。

また、符号化のエラーの可能性は皆無とは断定できない。 しばしば起こるエラーならば、もっと色んな所にでるので、そうではない。

レンダリングスレッド数を1にしても、同じであった。

int getUInt() は結果が非負の範囲であれば問題ないが、負のときは要注意。例えば long v = getUint() & 0xffffffff; として正の値に変化する必要がある。元々 int 型で非負の数値を 符号なし可変長バイトコードに符号化しているはず。 int getSInt() は問題ないはず。

境界線以外でも getSInt() の結果が4バイトのケースは多くある。しかし、今のところ境界線以外では異常は見つかっていない。 なお、結果が5バイトは見つかっていない。getUInt()では、結果が4バイト以上はなかった。

エラー原因は可変長バイトコードの復号化ではないかも知れない。

境界線エラーはいろんな場所で頻発している。

zoom 15 で高圧線と思われるものが道路ほどの太い線になったことが二度ある。再現性はない。 境界線のずれとは無関係であろうが、これもバグであろう。

マルチポリゴンについても、森林は正しく描画されているし、巨大なユーラシア大陸、 その inner polygon であるカスピ海にも異常はない。

しかし、ユーラシア大陸の北側でレンダリングされないタイルがあった。zoom 4 で2か所が描画されない。 レンダリングするのは同じユーラシア大陸レコードであり、場所によってレンダリングされないことが起きる。[解決]

境界線のずれは復号化処理で -1 が漏れていたのが原因だった。

2024.3.15 widをバイナリレコードに追加した

バイナリレコードの先頭バイトはこれまで type だけであったのを上位6ビットを wid とした。

小さいポリゴン/マルチポリゴンレコードは way_area ではなく、 境界ボックスの width、height を使うことにする。

zoom 0 では width、height が1度程度のものは無視してもよいだろう。zoom 10では 0.001度、 一般に 1/(1<2024.3.15 中ズームの抽出レコードを絞り込む

中ズームのレンダリング時間はタブレットで1~2秒である。レコード数は数万が多い。最大は20万越え。 中ズームファイルは現在は zoom 10と11 で使っている。使用範囲は試行錯誤で変えている。

zoom 10や11では使わないレコードも含まれている。完全に使用範囲が固まれば、 そのようなレコードは含むべきではないが、試行錯誤段階では、バイナリレコードファイルの取り直し 頻繁に行うには時間がかかるため、中低ズームにはどちらかといえばやや多めで余分なレコードを含んでいるといえる。 zoom 11 では使うが、zoom 10 では使わないレコードもある。

したがって、レコード抽出では空間検索だけでなく、タグによる絞り込みも行った方がよい。 元々レンダリングでは除外されているレコードのため、正味のレンダリング時間は変わらないが、 無駄な可変長バイトコードの復号化や座標変換がなくなり、使用メモリも少なくなる。


これを実現するには、バイナリレコードの仕様変更が必要となる。タグデータはレコードの末尾にあり、 その前にある可変長バイトコードを復号化しないとタグ部の先頭が分からない。

以前は、タグ部を前に置いていたが、OSMデータに合わせて後ろに移動させた。

座標値データの前にあるのはノード数であって、座標値データの長さではない。 ノード数を長さに変えることもできるが、マルチポリゴンではポリゴンの長さを合計する必要がある。

以前のように座標値データを後ろに移動し、前に移動したタグ部の先頭にはタグ部の長さをおく方が分かりやすい であろう。

しかし、バイナリレコードの仕様は軽々に変更すべきではない。以前は道路種別を表すIDである wid(1バイト) を バイナリレコードに持っていた。空間検索段階の絞り込みは道路などラインレコードではこの wid、 ポリゴンレコードでは way_area となるであろう。ポイントレコードは少ないので絞り込みは要らない。

現在は、wid は直接的にはバイナリレコードにはない。タグから算出している。way_area もタグ扱いとしている。 これにより、レコードフォーマットがシンプルになっている。パフォーマンスを考えれば、wid、way_area を バイナリレコードの表に出した方がよい。wid は type と合わせて1バイトあるいは2バイトとする。 way_area は type=2、type=3 で存在する独立要素(float型)とする方がプログラムの修正は少なく、 パフォーマンス上もよい。

2024.3.15 滑走路を中ズームに追加する

標準OSM地図では滑走路は zoom 11 から描画されているようである。 これに合わせるには、中ズームに加える必要がある。

この追加で japan-mid.dat は 286,748MB から 286,759MB に微増した。

zoom 11から羽田空港の滑走路が描画されることを確認した。

2024.3.14 滑走路が描画されていない

MapX から renderAeroway をコピーした。

2024.3.14 中ズームのレコード数が非常に大きい

zoom 8 で35万レコードを超えた。できれば10万レコードくらいに抑えたい。

並列スレッド数を落として、動かした。最大レコード数は 383350 であった。

zoom 9までを低ズームに変えた。zoom 10 での最大レコード数は 206959 であった。 MAX_OSM を 250000 に設定した。パフォーマンスは大幅に改善した。

2024.3.14 OSMバイナリレコードによるレンダリング

まず、OSMバイナリレコードの可変長バイトコード化を行う。

2024.3.14 シンプルに

極力 BitmapFctory の使用を減らしてみたが、実行時間は変わらないか少し増える。

ファイルの読み込みに時間がかかる。海や陸のデコードは速い。また、メモリ獲得が起きても、 直ちに解放するので、ガーベージコレクションの負荷は気にするほどではない。

        if (new File(pathLands).exists()) {
            byte[] img = read(pathLands);
            if (Arrays.equals(img, land)) {
                OSM.fillRect(canvas, paintLand);
            } else if (Arrays.equals(img, sea)) {
                OSM.fillRect(canvas, paintWater);
            } else if (img != null) {
                Bitmap bmp = BitmapFactory.decodeByteArray(img, 0, img.length);
                canvas.drawBitmap(bmp, 0, 0, null);
                bmp.recycle();   //System.arraycopy(tile.bmp, 0, bmp, 0, bmp.length);
            }

2024.3.14 メモリの再利用ができない

ひとまず

   tile.bmp = loadBitmap(pathLands);
としたが、これではメモリの再利用ができない。loadBitmapの中心は以下であるが、 decodeStreamは動的に新たなメモリを確保して、そこに解凍したビットマップをセットする。
  return BitmapFactory.decodeStream(new BufferedInputStream(stream));

ざっと調べた限りでは、引数で格納先を与えてそこにビットマップをセットするようはメソッドの使い方はできないようだ。 その場合、このタイル画像が要らなくなったときはガーベージコレクションの対象となる。

少なくとも当面は BitmapFactoryで作られたメモリの再利用は断念する。再利用 tile.bmp にコピーして、 ガーベージコレクション対象とする。

decodeStream の代わりに decodeByteArray(byte[] data, int offset, int length) を使う。 byte配列として読み込み、600バイトであれば陸地タイルの600バイトと比較して、陸地と分かれば、Bitmapへの解凍はせず、 tile.bmpにCaavasを通して、矩形描画を行う。海と陸が交じり合ったタイルのみ decodeByteArrayを使う。

2024.3.14 陸地ポリゴンの精度を上げる

これまで zoom 9以下で精度の低い陸地ポリゴンデータを使っていたが、 この低ズームファイルの利用は zoom 7以下で使うように変更した。zoom 8、9 の精度が上がった。

2024.3.14 画像フォーマットをWebPとする

陸の単色タイルは384x384画素で 600B になった。

読み込み時間は Androidタブレットで平均的 5、6 mS となった。 zoom 16 で日本地図の陸地を表示した場合、全タイルが陸地タイルとなり、平均時間は 4mS となった。

全体的なレンダリング&描画時間は少なくとも 100~300 mS となることから、 陸地ポリゴンにかかる時間は十分に小さくなった。これまでは 100~200mS を超えていたと思われる。 ただし、陸地かどうかを予め調べて、それをデータとしてメモリに読込んでいたときがある。 その方法では、通常は1mS 未満であろう。しかし、海近くでは 100mS 以上かかる。

新たな方法は内陸では 4mS、海岸線近くでも 10mS 未満であり、また、プログラムが分かりやすい。 Map4では、この方法を採用する。

平均的なファイルサイズはそれほど変わらないかも知れないが、通常は陸地を表示しているため、 陸地タイル画像ファイルが小さい方が平均パフォーマンスが向上する。

陸の単色タイルは256x256画素でも同じ 600B になった。また 768x768画素でも同じ 600Bになった。

陸地ポリゴンの平均的なレンダリングは 0.1~0.2秒程度なので、陸地ポリゴンについては、 一度レンダリングしたものをストレージに保存し、再利用する方式とする。

一般のOSMバイナリレコードによるレンダリングについても zoom 10~12 以下では、 レンダリング結果が変わることはないので、レンダリング結果の再利用によるパフォーマンス向上を考えてもいいであろう。

レンダリングの見直しが多いため、陸地ポリゴンとは分離しておく。上位のタイル画像は陸地ポリゴンを含んでいるため、 上位のタイル画像があれば、これを表示するだけ、なければ陸地ポリゴンタイルを読み込んでレンダリングとなる。 陸地ポリゴンタイルもなければ、陸地ポリゴンのレンダリングから行うこととなる。

しばらくは、陸地ポリゴンのタイル画像保存のみとする。安定した段階で、上位タイル画像の保存を考える。

2024.3.14 海、陸タイルの保存について

単純に pngファイルにすると、384x384画素では1.46KBになった。Windowsの場合、非常に小さかった。 Windowsの場合、確か、非常に小さいファイルは、ファルダに置かれ、ファイル自体は作られなかったように思う。 ひよっとすると、フォルダ当たり一つの画像について、だったかも知れない。 Android にもファイルサイズ0(ファイル実体なし)のときフォルダだけにファイル情報が置かれるのであれば、 陸地のときは 123land.png、海の時は 123sea.png としてもよい。

画像ファイルを作らず、データで持つ方法もあるが、画像ファイルで統一したのが分かりやすい。

いずれにせよ、急ぐ話ではないので、試行錯誤してみよう。WebPという新しい画像フォーマットも試してみる。

2024.3.13 陸地ポリゴンレンダリング&描画時間

zoom 7で日本地図を表示した場合、タイル当たりの描画時間はタブレットで 0.2~0.25秒であった。 一度作成したファイルをストレージに保存しておけば、読み出し時間は半分くらいになるだろうが、 桁違いに速くなるわけではない。

本州ポリゴンは大きいため、それぞれのスレッドが同じポリゴンレコード を復号化して、座標変換している。ある一つのスレッドが復号化&座標変換したものを 他のスレッドが利用すれば、効率的である。複数のタイルにまたがる大きなレコードについてのみ このようなことができないものでろうか?

復号化は同じとしても、座標変換の結果はタイルの原点をベースにしたものであるから、 全く同じではなく、平行移動した値となる。また、異なるzoomで使う場合には拡大か縮小が必要となる。

しかし、縮小・平行移動を行うとすれば、バイナリレコードの座標を極座標ではなく平面座標としたのと同じである。 以前は極座標としていたものを一時はXY平面座標に変えた。しかし、分かりやすさから極座標に戻した経緯がある。

大陸、本州や北海道など特に大きな数少ないポリゴンに限定すれば以前とは異なるものが考えられる。 例えば、少数の大きなポリゴンに限り、一度読み込んだデータをメモリに常駐させる。 低ズームユーラシア大陸は巨大といっても 184,053ノードに過ぎない。0.5~1.0MBに過ぎない。

アクセス頻度の高いものはアプリ起動時にバックグラウンドでこれらのレコードを読み込めば、 使用時のレスポンスが大幅に改善するであろう。低ズーム用陸地ポリゴンデータは zoom 9用を常駐させた場合、 zoom 9のタイル描画では減算のみ、zoom 8以下では 0.5、0.25、0.125、... の乗算と減算で描画用 x, y 座標を求める。

これらのレコードは ByteCoder が分離し、特別なファイルに格納する。同じように空間検索により抽出するため、 ID は必要としない。ファイル上では可変長バイトコードであるが、メモリに常駐させるときに、 例えば 9/0/0 タイルのレンダリング用 x、y 座標に変換した値で格納する。

これにより、陸地ポリゴンのレンダリング時間がどれほど高速化されるかはやってみないと分からない。 一部の描画のみ、あるいは実際には何も描画されなくても、座標値データをPathに設定するだけでも時間がかかる。

Pathに設定したデータをMatrix演算で迅速に縮小・移動ができるならば、Pathに設定したデータを共用した方がよい。


一度レンダリングしたタイル画像をpngファイルとしてしまっておくのが簡単である。 しかし、タイル単位では、読み込みに時間がかかり、期待したほどのレスポンス向上が図れない可能性がある。

圧縮率が高ければ多くのタイルをメモリに載せられる。どの程度のサイズになるか、やってみないと分からない。

上記のプログラムを実装した結果、Androidタブレットで読み込み時間は10mS以下であった。 ファイルサイズは単色でも 1.46KB であった。 単色(完全に陸地または海)タイルはデータでの管理の方が簡単である。

2024.3.13 並列処理でエラー[解決]

ここまでは一旦レンダリングスレッド数を1としていた。これを2に変えるとエラーが起きた。

複数のスレッドが同じメモリにアクセスしている可能性がある。

まず、次の static byte[] buffer は同時に複数のスレッドで使えない。 I/Oの並列処理は難しいので、readAll はシリアル処理にするか、または、buffer を Renderer に移す必要がある。 この場合、総メモリサイズは 4MB x スレッド数に増える。

とりあえず、ストレージI/Oは並列処理効果が上げにくいことと、メモリ使用量を抑えるために、排他制御とする。

    static byte[] buffer = new byte[4*1024*1024];
    static Block readAll(String file, Block block) {
        // buffer を使う処理
    }

もっと厄介なのは可変長バイトコードの復号化である。ByteBuffer はオーバヘッドが大きいので、 復号化メソッドの引数とはせず、static byte[] ba、static int off をメソッドの外に置いていた。 復号化は並列処理が必須なので、排他制御は使えない。

メモリに置いた可変長バイトコード自体は変更を加えないので、複数のスレッドが排他制御なしに同時アクセスできる。 配列をさす byte[] ba や off はスレッド毎に必要となる。

現在は Block.getSInt() などは Renderer#getSInt() に移動する。

Block.getSInt(Renderer r) とすることもできる。

2024.3.13 タイル9/440/213の空間検索の吟味

まず、奄美大島が描画されている9/439/214タイルを調べる。 多分、num_nodes=342が奄美大島、num_nodes=117が徳之島であろう。 これらの境界ボックスを調べる。

ixOsm=10
 type=2 x=439 y=214 num_nodes=13
 type=2 x=439 y=214 num_nodes=117
 type=2 x=439 y=214 num_nodes=5
 type=2 x=439 y=214 num_nodes=16
 type=2 x=439 y=214 num_nodes=342
 type=2 x=439 y=214 num_nodes=19
 type=2 x=439 y=214 num_nodes=5
 type=2 x=439 y=214 num_nodes=60
 type=2 x=439 y=214 num_nodes=6
 type=3 x=439 y=214 num_nodes=184053
 type=2 x=439 y=214 (12917846,2828226) (12922174,2830709)
 type=2 x=439 y=214 (12917575,2806215) (12935476,2820129)
 type=2 x=439 y=214 (12927173,2801962) (12927935,2802696)
 type=2 x=439 y=214 (12914376,2801380) (12917886,2806937)
 type=2 x=439 y=214 (12913390,2810937) (12906676,2853076)
 type=2 x=439 y=214 (12920487,2800550) (12926916,2805506)
 type=2 x=439 y=214 (12916559,2819200) (12917275,2819830)
 type=2 x=439 y=214 (12887888,2766104) (12903795,2789395)
 type=2 x=439 y=214 (12915937,2810987) (12917653,2812345)
 type=3 x=439 y=214 (-950059,126576) (18000000,7772291)

赤字が奄美大島と思われるが、maxlon が minlon よりも小さくなっている。明らかに誤りである。 恐らく、width、height がそれぞれ 16ビット以下で表現できることから合わせて 32ビットとしたのであろう。 分割したとき、width が 16ビットで、負の値になったのであろう。

赤字の部分を加えることにより、奄美大島が正しく描画されるようになった。めでたしめでたし。

    width = (wh >> 16) & 0xffff;

2024.3.13 ブロック分割および空間検索

分割プログラムおよび空間検索に問題があることがわかった。 カスピ海が多くのファイルに重複しておかれている。 また、奄美大島近辺の空間検索で抽出されるのもおかしい。

日本地図領域では経度、緯度とも正の値であるが、世界地図では -180~180 である。 負の値の処理に問題がないかチェックしたい。


これは誤解だった。

大きなマルチポリゴンはユーラシア大陸であり、カスピ海はその inner polygon である。 ユーラシア大陸の境界ボックスは巨大なため、多くの分割ファイルに含まれる。 日本全体がユーラシア大陸の境界ボックスに含まれるため、低ズームでは、全てのタイルのレンダリング対象となる。

実際には描画が起きない、しかし、可変長バイトコードを復号して、座標変換を行う 無駄がある。 少数の巨大なポリゴンを例外処理で除外すれば、無駄が省ける。しかし、ユーラシア大陸の一部は描画したいことも あるため、例外処理が細かくなる。

また、低ズームの陸地ポリゴンファイルの分割は非効率なため、やめた方がよい。形式上 zoom 0 分割に変更した。

2024.3.13 奄美大島の描画がかける[解決]

奄美大島の描画が低ズームではタイル境界で切れている。例えば、9-440-213 および 9-440-214 で描画されない。 喜界島は描画されている。

空間検索のレコード数は2となっている。多分、奄美大島と喜界島であろう。 しかし、ノード数は 41 と 184053 であった。41 は小さすぎる。

低ズームでのマルチポリゴンはカスピ海だけのはず。 type 2の小さなポリゴンは二つのタイルのレンダリングにまたがる可能性は極めて低い。

 type=2 x=440 y=213 num_nodes=41
 type=3 x=440 y=213 num_nodes=184053
 type=2 x=440 y=214 num_nodes=117
 type=2 x=440 y=214 num_nodes=41
 type=3 x=440 y=214 num_nodes=184053

ByteCoderで type=3 のレコードをチェックしてみる。

どうやら、num_nodes=184053 は低ズームでただ一つのカスピ海のようだ。 そもそも低ズームで小さな島が巨大なノード数になることはない。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -bytecode lands low 1
type 3: c:\map\data4\lands-low1\0 0.dat 184053
type 3: c:\map\data4\lands-low1\0 1.dat 184053
type 3: c:\map\data4\lands-low1\1 0.dat 184053
type 3: c:\map\data4\lands-low1\1 1.dat 184053
type 3: c:\map\data4\lands-low1\2 0.dat 184053
type 3: c:\map\data4\lands-low1\2 1.dat 184053
type 3: c:\map\data4\lands-low1\3 0.dat 184053
type 3: c:\map\data4\lands-low1\3 1.dat 184053

このレコードはカスピ海ではなく、ユーラシア大陸である。その inner polygon がカスピ海である。 ユーラシア大陸は強大なため、分割した場合、複数のブロックに含まれる。したがって、上の結果は正常である。

空間検索結果ではnum_nodes=41は喜界島であろう。9/440/213 では奄美大島ポリゴンが漏れているのであろう。

なぜ漏れたかを詳しく調べたい。

2024.3.12 可変長バイトコードの採用

1週間余りの準備を経て、可変長バイトコードの採用に踏み切った。 まず、陸地ポリゴンのレンダリングを確認した。概ね、正しく描画されるようになったが、

2024.3.4 Paintインスタンスの再利用[Map415]

ほぼ一通りのレンダリングを終えた。問題は、予め、無数の Paintインスタンスを生成していることである。 パフォーマンス上は問題ないとしても、プログラム行数が嵩み、わかりにくい。

色や線の幅は線を描画する直前で設定すればよいので、Paintインスタンスの再利用が簡単である。 点線、破線、鎖線のときには、float配列が必要となる。ライン描画ごとに配列を生成するのを避けるために、

現在は、事前に float 配列を生成しておく。この配列は enum { d20_30, d20_15_20_25, ... } を { 2.0f, 3.0f}, { 2.0f, 1.5f, 2.0f, 2.5 f}, ... といった float配列に変換することにより初期処理で生成している。

これをもう一歩すすめ、enum の要素ごとの Paintインスタンスにしておく。

現在のプログラムは永続的に使うインスタンス作成用である。

    final static int RoundCap  = 0x01;
    final static int RoundJoin = 0x02;
    final static int ButtCap   = 0x10;
    final static int Rounds = RoundCap | RoundJoin;

    static Paint getPaint(int color, float width, int flags, Dash dash) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        if (dash != Dash.d0) {
            float[] dashes = Resource.dashes[dash.ordinal()];
            paint.setPathEffect(new DashPathEffect(dashes, 0));
        }
        if (flags != 0) {
            if ((flags & RoundCap) != 0) paint.setStrokeCap(Paint.Cap.ROUND);
            if ((flags & ButtCap) != 0) paint.setStrokeCap(Paint.Cap.BUTT);
            if ((flags & RoundJoin) != 0) paint.setStrokeJoin(Paint.Join.ROUND);
        }
        paint.setColor(color);
        paint.setStrokeWidth(width * Map.Scale);
        return paint;
    }
これを、次のように修正する。new は存在しないので、動的なメモリ獲得はない。 初期化で、setPathEffect を施した Paintインスタンス配列を準備しておく。 Color、StrokeWidth、StrokeCap、StrokeJoin はライン描画毎に引数で与えられた値を使う。 StrokeCap、StrokeJoin は Round のときは必ず指定する。指定しなかったときは Cap.BUTT、Join.METERとみなす。
    final static int ButtCap   = 0x01;
    final static int RoundCap  = 0x02;
    final static int MiterJoin = 0x10;
    final static int RoundJoin = 0x20;

    // ライン描画用Paintインスタンスの内容を変更する
    static Paint allocPaint(Paint paint, int color, float width, int flags, Dash dash) {
        Paint paint = Resource.paints[dash.ordinal()];
        paint.setColor(color);
        paint.setStrokeWidth(width * Map.Scale);
        paint.setStrokeCap((flags & RoundCap) != 0 ? Paint.Cap.ROUND : Paint.Cap.BUTT);
        paint.setStrokeJoin((flags & RoundJoin) != 0 ? Paint.Join.ROUND : Paint.Join.MITER);
        return paint;
    }

試みに数か所のラインレンダリングを修正し、問題のないことを確認した。 全体の修正は一挙には行わず、描画を確認しながら少しずつ置き換えていく。

2024.3.3 位置情報取得サービスを実装[Map415]

ひとまず、Map3のプログラムをコピーした。

2024.3.2 バス時刻表動作確認

これまでと同じものとした。できればシンプルにしたいが、使い勝手を考えると、 ある程度の複雑さは避けられないであろう。

2024.3.2 バス路線表示完成[Map414]

MapXとはバス路線データの入手方法を根本的に変更した。レンダリングによってバス路線データを入手する方法とした。 入手完了はメインスレッドで検出するようにした。Thread.sleep() ではなく、Handlerを使って完了待ちをした。

レンダリングThreadでの待ちも Thread.sleep()の使用をやめたいが、Handlerはメインスレッドでないと使えないようである。

特にメインスレッドでの Thread.sleep() は避けなければならないようであるが、 レンダリングThreadで何も仕事がないときは、 Thread.sleep() を使っても問題ないようである。

2024.3.1 最近の修正で intermittent が描画されなくなった[解決]

以前は

            } else if (landuse == Val.basin || natural == Val.water) {
                Val intermittent = osm.getVal(Key.intermittent);
                if (zoom >= 15 && (intermittent!=Val.unknown && intermittent!=Val.no)) {
                    osm.fillPolygon(canvas, r, paintWater, bmpIntermittentWater);
                } else {
                    osm.fillPolygon(canvas, r, paintWater);
                }
            }
としていた。intermittent は共通のため前に移動、landuse と natural は分離した。
     case basin:
         osm.fillPolygon(canvas, r, paintWater,
              fIntermittent ? bmpIntermittentWater : null);
         break;

分離前の Map412 から正常に描画されなくなった。つまり、Map411 から Map412 への変更でエラーが入った。 このときから倍率を 2倍から 1.5倍に変えた。2倍に戻してみたが、エラーは解決しなかった。別の変更が原因のようだ。

森林描画などで PorterDuff関係を少しいじったので、これが関係していた。

2024.2.28 ある historic=wayside_shrineの名前が zoom 18 で描画されない

プログラム上は zoom 17のはずだが。他は描画されている。

タイル境界線が関係しているのだろうか。アイコンは描画されるのでレコードは抽出されている。

アイコンの描画を止めても文字は描画されない。

道路の描画を止めると文字が描画された。道路名は描画していないが、バグがあり、 矩形登録が起きているのかもしれない。

【解決】

道路名描画にバグがあり、表示がなくても矩形登録が行われたり、誤った表示(描画)が行われるようになっていた。

2024.2.28 バス路線情報を道路およびバス停レコードに追加した

バス路線CSVファイル(relation id, ref(路線番号), name(バス路線名))は未出力。

まず、バス路線には道路中央に細い青線を描画した。これにはバス路線データは要らない。

バス路線CSVファイルを用いず、relation idのほかに、 bus_route_ref、bus_route_name を含める案もあるが、個々のレコードに含めると バイナリレコードファイルのサイズが大きくなる。 バス路線やバス停を含むブロックファイルに一つといった工夫がいる。プログラムが簡単であればそうしたいが、 複雑になるようなら、一つの独立したバス路線CSVファイルとして、起動時にメモリに読込む。

2024.2.28 座標値の異常が検出されなかった

昨日は座標値に (0x80000000, 0) という異常値があった。 Divider.java でこの異常値があったとき表示するようにしたが、OSMUtil -devide 12 7 japan-high で現れなかった。 昨日は当初、japan-low や japan-mid でもこのエラーがあり、その後収まった。 その後も japan-high ではこのエラーが続いた。昨日、応急処理で、正常範囲の値に修正する処理を入れて実行したが、 この処理をおこなったかどうかを表示せず実行した。 本日、この表示を入れて実行したところ、この異常処理は実行されなかった。二度実行したが問題なかった。

昨日のデータと今日のデータの圧縮ファイルのサイズは同じであるが、 バイナリチェックすると違いがあるため、修正処理が働いたと思われる。

再現性のないエラーであるが、異常値は (0x80000000, 0) という特別な値である。 再び、この異常値が現れたとき、原因を追究する。

2024.2.27 relationのエラーチェックを緩和する

広域森林の描画は大幅に改善した。しかし、依然として、描画されない森林が残っている。

relation処理のバグの可能性がある。

バグが一つ見つかったが、この修正の影響か、japan では relation によるレコードの座標値が異常な値を含むようになった。

2024.2.27 所々、広域森林が描画されていないことに気づいた。かなり、以前のバージョンでも同様であった

現在使用中の地図アプリMapでは問題ないので、Map4の Relation処理に問題があるのであろう。 誤まって、エラーで破棄しているマルチポリゴンの osm_id をチェックしてみよう。

例えば 11753091(1年前に編集)には閉じていないウェイがある。 しかし、軽微なため、標準OSM地図では森林が表示されている。 以前の Relation処理ではこのようなエラーを救済したのかも知れない。 自分自身も建物のレンダリングなどで、つい、軽微なエラーが入り込むことがあった。 このため、軽微なエラーを救済していたかも知れない。 閉ループが完成したあと、もう一つ余分なクリックをすると、余分なノードが一つ加わり、 始点と終点が一致しなくなり、厳密には閉ループでなくなる。一つ前のノードで閉ループが完成しているので、 余分なノードを削除するのが救済策である。あるいは、終点と始点が一致しないが、極めて近ければ、 あと一つノードを追加して閉ループを完成させるといった救済も考えられる。

このようなOSMデータ自体の軽微なエラーではなく、自作のプログラム自体にバグがあるかも知れない。

2024.2.27 zoomが変わった場合、そのタイルのレンダリングを中止するようにした[Map413]

zoom を立て続けに zooom 16 ⇒ zoom 15 ⇒ zoom 14 というように変更した時、最後までレンダリングせず、 途中でうちきるようにした。

2024.2.26 森林描画が way(polygon) と relation(multipolygon) では葉っぱアイコンの描画が異なっている

multipolygonの方が望ましい。

次の修正で polygon と multipolygon が一致した。

    //fillPolygon(r.canvasWork, r, paintWhite);
    fillPolygon(r.canvasWork, r, paint);

2024.2.26 amenity=cinema の名前 109シネマズ が描画されない

グランベリーパークの109シネマズが描画されない。回りにアイコンや文字はないのに。 海老名のToHOシネマズは描画される。

タイルの境界線上にあった。境界を超える描画を禁止していたことが原因だった。 これを解除すると描画されるようになった。

こうした場合、タイルをまたがる文字列は、まれに一方のタイルだけ描画されることになる。 中低ズームでは文字は主に地名となり、不完全な表示になると目立つため、これまで通り、境界を超える描画を禁止した。

2024.2.26 境界線にそって表示する都道府県や市区町村名など

現在は、境界線をベースとする表示になっている。境界線が水平な場合、北側はいいが、 南側は見にくい。南側は逆転したほうがいい。Matrix演算に慣れていないため、それがうまく行かない。

県名と市名などの重なりも回避しているが、やはり、Matrix演算が関係しており、 所々で重なりが発生する。

Matrix演算を完全に理解することにより、これら二つの問題を解決したい。

2024.2.26 Androidタブレットのタイルの倍率を 1.5倍 とする

高精細でなく、パソコン並みの画素数であるが、パソコンより画面サイズが小さいため、 タイルの画素数を 256x256 とすると、文字が小さすぎる。 2倍の 512x512画素では文字が大きすぎる。1.5倍ぐらいがちょうどいい大きさとなる。

レンダリングは問題なかったが、国土地理院地図に対しては整数倍にしか対応していなかった。 256x256画素のイメージを拡大するMatrixを共用していたので、これをやめると対応できた。

Matrixの共用はトラブルのもとになるのでやめることにした。

アイコンについては、256x256画素用を縦横1.5倍する。 SVGアイコンのサイズをプログラムで1.5倍してPNGファイルに変換できるようになれば、 その方がアイコンは綺麗になる。

2024.2.25 あらかたのレンダリングは終わった[Map411]

細かい調整が多く残っているため、数か月かけて分かりやすいものにしたい。 長時間、広い範囲で地図の表示を続けるとメモリ使用量が増加する。 メモリを解放するようにしているが、うまく機能していないようである。

2024.2.25 OSMの int[] pnts を float[] pnts に変更する

Map410までは int[] pnts、Map411から float[] pnts とする。 空間検索では極座標配列を作らず、直接、float[] pnts を生成する。 プログラムはシンプルになった。

境界線より少し内側の閉曲線に問題はなくなった。

2024.2.23 境界線より少し内側の閉曲線計算にバグがある

中ズームでは現れれないが、高ズームでエラーが出る。

以前(1,2年前?)にも経験したが、解決方法の記憶がない。

元の境界線データが閉ループであれば、 二線分の交点座標が計算できる。しかし、元の閉曲線に冗長なデータがあり、二つの線分が一直線であれば、交点が求めらない。 また、2点がダブっているときも交点計算ができない。

2024.2.22 行政境界について

行政境界線に沿っての名称の位置にしばしば誤りがある。

行政境界線自体は wayレコードで描画するのが効率がよい。

名称描画では県名、市名などが重なる。この競合調整は地図アプリで行う方がいいであろう。 境界線は少なくとも2地域で共有されるため、行政境界ポリゴンとするとノード数では全体で2倍以上になる。 また、境界ボックスが大きくなるため、同じ行政境界ポリゴンを複数ブロックに含める必要から、 全体のレコード数が多くなる。塗りつぶしが必要な場合は、ポリゴンレコードがいるが、名称描画だけであれば、 描画位置と角度だけのポイントレコードでよい。間隔に乱数でばらつきを持たせておけば、ぶつかりは減らせる。

2024.2.20 葉っぱアイコンが薄くなるタイルがかなりある[2.26に解決]

森林のアイコンパターンの表示が所々異なっている。現在のプログラムでは、閉ループとなっている森林way が relation の outer polygon としても登録されている場合は、同じポリゴンが出力される。

relationのタグとwayのタグのチェックが必要である。 way の主タグが natural=wood あるいは landuse=forest だけで、 relation に natural=wood か landuse=forest が含まれている場合、way単独での polygonレコードの出力は要らない。

Map3 まではこの重複を排除していなかったため、これを排除するには一工夫がいる。 これまでのプログラムではrelationメンバーであることだけしか分からない。 way のパースで、親relation を知る必要がある。森林の場合、親は一つであろう。一般には親relationは一つとは限らない。

現在は HashMap<Long,Way> を使っているが、入力 HashMap<Long, List<Relation>> と 出力 HashMap<Long,Way> に分けた方が分かりやすいであろう。

入力は森林の場合、relation に natural=wood あるいは landuse=forest があるかどうかだけでよいので HashMap<Long, Integer> とする。将来、森林以外も対象とするかも知れないので、Integer にしておく。 当面、relation に natural=wood あるいは landuse=forest があれば 1 なければ 0 とする。

閉ループではなく、outer polygon の部分 way に対して、タグが存在した場合、wayセクションで単独レコードとしても 出力している。レンダリングで使われることのない無駄レコードとなる。 もし、このような無駄レコードが多いようならば対策が望まれる。


上記の重複は僅かであった。描画上の違いは別の原因かもしれない。例えば、OSMデータ自体に森林の重なりがあるなど。

やはり、葉っぱアイコンが薄くなるタイルがかなりある。共用や排他制御に不備があるのかもしれない。

2024.2.20 地名の描画を実装した

2024.2.20 ポリゴンの中心座標 center を Devider でバイナリレコードに追加

ひとまず、全ての polygon、multipolygon を対象とした。 実際に中心座標を必要とするレコードはごく一部なので、将来はこれらのレコードに限定するかも知れない。

2024.2.20 幅のある道路の描画を詳細化した

レンダリング時間が数日前に比べると、少なくとも3、4割は増えた。 プログラムを複雑にしない範囲で高速化も図りたい。

2024.2.19 Ferryルートを描画した

細部はこれからだが、主要な道路の描画も終わった。 急ぐとプログラムが汚くなりやすいので、ゆっくり進めよう。

2024.2.19 zorder算出

Map3 とは異なり、Map4アプリの空間検索で設定した。 橋やトンネルの情報はレンダリングでも使うため、実質上の算出オーバヘッドは僅少である。

2024.2.19 way_areaを Deviderでバイナリレコードに追加

way_areaを空間検索で算出すると、レンダリング時間が2割ほど増加するため、 バイナリレコードに含めることにした。形式的にはタグと同じ扱いとした。 OSMでは Tag配列には含めず、独立メンバー変数とした。

2024.2.18 skey、svalの導入[Map408]

試行錯誤の結果、タグの取り出し方が決まった。主タグが一つの場合に限り、 それをOSMの Key skey、Val sval にセットする。主タグが一つのケースが多いため、 そうでないときだけ、getVal(Key key)メソッドでタグの値を取り出す。 これにより、タグ取り出しの平均時間を大幅に短縮できた。

2024.2.17 境界線描画エラーか[解決]

建物に薄い紫の境界線が描画されることがある。何もないところに線が描画されることもある。 共用化にバグが入り込んだのであろう。

タグの例外処理には関係がない。中ズームで森林などにも多くの境界線が現れる。 タグ処理により誤まったタグが混入しているのかも知れない。

やはり、タグに問題があった。OSM#getInt()、OSM#getVal() で無効なタグの値を返していた。 探索範囲を 0~num_tags に変えることにより解決した。

2024.2.17 タグパースを簡単化した

buiding=yesなど標準タグが一つのレコードが多いので、例外的に簡単にタグ設定を行った。

2024.2.16 OSMのインスタンス、Paintを共用化[Map407]

タグについては見直す余地がある。

プログラムはさほど複雑化はせず、パフォーマンスは十分に改善した。

OSMの配列は極力再利用する。極端にサイズが大きくなることは稀であるから、 この場合は解放して、次回に必要なサイズを確保する。

2024.2.16 レンダリングメソッドの引数を Paint に変える

プログラムをさほど複雑にしない範囲で new の発行を減らしたい。 レンダリングメソッドの引数は int color の方が使いやすいが、 実際のレンダリングでは Paint が必要になる。HashMapで管理すれば、new は減らせるが、 取り出しにオーバヘッドが加わる。Map3と同様に、引数を Paint に変える。

2024.2.13 中間ズーム修正[Map406]

間引きを緩める(0.0003 → 0.0001)。道路の描画はもっと減らす(primary → trunk)。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=2058320, 最大レコード長=1039104, 平均レコード長=81.3, 平均タグ数=1.1, 平均タグ長=4.2B

2024.2.13 Scale が 2 のとき森林が1/4しか塗りつぶされない

琵琶湖は正常に描画される。

bmpWork、bmpHolesのサイズを 256x256画素固定にしていた。PX x PX 画素に変えると解決した。

2024.2.13 Scale が 2 とか 3 の時バグがある

   java.lang.RuntimeException: Canvas: trying to draw too large(191102976bytes) bitmap.
        at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:266)
        at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
        at com.example.map4.Map.drawBitmap(Map.java:292)

Scale=3のとき、レンダリングでは 256x256画素が GSIに変えると、768x768、2304x2304、6912x6912 のように どんどん大きくなる。明らかに異常であるが、レンダリングでは 256x256 ではなく、768x768 になるべきでは。

まず、matrix.postScale(Map.Scale, Map.Scale); は一度実行するだけでよい。毎回実行していたため、 倍率がどんどん大きくなっていた。

倍率を変えてもタイルサイズが同じ。Map3は正しく動作する。比較して、どこで間違えたか調べよう。

Map3ではタイル座標をセーブ/リカバリしていた。

        editor.putFloat("TX", ((float)CX)/PX);
        editor.putFloat("TY", ((float)CY)/PX);

Map4では CX, CY のセーブ/リカバリに変えた。致命的なエラーは生まないが、Scaleを変えてのデバッグがやりにくい。 これを Map3方式に戻す。

        editor.putInt("CX", CX);
        editor.putInt("CY", CY);
【解決】

Scaleだけ変えて、PX を変更していなかった。 連動するように修正した。これで全て解決した。

        Scale = fSP ? 3 : 2;
        //PX = fSP ? 768 : 256;
        PX = (int)(256 * Scale);

2024.2.13 タグの値を Renderer から OSM に戻した[Map405]

タグの呼び出しは少なくともレンダリングレイヤの数だけ行われる。 レイヤの先頭でタグのパースを行うのはオーバヘッド上問題になる。 Tag[] tags を OSM に置くことにした。

陸地ポリゴンは形式的にタグを持たせているが、レンダリングではタグを使わない。

buildingがレコードの過半数を占めるが、他のレコードと同じく、全てのレンダリングレイヤでタグチェックの対象となる。 buildingやhighwayなど出現頻度の高いタグだけ OSM に独立した変数で持たせることは容易であるが、 タグチェックの負担軽減効果はあまりない。陸地ポリゴンのように、buildingレコードだけのファイルを設ければ、 タグチェックの負担は半減するが、そこまでする必要性はない。 タグチェックの負担が重いようならば、タグに1ビットを割り当てたMap3方式の方がいいだろう。

まだ、多くのレンダリングが残っているが、今のところ、パフォーマンスは Map3より少し劣っているかも知れない。 ただし、タグ呼び出しのオーバヘッドとは限らない。当面はパフォーマンスよりプログラムの分かりやすさに重点をおく。

空間検索 Block.getOSMs() で、出力配列を建物タグのみとその他に分けるのは簡単である。 レンダリングでのタグチェックの負担は半減する。ただし、その採用はタグチェック時間を計測して、 全体に占める比重が高いと分かった場合に限る。

2024.2.12 admin-levelがない

Devider でチェックしたが、ここでは admin_level がタグに含まれている。 Map4アプリでは Tag.parseTags では admin_level が存在する。 RAdmin では admin_levelがない。
【原因判明】

admin_level はint型タグである。標準タグとしてパースしていた。 この修正で所期の結果が得られた。

2024.2.12 タグの値を OSM から Renderer に移した[Map404]

パフォーマンス上は OSM の変数の方がよいが、OSMタグの数が多いため、OSMに置くと、メモリ使用が多くなる。

2024.2.12 間引き誤差修正

// 1度=約100km ⇒ 1m=約0.00001度
double epsilon = Rank=='l' ? 0.005 : 0.0003;
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=1648904, 最大レコード長=487192, 平均レコード長=62.8, 平均タグ数=1.0, 平均タグ長=4.1B
128MB 実行時間: 0.11分

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  3 3 japan-low
レコード数=276765, 最大レコード長=95336, 平均レコード長=37.7, 平均タグ数=1.1, 平均タグ長=4.2B
24.3MB 実行時間: 0.03分

2024.2.11 経験のないエラーのため、2.7時点に戻した。

Can not extract resource from com.android.aaptcompiler.ParsedResource@55823481.

時々バージョン(プロジェクト名)を変えることにする。

2時間ほどかかったが、ほぼ直前まで戻った。 基本的には、まだ、解決すべき課題があるので、これでよいだろう。

2024.2.10 一部の森林が描画されない

面積による絞り込みを緩めたが改善されない。原因は他にあるようだ。

wayで面積が負の場合、reverseした。

procRelation の > 1 を > 0 に変えると正常になった。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=2688637, 最大レコード長=1039100, 平均レコード長=63.5, 平均タグ数=0.8, 平均タグ長=3.2B
実行時間: 0.16分

2024.2.10 Divider.java: 絞り込みタグを増やしたら、描画しなくなった

レコード数が増えたため、オーバフローが起きていた。

2024.2.10 OSMとGSIの切替不調[解決]

タブレットの倍率を 1.0 とした場合は問題ないが、倍率を2とか3にすると、正常に表示されない。 次のようなエラーで止まることもある。

表示回りは Map3 と同じはずだが、どこか違うところがあるのかもしれない。

    java.lang.RuntimeException: Canvas: trying to draw too large(191102976bytes) bitmap.
        at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:266)
        at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
        at com.example.map4.Map.drawBitmap(Map.java:289)
        at com.example.map4.Map.onDraw(Map.java:217)

2024.2.8 低中ズーム用バイナリファイル作成および描画確認

Map3ではパースに時間がかかったため、高ズーム用バイナリファイルから低中ズーム用 バイナリファイルを作成していたが、Map4ではパース時間は短くなったため、 パースで直接、低中ズーム用ファイルを作成できるようにした。 プログラム的にはこの方が分かりやすく簡単である。

細かい調整には月日をかけたいが、大雑把なところでは描画まで確認した。

2024.2.7 道路の実線描画確認

道路の実線描画を確認した。この先のことは急がず、分かりやすさを追求する。

2024.2.7 標高表示

特に問題はないので、Map3のプログラムをコピーした。

2024.2.7 MultiPolygonレコードの描画確認

Relation処理を終了し、穴あき森林の描画を確認した。

これまでのところ、Map3よりかなり簡単化できた。

2024.2.7 Divider.javaの修正

< とするところを <= としていたため、総ファイルサイズが4倍近くになっていた。

2024.2.7 Polygonレコードの確認

建物等の描画でPolygonレコードを確認した。

2024.2.6 Pointレコードの確認

数種類のアイコン描画でPointレコードを確認した。

2024.2.6 全地物のレンダリングに着手した

新しい方法について見通しが着いたので、Pointレコードの描画からスタートする。

2024.2.4/5 タグ部にエラー

まず、Divider でチェックしてエラーが起きることが分かった。 kanto の場合、110,788番目のレコードで初めてエラーが起きた。

Parser でも同じ結果を得た。

Encoder では最初のレコードエラーが起きる。 Parser では、ずっと先のレコードでエラーが起きる。 最初のタグは highway=motorway のはずが、ref='Z03' になっている。 node に ref='Z03' があるが、nodeセクションの最後の ref ではない。

ここでの ref は 'C1' である。なぜ、ref='Z03'となるのか分からない。
【解決】 Encoder.java のタグパースで赤字の部分をなぜかコメントアウトしていた。 これがないと、key なしで val のみとなり、エラーとなる。

  } else if (ikey <= Key.endFloatKey.ordinal()) {
     val = val.replace("m", "");
     try {
         float v = Float.parseFloat(val);
         bbTags.putShort(ikey);
         bbTags.putFloat(v);
     } catch (NumberFormatException e) { 
         try {
             Key s_key = Key.valueOf("s_" + skey);
             bbTags.putShort((short)s_key.ordinal());
             putString(bbTags, val);
         } catch (Exception ex) {
             numTags--;
             System.out.println("Error FloatTag: " + key + "=" + val);
         }
     }

2024.2.4 陸地ポリゴン描画完成

2024.2.4 Block.get()修正

type が 3のときの処理が漏れていた。これにより、低ズームの陸地は見た目では正常になった。

2024.2.3 type が 3 でポリゴン数が1のケースがある(継続)

LandParser ではポリゴン数が2以上のときに、type = 3 としているので、どこかに誤りがある。

LandParser では以下の通りである。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -land low
63912: head=7 polys.length=2

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -land high
214964: head=7 polys.length=4
450524: head=7 polys.length=2
744795: head=7 polys.length=3

Devider で outer/inner polygon のノード数を確認すると以下のようになった。 low zoom用はレコードを間引いているので、high zoom用と一致しなくて不思議はない。 low用の inner polygonのノード数の方が大きいのは意外。簡単化の仕組みで複数のポリゴンがつながって、 隙間に inner polygon が生まれたのであろう。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 1 1 lands-low
nRec=63912, #0 poly_nodes=180831
nRec=63912, #1 poly_nodes=3222

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 5 1 lands-split
nRec=214964, #0 poly_nodes=30385
nRec=214964, #1 poly_nodes=13
nRec=214964, #2 poly_nodes=16
nRec=214964, #3 poly_nodes=23
nRec=450524, #0 poly_nodes=72477
nRec=450524, #1 poly_nodes=11
nRec=744795, #0 poly_nodes=14325
nRec=744795, #1 poly_nodes=26
nRec=744795, #2 poly_nodes=38

2024.2.3 地図ソース変更エラー

OSM地図から国土地理院地図に移り、OSM地図に戻ろうとしたとき、 国土地理院タイル地図がばらばらに表示される。おそらく、うまくレンダリングできず、 古いビットマップが残っており、それが返されるのであろう。

頻繁ではないが、まれに起こる。まだ、LandParserかレンダリングのどこかにバグがある。

2024.2.3 java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

OSM地図から国土地理院地図に移り、OSM地図に戻ろうとしたとき Immutable bitmap passed to Canvas constructorエラーが起きた。

Tile で Canvasオブジェクトを生成し、それを再利用するように変更した。

2024.2.3 タブレットのMapデータをSDカードに移動する

スマホと同様にタブレットのMapデータをSDカードに移動した。

2024.2.3 parseTagsエラー[保留]

zoom 5で陸地ポリゴンを描画させようとしてエラーが起きた。レコード数は 878 のようだが、 parseTagsが延々と続き、数万回に一回程度の割合で下のエラーがおき、やがて、ストップする。

parseTagsが延々と続くのがおかしい。 陸地ポリゴン描画では、parseTagsはいらないので、止めた。

完全ではないが、陸地が描画された。LandParser.java は概ねバグがとれたようだ。

 80235: natural=land;
 java.lang.ArrayIndexOutOfBoundsException: length=88; index=9424
System.err:     at com.example.map4.Tag.parseTags(Tag.java:44)
System.err:     at com.example.map4.Tag.parseTags(Tag.java:32)
System.err:     at com.example.map4.Block.parseTags(Block.java:307)
System.out: 878/51610 time=275mS

2024.2.1 Polygon描画確認

建物、公園、水域等、一部のポリゴン描画を確認した。

2024.1.31 空間検索でエラー

数レコードのタグのパースを成功しているようだが、 やがて、java.nio.BufferUnderflowExceptionが起きた。

文字列タグのパースでエラーが起きる。

2024.1.31 Encoderの出力フォーマットを変更する

 
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
出力レコード数=6954785, Wayレコード=7026273, 出力レコードの平均タグ数=6.614819, 出力レコードの平均Node数=7.2623243
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
出力レコード数=31616319, Wayレコード=32023242, 出力レコードの平均タグ数=6.5017314, 出力レコードの平均Node数=8.089884

Encoderの出力フォーマットを次のように変更する。 文字コードは UTF-8 とする。

 
length, osm_id, lon, lat, {key, val}*                         ----- Node   
length, osm_id, num_nds, {lon, lat}*, {key, val}*             ----- Way  
length, osm_id, num_members, {type, ref, role}*, {key, val}*  ----- Relation       
 
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode kanto
maxId=2822F0E7C, nodes=2A6D42B, ways=6B3661, rels=8715, 総サイズ=0.57GB 実行時間: 3.19分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode japan
maxId=28230265A, nodes=DF15625, ways=1E8A2CA, rels=1E074, 総サイズ=2.77GB 実行時間: 18.70分

2024.1.31 タグを持つ全Wayレコードを出力

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
出力レコード数=6954785, Wayレコード=7026273, 出力レコードの平均タグ数=1.2490556, 出力レコードの平均Node数=7.2623243
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
出力レコード数=31616319, Wayレコード=32023242, 出力レコードの平均タグ数=1.2263029, 出力レコードの平均Node数=8.089884

kanto.dat   建物 175,730KB   全て    325,274KB
japan.dat   建物 764,356KB   全て  1,584,594KB(50B/レコード)

2024.1.30 建物描画テスト成功!!

タブレットで十分なパフォーマンスが得られた。東京の描画でメモリ使用量は 137MBとなった。 class OSM には int[] pnts を持つ。空間検索で、最初に、整数化した lon、lat 列データを格納する。 必要に応じて、面積など、極座標による計算を行う。 その後、レンダリング対象タイル上の相対XY座標(画素単位)の変換して、pnts配列に格納する。 このデータを使ってポリゴンやラインの描画を行う。

2024.1.30 空間検索中間結果

まだ、途中段階であるが、スマホの場合、空間検索時間は 10mS 前後であるので、 まずは問題ない。まだ、レンダリングは行っていない。

 レンダリング開始
 kanto bx=3637 by=1612 z=16
 1255/450578 time=6mS
 レンダリング終了
 RenderThread: tile #8 ready
 tile #10 renderring
 レンダリング開始
 kanto bx=3637 by=1612 z=16
 595/450578 time=12mS
 レンダリング終了

2024.1.30 空間検索を極座標系に戻す

最近はOSMバイナリレコードの座標系は世界XY平面座標としていたが、 これを極座標に戻してみる。

空間検索で絞り込んだレコードについて、必要に応じて、ポリゴンの面積などを求めてから 極座標をその zoom における平面座標に変換する。

記号上は原点(西北端)を (x0, y0)、東南端を (x1, y1) で表す。

空間検索の対象となるタイル座標(左上)を Zoom、X、Yとしたとき、右下(東南端)の座標は Zoom、X+1、Y+1 となる。左上、右下 の極座標は次のメソッドで得られる。

    static double X2Lon(double x, int zoom) {
        return x / Math.pow(2.0, zoom) * 360.0 - 180.0;
    }

    static double Y2Lat(double y, int zoom) {
        double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom);
        return 180.0 / Math.PI * Math.atan(Math.sinh(n));
    }

2024.1.29 Blockで管理データを生成した

思ったほど時間がかからなかった。

 scan 392698records, 130ms
 #1 /storage/emulated/0/Map/kanto12/3638/1612.dat 159ms 15637KB

2024.1.29 Deviderでエラー検出---文字列コード修正

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 kanto
error nRec=1 last nds_length=-1
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 hot
error nRec=1 last nds_length=-6
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 japan
error nRec=1 last nds_length=-1

まず、Parser.java にバグがある。length は2の倍数のはず。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
1: 47, 差分コード=true,length=254
2: 58, 差分コード=true,length=365
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187

文字コードが utf-16 ではなく utf-8 になっている。

半角文字が多いことから、utf-8の方がファイルサイズは少し小さくなる。しかし、サイズが2の倍数になることから、 文字コードは UTF-16としたい。

getBytes("UTF-16")とすべきだった。ただし、末尾に2バイト(多分\x00)が付くようである。 Encoderについても修正した。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode kanto
maxId=2822F0E7C, nodes=2A6D42B, ways=6B3661, rels=8715, 総サイズ=0.56GB 実行時間: 3.75分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode japan
maxId=28230265A, nodes=DF15625, ways=1E8A2CA, rels=1E074, 総サイズ=2.72GB 実行時間: 30.99分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
1: 47, 差分コード=true,length=312
nds_length=192, tags_length=118
2: 58, 差分コード=true,length=462
nds_length=236, tags_length=224
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
1: 47, 差分コード=true,length=312
nds_length=192, tags_length=118
2: 58, 差分コード=true,length=462
nds_length=236, tags_length=224
建物レコード数=19373553, Wayレコード=32023242, 建物レコードの平均タグ数=1.0443347, 建物レコードの平均Node数=6.330969

num_nodes を長さと勘違いしていた。

2024.1.29 バイナリレコード形式修正

バイナリレコード形式を下のように変更した。

head(4),  num_nds(0/2/4), {lon,lat}*, {key,val}*  ... point/line/polygon 
head(4), {num_nds(2/4)}*, {lon,lat}*, {key,val}*  ... multipolygon
head:
  第0,1bit(0x03) 0: point、   1: line、    2: polygon、 3: multipolygon
  第2bit(0x04)   0: 差分座標、1: 絶対座標
  第3bit(0x08)   0: num_nds2バイト、 1: num_nodes4バイト
  下位3バイト   レコード長

これに沿って Parser.java を修正した。Wayによる 建物レコード(polygon)のみ。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
建物レコード数=19373553, Wayレコード=32023242, 建物レコードの平均タグ数=1.0443347, 建物レコードの平均Node数=6.330969

2024.1.27 建物レコード分割完了

とりあえず zoom 12 で分割した。この場合、全ファイルをオープンのまま、 レコード書き込みが行えるため、プログラムは簡単である。

zip圧縮してもパソコンからタブレットへの転送に時間がかかる。

2024.1.24 Dividerでエラー検出

先頭レコードは hot.osm をチェックすると 10ノードであった。 nds_length は 8+4*9 = 44 であるべきが 46 となっている。 Divider.java か前段の Parser.java にエラーがある。あるいは Tag.java にエラーがあるのかもしれない。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide hot
path=c:/map/dat/hot.dat
record length=118
nds_length=46
1394914034,355306637
1394915761,355307513
1394916984,355306064
1394917975,355305066
1394918378,355305283
1394918781,355304949
1394917987,355304516
1394918106,355304252
1394916727,355303400
1394914034,355306637
1394914064,355306638
error last nds_length=-2

タグ数よりもタグ部の長さの方が分かりやすいので、Parserの出力を下のように変更した。 これで今回のエラーは取れた。

Encoder の出力形式と合わない。num_nds、num_members とも合わない。

valに文字列があり、roleも文字列のため、全て、長さにした方がよいだろうか。

{key,val}* の中身は不要で、座標値列データが欲しい場合、 len_tags の方が処理は速い。

もう少し、先に進めてから、決定したい。

length(2/4), len_tags(2), {key,val}*, {lon,lat}*                 ... point/line/polygon 
length(2/4), len_tags(2), {key,val}*, {num_nds(4)}*, {lon,lat}*  ... multipolygon

リファレンス