古事連記帖

趣味のこと、技術的なこと、適当につらつら書きます。

Blueskyに登録したらドメインがほしくなって手に入れた話

2023年、しばらくTwitter一強*1だったミニブログ界隈が突然、Twitterの混乱などにより空前のミニブログ戦国時代がおおよそ15年ぶりくらいに再勃発したような気がします。
戦国時代が始まる前あたりに登場したBlueskyに、先日知り合いから招待コードをいただいて登録することができました。

bsky.app
リンクは公式のWebクライアント


Blueskyは、自分を示すアカウント名として、「@username.bsky.social」の他、独自ドメインをあてることができるようになっているようです。
自身でもドメインの販売取り次ぎをおこなうようになったと報道されるくらいには割と推し気味な感じ。
ascii.jp

そういえば自分はいくつか独自ドメインを持ってはいますが、これまで「自分を示している」ドメインを持ったことがありませんでした。Blueskyで独自ドメインをアカウント名として使えるということで、せっかくなら…?と思い、これを機に自分を示す独自ドメインを取得しました。
せっかく自分の名前を独自ドメインにするなら「jp」にしようということで、レジストラを探しまわった感じでは総合的によさそうなConoHa Wingにしました*2
www.conoha.jp
アイデンティティとしての独自ドメインなら年間3000円程度は安い方です。


せっかくドメイン取ったんだからということで、さらにプロフィールページも作りました。
aynv.jp

aynv.jp です。ハンドルネーム「綾野ちい」の略称として「アヤノフ」を多用していますが、そのときの「フ」はロシア人名風に「v」を採用しているのでドメイン名はこんな感じになりました。
ドメイン名が4文字で取れたのは割とうれしいです。トップレベルドメイン含めても、持っているドメインの中で一番短いものになりました。

このブログもこのドメインを使いたいところですが、はてなブログProの契約をどうするか含めて現在検討中…。

*1:独自の見解です

*2:同じGMOでも、お名前.comやValueDomainそれぞれで値段が違うのが謎

Value DomainからCloudflareへドメインを移管する(大雑把に)

メモ程度ですが…

昨年の末あたり、Value Domainから「サービス維持調整費」なるものを取りますよっていうお達しがきていました。
www.value-domain.com
長年Value Domainにはお世話になってきましたが、そろそろ別のところに移管するときかな…と思いつつ、どこにしようかなとうだうだしていたまま、このときまで放置していましたが、
ようやく重い腰を上げて、とりあえず今はほとんど使っていない休眠ドメインでお試ししようと思って、 id:tmyt から教えてもらった Cloudflare へ移管することに決めました。

Cloudflareへの登録とか、下準備とかは他の記事に任せます
ダッシュボードの右上に「サイトの追加」があるので、そこをクリックして、

移管したいドメイン名を入れます。まだ移管手続きはここではしません。

DNS情報を勝手に読み取ってくれますが、まあまあいい加減というか、抜けてるものがちょいちょいあるので、もとのDNS設定と見比べながら設定をあわせるといいと思います。

終わったら、ネームサーバーを変えてくださいと案内が出るので、Value Domain側でネームサーバーを置き換えます。

変更したら、Cloudflare側に確認をしてもらいます。いろいろオプションが出てきますが、僕は適当にやりました。Freeプランにするのを忘れずに。

Cloudflare側でネームサーバーの変更が確認されると、以下の感じでいいニュースを伝えてくれます。

そしたらいよいよ移管です。上記画面の右側の「Cloudflare に移管」を押すか、

ダッシュボードに戻って「ドメイン登録」から「ドメインの移管」を押すかしてください。

先ほど登録したサイトに関連づいたドメインの移管手続きができるようになります。今回は「.faith」ドメインを移管するので、ICANN料金含めると2023年3月11日時点では5.16ドル(おおよそ700円弱くらい)です。

ちなみに、Value Domainでの更新価格は、2023年3月11日現在で3,868円なので、5倍ほどの価格差です。よくわからなん…
www.value-domain.com

これ見たらなんで今まで…って思ってしまいましたが、仕方ないのでこのまま進めます。「ドメインを確認する」をクリックして次にいきます。
「承認コードをリクエストする」については、Value Domain側で「WHOIS」設定から拾うことができます。このとき、WHOIS代行が有効な場合失敗するようなので、忘れずに解除をしておきます。ただし、変更後60日以上経たないと移管はできなくなります。
下の方に「認証鍵情報」があるので、「パスワードを表示する」のチェックを入れて出てきたコードを、

Cloudflare側のテキストボックスに書き写します。

次に連絡先を入れます。ここで入れた情報はWHOISには出ないらしいです。

入力できたら「移管を確認して確定する」をクリックします。

うまくいけば、裏で決済がおこなわれ、移管手続きが完了します。

