Google Maps API

Google Maps APIs で、Firefox や Safari で吹き出しが崩れてしまう。

のように、InfoWindow (吹き出し) を表示する部分のスタイルシートで、以下のように横幅 (width) と white-space: nowrap; を設定する。青い部分を追加。 #InfoWindow {
font-family: "Trebuchet MS", Trebuchet, verdana,"MS Pゴシック", arial, sans-serif;
text-align: left;
<font color="blue">width: 180px;
white-space: nowrap;</font>
}
続きを読む

Google Maps API で、地図上に経路 / ルート (パス) を表示する

これは、Perl で、Goolge Maps からルートを検索する方法 に対するブラウザ側の処理の例である。以下のコードを参考にして欲しい。repaint メソッドの中でパスを描画している。前提としては以下の通り。

  1. このスクリプトは呼び出し元の HTML から your_ajax_script.js としてリンクされている。
  2. このスクリプトが呼ばれる HTML には、
    があるとする。
  3. init メソッドは、HTML の onLoad から呼ばれるものとする。

your_ajax_script.js の例

//<![CDATA[ var BASE_URL = 'https://yourdomain.com'; var CGI_FILE = '/path_to_cgi_dir/your.cgi'; var DEFAULT_ZOOM = 5; var map; var currentPoint; // init は、HTML の onLoad から呼ばれるものとする。 function init() { <pre>currentPoint = new GPoint(-122.13930130004883, 37.40248284497232); map = new GMap(document.getElementById('map')); GEvent.addListener(map, 'moveend', function() { // add your code here }); GEvent.addListener(map, 'click', function(o, p) { // add your code here }); GEvent.addListener(map, 'infowindowopen', function() { // add your code here }); GEvent.addListener(map, 'infowindowclose', function() { // add your code here }); map.setMapType(G_MAP_TYPE); map.centerAndZoom(currentPoint, DEFAULT_ZOOM); map.addControl(new GLargeMapControl()); map.addControl(new GMapTypeControl()); map.addControl(new GScaleControl()); } function repaint(xml) {
var list_html = '';

if(xml) {

    // IE の場合はより厳密にチェックが必要、Firefox は xml だけでよい
    if(xml.documentElement) {

        var path = xml.documentElement.getElementsByTagName('path');

        var points = [];
        for (var i = 0; i < path.length; i++) {
            points.push(new GPoint( path[i].getAttribute('lng'),
                                    path[i].getAttribute('lat')));
        } // End of for

        var route = xml.documentElement.getElementsByTagName('route');
        list_html += route.length > 0 ? '<ol>' : '';
        for (var i = 0; i < route.length; i++) {
            list_html += '<li>' + route[i].getAttribute('description') + '</li>';
        } // End of for
        list_html += route.length > 0 ? '</ol>' : '';
    }
}

// このスクリプトが呼ばれる HTML には、<div id="list"></div> があるとする。
document.getElementById('list').innerHTML =  list_html;
map.addOverlay(new GPolyline(points));
} // getPath メソッドは、マウスでボタンが押された、などのイベントに反応する function getPath(lng, lat) {
currentPoint = new GPoint(lng, lat);

post(    'do=path'
    +    '&toX=' + lng
    +    '&toY=' + lat);
} // post メソッドは、例えば getPath メソッドから呼ばれる function post(data) {
var xmlHttp = GXmlHttp.create();

xmlHttp.onreadystatechange = function() {

    if (xmlHttp.readyState == 4) {
        var xml = xmlHttp.responseXML;
        repaint(xml);
    }
}

xmlHttp.open('POST', BASE_URL + CGI_FILE, true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send(data);
} //]]>
続きを読む

Perl で、Excel のデータ (XML) をアップロードして処理する方法

  1. まず、Excel で 以下のように category1、category2、name、address、phone、link、comment1、comment2 というカラム構成のワークシートを作成し、適当にデータを入力、XML 形式で保存しておく。
    注: 住所は米国の住所でないと正しく処理されないことに注意
  2. 次に、以下にあるように gmap_excel.html と gmap_excel.cgi の 2つのファイルを用意する (.gmap_excel.cgi 中のパスの設定などは適宜変更のこと)。
  3. gmap_excel.html から上の XML ファイルをサーバにアップロードする。
  4. サーバ側では Excel の XML データを処理して、gmap_excel.xml というファイルが出力される。
  5. このサーバ側で出力された gmap_excel.xml ファイルは Google Maps API を使った AJAX クライアントから利用できる。

