
MIERUNEインターン #007 新延空也さん
MIERUNEでは、弊社への就業に興味がある方向けに、1週間程度のインターンを実施しております。このたび、2024年11月18日から22日までの5日間、新延さんにインターンへご参加いただきました。
本記事は新延さんご自身によるレポートになります。
自己紹介
筑波大学大学院人間総合科学学術院人間総合科学研究群情報学学位プログラム博士前期課程1年の新延(にいのべ)と申します。
大学では図書館情報学というニッチな学問分野を専攻しています。図書館情報学とは、書籍などの資料の効率的な保管、検索などの活用などをコンピュータが登場する前から研究していたような学問で、大学では司書を目指している人が多いといったほうがわかりやすいでしょうか。そのなかで、研究室は機械学習系のところに所属しています。
一方で、プログラムを書く前から地図に興味を持っていました。小さい頃には紙の道路地図を眺めたり、自由帳に空想地図を書いてみたり、少し前からはOpenStreetMapの編集を始めてみたり、大学でも所属とは異なる地学系の学科の講義でGISや地理学の講義を取ったりしていました。
MIERUNEさんのインターンへ参加するに至ったのは、OSC Tokyo 2024/Springへ行ったことがきっかけです。OSCに行った際、MIERUNEさんのブースでCTOの井口さんに、大学内の自動販売機を地図上に表示するサイト( https://itf-vendingmachine.vercel.app/ )をつくったという話をしたところ、おもしろいと言っていただいたことから、COOの桐本さんとカジュアル面談を経て、インターンにお邪魔させてもらうことになりました。
作ったもの
ひとことでいうと「近くにあるATMを検索できるWebサイト」です。

サイト: https://atm-map.pages.dev/
GitHub: https://github.com/eniehack/atm-map
主な機能として、テキストでATM名や銀行名からATMを絞り込めるほか、

近くにあるATMを直線距離順に表示することもできます(スクリーンショット左下)。また、地図上の水色の円は左下で設定できる何m圏内に対応しています。赤色になったアイコンが左下に表示されている地点名のどれかに対応します。

他にも、以下のような機能を実装しています。
クエリマクロ
ATMをclickすると営業しているかどうかが表示される
今営業中のATMに絞って検索する
ダークモード

クエリマクロ機能とはATMを持たない一部の銀行名を入力しても対応するATMがヒットするようする機能です。具体的にはこれはネット銀行が自前のATMを持たず、コンビニ系ATMでの取引が無料になっていることから、ネット銀行の名前を入力すると、対応するコンビニATMなどが勝手に検索されるような機能になります。またゆうちょ銀行も郵便局にはATMがあることから、この機能に対応しています。

インターンで取り組んだこと
1日目
1日目にやったことは以下の通りです:
社内研修資料を読む
アプリで使うデータを作る
はじめは社内研修資料を読んでいました。社内資料はGISの基礎(Point、Polygonとはなにか?)に始まり、QGISの入門資料、フロントエンド開発入門までと様々なものがありました。QGISの資料を読み、大学で受けたGISの講義を思い出しながらQGISでは商用のGISソフトウェアと遜色ないような様々な処理ができるんだなあということを知りました。
次にアプリで使うデータを作りました。まだ何を作るかは決まっていなかったものの、何らかの場所(ATMやコインランドリーなど)を地図で表示するという方向性は決まっていたので、OpenStreetMapからデータを取得して、加工して保存しておくところまでをその日の目標に作業を進めていました。まず、geofabrikというOSMのデータを加工して公開しているサイトからOSMのデータを取得しました。
OSMの地物に付いているタグからPointを抽出しデータを作ってみよう、ということで、まずはDuckDBでやってみました。
DuckDBのspartial拡張を使うことで、OSMのpbfフォーマットでできたデータをそのまま入力できることや、DuckDBがデータをSQLライクなクエリを書くことができることを井口さんから教わり、試してみました。SELECT * FROM "./japan-latest.osm.pbf"; などと入力すると、OSMのデータが出力されることが新鮮でした。
ここでは、ATMのデータとしてatm=yesまたはamenity=atmを持つ地物、コンビニにもATMはあるだろうということでshop=convenienceを持つ地物もそれぞれ取得しました。
DuckDBはかなり便利だったのですが、OSMのデータではPolygon(建物などの図形を伴ったデータ)は属性の取得が難しいことから、DuckDBではなくquackosmと呼ばれるツールを使ってみることにしました。これはコンビニはPoint(点)として表現されるのではなく、建物としてPolygonとして表現されることもあるので、Polygonが取得できないと正確にコンビニの数が取得できないためです。
quackosmは、OSMで使われているpbfファイルを、より地理空間情報業界で一般的に使われているgeoparquetなどのフォーマットに変換することができるものです。geoparquetへ変換することでPolygonの属性を取得しやすくなるとのことで、このquackosmを使いgeoparquetを生成し、parquetも扱うことのできるDuckDBで条件を満たす地物を絞り込み、GeoPackageへ書き出してQGISでデータを表示してみることになりました。
余談ですが、DuckDBでGeoPackageを出力する文がややこしくて1時間くらい時間を溶かしました。例えば、pbfからフィルタなどの何らかの処理を行った一時テーブルconvenienceから、格納されている地物のそれぞれbrand、name、opening_hoursというOSMのタグを取得し、GeoPackageとして書き込む際のクエリが以下になります。
COPY (select geometry AS geom, tags['brand'][1] AS brand, tags['name'][1] AS name, tags['opening_hours'][1] as opening
_hours from convenience) TO "./convenience_with_quackosm.gpkg" WITH (FORMAT 'GDAL', DRIVER 'GPKG');
この WITH 句に着目すると FORMAT 'GDAL', DRIVER 'GPKG' となっているのですが、 GDAL はフォーマットを変換するアプリケーションであり、 GPKG はGeoPackageのことであることから、この表現は逆じゃないかと思い込んでつまづきました。
2日目
2日目は以下のことを行いました:
制作するものを決める
データのクリーニング
技術選定
環境構築
Web上で地物を表示する
2日目は作るものを決めるところから始まりました。当時はATMのほか、コインパーキング、コインランドリーなどの地図をWeb上で作るというアイデアもありましたが、ATMのほうが扱う地点数が多いことから、ATMのマップを作ることにしました。
前日に生成したデータのクリーニングを行いました。1日目に作成したデータはPointやPolygonが混ざっており、扱いが難しいです。ATMやコンビニの地点データはPolygonでなくともPointとして扱っても問題がないことから、QGISを使い内部保証点(重心のようなもの)を求めることでPolygonをPointに変換しました。

続いて、技術選定です。ここでは、大きく分けて2つの選定を行いました。1つ目にどのようなフォーマットで地物のデータを配信するかと、2つ目にどのようなライブラリ、サービスを使って実装していくかについて考えました。
まず、データフォーマットについてですが、主にGeoPackageとGeoJSONの特徴をそれぞれ井口さんからうかがって決めました。GeoPackageは前日、QGIS、DuckDBやquackosmで利用していたものです。GeoPackageは内部がSQLiteであることから読み出しが速いものの、Webではあまり使われていないことを知りました。一方でGeoJSONは、JSONをベースとした規格であることから、Webとの親和性が高いものの、何の変哲もない単なるテキストのJSONに過ぎないので、読み出しがGeoPackageと比べると遅いという特徴があります。
これらの特徴から、特にWebとの親和性からGeoJSONを採用することとしました。
GeoJSONを使うには、これまで扱ってきたGeoPackageから変換する必要があります。変換するにはQGISに同梱されているGDALというアプリケーションにある ogr2ogr というコマンドラインアプリケーションを使いました。
ogr2ogr routes.geojson routes.gpkg
続いて、ライブラリやサービスの選定です。Web開発となると、React、Vue、Svelteなどいろいろなフレームワークがありますが、自分がSvelteに慣れていること、一方で最近リリースされたSvelte5を使って開発した経験があまりなかったことから、SvelteとSvelteKitを使って開発することにしました。また、この時点ではサーバーサイドでなにかを行なう可能性もあったため、Cloudflare Pagesにデプロイすることとしました。
さらに、Webで地図の描画となると、maplibre-gl-jsやleafletなどのライブラリを使うことになりますが、今回はちょうどその日に公開されたMIERUNEさんが手がけるsvelte-maplibre-glというmaplibre-gl-jsのラッパーを使って開発を進めることとしました。
その後、GitHubの設定、Cloudflareの設定を経て実際に開発を始めました。
2日目までにできたアプリケーションです。

最低限、コンビニの種類でフィルタできるように付けたプルダウンメニューと、地図を配置しています。
3日目
3日目は以下のことを行いました:
ジオコーディングの実装
headerの実装
MIERUNE BBQへの参加
この日の開発はジオコーディングの実装が大きく占めました。
ジオコーディングとは、何らかの文字列から位置を得ることだそうです。例えば、地図アプリであれば検索クエリや住所から位置を得ることが該当しそうです。
今回のアプリでは、クエリを文字列として入力し、あいまい検索によって地物を絞り込む機能を実装しました。当初金融機関をプルダウンメニューなどから選択することで検索しようと考えていましたが、OSMから地物を得ていることから、データの品質にばらつきがあることが想像されたため、あいまい検索で実装することにしました。
あいまい検索にはFuse.jsというライブラリを用いて実装しました。
他にもFuse.jsの機能を使ってOR検索ができることから、クエリマクロ機能も実装しました。
また、初日にはCloud Optimizedな地点データ配信を行おうという話を井口さんと話していました。そこでFlatGeobufという規格を紹介されました。しかし、いまの配信データサイズ的にはあまり恩恵が得られないのに対し、実装の難易度が高かったため、これに関しては実装を断念しました。
この日の最後には、地図上に要素を配置するにはCSSをどのように扱うかについてレクチャーしていただきました。具体的にはCSSで position: absoluteを使うとHTMLのどこに記述したとしても、思いの位置に固定することができるなどです。

レクチャーしていただいた内容をもとに、TailwindCSSを使ってheaderを作り、地図を全画面に表示するように実装しました。
この日の最後に撮ったスクリーンショットです。

検索窓が追加されました
headerが追加されました
地図が画面いっぱいに表示されるようになりました
夕方は、MIERUNEが運営する勉強会であるMIERUNE BBQに参加しました。
どれも面白い発表でしたが、特に個人的に印象に残った発表はFacade Patternの話です。というのも、インターンで作ったプログラムもかなりぐちゃぐちゃなコードになっているので、もっと精進して可読性の高いコードを書けるようになりたいなと思いました。
4日目
4日目は以下のことを行いました:
近傍検索の実装続き
データのクリーニング
営業時間でのフィルタリング機能の実装
まずは、近傍検索の実装の続きですが、前日の時点では最寄りのATM1件しか表示されていませんでした。そこで、複数件、さらに検索と組み合わせて近傍検索できるとうれしいよね、という話を井口さんとしていたので、とりあえず近傍のATM9件を表示できるように実装しました。
その他、近傍検索関連では大小あわせて以下の機能をこの日に実装しました:
ATMをクリックすると画面真ん中に遷移しpopupが開くように
Turf.jsのbufferを使い、近傍検索の範囲を円で見せられるように
MapLibreのExpressionを使い、近傍検索結果上位9件は色を分けて(赤色にして)表示するように
Turf.jsのdistanceを使い、近傍検索結果に現在位置からの直線距離も記載するように
午後からは、データのクリーニングをまず行いました。これはコンビニとして抽出されたデータとATMとして抽出されたデータが被っていると近傍検索に重複して表示されてしまうためです。さらに、15MBほどであったGeoJSONデータから不要なプロパティなどを削減したかったこともあります。
この作業はQGISを使ってレイヤの差分を取り(2つのレイヤの地物同士で引き算をする、と考えるとわかりやすいかと思います)、不要なプロパティの削除や地物の緯度経度の精度などを出力オプションを適切な値に設定することで、最適なデータを作成することができることを知りました。これでデータを2MBほど削減することができました。



データのクリーニングを終えたら、近傍検索に文字検索を組み合わせられるようにも実装しました。
また、機能として欲しかった営業時間でのフィルタリングですが、当初は事前に文字列をparseして構造化したデータを作り、フィルタリングのみを検索時に行おうかを思っていたのですが、ライブラリからそのようなデータを作ろうとすると時間がかかりそうでした。そのため、ライブラリにあった、与えた時刻が営業時間内であるかどうかを判定する機能で、検索実行時にparseから判定までを行うことにしました。
4日目までの進捗です。

井口さんに協力していただき、前日まではただの点だったものが、アイコンになりました。
検索窓の下に近傍検索のプルダウンメニューが追加されました。
5日目
最終日の5日目は以下のことを行いました:
cloud optimizedなタイル配信に対応してみる
ダークモードの実装
営業時間でのフィルタリング
成果発表会
最終日は優先度の高い機能の実装と小ネタの実装を行いました。
はじめにCloud Optimizedなタイル配信に関して井口さんから教えていただきました。今回は他のベクタタイルフォーマットと比較しつつ、PMTilesという規格を使いました。
PMTilesでは、必要なデータが大きな1ファイルにまとまっています。Rangeと呼ばれるHTTPヘッダを使いリクエストを送ることで、1ファイルの中にある必要なデータのみを取得するようになっています。HTTPのRangeヘッダは特殊なものではなく、分割ダウンロードなどにも使われているような比較的標準的なHTTPの機能を利用しているので、サーバ側に専用の実装が必ずとも必要ではないとのことです。
次に、ダークモードの実装です。TailwindCSSのダークモード機能と黒ベースの配色がなされているタイルを使い、ダークモードを実装しました。

夕方にはSlackにて発表会が行われ、この5日間で作ったアプリケーションについて発表し、5日間の成果を社員さんに向けて発表しました。

感想
会社について
オフィスはミーティングしている社員さんの声でにぎやかでした。それだけではなく、社員さん同士の雑談も聞こえてくるような明るい職場だと感じました。社員さん同士のコミュニケーションからは、年齢、入社時期や立場を気にしない、フラットな印象を受けました。
さらにキーボード沼にハマった社員さんが多かったです。社員さんが「脳の信号をコンピュータに入力するデバイスに4万は安い」と言ってたことや、井口さんからUS配列を布教されたこと、あまり見ないUK配列のキーボードがオフィスに置いてあったことが印象に残っています。
成果物・インターンについて
インターンで実際にアプリケーションを実装してみて、インターフェースを設計することの難しさを改めて感じました。途中で社員さんから指摘されていたのですが、それぞれの部品についてそれぞれ説明をしないと、うまく使いこなせないものになっていると自分でも感じています。地図を配置したWebサイトは全面に地図が出ることが多く、ユーザの視界を邪魔せずにわかりやすいインタフェースを設計するにはどうするべきか?というような地図を表示するアプリケーションならではのUI設計にはまた別の難しさがあるのではないかと勝手に思ったりもしました。
Web上の地図を実装することに関して、インターンを受けるまではmaplibre-gl-jsに関して初心者でしたが、maplibreを使った地物の装飾したり、expressionを使って地点の絞り込みを実装したりと、maplibre-gl-jsに関する知識を様々得ることができました。学んだ技術を持ち帰って、以前から作っていた自販機Mapにも生かしていきたいです。
謝辞
技術的なサポートをしてくださったメンターの井口さんのおかげで、ここまでプログラムを作ることができました。井口さん、ありがとうございました!CTOを1週間メンターとしてインターンできたのはとても豪華な経験でした。
また、はじめは5日間うまくやっていけるのか不安で緊張していたのですが、優しく話しかけてくださったり、食事へ連れていってくださったり、ボードゲーム会を開催してくださったりと、社員さん達の優しさでリラックスしてインターン期間を過ごすことができました。ありがとうございました!
新延さんのメンターをつとめました、CTOの@kanahiro_iguchiです。今回は私にとっても学びの多い5日間となりました。というのは、新延さんはOpenStreetMapでマッピング活動をされるなど既にジオ界隈に足を踏み入れていて、それに加えてWeb技術にも明るかったので、テーマである「作りたいもの」に対し高い目標を設定され、業界の最新技術を取り入れるなど様々な取り組みが出来たためです。今回、OSMの生データから大量のデータを抽出して活用する方法を学ばれたと思います。GISデータは量が多ければ多いほど面白いですよ!今後も引き続き便利な地図アプリケーションを開発していただければと思います。この度はご参加いただきありがとうございました!