あとはレジストラー側での移管処理が終わり、GMOレジストラーの場合、承認依頼メールに応対すれば移管完了になるようです。eNomやKeySystemsの場合は何もしなくてよいとあるので、この移管がうまくいったら、eNomのドメインがあるのでそれでも試してみようと思います。
www.value-domain.com


移管完了まで、ドメイン一覧で「期限切れ」とでてくるのでちょっとビビりますが、たぶん大丈夫。

ドメインの管理画面に行けば移管の進捗状況が出てくるのでたぶん大丈夫。

というわけで、移管手続きしながらの記事書きでした。

レンズを借りてたら買った話

先日レンズをレンタルした話を書きました。
ayano.hateblo.jp

この記事を書いた後、立て続けにあと2本ほど借りていろいろと写真を撮ってまわっていました。
借りたレンズは以下。


www.sony.jp
同じ焦点距離同士でツァイスと G Master との比較をしたくて借りたもの


www.sony.jp
だんだんと買いたい気持ちが高まってきて、24mm (換算 36mm) に近いけどちょっと違う 20mm (換算 30mm) のをと思って選んだもの


G Master や G レンズにあって、最初に借りたツァイスのにはないもので、絞りリングの存在がありました。レンズを持つ手で絞り値を自在に操れるのは結構便利で、絞りリングがない場合本体をしっかり持っている手を少し持ち替えるような動作が若干しんどくなるなあと思ったりはしましたが、
とはいえ、あとから借りたレンズはちょっと重かったこと*1と、フルサイズ対応が故か結構大きめだったこともあって、おでかけのカバンに入れるにはちょっとかさばるなあという感じでした。

やはりこういうときはソニーストア ということで、予約を入れてソニーストア銀座へ行き、事情を説明しながらこれまで借りたレンズをもう一度手に取って試したり、35mm (換算 52.5mm) のレンズもいくつか試してみたりして、結局ファーストインプレッションとしてもよかった SEL24F18Z がベストチョイスという結論に至り、その場で購入を決意。

やっちまったなぁ
外箱

他にもソニーストアでは APS-C 用の 15mm (換算 22.5mm) G レンズも試させていただきましたが、思った以上に広角だったので、僕の撮りたい写真からすると扱いが限定されそうだなあと感じたりはしました。
www.sony.jp