gmap_excel.html のファイルの内容

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" href="styles-site.css" type="text/css" />
</head>
<body>
    <form enctype="multipart/form-data" action="gmap_excel.cgi" method="post">
        <input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
        <table width="600">
            <tr><td>Names file:</td><td><input type="file" name="file" />
                    <input type="submit" value="Upload" /></td></tr>
        </table>
    </form>
</body>

gmap_excel.cgi のファイルの内容

#!/usr/bin/perl use lib "path_to_your_perl_lib/lib/perl5/site_perl"; use strict; use Geo::Google; use CGI; our $XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; # XML ヘッダ our $HOME_DIR = 'your_home_dir'; our $LOCK_DIR = 'path_to_lock_dir/lock'; # do chmod 777 path_to_lock_dir/lock our $LOCK_FILE = 'lock'; our $XML_FILE = 'path_to_your_output_xml/gmap_excel.xml'; # 出力用 XML ファイル名 our $col_index = 0; # Excel シートの現在の列(カラム)インデックス our $row_index = 0; # Excel シートの現在の行インデックス our @data = (); # 出力用データ our @current_data = (); # 現在のデータ # オブジェクト初期化 our $CGI = new CGI; our $GEO = new Geo::Google; # HTML 出力 print qq|Content-type: text/html\n\n|; print qq||; print qq||; print qq|

|; init(); print qq|

|; # 個々のデータのハンドラ sub dataHandler {
my ($expat, $text) = @_;

#  $row_index = 0 と 1 は、Excel で データのヘッダのため必要なし
if($text !~ /^\s+$/ && $row_index > 1) {    #  空白行でなければ
    $current_data[$col_index] = $text;
}
} # 要素の開始 sub startElement {
my ($expat, $tag, %attr) = @_;

if($tag eq 'Row') {            #  <Row>で始まるとき

    $col_index = 0;                #  カラムを初期化
    $row_index++;                #  行を進める
    @current_data = ();            #  現在の行データを初期化
}

if($tag eq 'Data') {            #  <Data>で始まるとき

    $col_index++;                #  列を進める
}
} # 要素の終了 sub endElement {
my ($expat, $tag) = @_;
my ($xml, $html);
my ($lng, $lat, $name, $link, $category1, $category2);
my ($street, $city, $state, $phone, $comment1, $comment2);

#  </Row> のとき
if($tag eq 'Row') {

    push(@data, {    # データに現在の行データを代入

        'category1' => $current_data[1],
        'category2' => $current_data[2],
        'name'      => $current_data[3],
        'address'   => $current_data[4],
        'phone'     => $current_data[5],
        'link'      => $current_data[6],
        'comment1'  => $current_data[7],
        'comment2'  => $current_data[8]
    });
}

