グラフィックスによる地図表示


交差点等の地図情報を数値で表しただけでは、その情報を素早く理解することは困難です。では、どのようにしたら、情報を分かり易く、直観的に伝えることができるでしょうか。多くの情報を分かり易く伝えるための方法として、データの可視化(Visualization)がよく行なわれています。可視化とは、情報を図を用いて表現する方法です。例えば、数値データのグラフ 化は可視化の一つです。ここでは、ファイルから読み込んだ交差点情報を「可視化」する方法を 学びます。

OpenGLに関する説明は下記を適宜参照してください:

円,直線の書き方は以前の演習を参照してください:


交差点情報の可視化

前回作成したプログラム ex8-1.c を利用して、 map2.datから交差点情報を読み込み、ウィンドウに地図を描くプログラムを作成しましょう。

このプログラムの概要を以下にまとめます。

座標変換マクロは、交差点の座標をウィンドウ上での座標に変換するために使用します。関数 map_show()以外は、ex8-1.c で使用している関数を流用することができます。

ひな形を こちら に用意しましたので、適宜使用ください。

/* map.c --- 地図の表示 */
/* compile: cc map.c -g -O2 -Wall -Wno-unused-result -o map -I/usr/include/freetype2 -lftgl -lglfw -lGLU -lGL -lX11 -lXrandr -lm  */

#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <GL/glfw.h>
#include <FTGL/ftgl.h>

#define CROSSING_SIZE 100  /* 交差点数=100 */
#define MAX_NAME_SIZE  50  /* 最大文字数50文字(半角) */

/* 座標変換マクロの定義 */
#define ORIGIN_X     -3.0
#define ORIGIN_Y      0.0
#define REAL_SIZE_X   8.0
#define REAL_SIZE_Y   8.0

#ifndef FONT_FILENAME
/* フォントのファイル名 */
#define FONT_FILENAME "/usr/share/fonts/truetype/takao-gothic/TakaoGothic.ttf"
#endif
FTGLfont *font; /* 読み込んだフォントを差すポインタ */

/* データ構造の定義 */
typedef struct {
    double x, y;           /* 位置 x, y */
} Position;                /* 位置を表す構造体 */

typedef struct {
    int id;                /* 交差点番号 */
    Position pos;          /* 位置を表す構造体 */
    double wait;           /* 平均待ち時間 */
    char jname[MAX_NAME_SIZE];   /* 交差点名(日本語) */
    char ename[MAX_NAME_SIZE];   /* 交差点名(ローマ字) */
    int points;            /* 交差道路数 */
    int next[5];           /* 隣接する交差点番号 */
} Crossing;

/* データを格納する変数の定義 */
Crossing cross[CROSSING_SIZE];

/* 円を描画 */
void draw_circle(double x, double y, double r) {


    /* 描画処理を記述しましょう */


}

/* 文字列を描画 */
void draw_outtextxy(double x, double y, char const *text) {
    double const scale = 0.01;
    glPushMatrix();
    glTranslated(x, y, 0.0);
    glScaled(scale, scale, scale);
    ftglRenderFont(font, text, FTGL_RENDER_ALL);
    glPopMatrix();
}