35mm のレンズは次のステップとして楽しみに取っておこうと思っています(?


改めて、自分が所有するものとしての装着した姿を。ちゃんとレンズプロテクターも購入して装着済みです

マウントからまっすぐ伸びたフォルムがスッキリした印象でいい感じ
キットレンズとの比較


買って初めて撮った写真は、インドカレー屋のカレーでした

ターリー屋の


で、今回買ったレンズと、キットレンズでは写りはどうなるかなーと思って、同じ位置から撮影を試みてみましたが…

まずは SEL24F18Z F 値 1.8 で撮影しています
キットレンズの SEL18135 で 24mm になるように撮影。F 値は 4

そうでした。同じ撮影位置からだとキットレンズの方は最短撮影距離が 45cm なので近すぎて焦点が合わないのでした。

同じように寄れるレンズは既にマクロレンズを持っていますが、こちらは 30mm (換算 45mm) だったり、開放 F 値が 3.5 だったりなど性質が違うので、次の 35mm へのステップとしてうまいこと使い分けながら遊んでいこうと思っています。


今回買ったレンズは結構良いお値段したものの、撮ってて楽しいレンズ*2だったので、ドライボックスで眠ったままにならないようにこれからもいろいろ歩きまわって、このレンズの楽しさをもっと見つけていきたいところです。

      • -

いつもの Amazon アフィ置かせてください

以前キットレンズのために買ったこの CPL フィルターは、定期的にタイムセールにあわせて安くなるので、それを狙って改めて買おうと考えているところ

*1:SEL24F14GM は 445g で、SEL20F18G は 373g です。一方で SEL24F18Z は 225g と割と軽量

*2:とはいえ内浦を撮りまわっただけでしたが

レンズを借りたら気軽で楽しかった話

先日α6600を買った話をしました
ayano.hateblo.jp

キットレンズでも楽しいし、マクロレンズもまあまあ使える感じではありましたが、キットレンズは最低撮影距離が45cmとやや遠く、開放F値も高めなこと、マクロレンズとは違うレンズを試したいという思いがあったので、レンズを借りてみました。
レンズのレンタルサービスは結構いろいろ存在していますが、今回は見てる中で気軽に使えそうだった APEX RENTALS を選択しました。

www.apex106.com

そして今回借りたレンズは Sonnar T* E 24mm F1.8 ZA (SEL24F18Z) です。ほどよい焦点距離で開放F値が小さめでレンタルしやすい価格のを探して行き着いた感じです。2泊3日で4480円と、ラベリングがあるレンズとしては手頃でした。

www.sony.jp
www.apex106.com


APS-C専用レンズででラベリングがあるレンズってなかなかないですね。他だと15mmのがGレンズとしてある程度でしょうか。フルサイズだと同じくらいの焦点距離・開放F値のものはG Masterのがありますがめっちゃ高いです…。

だいたいのレンタルサービスは、貸し出し開始1日前に到着、貸し出し終了日の23:59までに配達受付を済ませるという、事実上1泊分多い形になっているようで、APEX RENTALSもその方式でした。ありがたい話です。おでかけするその日に届いても困りますからね。
借りた物がちゃんとあるかどうかを届いたときや返すときにチェックするシートが入っていました。貸す方借りる方双方で確認するのはとてもだいじです。

借りたのが届いたのでつけてみたときの

というわけで借りたレンズで沼津市周辺を巡って写真を撮ってきましたのでいくつか流します。

沼津内浦いけすや 最低撮影距離が短めなのがありがたい
淡島への桟橋近くから撮った富士山
淡島ホテルの裏手を貫くトンネル
あわしまマリンパークの桟橋。この日は波の関係で閉鎖していました
あわしまマリンパークの桟橋から見た富士山と連絡船
あわしまマリンパーク水族館のウニ骨格を使ったランプ。どれもきれいでしたね…
静岡といえば さわやかです!
最後に富士宮市スーパー銭湯

撮った感じ、初めて開放F値が3.5より小さい1.8を扱った*1ので、開放値で撮影しようとするとピントあわせが大変な上、その場で確認できるのが小さいモニターだけなので、帰ってからPCに取り込んだ際に実は意図しない位置にピントが合っていたものが数多くあったくらいには難しいなと感じました。バシッと決まると周辺のボケがすごくきれいで楽しいです。F値をうまく操れるようになりたいなーと思えるレンズでした。
他にも、24mmしか扱えないっていうのがいい制約になってるというか、良さそうなところで何も考えずに撮影できるのが楽しいし気軽でよかったです。

初めて扱うがゆえに慣れるまでが大変だったため、もう少しいろいろ撮影できれば…とは思うところはあります。前日から借りれてたのを利用してリハーサルをしておけばよかったかなと。まあそもそもカメラ本体の扱いも慣れてないので…w

楽しいレンズでもっと扱いたかったですが、残念ながら返す日になってしまったので返却。返す際にもチェックシートに借りた物がなくなってないかを確認して、梱包に詰めて送り返します。
ヤマト運輸の送り状が入っていましたが、自分で送り状を作り直して*2おきました。


ちょうどきょう返却の発送をしたばかりなので、今この記事を書いている時点では無事に到着して返却が済むことを祈るフェーズではありますが、いいレンズ、評価の高いレンズは総じて高いものが多い*3のと、店舗の外で自分の好きな時間に好きなだけ使い倒せるという点では、レンタルという選択肢はすごくアリだと思うし、あれもこれも試せるがゆえに沼へ一直線に導かれるヤバさも感じました。別の焦点距離を試したいとか、もっといいレンズはどうなんだろうとか既に思い始めてたりします。手軽ではありますが、安くないのでここぞというときに借りることになるので、普段から慣れておいて…というのがなかなかしづらいなとは感じます。長期レンタルできるプランやサービスもあるので、うまく使っていければなーと思います。もちろんいつかは所持したいです。今回借りたレンズも、新品で買うと10万円近くしますが、それくらい払っても充分満足できそうな感じがするものでした。

*1:Xperia 5 IIIの24mmレンズがF1.7なので実は扱っていた

*2:返す送料は借りた方が負担なので、できるだけ送料が掛からない方法にしたかったこと、返し方に指定が特にないのでできるワザでもあります

*3:4万円前後のいわゆる「撒き餌レンズ」で評価が高いものももちろんあります

α6600を買いました

カメラは激しく初心者です

先月から、急にミラーレス一眼がほしいって思い始めて、いろいろ家電量販店を見て回っていたんですが、最初はソニーだけに絞りつつ、他のメーカーのも触ったりしていました。
持った感触とか、シャッターボタンを半押し、全押ししたときの感じとか、いろいろ見て回って、結局しっくりくるのがソニーで、以下の機種にある程度絞ってました。

  • α6600
  • α7C

ソニー製品を買うんならソニーストアだよね ってことで、友人を連れて銀座のソニーストアへ。事前に予約していたのでスムーズに相談が始まり、
APS-Cだとレンズもコンパクトだし重くなくて、持ち歩きでスナップ写真を撮るにはすごく便利ですよーってことで、何も販売管理タグみたいな足かせがないフリーの状態*1で持ち比べて、カメラ初心者ということでまずはAPS-Cにしましょうーってなって、α6600をチョイス。キットレンズつきのやつにしました。

www.sony.jp

素人目にはこれだけでも充分楽しいんですが、最低撮影距離が45cmなので飯テロ椅子に座って机にあるものとかを撮りたいとき大変ということで、そこまで近づく必要はあるかとは思いつつ手頃だったマクロレンズもあわせて買いました。

www.sony.jp

今思えば別にマクロレンズじゃなくてもよかったかもしれないんですが、割と勢いに任せてたところはあります。


スマホ*2の写真も最近はすごい撮れるなって思ってはいましたが、このカメラで撮れる写真はどこか違うなって思うところが多くあって、今は休みの日に晴れてほしい、あちこち行きたいって気持ちがすごい高いです。ここまで世界が変わるんだなあと思ったりしています。


そして、今までできなかった*3RAW現像にもチャレンジしようと、最近Illustratorから乗り換えたAffinity DesignerのシリーズであるAffinity Photoを使ってRAW現像をはじめました。
今はちょうどAffinity V2シリーズになってて、僕もこれを機会にアップグレードしました。新しい方が現像後のやり直しができるので、なんかしっくりこないときに微調整しやすいです。

affinity.serif.com

RAW現像、割とフィーリングでぐりぐりいじってると「あっ、この瞬間好きだな」って思うところが出てくるので、楽しくてずっといじってます。時間が足りないくらい。

というわけで撮った写真を適当に投げておきます。初心者なのでいい加減なところはご容赦ください。全体的に暗い写真ばっかりw

お粗末様でした。

      • -

というわけでいつも通りAmazonアフィ置いておきます。

買ったカメラ

マクロレンズPLフィルターも買ってましたなんかのときに使えそうな小さな三脚

*1:家電量販店だとそれのせいで重さの比較が全然できないんですよね

*2:持ってるのは Xperia 5 III

*3:Xperia 5IIIにもRAW出力はありましたが、そこまで頑張る気持ちはなかったので…

地物ポリゴンデータで罠を踏んでた

逆ジオコーディング、ザックリ言うと緯度経度といった座標から住所を割り出す仕組みの第三弾みたいなノリです。少し間が空いてしまいましたが…
ayano.hateblo.jp
ayano.hateblo.jp

先日、F1日本グランプリを観に行くのにあわせて、高速道路の地形データを名古屋高速道路や東名阪・新名神の亀山のあたりまで作りまして、いざ現地で試していたところ、なかなか道路の情報が出てきませんでした。
自前のアプリでは、逆ジオコーディングを要求したときの位置情報をログとして記録するようにしていましたが、ちゃんとポリゴンの上で要求していたので何が悪かったのかがわからず、結局満足できないまま帰宅してきた訳ですが…

暗い色の点がアプリ上の位置情報。結構精度高く点が上に乗っています。写真は国土地理院地図の航空写真です。

いろいろ調べていく中で、MySQLの公式ドキュメントにこういう記述がありました。
dev.mysql.com

MultiPolygon にカットライン、突起、亀裂を含めることはできません。 MultiPolygon は通常の閉じた点集合です。

意味がちょっとわかりにくいですが、なんとなく心当たりがありました。地物の数を減らしたくて、作ったあと連続するものはできるだけ結合してひとつになるようにしていってましたが、このとき、ポリゴンの中に穴を作っていたのです。

①は単に道路をトレースしたときとか。②は例えば上下線が分離してるときとか。③は環状線とか。写真は国土地理院地図の航空写真です。

上記の②や③のパターンは通常作るのは難しいですが、結合したりすると作れたりします。

この状態でもQGISはgeojsonをエクスポートすることができます。

gist.github.com

これをクエリにして流し込んで、どう保持されるかというと…

MariaDB [expwy]> select gid,ST_AsText(geom) from ExpressWay;
+-----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| gid | ST_AsText(geom)
                                                                                          |
+-----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|   1 | MULTIPOLYGON(((139.97049799654377 35.43106573056407,139.98135690556836 35.43105054327872,139.981341718283 35.430716423001044,139.97052837111448 35.43082273399849,139.97049799654377 35.43106573056407))) |
|   2 | NULL
                                                                                          |
|   3 | NULL
                                                                                          |
+-----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.002 sec)