#  </Workbook> のとき
if($tag eq 'Workbook') {

    $row_index = -1; #  行数を初期化 ($row_index = 0 はヘッダのため -1 からスタート)
    foreach my $row (@data) {

        $row_index++;

        if($row->{address}) {
            $lng = '';  $lat = '';
            my ($degree) = getDegrees($row->{address});
            if($degree) {
                $lng    = $degree->longitude;
                $lat    = $degree->latitude;
            }
        }

        $name      = $row->{name};
        $link      = $row->{link}
                   =~ /http:\/\/[!#-9A-~]+\.+[a-z0-9]/ ? $row->{link} : '';
        $category1 = $row->{category1};
        $category2 = $row->{category2};
        ($street, $city, $state) = split(',', $row->{address});
        $street    = $street;
        $city      = $city;
        $state     = $state;
        $phone     = $row->{phone};
        $comment1  = $row->{comment1} eq '-' ? '' : $row->{comment1};
        $comment2  = $row->{comment2};

        #  lng と lat が検索できたら (データが入っているということだから)
        if($lng && $lat) {

            $xml    .= "\t<marker\n"
                    .  "\t\tlng       = \"$lng\" lat = \"$lat\"\n"
                    .  "\t\tname      = \"" . $name      . "\"\n"
                    .  "\t\tlink      = \"" . $link      . "\"\n"
                    .  "\t\tcategory1 = \"" . $category1 . "\"\n"
                    .  "\t\tcategory2 = \"" . $category2 . "\"\n"
                    .  "\t\taddress1  = \"$street\"\n"
                    .  "\t\taddress2  = \"$city, $state\"\n"
                    .  "\t\tphone     = \"$phone\"\n"
                    .  "\t\tcomment1  = \"$comment1\"\n"
                    .  "\t\tcomment2  = \"$comment2\"\n"
                    .  "\t/>\n\n";

        } elsif($row_index) {

            $html   .= '<tr><td>'
                    .  "$row_index / $name / $street, $city, $state"
                    .  '</td></tr>';
        }
    }

    $xml    =  $XML_HEADER
            .  "<markers>\n"
            .  $xml
            .  "</markers>\n";

    if($html) {    #  エラーがあれば
        $html    = '<table>'
                 . '<tr><td>***** 以下、緯度経度が検索不能でした。</td></tr>'
                 . $html
                 . '</table>';
    } else {    #  エラーがなければ
        print '<br />done.';
    }

    #  ファイルのオープン
    &file_lock;        #  ファイルロック
    open(XML, ">$HOME_DIR$XML_FILE")
    or die('Cannot open an XML file: ' . "$HOME_DIR$XML_FILE");
    print XML $xml;    #  書き込み
    close(XML);        #  ファイルを閉じる
    &file_unlock;      #  ロック解除

    print $html;
}
} sub getDegrees { # 住所から緯度経度を検索
return $GEO->location( address => shift); # shift = address / リファレンスを返す
} sub init() {
# XMLパーサー作成 ('UTF-8')
my $xml_parser = new XML::Parser(   ProtocolEncoding => 'UTF-8',
                                    Handlers=>{ Start=>\&startElement,
                                                End  =>\&endElement,
                                                Char =>\&dataHandler });
# ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
my $xml = $CGI->param('file');

# XMLパース処理
$xml_parser->parse($xml) or    die "XML error: $xml_parser";

# ファイルクローズ
close($xml);
} sub file_lock {
my $wait = 5;
while (!symlink('.',"$HOME_DIR$LOCK_DIR/.$LOCK_FILE")) {
    if (--$wait <= 0) {
# &error( '現在他の方が使用中です。', # 'しばらくしてから再度ご利用ください。');
    }
    sleep (1);
}
} sub file_unlock {
unlink ("$HOME_DIR$LOCK_DIR/.$LOCK_FILE");
} 1;

gmap_excel.xml 出力例

<?xml version="1.0" encoding="UTF-8"?>
<marker
    lng       = "-122.125690" lat = "37.413570"
    name      = "Tofu House"
    link      = ""
    category1 = "Restaurant"
    category2 = "Korean"
    address1  = "4127 El Camino Real"
    address2  = " Palo Alto,  CA 94306"
    phone     = "650-424-8805"
    comment1  = ""
    comment2  = "Santa Clara の Tofu House と同じ店。"
/>
...
続きを読む

Perl で、住所から緯度経度を取得する方法

Geo::Google モジュールを利用する (米国)。住所を渡せばコーディング的にはほとんど 1行で緯度経度を返してくれる。Google Maps にアクセスしにいって緯度経度を取ってきていてるので、地図データベースは Google Maps のものである。以前紹介した PHP のときのように SOAP も何もいらない (Geo::Google は、内部で LWP::Simple を利用している)。PHP の Geocoder 版は、Geocoder のデータベースに SOAP 経由でアクセスする。少し使ってみた結果、Geocoder のデータベースは少しズレが生じるようである (米国の場合)


