待ち合わせがスムーズになるWebアプリを作った

find.cateiru.com

どういうもの?

  • 相手の位置が矢印と距離で表示します。
  • Waaaaay!のWebアプリ版と思ってもらえればイメージしやすいと思います。
  • Androidは動作未確認です。多分うまく動かなさそうです。

使い方

  • 待ち合わせを作成を押して新しく待ち合わせを作成します。
  • ID、もしくはURLを相手に共有します。相手は、トップページからIDを直接入力するかURLにアクセスします。
  • 相手の位置情報が共有され矢印が相手に向き距離が表示されます。

技術的な解説など

相手との距離を算出する

位置情報の取得には位置情報APIを使用しています。位置情報APIでは自身の端末の緯度経度を取得することができます。

自分と相手の緯度経度情報を使用して距離を求めます。計算は愚直に三平方を使ってしまいたいところですが、地球は楕円状であるため三平方だと距離が離れると結構な誤差が生じてしまいます。そのため、地球の形状を考慮したヒュベニの近似式という計算式を使用して緯度経度から絶対距離(メートル)を算出しています。

相手の向きがわかる機能

位置情報APIDeviceOrientation APIを使用しています。

向きがわかるようにするためにはまず自身が向いていいる方位がわかる必要があります。そのため、DeviceOrientation APIを使用してz座標を求めます。計算方法などは以下の記事を参考にしました。

one-it-thing.com

これだけではただのコンパスであるため、これにプラスして位置情報から向きを求めます。
JavascriptにはMath.atan2という便利なメソッドが用意されています。これを使用することで角度θが求められます。

// 緯度経度では開始位置が上であるため座標を90度回転する
const y = -(target.lon - current.lon);
const x = target.lat - current.lat;
const d = Math.atan2(y, x);

return d * (180 / Math.PI); // 弧度法を度数法に変換する

相手と通信する機能

位置情報を相手と共有するのは、socket.ioを使用しています。もともと、firebaseのリアルタイムリスナーをしようかと考えましたがfirebase上に位置情報を保存しておきたくないためsocket.ioを採用しました。

バックエンドはGAE上にデプロイされています(Websocketはフレキシブルモードであるため現在はポーリングで通信しています)。

さいごに

これは、もともと別のプロジェクトで使うものでしたが単体でも十分有用であったため単独アプリとして作成しました。ぜひご利用ください!
また、雑に作っているのでissueやPRいただけると嬉しいです!

github.com

寝る前の30分でSambaで管理しているNASをVPNから接続できるようにする

TL;DR

前提条件

  • Sambaがインストールされており、ローカル上でNASとしてすでに機能しているPC

やり方

1. Tailscaleを使用してVPNを構築する

これは、簡単です。Tailscaleにアクセスしてアカウントを作成します。その後、クライアントソフトをインストールしてログインすればVPNが使用できるようになります。便利。

tailscale.com

2. Tailscaleからsambaに繋げられるようにする

そのままでは、Sambaにつなぐことができなかったので、smb.confbind interfaces only = yesnoに変更します。
noにすることでSambaは0.0.0.0からリクエストを受け付けることができます。

yesにしてもinterfacesにアドレスを指定すれば行けるかと思いますがTailscaleがどのアドレスでリクエストしているのかわからなかったため今回はbind interface onlyを変更しています。

sudo vi /etc/samba/smb.conf  
bind interfaces only = no

PCを再起動します。

sudo reboot

3. つなげる

これでもう、VPNを通じてsmbでファイル共有ができます。アドレスはTailscaleで簡単にコピーすることができます。

ちなみに、このPCをVPNの出口ノードにすることでどこにいても自宅のIPを使用できるのでめちゃくちゃ便利です。

参考文献

SNSの世代が変わっていく

3月はよくTwitterでこのようなツイートを見かける

〇〇大学 △△学部です!よろしくおねがいします!!

#春から○大 #○大生と繋がりたい

これをみると懐かしくなる。自分も大学入学したときはそのようなツイートをして同じ大学に入学する人とつながっていた。

しかし、今年、このテンプレが結構変わってきていると感じる。

よかったらインスタ交換しませんか?
https://instagram.com
#春から○大 #○大生と繋がりたい

コミュニケーションツールがTwitterからInstagramに変わってきているのである。
いや、もうホントにびっくりしました。去年まではインスタのリンクやQRコードを貼っている「繋がりたい」ツイートは見なかったのに今年になってめちゃくちゃ見ます。
さらには、bioにインスタのリンクを貼っている人も増えました。体感数倍ほど。

ここが世代交代の境目か…って少し感動しました。

ちなみに、私の通っている大学は東京オタク大学と呼ばれているぐらいなので在校生のSNS率はツイッタラー10割インスタ0割ぐらいです(偏見です)。

長年付き添ったMacBookProが急にいってしまった

そう、あのときは突然だった。

私は、サポーターズの人と面談をするためにMacBookProを充電していた。最近は、Windows機で開発をしていたため充電は0%だった。
そして、Zoomで面談が始まる。充電はまだ24%ほどしか溜まっていない。
最初は順調だった。充電ステータスは確認していなかったが減ることはないだろうと外付けディスプレイを接続して2画面で喋っていた。