といった感じで、②と③が NULL になってしまいました。ポリゴンは穴あきにならないように作る必要があるということです。
頑張って結合していったポリゴンたちを分割する作業に追われてしまいましたが、早めに気がつけてよかったです…。現地に行く前にある程度テストしておけばよかったのですがw

オレオレ逆ジオコーディングサービスを作る その2

逆ジオコーディング、ザックリ言うと緯度経度といった座標から住所を割り出す仕組み、前回は必要な情報をデータベースに入れる前にクエリファイルを準備するところまでやりました。

ayano.hateblo.jp

今回は実際にAPIサーバーを実装して動かすところまでをやってみます。
前回しれっと書いていましたが、以下の環境で実装することを目標にします。

あらかじめVisual Studio 2022と、Dockerを構成するのでDocker Desktopを導入しておきます。
なお、ここから出てくるコード例では名前空間を省いています。お試しになりたい方はご注意ください。

今回はASP.NET Core Web APIプロジェクトを使います。

追加情報の画面では「Docker を有効にする」にチェックを入れます。Docker OSについてはとりあえずLinuxで。
HTTPS 用の構成」はお好みにあわせて…ですがだいたい入れたままの方がいいかと思います。
「OpenAPI サポートを有効にする」のチェックが入っていることを確認します。