/* ファイルの読み込み */
int map_read(char *filename) {


    /* ex8-1.c を参考に埋めてください */


/* 道路網の表示。新しく作成する部分、そのままでは動きません */
void map_show(int crossing_number) {


    /* 指定した交差点の数をだけ、ウィンドウに交差点(地図)を  *
     * 表示する。まず各交差には赤丸を描き、隣接する交差点への *
     * 道路を直線で描く。                              */

    int i, j;
    double x0, y0, x1, y1;

    for (i = 0; i < crossing_number; i++) {     /* 交差点毎のループ */
        x0 = cross[i].pos.x;
        y0 = cross[i].pos.y;

        /* 交差点を表す円を描く */
        glColor3d(1.0, 0.5, 0.5);
        draw_circle(x0, y0, 0.1);

        /* 交差点の名前を描く */
        glColor3d(1.0, 1.0, 0.0);
        draw_outtextxy(x0, y0, cross[i].jname);

        /* 交差点から伸びる道路を描く */
        /* for文を使って、隣接交差点の数だけ、現時点からの交差道路を記述しよう */
        glColor3d(1.0, 1.0, 1.0);
        glBegin(GL_LINES);
        for (j = 0; j < cross[i].points; j++) {
        
            /* 隣接交差点の位置を書く */
        
            x1 = ???????????????????;
            y1 = ???????????????????;
            glVertex2d(x0, y0);
            glVertex2d(x1, y1);

        }
        glEnd();
    }

}

int main(void) {
    int crossing_number;          /* 交差点数 */

    /* グラフィック環境を初期化して、ウィンドウを開く */
    glfwInit();
    glfwOpenWindow(640, 640, 0, 0, 0, 0, 0, 0, GLFW_WINDOW);

    /* (ORIGIN_X, ORIGIN_Y) を中心に、REAL_SIZE_X * REAL_SIZE_Y の範囲の
         空間をビューポートに投影する */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(ORIGIN_X + REAL_SIZE_X * -0.5, ORIGIN_X + REAL_SIZE_X * 0.5,
                    ORIGIN_Y + REAL_SIZE_Y * -0.5, ORIGIN_Y + REAL_SIZE_Y * 0.5,
                    -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();             /* それ以外の座標変換は行わない */

    /* 文字列描画のためのフォントの読み込みと設定 */
    font = ftglCreateExtrudeFont(FONT_FILENAME);
    if (font == NULL) {
        perror(FONT_FILENAME);
        fprintf(stderr, "could not load font\n");
        exit(1);
    }
    ftglSetFontFaceSize(font, 24, 24);
    ftglSetFontDepth(font, 0.01);
    ftglSetFontOutset(font, 0, 0.1);
    ftglSetFontCharMap(font, ft_encoding_unicode);

    /* マップファイルの読み込み */
    crossing_number = map_read("map2.dat");
    if (crossing_number < 0) {
        fprintf(stderr, "couldn't read map file\n");
        exit(1);
    }

    while (1) {
        int width, height;

        /* Esc が押されるかウィンドウが閉じられたらおしまい */
        
        if (glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED))
            break;

        glfwGetWindowSize(&width, &height); /* 現在のウィンドウサイズを取得する */
        glViewport(0, 0, width, height); /* ウィンドウ全面をビューポートにする */

        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT); /* バックバッファを黒で塗り潰す */

        map_show(crossing_number);                /* 道路網の表示 */
        glfwSwapBuffers(); /* フロントバッファとバックバッファを入れ替える */
    }

    glfwTerminate();

    return 0;
}

可視化処理

関数 map_show() の中で行なう処理は以下の通りです。

for (交差点数分の繰り返し) {

   ・各交差点の位置に円を描く
   ・各交差点の位置に交差点名を表示する

   for (隣接交差点数分の繰り返し) {
         ・隣接する交差点へ直線をひく(交差道路の表示)
   }
}

以上の処理を行なう関数 map_show() を後程以下に示します。

/* 道路網の表示 ( 新しく作成する部分 ) */
void map_show(int crossing_number) {

    /* 指定した交差点の数をだけ、ウィンドウに交差点(地図)を  *
    * 表示する。まず各交差には赤丸を描き、隣接する交差点への *
    * 道路を直線で描く。                              */

    int i, j;

    /* 交差点毎のループ */
    for (i = 0; i < crossing_number; i++) {
        double x0 = cross[i].pos.x;
        double y0 = cross[i].pos.y;

        /* 交差点を表す円を描く */


        /* 交差点の名前を描く */


        /* 交差点から伸びる道路を描く */

    }

}

関数 map_show() 以外は ex8-1.c 中の関数を参考にして、関数 map_show() はテキストや 上記のヒントを参考にして、このプログラムを完成させて下さい。また、座標変換マクロ部分の #define REAL_SIZE_X 8.0 の数値 8.0, #define REAL_SIZE_Y 8.0の数値 8.0 を変えると表示される地図の大きさが変化します (拡大/縮小)。いろいろな数値を試してみて下さい。

時間が来たら完成したプログラムを示しますので、参考にして下さい。

プログラム例


戻る