位置情報サービスのはじめ方

第4回位置情報を表示してみよう

今回は、前回前々回で取得した位置情報を元に、地図を表示する方法を解説します。

Google Maps JavaScript APIで動的な地図を表示する

Webサイトで地図を表示する方法と言えば、多くの方がGoogle MapsのAPIを思い浮かべるのではないでしょうか。APIの公開後、現在も頻繁にアップデートがあり、いくつもの新機能が追加されています。ここでは、Google Maps JavaScript APIの基本的な使い方を解説しつつ、最新のversion 3で追加されたいくつかの面白い新機能を紹介したいと思います。

シンプルな地図の表示

まずは、オーソドックスに地図を表示してみましょう。

以下のようなHTMLを記述するだけで、ページ上に、マウス操作で自由自在に操作できる地図が表示できます。

https://github.com/chris4403/geolocation/blob/master/map-simple.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Map Sample</title>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map { width : 100%; height : 100%}
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
function init(){
  var myOptions = {
    zoom: 8,
    center: new google.maps.LatLng(35.68,139.76),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map"),myOptions);
}
</script>
</head>
<body onload="init()">
<div id="map"></div>
</body>
</html>

11行目が、Google Maps JavaScript APIのversion3をロードしているところになります。

この部分は以下のように記述することもできます。

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("maps","3", {"other_params":"sensor=true"});
</script>

version 2のAPIを利用されていた方は、API Keyがなくなったことに気づかれたでしょうか? version 2まで必要だったドメインごとのAPI Keyの発行が、version 3からは不要になりました。

また、version 3の特徴の1つとして、モバイルデバイスへの最適化というのが挙げられます。1つの大きなjsファイルとしてロードされていたversion 2に比べ、version 3は、必要最低限のものを最初にロードし、必要に応じて順次スクリプトをロードしていく方式になっています。そのため、version 2に比べて、モバイルデバイスでの表示や動作が早く、快適に利用できます。そのほか、地図のコントローラのデザインも最適化されています。

位置情報を取得して自分の位置を地図に表示

それでは、前回作った、定期的に自分自身の位置を取得するサンプルに手を加えて、取得した位置情報を地図上に表示してみます。

https://github.com/chris4403/geolocation/blob/master/geolocation-api-map.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Geolocation API Sample</title>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map {
  width : 100%;
  height : 300px;
}
</style>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery-ui.css">
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("jquery","1.4.2");
google.load("jqueryui", "1.8.2");
google.load("maps","3", {"other_params":"sensor=true"});
</script>
<script type="text/javascript">

