トップ地図アプリGIS_PC > 曲線に沿って文字を描画する
曲線に沿って文字を描画する

はじめに

道路名や川名など地図には曲線に沿って描く文字(列)が多い。車道の場合、カーブは緩やかなことが多いが、 都道府県や市区町村などの境界線に沿って(境界線の内側に)描く文字列の場合、曲がりくねっていることがある。 このような場合、変化が激しい区間は避けて描画する。

「曲線に沿って一つの文字を描画する」こと自体は「曲線に沿ってアイコンを描画する」と同じ手法が使える。 違いは、同じアイコンを連続するのではなく、複数の文字画像を一塊として、文字列と文字列の間にスペースを空ける ことである。また、すでに描画された別の文字やアイコンと一部でも重なるときは描画しない。 この二つのことからプログラムが複雑になる。

文字列を小さな画像配列を作成する

まず、文字列を画像配列に変換する。縁取りは後回しとする。

static Image[] str2img(String text, int color, float size, float halo) {
    BufferedImage[] imgs = new BufferedImage[text.length()];
    Font font = new Font("MS ゴシック", Font.BOLD, (int)size);
    BufferedImage img0 = new BufferedImage(4, 4, BufferedImage.TYPE_4BYTE_ABGR);
    Graphics2D g0 = img0.createGraphics();
    g0.setFont(font);
    FontMetrics fm0 = g0.getFontMetrics();
    int h0 = fm0.getAscent() + fm0.getDescent();    // 文字高さ
    for (int n = 0; n < imgs.length; n++) {
        String chr = text.substring(n, n+1);
        int width = fm0.stringWidth(chr);           // 文字長
        imgs[n] = new BufferedImage(width, h0, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D g = imgs[n].createGraphics();
        g.setFont(font);
        g.setColor(new Color(color, true));
        g.drawString(chr, 0, h0-fm0.getDescent());
    }
    return imgs;
}

タイルにテスト描画してみる。

g.setColor(Color.RED);
g.setStroke(new BasicStroke(1));
Image[] imgs = OSM.str2img("通り Playground", 0xffff0000, 18, 1.0f);
int x = 0;
for (int i=0; i < imgs.length; i++) {
    int w = imgs[i].getWidth(null);
    int h = imgs[i].getHeight(null);
    g.drawImage(imgs[i], x, 0, null);
    g.draw(new Rectangle2D.Double(x, 0, w, h));
    x += w;
}

文字の描画位置を算出するメソッド(seek)を作成する

現在の線分番号およびその線分上のオフセット、線分の角度、線分の長さなどは class Renderer の変数とする。

メソッドの引数は幅と画像の高さとする。幅は画像の幅か文字列描画間隔である。 画像の幅のときは高さは正の値、描画間隔のときは高さを0とする。

文字画像幅のときは、競合チェックを行う。

メソッドの戻り値は、「描画可」、「描画不可(競合等)」、「終端を超えた」の三つである。 「終端を超えた」場合、それ以上の seek は無用であるが、「描画不可(競合等)」の場合には、seek を続ける。

文字列がタイルをまたぐときは、まれに、一方のタイルで描画でき、他方のタイルで描画できないことが 起こりうる。文字列の描画間隔は描画されてもされなくても同じとする。

一つの文字が二つの線分にわたることがある。二つの線分の角度が180度より小さい場合、 隣り合う二つの文字の一部が回転により重なることがある。 文字間にスペースを置くことにより重なりを回避できるが、それなりのプログラムがいるので、 重なりが起きそうな角度のときは描画不可とする。

重なりチェックでは、厳密に回転を考慮するのは面倒であり、判定時間も増えるので、 回転は無視する。回転を考慮して、少し大きい矩形を登録してもよい。

判定は、すでに描画されている文字やアイコンとの競合チェックであり、 今回の文字列中の文字相互の競合チェックは無用である。描画した場合、少し大きい矩形を登録した場合、 重なりがあるが、問題はない。


描画フェーズでもこのseekメソッドを使用する。

リファレンス

[1] TextViewの文字をフチありにする