突然、黒い画面に自分の顔が映る。それと同時に「プツッ」という音がイヤホンから鳴り響いた。MacbookProが突然強制シャットダウンされてしまったのである。
ぼくは慌ててWindows機でZoomを開き、面談は続けることができた。

しかし、本当の戦いはこれからである……


というようなことが昨日(3/1)にありました。最初充電がなくなってしまったのか、、、と思っていたのですが、面談が終わったあと起動しようとしても立ち上がらず、SMCをリセットしてもうんともすんとも言わない状態となってしまいました。

そのため、自分ではもうなにもすることができないので諦めてAppleに問い合わせをし、Apple Storeに行ってきました(さっき)。で、色々みてもらった結果万策尽きてしまい結局修理に出してマザーボード交換するしか方法がないという結果になったしまいました(Appleの人によるとSMCがイカれてしまったっぽい)。
ですが、Apple Careはそもそも入ってなく買ってから2年以上過ぎているので修理に6~7万円かかると言われ、それなら直してまた違う部分が壊れたら元も子もないということでその場で新しくMacBookAirを買うことになりました。

MacBookProくんは2019年の10月ごろから大切にしていたものでこれのおかげでいまの自分があると言っても過言ではない代物ですごく悲しかったです(リサイクル出してもらいました)。

新しい、MacBookAirはM1のやつでMacBookProと性能は同じくらいというレビューをどこかで見たことがあったので選択しました。ちょうど学割もやっており、1万円引き+18000円のギフトカードももらえたのでまあ、ヨシですかね。

パソコンは急に逝くことがあるので本当に気をつけてください!!!!!!

Goのテストカバレッジ上げるのって難しくね?

Goのアプリケーションで、CodeCov使ってテストカバレッジとっているのだがカバレッジ上げるのにすごく大変なのでメモと言うか愚痴というか。
Goではエラーはタプルの返り値で帰ってきてnil判定で判別します。

このように:

hoge, err := sameHandle()
if err != nil {
  panic(err)
}

この仕様自体は気軽にかけてすごく好きなのですが、テストカバレッジを計測するときって行ごとに"そこはテストしたのか"っていうので計測しています。
そのため、上の仕様の場合はエラー時のテストも書かないとカバレッジ100%を目指せません。

エラーのときのテストを書くのは普通でなのですが、、、

if err := SetHoge("piyo"); err != nil {
  return err
}
if err := SetHoge("nya"); err != nil {
  return err
}

return nil

こんな処理をしていると仮定します(Datestoreの要素を連続で消すなど)。
そうするとSetHoge自体のテストは別で行っており、エラーのテストも書いていたとしてもここのテストでif内がテストされていない判定になるのでカバレッジがどうしても上がらなくなります。

まず書き方が悪いのかもしれないという可能性もありそう。

GoでFormFileがあるHandlerのテストを書く

はじめに

Goで、FormFileがあり画像ファイルを読み込むHandlerを書いたがテストでハマってしまったので備忘録として書く。

TL;DR

  • writer.Close()にdeferはつけない

本文

func HogeHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseMultipartForm(32 << 20); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    fileSrc, fileHeader, err := r.FormFile("upload")
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
 
    ...
}

このような、Handlerがあったとする。ParseMultipartFormとかFormFileは詳しくは説明しないがPost formから画像などのファイルを読取るやつである。

詳しく:

note.com

www.irohabook.com

さて、このテストを書く。 実際にhttptestでサーバを起動させ、POSTリクエストを送ることでテストする。

func server() *http.ServeMux {
    mux := http.NewServeMux()

    mux.HandleFunc("/", handler.HogeHandler)

    return mux
}

テストは、画像をos.Openで読み取り、multipartでまとめて送る。 以下のサイトを参考にした。

stackoverflow.com

const IMAGE_PATH= "logo.png"

func TestHoge(t *testing.T) {
    server := httptest.NewServer(handler)

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile("upload", IMAGE_PATH)
    require.NoError(t, err)
    f, err := os.Open(IMAGE_PATH)
    defer f.Close()
    require.NoError(t, err)
    _, err = io.Copy(part, f)
    require.NoError(t, err)
    writer.Close()

    resp, err := http.Post(server.URL+"/", writer.FormDataContentType(), body)
    require.NoError(t, err)
    require.Equal(t, resp.StatusCode, 200)
}

ここで大事なのがwriter.Close()を間違えてdefer writer.Close()と書かないことである。Postする前にcloseしないとEOFエラーがHandlerのParseMultipartFormで発生してしまう。

stackoverflow.com

自分はこれで数時間格闘したのでマジで気をつけてほしい。 実際、write.Close()の実装を見ると、

// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
func (w *Writer) Close() error {
    if w.lastpart != nil {
        if err := w.lastpart.close(); err != nil {
            return err
        }
        w.lastpart = nil
    }
    _, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
    return err
}

https://cs.opensource.google/go/go/+/master:src/mime/multipart/writer.go;l=168-179

Close finishes the multipart message and writes the trailing

Closeはマルチパートメッセージを終了し、末尾を書き込みます

確かに書いてある。が、しかし、これは命名で結構勘違いしそうなのでCloseWriteとかEndWriteなどの別の名前にしてほしいと思った。

だってほかのパッケージのCloseはdefer使って最後に実行するものがほとんどなんだもん。