$(function(){
  var myOptions = {
    zoom: 8,
    center: new google.maps.LatLng(35.68,139.76),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map"),myOptions);
  
  // geolocationオブジェクトを生成
  var geolocation;
  try {
    if(typeof(navigator.geolocation) == 'undefined'){
      geolocation = google.gears.factory.create('beta.geolocation');
    } else {
      geolocation = navigator.geolocation;
    }
  } catch(e) {}
  if (!geolocation) {
    alert('位置情報は利用できません');
    return;
  }

  var watchId;
  // 位置情報取得に成功したとき呼ばれるcallback関数
  var success = function (position) {
    var lat = position.coords.latitude;
    var lon = position.coords.longitude;
    var latlng = new google.maps.LatLng(lat, lon);
    map.setCenter(latlng);
    var marker = new google.maps.Marker({
        position: latlng,
        map: map
    });
    google.maps.event.addListener(marker, 'click', function() {
      map.setZoom(8);
    });
  }
  // 位置情報取得に失敗したとき呼ばれるcallback関数
  var error = function (error) {
    var result = $('<tr>' +
      '<td>' + error.code + '</td>' +
      '<td>' + error.message + '</td>' +
      '</tr>');
    $('#errorresult').append(result);
  }
  // 位置情報取得時に設定するオプション
  var option = {
    enableHighAccuracy: true,
    timeout : 10000,
    maximumAge: 0
  };
  // 位置情報取得を開始する関数
  function start() {
    watchId = geolocation.watchPosition(success, error, option);
    $('#controler').attr('value','stop');
  }
  // 位置情報取得を停止する関数
  function stop() {
    geolocation.clearWatch(watchId);
    $('#controler').attr('value','start');
  }
  // ボタンに開始/停止関数を紐付け
  $('#controler').toggle(start, stop);
});
</script>
</head>
<body>
<form><input type="button" value="start" id="controler"></form>
<h2>取得成功</h2>
<div id="map"></div>
<h2>取得失敗</h2>
<table id="errorresult">
  <tbody>
  <tr>
    <th>code</th>
    <th>message</th>
  </tr>
  </tbody>
</table>
</body>
</html>

このサンプルは、ブラウザで緯度経度を取得できたら、その位置にマーカーを表示するというものです。

24~29行目

地図を表示するdivをidで取得して、Mapオブジェクトをnewします。

myOptionsという変数で渡しているのは、Mapオブジェクト生成時に渡すオプションの値です。zoomは、ズームレベルの初期値、centerは、地図の中心地点、mapTypeIdは、地図の表示タイプを定数で指定できます。

その他にも、地図をドラッグ可能にするかどうか、マウスホイールのスクロールで地図にズームイン/アウトするかどうかなど、細かい設定を行うことができます。設定できる項目については、APIリファレンスを参照してください。

48~55行目

取得した位置情報を元に、LatLngオブジェクト(位置を指し示すオブジェクト)をnewします。setCenterメソッドにLatLngオブジェクトを渡すことで、表示されている地図の中心点をそこへ移動させます。

LatLngオブジェクトと、Mapオブジェクトを引数に渡して、Markerオブジェクトをnewすることで、地図上にマーカーが表示します。

56~58行目

eventオブジェクトでマーカーのclickイベントをひろい、地図のズームレベルを変更します。

位置情報を取得して自分の位置を地図に表示(その2)

さらに、さきほどのサンプルを改良して、定期的に取得した位置情報を線(polyline)でつないでみます。

こちらのページにアクセスして、⁠start」ボタンを押すと、地図上に赤い線が引かれていきます。PCよりも、スマートフォンなどのモバイル端末で試してみるほうがよく分かります。

ブラウザでこのページを開いたまま、あちこち移動してみると、簡易的なGPSロガーとして利用することができます。

サンプルの変更した箇所は、47~66行目の位置情報を取得したときに呼ばれる関数の部分です。

https://github.com/chris4403/geolocation/blob/master/geolocation-api-polyline.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Geolocation API Sample</title>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map {
  width : 100%;
  height : 300px;
}
</style>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery-ui.css">
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("jquery","1.4.2");
google.load("jqueryui", "1.8.2");
google.load("maps","3", {"other_params":"sensor=true"});
</script>
<script type="text/javascript">