プロジェクトを作成すると自動でコンテナが作成されます。実行ボタンが「Docker」になっているので、このまま押せばコンテナ上にビルドが展開され、SwaggerのAPIリファレンスページが出てきます。

では次にプロジェクトの準備をします。
作ったプロジェクトにNuGetで必要なパッケージをインストールします。

  • Microsoft.EntityFrameworkCore
  • Pomelo.EntityFrameworkCore.MySql
  • Pomelo.EntityFrameworkCore.Mysql.NetTopologySuite
    • たぶんこのパッケージを入れれば依存関係で勝手に他2つも入りそうです

次にデータベースから読み取るテーブルの定義を作ります。適当なところにコードファイルを追加します。クラス名およびファイル名は「DbAddress」としておきます。

using Microsoft.EntityFrameworkCore;
using NetTopologySuite.Geometries;
using System.ComponentModel.DataAnnotations.Schema;

public abstract class DbAddress
{
    [Column("gid")]
    public int Id { get; init; }
    [Column("pref_name")]
    public string? Prefecture { get; init; }
    [Column("gst_name")]
    public string? County { get; init; }
    [Column("css_name")]
    public string? City { get; init; }
    [Column("s_name")]
    public string? Aza { get; init; }
    [Column("geom")]
    public MultiPolygon? Geom { get; init; }
}

テーブル定義が抽象クラスになっているのには理由があって、同じテーブル定義は一対一である必要があるっぽく、都道府県ぜんぶ同じテーブル定義だとしてもそれぞれクラスを作らないといけなかったためです。
なので、この定義を継承した各都道府県のクラスをずらっと作ります。プロパティ名がテーブル名と一致していればOKです。

public class Tokyo : DbAddress { }

あんまし作ってもめんどくさいだけなので、とりあえずお試しに一個だけ東京都だけ用意します。
テーブル定義はできました。次にそれを良い感じのデータとして返すためのモデルクラスを作ります。「AddressModel」としておきました。

public record AddressModel
{
    /// <summary>
    /// 都道府県名
    /// </summary>
    public string? Prefecture { get; init; }
    /// <summary>
    /// 郡 (存在する場合) + 市区町村名
    /// </summary>
    public string? Municipal { get; init; }
    /// <summary>
    /// 大字名
    /// </summary>
    public string? Aza { get; init; }
}

最低限この3つだけで充分かと思います。郡と市区町村を混ぜて返すようにしているので、分けたい場合はもう一つ「County」とか作っておくとよいかもしれません。

さらに、これを含むレスポンスデータとして返すためのモデルクラスも作っておきます。「ResponseModel」と名付けます。
リクエストで受け取った緯度と経度をそのまま返すのと、複数の住所が取得できる可能性があるので*1IEnumerableとして列挙できるようにしておきました。

public class ResponseModel
{
    public double Latitude { get; init; }
    public double Longitude { get; init; }
    public IEnumerable<AddressModel> Addresses { get; init; }
}

次にデータベースとやりとりするクラスを作成します。Microsoft.EntityFrameworkCore.DbContextを継承した新しいクラスは「AddressDbContext」とでもしておきましょうか。

// null許容は一旦無効化
#nullable disable

using Microsoft.EntityFrameworkCore;

public class AddressDbContext : DbContext
{
    public AddressDbContext(DbContextOptions<AddressDbContext> options)
    : base(options)
    { 
    }

    public DbSet<Tokyo> Tokyo { get; init; }
}

続いては、いよいよAPIとしてコントローラーを作成します。一般的にはここで「新規スキャフォールディングアイテム」として追加することが多いですが、まだDockerでMariaDBを動かしたりしていないのと、だいたい何がしたいかはわかっているので、シンプルにクラスを作ります。
今回は「ReverseGeocodingController」としました。

「DbAddress」クラスで定義した「Geom」プロパティがここで活躍します。これを使って座標から必要な情報を受け取ります。
今回は大きく2つのメソッドを使うことになるかと思います。

まずは「Contains」メソッド。Listでも見覚えがあると思います。「特定の座標が範囲内に含まれているか」がわかります。図にするとこんな感じかなと

Containsの例

P1は範囲AとB両方に含まれているので、例えばP1を対象にして探索すると両方ともtrueになるので両方が結果として返ってきます。

public class Area
{
    public MultiPolygon Geom { get; init; }
}