use utf8;
use Encode;
use Geo::Google;
use XML::Parser;
#!/usr/bin/perl use utf8; use Encode; use Geo::Google; use XML::Parser; my $lng, $lat; my $address1 = 'your address part 1 here'; my $address2 = 'your address part 2 here'; my ($degree) = getDegrees("$address1 $address2"); if($degree) {
$lng = $degree->longitude;
$lat = $degree->latitude;
} else {
$lng = ''; $lat = '';
} sub getDegrees { # 住所から緯度経度を検索
return $GEO->location( address => Encode::encode_utf8(shift));
                                                    # shift = address / リファレンスを返す
}
続きを読む

ブログ (Movable Type) と Google Map を組み合わせる

今回取り組む内容の仕様としては、以下の通りである。

  • ブログを利用する。
  • ブログに自分の好きな地域(どこかは予め決めておく)の地図を表示する。
  • 地図にはマーカーが表示される。
  • マーカーの情報は AJAX (XMLHttpRequest) の非同期通信により、サーバーから XML ファイルを読み込んだものである。
  • マーカーをクリックすると吹き出しにその地点の情報が出る。
  • 吹き出し中の「続きを読む」リンクをクリックすると、ブログエントリにジャンプする。
  • マーカーは自分独自のアイコンを使う。

まずは、実例をみてイメージをつかんでもらった方がいいだろう。

このサイトで動作している JavaScript は以下のものである。

//<![CDATA[ var BASE_URL = "https://2kids.net"; var GMAP_FILE = "/yas/gmap.xml"; var ICON_DEFAULT = "/yas/images/icon_restaurant.png"; var ICON_SHADOW = "/yas/images/icon_restaurant_shadow.png"; var map; var baseicon; var xmlHttp; function init() { <pre>map = new GMap(document.getElementById("map")); // 地図オブジェクトを生成 baseicon = new GIcon(); // アイコンオブジェクトを作成 xmlHttp = GXmlHttp.create(); // XmlHttpRequest オブジェクトを作成 // 地図を設定 map.addControl(new GLargeMapControl()); // 拡大縮小のボタン map.addControl(new GMapTypeControl()); // 地図と衛星の切り替え map.centerAndZoom(new GPoint(-122.0745, 37.4170), 7); // ベイエリアを表示 // アイコンを設定 baseicon.image = BASE_URL + ICON_DEFAULT; baseicon.shadow = BASE_URL + ICON_SHADOW; baseicon.iconSize = new GSize (16, 16); baseicon.shadowSize = new GSize (34, 20); baseicon.iconAnchor = new GPoint( 8, 8); // <- (icon_width / 2, icon_height) baseicon.infoWindowAnchor = new GPoint( 9, 2); baseicon.infoShadowAnchor = new GPoint(18, 25); // マーカーを設定: XML データを読み込んで表示 xmlHttp.open("GET", BASE_URL + GMAP_FILE, true); xmlHttp.onreadystatechange = showMarkers; xmlHttp.send(null); } function showMarkers() {
if (xmlHttp.readyState == 4) {

    var xmlDoc = xmlHttp.responseXML;
    var marker = xmlDoc.documentElement.getElementsByTagName("marker");
    for (var i = 0; i < marker.length; i++) {
        addMarker(marker[i]);
    } // End of for
}
} function addMarker(xml) {
var point  = new GPoint(xml.getAttribute("lng"),
                        xml.getAttribute("lat"));
baseicon.image = BASE_URL + xml.getAttribute("icon");
var icon   = new GIcon(baseicon);
var marker = new GMarker(point, icon);
map.addOverlay(marker);
var html   = '<div id="InfoWindow">'
           + '<img src="' + baseicon.image + '" alt="icon"> '
           + '<a href="' + xml.getAttribute("site") + '">'
           + xml.getAttribute("name")
           + '</a><br />'
           + xml.getAttribute("street") + '<br />'
           + xml.getAttribute("city")   + '<br />'
           + xml.getAttribute("phone")  + '<br />'
           + '<a href="' + BASE_URL + xml.getAttribute("link") + '">'
           + 'レビューを読む'
           + '</a><br />'
           + '</div>';
GEvent.addListener(marker, "click", function() {
//  marker.showMapBlowup(3);  // 吹き出しに地図が表示される模様。
//  map.centerAndZoom(p, 3);
    marker.openInfoWindowHtml(html);
});  // click のイベントリスナ登録
} //]]>

