11.地図操作画面

初めに

 地図操作画面は、 Web Form プログラムで作成したものを MVC でもほぼ流用して利用しています。そのため、そのクラスの拡張子は aspx となります。
 地図操作画面には、次の4つがあります。

  • 緯度経度指定画面
    地図を表示し、任意の位置でマウスクリックすることで、その位置の緯度経度を指定することができます
  • エリア指定画面
    地図を表示し、三回以上のマウスクリックにより、閉多角形-これを、エリアと呼びます-を指定することができます
  • マーカ表示画面
    地図上にフィールドワークデータの緯度経度にマーカを表示する、表示専用の画面です
    マーカは大分類で決まる色ごとのマーカになります
    マーカをクリックすると、詳細説明とイメージファイルを個別に確認できるダイアログが表示されます
  • 路線表示画面
    地図上に、出発地点から目標地点までの路線図を表示する、表示専用の画面です

 上記の地図操作画面が機能するために、 Web サービスを1つ利用しています。その Web サービスである汎用ハンドラの説明から始めます。

汎用ハンドラ

 汎用ハンドラがなぜ必要なのかを議論しましょう。次のような操作手順を考えてください。

  • 緯度経度が必要な画面-親画面-があります(下記画面1)
  • 親画面の「Map」ボタンで、緯度経度指定画面-子画面-を表示します
  • 子画面で、マウスクリックにより緯度経度を指定します(下記画面2)
  • 子画面で、 OK ボタンのクリックにより緯度経度を確定します
  • 子画面で確定した緯度経度が、親画面に反映されます(下記画面3)
画面1
画面2
画面3

 子画面で、 OK ボタンがクリックされたとき、以下のような処理を行ったとしても、上記の処理手順を満たすことができます。

					
1     <script>
2         $(function () {
3             $('#mapOkButton').click(function () {
4                 if (親画面が表示されているか)
5                 {
6                     親画面の緯度の項目 = 子画面の緯度の項目値;
7                     親画面の経度の項目 = 子画面の経度の項目値;
8                 }

9                 子画面を閉じる;

10                return true;
11            });
12        });
13    </script>
					

 ここで問題です。

					
      この状態で、親画面の「Map」ボタンをクリックしたとき、子画面はどうなっているべきか?
					
					

 親画面には、すでに緯度経度の値が表示されています。そこで、子画面が表示されたときには親画面の緯度経度が示す位置にマーカが表示されている 状態から始まるのが望ましい動きになります。
 その動きを実現するためには、上記の処理ではできません。なぜなら、親画面の緯度経度の値はブラウザに表示されているだけです。 Web サーバでは今どのような 値がブラウザに表示されているかは分からないからです。そこで、次のような処理にする必要があります。

					
1     <script>
2         $(function () {
3             $('#mapOkButton').click(function () {
4                 子画面の緯度経度を Web サーバに送信する;

5                 if (親画面が表示されているか)
6                 {
7                     親画面の緯度の項目 = 子画面の緯度の項目値;
8                     親画面の経度の項目 = 子画面の経度の項目値;
9                 }

10                子画面を閉じる;

11                return true;
12            });
13        });
14    </script>
					
					

 子画面で入力した緯度経度の値を Web サーバに送るために、4行目の処理が必要になります。 Web サーバでは送られてきた緯度経度の値を受信し、 最後に受信したその値を利用者ごとに記憶しておく必要があります。
 この Web サーバの処理を Web サービス-汎用ハンドラ-として用意しました。値を記憶するにはセッション変数を利用しています。
 この汎用ハンドラは SessionHandler のクラス名で作成しました。そのプログラムを以下に示します。豊富なコメントが付いていますから、理解は容易と思います。

					
