Let's make something

PHPやJavaScriptを学びながら、サイトを作ってみようと思う今日この頃

地図の表示領域にあるマーカーだけを動的に読み込んで表示

今回は、動的に地点データを読み込みんでマーカーを表示し、地図とは別に地点データをリストを表示します。
前回はすべてのデータを読み込んでいましたが、今回は表示領域にあるデータだけを読み込みます。
そのため、今回からやっとPHPも使います。

今までは、ロードした際のみにマーカーを読み込んでいましたが、今回はスクロールやズームが終わったときにマーカーをリフレッシュする必要があります。


そのため、GoogleMapのオブジェクトを生成した後に、ズームや移動の後に地図がidle状態になると発生するイベント'idle'のリスナーを追加し、
このリスナー内でマーカーの再読み込みを行います。

イベントは、'idle'以外にも色々ありますが、ここにまとまっています。

  // 地図変更時のリスナーの追加
  google.maps.event.addListener(gmap, 'idle', function(){
    refleshMarker();
  })


マーカーのリフレッシュは、地点の表示とマーカー表示を削除します。
その後に、地図の表示範囲(北東・南西の緯度経度)を取得し、マーカー取得のphpを呼び出します。
そして、戻ってきた地点の表示とマーカー表示を表示します。

  /**
   * マーカー削除
   */
  var clearMarkerData = function(){
    var i;
    //表示中のマーカーがあれば削除
    if(markerArray.length > 0){
      //マーカー削除
      for ( i = 0; i <  markerArray.length; i++) {
        markerArray[i].setMap();
      }
      markerArray.length = 0;
    }
  }
    
  /**
   * マーカーのリフレッシュ 
   */
  var refleshMarker = function(){
    //リストの内容を削除
    $('#marker_list > ol').empty();
  
    //マーカー削除
    clearMarkerData();
    
    //地図の表示範囲を取得
    var bounds = gmap.getBounds();
    var northEastLatLng = bounds.getNorthEast();
    var southWestLatLng = bounds.getSouthWest();

    //jsonファイルの取得
    $.ajax({
      url: 'php/get_marker.php?neLat='+northEastLatLng.lat()+'&neLng='+northEastLatLng.lng()+'&swLat='+southWestLatLng.lat()+'&swLng='+southWestLatLng.lng(),
      type: 'GET',
      dataType: 'json',
      timeout: 1000,
      error: function(){
        alert("地図データの読み込みに失敗しました");
      },
      success: function(json){
        //帰ってきた地点の数だけループ
        var markerData = new Array();
        $.each(json.points,function(){
          markerData.push({
            position: new google.maps.LatLng(this.lat,this.lng), 
            title: this.title,
            content:this.content
          });
        });
      
        // マーカーデータをセット
        if(markerArray){
          setMarkerData(markerData);
        }      
      }
    });    
  }

phpでは、引数で指定された北東と南西の緯度・経度内に存在するデータのみをJSON形式で返却します。

<?php

//範囲データ取得
$neLat = $_GET["neLat"];
$neLng = $_GET["neLng"];
$swLat = $_GET["swLat"];
$swLng = $_GET["swLng"];


$json = file_get_contents('../json/points.json', true);

if ($json == false) {
  return;
}


$jsonData = json_decode($json);

$points = $jsonData->{'points'};
$result = new stdClass();
$result->points = array();

foreach ($points as &$point) {
  if ($point->{"lat"} < $neLat &&
          $point->{"lat"} > $swLat &&
          $point->{"lng"} < $neLng &&
          $point->{"lng"} > $swLng) {
    $result->points[] = $point;
  }
}
echo json_encode($result);

実際に地図を最初に開いた場合には、以下の表示となります。
f:id:pinoyuki:20120403002101p:plain

そして、1つマーカーを表示されないようにスクロールすると、マーカーも地点名も2つになります。
f:id:pinoyuki:20120403002526p:plain


完全なソースは、githubにあります。
https://github.com/pinoyuki/Sample-of-study/tree/master/GoogleMap/Sample5

GoogleMap API V3で地点一覧のリストをクリックすると地図上でマーカーの吹き出しを表示する

今回は、動的に地点データを読み込みんでマーカーを表示し、地図とは別に地点データをリストを表示します。
リスト表示した地点データのリンクをクリックすると、地図上で吹き出しを表示します。

基本は、前回と同じです。

