2013-10-28

Google Maps API:gpxファイルを描画&編集する

同じドメイン(ここでは同じフォルダ)にあるgpxファイル(拡張子はxml)を読み込む。
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行目:ファイルを読み込んだ後に実行するスクリプト
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel=icon href=../_img/BlueSky_favicon.png sizes="16x16" type="image/png">
  6. <title>gpxClipper</title>
  7. <script src="http://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=false"></script>
  8. <script src="jquery.js"></script>
  9. <script src="gpxClipperTest.js"></script>
  10. <script src="gmOverlayHtml.js"></script>
  11. <!--
  12. http://www.atmarkit.co.jp/ait/articles/1112/16/news135.html
  13.  
  14. -->
  15. <script>
  16.  
  17. $(function() {
  18.  
  19. url_gpx = "sample.xml";
  20. // XMLファイル読み込み開始
  21. $.ajax({
  22. url: url_gpx,
  23. cache:false,
  24. dataType:"xml",
  25. error: function(XMLHttpRequest, textStatus, errorThrown){
  26. alert('読み込みエラー!');
  27. },
  28. success:function(data){
  29. var gpxclip = new GpxClip({
  30. xml: data,
  31. // typeId: google.maps.MapTypeId.HYBRID,
  32. // pathInit: { weight: 1 },
  33. startButtonId: "start",
  34. goalButtonId: "goal"
  35. });
  36. gpxclip.init();
  37. gpxclip.draw();
  38. }
  39. });
  40.  
  41. });
  42.  
  43. </script></head>
  44. <style type="text/css">
  45. * {
  46. margin: 0;
  47. padding: 0;
  48. }
  49.  
  50. html, body {
  51. height: 100%;
  52. }
  53.  
  54. body {
  55. overflow: hidden;
  56. background-color: ivory;
  57. }
  58.  
  59. p {
  60. background-color: #99cc00
  61. }
  62.  
  63. #map_canvas {
  64. height: 100%;
  65. }
  66. </style>
  67.  
  68. <body>
  69. <input id="start" type="button" value="Startの周辺削除" onclick="gpx_clip(LatLngStart)" style="width:100px" >
  70. <input id="goal" type="button" value="Goalの周辺削除" onclick="gpx_clip(LatLngGoal)" style="width:100px" >
  71. <span>半径</span>
  72. <input type="text" id="clip" value="100" size="3">
  73. <span>(m) 以内の点を削除</span>
  74. <div id="map_canvas"></div>
  75. </body>
  76. </html>

◆コンストラクタ(GpxClip)

optionsとdefaultsにより、プロパティを設定する。
関数(メソッド)はprototypeで設定する。
  1. var GpxClip = function(options) { // ver.2.1
  2.  
  3. var defaults = {
  4. typeId: google.maps.MapTypeId.ROADMAP, // マップタイプ
  5. timeFontSize: "16px", // 時間表示のフォントサイズ
  6. period: 30, // 30分ごとに時間を表示
  7. pathInit: { // 読み込んだxmlのトラックポイント
  8. color: "Blue", // 線の色
  9. opacity: 0.5, // 線の透過度
  10. weight: 5 // 線の幅
  11. },
  12. path: { // クリップしたトラックポイント
  13. image: new google.maps.MarkerImage( // Markerのイメージ
  14. 'images/point.png', // marker image
  15. new google.maps.Size(16,16), // marker size
  16. new google.maps.Point(0,0), // marker origin
  17. new google.maps.Point(8,8) // marker anchor
  18. )
  19. },
  20. start: { // スタートポイント
  21. mark: {
  22. offsetX: -64,
  23. offsetY: -48,
  24. content: '<img src="images/start.png">'
  25. }
  26. },
  27. goal: { // ゴールポイント
  28. mark: {
  29. offsetX: 0,
  30. offsetY: -48,
  31. content: '<img src="images/goal.png">'
  32. }
  33. }
  34. };
  35. $.extend(true, this, defaults, options); // デフォルトオプションと渡されたオプションのマージ
  36.  
  37. this.period = this.period*60*1000; // 分→msに変換
  38.  
  39. };