1     /// <summary>
2     /// 起動時の POST パラメータでセッション変数を更新するハンドラである
3     /// 起動方法は、以下の形式である
4     /// SessionHandler.ashx?key1=keyValue1&value1=valueValue1&…
5     /// ここで、keyN と valueN はペアであり、どちらも英小文字、番号は1からの連番であること
6     /// keyN valueN どちらかが存在しない時点で処理を終了する
7     /// セッションの更新は、以下のどちらかとなる
8     /// 直接更新法
9     ///   キー keyN の値 keyValueN は必ず、「d_」(ダイレクトの意味)で始まる文字列であること
10    ///   この場合、以下の方法で更新する
11    ///     session[keyValueN.Substring(2)] = valueValueN
12    ///   例:
13    ///       SessionHandler.ashx?key1=d_ID&value1=123456   // の場合
14    ///       sessin["ID"] = "123456";                      // と更新する(キーから d_ を除く)
15    /// 間接更新法
16    ///   キー keyN の値 keyValueN が「d_」で始まらないこと
17    ///   この場合、以下の方法で更新する
18    ///     session[keyValueN] = Request.Params[valueValueN]
19    ///   例:
20    ///     SessionHandler.ashx?key1=ID&value1=inputID    // の場合で、以下のタグが POST にあった場合
21    ///     <input id="inputID" value="123456" />
22    ///     sessin["ID"] = "123456";                      // と更新する
23    ///   
24    /// 2つの方法の使い分け方
25    ///   GET パラメータとして引き渡すのに十分短い場合は、どちらの方法でもよい
26    ///   パラメータが長くて、POST として引き渡す必要がある場合に、間接更新法を使用する
27    ///   この場合、valueValueN は POST で渡される、例えば <input> タグの id 属性値を意味する
28    /// </summary>
29    public class SessionHandler : IHttpHandler, IRequiresSessionState
30    {
31        public void ProcessRequest(HttpContext context)
32        {
33            string key, value, val;
34            for (int i = 1; ; ++i)
35            {
36                key = context.Request.Params["key" + i];
37                value = context.Request.Params["value" + i];

38                if (key == null || value == null)
39                    break;

40                if (key.StartsWith("d_"))
41                {
42                    key = key.Substring(2);
43                    val = value;
44                }
45                else
46                {
47                    val = context.Request.Params[value];
48                    if (val == null)
49                        break;
50                }

51                context.Session[key] = val;
52            }

53            context.Response.ContentType = "text/plain";
54            context.Response.Write("OK");
55        }

56        public bool IsReusable
57        {
58            get
59            {
60                return false;
61            }
62        }
63    }
					

 29行目: IRequiresSessionState インターフェースを継承しないと、セッションの共有ができませんので注意が必要です

 ソースをご覧いただけはお分かりのように、このハンドラは常に成功して "OK" の文字列を返します。書式の不正を検査する必要があるようでしたら、 その結果に応じたステータスコードを返す必要があります。
 ブラウザからは Ajax を使用して Web サーバと通信しています。

 ここでは、緯度経度指定画面に限定して汎用ハンドラが必要なことを説明しました。実は、4つの地図画面すべてで使用していることがあります。  Google MAP API を使用して地図を表示するためには、地図を表示するエリアとして <div> タグを用意する必要があります。 そして、そのタグの表示幅と高さを指定する必要があります。表示幅は利用可能な幅100%で利用していますが、幅と高さの両方を相対値にはできないので、高さは絶対値を指定する必要があります。 そこで困ったのが、ブラウザの表示高は使用しているモニタ機器の解像度によりさまざまなため、固定値を使用することができないということです。
 そこで、一番最初に画面を表示したときに、使用している機器のモニタの解像度を Web サーバに送信することにしました。地図操作画面では ブラウザをフルスクリーン表示したときに、地図が目いっぱい表示されるように、かつ余計なスクロールバーが付かないように工夫してあります。

 ブラウザから表示機器の解像度を Web サーバに送信する Ajax プログラムを示します。参考にしてください。

					
1     <script>
2         function initializeCheck() {
3             var url = "/Fieldwork/Ajax/SessionHandler.ashx?key1=d_ScreenHeight&value1=" + screen.availHeight;
4             url = url + "&key2=d_OuterHeight&value2=" + window.outerHeight;
5             url = url + "&key3=d_InnerHeight&value3=" + window.innerHeight;

6             var request = $.ajax({
7                 type: "POST",
8                 url: url,
9             });
10        }
11    </script>
					
					

 3行目:モニタの物理的な解像度です
 4行目:ブラウザのウインドウの高さの解像度です
 5行目:ブラウザのウインドウのメニューなどを含まない高さの解像度です
 6~9行目: Ajax 送信の本体です

緯度経度指定画面

緯度経度指定画面

 初期状態の画面(下記画面4)とマウスをクリックした後の画面(下記画面5)を示します。(画像をクリックすると、拡大表示します)

画面4

画面5

画面要素-PointMapPage.aspx

 

       				
1     <!DOCTYPE html>
2     <html lang="ja">
3     <head runat="server">
4        <meta charset="utf-8">
5        <title>緯度経度</title>
6        <link href="../Contents/bootstrap.min.css" rel="stylesheet" />
7        <link href="../Contents/common.css" rel="stylesheet" />
8        <script src="../Scripts/jquery-3.1.0.min.js" type="text/javascript"></script>
9        <script src="../Scripts/bootstrap.min.js" type="text/javascript"></script>
10    </head>
11    <body>
12        <form id="MainForm" runat="server">
13            <div>
14                <div id="map"></div>
15                <div class="text-center">
16                    <button id="mapOkButton" type="button" class="btn btn-primary btn-sm" disabled="disabled">O K</button>
17                        
18                    <button id="mapCancelButton" type="button" class="btn btn-primary btn-sm">Cancel</button>
19                </div>
20            </div>
21            <asp:TextBox ID="latLng" runat="server" type="hidden" />
22            <asp:TextBox ID="latitude" runat="server" type="hidden" />
23            <asp:TextBox ID="longitude" runat="server" type="hidden" />
24        </form>
25    </body>
26    </html>
       				
       				

 21~23行目:緯度経度の値を格納する項目です

 html を見て分かるように、 Google MAP API に関した javascript が全く見当たりません。これらの javascript は静的に用意することができない 多くの項目から構成されます。そのため、<srcipt> タグを動的に生成して、この aspx ファイルに埋め込んでいます。