変更点は、動的に読み込んだ地点データをHTMLとして出力する部分と、HTMLで出力したリンクをクリックした場合に吹き出しを表示するようにする部分です。

まず、HTMLにマーカーのリストを出力する部分を追加します。
marker_list内のolに地点データの番号つきリストを追加します。

    <div id="marker_list"><ol></ol></div>

次に、JavaScriptで動的に読み込んだマーカーデータを地図にセットする際にHTML出力を行います。
このように、1つのマーカーに対して、liタグに地点名でリンクを作成したデータを作り、marker_listに番号つきリストとして追加します。
そして、setLinkClickEventでこのリンクをクリックした場合の動作を設定しています。

      // マーカーの一覧出力・リンクのクリック時のイベント設定
      var lnk = $('<li>').append($('<a href="javascript:void(0)"/>').text(markerArray[i].title));
      $('#marker_list >ol').append(lnk);      
      setLinkClickEvent(lnk, marker);    

setLinkClickEventは、リンクがクリックされた場合に、紐付いた地図上のマーカーのクリックイベントを呼ぶように指定します。

  /**
   * リンクのクリックイベントの登録
   */
  var setLinkClickEvent = function(lnk, marker){
    lnk.bind('click', function(){
      google.maps.event.trigger(marker, 'click');
    });    
  }


実際に表示直後の画面です。
f:id:pinoyuki:20120326233745p:plain

ここで、「南京町広場」をクリックすると以下のようになります。
マーカーをクリックした場合と同じように、吹き出しが表示されるように、地図の表示エリアが自動的に変わります。
f:id:pinoyuki:20120326233911p:plain

すべてのソースは、githubに置きました。
https://github.com/pinoyuki/Sample-of-study/tree/master/GoogleMap/Sample4

JSONデータの検証

GoogleMap API V3でマーカーの地点データを動的に読み込むを作っている時にajaxでJSONファイルが取得できなくて、困りました。

実際には取得できないのではなく、JSONファイルの中のデータの形式が間違っていたため、エラーになっていました。

JSONファイルのデータが間違っているとエラーになると判ったので、JSONのlintを探したところ、オンラインで手軽に確認できるサイトがありました。



JSONLint - The JSON Validator.


このサイトでは、検証だけではなくデータの整形もしてくれます。

GoogleMap API V3でマーカーの地点データを動的に読み込む

今回は、HTML内に持っていた地点データをJSONファイルに分離して、AJAXを使用してJSONファイルを読み込んでマーカーを設定します。
今回のサンプルでは、jQueryを使います。

処理イメージとしては、以下の感じです。

  1. HTML読み込み
  2. 地図生成
  3. 地点データ読み込み(JSONファイル)
  4. マーカーの設定

用意したJSONファイルは、以下のとおり

{
  "points":[
  {
    "title": "ローソン",
    "content":"ローソン 神戸中央西町",
    "lat":"34.687574",
    "lng":"135.189857"
  },
  {
    "title": "南京町広場",
    "content":"南京町広場",
    "lat":"34.6882",
    "lng":"135.188087"  
  },
  {
    "title": "ライオンズマンション",
    "content":"ライオンズマンション神戸栄町通",
    "lat":"34.687163",
    "lng":"135.187945"  
  }
  ]
}

まず、jQueryを読み込みます。今回は、独自にファイルを持たずにGoogle AJAX APIを使用して、Googleにあるファイルを使用します。

    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script>
      google.load("jquery", "1.7.1");
    </script>


今までは、HTMLファイルで指定していたマーカーデータをajaxを使用して読み込み、地図に設定します。

  //jsonファイルの取得
  $.ajax({
    url: 'json/points.json',
    type: 'GET',
    dataType: 'json',
    timeout: 10000,
    error: function(){
      alert("地図データの読み込みに失敗しました");
    },
    success: function(json){
      //帰ってきた地点の数だけループ
      $.each(json.points,function(){
        markerArray.push({
          position: new google.maps.LatLng(this.lat,this.lng), 
          title: this.title,
          content:this.content
        });
      });
      
      // マーカーデータをセット
      if(markerArray){
        setMarkerData(markerArray);
      }      
    }
    
  });


以下のような地図になります。この3つのマーカーは動的に読み込んで表示しています。
f:id:pinoyuki:20120326011643p:plain


完全なソースは、githubに置きました。