DbSet<Area> area; // 範囲AとBが入っている状態と想定
NetTopologySuite.Geometries.Point p1 = new(35, 140);

var result = area.Where(w => w.Geom.Contains(p1));

// result: { A, B }

一方で、P2とP3はそれぞれAとBにしか含まれていないので、探索すると片方だけが結果として返ってきます。

NetTopologySuite.Geometries.Point p2 = new(36, 138);
NetTopologySuite.Geometries.Point p2 = new(37, 139);

var result2 = area.Where(w => w.Geom.Contains(p2));
var result3 = area.Where(w => w.Geom.Contains(p3));

// result2: { A }
// result3: { B }

ここでは記載してませんが、仮にどこにも含まれない点P4があるとしたら、結果は空です。


次に「Distance」メソッドです。こちらは「特定の座標との距離」がわかります。図にするとこんな感じかなと

Distanceの例

主に点でやりとりする際には便利かと思います。距離の近い何点かをピックアップするなどとか。
もちろんMultiPolygonでも使えます。この場合、完全に含まれる場合は結果はゼロで返ります。

public class Area
{
    public MultiPolygon Geom { get; init; }
}

DbSet<Area> area;
NetTopologySuite.Geometries.Point p1 = new(35, 140);
NetTopologySuite.Geometries.Point p2 = new(32, 141);
NetTopologySuite.Geometries.Point p2 = new(33, 139);

var result1 = area.Select(s => s.Geom.Distance(p1));
var result2 = p1.Distance(p2);
var result3 = p2.Distance(p3);

これらを踏まえて、コントローラーは以下のようにしました。

using Microsoft.AspNetCore.Mvc;
using NetTopologySuite.Geometries;

[ApiController]
[Route("geo")]
public class ReverseGeocodingController : ControllerBase
{
    AddressDbContext dbContext;

    public ReverseGeocodingController(AddressDbContext context)
    {
        this.dbContext = context;
    }

    [HttpGet]
    public ResponseModel Get(double latitude, double longitude)
    {
        var point = new Point(longitude, latitude);

        var result = this.dbContext.Tokyo
            .Where(w => w.Geom != null && w.Geom.Contains(point))
            .Select(s => new AddressModel
            {
                Prefecture = s.Prefecture,
                Municipal = s.County + s.City,
                Aza = s.Aza
            });

        return new ResponseModel
        {
            Latitude = latitude,
            Longitude = longitude,
            Addresses = result
        };
    }
}

ここまでできたら、まわりの整備をおこないます。
appsetings.json にデータベースへの接続のための設定を書き込んでおきます。

"ConnectionStrings": {
  "DbContext": "Server=db;Database=reverse_geocoding;User=[データベースにアクセスするためのユーザー名];Password=[パスワード];CharSet=utf8mb4;"
}

ユーザー名とパスワードはあとで別の設定でも使います

Program.cs に以下を追記します。どこでもいいですが、builderのインスタンスを使うのでそのあたりに書いておくといいと思います。

var mariaDbVersion = new MariaDbServerVersion(new Version(15, 1));
var connectionString = builder.Configuration.GetConnectionString("DbContext");

builder.Services.AddDbContext<AddressDbContext>(
    options => options.UseMySql(connectionString, mariaDbVersion,
            o => o.UseNetTopologySuite()
                .EnableRetryOnFailure()));


次に、MariaDBを扱うためにDocker Composeを使います。ざっくり言えば複数のコンテナを一元的に管理するためのツールです。
ソリューションエクスプローラーのプロジェクトを右クリックし、「追加」から「コンテナー オーケストレーターのサポート」を選びます。
出てきたウインドウのプルダウンは「Docker Compose」が選ばれたまま*2なので、そのままOKを押します。次に出るウインドウではターゲットOSはLinuxでOKです。

そうすると、ソリューションエクスプローラーには「docker-compose」という新しい項目が追加され、「docker-compose.yml」が用意されていますので、そこにMariaDBを使うよう追記します。

services:
  db:
    container_name: db
    image: mariadb:latest
    restart: always
    ports:
      - 3306:3306
    command:
      - mysqld
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --max_allowed_packet=1G
    environment:
      - MARIADB_ROOT_PASSWORD=[rootが使うパスワード]
      - MARIADB_DATABASE=reverse_geocoding
      - MARIADB_USER=[データベースにアクセスするためのユーザー名]
      - MARIADB_PASSWORD=[パスワード]
      - TZ=Asia/Tokyo
      - LANG=C.UTF-8
    volumes:
      - "./Sql/Init:/docker-entrypoint-initdb.d"

appsetings.json に記述したUserとPasswordはそれぞれMARIADB_USERとMARIADB_PASSWORDでも同じものを使うようにします。
volumesはコンテナにマウントしたいディレクトリとそのマウント先です。こうすることで、docker-compose側に置いたSql/Initディレクトリが、コンテナ側のdocker-entrypoint-initdb.dとリンクします。
そして、MariaDBはdocker-entrypoint-initdb.dディレクトリにあるsqlファイルを見てテーブルを自動で作成してくれます。