コード要素-PointMapPage.aspx.cs

  PointMapPage.aspx.cs のうち、 Page_Load() メソッドの実装を示します。このメソッドで、動的に <script> タグを生成しています。

       				
      PointMapPage.aspx.cs の一部分
1     protected void Page_Load(object sender, EventArgs e)
2     {
3         String name = Request.Params["SessionName"];
4         if (string.IsNullOrEmpty(name))
5             name = "SessionName";

6         SessionName = name;

7         String lat, lon;
8         lat = Request.Params["Latitude"];
9         if (string.IsNullOrEmpty(lat))
10            lat = "InputLatitude";
12        lon = Request.Params["Longitude"];
13        if (string.IsNullOrEmpty(lon))
15            lon = "InputLongitude";

16        int height;
17        string screen, outer, inner;
18        screen = (string)Session["ScreenHeight"];
19        outer = (string)Session["OuterHeight"];
20        inner = (String)Session["InnerHeight"];
21        if (string.IsNullOrEmpty(screen) || string.IsNullOrEmpty(outer) || string.IsNullOrEmpty(inner))
22            height = 800;
23        else
24        {
25            // スクリーンの高さから、メニューバーなどの高さを計算する
26            // メニューバーなどを含んだ高さからクライアント領域の高さを最初に計算する(outer - inner)
27            // その高さには、OSのメニューバー、ブラウザのメニューバー、ツールバーがある
28            // そのブラウザのメニューバーは表示する際に消去している
29            // その分を「OK」ボタンなどの領域として使用する
30            int diff;
31            diff = int.Parse(outer) - int.Parse(inner);

32            height = int.Parse(screen) - diff;
33        }

34        string val;
35        String[] args = new String[9];
36        args[0] = "" + height;
37        args[1] = ConfigurationManager.AppSettings["mapKey"];
38        args[2] = (val = ConfigurationManager.AppSettings["centerLatitude"]) == null ? "38.018365" : val;
39        args[3] = (val = ConfigurationManager.AppSettings["centerLongitude"]) == null ? "138.368090" : val;
40        args[4] = (val = ConfigurationManager.AppSettings["zoom"]) == null ? "11" : val;
41        args[5] = MakeMarker();
42        args[6] = SessionName;
43        args[7] = lat;
44        args[8] = lon;

45        string format = string.Format(script, args);
46        ClientScript.RegisterStartupScript(this.GetType(), "mapScript", format);
47    }
       				

 3~6行目:地図から入力された緯度経度を Web サーバに送信するときに使用するセッション名を生成します
 7~15行目:地図表示を終了するときに、緯度経度の値を親画面のどの項目に返せばよいかの名前を生成します
 16~33行目:地図を表示する <div> タグの高さを計算します。 Session["ScreenHeight"] などに関しては、 こちら を参照してください
 36行目:地図表示の高さです
 37行目: web.config ファイルから Google MAP API のキーを読み込みます
 38行目:地図を表示するさいの中心座標の緯度を読み込みます
 39行目:地図を表示するさいの中心座標の経度を読み込みます
 40行目:地図を表示する際のズーム値を読み込みます
 41行目:初期状態でマーカーが必要な場合、そのための javascript を生成します
 42行目:セッション変数名です
 43~44行目:地図から入力された緯度経度を Web サーバに送信するときに使用する緯度と経度の名前です
 45行目:書式文字列に9個の変数を埋め込みます
 46行目: <script> タグをを動的に埋め込みます

エリア指定画面

 画面要素とコード要素は、緯度経度指定画面と同様の内容ですから省略します。

エリア指定画面

 初期状態の画面(下記画面6)、エリアの定義を開始した後の画面(下記画面7)、エリア入力の途中の画面(下記画面8)、 エリア定義の完了後の画面(下記画面9)を示します。(画像をクリックすると、拡大表示します)

画面6
画面7
画面8
画面9

マーカ表示画面

 画面要素とコード要素は、緯度経度指定画面と同様の内容ですから省略します。

マーカ表示画面

 初期状態の画面(下記画面10)とマーカをクリックした後の画面(下記画面11)を示します。(画像をクリックすると、拡大表示します)

画面10
画面11

路線表示画面

 画面要素とコード要素は、緯度経度指定画面と同様の内容ですから省略します。

路線表示画面

 路線表示画面を示します。(画像をクリックすると、拡大表示します)

画面12