Eclipseで新しいGAEアプリケーションを作成する

Eclipseのメニューから、「ファイル」-「」

  • プロジェクト名:
    公開時にアプリケーション(application)を識別する名前。登録済のアプリケーションはGAEのadmin consoleで確認可能できる。今回は、 triplifelog とする。
  • パッケージ:
    Javaの標準に従ってパッケージ名(ドメイン名を逆順にする)を入力する。GAEアプリケーションは、「プロジェクト名.appspot.com」という名前で公開されるので、今回のケースでは、com.appspot.triplifelog となる。
  • ロケーション:
    「ワークスペース内に新規プロジェクトを作成する」をチェックする。
  • Google SDK:
    「Google Appエンジンを使用」をチェック、「Google Webツールキットを使用」のチェックを外す(使用しない)。
  • その他:
    「マーケットプレイスのサポート」、「プロジェクトサンプルコード」も不要なのでチェックを外しておく。

eclipse_newproject

完了ボタンをクリックすると、プロジェクト名のフォルダが自動的に作成される。あわせて、Javaのソースを配置するフォルダ(src)、webアプリの各種ファイルを配置するフォルダ(war)が作成される。
warフォルダ下に必要なファイル(*.htmlなど)を配置することで、webアプリケーションとして機能する。

カテゴリー: Development | タグ: | コメントする

乗車予定をスケジューラに登録する(3)

前記事までの作業で、乗車予定を行動予定に変換できたので、今度はスケジューラ(Google Calendar)に登録する。乗車予定から行動予定の変換は一方向の処理(=手動での追加情報は無し)だったが、スケジューラ登録も同様の処理とする。

Google Calendarへの登録は、以前作成したライブラリによる。1件の行動予定データ(timeslot)を1件のイベントとして登録し、登録後に得られるイベントIDを予定データに保存しておく。また、Google Calendar側で説明(description)を変更した場合にのみ、その内容を予定データに反映する。(それ以外の変更は反映しない=一方向の処理。)

  • 画面全体イメージ
    xtsplan03_c01
  • 乗車予定の例
    xtsplan03_c02
  • 行動予定に変換した結果例
    xtsplan03_c04
  • Google Calendarに自動登録した結果例
    xtsplan03_c03_x

コードイメージは以下。

// JavaScript

function syncGCal(isCreateFirst){
  // 
  var idxStart = (isCreateFirst)? 0 : 1;
  var idxEnd = (isCreateFirst)? gCurTimeslots.length : gCurTimeslots.length - 1;
  for (var i = idxStart; i < idxEnd; i++) {
    (function(){
      syncGCal1(gCurTimeslots[i]);
    })();
  }
}

function syncGCal1(aTS){
  var _description = '';
  
  if (aTS.cal_id == '' || aTS.cal_id == 'na') {
    // GCに未登録の場合は新規登録
    gGCalendar.addEventA(aTS.slot_summary, 
      strToDate(aTS.slot_startdate, aTS.slot_starttime),
      strToDate(aTS.slot_enddate, aTS.slot_endtime), 
      aTS.loc_name, _description, function(resp){
        // 登録後返されるデータを処理する
        aTS.cal_id = resp.id; // イベントID
        aTS.cal_seq = resp.sequence; // シーケンス番号	
        // 予定データを更新する
        gDSTimeslot.dbUpd(aTS.slot_key, aTS, function(res_ts){
	
        });
    });

  } else {
    // GCに登録済の場合はイベントデータを取得する
    // イベントデータを取得
    gGCalendar.getEvent(aTS.cal_id, function(xEvent){
      // イベントデータのdescriptionを予定データにコピー
      if (xEvent.description){
        aTS.slot_description = xEvent.description; 
        gDSTimeslot.dbUpd(aTS.slot_key, aTS, function(res_ts){
				
        });
      }
    });
  }
}
カテゴリー: Development | タグ: , , , | コメントする

Google Maps APIで画像の一部をマーカーとして使う