https://github.com/pinoyuki/Sample-of-study/tree/master/GoogleMap/Sample3

GoogleMap API V3で番号付きマーカーを動的に生成

今回は、GoogleMap API V3で番号つきマーカーを動的に生成して表示するサンプルです。

f:id:pinoyuki:20120320225801p:plain

マーカーのピンの生成は、Google Chart APIを使います。

作成方法は簡単で、Google Chart APIは、番号つきのピンと影を生成できて、Google Map APIはマーカーのピンと影をそれぞれURL指定できるので、そこにGoogle Chart APIのURLを指定するだけです。

Google Chart APIでは、影つきピンも生成できますが、下のようにピンと影が少し離れているので、ピンと影をそれぞれ指定しました。

f:id:pinoyuki:20120320225055p:plain


ピンの生成は、http://chart.apis.google.com/chartのパラメータとして以下の値を設定します。

chst=d_map_pin_letter&chld=<character>|<fill_color>|<text_color>

例えば、http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=1|ff7e73|000000 は、以下のアイコンになります。

f:id:pinoyuki:20120320225240p:plain

その他にも、ピンを斜めにしたり色々できますが、詳しくはDynamic Icons - Google Chart Tools: Image Charts - Google Codeに書かれています。

JavaScriptはこちら。
ピンは指し示す位置が画像の中央ですが、影の場合は中央ではないため、影のピンの指し示す位置を座標で指定しています。これを行わないと地図上でピンの位置がずれてしまいます。

/**
 * GoogleMapの表示
 * @param {String} id 表示領域ID
 * @param {Object} option google.maps.Mapに設定するオプション
 * @param {Object} markerArray マーカーデータ配列
 * @param {Object} isNumberPin 番号付きマーカーで表示するか
 */
var viewGoogleMap = function(id, option, markerArray, isNumberPin){
  /**
   * マーカーのクリックイベントリスナーの登録
   * @param {google.maps.Marker} marker マーカーオブジェクト
   * @param {Object} markerData マーカーに設定する情報ウィンドウデータ
   */
  var setMarkerClickListener = function(marker, markerData) {
    google.maps.event.addListener(marker, 'click', function(event) {
      if (openInfoWindow) {
        openInfoWindow.close();
      }
      openInfoWindow = new google.maps.InfoWindow({
        content: markerData.content
      });
      google.maps.event.addListener(openInfoWindow,'closeclick',function(){
        openInfoWindow = null;
      })
      openInfoWindow.open(marker.getMap(), marker);
    });
  };
  
  /**
   * マーカーデータのセット
   * @param {Object} makerArray マーカーデータ
   */
  var setMarkerData = function(makerArray) {

    // 登録データ分のマーカーを作成
    for (var i = 0; i < makerArray.length; i++) {
      var marker = new google.maps.Marker({
        position: makerArray[i].position,
        title: makerArray[i].title,
        map: gmap,
        icon: isNumberPin ? new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld="+ (i + 1) + "|ff7e73|000000") : null,
        shadow:isNumberPin ? new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_shadow",null,null, new google.maps.Point(12, 35) ) : null
      });

      // マーカーのclickリスナー登録
      setMarkerClickListener(marker, makerArray[i], true);
    }
  }; 
  
  option = option ? option : {};
  if(id == null){
    return;
  }
  var mapOption = {
    zoom: option.zoom || 16,
    center:option.center || new google.maps.LatLng(34.687463, 135.18813),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    navigationControlOptions: {
      style: google.maps.NavigationControlStyle.DEFAULT
    }
  };
  
  var gmap = new google.maps.Map(document.getElementById(id), mapOption);

  var openInfoWindow;  
  
  if(markerArray){
    setMarkerData(markerArray);
  }
}


htmlは、こちら

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
    </script>
    <script type="text/javascript">

      function initialize(id){
        var data = new Array();
        data.push({position: new google.maps.LatLng(34.687574,135.189857), title: 'ローソン', content: 'ローソン 神戸中央西町 <br/><a href=\'http://www.lawson.co.jp/\'>HP</a>'});
        data.push({position: new google.maps.LatLng(34.6882,135.188087), title: '南京町広場', content: '南京町広場'});

        var map = viewGoogleMap(id,null,data,true);
      }
    </script>
    <script src="./js/gmap.js" type="text/javascript"></script>
    <title>GoogleMapTest2</title>
  </head>
  <body onload="initialize('gmap_canvas')">
    <div id="gmap_canvas" style="width: 480px; height: 320px;"></div>
  </body>