$(function(){
  var myOptions = {
    zoom: 14,
    center: new google.maps.LatLng(35.68,139.76),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map"),myOptions);
  
  // geolocationオブジェクトを生成
  var geolocation;
  try {
    if(typeof(navigator.geolocation) == 'undefined'){
      geolocation = google.gears.factory.create('beta.geolocation');
    } else {
      geolocation = navigator.geolocation;
    }
  } catch(e) {}
  if (!geolocation) {
    alert('位置情報は利用できません');
    return;
  }

  var watchId;
  var latlngs = [];
  // 位置情報取得に成功したとき呼ばれるcallback関数
  var success = function (position) {
    var lat = position.coords.latitude;
    var lon = position.coords.longitude;
    var latlng = new google.maps.LatLng(lat, lon);
    map.setCenter(latlng);
    if (latlngs.length > 0) {
      var prevLatlng = latlngs[latlngs.length -1];
      var pathLatlng = [prevLatlng, latlng];
      var path = new google.maps.Polyline({
        path: pathLatlng,
        strokeColor: "#FF0000",
        strokeOpacity: 1.0,
        strokeWeight: 2
      });
      path.setMap(map);
    }
    latlngs.push(latlng);
  }
  // 位置情報取得に失敗したとき呼ばれるcallback関数
  var error = function (error) {
    var result = $('<tr>' +
      '<td>' + error.code + '</td>' +
      '<td>' + error.message + '</td>' +
      '</tr>');
    $('#errorresult').append(result);
  }
  // 位置情報取得時に設定するオプション
  var option = {
    enableHighAccuracy: true,
    timeout : 10000,
    maximumAge: 0
  };
  // 位置情報取得を開始する関数
  function start() {
    watchId = geolocation.watchPosition(success, error, option);
    $('#controler').attr('value','stop');
  }
  // 位置情報取得を停止する関数
  function stop() {
    geolocation.clearWatch(watchId);
    $('#controler').attr('value','start');
  }
  // ボタンに開始/停止関数を紐付け
  $('#controler').toggle(start, stop);
});
</script>
</head>
<body>
<form><input type="button" value="start" id="controler"></form>
<h2>取得成功</h2>
<div id="map"></div>
<h2>取得失敗</h2>
<table id="errorresult">
  <tbody>
  <tr>
    <th>code</th>
    <th>message</th>
  </tr>
  </tbody>
</table>
</body>
</html>

47行目

取得した緯度経度を元に生成したLatLngオブジェクトを格納していく配列を定義しています。

54~64行目

緯度経度を格納している配列に1つ以上LatLngオブジェクトが入っているとき、1つ前に取得したLatLngオブジェクトと、今回取得したオブジェクトからPolyLineオブジェクトを生成して、マップ上に表示しています。

58行目のLatLngの配列を渡しているところで、47行目で定義した配列を渡すこともできます。ただし、この方法を取る場合は、同じところに線がなんども描かれないように、マップ上に描かれている線を都度削除する必要があります。

マップスタイルの変更

version 3のAPIでは、マップのスタイルをかなり自由にカスタマイズできるようになりました。

百聞は一見にしかずということで、こちらのページを表示してみてください。地図の下に入力フォームが並んでいるので、一番上のadministrative という項目の左のチェックボックスをONにすると、地図の地名が黒く縁どられます。さらに、一番右のvisibilityのセレクトボックスをoffにすると、黒く縁どられていた地名が非表示になります。

図1 左上から、通常の地図、町名を黒く縁どり、町名を削除、その他(駅名や道路など)を削除
図1-1 通常の地図 図1-2 町名を黒く縁どり 図1-3 町名を削除 図1-4 その他(駅名や道路など)を削除
https://github.com/chris4403/geolocation/blob/master/sample-google-maps-api-v3.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Google Maps API V3 Sample</title>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery-ui.css">
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery","1.4.2");google.load("jqueryui", "1.8.2");</script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">

var mapTypeStyleFeatureTypeData = [
["administrative","Apply the rule to administrative areas."],
["administrative.country","Apply the rule to countries."],
["administrative.land_parcel","Apply the rule to land parcels."],
["administrative.locality","Apply the rule to localities."],
["administrative.neighborhood","Apply the rule to neighborhoods."],
["administrative.province","Apply the rule to provinces."],
["all","Apply the rule to all selector types."],
["landscape","Apply the rule to landscapes."],
["landscape.man_made","Apply the rule to man made structures."],
["landscape.natural","Apply the rule to natural features."],
["poi","Apply the rule to points of interest."],
["poi.attraction","Apply the rule to attractions for tourists."],
["poi.business","Apply the rule to businesses."],
["poi.government","Apply the rule to government buildings."],
["poi.medical","Apply the rule to emergency services (hospitals, pharmacies, police, doctors, etc)."],
["poi.park","Apply the rule to parks."],
["poi.place_of_worship","Apply the rule to places of worship, such as church, temple, or mosque."],
["poi.school","Apply the rule to schools."],
["poi.sports_complex","Apply the rule to sports complexes."],
["road","Apply the rule to all roads."],
["road.arterial","Apply the rule to arterial roads."],
["road.highway","Apply the rule to highways."],
["road.local","Apply the rule to local roads."],
["transit","Apply the rule to all transit stations and lines."],
["transit.line","Apply the rule to transit lines."],
["transit.station","Apply the rule to all transit stations."],
["transit.station.airport","Apply the rule to airports."],
["transit.station.bus","Apply the rule to bus stops."],
["transit.station.rail","Apply the rule to rail stations."],
["water","Apply the rule to bodies of water."]
];
var map;
var changeMapEventId;
function changeMap() {
  clearTimeout(changeMapEventId);
  changeMapEventId = setTimeout(_changeMap,500);
}
function _changeMap() {
  var trs = $('#control tr.row');
  var style = [];
  trs.each(function() {
      if($('input.use:checked',this).size()) {
         var obj = {};
         obj.featureType = $('span.itemname',this).text();
         obj.elementType = "all";
         var stylers = [];
         stylers.push({visibility: $('select.visibility',this).val()});
         stylers.push({invert_lightness: ($('select.invert_lightness',this).val() == 'true') ? true : false});
         stylers.push({gamma: $('input.gamma',this).val()});
         stylers.push({lightness: $('input.lightness',this).val()});
         stylers.push({saturation: $('input.saturation',this).val()});
         if ($('input.hue', this).val()) stylers.push({hue : $('input.hue',this).val()});
         obj.stylers = stylers;
         style.push(obj);
      }
  });
  var customStyle = new google.maps.StyledMapType(style, { map: map, name: "custom" });
  map.mapTypes.set("custom", customStyle);
  map.setMapTypeId("custom");
}
function setUpRow(row, data) {
    $('input.use',row).removeAttr("checked").click(changeMap);
    $('span.itemname',row).text(data[0]);
    $('select.etype',row).change(changeMap);
    $('select.invert_lightness',row).change(changeMap);
    $('select.visibility',row).change(changeMap);
    $('div.gamma',row).slider({
      range: "min",
      value: 1.0,
      min: 0.01,
      max: 10,
      step: 0.01,
      slide: function(event, ui) {
        $("input.gamma", row).val(ui.value);
        changeMap();
      }
    });
    $('div.lightness',row).slider({
      range: "min",
      value: 0,
      min: -100,
      max: 100,
      step: 1,
      slide: function(event, ui) {
        $("input.lightness",row).val(ui.value);
        changeMap();
      }
    });
    $('div.saturation',row).slider({
      range: "min",
      value: 0,
      min: -100,
      max: 100,
      step: 1,
      slide: function(event, ui) {
        $("input.saturation",row).val(ui.value);
        changeMap();
      }
    });
    //$('input.hue',row).ColorPicker({flat: true});
  return row;
}

$(function(){
  var myOptions = {
    zoom: 17,
    mapTypeControl : true,
    mapTypeId: google.maps.MapTypeId.ROADMAP,

  };
  map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
  map.setCenter(new google.maps.LatLng('35.0036','135.759'));
  var control = $('#control');
  for (var i = 0, len = mapTypeStyleFeatureTypeData.length ; i < len ; i++) {
    var row = $('#base').clone().removeAttr('id');
    var data = mapTypeStyleFeatureTypeData[i];
    row = setUpRow(row,data);
    control.append(row);
  }
  $('#base').remove();
});
</script>
</head>
<body>
  <div id="map_canvas" style="width:100%;height:300px;background:#efefef;"></div>
  <table>
    <tbody id="control">
      <tr>
        <td> </td>
        <td>name</td>
        <td>Element Type</td>
        <td>gamma</td>
        <td>hue</td>
        <td>invert_lightness</td>
        <td>lightness</td>
        <td>saturation</td>
        <td>visibility</td>
      </tr>
      <tr id="base" class="row">
        <td><input type="checkbox" class="use" ></td>
        <td><span class="itemname">name</span></td>
        <td>
          <select class="etype">
            <option value="all">all</option>
            <option value="geometry">geometry</option>
            <option value="labels">labels</option>
          </select>
        </td>
        <td><div class="gamma" style="width:100px;"></div><input type="hidden" class="gamma" value="1.0"></td>
        <td><input type="text" class="hue"></td>
        <td>
          <select class="invert_lightness">
            <option value="true">true</option>
            <option value="false">false</option>
          </select>
        </td>
        <td><div class="lightness" style="width:100px;"></div><input type="hidden" class="lightness" value="1.0"></td>
        <td><div class="saturation" style="width:100px;"></div><input type="hidden" class="saturation" value="1.0"></td>
        <td>
          <select class="visibility">
            <option value="on">on</option>
            <option value="off">off</option>
            <option value="simplified">simplified</option>
          </select>
        </td>
      </tr>
    </tbody>
  </table>
  </div>
</body>
</html>

コードを解説します。長いコードになっていますが、実際にマップのスタイルを変更しているのは、50~72行目の部分です。52~68行目でページ中の入力フォームからマップのスタイルオブジェクトを組み立てて、69行目でStyledMapTypeオブジェクトを生成し、70、71行目でマップにスタイルを設定しています。

このStyledMapTypeを利用すると、白地図に近い地図や、道路のみ表示された地図、Webサイトのカラーリングに合わせた地図など、表示する地図にバリエーションを持たせることができます。

マップのローカライズ

Google Mapsは全世界で使われているだけあって、ローカライズの仕組みがしっかり整えられています。Google Mapsは読み込まれたブラウザの言語設定やドメインによって、言語や地域を適切な値で取り扱ってくれますが、⁠どんな国の人が見に来ても日本語で表示したい」というような特別な要求に対して、Map読み込み時のURLのパラメータを指定することで、特定の言語や地域に固定できるようになっています。

言語のローカライズ

マップの読み込み時に、languageパラメータで言語を指定することで、特定の言語に固定することができます。以下に、同じ地域を英語と日本語の設定で表示した時の画像例を見てみると、英語設定の時は、⁠渋谷」などの地名の上にアルファベットでの表記が加わっていることが分かります。

図2 渋谷周辺を日本語と英語それぞれの言語設定で表示した時
画像 画像

地域のローカライズ

マップの読み込み時に、regionパラメータで地域を指定することで、ジオコーディング時の検索エリアを指定することができます。検索対象を日本に絞ることで、例えば中国に同じ地名の場所が存在するとき、ジオコーディングの結果としてちゃんと日本の地名の場所が返ってくるようになります。

Google Static Maps APIで静的な地図を表示する

これまでは、JavaScriptによる動的な地図の生成について解説しました。ここでは、例えばフィーチャーフォンのように、JavaScriptが利用できない、あるいは利用できても端末のスペックが動的な地図の利用に向いていない環境での、地図の表示方法を解説します。

そのような環境の場合には、Google Static Map APIを利用します。その名の通り、static(静的)な地図画像を取得するAPIです。imgタグのsrc属性に特定のURLを指定することで、地図画像を表示することができます。

フィーチャーフォンに限らず、PCサイトなどでも、特にマウスで操作させる必要がない場合は、こちらの地図を利用しても良いでしょう。

こちらのAPIも、version 2が公開され、API Keyでの指定が不要になりました。version 1では、API Key毎に生成できる画像枚数が制限されていましたが、version 2からは画像枚数の制限が訪問者毎に変わり、利用しやすくなっています。

それでは、実際に地図を表示してみましょう。以下のURLをimgタグのsrcに指定すると、東京駅周辺の縦横300pxの画像が表示されます。

図3 東京駅周辺の縦横300pxの画像が表示される
http://maps.google.com/maps/api/staticmap?size=300x300&zoom=15&center=35.68,139.76&sensor=false

sizeは画像サイズを、zoomは地図のズームレベルを、centerは地図の中心点を、sensorはセンサーを利用するかどうかを指定しています。

日本の携帯電話で利用する際は、docomo端末がpng画像を表示できないため、typeパラメータにgifなどを指定する必要があります。

その他、地図画像上にマーカーを表示したり、PolyLineを表示したりといったことも、パラメータに値を含めるだけで簡単に実現できます。

豊富なサンプル例が、公式ページに紹介されているので、興味がある人はざっと見てみるとおもしろいと思います。

次回予告

今回は、位置情報を地図で表示する方法を解説しました。

次回は、⁠位置情報を保存する」というテーマで、DBなどに位置情報を保存する方法を解説したいと思います。

おすすめ記事

記事・ニュース一覧