初期処理(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行:ボタンの設定。
  1. GpxClip.prototype.init = function(){
  2.  
  3. var self = this;
  4.  
  5. // $(self.xml).find("name").each(function(){
  6. // $(this).text("gpxClipper");
  7. // });
  8. // <name>タグのテキストが日本語の場合エラーがでるので、すべてgpxClipperに変更するようにした。 2014-12-14
  9. // 問題なくなっていたので、コメントアウトした 2016-05-13
  10. // 日本語があるとUTF-8、ないとShift-JISで保存されるようである。
  11. var serializer = new XMLSerializer();
  12. var xmlString = serializer.serializeToString(self.xml);
  13. setBlobUrl("download", xmlString);
  14.  
  15. self.path.point = [];
  16. self.pathInit.point = [];
  17.  
  18. $(self.xml).find("trkpt").each(function(i){
  19. var Lat = this.getAttribute("lat");
  20. var Lng = this.getAttribute("lon");
  21. var LatLng = new google.maps.LatLng(Lat,Lng);
  22. var date = new Date($(this).find("time").text());
  23. self.pathInit.point.push(LatLng);
  24. self.path.point[i] = {
  25. latlng: LatLng,
  26. date: date,
  27. }
  28. });
  29.  
  30. self.markerList = new google.maps.MVCArray();
  31.  
  32. var layout = new GmLayout('map_canvas', self.path.point);
  33. self.map = new google.maps.Map(document.getElementById('map_canvas'),{
  34. zoom: layout.zoom,
  35. center: layout.center,
  36. mapTypeId: self.typeId
  37. });
  38.  
  39. $(self.xml).find("trkpt").each(function(i){
  40. self.path.point[i].marker = new google.maps.Marker({
  41. position: self.path.point[i].latlng,
  42. map: self.map,
  43. icon: self.path.image
  44. });
  45. self.path.point[i].infobox = self.setInfobox (self.path.point[i]);
  46. });
  47.  
  48. self.path.point[0].distance = 0;
  49. for ( i=1; i<self.path.point.length; i++) {
  50. self.path.point[i].distance = self.path.point[i-1].distance
  51. + google.maps.geometry.spherical.computeDistanceBetween(self.path.point[i].latlng, self.path.point[i-1].latlng);
  52. }
  53.  
  54. self.start.distance = self.path.point[0].distance;
  55. self.goal.distance = self.path.point[self.path.point.length-1].distance;
  56.  
  57. self.start.index = 0;
  58. self.goal.index = self.path.point.length-1;
  59.  
  60. var flightPath = new google.maps.Polyline({
  61. path: self.pathInit.point,
  62. strokeColor: self.pathInit.color,
  63. strokeOpacity: self.pathInit.opacity,
  64. strokeWeight: self.pathInit.weight
  65. });
  66. flightPath.setMap(self.map);
  67.  
  68. $('#'+self.startButtonId).on( "click", function(){
  69. self.distanceClip = $("#clip").val();
  70. self.clipStart();
  71. });
  72. $('#'+self.goalButtonId).on( "click", function(){
  73. self.distanceClip = $("#clip").val();
  74. self.clipGoal();
  75. });
  76.  
  77. }


描画(GpxClip.prototype.draw)

gpxファイルをGoogle Map 上に描画する。
  1. GpxClip.prototype.draw = function (){
  2.  
  3. var self = this;
  4.  
  5. // トラックポイントのマークを表示
  6. var trkptLength = $(self.xml).find("trkpt").length;
  7. for ( i=self.start.index; i<self.goal.index+1; i++) {
  8. self.path.point[i].marker.setMap(self.map);
  9. self.markerList.push(self.path.point[i].marker);
  10. self.setMarkerInfo(self.path.point[i].marker, self.path.point[i].infobox) ;
  11. }
  12.  
  13. // Startのマークを表示
  14. var i = self.start.index;
  15. var infobox = self.setInfobox (self.path.point[i]);
  16. infobox.setMap(self.map);
  17. self.markerList.push(infobox);
  18. var mark = new GMOverlayHtml({
  19. map: self.map,
  20. position: self.path.point[i].latlng,
  21. offsetX: self.start.mark.offsetX,
  22. offsetY: self.start.mark.offsetY,
  23. content: self.start.mark.content
  24. });
  25. mark.setMap(self.map);
  26. self.markerList.push(mark);
  27.  
  28. var min = self.path.point[i].date.getTime() % self.period;
  29. var nextTime = (self.path.point[i].date.getTime() - min) + self.period;
  30.  
  31. for ( i = 0; i < self.path.point.length; i++ ) {
  32. if(self.path.point[i].date.getTime() > nextTime){
  33. var infobox = self.setInfobox (self.path.point[i]);
  34. infobox.setMap(self.map);
  35. var min = self.path.point[i].date.getTime() % self.period;
  36. var nextTime = (self.path.point[i].date.getTime() - min) + self.period;
  37. }
  38. }
  39.  
  40. // Goalのマークを表示
  41. var i = self.goal.index;
  42. var infobox = self.setInfobox (self.path.point[i]);
  43. infobox.setMap(self.map);
  44. self.markerList.push(infobox);
  45. var mark = new GMOverlayHtml({
  46. map: self.map,
  47. position: self.path.point[i].latlng,
  48. offsetX: self.goal.mark.offsetX,
  49. offsetY: self.goal.mark.offsetY,
  50. content: self.goal.mark.content
  51. });
  52. mark.setMap(self.map);
  53. self.markerList.push(mark);
  54.  
  55. }


その他のプロトタイプ

  1. GpxClip.prototype.setInfobox = function (point) {
  2.  
  3. var self = this;
  4.  
  5. var date = point.date;
  6. var $content = $('<p></p>').css({
  7. fontSize: self.timeFontSize
  8. }).text(
  9. date.getHours() + ':' + date.getMinutesString()
  10. );
  11. var infobox = new GMOverlayHtml({
  12. map: self.map,
  13. position: point.latlng,
  14. offsetX: 0,
  15. offsetY: -16,
  16. content: $content[0].outerHTML
  17. });
  18.  
  19. return infobox;
  20.  
  21. }
  22.  
  23.  
  24. // gpxの各点に時刻を表示(mouseover時)
  25. GpxClip.prototype.setMarkerInfo = function (marker, infobox) {
  26. var self = this;
  27. google.maps.event.addListener(marker, 'mouseover', function() {
  28. infobox.setMap(self.map);
  29. self.markerList.push(infobox);
  30. });
  31. google.maps.event.addListener(marker, 'mouseout', function() {
  32. infobox.setMap(null);
  33. });
  34. }
  35.  
  36.  
  37. GpxClip.prototype.clipStart = function(){
  38.  
  39. var self = this;
  40.  
  41. var cnt=0;
  42. $(self.xml).find("trkpt").each(function(i){
  43. var distance = self.path.point[i+self.start.index].distance-self.start.distance;
  44. if ( distance < self.distanceClip ) {
  45. $(this).remove();
  46. cnt++;
  47. }
  48. });
  49. self.start.index = self.start.index + cnt;
  50. self.start.distance = self.path.point[self.start.index].distance;
  51.  
  52. var serializer = new XMLSerializer();
  53. var xmlString = serializer.serializeToString(self.xml);
  54. setBlobUrl("download", xmlString);
  55.  
  56. self.markerList.forEach(function(marker, idx) {
  57. marker.setMap(null);
  58. });
  59. self.draw();
  60.  
  61. }
  62.  
  63. GpxClip.prototype.clipGoal = function(){
  64.  
  65. var self = this;
  66.  
  67. var cnt=0;
  68. $(self.xml).find("trkpt").each(function(i){
  69. var distance = self.goal.distance-self.path.point[i+self.start.index].distance;
  70. if ( distance < self.distanceClip ) {
  71. $(this).remove();
  72. cnt++;
  73. }
  74. });
  75. self.goal.index = self.goal.index - cnt;
  76. self.goal.distance = self.path.point[self.goal.index].distance;
  77. var serializer = new XMLSerializer();
  78. var xmlString = serializer.serializeToString(self.xml);
  79. setBlobUrl("download", xmlString);
  80.  
  81. self.markerList.forEach(function(marker, idx) {
  82. marker.setMap(null);
  83. });
  84. self.draw();
  85.  
  86. }


Google Maps API のzoom, center を適当な値に設定する関数〔リンク〕


10-13行目:Math.max.apply(null,配列), Math.min.apply(null,配列) は配列の中から最大値、最小値を求める関数。
20-21行目:360°が 256×2zoom に対応していることを利用している〔リンク〕
  1. function gmLayout(arrLatLng){
  2.  
  3. var arrLat = new Array() ;
  4. var arrLng = new Array() ;
  5. for ( i = 0; i < arrLatLng.length; i++ ) {
  6. arrLat[i] = arrLatLng[i].lat();
  7. arrLng[i] = arrLatLng[i].lng();
  8. }
  9.  
  10. var maxLat = Math.max.apply(null,arrLat);
  11. var maxLng = Math.max.apply(null,arrLng);
  12. var minLat = Math.min.apply(null,arrLat);
  13. var minLng = Math.min.apply(null,arrLng);
  14.  
  15. var ctrLat = (maxLat+minLat)/2;
  16. var ctrLng = (maxLng+minLng)/2;
  17.  
  18. var rangeLat = maxLat-minLat;
  19. var rangeLng = maxLng-minLng;
  20. var zoomLat = Math.floor( Math.log(360/rangeLat*$(document).height()/256) / Math.log(2) );
  21. var zoomLng = Math.floor( Math.log(360/rangeLng*$(document).width()/256) / Math.log(2) );
  22.  
  23. this.zoom = Math.min ( zoomLat, zoomLng );
  24. this.center = new google.maps.LatLng(ctrLat,ctrLng);
  25.  
  26. }


分を2桁の文字列に変換するプロトタイプ〔リンク〕

  1. Date.prototype.getMinutesString = function(){
  2. var minutes = this.getMinutes();
  3. if ( minutes < 10 ) {
  4. minutes = "0" + minutes;
  5. }
  6. return minutes;
  7. }





以上