Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

GoogleのLocation History(Timeline)を可視化

Google Map に Timeline を見る機能がついていて、過去の位置情報の履歴をすべて参照することができることは有名だが、実はこのデータはダウンロードすることもできる。

support.google.com

昔、このデータを元に 1log(イチログ) へデータをインポートするアプリを書いた。

github.com

しかし、 1log は 10 年分の位置データのデータ量に耐えうることができず重くて利用不能になってしまったため、 1log で自分の過去の位置情報を見ることは諦めた。それならばと思い立ち、今度は自前でこのデータを可視化してみることにした。

Google から Takeout できるデータには、 Records.jsonSemantic Location History がある。(おそらく)前者が生データで、後者が Google 側で予測をした詳細データと思われる。ひとまず、簡単でデータ量も少ない Records.json を使ってみることにする。

地図情報は、ひとまず静的な可視化ができれば十分なので、 staticmap - Rust というクレートを使ってみた。まずは、経度緯度やズームなどを指定し、 StaticMapインスタンスを作成する。それを Visitor に渡してやる。簡単のため、 JSON のデシリアライザの deserialize_locations 内で直接地図を吐くことにした。

fn deserialize_locations<'de, D>(deserializer: D) -> Result<ConversionResult, D::Error>
where
    D: Deserializer<'de>,
{
    struct LocVisitor<'vis>(&'vis mut StaticMap);

    impl<'de, 'vis> Visitor<'de> for LocVisitor<'vis> {
        ..snip..
    }

    let mut map = StaticMapBuilder::default()
        // Tokyo
        .lat_center(35.586533759675454)
        .lon_center(139.6296830270391)
        .zoom(11)
        .width(10000)
        .height(10000)
        //.padding((10, 0))
        .build()
        .map_err(Error::custom)?;

    let visitor = LocVisitor(&mut map);
    let result = deserializer.deserialize_seq(visitor);
    map.save_png("line.png").map_err(Error::custom)?;
    result

LocVisitorStaticMap の参照を渡せるようにしたので、この中で出現する緯度経度をすべて plot する。古い情報は青色、新しい情報は赤色で描画するようにした。

        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
        where
            S: SeqAccess<'de>,
        {
            ..snip..

            while let Some(l) = seq.next_element::<quicktype::LocationRecord>()? {
                ..snip..
                if let (Some(lat), Some(lon)) = (l.latitude_e7, l.longitude_e7) {
                    let t = count as f64 / 2961678.0;
                    let color =
                        Color::new(false, (t * 255.0) as u8, 0, ((1.0 - t) * 255.0) as u8, 64);
                    let circle = CircleBuilder::default()
                        .lat_coordinate(lat as f64 / 10000000.0)
                        .lon_coordinate(lon as f64 / 10000000.0)
                        .radius(4.)
                        .color(color)
                        .build()
                        .map_err(Error::custom)?;
                    self.0.add_tool(circle);
                }
            }
        }

こんな雑な作りにはなったが、 --releaseコンパイルしてから実行完了までに約 35 秒。雑に書いても Rust は速くて良い。

結果はこんな感じ。まず、全体的に真っ赤なのは、おそらく古い順に描画しているせいで青い点が消されているものだと思われる。それに加えて、自転車に乗るようになってからスマートウォッチでの GPS 計測をしているが、 GPS 計測中の計測間隔が通常時の計測間隔より短いため、まばらにプロットされている青い点と比較して、最近の赤い点はすべて線になって繋がってしまっているのが要因だろう。

StaticMap(1)

出来上がった地図を眺めると、まず、久里浜港から金谷港へ青と赤の点が伸びていることがわかる。これは、東京湾フェリーを昔から(自転車に乗る前から)よく利用していたことを意味する。次に、画像の左側の方に、秦野から北側へ向かって伸びる赤い線がある。これはヤビツに登ったときのものだ。同じく、厚木市を囲むように赤い線があるが、こちらは宮ヶ瀬湖へ行ったときのものである。