次に docker-compose プロジェクトにSqlディレクトリと、その下にInitディレクトリを作成します。
そこに、前回作った住所のクエリファイルを入れます。特に出力後のファイル名を指定していなければ、東京都のであれば「h27ka13.sql」になっているかと思います。ファイル名で特段問題になることはないので、このままでも、わかりやすく「Tokyo.sql」などにするのもどちらでもよいと思います*3

ここまで終わればあとは実行します。実行ボタンが「Docker Compose」になっていることを確認して押します。
そうするとDocker Desktop上で「dockercompose****」といったのができあがり、SwaggerのAPIリファレンスページが出てきます。
このときにデータベースの初期化作業がおこなわれますが、完了を待たずにもう動かせる状態になるので、Docker Desktopを開いてデータベースのコンテナを探してログを開いて、必要なのがすべてロードされているのを待った方がいいかもしれないです。


Table Op Msg_type Msg_text

reverse_geocoding.Tokyo analyze status OK

クエリファイルの末尾に「ANALYZE TABLE `Tokyo`;」が入っていたことで、終わったことを確認することができるのはちょっと便利でした。


ここまでできればもうあとは試すことができます。SwaggerのAPIリファレンスページに「ReverseGeocoding」の項目と「GET | /geo」が追加されているのがわかるかと思います。

右側の「Try it out」を押してから、パラメーター「latitude」「longitude」に座標を入れます。今回は東京都だけを追加しましたので、都内の座標をGoogleマップなどから拾ってきます。
座標はアドレスバーの「@」の右側「@35.6815922,139.7704263」や、マップ上の建物やピンがない道路などをクリックして中央下に表示される簡易情報の座標が使えます。
今回は東京駅のあたりを選んでみます。座標を入力したら、「Execute」をクリックします。

35.681074, 139.766607

そうするとレスポンスが返ってきます。うまくいけば、以下のように「東京都千代田区丸の内1丁目」が得られるかと思います。

{
  "latitude": 35.681074,
  "longitude": 139.766607,
  "addresses": [
    {
      "prefecture": "東京都",
      "municipal": "千代田区",
      "aza": "丸の内1丁目"
    }
  ]
}

やりましたね!できました。
前回自分で作った道路の情報も流し込んじゃいましょう。やることは同じです。データベースのテーブル定義から必要な情報を拾うための定義クラスを作ります。

public class DbRoadName
{
    [Column("gid")]
    public int Id { get; init; }

    [Column("road_name")]
    public string? RoadName { get; init; }

    [Column("road_num")]
    public string? RoadNumber { get; init; }

    [Column("popular_road_name")]
    public string? PopularRoadName { get; init; }

    [Column("geom")]
    public MultiPolygon? Geom { get; init; }
}

この定義とマッピングするDbContextはAddressDbContextに一緒に入れちゃおうと思います。なので、AddressDbContextクラスに以下のプロパティを追記します。

public DbSet<DbRoadName> ExpressWay { get; init; }

テーブル名とプロパティ名が一致していればOKです。今回はテーブル名がExpressWayなのでこうしています。
レスポンスとして返すデータモデルも追加します。

public class RoadModel
{
    public string Name { get; init; }
    public string RoadNumber { get; init; }
    public string? PopularName { get; init; }
}
public class ResponseModel
{
    // 追加
    public IEnumerable<RoadModel> Roads { get; init; }
}

コントローラーのGetメソッドにも、道路名を取得するためのロジックを記入します。住所と同じくポリゴンなのでContainsでだいたい良いかと思います。