このスクリプトは以下のようなフォーマットの XML データを利用する。

<marker
    lng="-121.9485855102539" lat="37.318707484418496"
    icon="/yas/images/icon_mexican.png"
    name="Chevys Winchester"
    site="https://www.chevys.com/"
    link="/yas/archives/cat60/#a000761"
    street="550 S. Winchester"
    city=" San Jose, CA 95128"
    phone="(408) 241-0158"
/>

さて、それでは仕組みについてひとつひとつ説明していこう。 今回の動作確認済み環境としては、以下の通りである (2005/10/28 現在)。

<ul><li>Movable Type 3.2 Release 2 (日本語版)</li><li>Firefox 1.0.7 または IE 6.0SP2 または Safari 2.0.1</li></ul>

というわけでここでは Movable Type 3.2 Release 2 (日本語版) と Firefox 1.0.7 を例に取って話を進める。以下は必要な手順である。

  1. アイコンの作成 (Google Map に表示するマーカーのこと)
  2. JavaScript 作成 (Google Map 用)
  3. map.xml の作成 (XMLHttpRequest を使ってマーカーの情報を読み込む)
  4. ブログテンプレートの修正
  5. スタイルシートの設定
  6. ブログエントリの追加

それではまずはアイコンについ説明しよう。

  • Google Map API では、マーカーと呼ばれるポイントを指し示すアイコンが必要となる。Google が用意したデフォルトのマーカーアイコンはオレンジ色の水玉を逆さにしたような感じのものだが、これは自由にカスタマイズできる。
  • アイコンの実体は画像ファイルである。画像のフォーマットは、JPEG、GIF、PNG が使えるが、PNG がいいだろう。
  • なぜなら、アイコンには影を付けられるのだが、この影は、αチャネルを透過にした PNG を使うことになっているからだ (まぁ、どの画像フォーマットを選ぶかは実際は好みの問題だ)。
  • ちなみにアイコンの影は、オリジナルのアイコンに対して45度の傾きを付けるのが、Google からのガイドラインのようである。実際影をつけるのは結構難しかった。私の場合はオリジナル画像をグレー処理し、その後何度かソフト処理をし、ぼかしをかけた…という感じで、この作業に多くの時間を取ってしまった^^;

以下、JavaScript からのアイコン部分生成に関する抜粋だ。要は、GIcon オブジェクトに用意されているいろいろなプロパティを設定してあげれば、自分で作ったアイコンが表示されるのである。

var BASE_URL = "https://2kids.net"; var baseicon; function init() {
baseicon = new GIcon();  // アイコンオブジェクトを作成

// アイコンを設定
baseicon.image            = BASE_URL + "/yas/images/icon_restaurant.png";
baseicon.shadow           = BASE_URL + "/yas/images/icon_restaurant_shadow.png";
baseicon.iconSize         = new GSize (16, 16);
baseicon.shadowSize       = new GSize (34, 20);
baseicon.iconAnchor       = new GPoint( 8,  8);  // <- (icon_width / 2, icon_height)
baseicon.infoWindowAnchor = new GPoint( 9,  2);
baseicon.infoShadowAnchor = new GPoint(18, 25);
baseicon.image = BASE_URL + xml.getAttribute("icon");
} function addMarker(xml) {
var point  = new GPoint(xml.getAttribute("lng"),
                        xml.getAttribute("lat"));
baseicon.image = BASE_URL + xml.getAttribute("icon");
var icon   = new GIcon(baseicon);
var marker = new GMarker(point, icon);
map.addOverlay(marker);
} //]]>

次に肝心の JavaScript であるが、Movable Type のテンプレート作成機能を利用する。Movable Type で、環境設定 / テンプレート-モジュールと選択していき、「モジュールを新規作成」をクリックして以下のように作成(設定)し、保存する。

  • テンプレート名: gmap.js (←ここは実は何でもいい)
  • このテンプレートにリンクするファイル: gmap.js (別のテンプレートから使用するファイル名)
  • モジュールの内容: 冒頭の JavaScript