hiratara.hatenadiary.jp

もう少し北側に目を向けると、都民の森~奥多摩湖を走ったときの記録が残っている。それとは別に、鉄道に沿って青い点を観測することができ、過去に何度か旅行などで使っていることが見受けられる。

StaticMap(2)

最後に、茨城方面を見てみよう。土浦市付近には、霞ヶ浦を一周して 1 りんりんロードを走った記録がきれいに残っている。埼玉方面には、江戸川を通って五霞町で露頭に迷って引き換えした痕跡が残っている。埼玉県内で輪になっているところは、 菓子工房オークウッド|埼玉県春日部市のケーキと焼き菓子のお店 へ行った 2 ときのものである。そして、全体的に青や赤の点として公共交通機関場に残っている点は、自転車ではなく交通機関で移動した痕跡であろう。

StaticMap(3)

この可視化はとても面白く満足しているのだが、自転車に乗る前の過去の記録がかなり蔑ろにされているように思える。その理由は、

  • ワークアウトの計測時と比較して GPS の計測間隔がまばらであり、点の数が少ないこと
  • 古い点は新しい点に上書きされて、隠されてしまっていること

の 2 点によるものであろう。後者は描画順をランダムにするなど、どうにでもなる気はするのだが、前者についてはなかなか難しい問題だ。 GPS の 1 点あたりの時間まで考慮をして重み付けする(観測間隔が長い場合に 1 点のウェイトを重くする)か、 Semantic Location History の方のデータを使えばまた違う結果が得られるのかもしれない。


  1. そして、ロングではなくショートコースだったこと。
  2. そして、激混みで店には入れなかった。

公衆 wi-fi に繋がらない

PC が 公衆 wifi に繋がらないことがある。始めは店の wifi の不調だと決めつけていたのだが、どうも普段家においてあるマシンを持ち出したときに、決まって接続に失敗している気がする。

ええい、負けてなるものかと30 分くらい格闘したところ、 DNS servers が 1.1.1.18.8.8.8 に設定されていることに気がつく。もちろん、 DNS server assignment は Automatic (DHCP) である。これはおかしい。とりあえず公衆 LAN に関しては、 SSID を known networks から一度削除することで接続できるようになったが、 DNS が元に戻らない。放っておくとまた接続不良を起こしそうだ。

調べてみると、 Windows 11 では手動で設定した DNS が残ってしまうようだ。何だこのアホなバグ・・・。

blog.jbs.co.jp

regedit でこれでもかと言わんばかりに片っ端から削除。 IPv6 も忘れてはならない。これで無事に、 DNS servers が元に戻った。家でもネットワークが不調なことがあったのだが、これで改善するだろう。

おちおち手動で DNS 設定もできない。なぜ人類はこんな欠陥品を使っているのだろうか。

ERROR: (gcloud.run.deploy) FAILED_PRECONDITION

gcloud run deploy したらエラーが出た。

X Building and deploying new service... Uploading sources.
  Γ£ô Creating Container Repository...
  Γ£ô Uploading sources...
  . Building Container...
  . Creating Revision...
  . Routing traffic...
  . Setting IAM Policy...
Deployment failed
ERROR: (gcloud.run.deploy) FAILED_PRECONDITION: failed precondition: due to quota restrictions, cannot run builds in this region. Please contact support

以下の問題を踏んだようだ。

www.googlecloudcommunity.com

gcloud components update --version 455.0.0 とダウングレードしろと書いてあるが、これもエラーになる。

Beginning update. This process may take several minutes.
ERROR: (gcloud.components.update)
You cannot perform this action because the Google Cloud CLI component manager
is disabled for this installation. You can run the following command
to achieve the same result for this installation:

apt で入れているのが良くないようで、以下に書いてあるダウングレード手順が必要だった。