public ResponseModel Get(double latitude, double longitude)
{
    // 追加
    var roadNames = this.dbContext.ExpressWay
        .Where(w => w.Geom != null && w.Geom.Contains(point))
        .Select(s => new RoadModel
        {
            Name = s.RoadName ?? string.Empty,
            PopularName = s.PopularRoadName ?? string.Empty,
            RoadNumber = s.RoadNumber ?? string.Empty
        });

    return new ResponseModel
    {
        Latitude = latitude,
        Longitude = longitude,
        Addresses = result,
        Roads = roadNames
    };

あとはdocker-composeプロジェクトの「Sql/Init」ディレクトリに作った道路情報が入ったクエリファイルを追加します。
既に実行済みだと2回目以降MariaDBの初期化処理が走らないので、docker-composeプロジェクトを「クリーン」してあげると、Composeされたコンテナが消し飛びます。これを利用して初期化して立て直します。
これで実行すると以下のように道路名も含まれた状態で返ってくるかと思います。

35.702681, 139.775734

高速道路の位置を外すと道路名が空配列になります。

35.702632, 139.776227

これで動くものがついにできました!
実行速度は、前回の記事と書いたこととちょっと変わってしまいますが、感覚としては最初の1回目はデータベースのロードとかもあって4秒弱掛かる印象ですが、それ以降はすぐに応答がくるので、ずっと動かしている分には問題なさそうな気はします。
今回東京都しか取っていないので、他の道府県を追加する際は、前回の記事に書いたとおりテーブル1個に寄せるか、都道府県単位のテーブルを用意して、まず座標から大まかな位置を特定してから必要なテーブルにアクセスする方式を採るとよいと思います。後者の方が更新しやすいかも。


最後に外のウェブサーバーに公開する方法です。Azureを使わず、ASP.NET Coreが動かせる環境にしたVPSでホストするようにしました。
こうなると結構面倒で、いろいろ試行錯誤した結果、ソリューションに配置したdocker-composeは一切使わず一度フォルダーに発行したものに手を加えてVPSにアップロードしました。
発行ウインドウを表示して、フォルダーを選びます。

これでできあがったもののルートディレクトリに「docker-compose.yml」を追加します。
中身は下記記事が参考になるかと思います。

hnys.jp

これにちょっと手を加えるとこんな感じ。

version: '3.4'

services:
  db:
    container_name: db
    image: mariadb:latest
    restart: always
    ports:
      - 3306:3306
    command:
      - mysqld
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --max_allowed_packet=1G
    environment:
      - MARIADB_ROOT_PASSWORD=[rootが使うパスワード]
      - MARIADB_DATABASE=reverse_geocoding
      - MARIADB_USER=[データベースにアクセスするためのユーザー名]
      - MARIADB_PASSWORD=[パスワード]
      - TZ=Asia/Tokyo
      - LANG=C.UTF-8
    volumes:
      - "./Sql/Init:/docker-entrypoint-initdb.d"

  myapp:
    container_name: myapp
    image: mcr.microsoft.com/dotnet/aspnet:6.0
    restart: always
    ports:
      - 5080:80
      - 5443:443
    environment:
      ConnectionStrings__DbContext: [appsettings.jsonに書いたのを上書きしたいときに使う。使わなければ削除]
    volumes:
      - ./app:/myapp
    entrypoint: ["dotnet", "/myapp/myapp.exe"]
    depends_on:
      - db

開発中に使ったのとちょっと違うのは、ASP.NETのコンテナを作る部分が具体的に書かれているところです。これは似たようなものは「docker-compose.override.yml」にも書かれています。
「myapp」は自分で作ったものにあわせます。

できあがったら、VPSにもろもろをアップロードします。
その後ターミナルからSSHなどでアクセスして、docker-compose.ymlのあるディレクトリに移動し「docker-compose up -d」すればコンテナが立ちあがって無事に起動するはずです。
あとはnginxでリバースプロキシを書いてあげれば外から見えるようになります。書き方などはこのMicrosoft Docsあたりが参考になるかと思います。

docs.microsoft.com

Program.csで条件式「app.Environment.IsDevelopment()」を外していなければ、発行後はSwaggerのAPIリファレンスが表示されないので、

http://[addr]/geo?latitude=35.702681&longitude=139.775734

でアクセスすれば結果が返ってくるかと思います。


がんばって端折ったつもりがかなり長くなってしまいました。最初は難しそうだなあと思って敬遠していた逆ジオコーディングも、情報さえ手に入ってしまえば割と面倒なことなく作れることがわかりました。
自分ですべてを賄うので、必要な情報を付加して扱いやすいものが作れるという利点がありますが、ちゃんと定期的にメンテナンスしないと、特に道路は生き物*4なのですぐに陳腐化してしまうところが難しいところかなと思います。
まあ自分で使うだけなら、その辺はあまり気にせずのんびり対応するなりすればいいかなーって思いました。

ちなみに、デジタル庁が「アドレス・ベース・レジストリ」として住所情報のデータベース化を進めています。現状は各住所を点座標で得られるのみにとどまっていますが、今後ポリゴンデータも整備されそうなので注視していこうと思います。

www.digital.go.jp

まああまりこういうのを自分で作らなきゃいけないシーンってあまりないとは思いますが、何かの参考になれば幸いです。
僕自身も、たまに作るサーバーアプリケーションの復習とか、今回Dockerを初めて使ってコンテナ化したりしたので、Dockerの扱い方とかがなんとなくわかった気がしたところが収穫でした。

最後に、このサービスを使った自前アプリのスクショを貼って終わりにします。

いつかはこれをストアに公開したいなーって(画像の状態はエミュレーターを使った上で一部加工しています)

*1:取り方次第

*2:たぶんこれしか選択肢がないはず

*3:テーブル名がDbContextのDbSetで指定した型名と一致していればOKです

*4:高速道路なら追加ばかりですが、一般道に手を出した場合は全国で頻繁に新設・統廃合が起きるのでメンテコストが尋常じゃないはず…