JavaScript を保存したら、


map.xml

というファイル名で、XML ファイルを保存する。以下に XML ファイルの内容 (属性) を説明しよう (属性は独自に設定したものです)。

<marker
    lng="-121.9485855102539" lat="37.318707484418496" <font color="blue">← ここは、</font><b><font color="orange">経度</font><font color="blue">・</font><font color="red">緯度</font></b><font color="blue">の順で入る</font>
    icon="/yas/images/icon_mexican.png" <font color="blue">← アイコン画像へのルートからのパス + ファイル名 (https:// は、JavaScript 中の最初に記述済み)</font>
    name="Chevys Winchester" <font color="blue">← 吹き出し中の一番上に表示される、マーカーの名前</font>
    site="https://www.chevys.com/" <font color="blue">← マーカーの名前をクリックすると表示されるウェブページ</font>
    link="/yas/archives/cat60/#a000761" <font color="blue">← Movable Type のブログのエントリへのリンク※</font>
    street="550 S. Winchester" <font color="blue">← マーカーの住所 (サンプルはアメリカなので番地が先)</font>
    city=" San Jose, CA 95128" <font color="blue">← マーカーの住所 (サンプルはアメリカなので市町村がが後)</font>
    phone="(408) 241-0158" <font color="blue">← 電話番号</font>
/>

※…「Movable Type のブログのエントリへのリンク」とあるのは、個別ブログについている ID である。個々のブログエントリのソースを見ればわかると思う。
これでスクリプト & XML データの準備が整うので、テンプレートを修正する。環境設定 / テンプレートで、

  • メインページ
  • アーカイブ → カテゴリーアーカイブ
  • アーカイブ → 日付アーカイブ
  • アーカイブ → 個別エントリーアーカイブ

のすべてのテンプレートにおいて、

… <script src="https://maps.google.com/maps?file=api&v=1&key=ここにあなたが取得した Google Map API のキーが入る" type="text/javascript"</script> <script src="/ルートからブログへのパス/gmap.js" type="text/javascript"></script> <body onLoad="init()"> …

のように修正する。onLoad="..." で最初に呼ばれる関数を指定しないと、IE ではうまく動作しなかった。
また、吹き出し中に表示されるリンクをクリックすることによって、個別のブログエントリにジャンプできるように、テンプレートを少し修正する。ブログエントリのタイトルを表示する箇所を見つけ、以下のように... タグをはさむ。

">

"><$MTEntryTitle$>

また、個別エントリから地図に戻れるように、以下の部分の最後に「地図に戻る」リンクを追加する。「#a000835」とある部分は、個別ブログエントリで、Google Map を表示したいブログエントリの ID である。

投稿者 <$MTEntryAuthor$> : "><$MTEntryDate format="%Y/%m/%d"$> | #comments">コメント (<$MTEntryCommentCount$>) | #trackbacks">トラックバック | 地図に戻る

続いてスタイルシートも設定してしまおう。Movable Type のテンプレート→スタイルシートで、スタイルシートの最後に以下の内容を追加する。

#map {
font-family: "Trebuchet MS", Trebuchet, verdana, arial, sans-serif;
text-align: center;
} #InfoWindow {
font-family: "Trebuchet MS", Trebuchet, verdana, arial, sans-serif;
}

こんな具合である。#map と #infoWindow を自由にいじってみて欲しい (#InfoWindow は実は JavaScript 中にそのように設定できるように記述してある)。
最後はブログエントリの追加である。ブログの本文に、以下の1行を追加すれば Google Map が表示される。width と height は地図の大きさなので自由に設定して欲しい。

<div id="map" style="width: 500px; height: 400px;">
続きを読む

Google Maps API

Google Maps API を勉強してみた。サンプルプログラムを見ただけでなんとなく使い方がわかるくらい、非常に簡単に使えるという印象である。その割りに見た目 GUI 的にすごいことをやってそうに見えるモノである。

以下は、スクリプト中のコメントにあるように

  1. クリックしたとろにマーカーを置き
  2. そのマーカーを地図の中心に移動させると共に
  3. 吹き出しに緯度経度を表示する
続きを読む
Google Maps API を購読