cloud.google.com

sudo apt-get update && sudo apt-get install google-cloud-cli=455.0.0-0

ダウングレードをしたら、普通にデプロイできた。

libcuda.so.1 is not a symbolic link

Windows の WSL を使っていると度々遭遇するエラー。人類はいつまでこのエラーの回避に時間を使わなければならないのだろうか。

wsl /sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link

今回もググって issue を追っかけた上で、 mklink"C:\Program Files\WSL\lib\"シンボリックリンクを貼る方法で解決。 30 分くらい使ってしまった。

github.com

毎回、違う方法で解決している気がするのは、若干不安である。

ssh-agent を知らなかった

恥ずかしながら、 ssh-agentSSH agent forwarding というものを全く知らなかった。ファイルの秘密鍵の代わりに ssh-agent を使うことができ、さらに、 forwarding の設定をしておくと SSH 先でもローカルの SSH agent を使える。秘密鍵を踏み台ごとにばら撒く必要がない。

docs.github.com

さらに、 1Password にも ssh-agent の機能があり、 1Password に保存した SSH key を使えるようになる。

developer.1password.com

試しに Windows 環境で 1password の SSH agent 機能を有効化し、 PowerShell から SSH ログインするとうまく SSH agent を使うことができた。さらに .ssh/configForwardAgent yes を追加して、 VM から他のサーバへも 1password のキーを使ってログインできることを確認した。すごい。

後は、 WSL 上での利用と、 VSCode からの利用を試す。

Records.schema.json VS quicktype

Google の位置履歴の JSON のフォーマットは JSON Schema で定義されている。

github.com

JSON Schema があると quicktype.io で変換して構造体が作れて嬉しい。

quicktype.io

のだが、 source の定義が厳しい。

                "source": {
                    "type": "string",
                    "title": "Source",
                    "description": "Source (technology) that provided the location information for this record.\nCommon values are: `WIFI`, `CELL`, `GPS`, `UNKNOWN` (note: sometimes found in lowercase).",
                    "examples": [
                        "WIFI"
                    ],
                    "enum": [
                        "WIFI",
                        "wifi",
                        "CELL",
                        "cell",
                        "GPS",
                        "gps",
                        "UNKNOWN",
                        "unknown"
                    ]
                },

JSON Schema は名前を知っているだけのレベルなのだが、 "enum" として定義されていると読み取った。ただ、その enum の中身が酷くて、 WIFIwifi のように大文字小文字が混在している。歴史的な都合でそう記録されてしまっていることはあるかもしれないけど、大文字か小文字に統一して出力したり、 "enum" じゃなく文字列として定義したり、なんとかならなかったの?

これだけでもウッとなるのに、 Rust に変換したものを見ると目眩がしてくる。

#[derive(Serialize, Deserialize)]
pub enum Source {
    #[serde(rename = "CELL")]
    Cell,
    #[serde(rename = "GPS")]
    Gps,
    #[serde(rename = "cell")]
    SourceCell,
    #[serde(rename = "gps")]
    SourceGps,
    #[serde(rename = "unknown")]
    SourceUnknown,
    #[serde(rename = "wifi")]
    SourceWifi,
    #[serde(rename = "UNKNOWN")]
    Unknown,
    #[serde(rename = "WIFI")]
    Wifi,
}

CellSouceCell ってなにさ。小文字だと Source を接頭辞につけるルールなの? そもそも定義の順番どうしちゃった 1 のさ。


  1. 書き終わってから気がついた。要素名の辞書順か・・・。

VSCode で remote SSH 中に git fetch できない

できなくて悲しい。

Developing on Remote Machines using SSH and Visual Studio Code

Remote - SSH limitations If you clone a Git repository using SSH and your SSH key has a passphrase, VS Code's pull and sync features may hang when running remotely. Either use an SSH key without a passphrase, clone using HTTPS, or run git push from the command line to work around the issue.