Skip to content

misohena/my-location

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

過去に自分がいた位置をGPXファイルから求めるEmacs Lisp

何が出来る

M-x my-location-at-time の後に日時を入力するとその時刻にいた場所の緯度・経度を表示し、マップサービスのURLを生成して開きます。また、Emacs Lispから (my-location-latlng-at-time time) を評価すると緯度と経度が入ったconsセルが返ってきます。

写真の撮影日時情報と組み合わせてGPSを搭載していないカメラでも撮影場所を取得するために作りましたが、他にもアイデア次第で様々な活用方法があると思います。

事前の準備

GPXファイルの用意

過去の位置を記録したGPXファイル(GPSログ)が必要です。

すでにGPXファイルを沢山持っている場合

もしハイキングやサイクリングなど何らかの活動を趣味にしているならすでに沢山のGPXファイルを持っているかもしれません。もしそうならカスタマイズ変数 my-location-sources にそのパスを指定してください。

my-location-sources のデフォルト値は次のようになっています。

(setq my-location-sources
      '((:dir "~/my-location/%Y%m" :file-pattern "\\`%Y%m%d.*\\.gpx")))

:dir:file-pattern の文字列は format-time-string 関数を通した後使われます。%Yや%mの部分は求めたい日時の年や月等に置き換わります。

:file-pattern はファイルを探すための正規表現です。 :dir で指定したディレクトリ下にあるファイルの内、正規表現にマッチするファイルを全て読み込みます。例えば “\`%Y%m%d.*\.gpx” であれば、 “20211218_稲荷山.gpx” のようなファイルにマッチします。

Googleロケーション履歴のデータからGPXファイルを生成する

Android端末を持ち歩いていてロケーション履歴を有効にしているなら、既に沢山の位置情報がGoogle社のサーバに記録されているかもしれません。タイムラインにアクセスして確認してみましょう。もし過去の沢山の地点が表示されたなら、そのデータからGPXファイルを生成することができるかもしれません。

まずはロケーション履歴に関する全てのデータをjson形式でダウンロードする必要があります。指定した日のkmlでは時刻とポイントの正確な対応関係が分からないので面倒でも全てのデータをダウンロードする必要があります。タイムラインから歯車マークをクリックし、「全てのデータのコピーをダウンロード」を選択するとGoogleの個人情報を一括してダウンロードできるページにジャンプします。そこから他のサービスの選択を解除して、ロケーション履歴のデータだけをダウンロードしてください。やり方は今後変わるかもしれないのでそのときは調べてみてください。また、ダウンロードできるようになるまでにしばらくかかる可能性もあります。私の場合、夜寝る前にリクエストして起きたときにはダウンロードできるようになっていました。1時間程度で用意できたようです。

私の場合、 takeout-20211217T164337Z-001.zip というファイル名で80MB程度のzipファイルがダウンロードできました。

展開すると takeout-20211217T164337Z-001/Takeout/ロケーション履歴/ロケーション履歴.json というファイルができました。これがお目当てのファイルでAndroid端末から送られた全ての位置情報、とりわけ日時と緯度経度が入っています。

ただ、このファイルは 1.2GB というとんでもないサイズなのでEmacsで開くのもためらわれます。

そこでNode.jsで変換するスクリプトを作成しました。本リポジトリの google-location-history-to-gpx/ ディレクトリに入っているので、Node.jsを使えるようにして、そのディレクトリ下で npm install を実行して依存するパッケージをインストールしてください。 JSONStream というパッケージを使用します。

まずは enum-devices.js を使って除外する端末のデバイスタグを求めます。複数のAndroid端末を使用している場合、同時に別々の場所の位置情報が記録されている場合があります。私の場合自宅にタブレットを置きっぱなしにしているのでその位置情報も一緒に記録されています。それを除外しないと正しい位置が求められません。

  1. enum-devices.js 内の const inputFile = “…” の部分を書き替えて ロケーション履歴.json を指すようにする。
  2. コマンドラインから node enum-devices.js>devices.txt を実行してデバイス一覧をテキストファイルに保存する。
  3. devices.txtの内容を見て除外するタグ番号を集める。

タグ番号(tag:)の他に最初(firstTime:)や最後(lastTime:)に現れる日時、緯度経度の範囲(latMin:, latMax:, lngMin:, lngMax:)、記録回数(count:)も出力しているので、それを元にどのタグ番号がどの端末なのかを特定し除外するタグ番号を決めてください。

次に実際の変換作業に入ります。

  1. index.js 内の const inputFile = “…” の部分を書き替えて ロケーション履歴.json を指すようにする。
  2. index.js 内の const ignoreDevices = [ ... ] の部分に除外したい端末のタグ番号(整数値)をカンマ区切りで書く。
  3. コマンドラインから node index.js を実行する。

output/YYYYMM/YYYYMMDD.gpx という形式のファイル名で日毎のGPXファイルが出力されます。

GPXファイルが用意できたら、カスタマイズ変数 my-location-sources がGPXファイルを参照できるように修正してください。 output の中身をデフォルトの場所 ~/my-location/ へ移動するのでも構いません。

マップサービスのURLを設定する

必要ならカスタマイズ変数 my-location-map-url に使いたいマップサービスへのURLを設定してください。デフォルトはOpen Street Mapsを使うようになっています。

(setq my-location-map-url "https://www.openstreetmap.org/#map=17/{{{lat:%.6f}}}/{{{lng:%.6f}}}")

Emacs Lispコードを読み込む

my-location.el をEmacsが読み込める場所に置いてください。 (require 'my-location) で読み込みます。

使ってみる

インタラクティブなコマンド

M-x my-location-at-time と打つと過去の日時を聞いてきます。例えば 8/7 12:35 と入力すると、過去の一番近い 8月7日 12:35 の場所を探します。該当するGPXファイルをロードし、その中にその時刻の位置情報があるなら結果をエコーラインに表示します。同時にマップサービスのURLをブラウザで開きます。コマンドプレフィックスを付けると緯度経度をバッファに挿入します(マップは開きません)。

キャッシュのクリア方法

一度読み込んだGPXファイル内の情報はEmacs内の変数にキャッシュされます。クリアして再度読み込みたい場合は M-x my-location-clear を実行してください。

Emacs Lispからの利用

(my-location-latlng-at-time (encode-time (parse-time-string "2021-01-02 12:34:56"))) ;;=> (xx.xxxxxxxxxx . xxx.xxxxxxxx)

利用例

次のコードは写真(JPEG画像)の撮影場所を緯度経度で表示し、ブラウザでその場所を開きます。JPEG画像のExif情報から撮影日時とGPS情報を取得します。GPS情報が取得できなかった場合は my-location-latlng-at-time を使用して撮影日時から位置を割り出します。

(require 'my-location)
;; require code at end of http://misohena.jp/blog/2021-12-16-how-to-get-shooting-date-and-location-of-jpg-with-elisp.html
;; and exif.el
(defun my-photo-location (file)
  (interactive "fJPEG File: ")
  (let* ((exif (or (my-exif-parse-file file)
                   (error "No Exif Data")))
         (time (or (my-exif-date-time-original exif)
                   (error "No Exif.DateTimeOriginal")))
         (latlng (or (my-exif-latlng exif) ;;From GPS Info
                     (my-location-latlng-at-time time)))) ;From GPX File

    (when latlng
      (my-location-browse-map latlng)
      (message "%.6f,%.6f" (car latlng) (cdr latlng)))))

Emacs27以降に標準で入っているexif.elを使用していますが、それだけでは機能が十分ではないのでjpgファイルの撮影日時と撮影場所をEmacs Lispで取得する方法(exif.el) | Misohena Blogの最後に書かれているコード(my-exif-*)も使用しています。将来的にexif.elが改善されると良いのですが……。