Start地点、Goal地点近傍のトラックポイントを消去する。
変更履歴:
・経路(トラック)に沿って、消去するようにした(今までは、経路に関係なく、近ければ消去していた)。(2015-11-08)
・オブジェクト指向化した(コンストラクタを作成した)。(2015-11-08)
・メインのコードをhtmlファイルに記載するようにした。(2015-11-08)
応用例(GpxClipper:gpxファイルを加工するウェブアプリ)
◆html
70,71行目:ボタンのクリックで gpx_clip() を実行する。
19行目以降:xmlファイル(sample.xml)の読み込み。
29行目:コンストラクタGpxClipを用いてインスタンスgpxclipを作成する。
36行目:gpxclipを初期化する。
37行目:描画する。
xml ファイルの読み込みには、jQuery の $.ajax を使う。
29-37行目:ファイルを読み込んだ後に実行するスクリプト
19行目以降:xmlファイル(sample.xml)の読み込み。
29行目:コンストラクタGpxClipを用いてインスタンスgpxclipを作成する。
36行目:gpxclipを初期化する。
37行目:描画する。
xml ファイルの読み込みには、jQuery の $.ajax を使う。
29-37行目:ファイルを読み込んだ後に実行するスクリプト
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel=icon href=../_img/BlueSky_favicon.png sizes="16x16" type="image/png">
<title>gpxClipper</title>
<script src="http://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=false"></script> 
<script src="jquery.js"></script>
<script src="gpxClipperTest.js"></script>
<script src="gmOverlayHtml.js"></script> 
<!--
http://www.atmarkit.co.jp/ait/articles/1112/16/news135.html
-->
<script>
$(function() {
 url_gpx = "sample.xml";
   // XMLファイル読み込み開始
   $.ajax({
     url: url_gpx,
     cache:false,
     dataType:"xml",
  error:  function(XMLHttpRequest, textStatus, errorThrown){
    alert('読み込みエラー!');
   },
     success:function(data){
   var gpxclip = new GpxClip({
    xml: data,
//    typeId: google.maps.MapTypeId.HYBRID,
//    pathInit: { weight: 1 },
    startButtonId: "start",
    goalButtonId: "goal"
   });
      gpxclip.init();
      gpxclip.draw();
     }
   });
 
});
</script></head>
<style type="text/css"> 
* {
 margin: 0;
 padding: 0;
}
html, body {
  height: 100%;
}
body {
 overflow: hidden;
 background-color: ivory; 
}
p {
 background-color: #99cc00
}
#map_canvas {
  height: 100%;
}
</style>
<body>
<input id="start" type="button" value="Startの周辺削除" onclick="gpx_clip(LatLngStart)" style="width:100px" >
<input id="goal" type="button" value="Goalの周辺削除" onclick="gpx_clip(LatLngGoal)" style="width:100px" >
<span>半径</span>
<input type="text" id="clip" value="100" size="3">
<span>(m) 以内の点を削除</span>
<div id="map_canvas"></div> 
</body>
</html>
◆コンストラクタ(GpxClip)
optionsとdefaultsにより、プロパティを設定する。
関数(メソッド)はprototypeで設定する。
関数(メソッド)はprototypeで設定する。
var GpxClip = function(options) { // ver.2.1
 var defaults = {
  typeId: google.maps.MapTypeId.ROADMAP, // マップタイプ
             timeFontSize: "16px", // 時間表示のフォントサイズ
  period: 30, // 30分ごとに時間を表示
  pathInit: {    // 読み込んだxmlのトラックポイント
      color: "Blue",    // 線の色
      opacity: 0.5,    // 線の透過度
      weight: 5     // 線の幅
  },
   path: {     // クリップしたトラックポイント
   image: new google.maps.MarkerImage( // Markerのイメージ
    'images/point.png',    // marker image
         new google.maps.Size(16,16),  // marker size
         new google.maps.Point(0,0),  // marker origin
         new google.maps.Point(8,8)   // marker anchor
   )
  },
  start: {    // スタートポイント
   mark: {
    offsetX: -64,
    offsetY: -48,
    content: '<img src="images/start.png">'
   }
  },
  goal: {     // ゴールポイント
   mark: {
    offsetX: 0,
    offsetY: -48,
    content: '<img src="images/goal.png">'
   }
  }
 };
 $.extend(true, this, defaults, options); // デフォルトオプションと渡されたオプションのマージ
 this.period = this.period*60*1000; // 分→msに変換
};初期処理(GpxClip.prototype.init)
5-11行:出力用のxmlを作成する。13-50行:xmlファイルから緯度(Lat)、経度(Lng)、日時を取り込んで、初期のポイント(self.pathInit.point)、クリップ後のポイント(self.path.point)を初期化する。
28行:marker の一括消去用の配列を定義する。
30-35:Google Map を描画。
52-56行:Start地点、Goal地点を初期化する。
58-64行:トラックを描画。
66-73行:ボタンの設定。
GpxClip.prototype.init = function(){
 var self = this;
//  $(self.xml).find("name").each(function(){
//  $(this).text("gpxClipper");
//  });
//         <name>タグのテキストが日本語の場合エラーがでるので、すべてgpxClipperに変更するようにした。 2014-12-14
//         問題なくなっていたので、コメントアウトした 2016-05-13
//         日本語があるとUTF-8、ないとShift-JISで保存されるようである。
var serializer = new XMLSerializer();
 var xmlString = serializer.serializeToString(self.xml);
 setBlobUrl("download", xmlString);
  self.path.point = [];
 self.pathInit.point = [];
  $(self.xml).find("trkpt").each(function(i){
   var Lat = this.getAttribute("lat");
   var Lng = this.getAttribute("lon");
  var LatLng = new google.maps.LatLng(Lat,Lng);
  var date = new Date($(this).find("time").text());
  self.pathInit.point.push(LatLng);
  self.path.point[i]  = {
   latlng: LatLng,
   date: date,
  }
 });
    self.markerList = new google.maps.MVCArray();
 var layout = new GmLayout('map_canvas', self.path.point);
  self.map = new google.maps.Map(document.getElementById('map_canvas'),{
     zoom: layout.zoom,
     center: layout.center,
       mapTypeId: self.typeId
 });
  $(self.xml).find("trkpt").each(function(i){
  self.path.point[i].marker = new google.maps.Marker({
     position: self.path.point[i].latlng,
     map: self.map,
   icon: self.path.image
  });
  self.path.point[i].infobox = self.setInfobox (self.path.point[i]);
 });
 self.path.point[0].distance = 0;
 for ( i=1; i<self.path.point.length; i++) {
  self.path.point[i].distance = self.path.point[i-1].distance 
   + google.maps.geometry.spherical.computeDistanceBetween(self.path.point[i].latlng, self.path.point[i-1].latlng);
 }
 self.start.distance = self.path.point[0].distance;
 self.goal.distance = self.path.point[self.path.point.length-1].distance;
 self.start.index = 0;
 self.goal.index = self.path.point.length-1;
 var flightPath = new google.maps.Polyline({
       path: self.pathInit.point,
     strokeColor: self.pathInit.color,
     strokeOpacity: self.pathInit.opacity,
     strokeWeight: self.pathInit.weight
   });
  flightPath.setMap(self.map);
 $('#'+self.startButtonId).on( "click", function(){ 
  self.distanceClip = $("#clip").val();
  self.clipStart();
 });
 $('#'+self.goalButtonId).on( "click", function(){ 
  self.distanceClip = $("#clip").val();
  self.clipGoal();
 });
}描画(GpxClip.prototype.draw)
gpxファイルをGoogle Map 上に描画する。GpxClip.prototype.draw = function (){
 var self = this;
 // トラックポイントのマークを表示
 var trkptLength = $(self.xml).find("trkpt").length;
 for ( i=self.start.index; i<self.goal.index+1; i++) {
    self.path.point[i].marker.setMap(self.map);
  self.markerList.push(self.path.point[i].marker);
  self.setMarkerInfo(self.path.point[i].marker, self.path.point[i].infobox) ;
 }
 // Startのマークを表示
 var i = self.start.index;
 var infobox = self.setInfobox (self.path.point[i]);
 infobox.setMap(self.map);
 self.markerList.push(infobox);
    var mark = new GMOverlayHtml({
  map: self.map, 
  position: self.path.point[i].latlng,
  offsetX: self.start.mark.offsetX,
  offsetY: self.start.mark.offsetY,
  content: self.start.mark.content
 });
 mark.setMap(self.map);
 self.markerList.push(mark);
   var min = self.path.point[i].date.getTime() % self.period;
   var nextTime = (self.path.point[i].date.getTime() - min) + self.period;
 for ( i = 0; i < self.path.point.length; i++ ) {
    if(self.path.point[i].date.getTime() > nextTime){
   var infobox = self.setInfobox (self.path.point[i]);
   infobox.setMap(self.map);
     var min = self.path.point[i].date.getTime() % self.period;
     var nextTime = (self.path.point[i].date.getTime() - min) + self.period;
    }
 }
 // Goalのマークを表示
 var i = self.goal.index;
 var infobox = self.setInfobox (self.path.point[i]);
 infobox.setMap(self.map);
 self.markerList.push(infobox);
    var mark = new GMOverlayHtml({
  map: self.map, 
  position: self.path.point[i].latlng,
  offsetX: self.goal.mark.offsetX,
  offsetY: self.goal.mark.offsetY,
  content: self.goal.mark.content
 });
 mark.setMap(self.map);
 self.markerList.push(mark);
}その他のプロトタイプ
GpxClip.prototype.setInfobox = function (point) {
 var self = this;
 var date = point.date;
 var $content = $('<p></p>').css({
   fontSize: self.timeFontSize
  }).text(
   date.getHours() + ':' + date.getMinutesString()
  );
 var infobox = new GMOverlayHtml({
  map: self.map, 
  position: point.latlng,
  offsetX: 0,
  offsetY: -16,
  content: $content[0].outerHTML
 });
 return infobox;
}
// gpxの各点に時刻を表示(mouseover時)
GpxClip.prototype.setMarkerInfo = function (marker, infobox) {
 var self = this;
   google.maps.event.addListener(marker, 'mouseover', function() {
  infobox.setMap(self.map);
  self.markerList.push(infobox);
   });
   google.maps.event.addListener(marker, 'mouseout', function() {
    infobox.setMap(null);
   });
}
GpxClip.prototype.clipStart = function(){
 var self = this;
 var cnt=0;
  $(self.xml).find("trkpt").each(function(i){
  var distance = self.path.point[i+self.start.index].distance-self.start.distance;
  if ( distance < self.distanceClip ) {
   $(this).remove();
   cnt++;
  } 
 });
 self.start.index = self.start.index + cnt;
 self.start.distance = self.path.point[self.start.index].distance;
 var serializer = new XMLSerializer();
 var xmlString = serializer.serializeToString(self.xml);
 setBlobUrl("download", xmlString);
 self.markerList.forEach(function(marker, idx) {
      marker.setMap(null);
    });
 self.draw();
}
GpxClip.prototype.clipGoal = function(){
 var self = this;
 var cnt=0;
  $(self.xml).find("trkpt").each(function(i){
  var distance = self.goal.distance-self.path.point[i+self.start.index].distance;
  if ( distance < self.distanceClip ) {
   $(this).remove();
   cnt++;
  }
 });
 self.goal.index = self.goal.index - cnt;
 self.goal.distance = self.path.point[self.goal.index].distance;
 var serializer = new XMLSerializer();
 var xmlString = serializer.serializeToString(self.xml);
 setBlobUrl("download", xmlString);
 self.markerList.forEach(function(marker, idx) {
      marker.setMap(null);
    });
 self.draw();
}Google Maps API のzoom, center を適当な値に設定する関数〔リンク〕
10-13行目:Math.max.apply(null,配列), Math.min.apply(null,配列) は配列の中から最大値、最小値を求める関数。
20-21行目:360°が 256×2zoom に対応していることを利用している〔リンク〕。
function gmLayout(arrLatLng){
 var arrLat = new Array() ;
 var arrLng = new Array() ;
 for ( i = 0; i < arrLatLng.length; i++ ) {
  arrLat[i] = arrLatLng[i].lat();
  arrLng[i] = arrLatLng[i].lng();
 }
 var maxLat = Math.max.apply(null,arrLat);
 var maxLng = Math.max.apply(null,arrLng);
 var minLat = Math.min.apply(null,arrLat);
 var minLng = Math.min.apply(null,arrLng);
 var ctrLat = (maxLat+minLat)/2;
 var ctrLng = (maxLng+minLng)/2;
 var rangeLat = maxLat-minLat;
 var rangeLng = maxLng-minLng;
 var zoomLat = Math.floor( Math.log(360/rangeLat*$(document).height()/256) / Math.log(2) );
 var zoomLng = Math.floor( Math.log(360/rangeLng*$(document).width()/256) / Math.log(2) );
 this.zoom = Math.min ( zoomLat, zoomLng );
 this.center = new google.maps.LatLng(ctrLat,ctrLng);
}
分を2桁の文字列に変換するプロトタイプ〔リンク〕
Date.prototype.getMinutesString = function(){
 var minutes = this.getMinutes();
 if ( minutes < 10 ) {
  minutes = "0" + minutes;
 }
 return minutes;
}
以上