Google Maps API上にマーカーを表示するには、マーカー用の画像を用意するだけでいいのだが、マーカーごとに画像を変えたいケースで、個別に画像を準備すると、サーバから画像ファイルをロードするオーバヘッドが問題になってくる。つまり、画像ファイル自体は非常に小さくても、マーカーごとにサーバから画像をダウンロードすることになり、通信コストが大きくなってしまうという問題がある。

このため、Webサイトでは、サイト内で使うアイコンなどの小さい画像を一つのファイルにしておき、表示の際に必要な部分を切り出して使うというテクニック(CSSスプライト)を用いることが多い。

Google Maps APIにも似たような機能があり、複数のマーカー用画像をまとめてロードしておき、マーカー作成時に必要な部分を指定することができる。

  • マーカー用画像を作成する
    個々のマーカー用の画像を結合して、1つの画像ファイルにする。今回は、CSSスプライト用の画像・CSSを生成してくれるサイト「CSS Sprite Generator」を利用して、連番のマーカーを作成する。もとの画像サイズは32×37で、これを縦方向に結合する。その際の垂直オフセットは’37′とし、元画像と同じサイズの空白画像を挿入しておく。ちなみにファイルサイズ700から900バイト程度の画像(1つあたり)を100個結合すると、サイズは21kbになった。

  • MarkerImageオブジェクトを使ってマーカー画像を作成する
    通常は iconプロパティには画像のURIを指定していたが、本件ではMarkerImageオブジェクトを指定する。この部分を抜き出すと以下のイメージ。

    // JavaScript
    
    // 結合したマーカー画像を指定する
    var image = new google.maps.MarkerImage('/images/icon/numbers_a.png');
    // 個々のマーカーのサイズを指定する
    image.size = new google.maps.Size(32, 37); 
    // マーカーとして切り出す部分を(x, y)で指定する
    //// 今回は同じサイズの空白画像を挿入したためインデックスを2倍する
    image.origin = new google.maps.Point(0, 37 * index * 2);
    // マーカーの影を指定する場合の起点座標
    //image.anchor = new google.maps.Point(16, 37);
    
  • マーカーを表示する
    マーカーを表示するコードは以下のイメージ。

    // JavaScript
    //// spotsArray[] に表示したい地点を格納している
    for(var i=0; i < spotsArray.length; i++){
      var pos =  new google.maps.LatLng(spotsArray[i][latStr],spotsArray[i][lngStr]);
      var image = new google.maps.MarkerImage('/images/icon/numbers_a.png');
      image.size = new google.maps.Size(32, 37);
      image.origin = new google.maps.Point(0, 37 * (i + 1) * 2);
      
      var marker = new google.maps.Marker({
        position : pos,
        icon: image,
        shadow: '/images/icon/numbers_a_shadow.png',		
        animation: google.maps.Animation.DROP,
        map : map
      });
    }
    
  • 実行例

    xtsplan03_b01

参考にしたサイト:
大きな画像の一部をマーカー画像として使う
マーカー画像のカスタマイズ

カテゴリー: Tips | タグ: | コメントする

乗車予定をスケジューラに登録する(2)

前記事で作成したアプリケーションのコード覚え書き。

  • 乗車日リストを抽出・表示する
    乗車データ(rides)からユニークな乗車日を抽出し、上パネルの左のデータグリッドに表示する。配列に関するユーティリティ関数をいくつか整備した(重複削除・ソート・フィルター)。(xuarray.js)

    // JavaScript
    
    function showDgdRideDates(rides){
      // 乗車データから乗車日が重複しているものを削除する
      var _rides = xua_uniqueObjectArray(rides, 'ride_date');
      // 乗車日でソートする
      _rides = xua_sortObjectArray(_rides, 'ride_date');
      // データグリッドに表示する
      $('#dgdRideDates').datagrid({
        showHeader: false,
        columns: [[{
          field: 'ride_date',
          //title: 'ride_date',
          width: 150
        }]],
        data: _rides,
        onSelect: function(rowIndex, rowData){
          // pickupRidedate(rowData); // 日付が選択されたときのイベントハンドラ
        }
      });
    }
    
  • 乗車日が選択された時の動作(1)
    選択された乗車日に対応する「乗車データ」を、上パネルの右のデータグリッドに表示する。

    // JavaScript
    
    // 選択された乗車日付に対応する乗車データを表示する
    gCurRides = xua_filter(gRides, 'ride_date', ride_date);
    $('#dgdCurRides').datagrid({ data: gCurRides });
    
  • 乗車日が選択された時の動作(2)
    選択された乗車日に対応する「行動データ」を検索する。
    ・存在する場合は、得られた行動データリストを左パネルのデータグリッドに表示する。
    ・存在しない場合は、新規に行動データを作成し保存する。

    // JavaScript
    
    // 選択された乗車日付に対応する行動予定データを非同期ロードする
    loadTimeslot(ride_date, function(ts){
      
      if (ts){ // 対応する行動予定データが存在する
        gCurTimeslots = ts;
        $('#dgdTimeslots').datagrid({ data: gCurTimeslots });
        
        // 行動予定データのうちタイプが 'stay'のものを抽出し、地図にマーカーを表示する
        var timeslots_stay = xua_filter(gCurTimeslots, 'slot_type', 'stay');		
        xumap_displayMarkers(gMap, timeslots_stay, 'loc_lat', 'loc_lng', gMarkersTimeslot);
    
      } else { // 対応する行動予定データが存在しない
        // 行動データを作成する
        gCurTimeslots = createTimeslots( gCurRides);	
        $('#dgdTimeslots').datagrid({ data: gCurTimeslots });
        // 行動データをデータストアに保存する
        saveTimeslots(gCurTimeslots);	
      }
    });
    
  • 行動予定データを作成(変換)する
    乗車予定データから行動予定データを作成する。今のところ、以下のロジックで機械的に作成している。
    ・各乗車予定に対応する行動予定を1件
    タイプは’move’。乗車データから路線名や駅名を取り出している。
    ・各乗車予定の「後」に行動予定を1件
    タイプは’stay’。到着駅を場所とし、次の乗車まで滞在する。
    ・最初の乗車予定の「前」に1件
    タイプは’stay’。最初の乗車駅を場所とする。
    すなわち、乗車予定データが N件の場合、作成される行動予定データは 2*N+1 件になる。行動予定データには、経緯度や自動チェックイン用の地点IDが含まれるが、これらのデータは駅データベース(kind: xRWStation)から取得する。(データの作成時ではなく保存時に取得する。これは、データストアからの取得は非同期的に行われるため。)
  • 行動予定データを保存する
    作成した各行動予定(timeslot)データをデータストアに保存する。他テーブル(xRWStation)からは、非同期的に取得するため、
    ・メモリ上のデータをいったん保存し、
    ・他テーブルからのデータ取得完了後に追加保存、
    というステップで処理する。

    // JavaScript
    
    //// timeslotデータをデータストアに新規保存する
    function saveTimeslots(curTimeslots){
      for (var j = 0; j < curTimeslots.length; j++) {
      (function(){
        var aTimeslot = curTimeslots[j];
        // 現在メモリ上にある内容を保存する
        gDSTimeslot.dbAdd(curTimeslots[j].slot_key, curTimeslots[j], function(res){
          // この場所に対応する経緯度などの情報をロードし追加保存する
          saveTimeslotFromRWStation(aTimeslot);
        });
      })();
      }
    }
    
    //// 指定されたtimeslotデータに対応する駅データをロードし、
    //// データストアに追加保存(更新)する
    function saveTimeslotFromRWStation(timeslot){
      
      // 駅コードをキーとして検索実行
      var query = 'station_cd:' + timeslot.rw_station_cd1;
    
      gDSRWStation.dbRev('', query, function(d){
        if(d){
          // 駅コードは重複しないので、最初の検索結果が得られるデータ
          timeslot.loc_frsq = d[0].loc_frsq;
          timeslot.loc_loct = d[0].loc_loct;
          timeslot.loc_lat = d[0].lat;
          timeslot.loc_lng = d[0].lon;
          gDSTimeslot.dbUpd(timeslot.slot_key, timeslot, function(res){
          
          });
      
        } else {
          console.debug('unknown RWStation:' + timeslot.loc_name);
        }
      });
    }
    
  • 乗車日が選択された場合の動作(3)
    対応する行動予定データの経緯度から、地図にマーカーを表示する。新規に行動予定を作成した場合は、経緯度の取得が遅延することがある。
  • 行動予定が選択されたときの動作
  • 左パネルの行動予定リストから行動予定が選択されたら、右パネルにその詳細を表示する。地点IDが設定されている場合は、foursquareやロケタッチの地点ページへのリンクを有効にする。

  • 画面イメージ
    xtsplan03_a03
    xtsplan03_a03_ex2

カテゴリー: Development | タグ: , , | 1件のコメント

乗車予定をスケジューラに登録する(1)

以前、列車時刻表から乗車予定データを作成したが、これをGoogleカレンダーなどのスケジューラに登録するアプリケーションを作る。

時刻表アプリで保存した乗車予定の例はこういう感じ。
mttview06_g01

これを以下のような行動予定データに変換する。
xtsplan03_a01
乗車予定は、「A駅-B駅」「B駅-C駅」という形式になっているが、これを「A駅(滞在)」「A駅-B駅(移動)」「B駅(滞在)」「B駅-C駅(移動)」「C駅(滞在)」という形式に変換する。今のところ変換は一方向で、情報を手動で追加する機能はない。
作成したアプリケーションは以下のイメージ。(xtsplan03.html)
xtsplan03_a02

データストアのkind名
・乗車予定データ:xRide
・行動予定データ:xTimeslot

カテゴリー: Development | タグ: , , | 1件のコメント

駅データベース(5) – ロケタッチのスポットIDを路線単位で取得する

ロケタッチには、’駅データ’の駅コードからスポットデータを取得するAPIが提供されている。今回はこのAPIを使って、路線単位で駅のスポットIDを取得することを考える。

例えば駅コードが’1122501′のスポットデータは、以下のリクエストで取得できる。


https://api.loctouch.com/v1/railway/stations/1122501

  • 駅コードを指定してスポットデータを取得&保存する関数
    駅コードを指定してロケタッチサーバに問い合わせ、得られたスポットIDをデータストアに保存する関数を作成する。サーバへの負荷軽減のため、一定間隔で呼び出す前提。(そのため対象をグローバル変数に保持しておく。)

    // JavaScript
    // データストアにある駅データに、ロケタッチのスポットIDを追加する
    //// 対象はグローバル変数 gTgtStations[i] に保持されている
    //// 配列のインデックスを指定して処理を実行する
    function addLoctSpotByStation(i){
      
      // ロケタッチサーバのURI
      var uri = 'https://api.loctouch.com/v1/railway/stations/' + 
        gTgtStations[i].station_cd + '?callback=?';
      
      // サーバにリクエストを発行する
      $.getJSON(uri, function(res){
        if (res.code == 200){ // 処理成功
        // レスポンスからスポットIDを取り出して、データにセットする
          gTgtStations[i].loc_loct = res.station.station_spot_id;
          // データストアを更新する
          gDSrwStation.dbUpd(gTgtStations[i].station_cd, gTgtStations[i], function(){
            console.log('updated:' + i + ' ' + gTgtStations[i].station_name);
          });
        } else { // 処理失敗
          console.log('error:' + i)
        }
      });
    }
    
  • 路線コードを指定して駅データを取得する関数
    路線コードを指定してデータストアから駅データを取得し、結果をグローバル変数に保持しておく。

    // JavaScript
    function addStationsByLinecd(line_cd, callback){
      var query = 'line_cd:' + line_cd; //console.debug(query);
      gDSrwStation.dbRev('', query, function(stations){
        for (var i = 0; i < stations.length; i++) {
          gTgtStations.push(stations[i])
        }
        callback();
      });
    }
    
  • 2つの関数を組み合わせる
    保持している駅データをループで取り出し、(タイマー機能を使って一定間隔で)ロケタッチサーバにリクエストを発行&結果をデータストアに保存する。
    例えば、路線コード’11225′の駅データを10秒間隔で処理したい場合は、以下のようにする。

    // JavaScript
    addStationsByLinecd('11225', function(){
      for (var i = 0; i < gTgtStations.length; i++) {
        var func = 'addLoctSpotByStation(' + i + ')';
        setTimeout(func, 10000 * i);
      }
    });
    
  • 画面イメージ: xttrwconv02.html
    xttrwconv02_b01
カテゴリー: Development, Location Service | タグ: | コメントする

駅データベース(4) – 登録したデータの管理

作成中の駅データベースは、日本の全路線・全駅をカバーしていて、駅の位置情報や、位置情報サービスでチェックインするための情報を保持している。一方、作成した列車時刻表の路線・駅のデータは、駅データベースとは別立てになっている。(また、時刻表データを登録した路線・駅のみデータを保持しているので、データ的にも全路線をカバーしているわけではない。)

そこで、2つのデータを関連付けるツールを作成する。

時刻データベース(TT_xxx) <- 時刻表データ
| (駅)
駅データベース (RW_xxx) <- 駅データ.jp

駅データベースのほうがカバー範囲が広いので、参照の向きは時刻データベース->駅データベースとし、時刻データベースの駅テーブル(kind: TTStation)に新しいフィールド(property: rw_station_cd)を設けて、ここに駅データベースの「駅」のIDを保持する。

  • 画面イメージ: xttrwst01.html
    他の画面を流用して作成。
    xttrwst01_a01
  • 時刻表の路線データ(TT路線データ)をロード・表示する
    上右パネルに表示する。時刻表の路線データは、時刻データを登録した路線のみ。
  • 路線(TTデータ)を選択すると、対応する駅データベースの路線(RWデータ)を検索する
    選択(クリック)したTT路線に対応するRW路線を検索する。まだ路線同士の関連付けが行われていない場合は、searchRWLine()で関連付けを実施する。すでに関連付けが完了している場合は、駅リストを表示する。

    // JavaScript
    function pickupTTLine(ttLine){
      // TT路線がRW路線と関連付けされているかを判断
      if ( !(parseInt(ttLine.rw_line_cd) > 0) ){
        // 関連付けられていない場合
        searchRWLine(ttLine);
      } else {
        // ...
      }
    }
    
  • TT路線データをRW路線データに関連付ける
    結果が1路線だった場合は、RW路線の路線コードをTT路線に保存する。複数だった場合は、(このアプリではなく)手動で関連付けを実施する。
    現状、路線名で検索しているが、’磐越西線’のように類似の名称で複数の路線が登録されている場合がある。
  • TT駅データをRW駅データに関連付ける
    選択されている路線に含まれる駅(TT駅データ)に対応するRW駅を検索する。対応する駅が1つだった場合は、TT駅データにRW駅データの駅コードを保存する。
    現状、駅名で検索しているが、’安子ヶ島’と’安子ケ島’のように、両者で微妙に異なっている場合があり、自動マッチしないケースがある。

    // JavaScript
    
    // 対応するRW路線(駅DBデータ)の処理
    gRWCurLine = getLineByCode(ttLine.rw_line_cd);
    //// RW駅リストを取得
    gRWCurStations = filterStationByID(ttLine.rw_line_cd);
    
    for (var i = 0; i < gTTCurStations.length; i++) {
      if ( parseInt(gTTCurStations[i].rw_station_cd) > 0 ){
        // 対応するRW駅を紐付け済の場合
      } else {
        // 未処理の場合は対応するRW駅を検索する
        var _RWStation = searchRWStation(gRWCurStations, gTTCurStations[i]);
        
        if (_RWStation){
          // 見つかった場合はTT駅にRW駅の駅コードを保存する
          addRWStationcd(gTTCurStations[i], _RWStation.station_cd);
        }
      }
    }
    
  • 画面例
    ページを開くと、時刻表の路線データをロードする。路線を選択したタイミングで対応する駅データベースの路線データとの関連付けを実施する。
    xttrwst01_a02
カテゴリー: Development, Location Service | タグ: | コメントする

駅データベース(3) – アプリケーションにデータを登録する

駅データベースの作成にあたり、(順序が前後したが)アプリケーションにデータを投入するツールを作成する。

  • データ形式をJSON形式に変換
    駅データ.jpからダウンロードした会社データ、路線データ、駅データをJSON形式に変換する。
    ・会社データ:rw_company.json
    Online JSON Viewer_rw company
    ・路線データ:rw_line.json
    Online JSON Viewer_rw line
    ・駅データ: rw_station.json
    Online JSON Viewer_rw station
  • 駅データをデータストアに登録する
    上記の駅データをデータストアに登録する。その際、独自のプロパティとして、
    ・loc_loct (ロケタッチのスポットID)
    ・loc_frsq (foursquareのvenue ID)
    を追加する。(位置情報サービスの地点IDを保存する。)
  • 作成したアプリケーション xttrwconv02.html
    CSVファイルを枠の中にドラッグ&ドロップする。
    xttrwconv02_a01
カテゴリー: Development, Specification | タグ: | コメントする

JavaScriptでCSVデータ(項目行あり)をJSON形式に変換する

駅データ.jpの路線や駅のデータは、先頭行が項目名になっているCSV形式で提供される。こういった「項目行があるCSV形式」ファイルをJSONに変換する方法。項目行で指定される項目名を、そのままJSONデータのプロパティ名として利用する。(そのため、ファイルの種類によらず汎用的に使える。)

  • 入力データ (例) company20130120.csv
    company_cd,rr_cd,company_name,company_name_k,company_name_h,company_name_r,company_url,company_type,e_status,e_sort
    1,11,JR北海道,ジェイアールホッカイドウ,北海道旅客鉄道株式会社,JR北海道,http://www.jrhokkaido.co.jp/,1,0,1
    2,11,JR東日本,ジェイアールヒガシニホン,東日本旅客鉄道株式会社,JR東日本,http://www.jreast.co.jp/,1,0,2
    
  • コードイメージ
    // JavaScript
    // イベントハンドラ (ファイルがドロップされたら起動する)
    function loadedEkidata(event){
      event.preventDefault();
      var res = event.target.result;
      var d = res.split('\n'); // 1行ごとに分割する
      var jsonArray = csv2json(d); // JSON形式に変換
    }
    
    function csv2json(csvArray){
      var jsonArray = [];
    	
      // 1行目から「項目名」の配列を生成する
      var items = csvArray[0].split(',');
    
      // CSVデータの配列の各行をループ処理する
      //// 配列の先頭要素(行)は項目名のため処理対象外
      //// 配列の最終要素(行)は空のため処理対象外
      for (var i = 1; i < csvArray.length - 1; i++) {
        var a_line = new Object();
        // カンマで区切られた各データに分割する
        var csvArrayD = csvArray[i].split(',');
        //// 各データをループ処理する
        for (var j = 0; j < items.length; j++) {
          // 要素名:items[j]
          // データ:csvArrayD[j]
          a_line[items[j]] = csvArrayD[j];
        }
        jsonArray.push(a_line);
      }
      //console.debug(jsonArray);
      return jsonArray;
    }
    
  • 出力結果
    Online JSON Viewer_rw company

カテゴリー: Tips | タグ: | コメントする

HTMLで画像と他要素の縦方向位置を揃える

vertical-alignプロパティを使って、HTMLで画像と他要素の縦方向位置を揃える方法。

このプロパティを使って、リンクボタンと画像の縦方向位置を揃えた結果は以下。
xttrw02_b01

<!-- HTML -->
<div style="margin:5px 5px;">
 <img src="/images/icon/app_frsq16.png" alt="foursquare" style="vertical-align:middle"/>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">駅</a>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">all</a>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-mynewwindow'></a>
</div>

<div style="margin:5px 5px;">
 <img src="/images/icon/app_loct16.png" alt="loctouch" style="vertical-align:middle"/>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">駅</a>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">all</a>
 <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-mynewwindow'"></a>
</div>

少しわかりにくいが、下の行だけ vertical-align を指定しないと、次のような画面になる。(ロケタッチのアイコンが上に寄っている。)
xttrw02_b02

カテゴリー: Tips | タグ: | コメントする