</html>


参考:

GoogleMap API V3で複数のマーカーを表示吹き出しは1つだけにする(その2)

前回の記事で書いたコードは、グローバル変数を使っていたのですが、よくないことらしいので書き直しました。

こんな感じでいいのかな?

こっちが、JavaScript

/**
 * GoogleMapの表示
 * @param {String} id 表示領域ID
 * @param {Object} option google.maps.Mapに設定するオプション
 * @param {Object} markerArray マーカーデータ配列
 */
var viewGoogleMap = function(id, option, markerArray){
  /**
   * マーカーのクリックイベントリスナーの登録
   * @param {google.maps.Marker} marker マーカーオブジェクト
   * @param {Object} markerData マーカーに設定する情報ウィンドウデータ
   */
  var setMarkerClickListener = function(marker, markerData) {
    google.maps.event.addListener(marker, 'click', function(event) {
      if (openInfoWindow) {
        openInfoWindow.close();
      }
      openInfoWindow = new google.maps.InfoWindow({
        content: markerData.content
      });
      google.maps.event.addListener(openInfoWindow,'closeclick',function(){
        openInfoWindow = null;
      })
      openInfoWindow.open(marker.getMap(), marker);
    });
  };
  
  /**
   * マーカーデータのセット
   * @param {Object} makerArray マーカーデータ
   */
  var setMarkerData = function(makerArray) {

    // 登録データ分のマーカーを作成
    for (var i = 0; i < makerArray.length; i++) {
      var marker = new google.maps.Marker({
        position: makerArray[i].position,
        title: makerArray[i].title,
        map: gmap
      });

      // マーカーのclickリスナー登録
      setMarkerClickListener(marker, makerArray[i]);
    }
  }; 
  
  option = option ? option : {};
  if(id == null){
    return;
  }
  var mapOption = {
    zoom: option.zoom || 16,
    center:option.center || new google.maps.LatLng(34.687463, 135.18813),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    navigationControlOptions: {
      style: google.maps.NavigationControlStyle.DEFAULT
    }
  };
  
  var gmap = new google.maps.Map(document.getElementById(id), mapOption);

  var openInfoWindow;  
  
  if(markerArray){
    setMarkerData(markerArray);
  }
}

こっちが、html

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
    </script>
    <script type="text/javascript">

      function initialize(id){
        var data = new Array();
        data.push({position: new google.maps.LatLng(34.687574,135.189857), title: 'ローソン', content: 'ローソン 神戸中央西町 <br/><a href=\'http://www.lawson.co.jp/\'>HP</a>'});
        data.push({position: new google.maps.LatLng(34.6882,135.188087), title: '南京町広場', content: '南京町広場'});

        var map = viewGoogleMap(id,null,data);
      }
    </script>
    <script src="./js/gmap.js" type="text/javascript"></script>
    <title>GoogleMapTest1</title>
  </head>
  <body onload="initialize('gmap_canvas')">
    <div id="gmap_canvas" style="width: 480px; height: 320px;"></div>
  </body>
</html>

それにしても、JavaScriptは書き方に自由度がありすぎてどういう風に書けばよいのかよくわからない。
色々なサンプルソースを見ても、まだどれがよい書き方なのかの判断もできないので、難しい。

JavaScriptの論理演算子は、true/falseを返却しない

ソースコードを色々見ていると、下のようなコードが出てきた。

なぜこれで初期化ができるのかわからなかったので調べると、JavaScriptの論理演算子(&&、||)はtrue/falseを返さないとのこと。

ほとんどのJavaScript入門サイトでは、論理演算子(&&、||)はtrue/falseを返すと書かれているけど間違いみたい。

var foo ={};
var bar = foo.data1 || 50;

上記では、foo.data1が存在している場合にはbarにfoo.data1が設定され、存在していない場合にはbarに50が設定される。


JavaScriptでの論理演算子の動きは、以下のとおり。

論理積(&&) expr1 && expr2 expr1がfalseに変換できるときには、expr2を返却。変換できないときには、expr1を返却。
論理和(||) expr1 || expr2 expr1がtureに変換できるときには、expr1を返却。変換できないときには、expr2を返却。


これで、論理和が初期化に使われている意味がわかった。


参考: